Updated 1.21.6
This commit is contained in:
@ -1,91 +0,0 @@
|
||||
// Package basic provides some basic packet handler which client needs.
|
||||
//
|
||||
// # [Player]
|
||||
//
|
||||
// The [Player] is attached to a [Client] by calling [NewPlayer] before the client joins a server.
|
||||
//
|
||||
// There is 4 kinds of clientbound packet is handled by this package.
|
||||
// - LoginPacket, for cache player info. The player info will be stored in [Player.PlayerInfo].
|
||||
// - KeepAlivePacket, for avoid the client to be kicked by the server.
|
||||
// - PlayerPosition, is only received when server teleporting the player.
|
||||
// - Respawn, for updating player info, which may change when player respawned.
|
||||
//
|
||||
// # [EventsListener]
|
||||
//
|
||||
// Handles some basic event you probably need.
|
||||
// - GameStart
|
||||
// - Disconnect
|
||||
// - HealthChange
|
||||
// - Death
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
c *bot.Client
|
||||
Settings Settings
|
||||
|
||||
PlayerInfo
|
||||
WorldInfo
|
||||
}
|
||||
|
||||
// NewPlayer create a new Player manager.
|
||||
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: p.handleLoginPacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundKeepAlive, F: p.handleKeepAlivePacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundRespawn, F: p.handleRespawnPacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundPing, F: p.handlePingPacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundCookieRequest, F: p.handleCookieRequestPacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundStoreCookie, F: p.handleStoreCookiePacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundUpdateTags, F: p.handleUpdateTags},
|
||||
)
|
||||
events.attach(p)
|
||||
return p
|
||||
}
|
||||
|
||||
// Respawn is used to send a respawn packet to the server.
|
||||
// Typically, you should call this method when the player is dead (in the [Death] event handler).
|
||||
func (p *Player) Respawn() error {
|
||||
const PerformRespawn = 0
|
||||
|
||||
err := p.c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.ServerboundClientCommand,
|
||||
pk.VarInt(PerformRespawn),
|
||||
))
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptTeleportation is used to send a teleport confirmation packet to the server.
|
||||
// Typically, you should call this method when received a ClientboundPlayerPosition packet (in the [Teleported] event handler).
|
||||
func (p *Player) AcceptTeleportation(teleportID pk.VarInt) error {
|
||||
err := p.c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.ServerboundAcceptTeleportation,
|
||||
teleportID,
|
||||
))
|
||||
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
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
func (p *Player) handleCookieRequestPacket(packet pk.Packet) error {
|
||||
var key pk.Identifier
|
||||
err := packet.Scan(&key)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
cookieContent := p.c.Cookies[string(key)]
|
||||
err = p.c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.ServerboundCookieResponse,
|
||||
key, pk.OptionEncoder[pk.ByteArray]{
|
||||
Has: cookieContent != nil,
|
||||
Val: pk.ByteArray(cookieContent),
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Player) handleStoreCookiePacket(packet pk.Packet) error {
|
||||
var key pk.Identifier
|
||||
var payload pk.ByteArray
|
||||
err := packet.Scan(&key, &payload)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
p.c.Cookies[string(key)] = []byte(payload)
|
||||
return nil
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// EventsListener is a collection of event handlers.
|
||||
// Fill the fields with your handler functions and pass it to [NewPlayer] to create the Player manager.
|
||||
// For the event you don't want to handle, just leave it nil.
|
||||
type EventsListener struct {
|
||||
// GameStart event is called when the login process is completed and the player is ready to play.
|
||||
//
|
||||
// If you want to do some action when the bot joined the server like sending a chat message,
|
||||
// this event is the right place to do it.
|
||||
GameStart func() error
|
||||
|
||||
// Disconnect event is called before the server disconnects your client.
|
||||
// When the server willfully disconnects the client, it will send a ClientboundDisconnect packet and tell you why.
|
||||
// On vanilla client, the reason is displayed in the disconnect screen.
|
||||
//
|
||||
// This information may be very useful for debugging, and generally you should record it into the log.
|
||||
//
|
||||
// If the connection is disconnected due to network reasons or the client's initiative,
|
||||
// this event will not be triggered.
|
||||
Disconnect func(reason chat.Message) error
|
||||
|
||||
// HealthChange event is called when the player's health or food changed.
|
||||
HealthChange func(health float32, foodLevel int32, foodSaturation float32) error
|
||||
|
||||
// Death event is a special case of HealthChange.
|
||||
// It will be called after HealthChange handler called (if it isn't nil)
|
||||
// when the player's health is less than or equal to 0.
|
||||
//
|
||||
// Typically, you should call [Player.Respawn] in this handler.
|
||||
Death func() error
|
||||
|
||||
// Teleported event is called when the server think the player position in the client side is wrong,
|
||||
// and send a ClientboundPlayerPosition packet to correct the client.
|
||||
//
|
||||
// Typically, you need to do two things in this handler:
|
||||
// - Update the player's position and rotation you tracked to the correct position.
|
||||
// - Call [Player.AcceptTeleportation] to send a teleport confirmation packet to the server.
|
||||
//
|
||||
// Before you confirm the teleportation, the server will not accept any player motion packets.
|
||||
//
|
||||
// The position coordinates and rotation are absolute or relative depends on the flags.
|
||||
// The flag byte is a bitfield, specifies whether each coordinate value is absolute or relative.
|
||||
// For more information, see https://wiki.vg/Protocol#Synchronize_Player_Position
|
||||
Teleported func(x, y, z float64, yaw, pitch float32, flags byte, teleportID int32) error
|
||||
}
|
||||
|
||||
// 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(p *Player) {
|
||||
if e.GameStart != nil {
|
||||
attachJoinGameHandler(p.c, e.GameStart)
|
||||
}
|
||||
if e.Disconnect != nil {
|
||||
attachDisconnect(p.c, e.Disconnect)
|
||||
}
|
||||
if e.HealthChange != nil || e.Death != nil {
|
||||
attachUpdateHealth(p.c, e.HealthChange, e.Death)
|
||||
}
|
||||
if e.Teleported != nil {
|
||||
attachPlayerPosition(p.c, e.Teleported)
|
||||
}
|
||||
}
|
||||
|
||||
func attachJoinGameHandler(c *bot.Client, handler func() error) {
|
||||
c.Events.AddListener(bot.PacketHandler{
|
||||
Priority: 64, ID: packetid.ClientboundLogin,
|
||||
F: func(_ pk.Packet) error {
|
||||
return handler()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func attachDisconnect(c *bot.Client, handler func(reason chat.Message) error) {
|
||||
c.Events.AddListener(bot.PacketHandler{
|
||||
Priority: 64, ID: packetid.ClientboundDisconnect,
|
||||
F: func(p pk.Packet) error {
|
||||
var reason chat.Message
|
||||
if err := p.Scan(&reason); err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
return handler(chat.Message(reason))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func attachUpdateHealth(c *bot.Client, healthChangeHandler func(health float32, food int32, saturation float32) error, deathHandler func() error) {
|
||||
c.Events.AddListener(bot.PacketHandler{
|
||||
Priority: 64, ID: packetid.ClientboundSetHealth,
|
||||
F: func(p pk.Packet) error {
|
||||
var health pk.Float
|
||||
var food pk.VarInt
|
||||
var saturation pk.Float
|
||||
|
||||
if err := p.Scan(&health, &food, &saturation); err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
var healthChangeErr, deathErr error
|
||||
if healthChangeHandler != nil {
|
||||
healthChangeErr = healthChangeHandler(float32(health), int32(food), float32(saturation))
|
||||
}
|
||||
if deathHandler != nil && health <= 0 {
|
||||
deathErr = deathHandler()
|
||||
}
|
||||
if healthChangeErr != nil || deathErr != nil {
|
||||
return updateHealthError{healthChangeErr, deathErr}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func attachPlayerPosition(c *bot.Client, handler func(x, y, z float64, yaw, pitch float32, flag byte, teleportID int32) error) {
|
||||
c.Events.AddListener(bot.PacketHandler{
|
||||
Priority: 64, ID: packetid.ClientboundPlayerPosition,
|
||||
F: func(p pk.Packet) error {
|
||||
var (
|
||||
X, Y, Z pk.Double
|
||||
Yaw, Pitch pk.Float
|
||||
Flags pk.Byte
|
||||
TeleportID pk.VarInt
|
||||
)
|
||||
if err := p.Scan(&X, &Y, &Z, &Yaw, &Pitch, &Flags, &TeleportID); err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
return handler(float64(X), float64(Y), float64(Z), float32(Yaw), float32(Pitch), byte(Flags), int32(TeleportID))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type updateHealthError struct {
|
||||
healthChangeErr, deathErr error
|
||||
}
|
||||
|
||||
func (u updateHealthError) Unwrap() error {
|
||||
if u.healthChangeErr != nil {
|
||||
return u.healthChangeErr
|
||||
}
|
||||
if u.deathErr != nil {
|
||||
return u.deathErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u updateHealthError) Error() string {
|
||||
switch {
|
||||
case u.healthChangeErr != nil && u.deathErr != nil:
|
||||
return "[" + u.healthChangeErr.Error() + ", " + u.deathErr.Error() + "]"
|
||||
case u.healthChangeErr != nil:
|
||||
return u.healthChangeErr.Error()
|
||||
case u.deathErr != nil:
|
||||
return u.deathErr.Error()
|
||||
default:
|
||||
return "nil"
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// WorldInfo content player info in server.
|
||||
type WorldInfo struct {
|
||||
DimensionType int32
|
||||
DimensionNames []string // Identifiers for all worlds on the server.
|
||||
DimensionName string // Name of the world being spawned into.
|
||||
HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise
|
||||
MaxPlayers int32 // Was once used by the client to draw the player list, but now is ignored.
|
||||
ViewDistance int32 // Render distance (2-32).
|
||||
SimulationDistance int32 // The distance that the client will process specific things, such as entities.
|
||||
ReducedDebugInfo bool // If true, a vanilla client shows reduced information on the debug screen. For servers in development, this should almost always be false.
|
||||
EnableRespawnScreen bool // Set to false when the doImmediateRespawn gamerule is true.
|
||||
IsDebug bool // True if the world is a debug mode world; debug mode worlds cannot be modified and have predefined blocks.
|
||||
IsFlat bool // True if the world is a superflat world; flat worlds have different void fog and a horizon at y=0 instead of y=63.
|
||||
DoLimitCrafting bool // Whether players can only craft recipes they have already unlocked. Currently unused by the client.
|
||||
}
|
||||
|
||||
type PlayerInfo struct {
|
||||
EID int32 // The player's Entity ID (EID).
|
||||
Hardcore bool // Is hardcore
|
||||
Gamemode byte // Gamemode. 0: Survival, 1: Creative, 2: Adventure, 3: Spectator.
|
||||
PrevGamemode int8 // Previous Gamemode
|
||||
}
|
||||
|
||||
func (p *Player) handleLoginPacket(packet pk.Packet) error {
|
||||
err := packet.Scan(
|
||||
(*pk.Int)(&p.EID),
|
||||
(*pk.Boolean)(&p.Hardcore),
|
||||
pk.Array((*[]pk.Identifier)(unsafe.Pointer(&p.DimensionNames))),
|
||||
(*pk.VarInt)(&p.MaxPlayers),
|
||||
(*pk.VarInt)(&p.ViewDistance),
|
||||
(*pk.VarInt)(&p.SimulationDistance),
|
||||
(*pk.Boolean)(&p.ReducedDebugInfo),
|
||||
(*pk.Boolean)(&p.EnableRespawnScreen),
|
||||
(*pk.Boolean)(&p.DoLimitCrafting),
|
||||
(*pk.VarInt)(&p.WorldInfo.DimensionType),
|
||||
(*pk.Identifier)(&p.DimensionName),
|
||||
(*pk.Long)(&p.HashedSeed),
|
||||
(*pk.UnsignedByte)(&p.Gamemode),
|
||||
(*pk.Byte)(&p.PrevGamemode),
|
||||
(*pk.Boolean)(&p.IsDebug),
|
||||
(*pk.Boolean)(&p.IsFlat),
|
||||
// Death dimension & Death location & Portal cooldown are ignored
|
||||
)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
err = p.c.Conn.WritePacket(pk.Marshal( // PluginMessage packet
|
||||
packetid.ServerboundCustomPayload,
|
||||
pk.Identifier("minecraft:brand"),
|
||||
pk.String(p.Settings.Brand),
|
||||
))
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
err = p.c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.ServerboundClientInformation, // Client settings
|
||||
pk.String(p.Settings.Locale),
|
||||
pk.Byte(p.Settings.ViewDistance),
|
||||
pk.VarInt(p.Settings.ChatMode),
|
||||
pk.Boolean(p.Settings.ChatColors),
|
||||
pk.UnsignedByte(p.Settings.DisplayedSkinParts),
|
||||
pk.VarInt(p.Settings.MainHand),
|
||||
pk.Boolean(p.Settings.EnableTextFiltering),
|
||||
pk.Boolean(p.Settings.AllowListing),
|
||||
))
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
p.resetKeepAliveDeadline()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Player) handleRespawnPacket(packet pk.Packet) error {
|
||||
var copyMeta bool
|
||||
err := packet.Scan(
|
||||
(*pk.VarInt)(&p.DimensionType),
|
||||
(*pk.Identifier)(&p.DimensionName),
|
||||
(*pk.Long)(&p.HashedSeed),
|
||||
(*pk.UnsignedByte)(&p.Gamemode),
|
||||
(*pk.Byte)(&p.PrevGamemode),
|
||||
(*pk.Boolean)(&p.IsDebug),
|
||||
(*pk.Boolean)(&p.IsFlat),
|
||||
(*pk.Boolean)(©Meta),
|
||||
)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
const keepAliveDuration = time.Second * 20
|
||||
|
||||
func (p *Player) resetKeepAliveDeadline() {
|
||||
newDeadline := time.Now().Add(keepAliveDuration)
|
||||
p.c.Conn.Socket.SetDeadline(newDeadline)
|
||||
}
|
||||
|
||||
func (p *Player) handleKeepAlivePacket(packet pk.Packet) error {
|
||||
var KeepAliveID pk.Long
|
||||
if err := packet.Scan(&KeepAliveID); err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
p.resetKeepAliveDeadline()
|
||||
|
||||
// Response
|
||||
err := p.c.Conn.WritePacket(pk.Packet{
|
||||
ID: int32(packetid.ServerboundKeepAlive),
|
||||
Data: packet.Data,
|
||||
})
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
func (p *Player) handlePingPacket(packet pk.Packet) error {
|
||||
var pingID pk.Int
|
||||
if err := packet.Scan(&pingID); err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
// Response
|
||||
err := p.c.Conn.WritePacket(pk.Packet{
|
||||
ID: int32(packetid.ServerboundPong),
|
||||
Data: packet.Data,
|
||||
})
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package basic
|
||||
|
||||
// Settings of client
|
||||
type Settings struct {
|
||||
Locale string // 地区
|
||||
ViewDistance int // 视距
|
||||
ChatMode int // 聊天模式
|
||||
ChatColors bool // 聊天颜色
|
||||
DisplayedSkinParts uint8 // 皮肤显示
|
||||
MainHand int // 主手
|
||||
|
||||
// Enables filtering of text on signs and written book titles.
|
||||
// Currently, always false (i.e. the filtering is disabled)
|
||||
EnableTextFiltering bool
|
||||
AllowListing bool
|
||||
|
||||
// The brand string presented to the server.
|
||||
Brand string
|
||||
}
|
||||
|
||||
// Used by Settings.DisplayedSkinParts.
|
||||
// For each bit set if shows match part.
|
||||
const (
|
||||
_ = 1 << iota
|
||||
Jacket
|
||||
LeftSleeve
|
||||
RightSleeve
|
||||
LeftPantsLeg
|
||||
RightPantsLeg
|
||||
Hat
|
||||
)
|
||||
|
||||
// DefaultSettings are the default settings of client
|
||||
var DefaultSettings = Settings{
|
||||
Locale: "zh_CN", // ^_^
|
||||
ViewDistance: 15,
|
||||
ChatMode: 0,
|
||||
DisplayedSkinParts: Jacket | LeftSleeve | RightSleeve | LeftPantsLeg | RightPantsLeg | Hat,
|
||||
MainHand: 1,
|
||||
|
||||
EnableTextFiltering: false,
|
||||
AllowListing: true,
|
||||
|
||||
Brand: "vanilla",
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
func (p *Player) handleUpdateTags(packet pk.Packet) error {
|
||||
r := bytes.NewReader(packet.Data)
|
||||
|
||||
var length pk.VarInt
|
||||
_, err := length.ReadFrom(r)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
var registryID pk.Identifier
|
||||
for i := 0; i < int(length); i++ {
|
||||
_, err = registryID.ReadFrom(r)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
|
||||
registry := p.c.Registries.Registry(string(registryID))
|
||||
if registry == nil {
|
||||
return Error{errors.New("unknown registry: " + string(registryID))}
|
||||
}
|
||||
|
||||
_, err = registry.ReadTagsFrom(r)
|
||||
if err != nil {
|
||||
return Error{err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user