Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented brewing stands #632

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/df-mc/goleveldb v1.1.9
github.com/go-gl/mathgl v1.0.0
github.com/google/uuid v1.3.0
github.com/kr/pretty v0.1.0
github.com/pelletier/go-toml v1.9.4
github.com/rogpeppe/go-internal v1.3.0
github.com/sandertv/gophertunnel v1.22.3
Expand All @@ -22,6 +23,7 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/compress v1.15.1 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
github.com/sandertv/go-raknet v1.11.1 // indirect
github.com/stretchr/testify v1.7.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs=
github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
Expand Down
165 changes: 165 additions & 0 deletions server/block/brewer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
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
}

// 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() *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()
}
132 changes: 132 additions & 0 deletions server/block/brewing_stand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package block

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/block/model"
"github.com/df-mc/dragonfly/server/internal/nbtconv"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
"time"
)

// BrewingStand is a block used for brewing potions, splash potions, and lingering potions. It also serves as a cleric's
// job site block generated in village churches.
type BrewingStand struct {
*brewer

// LeftSlot is true if the left slot is filled.
LeftSlot bool
// MiddleSlot is true if the middle slot is filled.
MiddleSlot bool
// RightSlot is true if the right slot is filled.
RightSlot bool
}

// NewBrewingStand creates a new initialised brewing stand. The inventory is properly initialised.
func NewBrewingStand() BrewingStand {
return BrewingStand{brewer: newBrewer()}
}

// Model ...
func (b BrewingStand) Model() world.BlockModel {
return model.BrewingStand{}
}

// Tick is called to check if the brewing stand should update and start or stop brewing.
func (b BrewingStand) Tick(_ int64, pos cube.Pos, w *world.World) {
// Get each item in the brewing stand. We don't need to validate errors here since we know the bounds of the stand.
left, _ := b.Inventory().Item(1)
middle, _ := b.Inventory().Item(2)
right, _ := b.Inventory().Item(3)

// If any of the slots in the inventory got updated, update the appearance of the brewing stand.
displayLeft, displayMiddle, displayRight := b.LeftSlot, b.MiddleSlot, b.RightSlot
b.LeftSlot, b.MiddleSlot, b.RightSlot = !left.Empty(), !middle.Empty(), !right.Empty()
if b.LeftSlot != displayLeft || b.MiddleSlot != displayMiddle || b.RightSlot != displayRight {
w.SetBlock(pos, b, nil)
}

// Tick brewing.
b.tickBrewing("brewing_stand", pos, w)
}

// Activate ...
func (b BrewingStand) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool {
if opener, ok := u.(ContainerOpener); ok {
opener.OpenBlockContainer(pos)
return true
}
return false
}

// UseOnBlock ...
func (b BrewingStand) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) {
pos, _, used = firstReplaceable(w, pos, face, b)
if !used {
return
}

//noinspection GoAssignmentToReceiver
b = NewBrewingStand()
place(w, pos, b, user, ctx)
return placed(ctx)
}

// EncodeNBT ...
func (b BrewingStand) EncodeNBT() map[string]any {
if b.brewer == nil {
//noinspection GoAssignmentToReceiver
b = NewBrewingStand()
}
duration := b.Duration()
fuel, totalFuel := b.Fuel()
return map[string]any{
"id": "BrewingStand",
"Items": nbtconv.InvToNBT(b.Inventory()),
"CookTime": int16(duration.Milliseconds() / 50),
"FuelTotal": int16(totalFuel),
"FuelAmount": int16(fuel),
}
}

// DecodeNBT ...
func (b BrewingStand) DecodeNBT(data map[string]any) any {
brew := time.Duration(nbtconv.Map[int16](data, "CookTime")) * time.Millisecond * 50

fuel := int32(nbtconv.Map[int16](data, "FuelAmount"))
maxFuel := int32(nbtconv.Map[int16](data, "FuelTotal"))

//noinspection GoAssignmentToReceiver
b = NewBrewingStand()
b.setDuration(brew)
b.setFuel(fuel, maxFuel)
nbtconv.InvFromNBT(b.Inventory(), nbtconv.Map[[]any](data, "Items"))
return b
}

// EncodeBlock ...
func (b BrewingStand) EncodeBlock() (string, map[string]any) {
return "minecraft:brewing_stand", map[string]any{
"brewing_stand_slot_a_bit": b.LeftSlot,
"brewing_stand_slot_b_bit": b.MiddleSlot,
"brewing_stand_slot_c_bit": b.RightSlot,
}
}

// EncodeItem ...
func (b BrewingStand) EncodeItem() (name string, meta int16) {
return "minecraft:brewing_stand", 0
}

// allBrewingStands ...
func allBrewingStands() (stands []world.Block) {
for _, left := range []bool{false, true} {
for _, middle := range []bool{false, true} {
for _, right := range []bool{false, true} {
stands = append(stands, BrewingStand{LeftSlot: left, MiddleSlot: middle, RightSlot: right})
}
}
}
return
}
5 changes: 5 additions & 0 deletions server/block/hash.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions server/block/model/brewing_stand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package model

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
)

// BrewingStand is a model used by brewing stands.
type BrewingStand struct{}

// BBox ...
func (b BrewingStand) BBox(cube.Pos, *world.World) []cube.BBox {
return []cube.BBox{
full.ExtendTowards(cube.FaceDown, 0.875),
full.Stretch(cube.X, -0.4375).Stretch(cube.Z, -0.4375).ExtendTowards(cube.FaceDown, 0.125),
}
}

// FaceSolid ...
func (b BrewingStand) FaceSolid(cube.Pos, cube.Face, *world.World) bool {
return false
}
2 changes: 2 additions & 0 deletions server/block/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func init() {
registerAll(allBlackstone())
registerAll(allBlastFurnaces())
registerAll(allBoneBlock())
registerAll(allBrewingStands())
registerAll(allCactus())
registerAll(allCake())
registerAll(allCarpet())
Expand Down Expand Up @@ -205,6 +206,7 @@ func init() {
world.RegisterItem(BlueIce{})
world.RegisterItem(Bone{})
world.RegisterItem(Bookshelf{})
world.RegisterItem(BrewingStand{})
world.RegisterItem(Bricks{})
world.RegisterItem(Cactus{})
world.RegisterItem(Cake{})
Expand Down
Loading