aboutsummaryrefslogtreecommitdiff
path: root/internal/world/insert_remove.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/world/insert_remove.go')
-rw-r--r--internal/world/insert_remove.go152
1 files changed, 152 insertions, 0 deletions
diff --git a/internal/world/insert_remove.go b/internal/world/insert_remove.go
new file mode 100644
index 0000000..0811587
--- /dev/null
+++ b/internal/world/insert_remove.go
@@ -0,0 +1,152 @@
+package world
+
+// copyExits returns a shallow copy of an exit map so a hypothetical room copy
+// can be mutated without touching the original. A fresh map is returned even
+// when src is nil so callers can safely assign into it.
+func copyExits(src map[ExitDir]ExitDef) map[ExitDir]ExitDef {
+ dst := make(map[ExitDir]ExitDef, len(src))
+ for k, v := range src {
+ dst[k] = v
+ }
+ return dst
+}
+
+// RewirePreserving returns an ExitDef pointing at newTarget that keeps every
+// non-Room field of src (conditions, blocked messages, flags, hidden/always-
+// blocked). It is the shared "repoint an exit without losing its properties"
+// operation used by insert and remove (both in-game and admin).
+func RewirePreserving(src ExitDef, newTarget int) ExitDef {
+ return ExitDef{
+ Room: newTarget,
+ Condition: src.Condition,
+ BlockedMessage: src.BlockedMessage,
+ SetFlags: src.SetFlags,
+ SetPlayerFlags: src.SetPlayerFlags,
+ Hidden: src.Hidden,
+ AlwaysBlocked: src.AlwaysBlocked,
+ }
+}
+
+// InsertGridConflicts returns the grid conflicts that would result from
+// inserting a new room (identified by newID, which may be a sentinel such as
+// -1 since no file is written) between roomID and the room its dir exit
+// currently leads to (targetID). The new room inherits roomID's outward dir
+// exit properties on the near side and, when the far room's opposite exit
+// points back at roomID, the far room's reciprocal exit is repointed at the
+// new room preserving its own properties — exactly mirroring the writes
+// roomInsert performs. load is the real room loader (g.World.LoadRoom or
+// s.world.LoadRoom). A non-empty result means the insert would make the map
+// unembeddable (overlap or twist) and must be rejected.
+func InsertGridConflicts(roomID int, dir ExitDir, targetID, newID int, load func(int) (*Room, bool)) []GridConflict {
+ oppositeDir := OppositeExit[dir]
+
+ srcRoom, _ := load(roomID)
+ var srcDirExit ExitDef
+ if srcRoom != nil {
+ srcDirExit = srcRoom.Exits[dir]
+ }
+
+ targetRoom, targetOK := load(targetID)
+ hasReciprocal := targetOK && targetRoom.Exits[oppositeDir].Room == roomID
+ var targetOppExit ExitDef
+ if hasReciprocal {
+ targetOppExit = targetRoom.Exits[oppositeDir]
+ }
+
+ newRoom := &Room{Exits: map[ExitDir]ExitDef{
+ dir: {Room: targetID},
+ oppositeDir: {Room: roomID},
+ }}
+
+ return BuildGridConflicts(roomID, func(id int) (*Room, bool) {
+ if id == newID {
+ return newRoom, true
+ }
+ r, ok := load(id)
+ if !ok {
+ return nil, false
+ }
+ if id == roomID {
+ rc := *r
+ rc.Exits = copyExits(r.Exits)
+ rc.Exits[dir] = RewirePreserving(srcDirExit, newID)
+ return &rc, true
+ }
+ if id == targetID && hasReciprocal {
+ rc := *r
+ rc.Exits = copyExits(r.Exits)
+ rc.Exits[oppositeDir] = RewirePreserving(targetOppExit, newID)
+ return &rc, true
+ }
+ return r, true
+ })
+}
+
+// RemoveGridConflicts returns the grid conflicts that would result from
+// removing roomID's dir exit's target (the "inserted" room B) and pulling the
+// far room C (B's dir exit's target, when present) back to roomID — the exact
+// inverse of InsertGridConflicts. roomID's dir exit is repointed at C
+// preserving its current properties; C's opposite exit is repointed at roomID
+// preserving its current properties. When B has no forward exit (a dead end),
+// roomID's dir exit is simply removed (no pull). load is the real room loader.
+// A non-empty result means the pull would make the map unembeddable.
+//
+// The structural guards (B actually leads back to roomID, B has a forward
+// exit, B has no other exits, no extra inbound edges, C's opposite points back
+// at B) are the caller's responsibility; this helper only models the
+// geometric consequence of the rewiring.
+func RemoveGridConflicts(roomID int, dir ExitDir, load func(int) (*Room, bool)) []GridConflict {
+ oppositeDir := OppositeExit[dir]
+
+ srcRoom, ok := load(roomID)
+ if !ok {
+ return nil
+ }
+ bExit, has := srcRoom.Exits[dir]
+ if !has {
+ return nil
+ }
+ bID := bExit.Room
+ bRoom, ok := load(bID)
+ if !ok {
+ return nil
+ }
+ cExit, hasFar := bRoom.Exits[dir]
+ var cID int
+ if hasFar {
+ cID = cExit.Room
+ }
+ var cOppExit ExitDef
+ if hasFar {
+ if cRoom, ok := load(cID); ok {
+ cOppExit = cRoom.Exits[oppositeDir]
+ }
+ }
+
+ return BuildGridConflicts(roomID, func(id int) (*Room, bool) {
+ if id == bID {
+ return nil, false
+ }
+ r, ok := load(id)
+ if !ok {
+ return nil, false
+ }
+ if id == roomID {
+ rc := *r
+ rc.Exits = copyExits(r.Exits)
+ if hasFar {
+ rc.Exits[dir] = RewirePreserving(bExit, cID)
+ } else {
+ delete(rc.Exits, dir)
+ }
+ return &rc, true
+ }
+ if hasFar && id == cID {
+ rc := *r
+ rc.Exits = copyExits(r.Exits)
+ rc.Exits[oppositeDir] = RewirePreserving(cOppExit, roomID)
+ return &rc, true
+ }
+ return r, true
+ })
+}