aboutsummaryrefslogtreecommitdiff
path: root/internal/game/game.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 16:13:49 -0400
committerhistoria <[not public]>2026-09-18 16:13:49 -0400
commit194164a77eb4f2479b94c19e6f0e1278ab608781 (patch)
tree53413add8ed6f32a34d1733397e380cc98ef70ef /internal/game/game.go
parent67e01aab85607b9e601185c7d62dff576af3bb43 (diff)
downloadthehouseoficarus-194164a77eb4f2479b94c19e6f0e1278ab608781.tar.gz
fix: more casino, economy, gameplay, security, and world data hardening gaps
Diffstat (limited to 'internal/game/game.go')
-rw-r--r--internal/game/game.go38
1 files changed, 26 insertions, 12 deletions
diff --git a/internal/game/game.go b/internal/game/game.go
index 3ffc72e..5ad1033 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -79,6 +79,13 @@ type Game struct {
sequences map[string]*sequence
globalSeqs []*sequence
+ // disconnectDeferrals counts, per session, how many ticks its pending
+ // disconnect has been deferred for combat. Per-Game (not package-level)
+ // so a second Game in one process cannot inherit stale counters, and
+ // pruned each tick so a session hard-removed elsewhere cannot pin its
+ // Player in memory for the life of the process.
+ disconnectDeferrals map[*net.Session]int
+
shutdownMu sync.Mutex
// shutdownOnce guards RequestShutdown; transportStop is the registered
@@ -134,18 +141,19 @@ func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig *
// Entropy-seeded, not clock-seeded: a wall-clock seed makes every
// shoe and reel stream reconstructible. casino.NewManager(seed) stays
// the deterministic test seam.
- Casino: casino.NewManagerEntropy(),
- flagIndex: newFlagTriggerIndex(),
- queue: NewCommandQueue(),
- safespot: NewSafespotManager(),
- ValidationConfig: valConfig,
- StartingRoom: startingRoom,
- loggedInChars: make(map[string]*net.Session),
- restTimers: make(map[string]uint64),
- guardWatchTimers: make(map[string]int),
- hackingStates: make(map[string]*hacking.Session),
- sequences: make(map[string]*sequence),
- shutdownDone: make(chan struct{}),
+ Casino: casino.NewManagerEntropy(),
+ flagIndex: newFlagTriggerIndex(),
+ queue: NewCommandQueue(),
+ safespot: NewSafespotManager(),
+ ValidationConfig: valConfig,
+ StartingRoom: startingRoom,
+ loggedInChars: make(map[string]*net.Session),
+ restTimers: make(map[string]uint64),
+ guardWatchTimers: make(map[string]int),
+ hackingStates: make(map[string]*hacking.Session),
+ sequences: make(map[string]*sequence),
+ disconnectDeferrals: make(map[*net.Session]int),
+ shutdownDone: make(chan struct{}),
}
g.owner = newOwner()
// Runtime assignment (not initializer-level) so the command registry,
@@ -700,6 +708,12 @@ func (g *Game) ProcessQueuedCommands() {
})
for _, qc := range actives {
+ // A queued command can outlive its session (logout, disconnect, or a
+ // re-login on a new connection): never dereference a departed one on
+ // the owner goroutine. Mirrors the free-queue guard above.
+ if qc.Session == nil || qc.Session.Player == nil {
+ continue
+ }
raw := qc.Command
if qc.Args != "" {
raw += " " + qc.Args