update 1.19.2 bot, chat support

This commit is contained in:
Tnze
2022-11-26 15:58:31 +08:00
parent 6a3589ad61
commit 7814e7b1ab
20 changed files with 532 additions and 136 deletions

View File

@ -20,7 +20,7 @@
// - HealthChange
// - Death
//
// You must manully attach the [EventsListener] to the [Client] as needed.
// You must manually attach the [EventsListener] to the [Client] as needed.
package basic
import (
@ -38,22 +38,23 @@ type Player struct {
isSpawn bool
}
func NewPlayer(c *bot.Client, settings Settings) *Player {
b := &Player{c: c, Settings: settings}
func NewPlayer(c *bot.Client, settings Settings, events EventsListener) *Player {
p := &Player{c: c, Settings: settings}
c.Events.AddListener(
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundLogin, F: b.handleLoginPacket},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundKeepAlive, F: b.handleKeepAlivePacket},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundPlayerPosition, F: b.handlePlayerPosition},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundRespawn, F: b.handleRespawnPacket},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundLogin, F: p.handleLoginPacket},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundKeepAlive, F: p.handleKeepAlivePacket},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundPlayerPosition, F: p.handlePlayerPosition},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundRespawn, F: p.handleRespawnPacket},
)
return b
events.attach(p)
return p
}
func (p *Player) Respawn() error {
const PerformRespawn = 0
err := p.c.Conn.WritePacket(pk.Marshal(
packetid.ServerboundClientCommand,
int32(packetid.ServerboundClientCommand),
pk.VarInt(PerformRespawn),
))
if err != nil {

View File

@ -5,35 +5,30 @@ import (
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
"io"
)
type EventsListener struct {
GameStart func() error
ChatMsg func(c *PlayerMessage) error
SystemMsg func(c chat.Message, overlay bool) error
Disconnect func(reason chat.Message) error
HealthChange func(health float32) error
Death func() error
}
// Attach your event listener to the client.
// attach your event listener to the client.
// The functions are copied when attaching, and modify on [EventListener] doesn't affect after that.
func (e EventsListener) Attach(c *bot.Client) {
func (e EventsListener) attach(p *Player) {
if e.GameStart != nil {
attachJoinGameHandler(c, e.GameStart)
}
if e.ChatMsg != nil {
attachPlayerMsg(c, e.ChatMsg)
attachJoinGameHandler(p.c, e.GameStart)
}
if e.SystemMsg != nil {
attachSystemMsg(c, e.SystemMsg)
attachSystemMsg(p.c, e.SystemMsg)
}
if e.Disconnect != nil {
attachDisconnect(c, e.Disconnect)
attachDisconnect(p.c, e.Disconnect)
}
if e.HealthChange != nil || e.Death != nil {
attachUpdateHealth(c, e.HealthChange, e.Death)
attachUpdateHealth(p.c, e.HealthChange, e.Death)
}
}
@ -46,60 +41,6 @@ func attachJoinGameHandler(c *bot.Client, handler func() error) {
})
}
type PlayerMessage struct {
}
func (p *PlayerMessage) ReadFrom(r io.Reader) (n int64, err error) {
// SignedMessageHeader
// MessageSignature
// SignedMessageBody
// Optional<Component>
// FilterMask
panic("implement me")
}
type ChatType struct {
ID int32
Name chat.Message
TargetName *chat.Message
}
func (c *ChatType) ReadFrom(r io.Reader) (n int64, err error) {
var hasTargetName pk.Boolean
n1, err := (*pk.VarInt)(&c.ID).ReadFrom(r)
if err != nil {
return n1, err
}
n2, err := c.Name.ReadFrom(r)
if err != nil {
return n1 + n2, err
}
n3, err := hasTargetName.ReadFrom(r)
if err != nil {
return n1 + n2 + n3, err
}
if hasTargetName {
c.TargetName = new(chat.Message)
n4, err := c.TargetName.ReadFrom(r)
return n1 + n2 + n3 + n4, err
}
return n1 + n2 + n3, nil
}
func attachPlayerMsg(c *bot.Client, handler func(c *PlayerMessage) error) {
c.Events.AddListener(bot.PacketHandler{
Priority: 64, ID: packetid.ClientboundPlayerChat,
F: func(p pk.Packet) error {
var message PlayerMessage
var chatType ChatType
if err := p.Scan(&message, &chatType); err != nil {
return Error{err}
}
return handler(&message)
},
})
}
func attachSystemMsg(c *bot.Client, handler func(c chat.Message, overlay bool) error) {
c.Events.AddListener(bot.PacketHandler{
Priority: 64, ID: packetid.ClientboundSystemChat,

View File

@ -1,6 +1,7 @@
package basic
import (
"github.com/Tnze/go-mc/chat"
"unsafe"
"github.com/Tnze/go-mc/data/packetid"
@ -46,9 +47,14 @@ type Dimension struct {
MonsterSpawnBlockLightLimit int32 `nbt:"monster_spawn_block_light_limit"`
}
type ChatType struct {
Chat chat.Decoration `nbt:"chat"`
Narration chat.Decoration `nbt:"narration"`
}
type RegistryCodec struct {
// What is Below? (wiki.vg)
ChatType Registry[nbt.RawMessage] `nbt:"minecraft:chat_type"`
ChatType Registry[ChatType] `nbt:"minecraft:chat_type"`
DimensionType Registry[Dimension] `nbt:"minecraft:dimension_type"`
WorldGenBiome Registry[nbt.RawMessage] `nbt:"minecraft:worldgen/biome"`
}
@ -71,6 +77,18 @@ func (r *Registry[E]) Find(name string) *E {
return nil
}
func (r *Registry[E]) FindByID(id int32) *E {
if id >= 0 && id < int32(len(r.Value)) && r.Value[id].ID == id {
return &r.Value[id].Element
}
for i := range r.Value {
if r.Value[i].ID == id {
return &r.Value[i].Element
}
}
return nil
}
type PlayerInfo struct {
EID int32 // The player's Entity ID (EID).
Hardcore bool // Is hardcore
@ -106,7 +124,7 @@ func (p *Player) handleLoginPacket(packet pk.Packet) error {
return Error{err}
}
err = p.c.Conn.WritePacket(pk.Marshal( //PluginMessage packet
packetid.ServerboundCustomPayload,
int32(packetid.ServerboundCustomPayload),
pk.Identifier("minecraft:brand"),
pk.String(p.Settings.Brand),
))
@ -115,7 +133,7 @@ func (p *Player) handleLoginPacket(packet pk.Packet) error {
}
err = p.c.Conn.WritePacket(pk.Marshal(
packetid.ServerboundClientInformation, // Client settings
int32(packetid.ServerboundClientInformation), // Client settings
pk.String(p.Settings.Locale),
pk.Byte(p.Settings.ViewDistance),
pk.VarInt(p.Settings.ChatMode),

View File

@ -12,7 +12,7 @@ func (p Player) handleKeepAlivePacket(packet pk.Packet) error {
}
// Response
err := p.c.Conn.WritePacket(pk.Packet{
ID: packetid.ServerboundKeepAlive,
ID: int32(packetid.ServerboundKeepAlive),
Data: packet.Data,
})
if err != nil {

168
bot/chat/chat.go Normal file
View File

@ -0,0 +1,168 @@
package chat
import (
"fmt"
"io"
"time"
"github.com/Tnze/go-mc/bot"
"github.com/Tnze/go-mc/bot/basic"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/google/uuid"
)
type Chat struct {
c *bot.Client
p *basic.Player
}
func NewChat(c *bot.Client, p *basic.Player, events EventsHandler) *Chat {
attachPlayerMsg(c, p, events.PlayerChatMessage)
return &Chat{c, p}
}
func attachPlayerMsg(c *bot.Client, p *basic.Player, handler func(msg chat.Message) error) {
c.Events.AddListener(bot.PacketHandler{
Priority: 64, ID: packetid.ClientboundPlayerChat,
F: func(packet pk.Packet) error {
var message PlayerMessage
if err := packet.Scan(&message); err != nil {
return err
}
var content chat.Message
if message.content.formatted != nil {
content = *message.content.formatted
} else {
content = chat.Text(message.content.plainMsg)
}
ct := p.WorldInfo.RegistryCodec.ChatType.FindByID(message.chatType.ID)
if ct == nil {
return fmt.Errorf("chat type %d not found", message.chatType.ID)
}
msg := (*chat.Type)(&message.chatType).Decorate(content, &ct.Chat)
return handler(msg)
},
})
}
type PlayerMessage struct {
// SignedMessageHeader
signature []byte
sender uuid.UUID
// MessageSignature
msgSignature []byte
// SignedMessageBody
content msgContent
timestamp time.Time
salt int64
prevMessages []prevMsg
// Optional<Component>
unsignedContent *chat.Message
// FilterMask
filterType int32
filterSet pk.BitSet
// ChatType
chatType chatType
}
func (p *PlayerMessage) String() string {
return p.content.plainMsg
}
func (p *PlayerMessage) ReadFrom(r io.Reader) (n int64, err error) {
var hasMsgSign, hasUnsignedContent pk.Boolean
var timestamp pk.Long
var unsignedContent chat.Message
n, err = pk.Tuple{
&hasMsgSign, pk.Opt{Has: &hasMsgSign,
Field: (*pk.ByteArray)(&p.signature),
},
(*pk.UUID)(&p.sender),
(*pk.ByteArray)(&p.msgSignature),
&p.content,
&timestamp,
(*pk.Long)(&p.salt),
pk.Array(&p.prevMessages),
&hasUnsignedContent, pk.Opt{Has: &hasUnsignedContent,
Field: &unsignedContent,
},
(*pk.VarInt)(&p.filterType),
pk.Opt{
Has: func() bool { return p.filterType == 2 },
Field: &p.filterSet,
},
&p.chatType,
}.ReadFrom(r)
if err != nil {
return
}
p.timestamp = time.UnixMilli(int64(timestamp))
if hasUnsignedContent {
p.unsignedContent = &unsignedContent
}
return n, err
}
type msgContent struct {
plainMsg string
formatted *chat.Message
}
func (m *msgContent) ReadFrom(r io.Reader) (n int64, err error) {
var hasFormatted pk.Boolean
n1, err := (*pk.String)(&m.plainMsg).ReadFrom(r)
if err != nil {
return n1, err
}
n2, err := hasFormatted.ReadFrom(r)
if err != nil {
return n1 + n2, err
}
if hasFormatted {
m.formatted = new(chat.Message)
n3, err := m.formatted.ReadFrom(r)
return n1 + n2 + n3, err
}
return n1 + n2, err
}
type prevMsg struct {
sender uuid.UUID
signature []byte
}
func (p *prevMsg) ReadFrom(r io.Reader) (n int64, err error) {
return pk.Tuple{
(*pk.UUID)(&p.sender),
(*pk.ByteArray)(&p.signature),
}.ReadFrom(r)
}
type chatType chat.Type
func (c *chatType) ReadFrom(r io.Reader) (n int64, err error) {
var hasTargetName pk.Boolean
n1, err := (*pk.VarInt)(&c.ID).ReadFrom(r)
if err != nil {
return n1, err
}
n2, err := c.SenderName.ReadFrom(r)
if err != nil {
return n1 + n2, err
}
n3, err := hasTargetName.ReadFrom(r)
if err != nil {
return n1 + n2 + n3, err
}
if hasTargetName {
c.TargetName = new(chat.Message)
n4, err := c.TargetName.ReadFrom(r)
return n1 + n2 + n3 + n4, err
}
return n1 + n2 + n3, nil
}

7
bot/chat/events.go Normal file
View File

@ -0,0 +1,7 @@
package chat
import "github.com/Tnze/go-mc/chat"
type EventsHandler struct {
PlayerChatMessage func(msg chat.Message) error
}

View File

@ -1,6 +1,7 @@
package bot
import (
"github.com/Tnze/go-mc/data/packetid"
"github.com/Tnze/go-mc/net"
"github.com/Tnze/go-mc/yggdrasil/user"
"github.com/google/uuid"
@ -33,7 +34,7 @@ func (c *Client) Close() error {
func NewClient() *Client {
return &Client{
Auth: Auth{Name: "Steve"},
Events: Events{handlers: make(map[int32]*handlerHeap)},
Events: Events{handlers: make(map[packetid.ClientboundPacketID]*handlerHeap)},
}
}

View File

@ -1,12 +1,13 @@
package bot
import (
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
)
type Events struct {
generic *handlerHeap // for every packet
handlers map[int32]*handlerHeap // for specific packet id only
generic *handlerHeap // for every packet
handlers map[packetid.ClientboundPacketID]*handlerHeap // for specific packet id only
}
func (e *Events) AddListener(listeners ...PacketHandler) {
@ -36,7 +37,7 @@ func (e *Events) AddGeneric(listeners ...PacketHandler) {
type PacketHandlerFunc func(p pk.Packet) error
type PacketHandler struct {
ID int32
ID packetid.ClientboundPacketID
Priority int
F func(p pk.Packet) error
}

View File

@ -2,6 +2,8 @@ package bot
import (
"fmt"
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
)
@ -24,12 +26,12 @@ func (c *Client) HandleGame() error {
}
type PacketHandlerError struct {
ID int32
ID packetid.ClientboundPacketID
Err error
}
func (d PacketHandlerError) Error() string {
return fmt.Sprintf("handle packet 0x%X error: %v", d.ID, d.Err)
return fmt.Sprintf("handle packet %v error: %v", d.ID, d.Err)
}
func (d PacketHandlerError) Unwrap() error {
@ -37,18 +39,19 @@ func (d PacketHandlerError) Unwrap() error {
}
func (c *Client) handlePacket(p pk.Packet) (err error) {
packetID := packetid.ClientboundPacketID(p.ID)
if c.Events.generic != nil {
for _, handler := range *c.Events.generic {
if err = handler.F(p); err != nil {
return PacketHandlerError{ID: p.ID, Err: err}
return PacketHandlerError{ID: packetID, Err: err}
}
}
}
if listeners := c.Events.handlers[p.ID]; listeners != nil {
if listeners := c.Events.handlers[packetID]; listeners != nil {
for _, handler := range *listeners {
err = handler.F(p)
if err != nil {
return PacketHandlerError{ID: p.ID, Err: err}
return PacketHandlerError{ID: packetID, Err: err}
}
}
}