add player package with key client-side player functionality, update protocol codecs, and refactor metadata definitions and slot usage

This commit is contained in:
2025-08-23 16:19:45 +08:00
parent cff9d4a809
commit 4528bdc86a
32 changed files with 2613 additions and 468 deletions

View File

@ -0,0 +1,13 @@
package inventory
import "github.com/Tnze/go-mc/chat"
type ContainerOpenEvent struct {
WindowID int32
Type int32
Title chat.Message
}
func (c ContainerOpenEvent) EventID() string {
return "inventory:container_open"
}

View File

@ -0,0 +1,96 @@
package inventory
import (
"context"
"git.konjactw.dev/patyhank/minego/pkg/bot"
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
"github.com/Tnze/go-mc/level/item"
)
// Container 代表一個容器
type Container struct {
containerID int32
slots []slot.Slot
c bot.Client
}
func NewContainer(c bot.Client, cID int32) *Container {
return &Container{
c: c,
containerID: cID,
slots: make([]slot.Slot, 0),
}
}
func NewContainerWithSize(c bot.Client, cID, size int32) *Container {
return &Container{
c: c,
containerID: cID,
slots: make([]slot.Slot, size),
}
}
func (c *Container) GetSlot(index int) slot.Slot {
if index < 0 || index >= len(c.slots) {
return slot.Slot{}
}
return c.slots[index]
}
func (c *Container) Slots() []slot.Slot {
return c.slots
}
func (c *Container) SlotCount() int {
return len(c.slots)
}
func (c *Container) FindEmpty() int16 {
for i, s := range c.slots {
if s.Count <= 0 {
return int16(i)
}
}
return -1
}
func (c *Container) FindItem(itemID item.ID) int16 {
for i, s := range c.slots {
if s.ItemID == itemID && s.Count > 0 {
return int16(i)
}
}
return -1
}
func (c *Container) SetSlot(index int, s slot.Slot) {
// 自動擴容
for len(c.slots) <= index {
c.slots = append(c.slots, slot.Slot{})
}
if index >= 0 && index < len(c.slots) {
c.slots[index] = s
}
}
func (c *Container) SetSlots(slots []slot.Slot) {
c.slots = make([]slot.Slot, len(slots))
copy(c.slots, slots)
}
func (c *Container) Clear() {
c.slots = make([]slot.Slot, 0)
}
func (c *Container) Click(idx int16, mode int32, button int32) error {
clickPacket := &server.ContainerClick{
WindowID: c.containerID,
StateID: c.c.Player().StateID(),
Slot: idx,
Button: int8(button),
Mode: mode,
}
return c.c.WritePacket(context.Background(), clickPacket)
}

View File

@ -0,0 +1,98 @@
package inventory
import (
"context"
"git.konjactw.dev/patyhank/minego/pkg/bot"
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
"git.konjactw.dev/patyhank/minego/pkg/protocol/slot"
)
// Manager 管理inventory和container
type Manager struct {
c bot.Client
inventory *Container
container *Container
cursor *slot.Slot
currentContainerID int32
}
func NewManager(c bot.Client) *Manager {
m := &Manager{
c: c,
inventory: NewContainerWithSize(c, 0, 45),
currentContainerID: 0,
}
bot.AddHandler(c, func(ctx context.Context, p *client.SetContainerContent) {
if p.WindowID == 0 {
m.inventory.SetSlots(p.Slots)
} else if m.container != nil {
m.container.SetSlots(p.Slots)
}
m.c.Player().UpdateStateID(p.StateID)
})
bot.AddHandler(c, func(ctx context.Context, p *client.ContainerSetSlot) {
if p.ContainerID == 0 {
m.inventory.SetSlot(int(p.Slot), p.ItemStack)
} else if m.container != nil {
m.container.SetSlot(int(p.Slot), p.ItemStack)
}
m.c.Player().UpdateStateID(p.StateID)
})
bot.AddHandler(c, func(ctx context.Context, p *client.CloseContainer) {
if p.WindowID == m.currentContainerID {
m.currentContainerID = -1
if m.container != nil {
m.container = nil
}
}
})
bot.AddHandler(c, func(ctx context.Context, p *client.OpenScreen) {
m.currentContainerID = p.WindowID
m.container = NewContainer(c, p.WindowID)
go bot.PublishEvent(m.c, ContainerOpenEvent{
WindowID: p.WindowID,
Type: p.WindowType,
Title: p.WindowTitle,
})
})
return m
}
func (m *Manager) Inventory() bot.Container {
return m.inventory
}
func (m *Manager) Container() bot.Container {
return m.container
}
func (m *Manager) Cursor() *slot.Slot {
return m.cursor
}
func (m *Manager) CurrentContainerID() int32 {
return m.currentContainerID
}
func (m *Manager) Close() {
if m.currentContainerID != -1 {
_ = m.c.WritePacket(context.Background(), &server.ContainerClose{WindowID: m.currentContainerID})
} else {
_ = m.c.WritePacket(context.Background(), &server.ContainerClose{WindowID: 0})
}
}
// Click 點擊容器slot
func (m *Manager) Click(id int32, slotIndex int16, mode int32, button int32) error {
clickPacket := &server.ContainerClick{
WindowID: id,
StateID: m.c.Player().StateID(),
Slot: slotIndex,
Button: int8(button),
Mode: mode,
}
return m.c.WritePacket(context.Background(), clickPacket)
}