-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathextras.t
1351 lines (1096 loc) · 42.8 KB
/
extras.t
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* extras.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
*
*
*
*
* This file contains a number of (in principle) optional classes that can be
* used in addition to Thing. A game does not need to use any of these classes
* since authors can always just set the appropriate properties on thing, but
* some authors may find them a convenience, or may find that using them
* improves the clarity of their code.
*/
property tooFarAwayToHearMsg;
property tooFarAwayToSmellMsg;
property smellSize;
property soundSize;
/*
* We define SensoryEmanation to be the base class for Odor and Noise. It
* doesn't add any functionality here, but makes it possible for the sensory.t
* extension to add functionality to this class.
*/
class SensoryEmanation: Decoration
/*
* There's no point in including a SensoryEmanation in any command applying to ALL unless the
* command is one of our decoration actions.
*/
hideFromAll(action) { return decorationActions.indexOf(action) == nil; }
;
/*
* An Odor is an object representing a smell (as opposed to the object that
* might be emitting that smell). The desc property of an Odor is displayed in
* response to EXAMINE or SMELL; any other action is responded to with the
* notImportantMsg.
*/
class Odor: SensoryEmanation
/* An Odor responds to EXAMINE SOMETHING or SMELL SOMETHING */
decorationActions = [Examine, SmellSomething]
/*
* The message to display when any other action is attempted with an Odor
*/
notImportantMsg = BMsg(only smell, '{I} {can\'t} do that to a smell. ')
/* Treat Smelling an Odor as equivalent to Examining it. */
dobjFor(SmellSomething) asDobjFor(Examine)
dobjFor(Examine) { preCond = [objSmellable] }
/*
* Since we turn SMELL into EXAMINE we want our sightSize to be our
* smellSize.
*/
sightSize = smellSize
/*
* For the same reason we want to use our tooFarAwayToSmellMsg for our
* tooFarWayToSeeDetailMsg.
*/
tooFarAwayToSeeDetailMsg = tooFarAwayToSmellMsg
isOdor = true
;
/*
* A Noise is an object representing a sound (as opposed to the object that
* might be emitting that sound). The desc property of a Noise is displayed in
* response to EXAMINE or LISTEN TO; any other action is responded to with the
* notImportantMsg.
*/
class Noise: SensoryEmanation
/* A Noise responds to EXAMINE SOMETHING or LISTEN TO SOMETHING */
decorationActions = [Examine, ListenTo]
/*
* The message to display when any other action is attempted with a Noise
*/
notImportantMsg = BMsg(only listen, '{I} {can\'t} do that to a sound. ')
/* Treat Listening to a Noise as equivalent to Examining it. */
dobjFor(ListenTo) asDobjFor(Examine)
dobjFor(Examine) { preCond = [objAudible] }
/*
* Since we turn LISTEN TO into EXAMINE we want our sightSize to be our
* soundSize.
*/
sightSize = soundSize
/*
* For the same reason we want to use our tooFarAwayToHearlMsg for our
* tooFarWayToSeeDetailMsg.
*/
tooFarAwayToSeeDetailMsg = tooFarAwayToHearMsg
isNoise = true
;
/* A Container is a Thing that other things can be put inside */
class Container: Thing
/* Containers are open by default */
isOpen = true
/*
* The containment type of a container is In; i.e. things located in a
* Container are considered to be in it.
*/
contType = In
;
/* An OpenableContainer is a Container that can be opened and closed. */
class OpenableContainer: Container
/* Most OpenableContainers start out closed. */
isOpen = nil
/* An OpenableContainer is openable */
isOpenable = true
;
/*
* A LockableContainer is an OpenableContainer that can be locked and unlocked
* without the aid of a key.
*/
class LockableContainer: OpenableContainer
lockability = lockableWithoutKey
/* We usually want a LockableContainer to start out locked. */
isLocked = true
;
/*
* A KeyedContainer is an OpenableContainer that can be locked and unlocked
* only with the aid of a key.
*/
class KeyedContainer: OpenableContainer
lockability = lockableWithKey
/* We usually want a KeyedContainer to start out locked. */
isLocked = true
;
/* A Surface is a Thing that other things can be placed on top of */
class Surface: Thing
/* The contents of a Surface are considered to be on the Surface */
contType = On
/*
* Searching a Surface may involve look for what's on it as well as what's
* in it.
*
* We put this code here rather than checking for contType == On on a
* Thing to avoid burdening Thing with overcomplicated code for a rather
* special case.
*/
dobjFor(Search)
{
preCond = [objVisible, touchObj]
verify() {}
check() {}
action()
{
if(hiddenIn.length == 0
&& contents.countWhich({x: x.searchListed}) == 0)
{
say(nothingOnMsg);
return;
}
local onList = contents.subset({x: x.searchListed});
if(onList.length > 0)
listContentsOn(onList);
if(hiddenIn.length > 0)
findHidden(&hiddenIn, In);
}
}
nothingOnMsg = BMsg(nothing on, '{I} {find} nothing of interest on
{the dobj}. ')
/*
* List what's on me. We put this in a separate method to make it easier
* to customise on a per object basis.
*/
listContentsOn(lst)
{
lookInLister.show(lst, self, true);
}
;
/*
* A Plaftorm is a Surface that the player character (and other actors) can
* get on.
*/
class Platform: Surface
isBoardable = true
;
/*
* A Booth is a Container that the player character (and other actors) can get
* in.
*/
class Booth: Container
isEnterable= true
;
/* An Underside is a Thing that other things can be put under */
class Underside: Thing
/* The contents of an Underside are considered to be under it. */
contType = Under
;
/* An RearContainer is a Thing that other things can be put behind */
class RearContainer: Thing
/* The contents of an RearContainer are considered to be behind it. */
contType = Behind
;
/* A Wearable is a Thing that can be worn */
class Wearable: Thing
isWearable = true
;
/* A Food is a Thing that can be eaten */
class Food: Thing
isEdible = true
;
/*
* A Fixture is a Thing that can't be picked up and carried around (and so
* can't be put anywhere either).
*/
class Fixture: Thing
isFixed = true
;
/*
* A Decoration is a Fixture that can only be EXAMINEd. Any other action
* results in the display of its notImportantMsg. It's normally used for
* objects that are purely scenery.
*/
class Decoration: Fixture
isDecoration = true
/*
* Game code may wish to hide decorations from all commands applied to ALL. Tbis can be
* achieved by overriding hideFromAll() as shown below. This is not done in the library since
* making this change at version 1.61 would compromise backward compatibility.
*/
// hideFromAll(action) { return true; }
;
/*
* A Component is an object that's (usually permanently) part of something
* else, like the handle of a suitcase or a dial on a safe.
*/
class Component: Fixture
cannotTakeMsg = BMsg(cannot take component, '{I} {can\'t} have {that cobj},
{he dobj}{\'s} part of {1}. ', location.theName)
locType = PartOf
;
/*
* A Distant is a Decoration that's considered too far away to be interacted
* with by any command other than EXAMINE
*/
class Distant: ProxyDest, Decoration
/* Message to say that this object is too far away. */
notImportantMsg = BMsg(distant, '{The subj cobj} {is} too far away. ')
/*
* The base Decoration class includes GoTo as a decorationAction. For a
* Distant object (e.g. the sun or moon or a distanct mountain) this will
* often be inappropriate. Where an object is 'locally distant' as it
* were, i.e. a sign mounted high up on a wall, you can override the
* destination property to give the location that a GO TO action should
* take the player character to; decorationActions will then include GoTo,
* since GO TO SIGN would then be a reasonable way for the player to get
* to the location of the sign.
*/
decorationActions = destination ? [Examine, GoTo] : [Examine]
/*
* If this Distant represents an object that's notionally in another
* location, setting destination to that location will allow the GO TO
* command to take the actor to that location. If destination is specified
* it should normally be a room (usually one adjacent to that this Distant
* object is located in. For an object like the sky, the sun or a distant
* mountain this should be left as nil.
*/
destination = nil
/* A Distant is too far away for any action that requires touching. */
checkReach(actor)
{
say(notImportantMsg);
}
/*
* Going To a Distant object is unlike a normal GoTo, since if the object
* is meant to be Distant, going to its location (the normal response to a
* GO TO command) is precisely what we don't want to happen. We therefore
* need to provide custom GoTo handling on a Distant object.
*/
dobjFor(GoTo)
{
verify()
{
/*
* Unless the actor is in our location and we have a destination,
* we don't want to field a GoTo command, so we make it as
* illogical as possible. If we don't have a destination we
* shouldn't reach this point anyway since GoTo shouldn't be one
* of our decorationActions, but writing the test this way will
* make this verify routine prevent the action even if
* decorationActions is overridden, if there's a nil destination.
*/
if(!gActor.isIn(getOutermostRoom) || destination == nil)
inaccessible(notImportantMsg);
}
action()
{
/*
* Calculate the route from the actor's current room to the
* location where the target object was last seen, using the
* routeFinder to carry out the calculations if it is present. In
* this case we use routeFinder rather than pcRouteFinder since
* the PC can presumably see the way s/he needs to go even if s/he
* hasn't gone that way before.
*/
local route = defined(routeFinder)? routeFinder.findPath(
gActor.getOutermostRoom, destination.getOutermostRoom) : nil;
/*
* If we don't find a route, just display a message saying we
* don't know how to get to our destination.
*/
if(route == nil)
sayDontKnowHowToGetThere();
/*
* If the route we find has only one element in its list, that
* means that we're where we last saw the target but it's no
* longer there, so we don't know where it's gone. In which case
* we display a message saying we don't know how to reach our
* target.
*/
else if(route.length == 1)
sayDontKnowHowToReach();
/*
* If the route we found has at least two elements, then use the
* first element of the second element as the direction in which
* we need to travel, and use the Continue action to take a step
* in that direction.
*/
else
{
local dir = route[2][1];
Continue.takeStep(dir, destination.getOutermostRoom);
};
}
}
;
/* An Unthing is an object that represents the absence of a thing */
class Unthing: Decoration
/* An Unthing can't respond to any actions, as it isn't there */
decorationActions = []
/*
* The message to display when the player character tries to interact with
* this object; by default we just say it isn't there, but game code will
* normally want to override this message to explain the reason for the
* absence.
*/
notImportantMsg = BMsg(unthing absent, '{The subj cobj} {isn\'t} {here}. ')
/*
* Users coming from adv3 may be used to Unthings having a notHereMsg, so
* we'll define this to be the same as the notImportantMsg
*/
notHereMsg = notImportantMsg
/* An Unthing should never be included in ALL */
hideFromAll(action) { return true; }
/*
* A player is more likely to be trying to refer to something that is
* present than something that isn't, so we give Unthings a substantially
* reduced vocabLikelihood
*/
vocabLikelihood = -100
/*
* If there's anything else in the match list, remove myself from the
* matches
*/
filterResolveList(np, cmd, mode)
{
if(np.matches.length > 1)
np.matches = np.matches.subset({m: m.obj != self});
}
/* Make Unthings verify with the lowest possible score */
dobjFor(Default)
{
verify()
{
inaccessible(notHereMsg);
}
}
iobjFor(Default)
{
verify()
{
inaccessible(notHereMsg);
}
}
/*
* Giving an order to an Unthing should give the same response as any
* other command involving an Unthing.
*/
handleCommand(action)
{
say(notHereMsg);
}
/*
* If this property us defined, then it should hold the object we're standing in for that
* currently isn't present.
*/
unObject = nil
preinitThing()
{
/*
* If we have an unObject defined but no vocab defined, set our vocab to be the vocsb of
* out unObject and then initialize our vocab and name from it.
*/
if(unObject && vocab == nil)
{
vocab = unObject.vocab;
initVocab();
/* Make the Unthing and the unObject facets of each other. */
getFacets = [unObject];
unObject.getFacets = valToList(unObject.getFacets) + self;
}
/* Carry out the inherited handling. */
inherited();
}
/*
* Since we're not here, we can't be reached. This won't matter in most game code, but
* defining this method ensures we get an appropriate response to canReach(obj).
*/
checkReach(obj)
{
say(notHereMsg);
}
;
/*
* A CollectiveGroup is a an object that can represent a set of other objects
* for particular actions. For any of the objects in the myObjects list the
* CollectiveGroup will handle any of the actions in the myActions list; all
* the other actions will be handled by the individual objects.
*/
class CollectiveGroup: Fixture
/*
* The list of actions this CollectiveGroup will handle; all the rest will
* be handled by the individual objects.
*/
collectiveActions = [Examine]
/*
* Is action to be treated as a collective action by this group (i.e.
* handled by this CollectiveGroup object); by default it is if it's one
* of the actions listed in our collectiveActions property.
*/
isCollectiveAction(action)
{
return collectiveActions.indexWhich({a: action.ofKind(a)}) != nil;
}
/*
* If the current action is one of the collective Actions, then filter all
* myObjects from the resolve list; otherwise filter myself out from the
* resolve list.
*/
filterResolveList(np, cmd, mode)
{
/* If there are fewer than two matches, don't do any filtering */
if(np.matches.length < 2)
return;
if(isCollectiveAction(cmd.action))
np.matches = np.matches.subset(
{m: valToList(m.obj.collectiveGroups).indexOf(self) == nil});
else
np.matches = np.matches.subset({m: m.obj != self });
}
/* Obtain the list of the objects belonging to me that are in scope. */
myScopeObjects()
{
return Q.scopeList(gActor).toList.subset(
{o: valToList(o.collectiveGroups).indexOf(self) != nil});
}
/*
* The default descriptions of a CollectiveGroup: By default we just list
* those of our members that are in scope.
*/
desc()
{
/*
* Get a list of all the objects in scope that belong to this
* Collective Group.
*/
local lst = myScopeObjects();
/*
* Obtain the sublist of these that are currently held by the player
* character
*/
local heldLst = lst.subset({o: o.isIn(gPlayerChar)});
/*
* Subtract the list of held items from the list of in-scope items to
* obtain a list of other items in scope.
*/
lst -= heldLst;
/* If none of our items are in scope, say so */
if(nilToList(lst).length == 0 && nilToList(heldLst).length == 0)
DMsg(collective group empty, 'There{\'s} no {1} {here}. ', name);
/*
* Otherwise display lists of which of my members the player character
* is carrying and which others are also present.
*/
else
{
if(heldLst.length > 0)
DMsg(carrying collective group, '{I} {am} carrying {1}. ',
makeListStr(heldLst));
if(lst.length > 0)
DMsg(collective group members, 'There{\'s} {1} {here}. ',
makeListStr(lst));
}
}
/* A CollectiveGroup isn't normally listed as an item in its own right. */
isListed = nil
;
/*
* A Heavy is a Fixture that's too heavy to be picked up. We make Heavy a
* Fixture rather than an Immovable since it should normally be obvious that
* something is too heavy to be moved.
*/
class Heavy: Fixture
cannotTakeMsg = BMsg(too heavy, '{The subj cobj} {is} too heavy to move. ')
;
/*
* An Immovable is something that can't be picked up, although it may not be
* immediately obvious that it can't be moved. For that reason we rule out
* taking an Immovable at the check stage rather than the verify stage (this
* is what distinquished an Immovable from a Fixture).
*/
class Immovable: Thing
/* Respond to an attempt to TAKE by ruling it out at the check stage. */
dobjFor(Take)
{
check() { say(cannotTakeMsg); }
}
/* The message to display to explain why this object can't be taken. */
cannotTakeMsg = BMsg(cannot take immovable, '{I} {cannot} take {the cobj}.
')
/*
* Although an Immovable can't be picked up it's possible that it could be moved around, e.g.,
* by pushing or pulling it, and this must be the case if it can PushTravel and/or PullTravel.
*/
isMoveable = (canPushTravel || canPullTravel)
;
/*
* A StairwayUp is Thing the player character can climb up. It might represent
* an upward staircase, but it could also represent a tree, mast or hillside,
* for example. A StairwayUp is also a TravelConnector so it can be defined on
* the appropriate direction property of its enclosing room.
*/
class StairwayUp: TravelConnector, Thing
/* Climbing a StairwayUp is equivalent to travelling via it. */
dobjFor(Climb)
{
action() { travelVia(gActor); }
}
/* Climbing up a Stairway up is the same as Climbing it. */
dobjFor(ClimbUp) asDobjFor(Climb)
/* A StairwayUp is usually something fixed in place. */
isFixed = true
/* A StairwayUp is climbable */
isClimbable = true
/*
* The appropriate PushTravelAction for pushing something something up a
* StairwayUp.
*/
PushTravelVia = PushTravelClimbUp
/*
* Display message announcing that traveler (typically an NPC whose
* departure is witnessed by the player character) has left via this
* staircase.
*/
sayDeparting(traveler)
{
gMessageParams(traveler);
DMsg(say departing up stairs, '{The subj traveler} {goes} up
{1}. ', theName);
}
/*
* Display message announcing that follower is following leader up
* this staircase.
*/
sayActorFollowing(follower, leader)
{
/* Create message parameter substitutions for the follower and leader */
gMessageParams(follower, leader);
DMsg(say following up staircase, '{The subj follower} follow{s/ed} {the
leader} up {1}. ', theName);
}
traversalMsg = BMsg(traverse stairway up, 'up {1}', theName)
;
/*
* A StairwayDown is Thing the player character can climb down. It might
* represent an downward staircase, but it could also represent a tree, mast
* or hillside, for example. A StairwayDown is also a TravelConnector so it
* can be defined on the appropriate direction property of its enclosing room.
*/
class StairwayDown: TravelConnector, Thing
/* Climbing down a StairwayDown is equivalent to travelling via it. */
dobjFor(ClimbDown)
{
action { travelVia(gActor); }
}
/* A StairwayDown is usually something fixed in place. */
isFixed = true
/* A StairwayDown is something one can climb down */
canClimbDownMe = true
/*
* The appropriate PushTravelAction for pushing something something down a
* StairwayDown.
*/
PushTravelVia = PushTravelClimbDown
/*
* Display message announcing that traveler (typically an NPC whose
* departure is witnessed by the player character)has left via this
* staircase.
*/
sayDeparting(traveler)
{
gMessageParams(traveler);
DMsg(say departing down stairs, '{The subj traveler} {goes} down
{1}. ', theName);
}
/*
* Display message announcing that follower is following leader down
* this staircase.
*/
sayActorFollowing(follower, leader)
{
/* Create message parameter substitutions for the follower and leader */
gMessageParams(follower, leader);
DMsg(say following down staircase, '{The subj follower} follow{s/ed}
{the leader} down {1}. ', theName);
}
traversalMsg = BMsg(traverse stairway down, 'down {1}', theName)
cannotClimbMsg = BMsg(cannot climb stairway down, '{I} {can\'t} climb {the
dobj}, but {i} could go down {him dobj}. ')
;
/* A double sided (aks two-way) Stairway */
class DSStairway: DSCon, StairwayUp
/*
* The room at the upper end of this staircase. The library will try to determine which end of
* this stairway is the upper and end which the lower according to whether it's room1 or room2
* that points to us on its up or down property, so game code need not specify the upper or
* lower end unless neither room at the ends of this stairway point to us via up or down,
* either directly or via an asExit() macro. If the ends need to be specified manually, there
* is no need to specify both, since whiohever end is explicitly defined will implicitly
* define what the other end is.
*/
upperEnd = nil
/* The room at the lower end of this staircase */
lowerEnd = nil
/*
* initialise this SymStairway by first carrying out the inherited initialization and then
* trying to determine which end of the stairway is the upperEnd and which the lowerEnd.
*/
preinitThing
{
/* Carry out the inherited handling. */
inherited();
/* Attempt to determine which end of the Stairway is which. */
findUpperAndLowerEnds();
}
/*
* Method which attempts to determine which end of the staircase is the upper and which the
* lower based on the up and/or down properties defined in room1 and room2.
*/
findUpperAndLowerEnds()
{
/*
* If the lower end is not yet defined and room1 points to us on its up property, then our
* lower end must be room1.
*/
if(lowerEnd == nil && room1 && room1.getConnector(&up) == self)
lowerEnd = room1;
/*
* If the upper end is not yet defined and room1 points to us on its down property, then
* our upper end must be room 1.
*/
if(upperEnd == nil && room1 && room1.getConnector(&down) == self)
upperEnd = room1;
/*
* If the lower end is not yet defined and room2 points to us on its up property, then our
* lower end must be room 2.
*/
if(lowerEnd == nil && room2 && room2.getConnector(&up) == self)
lowerEnd = room2;
/*
* If the upper end is not yet defined and room2 points to us on its down property, then
* our upper end must be room 2.
*/
if(upperEnd == nil && room2 && room2.getConnector(&down) == self)
upperEnd = room2;
/*
* If the upper end is not yet defined but the lower end is, then our upper end must be
* whichever room isn't the lower end.
*/
if(upperEnd == nil && lowerEnd)
upperEnd = lowerEnd == room1 ? room2 : room1;
/*
* If the lower end is not yet defined but the upper end is, then our lower end must be
* whichever room isn't the upper end.
*/
if(lowerEnd == nil && upperEnd)
lowerEnd = upperEnd == room1 ? room2 : room1;
}
/*
* The appropriate PushTravelAction for pushing something something up or down a
* SymStairway.
*/
PushTravelVia = location.isOrIsIn(lowerEnd) ? PushTravelClimbUp : PushTravelClimbDown
/*
* Display message announcing that traveler (typically an NPC whose
* departure is witnessed by the player character) has left via this
* staircase.
*/
sayDeparting(traveler)
{
if(location.isOrIsIn(lowerEnd))
inherited(traveler);
else
delegated StairwayDown(traveler);
}
/*
* Display message announcing that follower is following leader up
* this staircase.
*/
sayActorFollowing(follower, leader)
{
/* Create message parameter substitutions for the follower and leader */
if(location.isOrIsIn(lowerEnd))
delegated StairwayUp(follower, leader);
else
delegated StairwayDown(follower, leader);
}
/* The message for traversing this stairway - we delegate to Thing's message. */
traversalMsg = location.isOrIsIn(lowerEnd) ? delegated StairwayUp : delegated StairwayDown
/* a trio of short service methods to provide convenient abbreviations in game code */
/* Is the player character in our upper end room? */
inUpper = (upperEnd && gPlayerChar.isIn(upperEnd))
/* Is the player character in our lower end room? */
inLower = (lowerEnd && gPlayerChar.isIn(lowerEnd))
/*
* Return a or b depending on whether or not the player character is in our upperEnd room.
* This is primarily intended to ease the writing of descriptions or travelDescs which vary
* slightly according to which end we're at, e.g. "The stairs lead steeply <<byUpLo('down',
* 'up')>> "
*/
byEnd(arg) { return inUpper ? arg[1] : arg[2]; }
/*
* Retuen 'down' or 'up' depending on whether we're at the upper or lower end of the stairway.
*/
upOrDown = inUpper ? downDir.name : upDir.name
dobjFor(Climb)
{
verify()
{
/* You can't climb up from the upper end of a staircase. */
if(gActor.isIn(upperEnd))
illogicalNow(cannotClimbMsg);
}
}
cannotClimbMsg = BMsg(cant climb from here, '{I} {can\'t} climb up {the dobj} from {here}. ')
dobjFor(ClimbDown)
{
verify()
{
/* You can't climb up from the upper end of a staircase. */
if(gActor.isIn(lowerEnd))
illogicalNow(cannotClimbDownMsg);
}
action()
{
delegated StairwayDown();
}
report()
{
delegated StairwayDown();
}
}
cannotClimbDownMsg = BMsg(cant climb doen from here,
'{I} {can\'t} climb down {the dobj} from {here}. ')
iobjFor(PushTravelClimbUp)
{
verify()
{
inherited();
if(gActor.isIn(upperEnd))
illogicalNow(cannotPushClimbMsg);
}
}
iobjFor(PushTravelClimbDown)
{
verify()
{
inherited();
if(gActor.isIn(lowerEnd))
illogicalNow(cannotPushClimbDownMsg);
}
}
cannotPushClimbMsg = BMsg(cannot push climb here, '{I} {can\'t} ascend {the iobj} from {here}.
')
cannotPushClimbDownMsg = BMsg(cannot push climb down here, '{I} {can\'t} descend {the iobj} from {here}.
')
;
/* A Passage represents a physical object an actor can travel through, like a
passage or portal. A Passage is also a TravelConnector so it
* can be defined on the appropriate direction property of its enclosing room.
*/
class Passage: TravelConnector, Thing
/* Going through a Passage is equivalent to travelling via it. */
dobjFor(GoThrough)
{
preCond = [travelPermitted, touchObj]
action() { travelVia(gActor); }
}
iobjFor(PushTravelThrough)
{
preCond = [travelPermitted, touchObj]
}
/* Entering a Passage is the same as going through it */
dobjFor(Enter) asDobjFor(GoThrough)
/* Going along a Passage is the same as going through it */
dobjFor(GoAlong) asDobjFor(GoThrough)
/* A Passage is usually something fixed in place. */
isFixed = true
/* A Passage is something one can go through */
canGoThroughMe = true
/*
* The appropriate PushTravelAction for pushing something something
* through a Passage.