Skip to content

Commit

Permalink
block/chest.go: Implement double chests (#872)
Browse files Browse the repository at this point in the history
Co-authored-by: Restart <[email protected]>
Co-authored-by: cqdetdisc <[email protected]>
Co-authored-by: T14Raptor <[email protected]>
Co-authored-by: cqdetdev <[email protected]>
Co-authored-by: TwistedAsylumMC <[email protected]>
  • Loading branch information
6 people authored Aug 15, 2024
1 parent b186364 commit ef1263a
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 40 deletions.
8 changes: 6 additions & 2 deletions server/block/barrel.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func NewBarrel() Barrel {
}

// Inventory returns the inventory of the barrel. The size of the inventory will be 27.
func (b Barrel) Inventory() *inventory.Inventory {
func (b Barrel) Inventory(*world.World, cube.Pos) *inventory.Inventory {
return b.inventory
}

Expand Down Expand Up @@ -124,7 +124,11 @@ func (b Barrel) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.

// BreakInfo ...
func (b Barrel) BreakInfo() BreakInfo {
return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(b))
return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(b)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) {
for _, i := range b.Inventory(w, pos).Clear() {
dropItem(w, i, pos.Vec3())
}
})
}

// FlammabilityInfo ...
Expand Down
10 changes: 7 additions & 3 deletions server/block/blast_furnace.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ func (b BlastFurnace) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *
// BreakInfo ...
func (b BlastFurnace) BreakInfo() BreakInfo {
xp := b.Experience()
return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withXPDropRange(xp, xp)
return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(b)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) {
for _, i := range b.Inventory(w, pos).Clear() {
dropItem(w, i, pos.Vec3())
}
})
}

// Activate ...
Expand All @@ -97,7 +101,7 @@ func (b BlastFurnace) EncodeNBT() map[string]interface{} {
"CookTime": int16(cook.Milliseconds() / 50),
"BurnDuration": int16(maximum.Milliseconds() / 50),
"StoredXPInt": int16(b.Experience()),
"Items": nbtconv.InvToNBT(b.Inventory()),
"Items": nbtconv.InvToNBT(b.inventory),
"id": "BlastFurnace",
}
}
Expand All @@ -116,7 +120,7 @@ func (b BlastFurnace) DecodeNBT(data map[string]interface{}) interface{} {
b.Lit = lit
b.setExperience(xp)
b.setDurations(remaining, maximum, cook)
nbtconv.InvFromNBT(b.Inventory(), nbtconv.Slice(data, "Items"))
nbtconv.InvFromNBT(b.inventory, nbtconv.Slice(data, "Items"))
return b
}

Expand Down
178 changes: 164 additions & 14 deletions server/block/chest.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,49 @@ type Chest struct {
// include colour codes.
CustomName string

paired bool
pairX, pairZ int
pairInv *inventory.Inventory

inventory *inventory.Inventory
viewerMu *sync.RWMutex
viewers map[ContainerViewer]struct{}
}

