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() }