From 153acb7f644d755c45533b7c5311e3ffa47e8973 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 18 Sep 2026 05:31:09 -0400 Subject: fix: harden engine across casino, economy, concurrency, security, and content --- internal/game/core_command_queue.go | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) (limited to 'internal/game/core_command_queue.go') diff --git a/internal/game/core_command_queue.go b/internal/game/core_command_queue.go index 7afc75b..28b7a36 100644 --- a/internal/game/core_command_queue.go +++ b/internal/game/core_command_queue.go @@ -1,6 +1,7 @@ package game import ( + "sort" "sync" "time" @@ -18,8 +19,10 @@ type QueuedCommand struct { // CommandQueue manages the three per-player command queues (free, active, // consume) with proper mutex protection. Free commands stack and execute in // order; active commands replace each other (only one survives); consume -// commands are a single-slot eat/drink queue. All three are written from -// session goroutines and drained from the tick goroutine. +// commands are a single-slot eat/drink queue. Writers are the command +// handlers (owner goroutine); the consumer is ProcessQueuedCommands on the +// owner tick. The mutex is kept as a cheap guard for callers that reach a +// queue from outside that single-owner path. type CommandQueue struct { mu sync.Mutex freeQueue map[string][]QueuedCommand @@ -65,8 +68,8 @@ func (q *CommandQueue) DrainFree(name string) []QueuedCommand { return cmds } -// DrainActive returns all active commands sorted by timestamp (first-to-queue -// wins), then clears the map. +// DrainActive returns all active commands, then clears the map. It does not +// order them: ProcessQueuedCommands sorts by Timestamp (first-to-queue wins). func (q *CommandQueue) DrainActive() []QueuedCommand { q.mu.Lock() defer q.mu.Unlock() @@ -103,19 +106,37 @@ func (q *CommandQueue) DeleteActive(name string) { delete(q.activeQueue, name) } -// ActivePositions returns the sorted names of all players with active commands, -// and the position (1-indexed) of the named player among them. Used by the -// `queued` command to show queue order. +// ActivePositions returns the sorted names of every player holding an active +// command, and the position (1-indexed) of the named player among them, or 0 +// when that player has no active command. Used by the `queued` command to +// show queue order. func (q *CommandQueue) ActivePositions(name string) (names []string, pos int) { q.mu.Lock() defer q.mu.Unlock() for n := range q.activeQueue { names = append(names, n) } - // sort names (caller may want them sorted) + sort.Strings(names) + for i, n := range names { + if n == name { + return names, i + 1 + } + } return names, 0 } +// ClearPlayer drops every queued command for the player: free, active, and +// consume. Called when a session detaches (logout, quit, disconnect, or +// character removal) so leftovers cannot retain the departed session and +// player, and cannot replay against a later session for the same name. +func (q *CommandQueue) ClearPlayer(name string) { + q.mu.Lock() + defer q.mu.Unlock() + delete(q.freeQueue, name) + delete(q.activeQueue, name) + delete(q.consumeQueue, name) +} + // DrainConsume returns and removes the consume command for the player. func (q *CommandQueue) DrainConsume(name string) (*QueuedCommand, bool) { q.mu.Lock() -- cgit v1.2.3