func hashProperties(properties map[string]interface{}) string {
	if properties == nil {
		return ""
	}
	keys := make([]string, 0, len(properties))
	for k := range properties {
		keys = append(keys, k)
	}
	sort.Slice(keys, func(i, j int) bool {
		return keys[i] < keys[j]
	})
	var b strings.Builder
	for _, k := range keys {
		switch v := properties[k].(type) {
		case bool:
			if v {
				b.WriteByte(1)
			} else {
				b.WriteByte(0)
			}
		case uint8:
			b.WriteByte(v)
		case int32:
			a := *(*[4]byte)(unsafe.Pointer(&v))
			b.Write(a[:])
		case string:
			b.WriteString(v)
		default:
			panic(fmt.Sprintf("invalid block property type %T for property %v", v, k))
		}
	}
	return b.String()
}
HashProperties produces a hash for the block properties held by the blockState.
package world
import (
	"bytes"
	_ "embed"
	"fmt"
	"github.com/df-mc/dragonfly/server/world/chunk"
	"github.com/sandertv/gophertunnel/minecraft/nbt"
	"math"
	"sort"
	"strings"
	"unsafe"
)
var (
	blockStateData []byte
	blocks []Block
	stateRuntimeIDs = map[stateHash]uint32{}
	nbtBlocks []bool
	randomTickBlocks []bool
	airRID uint32
)
func init() {
	dec := nbt.NewDecoder(bytes.NewBuffer(blockStateData))
	var s blockState
	for {
		if err := dec.Decode(&s); err != nil {
			break
		}
		registerBlockState(s)
	}
	chunk.RuntimeIDToState = func(runtimeID uint32) (name string, properties map[string]interface{}, found bool) {
		if runtimeID >= uint32(len(blocks)) {
			return "", nil, false
		}
		name, properties = blocks[runtimeID].EncodeBlock()
		return name, properties, true
	}
	chunk.StateToRuntimeID = func(name string, properties map[string]interface{}) (runtimeID uint32, found bool) {
		rid, ok := stateRuntimeIDs[stateHash{name: name, properties: hashProperties(properties)}]
		return rid, ok
	}
}
func registerBlockState(s blockState) {
	h := stateHash{name: s.Name, properties: hashProperties(s.Properties)}
	if _, ok := stateRuntimeIDs[h]; ok {
		panic(fmt.Sprintf("cannot register the same state twice (%+v)", s))
	}
	rid := uint32(len(blocks))
	if s.Name == "minecraft:air" {
		airRID = rid
	}
	stateRuntimeIDs[h] = rid
	blocks = append(blocks, unknownBlock{s})
	nbtBlocks = append(nbtBlocks, false)
	randomTickBlocks = append(randomTickBlocks, false)
	chunk.FilteringBlocks = append(chunk.FilteringBlocks, 15)
	chunk.LightBlocks = append(chunk.LightBlocks, 0)
}
type unknownBlock struct {
	blockState
}
func (b unknownBlock) EncodeBlock() (string, map[string]interface{}) {
	return b.Name, b.Properties
}
func (unknownBlock) Model() BlockModel {
	return unknownModel{}
}
func (b unknownBlock) Hash() uint64 {
	return math.MaxUint64
}
type blockState struct {
	Name       string                 `nbt:"name"`
	Properties map[string]interface{} `nbt:"states"`
	Version    int32                  `nbt:"version"`
}
type stateHash struct {
	name, properties string
}