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  }