github.com/df-mc/dragonfly@v0.9.13/server/world/block_state.go (about) 1 package world 2 3 import ( 4 "bytes" 5 _ "embed" 6 "fmt" 7 "github.com/df-mc/dragonfly/server/world/chunk" 8 "github.com/sandertv/gophertunnel/minecraft/nbt" 9 "github.com/segmentio/fasthash/fnv1" 10 "math" 11 "slices" 12 "sort" 13 "strings" 14 "unsafe" 15 ) 16 17 var ( 18 //go:embed block_states.nbt 19 blockStateData []byte 20 21 blockProperties = map[string]map[string]any{} 22 // blocks holds a list of all registered Blocks indexed by their runtime ID. Blocks that were not explicitly 23 // registered are of the type unknownBlock. 24 blocks []Block 25 // customBlocks maps a custom block's identifier to a slice of custom blocks. 26 customBlocks = map[string]CustomBlock{} 27 // stateRuntimeIDs holds a map for looking up the runtime ID of a block by the stateHash it produces. 28 stateRuntimeIDs = map[stateHash]uint32{} 29 // nbtBlocks holds a list of NBTer implementations for blocks registered that implement the NBTer interface. 30 // These are indexed by their runtime IDs. Blocks that do not implement NBTer have a false value in this slice. 31 nbtBlocks []bool 32 // randomTickBlocks holds a list of RandomTicker implementations for blocks registered that implement the RandomTicker interface. 33 // These are indexed by their runtime IDs. Blocks that do not implement RandomTicker have a false value in this slice. 34 randomTickBlocks []bool 35 // liquidBlocks holds a list of Liquid implementations for blocks registered that implement the Liquid interface. 36 // These are indexed by their runtime IDs. Blocks that do not implement Liquid have a false value in this slice. 37 liquidBlocks []bool 38 // liquidDisplacingBlocks holds a list of LiquidDisplacer implementations for blocks registered that implement the LiquidDisplacer interface. 39 // These are indexed by their runtime IDs. Blocks that do not implement LiquidDisplacer have a false value in this slice. 40 liquidDisplacingBlocks []bool 41 // airRID is the runtime ID of an air block. 42 airRID uint32 43 ) 44 45 func init() { 46 dec := nbt.NewDecoder(bytes.NewBuffer(blockStateData)) 47 48 // Register all block states present in the block_states.nbt file. These are all possible options registered 49 // blocks may encode to. 50 var s blockState 51 for { 52 if err := dec.Decode(&s); err != nil { 53 break 54 } 55 registerBlockState(s, false) 56 } 57 58 chunk.RuntimeIDToState = func(runtimeID uint32) (name string, properties map[string]any, found bool) { 59 if runtimeID >= uint32(len(blocks)) { 60 return "", nil, false 61 } 62 name, properties = blocks[runtimeID].EncodeBlock() 63 return name, properties, true 64 } 65 chunk.StateToRuntimeID = func(name string, properties map[string]any) (runtimeID uint32, found bool) { 66 if rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]; ok { 67 return rid, true 68 } 69 rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(blockProperties[name])}] 70 return rid, ok 71 } 72 } 73 74 // registerBlockState registers a new blockState to the states slice. The function panics if the properties the 75 // blockState hold are invalid or if the blockState was already registered. 76 func registerBlockState(s blockState, order bool) { 77 h := stateHash{name: s.Name, properties: hashProperties(s.Properties)} 78 if _, ok := stateRuntimeIDs[h]; ok { 79 panic(fmt.Sprintf("cannot register the same state twice (%+v)", s)) 80 } 81 if _, ok := blockProperties[s.Name]; !ok { 82 blockProperties[s.Name] = s.Properties 83 } 84 rid := uint32(len(blocks)) 85 blocks = append(blocks, unknownBlock{s}) 86 if order { 87 sort.SliceStable(blocks, func(i, j int) bool { 88 nameOne, _ := blocks[i].EncodeBlock() 89 nameTwo, _ := blocks[j].EncodeBlock() 90 return nameOne != nameTwo && fnv1.HashString64(nameOne) < fnv1.HashString64(nameTwo) 91 }) 92 93 for id, b := range blocks { 94 name, properties := b.EncodeBlock() 95 i := stateHash{name: name, properties: hashProperties(properties)} 96 if name == "minecraft:air" { 97 airRID = uint32(id) 98 } 99 if i == h { 100 rid = uint32(id) 101 } 102 stateRuntimeIDs[i] = uint32(id) 103 hashes.Put(int64(b.Hash()), int64(id)) 104 } 105 } 106 107 if s.Name == "minecraft:air" { 108 airRID = rid 109 } 110 111 nbtBlocks = slices.Insert(nbtBlocks, int(rid), false) 112 randomTickBlocks = slices.Insert(randomTickBlocks, int(rid), false) 113 liquidBlocks = slices.Insert(liquidBlocks, int(rid), false) 114 liquidDisplacingBlocks = slices.Insert(liquidDisplacingBlocks, int(rid), false) 115 chunk.FilteringBlocks = slices.Insert(chunk.FilteringBlocks, int(rid), 15) 116 chunk.LightBlocks = slices.Insert(chunk.LightBlocks, int(rid), 0) 117 stateRuntimeIDs[h] = rid 118 } 119 120 // unknownBlock represents a block that has not yet been implemented. It is used for registering block 121 // states that haven't yet been added. 122 type unknownBlock struct { 123 blockState 124 } 125 126 // EncodeBlock ... 127 func (b unknownBlock) EncodeBlock() (string, map[string]any) { 128 return b.Name, b.Properties 129 } 130 131 // Model ... 132 func (unknownBlock) Model() BlockModel { 133 return unknownModel{} 134 } 135 136 // Hash ... 137 func (b unknownBlock) Hash() uint64 { 138 return math.MaxUint64 139 } 140 141 // blockState holds a combination of a name and properties, together with a version. 142 type blockState struct { 143 Name string `nbt:"name"` 144 Properties map[string]any `nbt:"states"` 145 Version int32 `nbt:"version"` 146 } 147 148 // stateHash is a struct that may be used as a map key for block states. It contains the name of the block state 149 // and an encoded version of the properties. 150 type stateHash struct { 151 name, properties string 152 } 153 154 // hashProperties produces a hash for the block properties held by the blockState. 155 func hashProperties(properties map[string]any) string { 156 if properties == nil { 157 return "" 158 } 159 keys := make([]string, 0, len(properties)) 160 for k := range properties { 161 keys = append(keys, k) 162 } 163 sort.Slice(keys, func(i, j int) bool { 164 return keys[i] < keys[j] 165 }) 166 167 var b strings.Builder 168 for _, k := range keys { 169 switch v := properties[k].(type) { 170 case bool: 171 if v { 172 b.WriteByte(1) 173 } else { 174 b.WriteByte(0) 175 } 176 case uint8: 177 b.WriteByte(v) 178 case int32: 179 a := *(*[4]byte)(unsafe.Pointer(&v)) 180 b.Write(a[:]) 181 case string: 182 b.WriteString(v) 183 default: 184 // If block encoding is broken, we want to find out as soon as possible. This saves a lot of time 185 // debugging in-game. 186 panic(fmt.Sprintf("invalid block property type %T for property %v", v, k)) 187 } 188 } 189 190 return b.String() 191 }