diff options
Diffstat (limited to 'internal/world/insert_remove.go')
| -rw-r--r-- | internal/world/insert_remove.go | 152 |
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 + }) +} |
