forked from EricEve/adv3lite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
query.t
1252 lines (1071 loc) · 40.6 KB
/
query.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"
/*
* ***************************************************************************
* query.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
*
* Based substantially on the query.t module in the Mercury Library (c) 2012
* Michael J. Roberts.
*/
/* ------------------------------------------------------------------------ */
/*
* Q is the general-purpose global Query object. Its various methods are
* used to ask questions about the game state.
*
* For any query, there are two sources of answers. First, there's the
* standard answer based on the basic "physics" of the adventure world
* model. Second, there are any number of custom answers from Special
* objects, which define customizations that apply to specific
* combinations of actors, locations, objects, times, or just about
* anything else that the game can model.
*
* The standard physics-based answer is the default. It provides the
* answer if there are no active Special objects that provide custom
* answers.
*
* If there are active Specials, the only ones that matter for a
* particular query are the ones that define that query's method. If
* there are any active Special objects that define a query method,
* calling Q.foo() actually calls the highest-priority Special's version
* of the foo() method. That Special method can in turn call the next
* lower priority Special using next(). If there are no active Special
* objects defining a query method, the default handler in QDefaults will
* be used automatically.
*/
Q: object
/*
* Get the list of objects that are in scope for the given actor.
* Returns a ScopeList object containing the scope. You can convert
* the ScopeList to an ordinary list of objects via toList().
*/
scopeList(actor)
{ return Special.first(&scopeList).scopeList(actor); }
knownScopeList()
{ return Special.first(&knownScopeList).knownScopeList;}
topicScopeList()
{ return Special.first(&topicScopeList).topicScopeList;}
/*
* Is A in the light? This determines if there's light shining on
* the exterior surface of A.
*/
inLight(a)
{ return Special.first(&inLight).inLight(a); }
/*
* Can A see B?
*/
canSee(a, b)
{ return Special.first(&canSee).canSee(a, b); }
/*
* Determine if there's anything blocking the sight path from A to B.
* Returns a list of objects blocking sight; if there's no
* obstruction, returns an empty list. If the two objects are in
* separate rooms, the outermost room containing 'a' represents the
* room separation. If there's no obstruction, returns an empty
* list.
*/
sightBlocker(a, b)
{ return Special.first(&sightBlocker).sightBlocker(a, b); }
/*
* Can we reach from A to B? We return true if there's nothing in
* the way, nil otherwise.
*/
canReach(a, b)
{ return Special.first(&canReach).canReach(a, b); }
/*
* Determine if A can reach B, and if not, what stands in the way.
* Returns a list of containers along the path between A and B that
* obstruct the reach. If the two objects are in separate rooms, the
* top-level room containing A is in the list to represent the room
* separation. If there's no obstruction, we return an empty list.
*/
reachBlocker(a, b)
{ return Special.first(&reachBlocker).reachBlocker(a, b); }
/*
* Determine if there is a problem with A reaching B, and if so what it
* is. If there is a problem return a ReachProblem object describing what
* the problem is, otherwise return nil.
*/
reachProblem(a, b)
{ return Special.first(&reachProblem).reachProblem(a, b); }
reachProblemVerify(a, b)
{ return Special.first(&reachProblemVerify).reachProblemVerify(a, b); }
reachProblemCheck(a, b)
{ return Special.first(&reachProblemCheck).reachProblemCheck(a, b); }
/*
* Can A hear B?
*/
canHear(a, b)
{ return Special.first(&canHear).canHear(a, b); }
/*
* Determine if A can hear B, and if not, what stands in the way. We
* return a list of the obstructions to sound between A and B. If
* the two objects are in separate rooms, the top level room
* containing A represents the room separation. If there are no
* sound obstructions, returns an empty list.
*/
soundBlocker(a, b)
{ return Special.first(&soundBlocker).soundBlocker(a, b); }
/*
* Can A smell B?
*/
canSmell(a, b)
{ return Special.first(&canSmell).canSmell(a, b); }
/*
* Determine if A can smell B, and if not, what stands in the way.
* Returns a list of obstructions to scent between A and B. If the
* two objects are in separate rooms, the outermost room containing A
* represents the room separation. If there are no obstructions,
* returns an empty list.
*/
scentBlocker(a, b)
{ return Special.first(&scentBlocker).scentBlocker(a, b); }
/* Determine if A can talk to B. */
canTalkTo(a, b)
{ return Special.first(&canTalkTo).canTalkTo(a, b); }
/* Determine if A can Throw something to B. */
canThrowTo(a, b)
{ return Special.first(&canThrowTo).canThrowTo(a, b); }
;
/*
* Query Defaults. This provides the default handlers for all query
* methods. These are the results that you get using the basic adventure
* game "physics" model to answer the questions, ignoring any special
* exceptions defined by the game.
*
* This is the lowest-ranking Special object, and is always active.
*/
QDefaults: Special
/* this is the defaults object, so it has the lower priority */
priority = 0
/* this is the defaults object, so it's always active */
active = true
/*
* Get the list of objects that are in scope for the given actor.
* Returns a ScopeList object containing the scope. You can convert
* the ScopeList to an ordinary list of objects via toList().
*/
scopeList(actor)
{
/* start a new scope list */
local s = new ScopeList();
/* everything the actor is directly holding is in scope */
s.addAll(actor.directlyHeld);
local c = actor.outermostVisibleParent();
/*
* If we're in a lighted area, add the actor's outermost visible
* container and its contents. In the dark, add the actor's
* immediate container only (not its contents), on the assumption
* that the actor is in physical contact with it and thus can
* refer to it and manipulate it even without seeing it.
*/
if (inLight(actor))
{
/* lit area - add the outermost container and its contents */
s.addOnly(c);
s.addWithin(c);
}
else
{
/* in the dark - add only the immediate container */
s.addOnly(actor.location);
/* plus anything that's self illuminating */
s.addSelfIlluminatingWithin(c);
}
/* close the scope */
s.close();
/* return the ScopeList we've built */
return s;
}
/* Get a list of all objects that are known to the player char */
knownScopeList()
{
local vec = new Vector(30);
for(local obj = firstObj(Thing); obj != nil; obj = nextObj(obj, Thing))
{
if(obj.known)
vec += obj;
}
return vec.toList;
}
/*
* Get a list of all known mentionable objects, which we assume will
* include both known Things and known Topics
*/
topicScopeList()
{
return World.universalScope.subset({o: o.known});
}
/*
* Is A in the light? This determines if there's light shining on
* the exterior surface of A.
*/
inLight(a)
{
/* A is lit if it's a Room and it's illuminated */
if(a.ofKind(Room))
return a.isIlluminated;
/* Otherwise a may be lit if it's visible in the dark */
if(a.visibleInDark)
return true;
/* A is lit if its enclosing parent is lit within */
local par = a.interiorParent();
return par != nil && par.litWithin();
}
/*
* Can A see B? We return true if and only if B is in light and there's a
* clear sight path from A to B. Also A can't see B is B is explicitly
* hidden.
*/
canSee(a, b)
{
if(a.isIn(nil) || b.isIn(nil) || b.isHidden)
return nil;
/* we can see it if it's in light and there's a clear path to it */
return inLight(b)
&& sightBlocker(a, b).indexWhich({x: x not in (a, b)}) == nil;
}
/*
* Determine if there's anything blocking the sight path from A to B.
* Returns a list of objects blocking sight; if there's no
* obstruction, returns an empty list. If the two objects are in
* separate rooms, the outermost room containing 'a' represents the
* room separation. If there's no obstruction, returns an empty
* list.
*/
sightBlocker(a, b)
{
/* scan for sight blockages along the containment path */
return a.containerPathBlock(b, &canSeeOut, &canSeeIn);
}
/*
* Can we reach from A to B? We return true if there's a clear reach path
* from A to B, which we take to be the case if we can't find any problems
* in reaching from A to B.
*/
canReach(a, b)
{
return Q.reachProblem(a, b) == [];
}
/*
* Determine if there is anything preventing or hindering A from reaching
* B; if so return a ReachProblem object describing the problem in a way
* that a check or verify routine can act on (possibly with an implicit
* action to remove the problem). If not, return an empty list.
*
* NOTE: if you provide your own version of this method on a Special it
* must return either an empty list (to indicate that there are no
* problems with reaching from A to B) or a list of one or more
* ReachProblem objects describing what is preventing A from reaching B.
*
* Your own Special should normally leave reachProblem() alone and
* override reachProblemVerify() and/or reachProblemCheck().
*/
reachProblem(a, b)
{
/*
* A list of issues that might prevent reaching from A to B. If we
* encounter a fatal one we return the list straight away rather than
* carrying out more checks.
*/
local issues = Q.reachProblemVerify(a, b);
if(issues.length > 0)
return issues;
return Q.reachProblemCheck(a, b);
}
/* Return a list of reach issues that might occur at the verify stage. */
reachProblemVerify(a, b)
{
/*
* A list of issues that might prevent reaching from A to B. If we
* encounter a fatal one we return the list straight away rather than
* carrying out more checks.
*/
local issues = [];
if(a.isIn(nil) || b.isIn(nil))
{
issues += new ReachProblemDistance(a, b);
return issues;
}
local lst = Q.reachBlocker(a, b);
/*
* If there's a blocking object but the blocking object is the one
* we're trying to reach, then presumably we can reach it after all
* (e.g. an actor inside a closed box. Otherwise if there's a
* blocking object then reach is impossible.
*/
if(lst.length > 0 && lst[1] != b)
{
/*
* If the blocking object is a room, then the problem is that the
* other object is too far away.
*/
if(lst[1].ofKind(Room))
issues += new ReachProblemDistance(a, b);
/* Otherwise some enclosing object is in the way */
else
issues += new ReachProblemBlocker(b, lst[1]);
return issues;
}
/*
* If a defines a verifyReach method, check whether running it adds a
* new verify result to the current action's verifyTab table. If so,
* there's a problem with reaching so return a ReachProblemVerifyReach
* object.
*/
if(b.propDefined(&verifyReach))
{
local tabCount = gAction.verifyTab.getEntryCount();
b.verifyReach(a);
if(gAction.verifyTab.getEntryCount > tabCount)
issues += new ReachProblemVerifyReach(a, b);
}
/* Return our list of issues. */
return issues;
}
reachProblemCheck(a, b)
{
/*
* A list of issues that might prevent reaching from A to B. If we
* encounter a fatal one we return the list straight away rather than
* carrying out more checks.
*/
local issues = [];
local checkMsg = nil;
/*
* Next check whether the actor is in a nested room that does not
* contain the object being reached, and if said nested room does not
* allow reaching out.
*/
local loc = a.location;
if(loc != a.getOutermostRoom && !b.isOrIsIn(loc) &&
!loc.allowReachOut(b))
issues += new ReachProblemReachOut(b);
try
{
/*
* Determine whether there's any problem with b reached from a
* defined in b's checkReach (from a) method.
*/
checkMsg = gOutStream.captureOutputIgnoreExit({: b.checkReach(a)});
/*
* If the checkReach method generates a non-empty string, add a
* new ReachProblemCheckReach object that encapsulates it
*/
if(checkMsg not in (nil, ''))
issues += new ReachProblemCheckReach(b, checkMsg);
/*
* Next check whether there's any problem reaching inside B from A
* defined in B's checkReachIn method or the checkReach in method
* of anything that contains B.
*/
local cpar = b.commonContainingParent(a);
if(cpar != nil)
{
for(loc = b.location; loc != cpar; loc = loc.location)
{
checkMsg = gOutStream.captureOutputIgnoreExit(
{: loc.checkReachIn(a, b)});
if(checkMsg not in (nil, ''))
issues += new ReachProblemCheckReach(b, checkMsg);
}
}
}
/*
* Game authors aren't meant to use the exit macro in check methods,
* but in case they do we handle it here.
*/
catch (ExitSignal ex)
{
/*
* If for some reason a check method uses exit without displaying
* a method, we supply a dummy failure message at this point.
*/
if(checkMsg is in (nil, ''))
checkMsg = gAction.failCheckMsg;
issues += new ReachProblemCheckReach(b, checkMsg);
}
/* Return our list of issues */
return issues;
}
/*
* Determine if A can reach B, and if not, what stands in the way. Returns
* a list of containers along the path between A and B that obstruct the
* reach. If the two objects are in separate rooms, the top-level room
* containing A is in the list to represent the room separation. If
* there's no obstruction, we return an empty list.
*/
reachBlocker(a, b)
{
return a.containerPathBlock(b, &canReachOut, &canReachIn);
}
/*
* Can A hear B? We return true if there's a clear sound path from A to
* B.
*/
canHear(a, b)
{
if(a.isIn(nil) || b.isIn(nil))
return nil;
return soundBlocker(a, b).indexWhich({x: x not in (a, b)}) == nil;
}
/*
* Determine if A can hear B, and if not, what stands in the way. We
* return a list of the obstructions to sound between A and B. If
* the two objects are in separate rooms, the top level room
* containing A represents the room separation. If there are no
* sound obstructions, returns an empty list.
*/
soundBlocker(a, b)
{
return a.containerPathBlock(b, &canHearOut, &canHearIn);
}
/*
* Can A smell B? We return true if there's a clear scent path from
* A to B.
*/
canSmell(a, b)
{
if(a.isIn(nil) || b.isIn(nil))
return nil;
return scentBlocker(a, b).indexWhich({x: x not in (a, b)}) == nil;
}
/*
* Determine if A can smell B, and if not, what stands in the way.
* Returns a list of obstructions to scent between A and B. If the
* two objects are in separate rooms, the outermost room containing A
* represents the room separation. If there are no obstructions,
* returns an empty list.
*/
scentBlocker(a, b)
{
return a.containerPathBlock(b, &canSmellOut, &canSmellIn);
}
/*
* Determine if A can talk to B. In the base situation A can talk to B if
* A can hear B.
*/
canTalkTo(a, b)
{
return Q.canHear(a, b);
}
/*
* Determine if A can throw something to B. In the base situation A can
* throw to B if A can reach B.
*
*/
canThrowTo(a, b)
{
return canReach(a, b);
}
;
/* ------------------------------------------------------------------------ */
/*
* A Special defines a set of custom overrides to standard Query
* questions that apply under specific conditions.
*
* At any given time, a Special is either active or inactive. This is
* determined by the active() method.
*/
class Special: object
/*
* Am I active? Each instance should override this to define the
* conditions that activate the Special.
*/
active = nil
/*
* My priority. This is an integer value that determines which
* Special takes precedence when two or more Specials are active at
* the same time, and they both/all define a given query method. In
* such a situation, Q calls the active Specials in ascending
* priority order (lowest first, highest last), and takes the last
* one's answer as the true answer to the question. This means that
* the Special with the highest priority takes precedence, and can
* override any lower-ranking Special that's active at the same time.
*
* The library uses the following special priority values:
*
* 0 = the basic library defaults. The defaults must have the lowest
* priority, meaning that all Special objects defined by a game or
* extension must use priorities higher than 0.
*
* Other than the special priorities listed above, the priority is
* simply a relative ordering, so games and extensions can use
* whatever range of values they like.
*
* Note that priorities can't change while running. This is a
* permanent feature of the object. We take advantage of this to
* avoid re-sorting the active list every time we build it. We sort
* the master list at initialization and assume it stays sorted, so
* that any subset is inherently sorted. If it's important to the
* game to dynamically change priorities, you just need to re-sort
* the allActive_ list at appropriate times. If priorities can only
* change when the game-world state changes, you can simply sort the
* list in allActive() each time it's rebuilt. If priorities can
* change at other times (which doesn't seem like it'd be useful, but
* just in case), you'd need to re-sort the list on every call to
* allActive(), even when the list isn't rebuilt.
*/
priority = 1
/*
* Call the same method in the next lower priority Special. This can
* be used in any Special query method to invoke the "default"
* version that would have been used if the current Special had not
* been active.
*
* This is analogous to using 'inherited' to inherit the superclass
* version of a method from an overriding version in a subclass. As
* with 'inherited', you can only call this directly from the method
* that you want to pass to the default handling, because this
* routine determines what to call based on the caller.
*/
next()
{
/* get the caller's stack trace information */
local stk = t3GetStackTrace(2);
local prop = stk.prop_;
/* find the 'self' object in the currently active Specials list */
local slst = Special.allActive();
local idx = slst.indexOf(stk.self_);
/* get the next Special that defines the method */
while (!slst[++idx].propDefined(prop)) ;
/* call the query method in the next Special, returning the result */
return slst[idx].(prop)(stk.argList_...);
}
/*
* Get the first active Special (the one with the highest priority)
* that defines the given method. This is used by the Q query
* methods to invoke the correct current Special version of the
* method.
*/
first(prop)
{
/* get the active Specials */
local slst = Special.allActive();
/* find the first definer of the method */
local idx = 0;
while (!slst[++idx].propDefined(prop)) ;
/* return the one we found */
return slst[idx];
}
/* Class method: get the list of active Specials. */
allActive()
{
local a;
/* if the cache is empty, rebuild it */
if ((a = allActive_) == nil)
a = allActive_ = all.subset({ s: s.active() });
/* return the list */
return a;
}
/*
* Class property: cache of all currently active Specials. This is
* set whenever someone asks for the list and it's not available, and
* is cleared whenever an Effect modifies the game state. (Callers
* shouldn't access this directly - this is an internal cache. Use
* the allActive() method instead.)
*/
allActive_ = nil
/* during initialization, build the list of all Specials */
classInit()
{
/* build the list of all Specials */
local v = new Vector(128);
forEachInstance(Special, { s: v.append(s) });
/*
* Sort it in ascending priority order. Since we assume that
* priorities are fixed, this eliminates the need to sort when
* creating active subsets - the subsets will automatically come
* up in priority order because they're taken from a list that
* starts in priority order.
*/
v.sort(SortDesc, { a, b: a.priority - b.priority });
/* save it as a list */
all = v.toList();
}
/*
* Class property: the list of all Special objects throughout the
* game. This is set up during preinit.
*/
all = []
;
/*------------------------------------------------------------------------- */
/*
* A commLink is a Special that establishes a communications link between the
* player character and one or more actors in remote locations.
*
* To activate the commLink with another actor, call
* commLink.connectTo(other). To make it a video link as well as an audio
* link, call commLink.connectTo(other, true).
*
* To disconnect the call with a specific actor, call
* commLink.disconnectFrom(other); to terminate the commLink with all actors,
* call commLink.disconnect()
*
*/
commLink: Special
/*
* Our scope list must include all the actors we're currently connected
* to.
*/
scopeList(actor)
{
local s = next();
s.vec_ += connectionList.mapAll({x: x[1]});
s.vec_ = s.vec_.getUnique();
return s;
}
/* We can hear an actor if s/he's in our connection list */
canHear(a, b)
{
/*
* We assume that if a can hear b, b can hear a, but the link is only
* between the player character an another actor. If b is the player
* character swap a and b so that the tests that follow will still
* apply.
*/
if(b == gPlayerChar)
{
b = a;
a = gPlayerChar;
}
/*
* If one of the actors is the player character and the other is in
* our connection list, then they can hear each other.
*/
if(a == gPlayerChar && isConnectedTo(b))
return true;
/* Otherwise use the next special. */
return next();
}
canSee(a, b)
{
/*
* We assume that if a can see b, b can see a, but the link is only
* between the player character and another actor. If b is the player
* character swap a and b so that the tests that follow will still
* apply.
*/
if(b == gPlayerChar)
{
b = a;
a = gPlayerChar;
}
/*
* If one of the actors is the player character and the other is in
* our connection list with a video value of true, then they can see
* each other.
*/
if(a == gPlayerChar && isConnectedTo(b) == VideoLink)
return true;
/* Otherwise use the next special. */
return next();
}
canTalkTo(a, b)
{
/*
* We assume that if a can talk to b, b can talk to a, but the link is
* only between the player character an another actor. If b is the
* player character swap a and b so that the tests that follow will
* still apply.
*/
if(b == gPlayerChar)
{
b = a;
a = gPlayerChar;
}
/*
* If one of the actors is the player character and the other is in
* our connection list, then they can talk to each other.
*/
if(a == gPlayerChar && isConnectedTo(b))
return true;
/* Otherwise use the next special. */
return next();
}
/*
* The list of actors we're currently connected to. This is a list of two
* element lists in the form [actor, video], where actor is the actor
* we're connected to and video is true or nil according to whether the
* link to that actor is a video link as well as an audio link.
*/
connectionList = []
/* This Special is active is there's anything in its connectionList. */
active = connectionList.length > 0
/*
* Connect this comms link to other; if video is specified and is true,
* the comms links is also a video link.
*/
connectTo(other, video = nil)
{
/*
* In case the video parameter is supplied as AudioLink or VideoLink
* (some game authors may try this even though it's not documented),
* we should first translate the video parameter into true or nil as
* appropriate.
*/
if(video == AudioLink)
video = nil;
if(video == VideoLink)
video = true;
/* Add other to our connection list. */
connectionList = connectionList.append([other, video]);
/* Force the Special class to rebuild its list of active Specials. */
Special.allActive_ = nil;
}
/* Disconnect this commLink from everyone */
disconnect()
{
/* Empty our out connectionList */
connectionList = [];
/* Force the Special class to rebuild its list of active Specials. */
Special.allActive_ = nil;
}
/*
* Disconnect this commLink from lst, where lst may be a single actor or a
* list of actors.
*/
disconnectFrom(lst)
{
/* Convert the lst parameter to a list if it isn't one already */
lst = valToList(lst);
/*
* Reduce our connectionList to a subset of members that aren't in
* lst.
*/
connectionList = connectionList.subset({x: lst.indexOf(x[1]) == nil});
/* Force the Special class to rebuild its list of active Specials. */
Special.allActive_ = nil;
}
/*
* Is there a communications link with obj? Return nil if there is none,
* AudioLink if there's an audio connection only and VideoLink if there's
* a video connection as well.
*/
isConnectedTo(obj)
{
local conn = connectionList.valWhich({x: x[1] == obj});
if(conn == nil)
return nil;
return conn[2] ? VideoLink : AudioLink;
}
/*
* Give this Special a higher priority that the QSenseRegion Special so
* that it takes precedence when its active.
*/
priority = 5
;
/* ------------------------------------------------------------------------ */
/*
* A ScopeList is a helper object used to build the list of objects in
* scope. This object provides methods for the common ways of adding
* objects to scope.
*
* The ScopeList isn't a true Collection object, but it mimics one by
* providing most of the standard methods. You can use length() and the
* [] operator to scan the list, perform a foreach or for..in loop with a
* ScopeList to iterate over the items in scope, you can use find() to
* check if a given object is in scope, and you can use subset() to get a
* list of in-scope objects satisfying some condition.
*/
class ScopeList: object
/*
* Add an object and its contents to the scope.
*/
add(obj)
{
/*
* if we've already visited this object in full-contents mode,
* there's no need to repeat all that
*/
local tstat = status_[obj];
if (tstat == 2)
return;
/* if the object isn't already in the list at all, add it */
if (tstat == nil)
vec_.append(obj);
/* promote it to status 2: added with contents */
status_[obj] = 2;
/*
* if we can see in, add all of the contents, interior and
* exterior; otherwise add just the exterior contents
*/
if (obj.canSeeIn)
addAll(obj.contents);
else
addAll(obj.extContents);
}
/*
* Add all of the objects in the given list
*/
addAll(lst)
{
for (local i = 1, local len = lst.length() ; i <= len ; ++i)
add(lst[i]);
}
/*
* Add the interior contents of an object to the scope. This adds
* only the contents, not the object itself.
*/
addWithin(obj)
{
/* add each object in the interior contents */
addAll(obj.intContents);
}
/* add each self-illuminating object in the interior contents */
addSelfIlluminatingWithin(obj)
{
addAll(obj.intContents.subset({x: x.visibleInDark}));
}
/*
* Add a single object to the scope. This doesn't add anything
* related to the object (such as its contents) - just the object
* itself.
*/
addOnly(obj)
{
/*
* If this object is already in the status table with any status,
* there's no need to add it again. We also don't want to change
* its existing status, because if we've already added it with
* its contents, adding it redundantly by itself doesn't change
* the fact that we've added its contents.
*/
if (status_[obj] != nil)
return;
/* add it to the vector */
vec_.append(obj);
/* set the status to 1: we've added only this object */
status_[obj] = 1;
}
/* "close" the scope list - this converts the vector to a list */
close()
{
vec_ = vec_.toList();
status_ = nil;
}
/* get the number of items in scope */
length() { return vec_.length(); }
/* get an item from the list */
operator[](idx) { return vec_[idx]; }
/* is the given object in scope? */