-
Notifications
You must be signed in to change notification settings - Fork 95
/
server_status_provider.go
143 lines (129 loc) · 4.49 KB
/
server_status_provider.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
package minecraft
import (
"github.com/sandertv/go-raknet"
"net"
"strconv"
"sync"
"time"
)
// ServerStatusProvider represents a type that is able to provide the visual status of a server, in specific
// its MOTD, the amount of online players and the player limit displayed in the server list.
// These providers may be used to display different information in the server list. Although they overwrite
// the maximum players and online players maintained by a Listener in the server list, these values are not
// changed and will still be used internally to check if players are able to be connected. Players will still
// be disconnected if the maximum player count as set in the ListenConfig.MaximumPlayers field of a Listener
// is reached (unless ListenConfig.MaximumPlayers is 0).
type ServerStatusProvider interface {
// ServerStatus returns the server status which includes the MOTD/server name, amount of online players
// and the amount of maximum players.
// The player count and max players of the minecraft.Listener that calls this method is passed.
ServerStatus(playerCount, maxPlayers int) ServerStatus
}
// ServerStatus holds the information shown in the Minecraft server list. They have no impact on the listener
// functionality-wise.
type ServerStatus struct {
// ServerName is the name or MOTD of the server, as shown in the server list.
ServerName string
// PlayerCount is the current amount of players displayed in the list.
PlayerCount int
// MaxPlayers is the maximum amount of players in the server. If set to 0, MaxPlayers is set to
// PlayerCount + 1.
MaxPlayers int
}
// ListenerStatusProvider is the default ServerStatusProvider of a Listener. It displays a static server name/
// MOTD and displays the player count and maximum amount of players of the server.
type ListenerStatusProvider struct {
// name is the name of the server, or the MOTD, that is displayed in the server list.
name string
}
// NewStatusProvider creates a ListenerStatusProvider that displays the server name passed.
func NewStatusProvider(serverName string) ListenerStatusProvider {
return ListenerStatusProvider{name: serverName}
}
// ServerStatus ...
func (l ListenerStatusProvider) ServerStatus(playerCount, maxPlayers int) ServerStatus {
return ServerStatus{
ServerName: l.name,
PlayerCount: playerCount,
MaxPlayers: maxPlayers,
}
}
// ForeignStatusProvider is a ServerStatusProvider that provides the status of a target server to the Listener
// so that the MOTD, player count etc. is copied.
type ForeignStatusProvider struct {
addr string
mu sync.Mutex
status ServerStatus
once sync.Once
closed chan struct{}
}
// NewForeignStatusProvider creates a ForeignStatusProvider that uses the status of the server running at the
// target address. An error is returned if the address is invalid.
// Close must be called if the ForeignStatusProvider is discarded.
func NewForeignStatusProvider(addr string) (*ForeignStatusProvider, error) {
if _, err := net.ResolveUDPAddr("udp", addr); err != nil {
return nil, err
}
f := &ForeignStatusProvider{addr: addr, closed: make(chan struct{})}
go f.update()
return f, nil
}
// ServerStatus returns the ServerStatus of the target server.
func (f *ForeignStatusProvider) ServerStatus(int, int) ServerStatus {
f.mu.Lock()
defer f.mu.Unlock()
return f.status
}
// Close closes the ForeignStatusProvider and stops polling it. Close always returns nil.
func (f *ForeignStatusProvider) Close() error {
f.once.Do(func() {
close(f.closed)
})
return nil
}
// update updates the status every second and cancels when Close is called.
func (f *ForeignStatusProvider) update() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
data, err := raknet.Ping(f.addr)
if err != nil {
continue
}
f.mu.Lock()
f.status = parsePongData(data)
f.mu.Unlock()
case <-f.closed:
return
}
}
}
// parsePongData parses the unconnected pong data passed into the relevant fields of a ServerStatus struct.
func parsePongData(pong []byte) ServerStatus {
frag := splitPong(string(pong))
if len(frag) < 7 {
return ServerStatus{
ServerName: "Invalid pong data",
}
}
serverName := frag[1]
online, err := strconv.Atoi(frag[4])
if err != nil {
return ServerStatus{
ServerName: "Invalid player count",
}
}
max, err := strconv.Atoi(frag[5])
if err != nil {
return ServerStatus{
ServerName: "Invalid max player count",
}
}
return ServerStatus{
ServerName: serverName,
PlayerCount: online,
MaxPlayers: max,
}
}