From 0ec82d90a7faede597e4792630c3d95a139ed2ad Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 11 Sep 2020 17:58:15 -0700 Subject: [PATCH] Update to 1.16.3 + add a few new event callbacks. --- README.md | 6 ++--- bot/client.go | 26 ++++++++++++--------- bot/event.go | 49 ++++++++++++++++++++++++++++++++++++++-- bot/ingame.go | 60 +++++++++++++++++++++++++++++++++++++++++++------ bot/mcbot.go | 2 +- bot/settings.go | 2 ++ 6 files changed, 122 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 970876b..1005621 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Go-MC -![Version](https://img.shields.io/badge/Minecraft-1.16.2-blue.svg) -![Protocol](https://img.shields.io/badge/Protocol-751-blue.svg) +![Version](https://img.shields.io/badge/Minecraft-1.16.3-blue.svg) +![Protocol](https://img.shields.io/badge/Protocol-753-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) [![Build Status](https://travis-ci.org/Tnze/go-mc.svg?branch=master)](https://travis-ci.org/Tnze/go-mc) @@ -105,4 +105,4 @@ Originally it's all right to write a bot with only `go-mc/net` package. But cons 理论上讲,只用`go-mc/net`包实现一个bot是完全可行的,但是为了节省大家从头去理解MC握手、登录、加密等协议的过程,在`go-mc/bot`中我已经把这些都实现了,只不过它不是跨版本的。你可以直接使用,或者作为自己实现的参考。 -Now, go and have a look at the example! +Now, go and have a look at the example! diff --git a/bot/client.go b/bot/client.go index fdff6c1..a5eee4d 100644 --- a/bot/client.go +++ b/bot/client.go @@ -14,6 +14,7 @@ type Client struct { player.Player PlayInfo + ServInfo abilities PlayerAbilities settings Settings Wd world.World //the map data @@ -49,16 +50,21 @@ func NewClient() (c *Client) { //PlayInfo content player info in server. type PlayInfo struct { - Gamemode int //游戏模式 - Hardcore bool //是否是极限模式 - Dimension int //维度 - Difficulty int //难度 - ViewDistance int //视距 - ReducedDebugInfo bool //减少调试信息 - WorldName string //当前世界的名字 - IsDebug bool //调试 - IsFlat bool //超平坦世界 - // SpawnPosition Position //主世界出生点 + Gamemode int //游戏模式 + Hardcore bool //是否是极限模式 + Dimension int //维度 + Difficulty int //难度 + ViewDistance int //视距 + ReducedDebugInfo bool //减少调试信息 + WorldName string //当前世界的名字 + IsDebug bool //调试 + IsFlat bool //超平坦世界 + SpawnPosition Position //主世界出生点 +} + +// ServInfo contains information about the server implementation. +type ServInfo struct { + Brand string } // PlayerAbilities defines what player can do. diff --git a/bot/event.go b/bot/event.go index a34c280..10ec415 100644 --- a/bot/event.go +++ b/bot/event.go @@ -8,7 +8,30 @@ import ( pk "github.com/Tnze/go-mc/net/packet" ) +type seenPacketFlags uint8 + +// Valid seenPacketFlags values. +const ( + seenJoinGame seenPacketFlags = 1 << iota + seenServerDifficulty + seenPlayerAbilities + seenPlayerInventory + seenUpdateLight + seenChunkData + seenPlayerPositionAndLook + seenSpawnPos + + // gameReadyMinPackets are the minimum set of packets that must be seen, for + // the GameReady callback to be invoked. + gameReadyMinPackets = seenJoinGame | seenChunkData | seenUpdateLight | + seenPlayerAbilities | seenPlayerInventory | seenServerDifficulty | + seenPlayerPositionAndLook | seenSpawnPos +) + type eventBroker struct { + seenPackets seenPacketFlags + isReady bool + GameStart func() error ChatMsg func(msg chat.Message, pos byte, sender uuid.UUID) error Disconnect func(reason chat.Message) error @@ -28,7 +51,29 @@ type eventBroker struct { WindowsItem func(id byte, slots []entity.Slot) error WindowsItemChange func(id byte, slotID int, slot entity.Slot) error - // ReceivePacket will be called when new packet arrive. - // Default handler will run only if pass == false. + // ServerDifficultyChange is called whenever the gamemode of the server changes. + // At time of writing (1.16.3), difficulty values of 0, 1, 2, and 3 correspond + // to peaceful, easy, normal, and hard respectively. + ServerDifficultyChange func(difficulty int) error + + // GameReady is called after the client has joined the server and successfully + // received player state. Additionally, the server has begun sending world + // state (such as lighting and chunk information). + // + // Use this callback as a signal as to when your bot should start 'doing' + // things. + GameReady func() error + + // ReceivePacket will be called when new packets arrive. + // The default handler will run only if pass == false. ReceivePacket func(p pk.Packet) (pass bool, err error) } + +func (b *eventBroker) updateSeenPackets(f seenPacketFlags) error { + b.seenPackets |= f + if (^b.seenPackets)&gameReadyMinPackets == 0 && b.GameReady != nil && !b.isReady { + b.isReady = true + return b.GameReady() + } + return nil +} diff --git a/bot/ingame.go b/bot/ingame.go index 2aa87c5..3dedc9c 100644 --- a/bot/ingame.go +++ b/bot/ingame.go @@ -71,12 +71,33 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) { if err == nil && c.Events.GameStart != nil { err = c.Events.GameStart() } + + _ = c.conn.WritePacket( + //PluginMessage packet (serverbound) - sending minecraft brand. + pk.Marshal( + data.PluginMessageServerbound, + pk.Identifier("minecraft:brand"), + pk.String(c.settings.Brand), + ), + ) + if err2 := c.Events.updateSeenPackets(seenJoinGame); err == nil { + err = err2 + } case data.PluginMessageClientbound: err = handlePluginPacket(c, p) case data.ServerDifficulty: err = handleServerDifficultyPacket(c, p) + if err == nil && c.Events.ServerDifficultyChange != nil { + err = c.Events.ServerDifficultyChange(c.Difficulty) + } + if err2 := c.Events.updateSeenPackets(seenServerDifficulty); err == nil { + err = err2 + } case data.SpawnPosition: err = handleSpawnPositionPacket(c, p) + if err2 := c.Events.updateSeenPackets(seenSpawnPos); err == nil { + err = err2 + } case data.PlayerAbilitiesClientbound: err = handlePlayerAbilitiesPacket(c, p) _ = c.conn.WritePacket( @@ -91,13 +112,24 @@ func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) { pk.VarInt(c.settings.MainHand), ), ) + if err2 := c.Events.updateSeenPackets(seenPlayerAbilities); err == nil { + err = err2 + } case data.HeldItemChangeClientbound: err = handleHeldItemPacket(c, p) + case data.UpdateLight: + err = c.Events.updateSeenPackets(seenUpdateLight) case data.ChunkData: err = handleChunkDataPacket(c, p) + if err2 := c.Events.updateSeenPackets(seenChunkData); err == nil { + err = err2 + } case data.PlayerPositionAndLookClientbound: err = handlePlayerPositionAndLookPacket(c, p) sendPlayerPositionAndLookPacket(c) // to confirm the position + if err2 := c.Events.updateSeenPackets(seenPlayerPositionAndLook); err == nil { + err = err2 + } case data.DeclareRecipes: // handleDeclareRecipesPacket(g, reader) case data.EntityLookAndRelativeMove: @@ -395,6 +427,16 @@ func handlePluginPacket(c *Client, p pk.Packet) error { if err := p.Scan(&Channel, &Data); err != nil { return err } + + switch Channel { + case "minecraft:brand": + var brandRaw pk.String + if err := brandRaw.Decode(bytes.NewReader(Data)); err != nil { + return err + } + c.ServInfo.Brand = string(brandRaw) + } + if c.Events.PluginMessage != nil { return c.Events.PluginMessage(string(Channel), []byte(Data)) } @@ -417,8 +459,8 @@ func handleSpawnPositionPacket(c *Client, p pk.Packet) error { if err != nil { return err } - // c.SpawnPosition.X, c.SpawnPosition.Y, c.SpawnPosition.Z = - // pos.X, pos.Y, pos.Z + c.SpawnPosition.X, c.SpawnPosition.Y, c.SpawnPosition.Z = + pos.X, pos.Y, pos.Z return nil } @@ -598,11 +640,7 @@ func handleKeepAlivePacket(c *Client, p pk.Packet) error { )) } -func handleWindowItemsPacket(c *Client, p pk.Packet) (err error) { - if c.Events.WindowsItem == nil { - return nil - } - +func handleWindowItemsPacket(c *Client, p pk.Packet) error { r := bytes.NewReader(p.Data) var ( windowID pk.Byte @@ -623,6 +661,14 @@ func handleWindowItemsPacket(c *Client, p pk.Packet) (err error) { slots = append(slots, slot) } + if windowID == 0 { // Window ID 0 is the players' inventory. + if err := c.Events.updateSeenPackets(seenPlayerInventory); err != nil { + return err + } + } + if c.Events.WindowsItem == nil { + return nil + } return c.Events.WindowsItem(byte(windowID), slots) } diff --git a/bot/mcbot.go b/bot/mcbot.go index c5aebea..1b5310a 100644 --- a/bot/mcbot.go +++ b/bot/mcbot.go @@ -14,7 +14,7 @@ import ( ) // ProtocolVersion , the protocol version number of minecraft net protocol -const ProtocolVersion = 751 +const ProtocolVersion = 753 // JoinServer connect a Minecraft server for playing the game. func (c *Client) JoinServer(addr string, port int) (err error) { diff --git a/bot/settings.go b/bot/settings.go index 66b71a2..ae64edd 100644 --- a/bot/settings.go +++ b/bot/settings.go @@ -9,6 +9,7 @@ type Settings struct { DisplayedSkinParts uint8 //皮肤显示 MainHand int //主手 ReceiveMap bool //接收地图数据 + Brand string // The brand string presented to the server. } /* @@ -33,4 +34,5 @@ var DefaultSettings = Settings{ DisplayedSkinParts: Jacket | LeftSleeve | RightSleeve | LeftPantsLeg | RightPantsLeg | Hat, MainHand: 1, ReceiveMap: true, + Brand: "vanilla", }