Files
ngzz-mc/internal/blockentity/blockentity.go
2026-09-19 14:21:08 +08:00

212 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package blockentity 方块实体:箱子容器与熔炉烧炼(物品与背包.md §6
//
// 接口约定(设计.md §6.3Tick / 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 每炉烧炼所需 tick10 秒)。
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)
}