aboutsummaryrefslogtreecommitdiff
path: root/internal/game/cmd_swapid.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-07 00:22:32 -0400
committerhistoria <[not public]>2026-07-07 00:22:32 -0400
commita51f7a53aa2c487ebf94ba0363038574ae8bb590 (patch)
treeb393a8f9e4732b40f6fe8058d086bd631537ad3b /internal/game/cmd_swapid.go
parent9292634f2f8d71a53879ff07e1330c671b207d51 (diff)
downloadthehouseoficarus-a51f7a53aa2c487ebf94ba0363038574ae8bb590.tar.gz
feat: hidden exits (and related flags) work with commands that change room ids. exit code simplified and unified between impassable and blocked exits.
Diffstat (limited to 'internal/game/cmd_swapid.go')
-rw-r--r--internal/game/cmd_swapid.go91
1 files changed, 54 insertions, 37 deletions
diff --git a/internal/game/cmd_swapid.go b/internal/game/cmd_swapid.go
index 6ee1118..bbcc79c 100644
--- a/internal/game/cmd_swapid.go
+++ b/internal/game/cmd_swapid.go
@@ -3,10 +3,10 @@ package game
import (
"fmt"
"os"
- "path/filepath"
- "regexp"
"strconv"
+ "gopkg.in/yaml.v3"
+
"thehouseoficarus/internal/net"
)
@@ -63,38 +63,25 @@ func (g *Game) executeSwapID(sess *net.Session, args []string, rawInput string)
return
}
- roomsDir := filepath.Join(g.DataDir, "rooms")
- re1 := regexp.MustCompile(fmt.Sprintf(`\b%d\b`, id1))
- re2 := regexp.MustCompile(fmt.Sprintf(`\b%d\b`, id2))
- placeholder := fmt.Sprintf("__SWAPID_TEMP_%d__", id1^id2)
-
- var modified []string
- err := filepath.WalkDir(roomsDir, func(roomPath string, d os.DirEntry, err error) error {
- if err != nil || d.IsDir() || filepath.Ext(roomPath) != ".yaml" {
- return nil
- }
- data, rerr := os.ReadFile(roomPath)
- if rerr != nil {
- return nil
- }
- text := string(data)
- if !re1.MatchString(text) && !re2.MatchString(text) {
- return nil
+ // Refuse while non-admin players are online: swapping room IDs relocates
+ // every character whose state embeds those IDs, which is disruptive to
+ // normal play. Admins may stay online (their own state is migrated too).
+ for _, other := range g.Hub.AllSessions() {
+ if other == sess || other.Player == nil {
+ continue
}
- text = re1.ReplaceAllString(text, placeholder)
- text = re2.ReplaceAllString(text, strconv.Itoa(id1))
- text = regexp.MustCompile(placeholder).ReplaceAllString(text, strconv.Itoa(id2))
- if werr := os.WriteFile(roomPath, []byte(text), 0644); werr != nil {
- return werr
+ if !g.checkAdmin(other) {
+ sess.WriteLine("Cannot swap room IDs while non-admin players are online.")
+ return
}
- modified = append(modified, roomPath)
- return nil
- })
- if err != nil {
- sess.WriteLine(fmt.Sprintf("Error updating room files: %v", err))
- return
}
+ // Swap the two files first. After this, path1 holds the old room id2's
+ // content and path2 holds the old room id1's content. Rebuilding the index
+ // then makes LoadRoom(id1)/LoadRoom(id2) return the swapped content, so the
+ // reference rewrite below operates on the correctly-positioned rooms and
+ // re-marshaling writes an `id:` field matching the new filename (keeping
+ // the file's id field consistent with its name, as the old regex did).
tempPath := path1 + ".swapid-tmp"
if rerr := os.Rename(path1, tempPath); rerr != nil {
sess.WriteLine(fmt.Sprintf("Error renaming room %d: %v", id1, rerr))
@@ -113,14 +100,44 @@ func (g *Game) executeSwapID(sess *net.Session, args []string, rawInput string)
}
g.World.RebuildRoomIndex(g.DataDir)
- g.World.LoadRoom(id1)
- g.World.LoadRoom(id2)
- if p.RoomID == id1 {
- p.RoomID = id2
- } else if p.RoomID == id2 {
- p.RoomID = id1
+ // Remap every genuine room-ID reference (exit targets, trigger/on-enter
+ // room+teleport+despawn_rooms, mob wander_rooms) across all rooms. This
+ // replaces the old \b<id>\b regex, which also clobbered unrelated bare
+ // integers (delays, quantities, color indices) and author flag values.
+ // The two swapped rooms are always re-marshaled to correct their `id:`
+ // field; other rooms are re-marshaled only if a reference actually moved.
+ idMap := map[int]int{id1: id2, id2: id1}
+ var updated int
+ for id := range g.World.RoomIndex() {
+ room, err := g.World.LoadRoom(id)
+ if err != nil {
+ continue
+ }
+ changed := room.RewriteRoomIDs(idMap)
+ if !changed && id != id1 && id != id2 {
+ continue
+ }
+ path, ok := g.World.GetRoomPath(id)
+ if !ok {
+ continue
+ }
+ data, merr := yaml.Marshal(room)
+ if merr != nil {
+ continue
+ }
+ if werr := os.WriteFile(path, data, 0644); werr != nil {
+ continue
+ }
+ updated++
}
- sess.WriteLine(fmt.Sprintf("Swapped room IDs %d and %d (%d files updated).", id1, id2, len(modified)))
+ // Migrate room-ID-embedded state across all characters (online + offline):
+ // discovered-exit player flags, current RoomID, EnterSeqRoom, MapSymbols,
+ // and RoomsVisited. This is what the player asked for — without it,
+ // swapid leaves every character's hidden_exit_<id>_<dir> flags (and more)
+ // pointing at stale room IDs.
+ g.RewriteRoomIDs(idMap)
+
+ sess.WriteLine(fmt.Sprintf("Swapped room IDs %d and %d (%d files updated).", id1, id2, updated))
}