package game import ( "sync" "time" "thehouseoficarus/internal/net" ) // QueuedCommand represents a player command enqueued for next-tick execution. type QueuedCommand struct { Session *net.Session Command string Args string Timestamp time.Time } // 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. type CommandQueue struct { mu sync.Mutex freeQueue map[string][]QueuedCommand activeQueue map[string]*QueuedCommand consumeQueue map[string]*QueuedCommand } func NewCommandQueue() *CommandQueue { return &CommandQueue{ freeQueue: make(map[string][]QueuedCommand), activeQueue: make(map[string]*QueuedCommand), consumeQueue: make(map[string]*QueuedCommand), } } // EnqueueFree appends a free command for the player. func (q *CommandQueue) EnqueueFree(name string, qc QueuedCommand) { q.mu.Lock() defer q.mu.Unlock() q.freeQueue[name] = append(q.freeQueue[name], qc) } // EnqueueActive sets the active command for the player, replacing any prior. func (q *CommandQueue) EnqueueActive(name string, qc QueuedCommand) { q.mu.Lock() defer q.mu.Unlock() q.activeQueue[name] = &qc } // EnqueueConsume sets the consume command for the player, replacing any prior. func (q *CommandQueue) EnqueueConsume(name string, qc QueuedCommand) { q.mu.Lock() defer q.mu.Unlock() q.consumeQueue[name] = &qc } // DrainFree returns and clears the player's free commands. func (q *CommandQueue) DrainFree(name string) []QueuedCommand { q.mu.Lock() defer q.mu.Unlock() cmds := q.freeQueue[name] delete(q.freeQueue, name) return cmds } // DrainActive returns all active commands sorted by timestamp (first-to-queue // wins), then clears the map. func (q *CommandQueue) DrainActive() []QueuedCommand { q.mu.Lock() defer q.mu.Unlock() var actives []QueuedCommand for _, qc := range q.activeQueue { actives = append(actives, *qc) } q.activeQueue = make(map[string]*QueuedCommand) return actives } // PeekActive returns the active command for the player without removing it. func (q *CommandQueue) PeekActive(name string) (*QueuedCommand, bool) { q.mu.Lock() defer q.mu.Unlock() qc, ok := q.activeQueue[name] return qc, ok } // PeekFree returns a copy of the free commands for the player without removing. func (q *CommandQueue) PeekFree(name string) []QueuedCommand { q.mu.Lock() defer q.mu.Unlock() cmds := q.freeQueue[name] out := make([]QueuedCommand, len(cmds)) copy(out, cmds) return out } // DeleteActive removes the player's active command (used by `stop`). func (q *CommandQueue) DeleteActive(name string) { q.mu.Lock() defer q.mu.Unlock() 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. 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) return names, 0 } // DrainConsume returns and removes the consume command for the player. func (q *CommandQueue) DrainConsume(name string) (*QueuedCommand, bool) { q.mu.Lock() defer q.mu.Unlock() qc, ok := q.consumeQueue[name] delete(q.consumeQueue, name) return qc, ok } // ActiveCount returns the number of players with active commands. Used by the // `queued` command to show position. func (q *CommandQueue) ActiveCount() int { q.mu.Lock() defer q.mu.Unlock() return len(q.activeQueue) }