// NewChest creates a new initialised chest. The inventory is properly initialised.
func NewChest() Chest {
m := new(sync.RWMutex)
v := make(map[ContainerViewer]struct{}, 1)
return Chest{
inventory: inventory.New(27, func(slot int, _, item item.Stack) {
m.RLock()
defer m.RUnlock()
for viewer := range v {
viewer.ViewSlotChange(slot, item)
}
}),
viewerMu: m,
viewers: v,
c := Chest{
viewerMu: new(sync.RWMutex),
viewers: make(map[ContainerViewer]struct{}, 1),
}

c.inventory = inventory.New(27, func(slot int, _, after item.Stack) {
c.viewerMu.RLock()
defer c.viewerMu.RUnlock()
for viewer := range c.viewers {
viewer.ViewSlotChange(slot, after)
}
})
return c
}

// Inventory returns the inventory of the chest. The size of the inventory will be 27 or 54, depending on
// whether the chest is single or double.
func (c Chest) Inventory() *inventory.Inventory {
func (c Chest) Inventory(w *world.World, pos cube.Pos) *inventory.Inventory {
if c.paired {
if c.pairInv == nil {
if ch, pair, ok := c.pair(w, pos, c.pairPos(pos)); ok {
c = ch
w.SetBlock(pos, ch, nil)
w.SetBlock(c.pairPos(pos), pair, nil)
} else {
c.paired = false
w.SetBlock(pos, c, nil)
return c.inventory
}
}
return c.pairInv
}
return c.inventory
}

Expand All @@ -71,6 +89,9 @@ func (Chest) SideClosed(cube.Pos, cube.Pos, *world.World) bool {
// open opens the chest, displaying the animation and playing a sound.
func (c Chest) open(w *world.World, pos cube.Pos) {
for _, v := range w.Viewers(pos.Vec3()) {
if c.paired {
v.ViewBlockAction(c.pairPos(pos), OpenAction{})
}
v.ViewBlockAction(pos, OpenAction{})
}
w.PlaySound(pos.Vec3Centre(), sound.ChestOpen{})
Expand All @@ -79,6 +100,9 @@ func (c Chest) open(w *world.World, pos cube.Pos) {
// close closes the chest, displaying the animation and playing a sound.
func (c Chest) close(w *world.World, pos cube.Pos) {
for _, v := range w.Viewers(pos.Vec3()) {
if c.paired {
v.ViewBlockAction(c.pairPos(pos), CloseAction{})
}
v.ViewBlockAction(pos, CloseAction{})
}
w.PlaySound(pos.Vec3Centre(), sound.ChestClose{})
Expand Down Expand Up @@ -111,6 +135,11 @@ func (c Chest) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) {
// Activate ...
func (c Chest) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool {
if opener, ok := u.(ContainerOpener); ok {
if c.paired {
if d, ok := w.Block(c.pairPos(pos).Side(cube.FaceUp)).(LightDiffuser); !ok || d.LightDiffusionLevel() > 2 {
return false
}
}
if d, ok := w.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 {
opener.OpenBlockContainer(pos)
}
Expand All @@ -129,13 +158,34 @@ func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.W
c = NewChest()
c.Facing = user.Rotation().Direction().Opposite()

// Check both sides of the chest to see if it is possible to pair with another chest.
for _, dir := range []cube.Direction{c.Facing.RotateLeft(), c.Facing.RotateRight()} {
if ch, pair, ok := c.pair(w, pos, pos.Side(dir.Face())); ok {
place(w, pos, ch, user, ctx)
w.SetBlock(ch.pairPos(pos), pair, nil)
return placed(ctx)
}
}

place(w, pos, c, user, ctx)
return placed(ctx)
}

// BreakInfo ...
func (c Chest) BreakInfo() BreakInfo {
return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c))
return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) {
if c.paired {
pairPos := c.pairPos(pos)
if _, pair, ok := c.unpair(w, pos); ok {
c.paired = false
w.SetBlock(pairPos, pair, nil)
}
}

for _, i := range c.Inventory(w, pos).Clear() {
dropItem(w, i, pos.Vec3Centre())
}
})
}

// FuelInfo ...
Expand All @@ -148,13 +198,108 @@ func (c Chest) FlammabilityInfo() FlammabilityInfo {
return newFlammabilityInfo(0, 0, true)
}

// Paired returns whether the chest is paired with another chest.
func (c Chest) Paired() bool {
return c.paired
}

// pair pairs this chest with the given chest position.
func (c Chest) pair(w *world.World, pos, pairPos cube.Pos) (ch, pair Chest, ok bool) {
pair, ok = w.Block(pairPos).(Chest)
if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) {
return c, pair, false
}
m := new(sync.RWMutex)
v := make(map[ContainerViewer]struct{})
left, right := c.inventory.Clone(nil), pair.inventory.Clone(nil)
if pos.Side(c.Facing.RotateRight().Face()) == pairPos {
left, right = right, left
}
double := left.Merge(right, func(slot int, _, item item.Stack) {
if slot < 27 {
_ = left.SetItem(slot, item)
} else {
_ = right.SetItem(slot-27, item)
}
m.RLock()
defer m.RUnlock()
for viewer := range v {
viewer.ViewSlotChange(slot, item)
}
})

c.inventory, pair.inventory = left, right
if pos.Side(c.Facing.RotateRight().Face()) == pairPos {
c.inventory, pair.inventory = right, left
}
c.pairX, c.pairZ, c.paired = pairPos[0], pairPos[2], true
pair.pairX, pair.pairZ, pair.paired = pos[0], pos[2], true
c.viewerMu, pair.viewerMu = m, m
c.viewers, pair.viewers = v, v
c.pairInv, pair.pairInv = double, double
return c, pair, true
}

// unpair unpairs this chest from the chest it is currently paired with.
func (c Chest) unpair(w *world.World, pos cube.Pos) (ch, pair Chest, ok bool) {
if !c.paired {
return c, Chest{}, false
}

pair, ok = w.Block(c.pairPos(pos)).(Chest)
if !ok || c.Facing != pair.Facing || pair.paired && (pair.pairX != pos[0] || pair.pairZ != pos[2]) {
return c, pair, false
}

if len(c.viewers) != 0 {
c.close(w, pos)
}

c.inventory = c.inventory.Clone(func(slot int, _, after item.Stack) {
c.viewerMu.RLock()
defer c.viewerMu.RUnlock()
for viewer := range c.viewers {
viewer.ViewSlotChange(slot, after)
}
})
pair.inventory = pair.inventory.Clone(func(slot int, _, after item.Stack) {
pair.viewerMu.RLock()
defer pair.viewerMu.RUnlock()
for viewer := range pair.viewers {
viewer.ViewSlotChange(slot, after)
}
})
c.paired, pair.paired = false, false
c.viewerMu, pair.viewerMu = new(sync.RWMutex), new(sync.RWMutex)
c.viewers, pair.viewers = make(map[ContainerViewer]struct{}, 1), make(map[ContainerViewer]struct{}, 1)
c.pairInv, pair.pairInv = nil, nil
return c, pair, true
}

// pairPos returns the position of the chest that this chest is paired with.
func (c Chest) pairPos(pos cube.Pos) cube.Pos {
return cube.Pos{c.pairX, pos[1], c.pairZ}
}

// DecodeNBT ...
func (c Chest) DecodeNBT(data map[string]any) any {
facing := c.Facing
//noinspection GoAssignmentToReceiver
c = NewChest()
c.Facing = facing
c.CustomName = nbtconv.String(data, "CustomName")

pairX, ok := data["pairx"]
pairZ, ok2 := data["pairz"]
if ok && ok2 {
pairX, ok := pairX.(int32)
pairZ, ok2 := pairZ.(int32)
if ok && ok2 {
c.paired = true
c.pairX, c.pairZ = int(pairX), int(pairZ)
}
}

nbtconv.InvFromNBT(c.inventory, nbtconv.Slice(data, "Items"))
return c
}
Expand All @@ -174,6 +319,11 @@ func (c Chest) EncodeNBT() map[string]any {
if c.CustomName != "" {
m["CustomName"] = c.CustomName
}

if c.paired {
m["pairx"] = int32(c.pairX)
m["pairz"] = int32(c.pairZ)
}
return m
}

Expand Down
2 changes: 1 addition & 1 deletion server/block/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ type ContainerOpener interface {
type Container interface {
AddViewer(v ContainerViewer, w *world.World, pos cube.Pos)
RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos)
Inventory() *inventory.Inventory
Inventory(w *world.World, pos cube.Pos) *inventory.Inventory
}
20 changes: 20 additions & 0 deletions server/block/explosion.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,26 @@ func (c ExplosionConfig) Explode(w *world.World, explosionPos mgl64.Vec3) {
dropItem(w, drop, pos.Vec3Centre())
}
}

if container, ok := bl.(Container); ok {
if cb, ok := bl.(Chest); ok {
if cb.Paired() {
pairPos := cb.pairPos(pos)
if _, pair, ok := cb.unpair(w, pos); ok {
cb.paired = false
w.SetBlock(pairPos, pair, nil)
}
}

for _, i := range cb.Inventory(w, pos).Clear() {
dropItem(w, i, pos.Vec3())
}
} else {
for _, i := range container.Inventory(w, pos).Clear() {
dropItem(w, i, pos.Vec3())
}
}
}
}
}
if c.SpawnFire {
Expand Down
10 changes: 7 additions & 3 deletions server/block/furnace.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ func (f Furnace) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world
// BreakInfo ...
func (f Furnace) BreakInfo() BreakInfo {
xp := f.Experience()
return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(f)).withXPDropRange(xp, xp)
return newBreakInfo(3.5, alwaysHarvestable, pickaxeEffective, oneOf(f)).withXPDropRange(xp, xp).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) {
for _, i := range f.Inventory(w, pos).Clear() {
dropItem(w, i, pos.Vec3())
}
})
}

