542 lines
19 KiB
Go
542 lines
19 KiB
Go
|
|
package world
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"maps"
|
||
|
|
"math"
|
||
|
|
"math/bits"
|
||
|
|
"slices"
|
||
|
|
"sort"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/brentp/intintmap"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/chunk"
|
||
|
|
"github.com/segmentio/fasthash/fnv1"
|
||
|
|
)
|
||
|
|
|
||
|
|
// DefaultBlockRegistry is the default (vanilla) block registry used by Dragonfly when no custom registry is provided.
|
||
|
|
//
|
||
|
|
// The registry is populated during package init (block states + block implementations) and is finalized on first use
|
||
|
|
// by `server.Config.New()`, `world.Config.New()`, or `mcdb.Config.Open()`. Callers that need custom blocks should
|
||
|
|
// create a new registry using `NewBlockRegistry()` and pass it through config instead of mutating this value.
|
||
|
|
var DefaultBlockRegistry = &BasicBlockRegistry{
|
||
|
|
blockProperties: make(map[string]map[string]any),
|
||
|
|
stateRuntimeIDs: make(map[stateHash]uint32),
|
||
|
|
customBlocks: make(map[string]CustomBlock),
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlockRegistry converts between runtime IDs and block states/implementations.
|
||
|
|
//
|
||
|
|
// A BlockRegistry has a build/finalize lifecycle:
|
||
|
|
// - During setup, blocks and block states may be registered using RegisterBlockState/RegisterBlock.
|
||
|
|
// - After calling Finalize, the registry becomes immutable and is ready for use in chunk encoding/decoding and
|
||
|
|
// network serialization. RegisterBlock/RegisterBlockState will panic after finalization.
|
||
|
|
//
|
||
|
|
// The interface is split because the chunk package cannot import world.Block.
|
||
|
|
type BlockRegistry interface {
|
||
|
|
chunk.BlockRegistry
|
||
|
|
|
||
|
|
// BlockByRuntimeID looks up a Block by runtime ID. If the runtime ID is unknown/out of range, ok is false.
|
||
|
|
BlockByRuntimeID(rid uint32) (Block, bool)
|
||
|
|
// BlockByRuntimeIDOrAir looks up a Block by runtime ID. If not found, an air block is returned.
|
||
|
|
BlockByRuntimeIDOrAir(rid uint32) Block
|
||
|
|
// BlockRuntimeID looks up the runtime ID of a previously registered Block.
|
||
|
|
BlockRuntimeID(block Block) (rid uint32)
|
||
|
|
// RegisterBlock registers a Block implementation for a previously registered block state.
|
||
|
|
RegisterBlock(block Block)
|
||
|
|
// RegisterBlockState registers a block state that blocks may encode to.
|
||
|
|
RegisterBlockState(blockState BlockState)
|
||
|
|
// CustomBlocks returns custom blocks registered in this registry, keyed by identifier.
|
||
|
|
CustomBlocks() map[string]CustomBlock
|
||
|
|
// BlockByName looks up a Block by full identifier and properties.
|
||
|
|
BlockByName(name string, properties map[string]any) (Block, bool)
|
||
|
|
// Blocks returns all blocks registered in the registry, indexed by runtime ID.
|
||
|
|
Blocks() []Block
|
||
|
|
// Air returns the air block registered in the registry.
|
||
|
|
Air() Block
|
||
|
|
// Finalize finalizes the registry, building derived lookup tables required for runtime usage. Finalize is
|
||
|
|
// idempotent.
|
||
|
|
Finalize()
|
||
|
|
// BitSize returns the number of bits used by BlockHash (depends on the number of registered blocks).
|
||
|
|
BitSize() int
|
||
|
|
// BlockHash returns a unique identifier of the block including the block states. The hash is internal to Dragonfly
|
||
|
|
// and is used for fast map lookups; it does not need to match any in-game identifiers.
|
||
|
|
BlockHash(b Block) uint64
|
||
|
|
// RuntimeIDToHash resolves a runtime ID to its network block hash.
|
||
|
|
RuntimeIDToHash(runtimeID uint32) (hash uint32, ok bool)
|
||
|
|
}
|
||
|
|
|
||
|
|
const (
|
||
|
|
blockFlagNBT uint16 = 1 << iota
|
||
|
|
blockFlagRandomTick
|
||
|
|
blockFlagLiquid
|
||
|
|
blockFlagLiquidDisplacing
|
||
|
|
)
|
||
|
|
|
||
|
|
// blockInfo is a packed set of per-runtime-ID properties used in hot paths (lighting, ticking, fluid checks, etc.).
|
||
|
|
//
|
||
|
|
// Layout (uint16):
|
||
|
|
// - bits 0..3: boolean flags (blockFlag*).
|
||
|
|
// - bits 8..11: 4-bit light emission level (0-15).
|
||
|
|
// - bits 12..15: 4-bit light filtering level (0-15).
|
||
|
|
//
|
||
|
|
// Values are computed during BlockRegistry.Finalize() and stored in BasicBlockRegistry.blockInfos, indexed by runtime ID.
|
||
|
|
type blockInfo uint16
|
||
|
|
|
||
|
|
func (b *blockInfo) set(flag uint16) {
|
||
|
|
*b |= blockInfo(flag)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b blockInfo) get(flag uint16) bool {
|
||
|
|
return uint16(b)&flag != 0
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *blockInfo) setLight(light uint8) {
|
||
|
|
// Overwrite the 4-bit light emission field.
|
||
|
|
*b &^= blockInfo(0xF) << 8
|
||
|
|
*b |= blockInfo(light&0xF) << 8
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *blockInfo) setLightFilter(light uint8) {
|
||
|
|
// Overwrite the 4-bit light filtering field.
|
||
|
|
*b &^= blockInfo(0xF) << 12
|
||
|
|
*b |= blockInfo(light&0xF) << 12
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b blockInfo) getLight() uint8 {
|
||
|
|
return uint8((b >> 8) & 0xF)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b blockInfo) getLightFilter() uint8 {
|
||
|
|
return uint8((b >> 12) & 0xF)
|
||
|
|
}
|
||
|
|
|
||
|
|
// BasicBlockRegistry is the default BlockRegistry implementation used by Dragonfly.
|
||
|
|
type BasicBlockRegistry struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
|
||
|
|
finalized bool
|
||
|
|
bitSize int
|
||
|
|
hashes *intintmap.Map
|
||
|
|
networkhashToRids map[uint32]uint32
|
||
|
|
ridsToNetworkhash []uint32
|
||
|
|
|
||
|
|
// stateRuntimeIDs holds a map for looking up the runtime ID of a block by the stateHash it produces.
|
||
|
|
stateRuntimeIDs map[stateHash]uint32
|
||
|
|
|
||
|
|
blockProperties map[string]map[string]any
|
||
|
|
// blocks holds a list of all registered Blocks indexed by their runtime ID. Blocks that were not explicitly
|
||
|
|
// registered are of the type unknownBlock.
|
||
|
|
blocks []Block
|
||
|
|
// customBlocks maps a custom block's identifier to the custom block.
|
||
|
|
customBlocks map[string]CustomBlock
|
||
|
|
|
||
|
|
blockInfos []blockInfo
|
||
|
|
|
||
|
|
airRID uint32
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) BitSize() int {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.BitSize called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.bitSize
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) BlockCount() int {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.BlockCount called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return len(br.blockInfos)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) RandomTickBlock(rid uint32) bool {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.RandomTickBlock called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.blockInfos[rid].get(blockFlagRandomTick)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) FilteringBlock(rid uint32) uint8 {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.FilteringBlock called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.blockInfos[rid].getLightFilter()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) LightBlock(rid uint32) uint8 {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.LightBlock called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.blockInfos[rid].getLight()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) NBTBlock(rid uint32) bool {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.NBTBlock called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.blockInfos[rid].get(blockFlagNBT)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) LiquidDisplacingBlock(rid uint32) bool {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.LiquidDisplacingBlock called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.blockInfos[rid].get(blockFlagLiquidDisplacing)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) LiquidBlock(rid uint32) bool {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.LiquidBlock called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.blockInfos[rid].get(blockFlagLiquid)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) Blocks() []Block {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.Blocks called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return slices.Clone(br.blocks)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) HashToRuntimeID(hash uint32) (rid uint32, ok bool) {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.HashToRuntimeID called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
rid, ok = br.networkhashToRids[hash]
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) RuntimeIDToHash(runtimeID uint32) (hash uint32, ok bool) {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.RuntimeIDToHash called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
if runtimeID >= uint32(len(br.ridsToNetworkhash)) {
|
||
|
|
return 0, false
|
||
|
|
}
|
||
|
|
return br.ridsToNetworkhash[runtimeID], true
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clone returns an independent copy of the registry. If the source registry is finalized, the clone is also finalized.
|
||
|
|
// If the source is not finalized, the clone remains mutable.
|
||
|
|
func (br *BasicBlockRegistry) Clone() *BasicBlockRegistry {
|
||
|
|
br.mu.Lock()
|
||
|
|
defer br.mu.Unlock()
|
||
|
|
|
||
|
|
br2 := &BasicBlockRegistry{
|
||
|
|
blockProperties: make(map[string]map[string]any, len(br.blockProperties)),
|
||
|
|
stateRuntimeIDs: make(map[stateHash]uint32, len(br.stateRuntimeIDs)),
|
||
|
|
customBlocks: make(map[string]CustomBlock, len(br.customBlocks)),
|
||
|
|
finalized: br.finalized,
|
||
|
|
bitSize: br.bitSize,
|
||
|
|
airRID: br.airRID,
|
||
|
|
}
|
||
|
|
|
||
|
|
for k, v := range br.blockProperties {
|
||
|
|
br2.blockProperties[k] = maps.Clone(v)
|
||
|
|
}
|
||
|
|
maps.Copy(br2.stateRuntimeIDs, br.stateRuntimeIDs)
|
||
|
|
maps.Copy(br2.customBlocks, br.customBlocks)
|
||
|
|
|
||
|
|
br2.blocks = make([]Block, len(br.blocks))
|
||
|
|
copy(br2.blocks, br.blocks)
|
||
|
|
br2.blockInfos = append([]blockInfo(nil), br.blockInfos...)
|
||
|
|
|
||
|
|
if br.finalized {
|
||
|
|
br2.hashes = intintmap.New(len(br.blocks), 0.999)
|
||
|
|
for rid, b := range br2.blocks {
|
||
|
|
if _, hash := b.Hash(); hash == math.MaxUint64 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
br2.hashes.Put(int64(br2.BlockHash(b)), int64(rid))
|
||
|
|
}
|
||
|
|
br2.networkhashToRids = make(map[uint32]uint32, len(br.networkhashToRids))
|
||
|
|
maps.Copy(br2.networkhashToRids, br.networkhashToRids)
|
||
|
|
br2.ridsToNetworkhash = append([]uint32(nil), br.ridsToNetworkhash...)
|
||
|
|
}
|
||
|
|
|
||
|
|
return br2
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewBlockRegistry returns a mutable registry seeded with all vanilla block states and block implementations.
|
||
|
|
// Callers may RegisterBlockState/RegisterBlock and must call Finalize() before using the registry for world/chunk
|
||
|
|
// serialization. The returned registry is independent from DefaultBlockRegistry.
|
||
|
|
func NewBlockRegistry() BlockRegistry {
|
||
|
|
// Clone() produces a fully populated registry. We then mark it mutable again so that additional block states and
|
||
|
|
// blocks can be registered before finalizing.
|
||
|
|
br := DefaultBlockRegistry.Clone()
|
||
|
|
br.finalized = false
|
||
|
|
br.bitSize = 0
|
||
|
|
br.hashes = nil
|
||
|
|
br.networkhashToRids = nil
|
||
|
|
br.ridsToNetworkhash = nil
|
||
|
|
br.blockInfos = nil
|
||
|
|
return br
|
||
|
|
}
|
||
|
|
|
||
|
|
// RegisterBlock registers the Block passed. The EncodeBlock method will be used to encode and decode the
|
||
|
|
// block passed. RegisterBlock panics if the block properties returned were not valid, existing properties.
|
||
|
|
func (br *BasicBlockRegistry) RegisterBlock(b Block) {
|
||
|
|
br.mu.Lock()
|
||
|
|
defer br.mu.Unlock()
|
||
|
|
|
||
|
|
if br.finalized {
|
||
|
|
panic("BlockRegistry.RegisterBlock called on finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
name, properties := b.EncodeBlock()
|
||
|
|
if _, ok := b.(CustomBlock); ok {
|
||
|
|
br.registerBlockStateLocked(BlockState{Name: name, Properties: properties})
|
||
|
|
}
|
||
|
|
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
|
||
|
|
if !ok {
|
||
|
|
// We assume all blocks must have all their states registered beforehand. Vanilla blocks will have
|
||
|
|
// this done through registering of all states present in the block_states.nbt file.
|
||
|
|
panic(fmt.Sprintf("block state returned is not registered (%v {%#v})", name, properties))
|
||
|
|
}
|
||
|
|
if _, ok := br.blocks[rid].(unknownBlock); !ok {
|
||
|
|
panic(fmt.Sprintf("block with name and properties %v {%#v} already registered", name, properties))
|
||
|
|
}
|
||
|
|
br.blocks[rid] = b
|
||
|
|
if c, ok := b.(CustomBlock); ok {
|
||
|
|
if _, ok := br.customBlocks[name]; !ok {
|
||
|
|
br.customBlocks[name] = c
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// RegisterBlockState registers a BlockState to the registry. The function panics if the properties the
|
||
|
|
// BlockState holds are invalid or if the BlockState was already registered.
|
||
|
|
func (br *BasicBlockRegistry) RegisterBlockState(s BlockState) {
|
||
|
|
br.mu.Lock()
|
||
|
|
defer br.mu.Unlock()
|
||
|
|
br.registerBlockStateLocked(s)
|
||
|
|
}
|
||
|
|
|
||
|
|
// registerBlockStateLocked is the implementation of RegisterBlockState. br.mu must be held by the caller.
|
||
|
|
func (br *BasicBlockRegistry) registerBlockStateLocked(s BlockState) {
|
||
|
|
if br.finalized {
|
||
|
|
panic("BlockRegistry.RegisterBlockState called on finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
h := stateHash{name: s.Name, properties: hashProperties(s.Properties)}
|
||
|
|
if _, ok := br.stateRuntimeIDs[h]; ok {
|
||
|
|
panic(fmt.Sprintf("cannot register the same state twice (%+v)", s))
|
||
|
|
}
|
||
|
|
if _, ok := br.blockProperties[s.Name]; !ok {
|
||
|
|
br.blockProperties[s.Name] = s.Properties
|
||
|
|
}
|
||
|
|
rid := uint32(len(br.blocks))
|
||
|
|
br.blocks = append(br.blocks, unknownBlock{BlockState: s})
|
||
|
|
br.stateRuntimeIDs[h] = rid
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) Finalize() {
|
||
|
|
br.mu.Lock()
|
||
|
|
defer br.mu.Unlock()
|
||
|
|
|
||
|
|
if br.finalized {
|
||
|
|
// Finalize is intentionally idempotent so code can defensively call it when a registry is provided through
|
||
|
|
// config. RegisterBlock/RegisterBlockState still panic after finalization.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
br.bitSize = bits.Len64(uint64(len(br.blocks)))
|
||
|
|
sort.SliceStable(br.blocks, func(i, j int) bool {
|
||
|
|
var nameOne string
|
||
|
|
if b1, ok := br.blocks[i].(unknownBlock); ok {
|
||
|
|
nameOne = b1.Name
|
||
|
|
} else {
|
||
|
|
nameOne, _ = br.blocks[i].EncodeBlock()
|
||
|
|
}
|
||
|
|
var nameTwo string
|
||
|
|
if b2, ok := br.blocks[j].(unknownBlock); ok {
|
||
|
|
nameTwo = b2.Name
|
||
|
|
} else {
|
||
|
|
nameTwo, _ = br.blocks[j].EncodeBlock()
|
||
|
|
}
|
||
|
|
return fnv1.HashString64(nameOne) < fnv1.HashString64(nameTwo)
|
||
|
|
})
|
||
|
|
|
||
|
|
br.blockInfos = make([]blockInfo, len(br.blocks))
|
||
|
|
br.hashes = intintmap.New(len(br.blocks), 0.999)
|
||
|
|
br.networkhashToRids = make(map[uint32]uint32, len(br.blocks))
|
||
|
|
br.ridsToNetworkhash = make([]uint32, len(br.blocks))
|
||
|
|
br.stateRuntimeIDs = make(map[stateHash]uint32, len(br.blocks))
|
||
|
|
networkHashScratch := make([]byte, 0, 0xff)
|
||
|
|
foundAir := false
|
||
|
|
|
||
|
|
for idx, b := range br.blocks {
|
||
|
|
rid := uint32(idx)
|
||
|
|
name, properties := b.EncodeBlock()
|
||
|
|
h := stateHash{name: name, properties: hashProperties(properties)}
|
||
|
|
if name == "minecraft:air" {
|
||
|
|
br.airRID = rid
|
||
|
|
foundAir = true
|
||
|
|
}
|
||
|
|
if _, ok := br.stateRuntimeIDs[h]; ok {
|
||
|
|
panic(fmt.Sprintf("cannot register the same state twice (%s %+v)", name, properties))
|
||
|
|
}
|
||
|
|
br.stateRuntimeIDs[h] = rid
|
||
|
|
|
||
|
|
var info blockInfo
|
||
|
|
// Default to fully opaque. Blocks that implement lightDiffuser may override this (e.g., air -> 0, leaves -> 1-14).
|
||
|
|
info.setLightFilter(15)
|
||
|
|
if diffuser, ok := b.(lightDiffuser); ok {
|
||
|
|
info.setLightFilter(diffuser.LightDiffusionLevel())
|
||
|
|
}
|
||
|
|
if emitter, ok := b.(lightEmitter); ok {
|
||
|
|
info.setLight(emitter.LightEmissionLevel())
|
||
|
|
}
|
||
|
|
if _, ok := b.(NBTer); ok {
|
||
|
|
info.set(blockFlagNBT)
|
||
|
|
}
|
||
|
|
if _, ok := b.(RandomTicker); ok {
|
||
|
|
info.set(blockFlagRandomTick)
|
||
|
|
}
|
||
|
|
if _, ok := b.(Liquid); ok {
|
||
|
|
info.set(blockFlagLiquid)
|
||
|
|
}
|
||
|
|
if _, ok := b.(LiquidDisplacer); ok {
|
||
|
|
info.set(blockFlagLiquidDisplacing)
|
||
|
|
}
|
||
|
|
br.blockInfos[rid] = info
|
||
|
|
|
||
|
|
if _, hash := b.Hash(); hash != math.MaxUint64 {
|
||
|
|
h := int64(br.BlockHash(b))
|
||
|
|
if other, ok := br.hashes.Get(h); ok {
|
||
|
|
panic(fmt.Sprintf("block %#v with hash %v already registered by %#v", b, h, br.blocks[other]))
|
||
|
|
}
|
||
|
|
br.hashes.Put(h, int64(rid))
|
||
|
|
}
|
||
|
|
var netHash uint32
|
||
|
|
netHash, networkHashScratch = networkBlockHash(name, properties, networkHashScratch)
|
||
|
|
if other, ok := br.networkhashToRids[netHash]; ok {
|
||
|
|
otherName, otherProperties := br.blocks[other].EncodeBlock()
|
||
|
|
panic(fmt.Sprintf("network block hash collision for (%s %+v) and (%s %+v)", name, properties, otherName, otherProperties))
|
||
|
|
}
|
||
|
|
br.networkhashToRids[netHash] = rid
|
||
|
|
br.ridsToNetworkhash[rid] = netHash
|
||
|
|
}
|
||
|
|
if !foundAir {
|
||
|
|
panic("BlockRegistry.Finalize: no minecraft:air block state registered")
|
||
|
|
}
|
||
|
|
br.finalized = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// AirRuntimeID returns the runtime ID of the air block.
|
||
|
|
func (br *BasicBlockRegistry) AirRuntimeID() uint32 {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.AirRuntimeID called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
return br.airRID
|
||
|
|
}
|
||
|
|
|
||
|
|
// RuntimeIDToState returns the name and state properties of a block by its runtime ID.
|
||
|
|
func (br *BasicBlockRegistry) RuntimeIDToState(runtimeID uint32) (name string, properties map[string]any, found bool) {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.RuntimeIDToState called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
if runtimeID >= uint32(len(br.blocks)) {
|
||
|
|
return "", nil, false
|
||
|
|
}
|
||
|
|
name, properties = br.blocks[runtimeID].EncodeBlock()
|
||
|
|
return name, properties, true
|
||
|
|
}
|
||
|
|
|
||
|
|
// StateToRuntimeID returns the runtime ID of a block by its name and state properties.
|
||
|
|
func (br *BasicBlockRegistry) StateToRuntimeID(name string, properties map[string]any) (runtimeID uint32, found bool) {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.StateToRuntimeID called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
if rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]; ok {
|
||
|
|
return rid, true
|
||
|
|
}
|
||
|
|
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(br.blockProperties[name])}]
|
||
|
|
return rid, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlockHash returns a unique identifier of the block including the block states. This function is used internally
|
||
|
|
// to convert a block to a single integer which can be used in map lookups. The hash produced therefore does not
|
||
|
|
// need to match anything in the game, but it must be unique among all registered blocks.
|
||
|
|
// The tool in `/cmd/blockhash` may be used to automatically generate block hashes of blocks in a package.
|
||
|
|
func (br *BasicBlockRegistry) BlockHash(b Block) uint64 {
|
||
|
|
base, hash := b.Hash()
|
||
|
|
return base | (hash << uint64(br.bitSize))
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlockRuntimeID attempts to return a runtime ID of a block previously registered using RegisterBlock().
|
||
|
|
// If the runtime ID cannot be found because the Block wasn't registered, BlockRuntimeID will panic.
|
||
|
|
func (br *BasicBlockRegistry) BlockRuntimeID(b Block) uint32 {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.BlockRuntimeID called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
if b == nil {
|
||
|
|
return br.airRID
|
||
|
|
}
|
||
|
|
if _, hash := b.Hash(); hash != math.MaxUint64 {
|
||
|
|
// b is not an unknownBlock.
|
||
|
|
if rid, ok := br.hashes.Get(int64(br.BlockHash(b))); ok {
|
||
|
|
return uint32(rid)
|
||
|
|
}
|
||
|
|
panic(fmt.Sprintf("cannot find block by non-0 hash of block %#v", b))
|
||
|
|
}
|
||
|
|
return br.slowBlockRuntimeID(b)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (br *BasicBlockRegistry) BlockByRuntimeIDOrAir(rid uint32) Block {
|
||
|
|
bl, _ := br.BlockByRuntimeID(rid)
|
||
|
|
return bl
|
||
|
|
}
|
||
|
|
|
||
|
|
// slowBlockRuntimeID finds the runtime ID of a Block by hashing the properties produced by calling the
|
||
|
|
// Block.EncodeBlock method and looking it up in the stateRuntimeIDs map.
|
||
|
|
func (br *BasicBlockRegistry) slowBlockRuntimeID(b Block) uint32 {
|
||
|
|
name, properties := b.EncodeBlock()
|
||
|
|
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
|
||
|
|
if !ok {
|
||
|
|
panic(fmt.Sprintf("cannot find block by (name + properties): %#v", b))
|
||
|
|
}
|
||
|
|
return rid
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlockByRuntimeID attempts to return a Block by its runtime ID. If not found, the bool returned is
|
||
|
|
// false. If found, the block is non-nil and the bool true.
|
||
|
|
func (br *BasicBlockRegistry) BlockByRuntimeID(rid uint32) (Block, bool) {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.BlockByRuntimeID called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
if rid >= uint32(len(br.blocks)) {
|
||
|
|
return br.Air(), false
|
||
|
|
}
|
||
|
|
return br.blocks[rid], true
|
||
|
|
}
|
||
|
|
|
||
|
|
// BlockByName attempts to return a Block by its name and properties. If not found, the bool returned is
|
||
|
|
// false.
|
||
|
|
func (br *BasicBlockRegistry) BlockByName(name string, properties map[string]any) (Block, bool) {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.BlockByName called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
rid, ok := br.stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
|
||
|
|
if !ok {
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
return br.blocks[rid], true
|
||
|
|
}
|
||
|
|
|
||
|
|
// CustomBlocks returns a map of all custom blocks registered with their names as keys.
|
||
|
|
func (br *BasicBlockRegistry) CustomBlocks() map[string]CustomBlock {
|
||
|
|
return maps.Clone(br.customBlocks)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Air returns an air block.
|
||
|
|
func (br *BasicBlockRegistry) Air() Block {
|
||
|
|
if !br.finalized {
|
||
|
|
panic("BlockRegistry.Air called on non finalized BlockRegistry")
|
||
|
|
}
|
||
|
|
if br.airRID >= uint32(len(br.blocks)) {
|
||
|
|
// This should never happen for a valid registry (Finalize enforces air exists).
|
||
|
|
panic("BlockRegistry.Air: air runtime ID out of range")
|
||
|
|
}
|
||
|
|
return br.blocks[br.airRID]
|
||
|
|
}
|