aboutsummaryrefslogtreecommitdiff
path: root/internal/game/game.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 15:16:44 -0400
committerhistoria <[not public]>2026-09-18 15:16:44 -0400
commit7eab582d8a8ee2af1a6baae7baf0696c7005de2e (patch)
tree902bbc8c6e39c9f62f779ca23267714ec420b9f5 /internal/game/game.go
parent153acb7f644d755c45533b7c5311e3ffa47e8973 (diff)
downloadthehouseoficarus-7eab582d8a8ee2af1a6baae7baf0696c7005de2e.tar.gz
fix: harden triggers, economy, casino, world data, and admin/net paths
Diffstat (limited to 'internal/game/game.go')
-rw-r--r--internal/game/game.go18
1 files changed, 18 insertions, 0 deletions
diff --git a/internal/game/game.go b/internal/game/game.go
index de383a6..3ffc72e 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -87,6 +87,11 @@ type Game struct {
shutdownRun *scheduledShutdown
shutdownHook func(reason string)
transportStop func()
+ // shutdownDone is closed after runShutdown's ordered sequence (drain,
+ // refund, final save, transport close) completes. main waits on it on the
+ // transport-failure path before reading ShutdownSaveError, so it cannot
+ // exit while the final save is still in flight.
+ shutdownDone chan struct{}
// shutdownSaveMu guards shutdownSaveErr, the error of the final save
// runShutdown performs (nil when it succeeded or no shutdown ran). main
@@ -140,6 +145,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig *
guardWatchTimers: make(map[string]int),
hackingStates: make(map[string]*hacking.Session),
sequences: make(map[string]*sequence),
+ shutdownDone: make(chan struct{}),
}
g.owner = newOwner()
// Runtime assignment (not initializer-level) so the command registry,
@@ -250,6 +256,15 @@ func (g *Game) ShutdownSaveError() error {
return g.shutdownSaveErr
}
+// ShutdownDone returns a channel that is closed once the graceful shutdown
+// sequence (including the final online-character save and the transport
+// close) has completed. A caller that starts shutdown itself (the
+// transport-failure path) waits on it before reading ShutdownSaveError and
+// exiting, so the save is not skipped.
+func (g *Game) ShutdownDone() <-chan struct{} {
+ return g.shutdownDone
+}
+
// recordShutdownSaveErr stores the final-save failure for ShutdownSaveError.
// The shutdown sequence runs on its own goroutine, so the field is mutex
// guarded against the reader in main.
@@ -300,6 +315,9 @@ func (g *Game) runShutdown() {
g.transportStop()
}
log.Println("shutdown complete")
+ // Publish completion last: main waits on this before reading
+ // ShutdownSaveError on the transport-failure path.
+ close(g.shutdownDone)
}
// saveAllOnlineCharacters persists every online player exactly once under