Add generic event

This commit is contained in:
Tnze
2021-02-27 14:49:11 +08:00
parent 3b83aaf8ae
commit 8e7ac43bf5
10 changed files with 316 additions and 195 deletions

View File

@ -1,5 +1,5 @@
# Go-MC
![Version](https://img.shields.io/badge/Minecraft-1.16.4-blue.svg)
![Version](https://img.shields.io/badge/Minecraft-1.16.5-blue.svg)
![Protocol](https://img.shields.io/badge/Protocol-754-blue.svg)
[![GoDoc](https://godoc.org/github.com/Tnze/go-mc?status.svg)](https://godoc.org/github.com/Tnze/go-mc)
[![Go Report Card](https://goreportcard.com/badge/github.com/Tnze/go-mc)](https://goreportcard.com/report/github.com/Tnze/go-mc)

View File

@ -12,6 +12,7 @@ type Player struct {
PlayerInfo
WorldInfo
isSpawn bool
}
func NewPlayer(c *bot.Client, settings Settings) *Player {
@ -19,14 +20,33 @@ func NewPlayer(c *bot.Client, settings Settings) *Player {
c.Events.AddListener(
bot.PacketHandler{Priority: 0, ID: packetid.Login, F: b.handleJoinGamePacket},
bot.PacketHandler{Priority: 0, ID: packetid.KeepAliveClientbound, F: b.handleKeepAlivePacket},
bot.PacketHandler{Priority: 0, ID: packetid.PositionClientbound, F: b.handlePlayerPositionAndLook},
)
return b
}
func (p *Player) Respawn() error {
const PerformRespawn = 0
return p.c.Conn.WritePacket(pk.Marshal(
err := p.c.Conn.WritePacket(pk.Marshal(
packetid.ClientCommand,
pk.VarInt(PerformRespawn),
))
if err != nil {
return Error{err}
}
return nil
}
type Error struct {
Err error
}
func (e Error) Error() string {
return "bot/basic: " + e.Err.Error()
}
func (e Error) Unwrap() error {
return e.Err
}

View File

@ -37,7 +37,7 @@ func (e *EventsListener) onDisconnect(p pk.Packet) error {
if e.Disconnect != nil {
var reason chat.Message
if err := p.Scan(&reason); err != nil {
return err
return Error{err}
}
return e.Disconnect(reason)
}
@ -51,7 +51,7 @@ func (e *EventsListener) onChatMsg(p pk.Packet) error {
var sender pk.UUID
if err := p.Scan(&msg, &pos, &sender); err != nil {
return err
return Error{err}
}
return e.ChatMsg(msg, byte(pos), uuid.UUID(sender))
@ -66,7 +66,7 @@ func (e *EventsListener) onUpdateHealth(p pk.Packet) error {
var foodSaturation pk.Float
if err := p.Scan(&health, &food, &foodSaturation); err != nil {
return err
return Error{err}
}
if e.HealthChange != nil {
if err := e.HealthChange(float32(health)); err != nil {

View File

@ -1,9 +1,10 @@
package basic
import (
"unsafe"
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
"unsafe"
)
// WorldInfo content player info in server.
@ -38,7 +39,7 @@ type ServInfo struct {
func (p *Player) handleJoinGamePacket(packet pk.Packet) error {
var WorldCount pk.VarInt
var WorldNames = []pk.Identifier{}
var WorldNames = make([]pk.Identifier, 0)
err := packet.Scan(
(*pk.Int)(&p.EID),
(*pk.Boolean)(&p.Hardcore),
@ -58,7 +59,7 @@ func (p *Player) handleJoinGamePacket(packet pk.Packet) error {
(*pk.Boolean)(&p.IsFlat),
)
if err != nil {
return err
return Error{err}
}
// This line should work "like" the following code but without copy things
@ -74,10 +75,10 @@ func (p *Player) handleJoinGamePacket(packet pk.Packet) error {
pk.String(p.Settings.Brand),
))
if err != nil {
return err
return Error{err}
}
return p.c.Conn.WritePacket(pk.Marshal(
err = p.c.Conn.WritePacket(pk.Marshal(
packetid.Settings, // Client settings
pk.String(p.Settings.Locale),
pk.Byte(p.Settings.ViewDistance),
@ -86,4 +87,8 @@ func (p *Player) handleJoinGamePacket(packet pk.Packet) error {
pk.UnsignedByte(p.Settings.DisplayedSkinParts),
pk.VarInt(p.Settings.MainHand),
))
if err != nil {
return Error{err}
}
return nil
}

View File

@ -3,16 +3,59 @@ package basic
import (
"github.com/Tnze/go-mc/data/packetid"
pk "github.com/Tnze/go-mc/net/packet"
"log"
)
func (p Player) handleKeepAlivePacket(packet pk.Packet) error {
var KeepAliveID pk.Long
if err := packet.Scan(&KeepAliveID); err != nil {
return err
return Error{err}
}
// Response
return p.c.Conn.WritePacket(pk.Marshal(
packetid.KeepAliveServerbound,
KeepAliveID,
))
err := p.c.Conn.WritePacket(pk.Packet{
ID: packetid.KeepAliveServerbound,
Data: packet.Data,
})
if err != nil {
return Error{err}
}
return nil
}
func (p *Player) handlePlayerPositionAndLook(packet pk.Packet) error {
var (
X, Y, Z pk.Double
Yaw, Pitch pk.Float
Flags pk.Byte
TeleportID pk.VarInt
)
if err := packet.Scan(&X, &Y, &Z, &Yaw, &Pitch, &Flags, &TeleportID); err != nil {
return Error{err}
}
// Teleport Confirm
err := p.c.Conn.WritePacket(pk.Marshal(
packetid.TeleportConfirm,
TeleportID,
))
if err != nil {
return Error{err}
}
if !p.isSpawn {
// PlayerPositionAndRotation to confirm the spawn position
err = p.c.Conn.WritePacket(pk.Marshal(
packetid.PositionLook,
X, Y-1.62, Z,
Yaw, Pitch,
pk.Boolean(true),
))
if err != nil {
return Error{err}
}
p.isSpawn = true
log.Print("Position confirmed")
}
return nil
}

View File

@ -5,7 +5,8 @@ import (
)
type Events struct {
handlers map[int32]*handlerHeap
generic *handlerHeap // for every packet
handlers map[int32]*handlerHeap // for specific packet id only
}
func (e *Events) AddListener(listeners ...PacketHandler) {
@ -21,6 +22,18 @@ func (e *Events) AddListener(listeners ...PacketHandler) {
}
}
// AddGeneric adds listeners like AddListener, but the packet ID is ignored.
// Generic listener is always called before specific packet listener.
func (e *Events) AddGeneric(listeners ...PacketHandler) {
for _, l := range listeners {
if e.generic == nil {
e.generic = &handlerHeap{l}
} else {
e.generic.Push(l)
}
}
}
type PacketHandlerFunc func(p pk.Packet) error
type PacketHandler struct {
ID int32

View File

@ -37,6 +37,13 @@ func (d PacketHandlerError) Unwrap() error {
}
func (c *Client) handlePacket(p pk.Packet) (err error) {
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}
}
}
}
if listeners := c.Events.handlers[p.ID]; listeners != nil {
for _, handler := range *listeners {
err = handler.F(p)

View File

@ -1,9 +1,14 @@
// Daze could join an offline-mode server as client.
// Just standing there and do nothing. Automatically reborn after five seconds of death.
//
// BUG(Tnze): Kick by Disconnect: Time Out
package main
import (
"errors"
"flag"
"log"
"time"
"github.com/google/uuid"
@ -20,6 +25,7 @@ var player *basic.Player
func main() {
flag.Parse()
client = bot.NewClient()
client.Auth.Name = "Daze"
player = basic.NewPlayer(client, basic.DefaultSettings)
basic.EventsListener{
GameStart: onGameStart,
@ -35,19 +41,22 @@ func main() {
}
log.Println("Login success")
//Register event handlers
//JoinGame
for {
if err = client.HandleGame(); err != nil {
var interErr *bot.PacketHandlerError
if errors.As(err, &interErr) {
log.Print("Internal bugs: ", interErr)
if err = client.HandleGame(); err == nil {
panic("HandleGame never return nil")
}
if err2 := new(bot.PacketHandlerError); errors.As(err, err2) {
if err := new(DisconnectErr); errors.As(err2, err) {
log.Print("Disconnect: ", err.Reason)
return
} else {
log.Fatal(err)
// print and ignore the error
log.Print(err2)
}
} else {
break
log.Fatal(err)
}
}
}
@ -55,7 +64,14 @@ func main() {
func onDeath() error {
log.Println("Died and Respawned")
// If we exclude Respawn(...) then the player won't press the "Respawn" button upon death
return player.Respawn()
go func() {
time.Sleep(time.Second * 5)
err := player.Respawn()
if err != nil {
log.Print(err)
}
}()
return nil
}
func onGameStart() error {
@ -63,12 +79,20 @@ func onGameStart() error {
return nil //if err isn't nil, HandleGame() will return it.
}
func onChatMsg(c chat.Message, pos byte, uuid uuid.UUID) error {
func onChatMsg(c chat.Message, _ byte, _ uuid.UUID) error {
log.Println("Chat:", c.ClearString()) // output chat message without any format code (like color or bold)
return nil
}
func onDisconnect(reason chat.Message) error {
log.Println("Disconnect:", reason)
return nil
type DisconnectErr struct {
Reason chat.Message
}
func (d DisconnectErr) Error() string {
return "disconnect: " + d.Reason.String()
}
func onDisconnect(reason chat.Message) error {
// return a error value so that we can stop main loop
return DisconnectErr{Reason: reason}
}

View File

@ -1,5 +1,3 @@
//+build ignore
// gen_packetIDs.go generates the enumeration of packet IDs used on the wire.
package main
@ -8,6 +6,7 @@ import (
"fmt"
"net/http"
"os"
"strconv"
"text/template"
"github.com/iancoleman/strcase"
@ -22,30 +21,30 @@ package packetid
// Login state
const (
// Clientbound
{{range $Name, $ID := .Login.Clientbound}} {{$Name}} = {{$ID}}
{{range $ID, $Name := .Login.Clientbound}} {{$Name}} = {{$ID | printf "%#x"}}
{{end}}
// Serverbound
{{range $Name, $ID := .Login.Serverbound}} {{$Name}} = {{$ID}}
{{range $ID, $Name := .Login.Serverbound}} {{$Name}} = {{$ID | printf "%#x"}}
{{end}}
)
// Ping state
const (
// Clientbound
{{range $Name, $ID := .Status.Clientbound}} {{$Name}} = {{$ID}}
{{range $ID, $Name := .Status.Clientbound}} {{$Name}} = {{$ID | printf "%#x"}}
{{end}}
// Serverbound
{{range $Name, $ID := .Status.Serverbound}} {{$Name}} = {{$ID}}
{{range $ID, $Name := .Status.Serverbound}} {{$Name}} = {{$ID | printf "%#x"}}
{{end}}
)
// Play state
const (
// Clientbound
{{range $Name, $ID := .Play.Clientbound}} {{$Name}} = {{$ID}}
{{range $ID, $Name := .Play.Clientbound}} {{$Name}} = {{$ID | printf "%#x"}}
{{end}}
// Serverbound
{{range $Name, $ID := .Play.Serverbound}} {{$Name}} = {{$ID}}
{{range $ID, $Name := .Play.Serverbound}} {{$Name}} = {{$ID | printf "%#x"}}
{{end}}
)
`
@ -69,25 +68,21 @@ func unnest(input map[string]interface{}, keys ...string) (map[string]interface{
}
type duplexMappings struct {
Clientbound map[string]string
Serverbound map[string]string
Clientbound map[int32]string
Serverbound map[int32]string
}
func (m *duplexMappings) EnsureUniqueNames() {
// Assemble a slice of keys to check across both maps, because we cannot
// mutate a map while iterating it.
clientKeys := make([]string, 0, len(m.Clientbound))
for k, _ := range m.Clientbound {
clientKeys = append(clientKeys, k)
clientBounds := make(map[string]int32)
for sk, sv := range m.Clientbound {
clientBounds[sv] = sk
}
for _, k := range clientKeys {
if _, alsoServerKey := m.Serverbound[k]; alsoServerKey {
cVal, sVal := m.Clientbound[k], m.Serverbound[k]
delete(m.Clientbound, k)
delete(m.Serverbound, k)
m.Clientbound[k+"Clientbound"] = cVal
m.Serverbound[k+"Serverbound"] = sVal
for sk, sv := range m.Serverbound {
if ck, ok := clientBounds[sv]; ok {
m.Clientbound[ck] = sv + "Clientbound"
m.Serverbound[sk] = sv + "Serverbound"
}
}
}
@ -96,8 +91,8 @@ func (m *duplexMappings) EnsureUniqueNames() {
// game state.
func unpackMapping(data map[string]interface{}, gameState string) (duplexMappings, error) {
out := duplexMappings{
Clientbound: make(map[string]string),
Serverbound: make(map[string]string),
Clientbound: make(map[int32]string),
Serverbound: make(map[int32]string),
}
info, err := unnest(data, gameState, "toClient", "types")
@ -107,7 +102,7 @@ func unpackMapping(data map[string]interface{}, gameState string) (duplexMapping
pType := info["packet"].([]interface{})[1].([]interface{})[0].(map[string]interface{})["type"]
mappings := pType.([]interface{})[1].(map[string]interface{})["mappings"].(map[string]interface{})
for k, v := range mappings {
out.Clientbound[strcase.ToCamel(v.(string))] = k
out.Clientbound[mustAtoi(k)] = strcase.ToCamel(v.(string))
}
info, err = unnest(data, gameState, "toServer", "types")
if err != nil {
@ -116,12 +111,20 @@ func unpackMapping(data map[string]interface{}, gameState string) (duplexMapping
pType = info["packet"].([]interface{})[1].([]interface{})[0].(map[string]interface{})["type"]
mappings = pType.([]interface{})[1].(map[string]interface{})["mappings"].(map[string]interface{})
for k, v := range mappings {
out.Serverbound[strcase.ToCamel(v.(string))] = k
out.Serverbound[mustAtoi(k)] = strcase.ToCamel(v.(string))
}
return out, nil
}
func mustAtoi(num string) int32 {
if n, err := strconv.ParseInt(num, 0, 32); err != nil {
panic(err)
} else {
return int32(n)
}
}
type protocolIDs struct {
Login duplexMappings
Play duplexMappings
@ -158,7 +161,7 @@ func downloadInfo() (*protocolIDs, error) {
}
//go:generate go run $GOFILE
//go:generate go fmt packetid.go
//go:generate go fmt ../packetid.go
func main() {
pIDs, err := downloadInfo()
if err != nil {
@ -166,7 +169,7 @@ func main() {
os.Exit(1)
}
f, err := os.Create("packetid.go")
f, err := os.Create("../packetid.go")
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)

View File

@ -2,169 +2,175 @@
package packetid
// Valid PktID values.
// Login state
const (
// Clientbound packets for connections in the login state.
Compress = 0x03
Disconnect = 0x00
EncryptionBeginClientbound = 0x01
LoginPluginRequest = 0x04
Success = 0x02
// Clientbound
Disconnect = 0x0
EncryptionBeginClientbound = 0x1
Success = 0x2
Compress = 0x3
LoginPluginRequest = 0x4
// Serverbound packets for connections in the login state
EncryptionBeginServerbound = 0x01
LoginPluginResponse = 0x02
LoginStart = 0x00
// Serverbound
LoginStart = 0x0
EncryptionBeginServerbound = 0x1
LoginPluginResponse = 0x2
)
// Clientbound packets for connections in the play state.
AbilitiesClientbound = 0x30
AcknowledgePlayerDigging = 0x07
Advancements = 0x57
Animation = 0x05
AttachEntity = 0x45
BlockAction = 0x0a
BlockBreakAnimation = 0x08
BlockChange = 0x0b
BossBar = 0x0c
Camera = 0x3e
ChatClientbound = 0x0e
CloseWindowClientbound = 0x12
Collect = 0x55
CombatEvent = 0x31
CraftProgressBar = 0x14
CraftRecipeResponse = 0x2f
CustomPayloadClientbound = 0x17
// Ping state
const (
// Clientbound
ServerInfo = 0x0
PingClientbound = 0x1
// Serverbound
PingStart = 0x0
PingServerbound = 0x1
)
// Play state
const (
// Clientbound
SpawnEntity = 0x0
SpawnEntityExperienceOrb = 0x1
SpawnEntityLiving = 0x2
SpawnEntityPainting = 0x3
NamedEntitySpawn = 0x4
Animation = 0x5
Statistics = 0x6
AcknowledgePlayerDigging = 0x7
BlockBreakAnimation = 0x8
TileEntityData = 0x9
BlockAction = 0xa
BlockChange = 0xb
BossBar = 0xc
Difficulty = 0xd
ChatClientbound = 0xe
TabCompleteClientbound = 0xf
DeclareCommands = 0x10
DeclareRecipes = 0x5a
Difficulty = 0x0d
Entity = 0x2a
EntityDestroy = 0x36
EntityEffect = 0x59
EntityEquipment = 0x47
EntityHeadRotation = 0x3a
EntityLook = 0x29
EntityMetadata = 0x44
EntityMoveLook = 0x28
EntitySoundEffect = 0x50
EntityStatus = 0x1a
EntityTeleport = 0x56
EntityUpdateAttributes = 0x58
EntityVelocity = 0x46
Experience = 0x48
Explosion = 0x1b
FacePlayer = 0x33
GameStateChange = 0x1d
HeldItemSlotClientbound = 0x3f
KeepAliveClientbound = 0x1f
TransactionClientbound = 0x11
CloseWindowClientbound = 0x12
WindowItems = 0x13
CraftProgressBar = 0x14
SetSlot = 0x15
SetCooldown = 0x16
CustomPayloadClientbound = 0x17
NamedSoundEffect = 0x18
KickDisconnect = 0x19
EntityStatus = 0x1a
Explosion = 0x1b
UnloadChunk = 0x1c
GameStateChange = 0x1d
OpenHorseWindow = 0x1e
KeepAliveClientbound = 0x1f
MapChunk = 0x20
WorldEvent = 0x21
WorldParticles = 0x22
UpdateLight = 0x23
Login = 0x24
Map = 0x25
MapChunk = 0x20
MultiBlockChange = 0x3b
NamedEntitySpawn = 0x04
NamedSoundEffect = 0x18
NbtQueryResponse = 0x54
OpenBook = 0x2c
OpenHorseWindow = 0x1e
OpenSignEntity = 0x2e
OpenWindow = 0x2d
PlayerInfo = 0x32
PlayerlistHeader = 0x53
PositionClientbound = 0x34
TradeList = 0x26
RelEntityMove = 0x27
EntityMoveLook = 0x28
EntityLook = 0x29
Entity = 0x2a
VehicleMoveClientbound = 0x2b
OpenBook = 0x2c
OpenWindow = 0x2d
OpenSignEntity = 0x2e
CraftRecipeResponse = 0x2f
AbilitiesClientbound = 0x30
CombatEvent = 0x31
PlayerInfo = 0x32
FacePlayer = 0x33
PositionClientbound = 0x34
UnlockRecipes = 0x35
EntityDestroy = 0x36
RemoveEntityEffect = 0x37
ResourcePackSend = 0x38
Respawn = 0x39
ScoreboardDisplayObjective = 0x43
ScoreboardObjective = 0x4a
ScoreboardScore = 0x4d
EntityHeadRotation = 0x3a
MultiBlockChange = 0x3b
SelectAdvancementTab = 0x3c
SetCooldown = 0x16
SetPassengers = 0x4b
SetSlot = 0x15
SoundEffect = 0x51
SpawnEntity = 0x00
SpawnEntityExperienceOrb = 0x01
SpawnEntityLiving = 0x02
SpawnEntityPainting = 0x03
SpawnPosition = 0x42
Statistics = 0x06
StopSound = 0x52
TabCompleteClientbound = 0x0f
Tags = 0x5b
Teams = 0x4c
TileEntityData = 0x09
Title = 0x4f
TradeList = 0x26
TransactionClientbound = 0x11
UnloadChunk = 0x1c
UnlockRecipes = 0x35
UpdateHealth = 0x49
UpdateLight = 0x23
UpdateTime = 0x4e
UpdateViewDistance = 0x41
UpdateViewPosition = 0x40
VehicleMoveClientbound = 0x2b
WindowItems = 0x13
WorldBorder = 0x3d
WorldEvent = 0x21
WorldParticles = 0x22
Camera = 0x3e
HeldItemSlotClientbound = 0x3f
UpdateViewPosition = 0x40
UpdateViewDistance = 0x41
SpawnPosition = 0x42
ScoreboardDisplayObjective = 0x43
EntityMetadata = 0x44
AttachEntity = 0x45
EntityVelocity = 0x46
EntityEquipment = 0x47
Experience = 0x48
UpdateHealth = 0x49
ScoreboardObjective = 0x4a
SetPassengers = 0x4b
Teams = 0x4c
ScoreboardScore = 0x4d
UpdateTime = 0x4e
Title = 0x4f
EntitySoundEffect = 0x50
SoundEffect = 0x51
StopSound = 0x52
PlayerlistHeader = 0x53
NbtQueryResponse = 0x54
Collect = 0x55
EntityTeleport = 0x56
Advancements = 0x57
EntityUpdateAttributes = 0x58
EntityEffect = 0x59
DeclareRecipes = 0x5a
Tags = 0x5b
// Serverbound packets for connections in the play state.
AbilitiesServerbound = 0x1a
AdvancementTab = 0x22
ArmAnimation = 0x2c
BlockDig = 0x1b
BlockPlace = 0x2e
ChatServerbound = 0x03
ClientCommand = 0x04
CloseWindowServerbound = 0x0a
CraftRecipeRequest = 0x19
CustomPayloadServerbound = 0x0b
DisplayedRecipe = 0x1e
EditBook = 0x0c
EnchantItem = 0x08
EntityAction = 0x1c
Flying = 0x15
GenerateStructure = 0x0f
HeldItemSlotServerbound = 0x25
// Serverbound
TeleportConfirm = 0x0
QueryBlockNbt = 0x1
SetDifficulty = 0x2
ChatServerbound = 0x3
ClientCommand = 0x4
Settings = 0x5
TabCompleteServerbound = 0x6
TransactionServerbound = 0x7
EnchantItem = 0x8
WindowClick = 0x9
CloseWindowServerbound = 0xa
CustomPayloadServerbound = 0xb
EditBook = 0xc
QueryEntityNbt = 0xd
UseEntity = 0xe
GenerateStructure = 0xf
KeepAliveServerbound = 0x10
LockDifficulty = 0x11
Look = 0x14
NameItem = 0x20
PickItem = 0x18
PositionLook = 0x13
PositionServerbound = 0x12
QueryBlockNbt = 0x01
QueryEntityNbt = 0x0d
PositionLook = 0x13
Look = 0x14
Flying = 0x15
VehicleMoveServerbound = 0x16
SteerBoat = 0x17
PickItem = 0x18
CraftRecipeRequest = 0x19
AbilitiesServerbound = 0x1a
BlockDig = 0x1b
EntityAction = 0x1c
SteerVehicle = 0x1d
DisplayedRecipe = 0x1e
RecipeBook = 0x1f
NameItem = 0x20
ResourcePackReceive = 0x21
AdvancementTab = 0x22
SelectTrade = 0x23
SetBeaconEffect = 0x24
SetCreativeSlot = 0x28
SetDifficulty = 0x02
Settings = 0x05
Spectate = 0x2d
SteerBoat = 0x17
SteerVehicle = 0x1d
TabCompleteServerbound = 0x06
TeleportConfirm = 0x00
TransactionServerbound = 0x07
HeldItemSlotServerbound = 0x25
UpdateCommandBlock = 0x26
UpdateCommandBlockMinecart = 0x27
SetCreativeSlot = 0x28
UpdateJigsawBlock = 0x29
UpdateSign = 0x2b
UpdateStructureBlock = 0x2a
UseEntity = 0x0e
UpdateSign = 0x2b
ArmAnimation = 0x2c
Spectate = 0x2d
BlockPlace = 0x2e
UseItem = 0x2f
VehicleMoveServerbound = 0x16
WindowClick = 0x09
// Clientbound packets used to respond to ping/status requests.
PingClientbound = 0x01
ServerInfo = 0x00
// Serverbound packets used to ping or read server status.
PingServerbound = 0x01
PingStart = 0x00
)