feat: add initial code
This commit is contained in:
217
cmd/urbanterror-agones/agones.go
Normal file
217
cmd/urbanterror-agones/agones.go
Normal file
@ -0,0 +1,217 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
sdk "agones.dev/agones/sdks/go"
|
||||
q3c "bp99.eu/ut-agones/internal/quake3/client"
|
||||
)
|
||||
|
||||
const (
|
||||
checkInterval = 5 * time.Second
|
||||
playersListName = "players"
|
||||
)
|
||||
|
||||
type sidecarState struct {
|
||||
q3 *q3c.RobustQ3Client
|
||||
sdk *sdk.SDK
|
||||
players map[string]q3c.Player
|
||||
}
|
||||
|
||||
type agonesObserver struct {
|
||||
id string
|
||||
state *sidecarState
|
||||
ctx context.Context
|
||||
firstPing bool
|
||||
}
|
||||
|
||||
func (o *agonesObserver) Update(event string) {
|
||||
slog.Debug("Got notification", "event", event)
|
||||
switch event {
|
||||
case q3c.PongEvent:
|
||||
if o.firstPing {
|
||||
slog.Debug("This is the first time gameserver was reached; reporting ready to Agones")
|
||||
if err := o.state.sdk.Ready(); err != nil {
|
||||
slog.Error("Failed to send ready signal to Agones", "error", err)
|
||||
}
|
||||
o.firstPing = false
|
||||
} else {
|
||||
slog.Debug("Sending health ping to Agones")
|
||||
if err := o.state.sdk.Health(); err != nil {
|
||||
slog.Error("Failed to send health ping to Agones", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *agonesObserver) GetID() string {
|
||||
return o.id
|
||||
}
|
||||
|
||||
func StartAgonesSidecar() error {
|
||||
slog.Info("Starting Urban Terror Agones sidecar")
|
||||
|
||||
// Create context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Create simple game client
|
||||
client, err := q3c.New("localhost", 27960)
|
||||
if err != nil {
|
||||
slog.Error("Failed to create new Quake3 client", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create robust game client
|
||||
robustClient := q3c.NewRobust(ctx, client)
|
||||
|
||||
// Initialize the Agones SDK
|
||||
sdk, err := sdk.NewSDK()
|
||||
if err != nil {
|
||||
slog.Error("Failed to initialize Agones SDK", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set up SIGINT/SIGTERM handling
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigChan
|
||||
slog.Info("Received shutdown signal")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// Keep state
|
||||
state := &sidecarState{
|
||||
q3: robustClient,
|
||||
sdk: sdk,
|
||||
players: make(map[string]q3c.Player, 0),
|
||||
}
|
||||
|
||||
// Create and subscribe game client event observer
|
||||
o := &agonesObserver{
|
||||
id: "agones",
|
||||
state: state,
|
||||
ctx: ctx,
|
||||
firstPing: true,
|
||||
}
|
||||
robustClient.Subscribe(o)
|
||||
|
||||
// Start status check loop
|
||||
go statusLoop(ctx, state)
|
||||
|
||||
// Keep alive
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
slog.Info("Shutting down Urban Terror Agones sidecar")
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
if err := state.q3.Stop(); err != nil {
|
||||
slog.Error("Failed to gracefully Quake3 client", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusLoop(ctx context.Context, state *sidecarState) {
|
||||
slog.Info("Starting status check loop", "interval", checkInterval)
|
||||
|
||||
ticker := time.NewTicker(checkInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
slog.Info("Exiting status check loop", "reason", "context cancelled")
|
||||
return
|
||||
case <-ticker.C:
|
||||
slog.Debug("Tick; Retrieving gameserver status")
|
||||
reportStatus(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reportStatus(state *sidecarState) {
|
||||
status, err := state.q3.GetStatus()
|
||||
if err != nil {
|
||||
slog.Error("Failed to get gameserer status from Quake3 client", "error", err)
|
||||
}
|
||||
|
||||
if err := updatePlayersList(state, status.Players); err != nil {
|
||||
slog.Error("Error while updating player list", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePlayersList(state *sidecarState, players []q3c.Player) error {
|
||||
curPlayerMap := make(map[string]q3c.Player)
|
||||
for _, p := range players {
|
||||
curPlayerMap[p.Name] = p
|
||||
}
|
||||
|
||||
// New and still connected players
|
||||
for _, p := range players {
|
||||
if _, exists := state.players[p.Name]; exists {
|
||||
slog.Debug("Player already known to be online", "player", p.Name)
|
||||
} else {
|
||||
slog.Debug("Player joined", "player", p.Name)
|
||||
if err := tryAppendToPlayerList(state.sdk, p.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
state.players[p.Name] = p
|
||||
}
|
||||
}
|
||||
|
||||
// Check for disconnected players
|
||||
for name := range state.players {
|
||||
if _, exists := curPlayerMap[name]; !exists {
|
||||
slog.Debug("Player disconnected", "player", name)
|
||||
if err := tryDeleteFromPlayerList(state.sdk, name); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(state.players, name)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Debug("Updated players")
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryAppendToPlayerList(sdk *sdk.SDK, player string) error {
|
||||
exists, err := sdk.Beta().ListContains(playersListName, player)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := sdk.Beta().AppendListValue(playersListName, player); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryDeleteFromPlayerList(sdk *sdk.SDK, player string) error {
|
||||
exists, err := sdk.Beta().ListContains(playersListName, player)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := sdk.Beta().DeleteListValue(playersListName, player); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user