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, OnTraverse: src.OnTraverse, 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 }) }