Skip to content

Commit

Permalink
server/block: Implement brewing stand (#916)
Browse files Browse the repository at this point in the history
Co-authored-by: TwistedAsylumMC <[email protected]>
Co-authored-by: DaPigGuy <[email protected]>
  • Loading branch information
3 people authored Nov 17, 2024
1 parent 44f13f2 commit 9dde24c
Show file tree
Hide file tree
Showing 15 changed files with 730 additions and 31 deletions.
253 changes: 253 additions & 0 deletions server/block/brewer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
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, w *world.World) 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(w, 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(w, 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, w *world.World) bool {
for sourceSlot, sourceStack := range b.inventory.Slots() {
if sourceStack.Empty() {
continue
}

if sourceSlot == 0 || sourceSlot == 4 {
continue
}

_, err := h.inventory.AddItem(sourceStack.Grow(-sourceStack.Count() + 1))
if err != nil {
// The hopper is full.
continue
}

_ = b.Inventory(w, 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.World, 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.World, _ 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.World, _ 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, w *world.World) {
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))
w.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()
}
Loading

0 comments on commit 9dde24c

Please sign in to comment.