Files
ngzz-mc/internal/block/registry.go

143 lines
4.4 KiB
Go
Raw Normal View History

// Package block 方块注册表:加载 blocks.json,提供方块定义与状态查询(设计.md §6.1)。
//
// 设计要点:
// - 运行时状态 = 数字 ID(uint16)+ meta(uint8),打包为 State(uint32);
// - ID 一旦发布不可变(ID 稳定性铁律,热更新.md §4.4);
// - isSolid / isOpaque / light 等判定为「单一来源」,光照、物理、渲染共用。
package block
import (
"encoding/json"
"fmt"
"os"
)
// State 运行时方块状态:ID(低 16 位)+ meta(高 8 位)。
type State uint32
// Air 空气(ID 0)。
const Air State = 0
// NewState 打包状态。
func NewState(id uint16, meta uint8) State { return State(uint32(id) | uint32(meta)<<16) }
// ID 返回方块 ID。
func (s State) ID() uint16 { return uint16(s & 0xFFFF) }
// Meta 返回附加数据(朝向/含水/阶段等)。
func (s State) Meta() uint8 { return uint8(s >> 16) }
// Def 方块定义(blocks.json 条目,设计.md §2.3)。
type Def struct {
ID uint16 `json:"id"`
Name string `json:"-"`
Hardness float64 `json:"hardness"`
Resistance float64 `json:"resistance"`
Light uint8 `json:"light"`
Transparent bool `json:"transparent"`
Solid bool `json:"solid"`
Cutout bool `json:"cutout"`
Translucent bool `json:"translucent"`
Fluid bool `json:"fluid"`
RequiredTier int `json:"required_tier"`
Drops string `json:"drops"`
Textures map[string]string `json:"textures"`
}
// Registry 方块注册表。
type Registry struct {
byID []Def
byName map[string]uint16
}
// Load 解析 blocks.json 构建注册表(按 id 索引;ID 0 保留给空气,其余不要求连续但必须有唯一)。
func Load(path string) (*Registry, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("block.Load %s: %w", path, err)
}
var doc struct {
Blocks map[string]Def `json:"blocks"`
}
if err := json.Unmarshal(b, &doc); err != nil {
return nil, fmt.Errorf("block.Load %s: %w", path, err)
}
r := &Registry{byName: make(map[string]uint16, len(doc.Blocks))}
maxID := uint16(0)
for name, d := range doc.Blocks {
if d.ID == 0 {
return nil, fmt.Errorf("block.Load: 方块 %q 使用保留 ID 0(空气)", name)
}
if d.ID > maxID {
maxID = d.ID
}
if _, dup := r.byName[name]; dup {
return nil, fmt.Errorf("block.Load: 方块重名 %q", name)
}
r.byName[name] = d.ID
}
// 按 ID 建立索引(下标 0 = 空气零值)
r.byID = make([]Def, int(maxID)+1)
for name, d := range doc.Blocks {
d.Name = name
if r.byID[d.ID].ID != 0 {
return nil, fmt.Errorf("block.Load: ID %d 被重复使用", d.ID)
}
r.byID[d.ID] = d
}
return r, nil
}
// Get 按 ID 取定义;越界返回零值(视为空气)。
func (r *Registry) Get(id uint16) Def {
if int(id) < len(r.byID) {
return r.byID[id]
}
return Def{}
}
// ID 按名称取 ID。
func (r *Registry) ID(name string) (uint16, bool) {
id, ok := r.byName[name]
return id, ok
}
// Name 按 ID 取名称。
func (r *Registry) Name(id uint16) string { return r.Get(id).Name }
// IsSolid 实体可站立/碰撞判定(场景/物理与碰撞.md §2)。
func (r *Registry) IsSolid(s State) bool { return s != Air && r.Get(s.ID()).Solid }
// IsOpaque 完全遮光判定(场景/光照.md §7 分类表)。
func (r *Registry) IsOpaque(s State) bool {
if s == Air {
return false
}
d := r.Get(s.ID())
return !d.Transparent
}
// Light 光源亮度(0–15,场景/光照.md §4)。
func (r *Registry) Light(s State) uint8 { return r.Get(s.ID()).Light }
// IsCutout alpha test 类型(树叶/玻璃,渲染.md §6)。
func (r *Registry) IsCutout(s State) bool { return r.Get(s.ID()).Cutout }
// IsTranslucent 半透明类型(水/彩色玻璃,需要排序 pass)。
func (r *Registry) IsTranslucent(s State) bool { return r.Get(s.ID()).Translucent }
// IsFluid 液体判定(水/岩浆)。
func (r *Registry) IsFluid(s State) bool { return r.Get(s.ID()).Fluid }
// Texture 取方块某个面的纹理键(如 "up"/"side"/"all")。
func (r *Registry) Texture(s State, face string) string {
d := r.Get(s.ID())
if t, ok := d.Textures[face]; ok {
return t
}
if t, ok := d.Textures["all"]; ok {
return t
}
return "block/" + d.Name
}