// Package blockentity 方块实体:箱子容器与熔炉烧炼(物品与背包.md §6)。 // // 接口约定(设计.md §6.3):Tick / Serialize / Deserialize;随区块保存。 package blockentity import ( "fmt" "mc/internal/inventory" "mc/internal/item" ) // Kind 方块实体类型。 type Kind uint8 // 方块实体类型(物品与背包.md §6)。 const ( KindChest Kind = iota // 箱子(27 格) KindFurnace // 熔炉(燃料+输入+输出) ) // Entity 方块实体。 type Entity struct { Kind Kind X, Y, Z int32 // 容器(箱子/熔炉共用) Slots []item.Stack // 0..N-1 槽位(N 按类型) // 熔炉进度(物品与背包.md §6) BurnTicks int // 剩余燃烧时间(tick) BurnMaxTicks int // 当前燃料总燃烧时间 CookTicks int // 已烧炼进度 } // 熔炉槽位(物品与背包.md §6:燃料+输入+输出)。 const ( SlotFuel = 0 SlotInput = 1 SlotOutput = 2 ) // CookTime 每炉烧炼所需 tick(10 秒)。 const CookTime = 200 // FuelTicks 燃料燃烧时长表(tick,物品与背包.md §6)。 var FuelTicks = map[string]int{ "coal": 1600, "oak_planks": 300, "oak_log": 300, "stick": 100, "crafting_table": 300, "chest": 300, } // New 创建方块实体(按类型分配槽位)。 func New(kind Kind, x, y, z int32) *Entity { e := &Entity{Kind: kind, X: x, Y: y, Z: z} switch kind { case KindChest: e.Slots = make([]item.Stack, 27) case KindFurnace: e.Slots = make([]item.Stack, 3) } return e } // NewFurnace 创建熔炉。 func NewFurnace(x, y, z int32) *Entity { return New(KindFurnace, x, y, z) } // NewChest 创建箱子。 func NewChest(x, y, z int32) *Entity { return New(KindChest, x, y, z) } // Tick 熔炉烧炼(每 tick;箱子无逻辑)。 // 产出时返回 (输入物品名, true)——输入名在消耗前捕获,供调用方查熔炼配方。 func (e *Entity) Tick() (string, bool) { if e.Kind != KindFurnace { return "", false } // 燃料耗尽则尝试补充 if e.BurnTicks <= 0 { fuel := e.Slots[SlotFuel] if !fuel.Empty() { if ticks, ok := FuelTicks[fuel.Name]; ok { e.BurnMaxTicks = ticks e.BurnTicks = ticks e.Slots[SlotFuel].Count-- if e.Slots[SlotFuel].Count == 0 { e.Slots[SlotFuel] = item.Stack{} } } } } // 燃烧中 + 输入可烧炼 → 推进度 if e.BurnTicks > 0 && !e.Slots[SlotInput].Empty() { e.BurnTicks-- e.CookTicks++ if e.CookTicks >= CookTime { e.CookTicks = 0 // 消耗前捕获输入名(产出配方查询用,物品与背包.md §6) inputName := e.Slots[SlotInput].Name e.Slots[SlotInput].Count-- if e.Slots[SlotInput].Count == 0 { e.Slots[SlotInput] = item.Stack{} } return inputName, true } } return "", false } // CanInsert 槽位插入规则(容器版:与背包规则表一致,物品与背包.md §3)。 func (e *Entity) CanInsert(slot int, s item.Stack, reg *item.Registry) bool { if slot < 0 || slot >= len(e.Slots) { return false } // 熔炉槽位限制 if e.Kind == KindFurnace { switch slot { case SlotFuel: _, ok := FuelTicks[s.Name] return ok || e.Slots[slot].Name == s.Name case SlotInput: return e.Slots[slot].Empty() || e.Slots[slot].Name == s.Name case SlotOutput: return false } } return inventory.CanMerge(e.Slots[slot], s) || e.Slots[slot].Empty() } // Insert 放入槽位(返回剩余;规则表校验由 CanInsert 先行)。 func (e *Entity) Insert(slot int, s item.Stack, reg *item.Registry) item.Stack { if slot < 0 || slot >= len(e.Slots) { return s } if e.Slots[slot].Empty() { e.Slots[slot] = s return item.Stack{} } if e.Slots[slot].Name == s.Name { limit := s.Limit(reg) merged, remain := inventory.Merge(limit, e.Slots[slot], s) e.Slots[slot] = merged return remain } return s } // Remove 从槽位取出 count 个。 func (e *Entity) Remove(slot, count int) item.Stack { if slot < 0 || slot >= len(e.Slots) || e.Slots[slot].Empty() { return item.Stack{} } s := e.Slots[slot] if count >= int(s.Count) { e.Slots[slot] = item.Stack{} return s } s.Count -= uint8(count) out := s out.Count = uint8(count) e.Slots[slot] = s return out } // Snapshot 存档快照(存档格式.md §2 block_entities)。 type Snapshot struct { X, Y, Z int32 `json:"x"` Kind string `json:"kind"` Slots []item.Stack `json:"slots,omitempty"` BurnTicks int `json:"burn_ticks,omitempty"` BurnMaxTicks int `json:"burn_max,omitempty"` CookTicks int `json:"cook_ticks,omitempty"` } // Serialize 导出快照。 func (e *Entity) Serialize() Snapshot { if e == nil { return Snapshot{} } kind := "chest" if e.Kind == KindFurnace { kind = "furnace" } slots := make([]item.Stack, len(e.Slots)) copy(slots, e.Slots) return Snapshot{ X: e.X, Y: e.Y, Z: e.Z, Kind: kind, Slots: slots, BurnTicks: e.BurnTicks, BurnMaxTicks: e.BurnMaxTicks, CookTicks: e.CookTicks, } } // Deserialize 从快照重建。 func Deserialize(s Snapshot) *Entity { kind := KindChest if s.Kind == "furnace" { kind = KindFurnace } e := New(kind, s.X, s.Y, s.Z) for i := 0; i < len(s.Slots) && i < len(e.Slots); i++ { e.Slots[i] = s.Slots[i] } e.BurnTicks, e.BurnMaxTicks, e.CookTicks = s.BurnTicks, s.BurnMaxTicks, s.CookTicks return e } // String 描述(调试)。 func (e *Entity) String() string { return fmt.Sprintf("blockentity(%d @%d,%d,%d)", e.Kind, e.X, e.Y, e.Z) }