forked from Tnze/go-mc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
events.go
163 lines (148 loc) · 5.47 KB
/
events.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package basic
import (
"github.com/apepenkov/go-mc/bot"
"github.com/apepenkov/go-mc/chat"
"github.com/apepenkov/go-mc/data/packetid"
pk "github.com/apepenkov/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(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"
}
}