// Activate ...
Expand All @@ -96,7 +100,7 @@ func (f Furnace) EncodeNBT() map[string]interface{} {
"CookTime": int16(cook.Milliseconds() / 50),
"BurnDuration": int16(maximum.Milliseconds() / 50),
"StoredXPInt": int16(f.Experience()),
"Items": nbtconv.InvToNBT(f.Inventory()),
"Items": nbtconv.InvToNBT(f.inventory),
"id": "Furnace",
}
}
Expand All @@ -115,7 +119,7 @@ func (f Furnace) DecodeNBT(data map[string]interface{}) interface{} {
f.Lit = lit
f.setExperience(xp)
f.setDurations(remaining, maximum, cook)
nbtconv.InvFromNBT(f.Inventory(), nbtconv.Slice(data, "Items"))
nbtconv.InvFromNBT(f.inventory, nbtconv.Slice(data, "Items"))
return f
}

Expand Down
1 change: 1 addition & 0 deletions server/block/jukebox.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (j Jukebox) BreakInfo() BreakInfo {
}
return newBreakInfo(0.8, alwaysHarvestable, axeEffective, simpleDrops(d...)).withBreakHandler(func(pos cube.Pos, w *world.World, u item.User) {
if _, hasDisc := j.Disc(); hasDisc {
dropItem(w, j.Item, pos.Vec3())
w.PlaySound(pos.Vec3Centre(), sound.MusicDiscEnd{})
}
})
Expand Down
2 changes: 1 addition & 1 deletion server/block/smelter.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (s *smelter) ResetExperience() int {
}

// Inventory returns the inventory of the furnace.
func (s *smelter) Inventory() *inventory.Inventory {
func (s *smelter) Inventory(*world.World, cube.Pos) *inventory.Inventory {
return s.inventory
}

Expand Down
Loading

0 comments on commit ef1263a

Please sign in to comment.