up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/df-mc/dragonfly/server/block/cube"
|
||||
"github.com/df-mc/dragonfly/server/internal/nbtconv"
|
||||
"github.com/df-mc/dragonfly/server/item"
|
||||
"github.com/df-mc/dragonfly/server/item/inventory"
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
"github.com/df-mc/dragonfly/server/world/sound"
|
||||
"github.com/go-gl/mathgl/mgl64"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Chest is a container block which may be used to store items. Chests may also be paired to create a bigger
|
||||
// single container.
|
||||
// The empty value of Chest is not valid. It must be created using block.NewChest().
|
||||
type Chest struct {
|
||||
chest
|
||||
transparent
|
||||
bass
|
||||
sourceWaterDisplacer
|
||||
|
||||
// Facing is the direction that the chest is facing.
|
||||
Facing cube.Direction
|
||||
// CustomName is the custom name of the chest. This name is displayed when the chest is opened, and may
|
||||
// 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 {
|
||||
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(tx *world.Tx, pos cube.Pos) *inventory.Inventory {
|
||||
inv, _ := c.tryPair(tx, pos)
|
||||
return inv
|
||||
}
|
||||
|
||||
// tryPair attempts to pair the inventories of this chest with a potential
|
||||
// paired chest next to it. The (shared) inventory is returned and a bool is
|
||||
// returned indicating if the chest changed its pairing state.
|
||||
func (c Chest) tryPair(tx *world.Tx, pos cube.Pos) (*inventory.Inventory, bool) {
|
||||
if c.paired {
|
||||
if c.pairInv == nil {
|
||||
if ch, pair, ok := c.pair(tx, pos, c.pairPos(pos)); ok {
|
||||
tx.SetBlock(pos, ch, nil)
|
||||
tx.SetBlock(c.pairPos(pos), pair, nil)
|
||||
return ch.pairInv, true
|
||||
}
|
||||
c.paired = false
|
||||
tx.SetBlock(pos, c, nil)
|
||||
return c.inventory, true
|
||||
}
|
||||
return c.pairInv, false
|
||||
}
|
||||
return c.inventory, false
|
||||
}
|
||||
|
||||
// WithName returns the chest after applying a specific name to the block.
|
||||
func (c Chest) WithName(a ...any) world.Item {
|
||||
c.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n")
|
||||
return c
|
||||
}
|
||||
|
||||
// SideClosed ...
|
||||
func (Chest) SideClosed(cube.Pos, cube.Pos, *world.Tx) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// open opens the chest, displaying the animation and playing a sound.
|
||||
func (c Chest) open(tx *world.Tx, pos cube.Pos) {
|
||||
for _, v := range tx.Viewers(pos.Vec3()) {
|
||||
if c.paired {
|
||||
v.ViewBlockAction(c.pairPos(pos), OpenAction{})
|
||||
}
|
||||
v.ViewBlockAction(pos, OpenAction{})
|
||||
}
|
||||
tx.PlaySound(pos.Vec3Centre(), sound.ChestOpen{})
|
||||
}
|
||||
|
||||
// close closes the chest, displaying the animation and playing a sound.
|
||||
func (c Chest) close(tx *world.Tx, pos cube.Pos) {
|
||||
for _, v := range tx.Viewers(pos.Vec3()) {
|
||||
if c.paired {
|
||||
v.ViewBlockAction(c.pairPos(pos), CloseAction{})
|
||||
}
|
||||
v.ViewBlockAction(pos, CloseAction{})
|
||||
}
|
||||
tx.PlaySound(pos.Vec3Centre(), sound.ChestClose{})
|
||||
}
|
||||
|
||||
// AddViewer adds a viewer to the chest, so that it is updated whenever the inventory of the chest is changed.
|
||||
func (c Chest) AddViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) {
|
||||
if _, changed := c.tryPair(tx, pos); changed {
|
||||
c = tx.Block(pos).(Chest)
|
||||
}
|
||||
c.viewerMu.Lock()
|
||||
defer c.viewerMu.Unlock()
|
||||
if len(c.viewers) == 0 {
|
||||
c.open(tx, pos)
|
||||
}
|
||||
c.viewers[v] = struct{}{}
|
||||
}
|
||||
|
||||
// RemoveViewer removes a viewer from the chest, so that slot updates in the inventory are no longer sent to
|
||||
// it.
|
||||
func (c Chest) RemoveViewer(v ContainerViewer, tx *world.Tx, pos cube.Pos) {
|
||||
if _, changed := c.tryPair(tx, pos); changed {
|
||||
c = tx.Block(pos).(Chest)
|
||||
}
|
||||
c.viewerMu.Lock()
|
||||
defer c.viewerMu.Unlock()
|
||||
if len(c.viewers) == 0 {
|
||||
return
|
||||
}
|
||||
delete(c.viewers, v)
|
||||
if len(c.viewers) == 0 {
|
||||
c.close(tx, pos)
|
||||
}
|
||||
}
|
||||
|
||||
// Activate ...
|
||||
func (c Chest) Activate(pos cube.Pos, _ cube.Face, tx *world.Tx, u item.User, _ *item.UseContext) bool {
|
||||
if opener, ok := u.(ContainerOpener); ok {
|
||||
if c.paired {
|
||||
if d, ok := tx.Block(c.pairPos(pos).Side(cube.FaceUp)).(LightDiffuser); !ok || d.LightDiffusionLevel() > 2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if d, ok := tx.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 {
|
||||
opener.OpenBlockContainer(pos, tx)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UseOnBlock ...
|
||||
func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, tx *world.Tx, user item.User, ctx *item.UseContext) (used bool) {
|
||||
pos, _, used = firstReplaceable(tx, pos, face, c)
|
||||
if !used {
|
||||
return
|
||||
}
|
||||
//noinspection GoAssignmentToReceiver
|
||||
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(tx, pos, pos.Side(dir.Face())); ok {
|
||||
place(tx, pos, ch, user, ctx)
|
||||
tx.SetBlock(ch.pairPos(pos), pair, nil)
|
||||
return placed(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
place(tx, pos, c, user, ctx)
|
||||
return placed(ctx)
|
||||
}
|
||||
|
||||
// BreakInfo ...
|
||||
func (c Chest) BreakInfo() BreakInfo {
|
||||
return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c)).withBreakHandler(func(pos cube.Pos, tx *world.Tx, u item.User) {
|
||||
if c.paired {
|
||||
pairPos := c.pairPos(pos)
|
||||
if _, pair, ok := c.unpair(tx, pos); ok {
|
||||
c.paired = false
|
||||
tx.SetBlock(pairPos, pair, nil)
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range c.Inventory(tx, pos).Clear() {
|
||||
dropItem(tx, i, pos.Vec3Centre())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FuelInfo ...
|
||||
func (Chest) FuelInfo() item.FuelInfo {
|
||||
return newFuelInfo(time.Second * 15)
|
||||
}
|
||||
|
||||
// FlammabilityInfo ...
|
||||
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(tx *world.Tx, pos, pairPos cube.Pos) (ch, pair Chest, ok bool) {
|
||||
pair, ok = tx.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(tx *world.Tx, pos cube.Pos) (ch, pair Chest, ok bool) {
|
||||
if !c.paired {
|
||||
return c, Chest{}, false
|
||||
}
|
||||
|
||||
pair, ok = tx.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(tx, 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
|
||||
}
|
||||
|
||||
// EncodeNBT ...
|
||||
func (c Chest) EncodeNBT() map[string]any {
|
||||
if c.inventory == nil {
|
||||
facing, customName := c.Facing, c.CustomName
|
||||
//noinspection GoAssignmentToReceiver
|
||||
c = NewChest()
|
||||
c.Facing, c.CustomName = facing, customName
|
||||
}
|
||||
m := map[string]any{
|
||||
"Items": nbtconv.InvToNBT(c.inventory),
|
||||
"id": "Chest",
|
||||
}
|
||||
if c.CustomName != "" {
|
||||
m["CustomName"] = c.CustomName
|
||||
}
|
||||
|
||||
if c.paired {
|
||||
m["pairx"] = int32(c.pairX)
|
||||
m["pairz"] = int32(c.pairZ)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// EncodeItem ...
|
||||
func (Chest) EncodeItem() (name string, meta int16) {
|
||||
return "minecraft:chest", 0
|
||||
}
|
||||
|
||||
// EncodeBlock ...
|
||||
func (c Chest) EncodeBlock() (name string, properties map[string]any) {
|
||||
return "minecraft:chest", map[string]any{"minecraft:cardinal_direction": c.Facing.String()}
|
||||
}
|
||||
|
||||
// allChests ...
|
||||
func allChests() (chests []world.Block) {
|
||||
for _, direction := range cube.Directions() {
|
||||
chests = append(chests, Chest{Facing: direction})
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user