Files

212 lines
5.4 KiB
Go
Raw Permalink Normal View History

// 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{
2026-09-19 14:21:08 +08:00
"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:
2026-09-19 14:21:08 +08:00
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
}
2026-09-19 14:21:08 +08:00
// 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)
}