github.com/df-mc/dragonfly@v0.9.13/server/block/chest.go (about) 1 package block 2 3 import ( 4 "fmt" 5 "github.com/df-mc/dragonfly/server/block/cube" 6 "github.com/df-mc/dragonfly/server/internal/nbtconv" 7 "github.com/df-mc/dragonfly/server/item" 8 "github.com/df-mc/dragonfly/server/item/inventory" 9 "github.com/df-mc/dragonfly/server/world" 10 "github.com/df-mc/dragonfly/server/world/sound" 11 "github.com/go-gl/mathgl/mgl64" 12 "strings" 13 "sync" 14 "time" 15 ) 16 17 // Chest is a container block which may be used to store items. Chests may also be paired to create a bigger 18 // single container. 19 // The empty value of Chest is not valid. It must be created using block.NewChest(). 20 type Chest struct { 21 chest 22 transparent 23 bass 24 sourceWaterDisplacer 25 26 // Facing is the direction that the chest is facing. 27 Facing cube.Direction 28 // CustomName is the custom name of the chest. This name is displayed when the chest is opened, and may 29 // include colour codes. 30 CustomName string 31 32 inventory *inventory.Inventory 33 viewerMu *sync.RWMutex 34 viewers map[ContainerViewer]struct{} 35 } 36 37 // NewChest creates a new initialised chest. The inventory is properly initialised. 38 func NewChest() Chest { 39 m := new(sync.RWMutex) 40 v := make(map[ContainerViewer]struct{}, 1) 41 return Chest{ 42 inventory: inventory.New(27, func(slot int, _, item item.Stack) { 43 m.RLock() 44 defer m.RUnlock() 45 for viewer := range v { 46 viewer.ViewSlotChange(slot, item) 47 } 48 }), 49 viewerMu: m, 50 viewers: v, 51 } 52 } 53 54 // Inventory returns the inventory of the chest. The size of the inventory will be 27 or 54, depending on 55 // whether the chest is single or double. 56 func (c Chest) Inventory() *inventory.Inventory { 57 return c.inventory 58 } 59 60 // WithName returns the chest after applying a specific name to the block. 61 func (c Chest) WithName(a ...any) world.Item { 62 c.CustomName = strings.TrimSuffix(fmt.Sprintln(a...), "\n") 63 return c 64 } 65 66 // SideClosed ... 67 func (Chest) SideClosed(cube.Pos, cube.Pos, *world.World) bool { 68 return false 69 } 70 71 // open opens the chest, displaying the animation and playing a sound. 72 func (c Chest) open(w *world.World, pos cube.Pos) { 73 for _, v := range w.Viewers(pos.Vec3()) { 74 v.ViewBlockAction(pos, OpenAction{}) 75 } 76 w.PlaySound(pos.Vec3Centre(), sound.ChestOpen{}) 77 } 78 79 // close closes the chest, displaying the animation and playing a sound. 80 func (c Chest) close(w *world.World, pos cube.Pos) { 81 for _, v := range w.Viewers(pos.Vec3()) { 82 v.ViewBlockAction(pos, CloseAction{}) 83 } 84 w.PlaySound(pos.Vec3Centre(), sound.ChestClose{}) 85 } 86 87 // AddViewer adds a viewer to the chest, so that it is updated whenever the inventory of the chest is changed. 88 func (c Chest) AddViewer(v ContainerViewer, w *world.World, pos cube.Pos) { 89 c.viewerMu.Lock() 90 defer c.viewerMu.Unlock() 91 if len(c.viewers) == 0 { 92 c.open(w, pos) 93 } 94 c.viewers[v] = struct{}{} 95 } 96 97 // RemoveViewer removes a viewer from the chest, so that slot updates in the inventory are no longer sent to 98 // it. 99 func (c Chest) RemoveViewer(v ContainerViewer, w *world.World, pos cube.Pos) { 100 c.viewerMu.Lock() 101 defer c.viewerMu.Unlock() 102 if len(c.viewers) == 0 { 103 return 104 } 105 delete(c.viewers, v) 106 if len(c.viewers) == 0 { 107 c.close(w, pos) 108 } 109 } 110 111 // Activate ... 112 func (c Chest) Activate(pos cube.Pos, _ cube.Face, w *world.World, u item.User, _ *item.UseContext) bool { 113 if opener, ok := u.(ContainerOpener); ok { 114 if d, ok := w.Block(pos.Side(cube.FaceUp)).(LightDiffuser); ok && d.LightDiffusionLevel() <= 2 { 115 opener.OpenBlockContainer(pos) 116 } 117 return true 118 } 119 return false 120 } 121 122 // UseOnBlock ... 123 func (c Chest) UseOnBlock(pos cube.Pos, face cube.Face, _ mgl64.Vec3, w *world.World, user item.User, ctx *item.UseContext) (used bool) { 124 pos, _, used = firstReplaceable(w, pos, face, c) 125 if !used { 126 return 127 } 128 //noinspection GoAssignmentToReceiver 129 c = NewChest() 130 c.Facing = user.Rotation().Direction().Opposite() 131 132 place(w, pos, c, user, ctx) 133 return placed(ctx) 134 } 135 136 // BreakInfo ... 137 func (c Chest) BreakInfo() BreakInfo { 138 return newBreakInfo(2.5, alwaysHarvestable, axeEffective, oneOf(c)) 139 } 140 141 // FuelInfo ... 142 func (Chest) FuelInfo() item.FuelInfo { 143 return newFuelInfo(time.Second * 15) 144 } 145 146 // FlammabilityInfo ... 147 func (c Chest) FlammabilityInfo() FlammabilityInfo { 148 return newFlammabilityInfo(0, 0, true) 149 } 150 151 // DecodeNBT ... 152 func (c Chest) DecodeNBT(data map[string]any) any { 153 facing := c.Facing 154 //noinspection GoAssignmentToReceiver 155 c = NewChest() 156 c.Facing = facing 157 c.CustomName = nbtconv.String(data, "CustomName") 158 nbtconv.InvFromNBT(c.inventory, nbtconv.Slice(data, "Items")) 159 return c 160 } 161 162 // EncodeNBT ... 163 func (c Chest) EncodeNBT() map[string]any { 164 if c.inventory == nil { 165 facing, customName := c.Facing, c.CustomName 166 //noinspection GoAssignmentToReceiver 167 c = NewChest() 168 c.Facing, c.CustomName = facing, customName 169 } 170 m := map[string]any{ 171 "Items": nbtconv.InvToNBT(c.inventory), 172 "id": "Chest", 173 } 174 if c.CustomName != "" { 175 m["CustomName"] = c.CustomName 176 } 177 return m 178 } 179 180 // EncodeItem ... 181 func (Chest) EncodeItem() (name string, meta int16) { 182 return "minecraft:chest", 0 183 } 184 185 // EncodeBlock ... 186 func (c Chest) EncodeBlock() (name string, properties map[string]any) { 187 return "minecraft:chest", map[string]any{"minecraft:cardinal_direction": c.Facing.String()} 188 } 189 190 // allChests ... 191 func allChests() (chests []world.Block) { 192 for _, direction := range cube.Directions() { 193 chests = append(chests, Chest{Facing: direction}) 194 } 195 return 196 }