github.com/df-mc/dragonfly@v0.9.13/server/world/block.go (about) 1 package world 2 3 import ( 4 "fmt" 5 "github.com/brentp/intintmap" 6 "github.com/df-mc/dragonfly/server/block/cube" 7 "github.com/df-mc/dragonfly/server/block/customblock" 8 "github.com/df-mc/dragonfly/server/world/chunk" 9 "image" 10 "math" 11 "math/rand" 12 ) 13 14 // Block is a block that may be placed or found in a world. In addition, the block may also be added to an 15 // inventory: It is also an item. 16 // Every Block implementation must be able to be hashed as key in a map. 17 type Block interface { 18 // EncodeBlock encodes the block to a string ID such as 'minecraft:grass' and properties associated 19 // with the block. 20 EncodeBlock() (string, map[string]any) 21 // Hash returns a unique identifier of the block including the block states. This function is used internally to 22 // convert a block to a single integer which can be used in map lookups. The hash produced therefore does not need 23 // to match anything in the game, but it must be unique among all registered blocks. 24 // The tool in `/cmd/blockhash` may be used to automatically generate block hashes of blocks in a package. 25 Hash() uint64 26 // Model returns the BlockModel of the Block. 27 Model() BlockModel 28 } 29 30 // CustomBlock represents a block that is non-vanilla and requires a resource pack and extra steps to show it to the 31 // client. 32 type CustomBlock interface { 33 Block 34 Properties() customblock.Properties 35 } 36 37 type CustomBlockBuildable interface { 38 CustomBlock 39 // Name is the name displayed to clients using the block. 40 Name() string 41 // Geometries is the geometries for the block that define the shape of the block. If false is returned, no custom 42 // geometry will be applied. Permutation-specific geometry can be defined by returning a map of permutations to 43 // geometry. 44 Geometry() []byte 45 // Textures is a map of images indexed by their target, used to map textures on to the block. Permutation-specific 46 // textures can be defined by returning a map of permutations to textures. 47 Textures() map[string]image.Image 48 } 49 50 // Liquid represents a block that can be moved through and which can flow in the world after placement. There 51 // are two liquids in vanilla, which are lava and water. 52 type Liquid interface { 53 Block 54 // LiquidDepth returns the current depth of the liquid. 55 LiquidDepth() int 56 // SpreadDecay returns the amount of depth that is subtracted from the liquid's depth when it spreads to 57 // a next block. 58 SpreadDecay() int 59 // WithDepth returns the liquid with the depth passed. 60 WithDepth(depth int, falling bool) Liquid 61 // LiquidFalling checks if the liquid is currently considered falling down. 62 LiquidFalling() bool 63 // BlastResistance is the blast resistance of the liquid, which influences the liquid's ability to withstand an 64 // explosive blast. 65 BlastResistance() float64 66 // LiquidType returns an int unique for the liquid, used to check if two liquids are considered to be 67 // of the same type. 68 LiquidType() string 69 // Harden checks if the block should harden when looking at the surrounding blocks and sets the position 70 // to the hardened block when adequate. If the block was hardened, the method returns true. 71 Harden(pos cube.Pos, w *World, flownIntoBy *cube.Pos) bool 72 } 73 74 // hashes holds a list of runtime IDs indexed by the hash of the Block that implements the blocks pointed to by those 75 // runtime IDs. It is used to look up a block's runtime ID quickly. 76 var hashes = intintmap.New(7000, 0.999) 77 78 // RegisterBlock registers the Block passed. The EncodeBlock method will be used to encode and decode the 79 // block passed. RegisterBlock panics if the block properties returned were not valid, existing properties. 80 func RegisterBlock(b Block) { 81 name, properties := b.EncodeBlock() 82 if _, ok := b.(CustomBlock); ok { 83 registerBlockState(blockState{Name: name, Properties: properties}, true) 84 } 85 rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}] 86 if !ok { 87 // We assume all blocks must have all their states registered beforehand. Vanilla blocks will have 88 // this done through registering of all states present in the block_states.nbt file. 89 panic(fmt.Sprintf("block state returned is not registered (%v {%#v})", name, properties)) 90 } 91 if _, ok := blocks[rid].(unknownBlock); !ok { 92 panic(fmt.Sprintf("block with name and properties %v {%#v} already registered", name, properties)) 93 } 94 hash := int64(b.Hash()) 95 if other, ok := hashes.Get(hash); ok { 96 panic(fmt.Sprintf("block %#v with hash %v already registered by %#v", b, hash, blocks[other])) 97 } 98 blocks[rid] = b 99 hashes.Put(hash, int64(rid)) 100 101 if diffuser, ok := b.(lightDiffuser); ok { 102 chunk.FilteringBlocks[rid] = diffuser.LightDiffusionLevel() 103 } 104 if emitter, ok := b.(lightEmitter); ok { 105 chunk.LightBlocks[rid] = emitter.LightEmissionLevel() 106 } 107 if _, ok := b.(NBTer); ok { 108 nbtBlocks[rid] = true 109 } 110 if _, ok := b.(RandomTicker); ok { 111 randomTickBlocks[rid] = true 112 } 113 if _, ok := b.(Liquid); ok { 114 liquidBlocks[rid] = true 115 } 116 if _, ok := b.(LiquidDisplacer); ok { 117 liquidDisplacingBlocks[rid] = true 118 } 119 if c, ok := b.(CustomBlock); ok { 120 if _, ok := customBlocks[name]; !ok { 121 customBlocks[name] = c 122 } 123 } 124 } 125 126 // BlockRuntimeID attempts to return a runtime ID of a block previously registered using RegisterBlock(). 127 // If the runtime ID cannot be found because the Block wasn't registered, BlockRuntimeID will panic. 128 func BlockRuntimeID(b Block) uint32 { 129 if b == nil { 130 return airRID 131 } 132 if h := b.Hash(); h != math.MaxUint64 { 133 if rid, ok := hashes.Get(int64(h)); ok { 134 return uint32(rid) 135 } 136 panic(fmt.Sprintf("cannot find block by non-0 hash of block %#v", b)) 137 } 138 return slowBlockRuntimeID(b) 139 } 140 141 // slowBlockRuntimeID finds the runtime ID of a Block by hashing the properties produced by calling the 142 // Block.EncodeBlock method and looking it up in the stateRuntimeIDs map. 143 func slowBlockRuntimeID(b Block) uint32 { 144 name, properties := b.EncodeBlock() 145 146 rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}] 147 if !ok { 148 panic(fmt.Sprintf("cannot find block by (name + properties): %#v", b)) 149 } 150 return rid 151 } 152 153 // BlockByRuntimeID attempts to return a Block by its runtime ID. If not found, the bool returned is 154 // false. If found, the block is non-nil and the bool true. 155 func BlockByRuntimeID(rid uint32) (Block, bool) { 156 if rid >= uint32(len(blocks)) { 157 return air(), false 158 } 159 return blocks[rid], true 160 } 161 162 // BlockByName attempts to return a Block by its name and properties. If not found, the bool returned is 163 // false. 164 func BlockByName(name string, properties map[string]any) (Block, bool) { 165 rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}] 166 if !ok { 167 return nil, false 168 } 169 return blocks[rid], true 170 } 171 172 // CustomBlocks returns a map of all custom blocks registered with their names as keys. 173 func CustomBlocks() map[string]CustomBlock { 174 return customBlocks 175 } 176 177 // air returns an air block. 178 func air() Block { 179 b, _ := BlockByRuntimeID(airRID) 180 return b 181 } 182 183 // RandomTicker represents a block that executes an action when it is ticked randomly. Every 20th of a second, 184 // one random block in each sub chunk are picked to receive a random tick. 185 type RandomTicker interface { 186 // RandomTick handles a random tick of the block at the position passed. Additionally, a rand.Rand 187 // instance is passed which may be used to generate values randomly without locking. 188 RandomTick(pos cube.Pos, w *World, r *rand.Rand) 189 } 190 191 // ScheduledTicker represents a block that executes an action when it has a block update scheduled, such as 192 // when a block adjacent to it is broken. 193 type ScheduledTicker interface { 194 // ScheduledTick handles a scheduled tick initiated by an event in one of the neighbouring blocks, such as 195 // when a block is placed or broken. Additionally, a rand.Rand instance is passed which may be used to 196 // generate values randomly without locking. 197 ScheduledTick(pos cube.Pos, w *World, r *rand.Rand) 198 } 199 200 // TickerBlock is an implementation of NBTer with an additional Tick method that is called on every world 201 // tick for loaded blocks that implement this interface. 202 type TickerBlock interface { 203 NBTer 204 Tick(currentTick int64, pos cube.Pos, w *World) 205 } 206 207 // NeighbourUpdateTicker represents a block that is updated when a block adjacent to it is updated, either 208 // through placement or being broken. 209 type NeighbourUpdateTicker interface { 210 // NeighbourUpdateTick handles a neighbouring block being updated. The position of that block and the 211 // position of this block is passed. 212 NeighbourUpdateTick(pos, changedNeighbour cube.Pos, w *World) 213 } 214 215 // NBTer represents either an item or a block which may decode NBT data and encode to NBT data. Typically, 216 // this is done to store additional data. 217 type NBTer interface { 218 // DecodeNBT returns the (new) item, block or entity, depending on which of those the NBTer was, with the NBT data 219 // decoded into it. 220 DecodeNBT(data map[string]any) any 221 // EncodeNBT encodes the entity into a map which can then be encoded as NBT to be written. 222 EncodeNBT() map[string]any 223 } 224 225 // LiquidDisplacer represents a block that is able to displace a liquid to a different world layer, without 226 // fully removing the liquid. 227 type LiquidDisplacer interface { 228 // CanDisplace specifies if the block is able to displace the liquid passed. 229 CanDisplace(b Liquid) bool 230 // SideClosed checks if a position on the side of the block placed in the world at a specific position is 231 // closed. When this returns true (for example, when the side is below the position and the block is a 232 // slab), liquid inside the displacer won't flow from pos into side. 233 SideClosed(pos, side cube.Pos, w *World) bool 234 } 235 236 // lightEmitter is identical to a block.LightEmitter. 237 type lightEmitter interface { 238 LightEmissionLevel() uint8 239 } 240 241 // lightDiffuser is identical to a block.LightDiffuser. 242 type lightDiffuser interface { 243 LightDiffusionLevel() uint8 244 } 245 246 // replaceableBlock represents a block that may be replaced by another block automatically. An example is 247 // grass, which may be replaced by clicking it with another block. 248 type replaceableBlock interface { 249 // ReplaceableBy returns a bool which indicates if the block is replaceable by another block. 250 ReplaceableBy(b Block) bool 251 } 252 253 // replaceable checks if the block at the position passed is replaceable with the block passed. 254 func replaceable(w *World, c *Column, pos cube.Pos, with Block) bool { 255 if r, ok := w.blockInChunk(c, pos).(replaceableBlock); ok { 256 return r.ReplaceableBy(with) 257 } 258 return false 259 } 260 261 // BlockAction represents an action that may be performed by a block. Typically, these actions are sent to 262 // viewers in a world so that they can see these actions. 263 type BlockAction interface { 264 BlockAction() 265 }