update comments

This commit is contained in:
Tnze
2023-02-12 00:57:17 +08:00
parent 807e94e360
commit e7984776c8
7 changed files with 119 additions and 61 deletions

View File

@ -7,14 +7,13 @@
// 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. And the confirm packet is automatically sent.
// - 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
// - ChatMsg
// - Disconnect
// - HealthChange
// - Death
@ -32,21 +31,22 @@ type Player struct {
PlayerInfo
WorldInfo
isSpawn bool
}
// 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.ClientboundPlayerPosition, F: p.handlePlayerPosition},
bot.PacketHandler{Priority: 0, ID: packetid.ClientboundRespawn, F: p.handleRespawnPacket},
)
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
@ -61,6 +61,19 @@ func (p *Player) Respawn() error {
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
}

View File

@ -7,11 +7,49 @@ import (
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 func(health float32) 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, dismountVehicle bool) error
}
// attach your event listener to the client.
@ -26,6 +64,9 @@ func (e EventsListener) attach(p *Player) {
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) {
@ -50,20 +91,20 @@ func attachDisconnect(c *bot.Client, handler func(reason chat.Message) error) {
})
}
func attachUpdateHealth(c *bot.Client, healthChangeHandler func(health float32) error, deathHandler func() error) {
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 foodSaturation pk.Float
var saturation pk.Float
if err := p.Scan(&health, &food, &foodSaturation); err != nil {
if err := p.Scan(&health, &food, &saturation); err != nil {
return Error{err}
}
var healthChangeErr, deathErr error
if healthChangeHandler != nil {
healthChangeErr = healthChangeHandler(float32(health))
healthChangeErr = healthChangeHandler(float32(health), int32(food), float32(saturation))
}
if deathHandler != nil && health <= 0 {
deathErr = deathHandler()
@ -76,6 +117,25 @@ func attachUpdateHealth(c *bot.Client, healthChangeHandler func(health float32)
})
}
func attachPlayerPosition(c *bot.Client, handler func(x, y, z float64, yaw, pitch float32, flag byte, teleportID int32, dismountVehicle bool) 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
DismountVehicle pk.Boolean
)
if err := p.Scan(&X, &Y, &Z, &Yaw, &Pitch, &Flags, &TeleportID, &DismountVehicle); err != nil {
return Error{err}
}
return handler(float64(X), float64(Y), float64(Z), float32(Yaw), float32(Pitch), byte(Flags), int32(TeleportID), bool(DismountVehicle))
},
})
}
type updateHealthError struct {
healthChangeErr, deathErr error
}

View File

@ -18,7 +18,7 @@ type WorldInfo struct {
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 Notchian client shows reduced information on the debug screen. For servers in development, this should almost always be false.
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.
@ -31,11 +31,6 @@ type PlayerInfo struct {
PrevGamemode int8 // Previous Gamemode
}
// ServInfo contains information about the server implementation.
type ServInfo struct {
Brand string
}
func (p *Player) handleLoginPacket(packet pk.Packet) error {
err := packet.Scan(
(*pk.Int)(&p.EID),

View File

@ -20,41 +20,3 @@ func (p *Player) handleKeepAlivePacket(packet pk.Packet) error {
}
return nil
}
func (p *Player) handlePlayerPosition(packet pk.Packet) error {
var (
X, Y, Z pk.Double
Yaw, Pitch pk.Float
Flags pk.Byte
TeleportID pk.VarInt
DismountVehicle pk.Boolean
)
if err := packet.Scan(&X, &Y, &Z, &Yaw, &Pitch, &Flags, &TeleportID, &DismountVehicle); err != nil {
return Error{err}
}
// Teleport Confirm
err := p.c.Conn.WritePacket(pk.Marshal(
packetid.ServerboundAcceptTeleportation,
TeleportID,
))
if err != nil {
return Error{err}
}
if !p.isSpawn {
// PlayerPositionAndRotation to confirm the spawn position
err = p.c.Conn.WritePacket(pk.Marshal(
packetid.ServerboundMovePlayerPosRot,
X, Y-1.62, Z,
Yaw, Pitch,
pk.Boolean(true),
))
if err != nil {
return Error{err}
}
p.isSpawn = true
}
return nil
}

View File

@ -17,6 +17,7 @@ import (
pk "github.com/Tnze/go-mc/net/packet"
)
// The Manager is used to receive and send chat messages.
type Manager struct {
c *bot.Client
p *basic.Player
@ -26,6 +27,7 @@ type Manager struct {
sign.SignatureCache
}
// New returns a new chat manager.
func New(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, events EventsHandler) *Manager {
m := &Manager{
c: c,
@ -37,7 +39,7 @@ func New(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, events Event
if events.SystemChat != nil {
c.Events.AddListener(bot.PacketHandler{
Priority: 64, ID: packetid.ClientboundSystemChat,
F: m.handleSystemMessage,
F: m.handleSystemChat,
})
}
if events.PlayerChatMessage != nil {
@ -55,7 +57,7 @@ func New(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, events Event
return m
}
func (m *Manager) handleSystemMessage(p pk.Packet) error {
func (m *Manager) handleSystemChat(p pk.Packet) error {
var msg chat.Message
var overlay pk.Boolean
if err := p.Scan(&msg, &overlay); err != nil {
@ -149,7 +151,7 @@ func (m *Manager) handleDisguisedChat(packet pk.Packet) error {
}
// SendMessage send chat message to server.
// Currently only support offline-mode or "Not Secure" chat
// Doesn't support sending message with signature currently.
func (m *Manager) SendMessage(msg string) error {
if len(msg) > 256 {
return errors.New("message length greater than 256")

View File

@ -2,8 +2,28 @@ package msg
import "github.com/Tnze/go-mc/chat"
// EventsHandler is a collection of event handlers.
// Fill the fields with your handler functions and pass this struct to [New] to create the msg manager.
// The handler functions will be called when the corresponding event is triggered.
// Leave the fields as nil if you don't want to handle the event.
type EventsHandler struct {
// SystemChat handles messages sent by gaming system.
//
// In vanilla client:
// If overlay is false, the message will be displayed in the chat box.
// If overlay is true, the message will be displayed on the top of the hot-bar.
SystemChat func(msg chat.Message, overlay bool) error
// PlayerChatMessage handles messages sent by players.
//
// Message signing system is added in 1.19. The message and its context could be signed by the player's private key.
// The manager tries to verify the message signature through the player's public key,
// and return the result as validated boolean.
PlayerChatMessage func(msg chat.Message, validated bool) error
// DisguisedChat handles DisguisedChat message.
//
// DisguisedChat message used to send system chat.
// Now it is used to send messages from "/say" command from server console.
DisguisedChat func(msg chat.Message) error
}

View File

@ -1,3 +1,9 @@
// Package playerlist contains a PlayerList struct that used to manage player information.
//
// The [PlayerList] contains a list of [PlayerInfo] which is received from server when client join.
// The playerlist contains every players' information of name, display name, uuid, gamemode, latency, public key, etc.
// And can be used to render the "TAB List". Other packages may also require playerlist to work,
// for example, the bot/msg package.
package playerlist
import (