241 lines
5.1 KiB
Go
241 lines
5.1 KiB
Go
package screen
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
|
|
"github.com/Tnze/go-mc/bot"
|
|
"github.com/Tnze/go-mc/chat"
|
|
"github.com/Tnze/go-mc/data/packetid"
|
|
"github.com/Tnze/go-mc/nbt"
|
|
pk "github.com/Tnze/go-mc/net/packet"
|
|
)
|
|
|
|
type Manager struct {
|
|
c *bot.Client
|
|
|
|
Screens map[int]Container
|
|
Inventory Inventory
|
|
Cursor Slot
|
|
events EventsListener
|
|
// The last received State ID from server
|
|
stateID int32
|
|
}
|
|
|
|
func NewManager(c *bot.Client, e EventsListener) *Manager {
|
|
m := &Manager{
|
|
c: c,
|
|
Screens: make(map[int]Container),
|
|
events: e,
|
|
}
|
|
m.Screens[0] = &m.Inventory
|
|
c.Events.AddListener(
|
|
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundOpenScreen, F: m.onOpenScreen},
|
|
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundContainerSetContent, F: m.onSetContentPacket},
|
|
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundContainerClose, F: m.onCloseScreen},
|
|
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundContainerSetSlot, F: m.onSetSlot},
|
|
)
|
|
return m
|
|
}
|
|
|
|
type ChangedSlots map[int]*Slot
|
|
|
|
func (m *Manager) ContainerClick(id int, slot int16, button byte, mode int32, slots ChangedSlots, carried *Slot) error {
|
|
return m.c.Conn.WritePacket(pk.Marshal(
|
|
packetid.ServerboundContainerClick,
|
|
pk.UnsignedByte(id),
|
|
pk.VarInt(m.stateID),
|
|
pk.Short(slot),
|
|
pk.Byte(button),
|
|
pk.VarInt(mode),
|
|
slots,
|
|
carried,
|
|
))
|
|
}
|
|
|
|
func (c ChangedSlots) WriteTo(w io.Writer) (n int64, err error) {
|
|
n, err = pk.VarInt(len(c)).WriteTo(w)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for i, v := range c {
|
|
n1, err := pk.Short(i).WriteTo(w)
|
|
if err != nil {
|
|
return n + n1, err
|
|
}
|
|
n2, err := v.WriteTo(w)
|
|
if err != nil {
|
|
return n + n1 + n2, err
|
|
}
|
|
n += n1 + n2
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *Manager) onOpenScreen(p pk.Packet) error {
|
|
var (
|
|
ContainerID pk.VarInt
|
|
Type pk.VarInt
|
|
Title chat.Message
|
|
)
|
|
if err := p.Scan(&ContainerID, &Type, &Title); err != nil {
|
|
return Error{err}
|
|
}
|
|
if _, ok := m.Screens[int(ContainerID)]; !ok {
|
|
TypeInt32 := int32(Type)
|
|
if TypeInt32 < 6 {
|
|
Rows := TypeInt32 + 1
|
|
chest := Chest{
|
|
Type: TypeInt32,
|
|
Slots: make([]Slot, 9*Rows),
|
|
Rows: int(Rows),
|
|
Title: Title,
|
|
}
|
|
m.Screens[int(ContainerID)] = &chest
|
|
}
|
|
} else {
|
|
return errors.New("container id already exists in screens")
|
|
}
|
|
if m.events.Open != nil {
|
|
if err := m.events.Open(int(ContainerID), int32(Type), Title); err != nil {
|
|
return Error{err}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) onSetContentPacket(p pk.Packet) error {
|
|
var (
|
|
ContainerID pk.UnsignedByte
|
|
StateID pk.VarInt
|
|
SlotData []Slot
|
|
CarriedItem Slot
|
|
)
|
|
if err := p.Scan(
|
|
&ContainerID,
|
|
&StateID,
|
|
pk.Array(&SlotData),
|
|
&CarriedItem,
|
|
); err != nil {
|
|
return Error{err}
|
|
}
|
|
m.stateID = int32(StateID)
|
|
// copy the slot data to container
|
|
container, ok := m.Screens[int(ContainerID)]
|
|
if !ok {
|
|
return Error{errors.New("setting content of non-exist container")}
|
|
}
|
|
for i, v := range SlotData {
|
|
err := container.onSetSlot(i, v)
|
|
if err != nil {
|
|
return Error{err}
|
|
}
|
|
if m.events.SetSlot != nil {
|
|
if err := m.events.SetSlot(int(ContainerID), i); err != nil {
|
|
return Error{err}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) onCloseScreen(p pk.Packet) error {
|
|
var ContainerID pk.UnsignedByte
|
|
if err := p.Scan(&ContainerID); err != nil {
|
|
return Error{err}
|
|
}
|
|
if c, ok := m.Screens[int(ContainerID)]; ok {
|
|
delete(m.Screens, int(ContainerID))
|
|
if err := c.onClose(); err != nil {
|
|
return Error{err}
|
|
}
|
|
if m.events.Close != nil {
|
|
if err := m.events.Close(int(ContainerID)); err != nil {
|
|
return Error{err}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) onSetSlot(p pk.Packet) (err error) {
|
|
var (
|
|
ContainerID pk.Byte
|
|
StateID pk.VarInt
|
|
SlotID pk.Short
|
|
SlotData Slot
|
|
)
|
|
if err := p.Scan(&ContainerID, &StateID, &SlotID, &SlotData); err != nil {
|
|
return Error{err}
|
|
}
|
|
|
|
m.stateID = int32(StateID)
|
|
if ContainerID == -1 && SlotID == -1 {
|
|
m.Cursor = SlotData
|
|
} else if ContainerID == -2 {
|
|
err = m.Inventory.onSetSlot(int(SlotID), SlotData)
|
|
} else if c, ok := m.Screens[int(ContainerID)]; ok {
|
|
err = c.onSetSlot(int(SlotID), SlotData)
|
|
}
|
|
|
|
if m.events.SetSlot != nil {
|
|
if err := m.events.SetSlot(int(ContainerID), int(SlotID)); err != nil {
|
|
return Error{err}
|
|
}
|
|
}
|
|
if err != nil {
|
|
return Error{err}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Slot struct {
|
|
ID pk.VarInt
|
|
Count pk.VarInt
|
|
NBT nbt.RawMessage
|
|
}
|
|
|
|
func (s *Slot) WriteTo(w io.Writer) (n int64, err error) {
|
|
var present pk.Boolean = s != nil
|
|
return pk.Tuple{
|
|
present, pk.Opt{
|
|
Has: present,
|
|
Field: pk.Tuple{
|
|
&s.ID, &s.Count, pk.NBT(&s.NBT),
|
|
},
|
|
},
|
|
}.WriteTo(w)
|
|
}
|
|
|
|
func (s *Slot) ReadFrom(r io.Reader) (n int64, err error) {
|
|
var componentsAdd, componentsRemove pk.VarInt
|
|
return pk.Tuple{
|
|
&s.Count, pk.Opt{
|
|
Has: func() bool { return s.Count > 0 },
|
|
Field: pk.Tuple{
|
|
&s.ID,
|
|
&componentsAdd,
|
|
&componentsRemove,
|
|
// TODO: Components Ignored
|
|
},
|
|
},
|
|
}.ReadFrom(r)
|
|
}
|
|
|
|
type Container interface {
|
|
onSetSlot(i int, s Slot) error
|
|
onClose() error
|
|
}
|
|
|
|
type Error struct {
|
|
Err error
|
|
}
|
|
|
|
func (e Error) Error() string {
|
|
return "bot/screen: " + e.Err.Error()
|
|
}
|
|
|
|
func (e Error) Unwrap() error {
|
|
return e.Err
|
|
}
|