-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
281 lines (240 loc) · 8.15 KB
/
server.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package MinecraftLightServer
import (
"errors"
"fmt"
"math/rand"
"runtime"
"sync"
"time"
)
const serverPort = "25565" // default listen port
// Server is a running Minecraft server.
type Server struct {
listener struct { // listening port handling
port string // current listening port
portValue chan string // send port to listening function
err chan error // get errors
}
players sync.Map // map of players online
counter int // number of players online
counterMut sync.Mutex // mutex for players counter
}
// NewServer creates a new Server using default port.
// portNumber is an optional argument and you have to leave
// it empty to use default port (25565).
func NewServer(portNumber ...string) *Server {
s := new(Server)
if len(portNumber) == 0 {
s.listener.port = serverPort
} else {
s.listener.port = portNumber[0]
}
s.listener.portValue = make(chan string)
s.listener.err = make(chan error)
return s
}
// Start starts the server using the current port.
func (s *Server) Start() error {
go s.listen(s.listener.portValue, s.listener.err)
s.listener.portValue <- s.listener.port
return <-s.listener.err
}
// SetPort changes port of the Minecraft server.
// Use it when server is running.
func (s *Server) SetPort(portNumber string) error {
s.listener.portValue <- portNumber
return <-s.listener.err
}
// Close stops the server and close its components.
func (s *Server) Close() error {
// Close port changer channel
close(s.listener.portValue)
// Remove each of the connected clients
s.players.Range(func(key interface{}, value interface{}) bool {
s.removePlayerAndExit(value.(*Player), errors.New("server closed"))
return true
})
return nil
}
// keepAliveUser sends keepalive packet to current player.
// This function must be started within a new goroutine.
func (s *Server) keepAliveUser(p *Player) {
for {
// Keep Alive packet with random int
random := Long(rand.Int63())
keepAlive := NewPacket(keepAlivePacketID, random)
// If there is a connection error remove client from players map
if err := keepAlive.Pack(p.connection); err != nil {
if p.isDeleted {
// Stop keepalive if user has been deleted
break
} else {
// If there is an error and player hasn't
// yet been deleted, delete him
s.removePlayerAndExit(p, err)
}
}
// send keep alive every 18 seconds (the maximum limit is 20 seconds)
time.Sleep(time.Second * 18)
}
}
// addPlayer add a player and removes players
// actually connected with same username.
func (s *Server) addPlayer(p *Player) {
precedent, ok := s.players.Load(p.username)
if ok {
// Remove old player
s.removePlayer(precedent.(*Player), errors.New("new player with same username"))
} else {
// Increment players counter if the player is new
s.counterMut.Lock()
s.counter++
s.counterMut.Unlock()
}
s.players.Store(p.username, p)
}
// removePlayer removes a player from current Server.
// must be invoked by the player's handler goroutine.
func (s *Server) removePlayer(p *Player, err error) {
p.isDeleted = true
_ = p.connection.Close()
// Remove player from players map
if _, ok := s.players.LoadAndDelete(p.username); ok {
// Log error
fmt.Println("Client " + string(p.username) + " has been removed due to [" + err.Error() + "]")
// Decrement players counter if deleted
s.counterMut.Lock()
s.counter--
s.counterMut.Unlock()
// Remove player from other clients
s.players.Range(func(key interface{}, value interface{}) bool {
currentPlayer := value.(*Player)
_ = NewPacket(broadcastPlayerInfoPacketID,
VarInt(4), // remove player
VarInt(1), // number of players
p.id, // uuid
).Pack(currentPlayer.connection)
_ = NewPacket(destroyEntityPacketID,
VarInt(1), // number of players
VarInt(p.int32FromUUID()), // uuid
).Pack(currentPlayer.connection)
return true
})
}
}
// removePlayerAndExit removes a player from current Server
// and stops current goroutine.
func (s *Server) removePlayerAndExit(p *Player, err error) {
s.removePlayer(p, err)
runtime.Goexit()
}
// broadcastPlayerInfo sends to all players the current players connected,
// to use when a new user needs to be added.
func (s *Server) broadcastPlayerInfo() {
s.players.Range(func(key interface{}, currentPlayer interface{}) bool {
// Send packet to current host
broadcast := NewPacket(broadcastPlayerInfoPacketID,
VarInt(0), // add player
VarInt(s.counter), // number of players
)
// Add every player to packet
s.players.Range(func(key interface{}, value interface{}) bool {
currentPlayer := value.(*Player)
_, _ = currentPlayer.id.WriteTo(broadcast) // player uuid
_, _ = currentPlayer.username.WriteTo(broadcast) // username
_, _ = VarInt(0).WriteTo(broadcast) // no properties
_, _ = VarInt(0).WriteTo(broadcast) // gamemode 0 (survival)
_, _ = VarInt(123).WriteTo(broadcast) // hardcoded ping
_, _ = Boolean(false).WriteTo(broadcast) // has display name
return true
})
// Send players packet
_ = broadcast.Pack(currentPlayer.(*Player).connection)
return true
})
}
// broadcastChatMessage sends a chat message to all connected players.
// msg is the message string and username is the sender.
func (s *Server) broadcastChatMessage(msg, username string) {
s.players.Range(func(key interface{}, value interface{}) bool {
player := value.(*Player)
if err := player.writeChatMessage(msg, username); err != nil {
s.removePlayerAndExit(player, err)
}
return true
})
fmt.Println("Broadcast chat message: <" + username + "> " + msg)
}
// broadcastSpawnPlayer sends the position of all other players to every client.
func (s *Server) broadcastSpawnPlayer() {
s.players.Range(func(key interface{}, playerInterface interface{}) bool {
currentPlayer := playerInterface.(*Player)
s.players.Range(func(key interface{}, p interface{}) bool {
// Get all other players
otherPlayer := p.(*Player)
if currentPlayer.id != otherPlayer.id {
_ = currentPlayer.writeSpawnPlayer(
VarInt(otherPlayer.int32FromUUID()),
otherPlayer.id,
otherPlayer.x,
otherPlayer.y,
otherPlayer.z,
otherPlayer.yaw,
otherPlayer.pitch,
)
_ = currentPlayer.writeEntityLook(
VarInt(otherPlayer.int32FromUUID()),
otherPlayer.yaw,
)
}
return true
})
return true
})
}
// broadcastPlayerPosAndLook sends to all other clients the position and the view of a player.
func (s *Server) broadcastPlayerPosAndLook(id VarInt, x, y, z Double, yaw, pitch Angle, onGround Boolean) {
s.players.Range(func(key interface{}, playerInterface interface{}) bool {
player := playerInterface.(*Player)
// Don't send to current player
if VarInt(player.int32FromUUID()) != id {
_ = player.writeEntityTeleport(x, y, z, yaw, pitch, onGround, id)
_ = player.writeEntityLook(id, yaw)
}
return true
})
}
// broadcastPlayerPosAndLook sends to all other clients the rotation and the look of a player.
func (s *Server) broadcastPlayerRotation(id VarInt, yaw, pitch Angle, onGround Boolean) {
s.players.Range(func(key interface{}, playerInterface interface{}) bool {
player := playerInterface.(*Player)
// Don't send to current player
if VarInt(player.int32FromUUID()) != id {
_ = player.writeEntityRotation(id, yaw, pitch, onGround)
_ = player.writeEntityLook(id, yaw)
}
return true
})
}
// broadcastEntityAction sends to all other clients an action of a player.
func (s *Server) broadcastEntityAction(id VarInt, action VarInt) {
s.players.Range(func(key interface{}, playerInterface interface{}) bool {
player := playerInterface.(*Player)
// Don't send to current player
if VarInt(player.int32FromUUID()) != id {
_ = player.writeEntityAction(id, action)
}
return true
})
}
// broadcastEntityAnimation sends to all other clients an animation of a player.
func (s *Server) broadcastEntityAnimation(id VarInt, animation VarInt) {
s.players.Range(func(key interface{}, playerInterface interface{}) bool {
player := playerInterface.(*Player)
// Don't send to current player
if VarInt(player.int32FromUUID()) != id {
_ = player.writeEntityAnimation(id, animation)
}
return true
})
}