WorldModel contents
The Core Tree.
Whereas I6 traditionally has a simple object tree hierarchy for containment, support, carrying and so on, the I7 template also uses the component_* properties to provide a second tree which defines the "part of" relation. These two trees interact in a subtle way, and it took a very long time to work out the simplest way to express this.
The core of an object is the root of its subtree in the component relation tree. So for a television set with a control panel which has a button on, the core for the button (and also for the panel and the set) will be the set. When X is a part of Y, it must be spatially in the same position as Y, and so the spatial location of X is determined by the position in the object tree of its core. (In effect, the spatial situation can be found by contracting together all nodes corresponding to objects which are parts of each other, but it would waste memory to construct such a tree: CoreOfParentOfCoreOf simulates what the parent operation would be in such a tree if it existed, wasting a little time instead.)
The holder of an object is its component parent, if it is part of something, or its object-tree parent if it has one. It is illegal for both of these to be non-nothing, so this is unambiguous.
It is just possible for HolderOf to be called before the player has been placed into the model world, in cases where Inform is checking a past tense condition in the opening pre-turn. We don't want to return nothing then because that would make it true that
HolderOf(player) == ContainerOf(player)
and similar conditions – thus, it would appear that in the immediate past the player had been on the holder of the player, which is not in fact the case. So we return thedark as a typesafe but impossible value here.
41[ HolderOf o;
42 if (InitialSituation-->DONE_INIS == false) return thedark;
43 if (o && (o.component_parent)) return o.component_parent;
44 if (o && (parent(o))) return parent(o);
45 return nothing;
46];
47
48[ ParentOf o;
49 if (o) o = parent(o);
50 return o;
51];
52
53[ CoreOf o;
54 while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent;
55 return o;
56];
57
58[ CoreOfParentOfCoreOf o;
59 while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent;
60 if (o) o = parent(o);
61 while (o && (o provides component_parent) && (o.component_parent)) o = o.component_parent;
62 return o;
63];
Climbing the Core Tree.
LocationOf returns the room in which an object can be found, or nothing if it is out of play. Directions and regions are always out of play. Doors and backdrops can be in multiple places: if they are in the current location, then that's the answer; otherwise a two-sided door is in its "front side", and a backdrop in the earliest declared room which it's currently in. For a room, LocationOf is necessarily itself.
CommonAncestor finds the nearest object indirectly containing o1 and o2, or returns nothing if there is no common ancestor. (This is a port of CommonAncestor from the I6 library, adapted to work on the core tree.)
78[ LocationOf o;
79 if (~~(o ofclass K1_room or K2_thing)) return nothing;
80 if (o ofclass K4_door) {
81 if (parent(o) == real_location) return real_location;
82 return FrontSideOfDoor(o);
83 }
84 if (o ofclass K7_backdrop) {
85
86 return BackdropLocation(o);
87 }
88 while (o) {
89 if (o ofclass K1_room) return o;
90 o = CoreOfParentOfCoreOf(o);
91 }
92 return nothing;
93];
94
95[ CommonAncestor o1 o2 i j;
96 o1 = CoreOf(o1);
97 o2 = CoreOf(o2);
98
99 for (i=o1: i: i = CoreOfParentOfCoreOf(i))
100 for (j=o2: j: j = CoreOfParentOfCoreOf(j))
101 if (j == i) return j;
102
103 return nothing;
104];
105
106[ IndirectlyContains o1 o2;
107 if ((o1 == nothing) || (o2 == nothing)) rfalse;
108 if ((o1 ofclass K1_room) && (o2 ofclass K4_door)) {
109 if (o1 == FrontSideOfDoor(o2)) rtrue;
110 if (o1 == BackSideOfDoor(o2)) rtrue;
111 rfalse;
112 }
113 if (o2 ofclass K7_backdrop) rfalse;
114 for (o2 = HolderOf(o2) : o2 && o2 ~= thedark : o2 = HolderOf(o2)) if (o2 == o1) rtrue;
115 rfalse;
116];
To Decide Whether In.
A curiosity, this: the I6 definition of "To decide whether in (obj - object)". As can be seen, there are three possible interpretations of "in", depending on the kind of object, so this could all be done with three different definitions in the Standard Rules, so that run-time type checking would decide which to apply: but that would produce a little overhead and make the code for this rather common operation a bit complicated. Besides, this way we can produce a respectable run-time problem message when the phrase is misapplied.
Note that "in X" is not equivalent to "the player is in X": the latter uses direct containment, whereas we use indirect containment. Thus "in the Hall" and "in the laundry-basket" are both true if the player is in a laundry-basket in the Hall.
133[ WhetherIn obj;
134 if (obj has enterable) {
135 if (IndirectlyContains(obj, player)) rtrue;
136 rfalse;
137 }
138 if (obj ofclass K9_region) return TestRegionalContainment(real_location, obj);
139 if (obj ofclass K1_room) {
140 if (obj == real_location) rtrue;
141 rfalse;
142 }
143 RunTimeProblem(RTP_NOTINAROOM, obj);
144 rfalse;
145];
Containment Relation.
This is the single most important relation in I7: direct containment. It is complicated by the fact that "A is in B" is represented differently at run-time when A is a room and B is a region, and when it isn't.
For each A there is at most one B such that "A is in B" is true. Because of this we test the relation with a function of one variable which turns A into B, rather than a function of two variables returning true or false. ContainerOf is that function.
I7 frequently needs to compile loops over all A such that "A is in B". In simpler relations (such as the support relation below) it can do this efficiently by iterating through the object-tree children of B, but for containment we have to provide an iterator function TestContainmentRange, as otherwise we would get the wrong result when B is a region.
164[ ContainerOf A p;
165 if (A ofclass K1_room) return A.map_region;
166 p = parent(A);
167 if (p == nothing) return nothing;
168 if (p ofclass K5_container) return p;
169 if (p ofclass K1_room) return p;
170 if (p ofclass K9_region) return p;
171 return nothing;
172];
173
174[ TestContainmentRange obj e f;
175 if (obj ofclass K9_region) {
176 objectloop (f ofclass K1_room && f.map_region == obj)
177 if (f > e) return f;
178 return nothing;
179 }
180 if (obj ofclass K5_container or K1_room) {
181 if (e == nothing) return child(obj);
182 return sibling(e);
183 }
184 return nothing;
185];
Support Relation.
This then is simpler, with no need for an iterator governing searches.
191[ SupporterOf obj p;
192 p = parent(obj);
193 if (p == nothing) return nothing;
194 if (p ofclass K6_supporter) return p;
195 return nothing;
196];
Carrying Relation.
Only people may carry, and something worn is not in this sense carried.
202[ CarrierOf obj p;
203 p = parent(obj);
204 if (p && (p ofclass K8_person) && (obj hasnt worn)) return p;
205 return nothing;
206];
Wearing Relation.
Only people may wear.
212[ WearerOf obj p;
213 p = parent(obj);
214 if (p && (p ofclass K8_person) && (obj has worn)) return p;
215 return nothing;
216];
Having Relation.
A person has something if and only if he either wears or carries it.
222[ OwnerOf obj p;
223 p = parent(obj);
224 if (p && (p ofclass K8_person)) return p;
225 return nothing;
226];
Making Parts.
Note that MakePart removes the part-to-be from the object tree before attaching it: it cannot, of course, be the core of the resulting object.
233[ MakePart P Of First;
234 if (P == player) return RunTimeProblem(RTP_CANTMAKEPART, Of);
235 if (parent(P)) remove P; give P ~worn;
236 if (Of == nothing) { DetachPart(P); return; }
237 if (P.component_parent) DetachPart(P);
238 P.component_parent = Of;
239 First = Of.component_child;
240 Of.component_child = P; P.component_sibling = First;
241];
242
243[ DetachPart P From Daddy O;
244 Daddy = P.component_parent; P.component_parent = nothing;
245 if (Daddy == nothing) { P.component_sibling = nothing; return; }
246 if (Daddy.component_child == P) {
247 Daddy.component_child = P.component_sibling;
248 P.component_sibling = nothing; return;
249 }
250 for (O = Daddy.component_child: O: O = O.component_sibling)
251 if (O.component_sibling == P) {
252 O.component_sibling = P.component_sibling;
253 P.component_sibling = nothing; return;
254 }
255];
Movements.
Note that an object is detached from its component parent, if it has one, when moved.
262[ MoveObject F T opt going_mode was L;
263 if (F == nothing) return RunTimeProblem(RTP_CANTMOVENOTHING);
264 if (F ofclass K7_backdrop) {
265 if (T ofclass K9_region) {
266 give F ~absent; F.found_in = T.regional_found_in;
267 if (TestRegionalContainment(LocationOf(player), T)) move F to LocationOf(player);
268 else remove F;
269 return; }
270 if (T == FoundEverywhere) {
271 give F ~absent; F.found_in = FoundEverywhere;
272 return;
273 }
274 return RunTimeProblem(RTP_BACKDROP, F, T);
275 }
276 if (T ofclass K9_region) return RunTimeProblem(RTP_NOTBACKDROP, F, T);
277 if (T == FoundEverywhere) return RunTimeProblem(RTP_BACKDROPONLY, F);
278 if (~~(F ofclass K2_thing)) return RunTimeProblem(RTP_NOTTHING, F, T);
279 if (F has worn) {
280 give F ~worn;
281 if (F in T) return;
282 }
283 DetachPart(F);
284 if (going_mode == false) {
285 if (F == player) { PlayerTo(T, opt); return; }
286 if (IndirectlyContains(F, player)) {
287 L = LocationOf(T);
288 if (L == nothing) return RunTimeProblem(RTP_CANTBEOFFSTAGE);
289 if (LocationOf(player) ~= L) {
290 was = parent(player);
291 move player to real_location;
292 move F to T;
293 PlayerTo(was, true);
294 return;
295 }
296 }
297 }
298 move F to T;
299];
300
301[ RemoveFromPlay F;
302 if (F == nothing) return RunTimeProblem(RTP_CANTREMOVENOTHING);
303 if (F == player) return RunTimeProblem(RTP_CANTREMOVEPLAYER);
304 if (F ofclass K4_door) return RunTimeProblem(RTP_CANTREMOVEDOORS);
305 if (IndirectlyContains(F, player)) return RunTimeProblem(RTP_CANTBEOFFSTAGE);
306 give F ~worn; DetachPart(F);
307 if (F ofclass K7_backdrop) give F absent;
308 remove F;
309];
On Stage.
The following implements the "on-stage" and "off-stage" adjectives provided by the Standard Rules. Here, as above, note that the I6 attribute absent marks a floating object (see below) which has been removed from play; in I7 only doors and backdrops are allowed to float, and only backdrops are allowed to be removed from play.
319[ OnStage O set x;
320 if (O ofclass K1_room) rfalse;
321 if (set < 0) {
322 while (metaclass(O) == Object) {
323 if (O ofclass K1_room) rtrue;
324 if (O ofclass K9_region) rfalse;
325 if (O ofclass K4_door) rtrue;
326 if (O ofclass K7_backdrop) { if (O has absent) rfalse; rtrue; }
327 x = O.component_parent; if (x) { O = x; continue; }
328 x = parent(O); if (x) { O = x; continue; }
329 rfalse;
330 }
331 }
332 x = OnStage(O, -1);
333 if ((x) && (set == false)) RemoveFromPlay(O);
334 if ((x == false) && (set)) MoveObject(O, real_location);
335 rfalse;
336];
Moving the Player.
Note that the player object can only be moved by this routine: this allows us to maintain the invariant for real_location and location (for which, see Light.i6t) and to ensure that multiply-present objects can be witnessed where they need to be.
345[ PlayerTo newplace flag L;
346 L = LocationOf(newplace);
347 if (L == nothing) return RunTimeProblem(RTP_CANTBEOFFSTAGE);
348 @push actor; actor = player;
349 move player to newplace;
350 location = L;
351 real_location = location;
352 MoveFloatingObjects();
353 SilentlyConsiderLight();
354 DivideParagraphPoint();
355 if (flag == 0) <Look>;
356 if (flag == 1) give location visited;
357 if (flag == 2) AbbreviatedRoomDescription();
358 @pull actor;
359];
Move During Going.
The following routine preserves the invariant for real_location, but gets location wrong since it doesn't adjust for light. Nor are floating objects moved. It should be used only in the course of other operations which get the rest of this right. (I7 uses it only for the "going" action, where these various operations are each handled by different named rules to increase the flexibility of the system.)
370[ MoveDuringGoing F T;
371 MoveObject(F, T, 0, true);
372 if (actor == player) {
373 location = LocationOf(player);
374 real_location = location;
375 }
376];
Being Everywhere.
The following is used as the found_in property for any backdrop which is "everywhere", that is, which is found in every room.
383[ FoundEverywhere; rtrue; ];
388[ BackdropEverywhere O;
389 if (O ofclass K7_backdrop) {
390 if (O has absent) rfalse;
391 if (O.found_in == FoundEverywhere) rtrue;
392 }
393 rfalse;
394];
Changing the Player.
It is very important that nobody simply change the value of the player variable, because so much else must be updated when the identity of the player changes: the light situation, floating objects, the real_location and so on. Because of this, the NI compiler contains code which compiles an assertion of the proposition "is(player, X)" into a function call ChangePlayer(X) rather than a variable assignment player = X. So we cannot catch out the system by writing "now the player is Mr Henderson".
We must ensure that if player is initially X, is changed to Y, and is then changed back to X, that both X and Y end exactly as they began – hence the flummery below with using remove_proper to ensure that proper is left with the correct value. Note that: (1) at any given time exactly one person has the I6 concealed attribute: the current player; (2) the selfobj is the default initial value of player, and because it has as its actual printed name "yourself", we need to override this when something else takes over as player: we change to "your former self", in fact. No such device is needed for other people being changed from because they are explicitly given printed names – say "Mr Darcy", "the sous-chef", etc. – in the source text.
419[ ChangePlayer obj flag;
420 if (~~(obj ofclass K8_person)) return RunTimeProblem(RTP_CANTCHANGE, obj);
421 if (~~(OnStage(obj, -1))) return RunTimeProblem(RTP_CANTCHANGEOFFSTAGE, obj);
422 if (obj.component_parent) return RunTimeProblem(RTP_CANTMAKEPART, obj);
423 if (obj == player) return;
424
425 give player ~concealed;
426 if (player has remove_proper) give player ~proper;
427 if (player == selfobj) {
428 player.saved_short_name = player.short_name;
429 player.short_name = PRINT_PROTAGONIST_INTERNAL_RM('c');
430 }
431 player = obj;
432 if (player == selfobj) {
433 player.short_name = player.saved_short_name;
434 }
435 if (player hasnt proper) give player remove_proper;
436 give player concealed proper;
437
438 location = LocationOf(player); real_location = location;
439 MoveFloatingObjects();
440 SilentlyConsiderLight();
441];
Floating Objects.
A single object can only be in one position in the object tree, yet backdrops and doors must be present in multiple rooms. This is accomplished by making them, in I6 jargon, "floating objects": objects which move in the tree whenever the player does, so that – from the player's perspective – they are always present when they should be. In I6, almost anything can be made a floating object, but in I7 this is strictly and only used for backdrops and two-sided doors.
There are several conceptual problems with this scheme: chiefly that it assumes that the only witness to the spatial arrangement of objects is the player. In I6 that was usually true, but in I7, where every person can undertake actions, it really isn't true any longer: if the objects float to follow the player, it means that they are not present with other people who might need to interact with them. This is why the accessibility rules are somewhat hacked for backdrops and doors (see Light.i6t). In fact we generally achieve the illusion we want, but this is largely because it is difficult to catch out all the exceptions for backdrops and doors, and because in practice authors tend not to set things up so that the presence or absence of backdrops much affects what non-player characters do.
All the same, the scheme is not logically defensible, and this is why we do not allow the user to create new categories of floating objects in I7.
The I6 implementation of MoveFloatingObjects acted on the location rather than the real_location, which (a) meant that multiply-present objects could include thedark – the generic Darkness place – as one possible location, but (b) assumed that the only sense by which the player could witness an object was sight. In I7, thedark is not a valid room, and we are a bit more careful about the senses.
475[ MoveFloatingObjects toroom i k l m address flag;
476 if (toroom == nothing) toroom = real_location;
477 if (toroom == nothing) return;
478 objectloop (i) {
479 address = i.&found_in;
480 if (address ~= 0 && i hasnt absent) {
481 if (ZRegion(address-->0) == 2) {
482 m = address-->0;
483 .TestPropositionally;
484 if (m.call(toroom) ~= 0) move i to toroom;
485 else { if (i in toroom) remove i; }
486 } else {
487 k = i.#found_in;
488 for (l=0 : l<k/WORDSIZE : l++) {
489 m = address-->l;
490 if (ZRegion(m) == 2) jump TestPropositionally;
491 if (m == toroom || m in toroom) {
492 if (i notin toroom) move i to toroom;
493 flag = true;
494 }
495 }
496 if (flag == false) { if (i in toroom) remove i; }
497 }
498 if ((i ofclass K4_door) && (parent(i) == nothing)) {
499 move i to ((i.door_to).call());
500 }
501 }
502 }
503];
504
505[ MoveBackdrop bd D x address;
506 if (~~(bd ofclass K7_backdrop)) return RunTimeProblem(RTP_BACKDROPONLY, bd);
507 if (bd.#found_in > WORDSIZE) {
508 address = bd.&found_in;
509 address-->0 = D;
510 } else bd.found_in = D;
511 give bd ~absent;
512 MoveFloatingObjects();
513];
Backdrop Location.
This determines what the "location" of a backdrop is, or, if "target" is other than nothing, determines whether it can ever be the location. Note that this routine also works for two-sided doors, despite its name.
521[ BackdropLocation O target address m x i k l r sl;
522 if (O has absent) return nothing;
523 if ((target == nothing or real_location) && (parent(O) == real_location))
524 return real_location;
525 address = O.&found_in;
526 if (address ~= 0) {
527 k = O.#found_in;
528 for (l=0 : l<k/WORDSIZE : l++) {
529 m = address-->l;
530 if (ZRegion(m) == 2) {
531 sl = location;
532 if (target) {
533 location = target;
534 r = m.call();
535 if (r ~= 0) { location = sl; return target; }
536 } else {
537 objectloop (x ofclass K1_room) {
538 location = x;
539 r = m.call();
540 if (r ~= 0) { location = sl; return x; }
541 }
542 }
543 location = sl;
544 } else {
545 if (m ofclass K9_region) {
546 objectloop (x ofclass K1_room)
547 if (TestRegionalContainment(x, m))
548 if (target == nothing or x)
549 return x;
550 } else {
551 if (target == nothing or m) return m;
552 }
553 }
554 }
555 }
556 return nothing;
557];
Wearing Clothes.
An object X is worn by a person P if and only if (i) the object tree parent of X is P, and (ii) X has the worn attribute. In fact for I7 purposes we are careful to ensure that (ii) happens only when (i) does in any case: if X is moved in the object tree, or made a part of something, or removed from play, then worn is removed.
567[ WearObject X P opt;
568 if (X == false) rfalse;
569 if (X notin P) MoveObject(X, P, opt);
570 give X worn;
571];
Map Connections.
MapConnection returns the room which is in the given direction (as an object) from the given room: it is used, among other things, to test the "mapped east of" and similar relations. (The same relations are asserted true with AssertMapConnection and false with AssertMapUnconnection.)
RoomOrDoorFrom returns either the room or door which is in the given direction, and is thus simpler (since it doesn't have to investigate what is through such a door).
Both routines return values which are type-safe in I7 provided the kind of value they are assigned to is "object": neither returns any specific kind of object without fail. (MapConnection is always either a door or nothing, but nothing is not a typesafe value for "door".)
Note that map connections via doors are immutable.
591[ MapConnection from_room dir
592 in_direction through_door;
593 if ((from_room ofclass K1_room) && (dir ofclass K3_direction)) {
594 in_direction = Map_Storage-->
595 ((from_room.IK1_Count)*No_Directions + dir.IK3_Count);
596 if (in_direction ofclass K1_room) return in_direction;
597 if (in_direction ofclass K4_door) {
598 @push location;
599 location = from_room;
600 through_door = in_direction.door_to();
601 @pull location;
602 if (through_door ofclass K1_room) return through_door;
603 }
604 }
605 return nothing;
606];
607
608[ DoorFrom obj dir rv;
609 rv = RoomOrDoorFrom(obj, dir);
610 if (rv ofclass K4_door) return rv;
611 return nothing;
612];
613
614[ RoomOrDoorFrom obj dir use_doors in_direction sl through_door;
615 if ((obj ofclass K1_room) && (dir ofclass K3_direction)) {
616 in_direction = Map_Storage-->
617 ((obj.IK1_Count)*No_Directions + dir.IK3_Count);
618 if (in_direction ofclass K1_room or K4_door) return in_direction;
619 }
620 return nothing;
621];
622
623[ AssertMapConnection r1 dir r2 in_direction;
624 SignalMapChange();
625 in_direction = Map_Storage-->
626 ((r1.IK1_Count)*No_Directions + dir.IK3_Count);
627 if ((in_direction == 0) || (in_direction ofclass K1_room)) {
628 Map_Storage-->((r1.IK1_Count)*No_Directions + dir.IK3_Count) = r2;
629 return;
630 }
631 if (in_direction ofclass K4_door) {
632 RunTimeProblem(RTP_EXITDOOR, r1, dir);
633 return;
634 }
635 RunTimeProblem(RTP_NOEXIT, r1, dir);
636];
637
638[ AssertMapUnconnection r1 dir r2 in_direction;
639 SignalMapChange();
640 in_direction = Map_Storage-->
641 ((r1.IK1_Count)*No_Directions + dir.IK3_Count);
642 if (r1 ofclass K4_door) {
643 RunTimeProblem(RTP_EXITDOOR, r1, dir);
644 return;
645 }
646 if (in_direction == r2)
647 Map_Storage-->((r1.IK1_Count)*No_Directions + dir.IK3_Count) = 0;
648 return;
649];
Adjacency Relation.
A relation between two rooms which, note, does not see connections through doors.
656[ TestAdjacency R1 R2 i row;
657 if (R1 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R1);
658 else if (R2 ofclass K9_region) RunTimeProblem(RTP_REGIONSNOTADJACENT, R2);
659 row = (R1.IK1_Count)*No_Directions;
660 for (i=0: i<No_Directions: i++, row++)
661 if (Map_Storage-->row == R2) rtrue;
662 rfalse;
663];
Regional Containment Relation.
This tests whether an object with a definite physical location is in a given region. (For a two-sided door which straddles regions, the front side is what counts; for a backdrop, a direction or another region, the answer is always no.) We rely on the fact that every room object has a map_region property which is the smallest region containing it, if any does, and nothing otherwise; region objects are then in the object tree such that parenthood corresponds to spatial containment. (Note that in I7, a region must either completely contain another region, or else have no overlap with it.)
676[ TestRegionalContainment obj region o;
677 if ((obj == nothing) || (region == nothing)) rfalse;
678 if (obj ofclass K7_backdrop or K4_door) {
679 if (obj has absent) rfalse;
680 objectloop (o ofclass K1_room)
681 if (TestRegionalContainment(o, region))
682 if (BackdropLocation(obj, o))
683 rtrue;
684 rfalse;
685 }
686 if (~~(obj ofclass K1_room)) obj = LocationOf(obj);
687 if (obj == nothing) rfalse;
688 o = obj.map_region;
689 while (o) {
690 if (o == region) rtrue;
691 o = parent(o);
692 }
693 rfalse;
694];
Doors.
There are two sorts of door: one-sided and two-sided.
A two-sided door is in two rooms at once: one is called the front side, the other the back side. The front side is the one declared first in the source. Note that a one-sided door is also an I6 object of class K4_door, and then the front side is the room holding it, while the back side is nothing.
The door_to property calculates the room which the door leads to; that of course depends on which side of it the player is standing. Similarly, door_dir calculates the direction it leads in. Unlike the I6 setup, where door_dir returned the direction property (n_to, s_to, etc.), here in I7's template it returns the direction object (n_obj, s_obj, etc.)
711[ FrontSideOfDoor D; if (~~(D ofclass K4_door)) rfalse;
712 if (D provides found_in) return (D.&found_in)-->0;
713 return parent(D);
714];
715
716[ BackSideOfDoor D; if (~~(D ofclass K4_door)) rfalse;
717 if (D provides found_in) return (D.&found_in)-->1;
718 return nothing;
719];
720
721[ OtherSideOfDoor D from_room rv;
722 if (D ofclass K4_door) {
723 @push location;
724 location = LocationOf(from_room);
725 rv = D.door_to();
726 @pull location;
727 }
728 return rv;
729];
730
731[ DirectionDoorLeadsIn D from_room rv dir;
732 if (D ofclass K4_door) {
733 @push location;
734 location = LocationOf(from_room);
735 rv = D.door_dir();
736 @pull location;
737 }
738 return rv;
739];
Visibility Relation.
We use TestScope to decide whether there is a line of sight from A to B; it's a relation which cannot be asserted true or false.
746[ TestVisibility A B;
747 if (~~OffersLight(parent(CoreOf(A)))) rfalse;
748 if (suppress_scope_loops) rtrue;
749 return TestScope(B, A);
750];
Touchability Relation.
We use ObjectIsUntouchable to decide whether there is physical access from A to B; it's a relation which cannot be asserted true or false.
757[ TestTouchability A B rv;
758 if (A ofclass K4_door or K7_backdrop) MoveFloatingObjects(LocationOf(B));
759 if (B ofclass K4_door or K7_backdrop) MoveFloatingObjects(LocationOf(A));
760 if (TestScope(B,A) == false) rv = true;
761 else rv = ObjectIsUntouchable(B, true, A);
762 if (A ofclass K4_door or K7_backdrop) MoveFloatingObjects();
763 if (rv) rfalse;
764 rtrue;
765];
Concealment Relation.
An activity determines whether one object conceals another; it's a relation which cannot be asserted true or false.
772[ TestConcealment A B;
773 if (A ofclass K2_thing && B ofclass K2_thing) {
774 particular_possession = B;
775 if (CarryOutActivity(DECIDING_CONCEALED_POSSESS_ACT, A)) rtrue;
776 }
777 rfalse;
778];