// Package inventory 背包模型与规则表(场景/物品与背包.md §3、UI与输入.md §4)。 // // 规则表(CanPlace / CanMerge / Merge)为**单一来源**:客户端与服务器共用同一实现, // 防双端不一致(多人同步.md §5)。 package inventory import ( "mc/internal/item" ) // 背包槽位常量(UI还原.md §3.3:27 主区 + 9 热键栏)。 const ( HotbarSize = 9 // 热键栏槽数 MainSize = 27 // 主物品区槽数 TotalSize = HotbarSize + MainSize ) // Inventory 背包(主线程所有权,无需锁——架构.md §4)。 type Inventory struct { Slots []item.Stack // 36 槽 Selected int // 热键栏选中索引 0–8 reg *item.Registry } // New 创建空背包。 func New(reg *item.Registry) *Inventory { return &Inventory{Slots: make([]item.Stack, TotalSize), reg: reg} } // CanMerge 两栈是否可合并:同名、同耐久、数量未满(物品与背包.md §3)。 func CanMerge(a, b item.Stack) bool { if a.Empty() || b.Empty() || a.Name != b.Name { return false } if a.Durability != b.Durability { return false } return int(a.Count)+int(b.Count) <= 127 } // Merge 合并两栈:返回合并结果与剩余量(调用方负责写回槽位)。 func Merge(limit uint8, a, b item.Stack) (item.Stack, item.Stack) { total := int(a.Count) + int(b.Count) if total <= int(limit) { a.Count = uint8(total) return a, item.Stack{} } a.Count = limit b.Count = uint8(total - int(limit)) return a, b } // Add 把物品栈放入背包(先热键栏选中槽,再顺序找空/可合并槽),返回剩余。 func (inv *Inventory) Add(s item.Stack) item.Stack { if s.Empty() { return s } limit := s.Limit(inv.reg) // 第一遍:合并到已有同类 for i := range inv.Slots { if inv.Slots[i].Empty() { continue } if inv.Slots[i].Name == s.Name && inv.Slots[i].Durability == s.Durability && inv.Slots[i].Count < limit { merged, remain := Merge(limit, inv.Slots[i], s) inv.Slots[i] = merged s = remain if s.Empty() { return s } } } // 第二遍:放入空槽 for i := range inv.Slots { if inv.Slots[i].Empty() { inv.Slots[i] = s return item.Stack{} } } return s // 放不下:返回剩余 } // Remove 从背包移除指定物品 count 个;不足返回 false。 func (inv *Inventory) Remove(name string, count int) bool { if count <= 0 { return false } have := 0 for i := range inv.Slots { if inv.Slots[i].Name == name { have += int(inv.Slots[i].Count) } } if have < count { return false } left := count for i := range inv.Slots { if inv.Slots[i].Name != name { continue } if int(inv.Slots[i].Count) <= left { left -= int(inv.Slots[i].Count) inv.Slots[i] = item.Stack{} } else { inv.Slots[i].Count -= uint8(left) left = 0 } if left == 0 { return true } } return true } // SelectedStack 返回选中槽(只读)。 func (inv *Inventory) SelectedStack() item.Stack { if inv.Selected < 0 || inv.Selected >= HotbarSize { return item.Stack{} } return inv.Slots[inv.Selected] }