aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/casino.go4
-rw-r--r--internal/game/cmd_queued.go2
-rw-r--r--internal/game/core_command_queue.go39
-rw-r--r--internal/game/exit_migrate.go3
-rw-r--r--internal/game/hardening_queue_cleanup_test.go25
-rw-r--r--internal/game/sys_safespot_state.go16
-rw-r--r--internal/game/sys_safespot_state_test.go19
-rw-r--r--internal/game/tick.go3
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)
}