1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
package game
import (
"strconv"
"strings"
"thehouseoficarus/internal/player"
)
// rewritePlayerRoomIDs remaps room-ID-embedded state in a single player per
// idMap (oldID -> newID): discovered-exit player flag keys
// (hidden_exit_<id>_<dir>), current RoomID, PendingSequence.RoomID, MapSymbols
// keys, and Stats.RoomsVisited bitset. Returns whether any field was changed.
//
// idMap is assumed to be a bijection (see Room.RewriteRoomIDs); the flag-key
// and int-map rekeys use delete-all-then-set-all so a swap (A<->B) cannot
// clobber an entry mid-rewrite.
func rewritePlayerRoomIDs(p *player.Player, idMap map[int]int) bool {
if p == nil || len(idMap) == 0 {
return false
}
changed := false
if remapScalar(idMap, &p.RoomID) {
changed = true
}
if p.PendingSequence != nil && p.PendingSequence.RoomID != 0 && remapScalar(idMap, &p.PendingSequence.RoomID) {
changed = true
}
if len(p.Flags) > 0 && rewriteDiscoveredExitFlags(p.Flags, idMap) {
changed = true
}
if len(p.MapSymbols) > 0 && rekeyIntMap(p.MapSymbols, idMap) {
changed = true
}
next, moved := p.Stats.RoomsVisited.Rekey(idMap)
if moved {
p.Stats.RoomsVisited = next
changed = true
}
return changed
}
func remapScalar(idMap map[int]int, n *int) bool {
if newID, ok := idMap[*n]; ok && newID != *n {
*n = newID
return true
}
return false
}
// rewriteDiscoveredExitFlags rewrites hidden_exit_<roomID>_<dir> flag keys,
// remapping the embedded roomID per idMap. Flag values are preserved.
func rewriteDiscoveredExitFlags(flags map[string]any, idMap map[int]int) bool {
type move struct {
oldKey, newKey string
val any
}
var moves []move
for k, v := range flags {
if !strings.HasPrefix(k, hiddenExitFlagPrefix) {
continue
}
rest := k[len(hiddenExitFlagPrefix):] // "<roomID>_<dir>"
idx := strings.IndexByte(rest, '_')
if idx <= 0 {
continue
}
n, err := strconv.Atoi(rest[:idx])
if err != nil {
continue
}
newID, ok := idMap[n]
if !ok || newID == n {
continue
}
moves = append(moves, move{k, hiddenExitFlagPrefix + strconv.Itoa(newID) + "_" + rest[idx+1:], v})
}
if len(moves) == 0 {
return false
}
for _, m := range moves {
delete(flags, m.oldKey)
}
for _, m := range moves {
flags[m.newKey] = m.val
}
return true
}
// rekeyIntMap rekeys a map[int]V per idMap (oldID -> newID), preserving values.
// Uses delete-all-then-set-all so a swap cannot clobber an entry.
func rekeyIntMap[V any](m map[int]V, idMap map[int]int) bool {
type entry struct {
key int
val V
}
var saved []entry
for k, v := range m {
if newID, ok := idMap[k]; ok && newID != k {
saved = append(saved, entry{k, v})
}
}
if len(saved) == 0 {
return false
}
for _, e := range saved {
delete(m, e.key)
}
for _, e := range saved {
m[idMap[e.key]] = e.val
}
return true
}
// RewriteRoomIDs is the shared entry point called by every room-ID change path
// (the swapid command and the admin web GUI rename) to migrate room-ID-embedded
// state across all characters. Online players are updated in memory (and
// re-saved); offline characters are loaded, rewritten, and saved. It follows
// the same cross-goroutine mutation precedent as cmd_undig/cmd_summon.
func (g *Game) RewriteRoomIDs(idMap map[int]int) {
if len(idMap) == 0 {
return
}
online := make(map[string]bool)
if g.Hub != nil {
for _, sess := range g.Hub.AllSessions() {
if sess.Player == nil {
continue
}
if rewritePlayerRoomIDs(sess.Player, idMap) {
g.AccountStore.SaveCharacter(sess.Player)
}
online[sess.Player.Name] = true
}
}
names, err := g.AccountStore.ListCharacterNames()
if err != nil {
return
}
for _, name := range names {
if online[name] {
continue
}
p, err := g.AccountStore.LoadCharacter(name)
if err != nil {
continue
}
if rewritePlayerRoomIDs(p, idMap) {
g.AccountStore.SaveCharacter(p)
}
}
}
|