/
server.go
471 lines (410 loc) · 15.7 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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
package dragonfly
import (
"encoding/base64"
"errors"
"fmt"
_ "github.com/df-mc/dragonfly/dragonfly/block"
"github.com/df-mc/dragonfly/dragonfly/cmd"
_ "github.com/df-mc/dragonfly/dragonfly/item" // Imported for compiler directives.
"github.com/df-mc/dragonfly/dragonfly/player"
"github.com/df-mc/dragonfly/dragonfly/player/skin"
"github.com/df-mc/dragonfly/dragonfly/session"
"github.com/df-mc/dragonfly/dragonfly/world"
"github.com/df-mc/dragonfly/dragonfly/world/generator"
"github.com/df-mc/dragonfly/dragonfly/world/mcdb"
"github.com/go-gl/mathgl/mgl32"
"github.com/go-gl/mathgl/mgl64"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/text"
"github.com/sirupsen/logrus"
"go.uber.org/atomic"
"os"
"os/signal"
"sync"
"syscall"
"time"
_ "unsafe" // Imported for compiler directives.
)
// Server implements a Dragonfly server. It runs the main server loop and handles the connections of players
// trying to join the server.
type Server struct {
started atomic.Bool
name atomic.String
joinMessage, quitMessage atomic.String
c Config
log *logrus.Logger
listener *minecraft.Listener
world *world.World
players chan *player.Player
startTime time.Time
playerMutex sync.RWMutex
// p holds a map of all players currently connected to the server. When they leave, they are removed from
// the map.
p map[uuid.UUID]*player.Player
}
// New returns a new server using the Config passed. If nil is passed, a default configuration is returned.
// (A call to dragonfly.DefaultConfig().)
// The Logger passed will be used to log errors and information to. If nil is passed, a default Logger is
// used by calling logrus.New().
// Note that no two servers should be active at the same time. Doing so anyway will result in unexpected
// behaviour.
func New(c *Config, log *logrus.Logger) *Server {
if log == nil {
log = logrus.New()
}
if c == nil {
conf := DefaultConfig()
c = &conf
}
s := &Server{
c: *c,
log: log,
players: make(chan *player.Player),
world: world.New(log, c.World.SimulationDistance),
p: make(map[uuid.UUID]*player.Player),
name: *atomic.NewString(c.Server.Name),
}
s.JoinMessage(c.Server.JoinMessage)
s.QuitMessage(c.Server.QuitMessage)
return s
}
// Accept accepts an incoming player into the server. It blocks until a player connects to the server.
// Accept returns an error if the Server is closed using a call to Close.
func (server *Server) Accept() (*player.Player, error) {
p, ok := <-server.players
if !ok {
return nil, errors.New("server closed")
}
server.playerMutex.Lock()
server.p[p.UUID()] = p
server.playerMutex.Unlock()
return p, nil
}
// World returns the world of the server. Players will be spawned in this world and this world will be read
// from and written to when the world is edited.
func (server *Server) World() *world.World {
return server.world
}
// Run runs the server and blocks until it is closed using a call to Close(). When called, the server will
// accept incoming connections. Run will block the current goroutine until the server is stopped. To start
// the server on a different goroutine, use (*Server).Start() instead.
// After a call to Run, calls to Server.Accept() may be made to accept players into the server.
func (server *Server) Run() error {
if !server.started.CAS(false, true) {
panic("server already running")
}
server.log.Info("Starting server...")
server.loadWorld()
server.World().Generator(generator.Flat{})
server.registerTargetFunc()
if err := world_loadItemEntries(); err != nil {
return err
}
item_registerVanillaCreativeItems()
if err := server.startListening(); err != nil {
return err
}
server.run()
return nil
}
// Start runs the server but does not block, unlike Run, but instead accepts connections on a different
// goroutine. Connections will be accepted until the listener is closed using a call to Close.
// One started, players may be accepted using Server.Accept().
func (server *Server) Start() error {
if !server.started.CAS(false, true) {
panic("server already running")
}
server.log.Info("Starting server...")
server.loadWorld()
server.World().Generator(generator.Flat{})
server.registerTargetFunc()
if err := world_loadItemEntries(); err != nil {
return err
}
item_registerVanillaCreativeItems()
if err := server.startListening(); err != nil {
return err
}
go server.run()
return nil
}
// Uptime returns the duration that the server has been running for. Measurement starts the moment a call to
// Server.Start or Server.Run is made.
func (server *Server) Uptime() time.Duration {
if !server.running() {
return 0
}
return time.Since(server.startTime)
}
// PlayerCount returns the current player count of the server. It is equivalent to calling
// len(server.Players()).
func (server *Server) PlayerCount() int {
server.playerMutex.RLock()
defer server.playerMutex.RUnlock()
return len(server.p)
}
// MaxPlayerCount returns the maximum amount of players that are allowed to play on the server at the same
// time. Players trying to join when the server is full will be refused to enter.
// If the config has a maximum player count set to 0, MaxPlayerCount will return Server.PlayerCount + 1.
func (server *Server) MaxPlayerCount() int {
if server.c.Server.MaximumPlayers == 0 {
return server.PlayerCount() + 1
}
return server.c.Server.MaximumPlayers
}
// Players returns a list of all players currently connected to the server. Note that the slice returned is
// not updated when new players join or leave, so it is only valid for as long as no new players join or
// players leave.
func (server *Server) Players() []*player.Player {
server.playerMutex.RLock()
defer server.playerMutex.RUnlock()
players := make([]*player.Player, 0, len(server.p))
for _, p := range server.p {
players = append(players, p)
}
return players
}
// Player looks for a player on the server with the UUID passed. If found, the player is returned and the bool
// returns holds a true value. If not, the bool returned is false and the player is nil.
func (server *Server) Player(uuid uuid.UUID) (*player.Player, bool) {
server.playerMutex.RLock()
defer server.playerMutex.RUnlock()
if p, ok := server.p[uuid]; ok {
return p, true
}
return nil, false
}
// PlayerByName looks for a player on the server with the name passed. If found, the player is returned and the bool
// returns holds a true value. If not, the bool is false and the player is nil
func (server *Server) PlayerByName(name string) (*player.Player, bool) {
for _, p := range server.Players() {
if p.Name() == name {
return p, true
}
}
return nil, false
}
// SetNamef sets the name of the Server, also known as the MOTD. This name is displayed in the server list.
// The formatting of the name passed follows the rules of fmt.Sprintf.
func (server *Server) SetNamef(format string, a ...interface{}) {
server.name.Store(fmt.Sprintf(format, a...))
}
// SetName sets the name of the Server, also known as the MOTD. This name is displayed in the server list.
// The formatting of the name passed follows the rules of fmt.Sprint.
func (server *Server) SetName(a ...interface{}) {
server.name.Store(fmt.Sprint(a...))
}
// JoinMessage changes the join message for all players on the server. Leave this empty to disable it.
// %v is the placeholder for the username of the player
func (server *Server) JoinMessage(message string) {
server.joinMessage.Store(message)
}
// QuitMessage changes the leave message for all players on the server. Leave this empty to disable it.
// %v is the placeholder for the username of the player
func (server *Server) QuitMessage(message string) {
server.quitMessage.Store(message)
}
// Close closes the server, making any call to Run/Accept cancel immediately.
func (server *Server) Close() error {
if !server.running() {
panic("server not yet running")
}
server.log.Info("Server shutting down...")
defer server.log.Info("Server stopped.")
server.log.Debug("Disconnecting players...")
server.playerMutex.RLock()
for _, p := range server.p {
p.Disconnect(text.Colourf("<yellow>%v</yellow>", server.c.Server.ShutdownMessage))
}
server.playerMutex.RUnlock()
server.log.Debug("Closing world...")
if err := server.world.Close(); err != nil {
return err
}
server.log.Debug("Closing listener...")
return server.listener.Close()
}
// CloseOnProgramEnd closes the server right before the program ends, so that all data of the server are
// saved properly.
func (server *Server) CloseOnProgramEnd() {
c := make(chan os.Signal, 2)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-c
if err := server.Close(); err != nil {
server.log.Errorf("error shutting down server: %v", err)
}
}()
}
// running checks if the server is currently running.
func (server *Server) running() bool {
return server.started.Load()
}
// startListening starts making the EncodeBlock listener listen, accepting new connections from players.
func (server *Server) startListening() error {
server.startTime = time.Now()
w := server.log.Writer()
defer func() {
_ = w.Close()
}()
cfg := minecraft.ListenConfig{
MaximumPlayers: server.c.Server.MaximumPlayers,
StatusProvider: statusProvider{s: server},
AuthenticationDisabled: !server.c.Server.AuthEnabled,
}
var err error
//noinspection SpellCheckingInspection
server.listener, err = cfg.Listen("raknet", server.c.Network.Address)
if err != nil {
return fmt.Errorf("listening on address failed: %w", err)
}
server.log.Infof("Server running on %v.\n", server.listener.Addr())
return nil
}
// run runs the server, continuously accepting new connections from players. It returns when the server is
// closed by a call to Close.
func (server *Server) run() {
for {
c, err := server.listener.Accept()
if err != nil {
// Accept will only return an error if the Listener was closed, meaning trying to continue
// listening is futile.
close(server.players)
return
}
go server.handleConn(c.(*minecraft.Conn))
}
}
// handleConn handles an incoming connection accepted from the Listener.
func (server *Server) handleConn(conn *minecraft.Conn) {
//noinspection SpellCheckingInspection
data := minecraft.GameData{
Yaw: 90,
WorldName: server.c.World.Name,
PlayerPosition: vec64To32(server.world.Spawn().Vec3Centre().Add(mgl64.Vec3{0, 1.62})),
PlayerGameMode: 1,
// We set these IDs to 1, because that's how the session will treat them.
EntityUniqueID: 1,
EntityRuntimeID: 1,
Time: int64(server.world.Time()),
GameRules: map[string]interface{}{"naturalregeneration": false},
Difficulty: 2,
Items: server.itemEntries(),
ServerAuthoritativeMovementMode: packet.AuthoritativeMovementModeServer,
ServerAuthoritativeInventory: true,
}
if err := conn.StartGame(data); err != nil {
_ = server.listener.Disconnect(conn, "Connection timeout.")
server.log.Debugf("connection %v failed spawning: %v\n", conn.RemoteAddr(), err)
return
}
id, err := uuid.Parse(conn.IdentityData().Identity)
if err != nil {
_ = conn.Close()
server.log.Warnf("connection %v has a malformed UUID ('%v')\n", conn.RemoteAddr(), id)
return
}
if p, ok := server.Player(id); ok {
p.Disconnect("Logged in from another location.")
}
server.players <- server.createPlayer(id, conn)
}
// handleSessionClose handles the closing of a session. It removes the player of the session from the server.
func (server *Server) handleSessionClose(controllable session.Controllable) {
server.playerMutex.Lock()
delete(server.p, controllable.UUID())
server.playerMutex.Unlock()
}
// createPlayer creates a new player instance using the UUID and connection passed.
func (server *Server) createPlayer(id uuid.UUID, conn *minecraft.Conn) *player.Player {
s := session.New(conn, server.c.World.MaximumChunkRadius, server.log, &server.joinMessage, &server.quitMessage)
p := player.NewWithSession(conn.IdentityData().DisplayName, conn.IdentityData().XUID, id, server.createSkin(conn.ClientData()), s, server.world.Spawn().Vec3Middle())
s.Start(p, server.world, server.handleSessionClose)
return p
}
// loadWorld loads the world of the server, ending the program if the world could not be loaded.
func (server *Server) loadWorld() {
server.log.Debug("Loading world...")
p, err := mcdb.New(server.c.World.Folder)
if err != nil {
server.log.Fatalf("error loading world: %v", err)
}
server.world.Provider(p)
server.log.Debugf("Loaded world '%v'.", server.world.Name())
}
// createSkin creates a new skin using the skin data found in the client data in the login, and returns it.
func (server *Server) createSkin(data login.ClientData) skin.Skin {
// gopher tunnel guarantees the following values are valid data and are of the correct size.
skinData, _ := base64.StdEncoding.DecodeString(data.SkinData)
modelData, _ := base64.StdEncoding.DecodeString(data.SkinGeometry)
skinResourcePatch, _ := base64.StdEncoding.DecodeString(data.SkinResourcePatch)
modelConfig, _ := skin.DecodeModelConfig(skinResourcePatch)
playerSkin := skin.New(data.SkinImageWidth, data.SkinImageHeight)
playerSkin.Persona = data.PersonaSkin
playerSkin.Pix = skinData
playerSkin.Model = modelData
playerSkin.ModelConfig = modelConfig
for _, animation := range data.AnimatedImageData {
var t skin.AnimationType
switch animation.Type {
case protocol.SkinAnimationHead:
t = skin.AnimationHead
case protocol.SkinAnimationBody32x32:
t = skin.AnimationBody32x32
case protocol.SkinAnimationBody128x128:
t = skin.AnimationBody128x128
}
anim := skin.NewAnimation(animation.ImageWidth, animation.ImageHeight, animation.AnimationExpression, t)
anim.FrameCount = int(animation.Frames)
anim.Pix, _ = base64.StdEncoding.DecodeString(animation.Image)
playerSkin.Animations = append(playerSkin.Animations, anim)
}
return playerSkin
}
// registerTargetFunc registers a cmd.TargetFunc to be able to get all players connected and all entities in
// the server's world.
func (server *Server) registerTargetFunc() {
cmd.AddTargetFunc(func(src cmd.Source) ([]cmd.Target, []cmd.Target) {
entities, players := src.World().Entities(), server.Players()
eTargets, pTargets := make([]cmd.Target, len(entities)), make([]cmd.Target, len(players))
entities = src.World().Entities()
for i, e := range entities {
eTargets[i] = e
}
for i, p := range players {
pTargets[i] = p
}
return eTargets, pTargets
})
}
// vec64To32 converts a mgl64.Vec3 to a mgl32.Vec3.
func vec64To32(vec3 mgl64.Vec3) mgl32.Vec3 {
return mgl32.Vec3{float32(vec3[0]), float32(vec3[1]), float32(vec3[2])}
}
// itemEntries loads a list of all custom item entries of the server, ready to be sent in the StartGame
// packet.
func (server *Server) itemEntries() (entries []protocol.ItemEntry) {
for _, name := range world_itemNames() {
entries = append(entries, protocol.ItemEntry{
Name: name,
RuntimeID: int16(world_runtimeById(world.ItemEntry{Name: name})),
})
}
return
}
//go:linkname item_registerVanillaCreativeItems github.com/df-mc/dragonfly/dragonfly/item.registerVanillaCreativeItems
//noinspection ALL
func item_registerVanillaCreativeItems()
//go:linkname world_loadItemEntries github.com/df-mc/dragonfly/dragonfly/world.loadItemEntries
//noinspection all
func world_loadItemEntries() error
//go:linkname world_runtimeById github.com/df-mc/dragonfly/dragonfly/world.runtimeById
//noinspection ALL
func world_runtimeById(entry world.ItemEntry) int32
//go:linkname world_itemNames github.com/df-mc/dragonfly/dragonfly/world.itemNames
//noinspection all
func world_itemNames() map[int32]string