aboutsummaryrefslogtreecommitdiff
path: root/internal/game/exit_migrate.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/exit_migrate.go')
-rw-r--r--internal/game/exit_migrate.go150
1 files changed, 150 insertions, 0 deletions
diff --git a/internal/game/exit_migrate.go b/internal/game/exit_migrate.go
new file mode 100644
index 0000000..845a6b2
--- /dev/null
+++ b/internal/game/exit_migrate.go
@@ -0,0 +1,150 @@
+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, EnterSeqRoom, MapSymbols keys, and
+// Stats.RoomsVisited keys. 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.EnterSeqRoom != 0 && remapScalar(idMap, &p.EnterSeqRoom) {
+ changed = true
+ }
+ if len(p.Flags) > 0 && rewriteDiscoveredExitFlags(p.Flags, idMap) {
+ changed = true
+ }
+ if len(p.MapSymbols) > 0 && rekeyIntMap(p.MapSymbols, idMap) {
+ changed = true
+ }
+ if len(p.Stats.RoomsVisited) > 0 && rekeyIntMap(p.Stats.RoomsVisited, idMap) {
+ 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)
+ }
+ }
+}