212 lines
5.4 KiB
Go
212 lines
5.4 KiB
Go
// 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)
|
||
}
|