-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathstatusUpdater.go
104 lines (87 loc) · 2.59 KB
/
statusUpdater.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
package main
import (
"bytes"
"context"
"errors"
"log/slog"
"time"
)
// statusUpdater is responsible for periodically sending `status` and `g15_dumpplayer` command to the
// game client.
type statusUpdater struct {
rcon rconConnection
process *processState
state *gameState
updateRate time.Duration
g15 g15Parser
}
func newStatusUpdater(rcon rconConnection, process *processState, state *gameState, updateRate time.Duration) statusUpdater {
return statusUpdater{
rcon: rcon,
process: process,
state: state,
updateRate: updateRate,
g15: newG15Parser(),
}
}
func (s statusUpdater) start(ctx context.Context) {
timer := time.NewTicker(s.updateRate)
for {
select {
case <-timer.C:
if !s.process.gameProcessActive.Load() {
// Don't do anything until the game is open
continue
}
if err := s.updatePlayerState(ctx); err != nil {
slog.Error("failed to update player state", errAttr(err))
}
case <-ctx.Done():
return
}
}
}
// updatePlayerState fetches the current game state over rcon using both the `status` and `g15_dumpplayer` command
// output. The results are then parsed and applied to the current player and server states.
func (s statusUpdater) updatePlayerState(ctx context.Context) error {
// Sent to client, response via log output
_, errStatus := s.rcon.exec(ctx, "status", true)
if errStatus != nil {
return errors.Join(errStatus, errRCONStatus)
}
dumpPlayer, errDumpPlayer := s.rcon.exec(ctx, "g15_dumpplayer", true)
if errDumpPlayer != nil {
return errors.Join(errDumpPlayer, errRCONG15)
}
var dump DumpPlayer
if errG15 := s.g15.Parse(bytes.NewBufferString(dumpPlayer), &dump); errG15 != nil {
return errors.Join(errG15, errG15Parse)
}
for index, sid := range dump.SteamID {
if index == 0 || index > 32 || !sid.Valid() {
// Actual data always starts at 1
continue
}
player, errPlayer := s.state.players.bySteamID(sid)
if errPlayer != nil {
// status command is what we use to add players to the active game.
continue
}
player.MapTime = time.Since(player.MapTimeStart).Seconds()
if player.Kills > 0 {
player.KPM = float64(player.Kills) / (player.MapTime / 60)
}
player.Ping = dump.Ping[index]
player.Score = dump.Score[index]
player.Deaths = dump.Deaths[index]
player.IsConnected = dump.Connected[index]
player.Team = Team(dump.Team[index])
player.Alive = dump.Alive[index]
player.Health = dump.Health[index]
player.Valid = dump.Valid[index]
player.UserID = dump.UserID[index]
player.UpdatedOn = time.Now()
s.state.players.update(player)
}
return nil
}