diff options
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/casino.go | 4 | ||||
| -rw-r--r-- | internal/game/cmd_queued.go | 2 | ||||
| -rw-r--r-- | internal/game/core_command_queue.go | 39 | ||||
| -rw-r--r-- | internal/game/exit_migrate.go | 3 | ||||
| -rw-r--r-- | internal/game/hardening_queue_cleanup_test.go | 25 | ||||
| -rw-r--r-- | internal/game/sys_safespot_state.go | 16 | ||||
| -rw-r--r-- | internal/game/sys_safespot_state_test.go | 19 | ||||
| -rw-r--r-- | internal/game/tick.go | 3 |
8 files changed, 75 insertions, 36 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go index 3e382e9..971952a 100644 --- a/internal/game/casino.go +++ b/internal/game/casino.go @@ -414,9 +414,7 @@ func (g *Game) casinoMachine(roomID int, gameName string) (*casino.Machine, bool if strings.EqualFold(gameName, "slots") { for _, cfg := range room.CasinoTables { if strings.EqualFold(string(cfg.Game), "slots") { - machine := g.Casino.EnsureMachine(roomID, casino.MachineConfig{ - ID: cfg.ID, Game: casino.GameSlots, MinBet: cfg.MinBet, MaxBet: cfg.MaxBet, - }) + machine := g.Casino.EnsureMachine(roomID, casino.LegacySlotMachine(cfg)) return machine, machine != nil } } diff --git a/internal/game/cmd_queued.go b/internal/game/cmd_queued.go index 5f43ad1..00cf994 100644 --- a/internal/game/cmd_queued.go +++ b/internal/game/cmd_queued.go @@ -50,7 +50,7 @@ func (g *Game) doQueued(sess *net.Session) { sess.WriteLine(" All queued actions take effect on the next game tick (600ms).") sess.WriteLine(" Free actions stack. Only the most recent active action survives.") - // ActivePositions already returns the names sorted and the caller's + // ActivePositions returns the names in resolution order and the caller's // 1-based position, so there is nothing to recompute here. names, pos := g.queue.ActivePositions(p.Name) if pos > 0 && len(names) > 1 { diff --git a/internal/game/core_command_queue.go b/internal/game/core_command_queue.go index 28b7a36..8fce71d 100644 --- a/internal/game/core_command_queue.go +++ b/internal/game/core_command_queue.go @@ -106,23 +106,32 @@ func (q *CommandQueue) DeleteActive(name string) { delete(q.activeQueue, name) } -// 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. +// ActivePositions returns the names of every player holding an active command +// in actual resolution order (earliest Timestamp first, matching +// ProcessQueuedCommands), and the 1-based position 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) + type entry struct { + name string + at time.Time } - sort.Strings(names) - for i, n := range names { - if n == name { - return names, i + 1 + entries := make([]entry, 0, len(q.activeQueue)) + for n, qc := range q.activeQueue { + entries = append(entries, entry{name: n, at: qc.Timestamp}) + } + sort.SliceStable(entries, func(i, j int) bool { + return entries[i].at.Before(entries[j].at) + }) + for _, e := range entries { + names = append(names, e.name) + if e.name == name { + pos = len(names) } } - return names, 0 + return names, pos } // ClearPlayer drops every queued command for the player: free, active, and @@ -145,11 +154,3 @@ func (q *CommandQueue) DrainConsume(name string) (*QueuedCommand, bool) { 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) -} diff --git a/internal/game/exit_migrate.go b/internal/game/exit_migrate.go index 2c56d27..55289f2 100644 --- a/internal/game/exit_migrate.go +++ b/internal/game/exit_migrate.go @@ -183,6 +183,9 @@ func (g *Game) MigrateRoomIDsOnOwner(idMap map[int]int) { } g.World.RekeyRooms(idMap) g.MobStore.RekeyRooms(idMap) + if g.safespot != nil { + g.safespot.RekeyRooms(idMap) + } // The file paths changed under the world: rebuild its room index so // lookups agree with the post-migration layout. g.World.RebuildRoomIndex(g.DataDir) diff --git a/internal/game/hardening_queue_cleanup_test.go b/internal/game/hardening_queue_cleanup_test.go index 95b96bf..3285381 100644 --- a/internal/game/hardening_queue_cleanup_test.go +++ b/internal/game/hardening_queue_cleanup_test.go @@ -114,24 +114,27 @@ func TestProcessQueuedCommandsSkipsStaleSession(t *testing.T) { } } -// TestActivePositionsReturnsSortedNamesAndOneBasedPosition pins the documented -// contract of ActivePositions: names sorted, position 1-indexed, 0 when the -// player holds no active command. -func TestActivePositionsReturnsSortedNamesAndOneBasedPosition(t *testing.T) { +// TestActivePositionsReturnsResolutionOrderAndOneBasedPosition pins the +// contract of ActivePositions: names in actual resolution order (earliest +// Timestamp first, matching ProcessQueuedCommands), position 1-indexed, 0 when +// the player holds no active command. +func TestActivePositionsReturnsResolutionOrderAndOneBasedPosition(t *testing.T) { q := NewCommandQueue() - for _, name := range []string{"carol", "alice", "bob"} { - q.EnqueueActive(name, QueuedCommand{Command: "look", Timestamp: time.Now()}) - } + base := time.Now() + // Queue deliberately out of alphabetical order: resolution is first-to-queue. + q.EnqueueActive("alice", QueuedCommand{Command: "look", Timestamp: base.Add(3 * time.Millisecond)}) + q.EnqueueActive("bob", QueuedCommand{Command: "look", Timestamp: base.Add(2 * time.Millisecond)}) + q.EnqueueActive("carol", QueuedCommand{Command: "look", Timestamp: base.Add(1 * time.Millisecond)}) names, pos := q.ActivePositions("bob") - if want := []string{"alice", "bob", "carol"}; !reflect.DeepEqual(names, want) { - t.Errorf("names = %v, want %v", names, want) + if want := []string{"carol", "bob", "alice"}; !reflect.DeepEqual(names, want) { + t.Errorf("names = %v, want %v (timestamp order)", names, want) } if pos != 2 { t.Errorf("position of bob = %d, want 2", pos) } - if _, pos := q.ActivePositions("alice"); pos != 1 { - t.Errorf("position of alice = %d, want 1", pos) + if _, pos := q.ActivePositions("carol"); pos != 1 { + t.Errorf("position of carol = %d, want 1", pos) } if _, pos := q.ActivePositions("nobody"); pos != 0 { t.Errorf("position of an unqueued player = %d, want 0", pos) diff --git a/internal/game/sys_safespot_state.go b/internal/game/sys_safespot_state.go index 1520bc7..32f57ec 100644 --- a/internal/game/sys_safespot_state.go +++ b/internal/game/sys_safespot_state.go @@ -67,6 +67,22 @@ func (m *SafespotManager) Delete(name string) bool { return ok } +// RekeyRooms remaps the stored RoomID of every safespot per idMap so a live +// room rename does not orphan a player's cover (the safespot lookup keys on +// the room ID and force-ejects on a miss). +func (m *SafespotManager) RekeyRooms(idMap map[int]int) { + if len(idMap) == 0 { + return + } + m.mu.Lock() + defer m.mu.Unlock() + for _, ss := range m.states { + if newID, ok := idMap[ss.RoomID]; ok && newID != ss.RoomID { + ss.RoomID = newID + } + } +} + // DecrementHideCountdown decrements the hide countdown for a player who is // transitioning from hiding to hidden. Returns (newCountdown, ok). A return of // ok=true with newCountdown > 0 means the player is still counting down; diff --git a/internal/game/sys_safespot_state_test.go b/internal/game/sys_safespot_state_test.go new file mode 100644 index 0000000..2976bce --- /dev/null +++ b/internal/game/sys_safespot_state_test.go @@ -0,0 +1,19 @@ +package game + +import "testing" + +// TestSafespotManagerRekeyRooms: a live room rename must remap the room ID +// stored on an active safespot, or the safespot lookup misses and the player +// is force-ejected from cover. +func TestSafespotManagerRekeyRooms(t *testing.T) { + m := NewSafespotManager() + m.Set("alice", SafespotState{Active: true, ObjectDefID: "bush", RoomID: 5}) + m.Set("bob", SafespotState{Active: true, ObjectDefID: "rock", RoomID: 9}) + m.RekeyRooms(map[int]int{5: 7}) + if ss, _ := m.Get("alice"); ss.RoomID != 7 { + t.Fatalf("alice safespot room = %d, want 7", ss.RoomID) + } + if ss, _ := m.Get("bob"); ss.RoomID != 9 { + t.Fatalf("bob safespot room = %d, want 9 (unchanged)", ss.RoomID) + } +} diff --git a/internal/game/tick.go b/internal/game/tick.go index 21489ca..a57ea26 100644 --- a/internal/game/tick.go +++ b/internal/game/tick.go @@ -377,8 +377,7 @@ func (g *Game) MoveTick() { continue } p.MoveTicks-- - if p.MoveTicks > 0 { - } else { + if p.MoveTicks == 0 { g.completeMove(sess, p) g.writePrompt(sess) } |
