1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
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
})
}
|