248 lines
7.0 KiB
Go
248 lines
7.0 KiB
Go
|
|
package block
|
||
|
|
|
||
|
|
import (
|
||
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
||
|
|
"github.com/df-mc/dragonfly/server/item"
|
||
|
|
"github.com/df-mc/dragonfly/server/item/inventory"
|
||
|
|
"github.com/df-mc/dragonfly/server/item/recipe"
|
||
|
|
"github.com/df-mc/dragonfly/server/world"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/sound"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// brewer is a struct that may be embedded by blocks that can brew potions, such as brewing stands.
|
||
|
|
type brewer struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
|
||
|
|
viewers map[ContainerViewer]struct{}
|
||
|
|
inventory *inventory.Inventory
|
||
|
|
|
||
|
|
duration time.Duration
|
||
|
|
fuelAmount int32
|
||
|
|
fuelTotal int32
|
||
|
|
}
|
||
|
|
|
||
|
|
// newBrewer creates a new initialised brewer. The inventory is properly initialised.
|
||
|
|
func newBrewer() *brewer {
|
||
|
|
b := &brewer{viewers: make(map[ContainerViewer]struct{})}
|
||
|
|
b.inventory = inventory.New(5, func(slot int, _, item item.Stack) {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
for viewer := range b.viewers {
|
||
|
|
viewer.ViewSlotChange(slot, item)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
return b
|
||
|
|
}
|
||
|
|
|
||
|
|
// InsertItem ...
|
||
|
|
func (b *brewer) InsertItem(h Hopper, pos cube.Pos, tx *world.Tx) bool {
|
||
|
|
for sourceSlot, sourceStack := range h.inventory.Slots() {
|
||
|
|
var slot int
|
||
|
|
|
||
|
|
if sourceStack.Empty() {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
if h.Facing == cube.FaceDown {
|
||
|
|
if !recipe.ValidBrewingReagent(sourceStack.Item()) {
|
||
|
|
// This item is not a valid brewing reagent.
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
slot = 0
|
||
|
|
} else if _, ok := sourceStack.Item().(item.BlazePowder); ok {
|
||
|
|
slot = 4
|
||
|
|
} else {
|
||
|
|
_, okPotion := sourceStack.Item().(item.Potion)
|
||
|
|
_, okSplash := sourceStack.Item().(item.SplashPotion)
|
||
|
|
_, okLingering := sourceStack.Item().(item.LingeringPotion)
|
||
|
|
_, okBottle := sourceStack.Item().(item.GlassBottle)
|
||
|
|
if !okPotion && !okSplash && !okLingering && !okBottle {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for brewingSlot, brewingStack := range b.inventory.Slots() {
|
||
|
|
if brewingSlot == 0 || brewingSlot == 4 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if brewingStack.Count() == brewingStack.MaxCount() || !brewingStack.Comparable(sourceStack) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
slot = brewingSlot
|
||
|
|
break
|
||
|
|
}
|
||
|
|
// Could not find an empty slot
|
||
|
|
if slot == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
stack := sourceStack.Grow(-sourceStack.Count() + 1)
|
||
|
|
it, _ := b.Inventory(tx, pos).Item(slot)
|
||
|
|
|
||
|
|
if !sourceStack.Comparable(it) {
|
||
|
|
// The items are not the same.
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if it.Count() == it.MaxCount() {
|
||
|
|
// The item has the maximum count that the stack is able to hold.
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if !it.Empty() {
|
||
|
|
stack = it.Grow(1)
|
||
|
|
}
|
||
|
|
|
||
|
|
_ = b.Inventory(tx, pos).SetItem(slot, stack)
|
||
|
|
_ = h.inventory.SetItem(sourceSlot, sourceStack.Grow(-1))
|
||
|
|
return true
|
||
|
|
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// ExtractItem ...
|
||
|
|
func (b *brewer) ExtractItem(h Hopper, pos cube.Pos, tx *world.Tx) bool {
|
||
|
|
for sourceSlot, sourceStack := range b.inventory.Slots() {
|
||
|
|
if sourceStack.Empty() || sourceSlot == 0 || sourceSlot == 4 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
_, err := h.inventory.AddItem(sourceStack.Grow(-sourceStack.Count() + 1))
|
||
|
|
if err != nil {
|
||
|
|
// The hopper is full.
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
_ = b.Inventory(tx, pos).SetItem(sourceSlot, sourceStack.Grow(-1))
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// Duration returns the remaining duration of the brewing process.
|
||
|
|
func (b *brewer) Duration() time.Duration {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
return b.duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fuel returns the fuel and maximum fuel of the brewer.
|
||
|
|
func (b *brewer) Fuel() (fuel, maxFuel int32) {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
return b.fuelAmount, b.fuelTotal
|
||
|
|
}
|
||
|
|
|
||
|
|
// Inventory returns the inventory of the brewer.
|
||
|
|
func (b *brewer) Inventory(*world.Tx, cube.Pos) *inventory.Inventory {
|
||
|
|
return b.inventory
|
||
|
|
}
|
||
|
|
|
||
|
|
// AddViewer adds a viewer to the brewer, so that it is updated whenever the inventory of the brewer is changed.
|
||
|
|
func (b *brewer) AddViewer(v ContainerViewer, _ *world.Tx, _ cube.Pos) {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
b.viewers[v] = struct{}{}
|
||
|
|
}
|
||
|
|
|
||
|
|
// RemoveViewer removes a viewer from the brewer, so that slot updates in the inventory are no longer sent to
|
||
|
|
// it.
|
||
|
|
func (b *brewer) RemoveViewer(v ContainerViewer, _ *world.Tx, _ cube.Pos) {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
delete(b.viewers, v)
|
||
|
|
}
|
||
|
|
|
||
|
|
// setDuration sets the brew duration of the brewer to the given duration.
|
||
|
|
func (b *brewer) setDuration(duration time.Duration) {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
b.duration = duration
|
||
|
|
}
|
||
|
|
|
||
|
|
// setFuel sets the fuel of the brewer to the given fuel and maximum fuel.
|
||
|
|
func (b *brewer) setFuel(fuel, maxFuel int32) {
|
||
|
|
b.mu.Lock()
|
||
|
|
defer b.mu.Unlock()
|
||
|
|
b.fuelAmount, b.fuelTotal = fuel, maxFuel
|
||
|
|
}
|
||
|
|
|
||
|
|
// tickBrewing ticks the brewer, ensuring the necessary items exist in the brewer, and then processing all inputted
|
||
|
|
// items for the necessary duration.
|
||
|
|
func (b *brewer) tickBrewing(block string, pos cube.Pos, tx *world.Tx) {
|
||
|
|
b.mu.Lock()
|
||
|
|
|
||
|
|
// Get each item in the brewer. We don't need to validate errors here since we know the bounds of the brewer.
|
||
|
|
left, _ := b.inventory.Item(1)
|
||
|
|
middle, _ := b.inventory.Item(2)
|
||
|
|
right, _ := b.inventory.Item(3)
|
||
|
|
|
||
|
|
// Keep track of our past durations, since if any of them change, we need to be able to tell they did and then
|
||
|
|
// update the viewers on the change.
|
||
|
|
prevDuration := b.duration
|
||
|
|
prevFuelAmount := b.fuelAmount
|
||
|
|
prevFuelTotal := b.fuelTotal
|
||
|
|
|
||
|
|
// If we need fuel, try and burn some.
|
||
|
|
fuel, _ := b.inventory.Item(4)
|
||
|
|
|
||
|
|
if _, ok := fuel.Item().(item.BlazePowder); ok && b.fuelAmount <= 0 {
|
||
|
|
defer b.inventory.SetItem(4, fuel.Grow(-1))
|
||
|
|
b.fuelAmount, b.fuelTotal = 20, 20
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now get the ingredient item.
|
||
|
|
ingredient, _ := b.inventory.Item(0)
|
||
|
|
|
||
|
|
// Check each input and see if it is affected by the ingredient.
|
||
|
|
leftOutput, leftAffected := recipe.Perform(block, left.Item(), ingredient.Item())
|
||
|
|
middleOutput, middleAffected := recipe.Perform(block, middle.Item(), ingredient.Item())
|
||
|
|
rightOutput, rightAffected := recipe.Perform(block, right.Item(), ingredient.Item())
|
||
|
|
|
||
|
|
// Ensure that we have enough fuel to continue.
|
||
|
|
if b.fuelAmount > 0 {
|
||
|
|
// Now make sure that we have at least one potion that is affected by the ingredient.
|
||
|
|
if leftAffected || middleAffected || rightAffected {
|
||
|
|
// Tick our duration. If we have no brew duration, set it to the default of twenty seconds.
|
||
|
|
if b.duration == 0 {
|
||
|
|
b.duration = time.Second * 20
|
||
|
|
}
|
||
|
|
b.duration -= time.Millisecond * 50
|
||
|
|
|
||
|
|
// If we have no duration, we are done.
|
||
|
|
if b.duration <= 0 {
|
||
|
|
// Create the output items.
|
||
|
|
if leftAffected {
|
||
|
|
defer b.inventory.SetItem(1, leftOutput[0])
|
||
|
|
}
|
||
|
|
if middleAffected {
|
||
|
|
defer b.inventory.SetItem(2, middleOutput[0])
|
||
|
|
}
|
||
|
|
if rightAffected {
|
||
|
|
defer b.inventory.SetItem(3, rightOutput[0])
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reduce the ingredient by one.
|
||
|
|
defer b.inventory.SetItem(0, ingredient.Grow(-1))
|
||
|
|
tx.PlaySound(pos.Vec3Centre(), sound.PotionBrewed{})
|
||
|
|
|
||
|
|
// Decrement the fuel, and reset the duration.
|
||
|
|
b.fuelAmount--
|
||
|
|
b.duration = 0
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// None of the potions are affected by the ingredient, so reset the duration.
|
||
|
|
b.duration = 0
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// We don't have enough fuel, so reset our progress.
|
||
|
|
b.duration, b.fuelAmount, b.fuelTotal = 0, 0, 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update the viewers on the new durations.
|
||
|
|
for v := range b.viewers {
|
||
|
|
v.ViewBrewingUpdate(prevDuration, b.duration, prevFuelAmount, b.fuelAmount, prevFuelTotal, b.fuelTotal)
|
||
|
|
}
|
||
|
|
|
||
|
|
b.mu.Unlock()
|
||
|
|
}
|