cfad47cfa3/t3compiler/tads3/lib/adv3/thing.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 by Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library - Thing
7
 *   
8
 *   This module defines Thing, the base class for physical objects in the
9
 *   simulation.  We also define some utility classes that Thing uses
10
 *   internally.  
11
 */
12
13
/* include the library header */
14
#include "adv3.h"
15
16
17
/* ------------------------------------------------------------------------ */
18
/* 
19
 *   this property is defined in the 'exits' module, but declare it here
20
 *   in case we're not including the 'exits' module 
21
 */
22
property lookAroundShowExits;
23
24
25
/* ------------------------------------------------------------------------ */
26
/*
27
 *   Sense Information entry.  Thing.senseInfoTable() returns a list of
28
 *   these objects to provide full sensory detail on the objects within
29
 *   range of a sense.  
30
 */
31
class SenseInfo: object
32
    construct(obj, trans, obstructor, ambient)
33
    {
34
        /* set the object being described */
35
        self.obj = obj;
36
37
        /* remember the transparency and obstructor */
38
        self.trans = trans;
39
        self.obstructor = obstructor;
40
41
        /* 
42
         *   set the energy level, as seen from the point of view - adjust
43
         *   the level by the transparency 
44
         */
45
        self.ambient = (ambient != nil
46
                        ? adjustBrightness(ambient, trans)
47
                        : nil);
48
    }
49
    
50
    /* the object being sensed */
51
    obj = nil
52
53
    /* the transparency from the point of view to this object */
54
    trans = nil
55
56
    /* the obstructor that introduces a non-transparent value of trans */
57
    obstructor = nil
58
59
    /* the ambient sense energy level at this object */
60
    ambient = nil
61
62
    /* 
63
     *   compare this SenseInfo object's transparency to the other one;
64
     *   returns a number greater than zero if 'self' is more transparent,
65
     *   zero if they're equally transparent, or a negative number if
66
     *   'self' is less transparent 
67
     */
68
    compareTransTo(other) { return transparencyCompare(trans, other.trans); }
69
70
    /*
71
     *   Return the more transparent of two SenseInfo objects.  Either
72
     *   argument can be nil, in which case we'll return the non-nil one;
73
     *   if both are nil, we'll return nil.  If they're equal, we'll return
74
     *   the first one.  
75
     */
76
    selectMoreTrans(a, b)
77
    {
78
        /* if one or the other is nil, return the non-nil one */
79
        if (a == nil)
80
            return b;
81
        else if (b == nil)
82
            return a;
83
        else if (a.compareTransTo(b) >= 0)
84
            return a;
85
        else
86
            return b;
87
    }
88
;
89
90
/*
91
 *   Can-Touch information.  This object keeps track of whether or not a
92
 *   given object is able to reach out and touch another object. 
93
 */
94
class CanTouchInfo: object
95
    /* construct, given the touch path */
96
    construct(path) { touchPath = path; }
97
    
98
    /* the full reach-and-touch path from the source to the target */
99
    touchPath = nil
100
101
    /* 
102
     *   if we have calculated whether or not the source can touch the
103
     *   target, we'll set the property canTouch to nil or true
104
     *   accordingly; if this property is left undefined, this information
105
     *   has never been calculated 
106
     */
107
    // canTouch = nil
108
;
109
110
/*
111
 *   Given a sense information table (a LookupTable returned from
112
 *   Thing.senseInfoTable()), return a vector of only those objects in the
113
 *   table that match the given criteria.
114
 *   
115
 *   'func' is a function that takes two arguments, func(obj, info), where
116
 *   'obj' is a simulation object and 'info' is the corresponding
117
 *   SenseInfo object.  This function is invoked for each object in the
118
 *   sense info table; if 'func' returns true, then 'obj' is part of the
119
 *   list that we return.
120
 *   
121
 *   The return value is a simple vector of game objects.  (Note that
122
 *   SenseInfo objects are not returned - just the simulation objects.)  
123
 */
124
senseInfoTableSubset(senseTab, func)
125
{
126
    local vec;
127
    
128
    /* set up a vector for the return list */
129
    vec = new Vector(32);
130
131
    /* scan the table for objects matching criteria given by 'func' */
132
    senseTab.forEachAssoc(new function(obj, info)
133
    {
134
        /* if the function accepts this object, include it in the vector */
135
        if ((func)(obj, info))
136
            vec.append(obj);
137
    });
138
139
    /* return the result vector */
140
    return vec;
141
}
142
143
/*
144
 *   Sense calculation scratch-pad globals.  Many of the sense
145
 *   calculations involve recursive descents of portions of the
146
 *   containment tree.  In the course of these calculations, it's
147
 *   sometimes useful to have information about the entire operation in
148
 *   one of the recursive calls.  We could pass the information around as
149
 *   extra parameters, but that adds overhead, and performance is critical
150
 *   in the sense routines (because they tend to get invoked a *lot*).  To
151
 *   reduce the overhead, particularly for information that's not needed
152
 *   very often, we stuff some information into this global object rather
153
 *   than passing it around through parameters.
154
 *   
155
 *   Note that this object is transient because this information is useful
156
 *   only during the course of a single tree traversal, and so doesn't
157
 *   need to be saved or undone.  
158
 */
159
transient senseTmp: object
160
    /* 
161
     *   The point of view of the sense calculation.  This is the starting
162
     *   point of a sense traversal; it's the object that's viewing the
163
     *   other objects.  
164
     */
165
    pointOfView = nil
166
167
    /* post-calculation notification list */
168
    notifyList = static new Vector(16)
169
;
170
171
172
/* ------------------------------------------------------------------------ */
173
/*
174
 *   Command check status object.  This is an abstract object that we use
175
 *   in to report results from a check of various kinds.
176
 *   
177
 *   The purpose of this object is to consolidate the code for certain
178
 *   kinds of command checks into a single routine that can be used for
179
 *   different purposes - verification, selection from multiple
180
 *   possibilities (such as multiple paths), and command action
181
 *   processing.  This object encapsulates a status - success or failure -
182
 *   and, when the status is failure, a message giving the reason for the
183
 *   failure.
184
 */
185
class CheckStatus: object
186
    /* did the check succeed or fail? */
187
    isSuccess = nil
188
189
    /* 
190
     *   the message property or string, and parameters, for failure -
191
     *   this is for use with reportFailure or the like 
192
     */
193
    msgProp = nil
194
    msgParams = []
195
;
196
197
/* 
198
 *   Success status object.  Note that this is a single object, not a
199
 *   class - there's no distinct information per success indicator, so we
200
 *   only need this single success indicator for all uses. 
201
 */
202
checkStatusSuccess: CheckStatus
203
    isSuccess = true
204
;
205
206
/* 
207
 *   Failure status object.  Unlike the success indicator, this is a
208
 *   class, because we need to keep track of the separate failure message
209
 *   for each kind of failure.  
210
 */
211
class CheckStatusFailure: CheckStatus
212
    construct(prop, [params])
213
    {
214
        isSuccess = nil;
215
        msgProp = prop;
216
        msgParams = params;
217
    }
218
;
219
220
221
/* ------------------------------------------------------------------------ */
222
/*
223
 *   Equivalent group state information.  This keeps track of a state and
224
 *   the number of items in that state when we're listing a group of
225
 *   equivalent items in different states.  
226
 */
227
class EquivalentStateInfo: object
228
    construct(st, obj, nameProp)
229
    {
230
        /* remember the state object and the name property to display */
231
        stateObj = st;
232
        stateNameProp = nameProp;
233
234
        /* this is the first one in this state */
235
        stateVec = new Vector(8);
236
        stateVec.append(obj);
237
    }
238
239
    /* add an object to the list of equivalent objects in this state */
240
    addEquivObj(obj) { stateVec.append(obj); }
241
242
    /* get the number of equivalent items in the same state */
243
    getEquivCount() { return stateVec.length(); }
244
245
    /* get the list of equivalent items in the same state */
246
    getEquivList() { return stateVec; }
247
248
    /* get the name to use for listing purposes */
249
    getName() { return stateObj.(stateNameProp)(stateVec); }
250
251
    /* the ThingState object describing the state */
252
    stateObj = nil
253
254
    /* the property to evaluate to get the name for listing purposes */
255
    stateNameProp = nil
256
257
    /* list of items in this same state */
258
    stateVec = nil
259
;
260
261
262
/* ------------------------------------------------------------------------ */
263
/*
264
 *   Drop Descriptor.  This is passed to the receiveDrop() method of a
265
 *   "drop destination" when an object is discarded via commands such as
266
 *   DROP or THROW.  The purpose of the descriptor is to identify the type
267
 *   of command being performed, so that the receiveDrop() method can
268
 *   generate an appropriate report message.  
269
 */
270
class DropType: object
271
    /*
272
     *   Generate the standard report message for the action.  The drop
273
     *   destination's receiveDrop() method can call this if the standard
274
     *   message is adequate to describe the result of the action.
275
     *   
276
     *   'obj' is the object being dropped, and 'dest' is the drop
277
     *   destination.  
278
     */
279
    // standardReport(obj, dest) { /* subclasses must override */ }
280
281
    /*
282
     *   Get a short report describing the action without saying where the
283
     *   object ended up.  This is roughly the same as the standard report,
284
     *   but omits any information on where the object lands, so that the
285
     *   caller can show a separate message explaining that part.
286
     *   
287
     *   The report must be worded such that the object being dropped is
288
     *   the logical antecedent for any subsequent text.  This means that
289
     *   callers can use a pronoun to refer back to the object dropped,
290
     *   allowing for more natural sequences to be constructed.  (It
291
     *   usually sounds stilted to repeat the full name: "You drop the box.
292
     *   The box falls into the chasm."  It's better if we can use a
293
     *   pronoun in the second sentence: "You drop the box. It falls into
294
     *   the chasm.")
295
     *   
296
     *   'obj' is the object being dropped, and 'dest' is the drop
297
     *   destination.  
298
     */
299
    // getReportPrefix(obj, dest) { return ''; }
300
;
301
302
/*
303
 *   A drop-type descriptor for the DROP command.  Since we have no need to
304
 *   include any varying parameters in this object, we simply provide this
305
 *   singleton instance.  
306
 */
307
dropTypeDrop: DropType
308
    standardReport(obj, dest)
309
    {
310
        /* show the default "Dropped" response */
311
        defaultReport(&okayDropMsg);
312
    }
313
314
    getReportPrefix(obj, dest)
315
    {
316
        /* return the standard "You drop the <obj>" message */
317
        return gActor.getActionMessageObj().droppingObjMsg(obj);
318
    }
319
;
320
321
/*
322
 *   A drop-type descriptor for the THROW command.  This object keeps track
323
 *   of the target (the object that was hit by the projectile) and the
324
 *   projectile's path to the target.  The projectile is simply the direct
325
 *   object.  
326
 */
327
class DropTypeThrow: DropType
328
    construct(target, path)
329
    {
330
        /* remember the target and path */
331
        target_ = target;
332
        path_ = path;
333
    }
334
335
    standardReport(obj, dest)
336
    {
337
        local nominalDest;
338
        
339
        /* get the nominal drop destination */
340
        nominalDest = dest.getNominalDropDestination();
341
342
        /* 
343
         *   if the actual target and the nominal destination are the same,
344
         *   just say that it lands on the destination; otherwise, say that
345
         *   it bounces off the target and falls to the nominal destination
346
         */
347
        if (target_ == nominalDest)
348
            mainReport(&throwFallMsg, obj, target_);
349
        else
350
            mainReport(&throwHitFallMsg, obj, target_, nominalDest);
351
    }
352
353
    getReportPrefix(obj, dest)
354
    {
355
        /* return the standard "The <projectile> hits the <target>" message */
356
        return gActor.getActionMessageObj().throwHitMsg(obj, target_);
357
    }
358
359
    /* the object that was hit by the projectile */
360
    target_ = nil
361
362
    /* the path the projectile took to reach the target */
363
    path_ = nil
364
;
365
366
367
/* ------------------------------------------------------------------------ */
368
/*
369
 *   Bag Affinity Info - this class keeps track of the affinity of a bag
370
 *   of holding for an object it might contain.  We use this class in
371
 *   building bag affinity lists.  
372
 */
373
class BagAffinityInfo: object
374
    construct(obj, bulk, aff, bag)
375
    {
376
        /* save our parameters */
377
        obj_ = obj;
378
        bulk_ = bulk;
379
        aff_ = aff;
380
        bag_ = bag;
381
    }
382
383
    /*
384
     *   Compare this item to another item, for affinity ranking purposes.
385
     *   Returns positive if I should rank higher than the other item,
386
     *   zero if we have equal ranking, negative if I rank lower than the
387
     *   other item.  
388
     */
389
    compareAffinityTo(other)
390
    {
391
        /* 
392
         *   if this object is the indirect object of 'take from', treat
393
         *   it as having the lowest ranking 
394
         */
395
        if (gActionIs(TakeFrom) && gIobj == obj_)
396
            return -1;
397
398
        /* if we have different affinities, sort according to affinity */
399
        if (aff_ != other.aff_)
400
            return aff_ - other.aff_;
401
402
        /* we have the same affinity, so put the higher bulk item first */
403
        if (bulk_ != other.bulk_)
404
            return bulk_ - other.bulk_;
405
406
        /* 
407
         *   We have the same affinity and same bulk; rank according to
408
         *   how recently the items were picked up.  Put away the oldest
409
         *   items first, so the lower holding (older) index has the
410
         *   higher ranking.  (Note that because lower holding index is
411
         *   the higher ranking, we return the negative of the holding
412
         *   index comparison.)  
413
         */
414
        return other.obj_.holdingIndex - obj_.holdingIndex;
415
    }
416
417
    /* 
418
     *   given a vector of affinities, remove the most recent item (as
419
     *   indicated by holdingIndex) and return the BagAffinityInfo object 
420
     */
421
    removeMostRecent(vec)
422
    {
423
        local best = vec[1];
424
        
425
        /* find the most recent item */
426
        foreach (local cur in vec)
427
        {
428
            /* if this is better than the best so far, remember it */
429
            if (cur.obj_.holdingIndex > best.obj_.holdingIndex)
430
                best = cur;
431
        }
432
433
        /* remove the best item from the vector */
434
        vec.removeElement(best);
435
436
        /* return the best item */
437
        return best;
438
    }
439
440
    /* the object the bag wants to contain */
441
    obj_ = nil
442
443
    /* the object's bulk */
444
    bulk_ = nil
445
446
    /* the bag that wants to contain the object */
447
    bag_ = nil
448
449
    /* affinity of the bag for the object */
450
    aff_ = nil
451
;
452
453
454
/* ------------------------------------------------------------------------ */
455
/*
456
 *   "State" of a Thing.  This is an object abstractly describing the
457
 *   state of an object that can assume different states.
458
 *   
459
 *   The 'listName', 'inventoryName', and 'wornName' give the names of
460
 *   state as displayed in room/contents listings, inventory listings, and
461
 *   listings of items being worn by an actor.  This state name is
462
 *   displayed along with the item name (usually parenthetically after the
463
 *   item name, but the exact nature of the display is controlled by the
464
 *   language-specific part of the library).
465
 *   
466
 *   The 'listingOrder' is an integer giving the listing order of this
467
 *   state relative to other states of the same kind of object.  When we
468
 *   show a list of equivalent items in different states, we'll order the
469
 *   state names in ascending order of listingOrder.  
470
 */
471
class ThingState: object
472
    /* 
473
     *   The name of the state to use in ordinary room/object contents
474
     *   listings.  If the name is nil, no extra state information is shown
475
     *   in a listing for an object in this state.  (It's often desirable
476
     *   to leave the most ordinary state an object can be in unnamed, to
477
     *   avoid belaboring the obvious.  For example, a match that isn't
478
     *   burning would probably not want to mention "(not lit)" every time
479
     *   it's listed.)
480
     *   
481
     *   'lst' is a list of the objects being listed in this state.  If
482
     *   we're only listing a single object, this will be a list with one
483
     *   element giving the object being listed.  If we're listing a
484
     *   counted set of equivalent items all in this same state, this will
485
     *   be the list of items.  Everything in 'lst' will be equivalent (in
486
     *   the isEquivalent sense).  
487
     */
488
    listName(lst) { return nil; }
489
490
    /* 
491
     *   The state name to use in inventory lists.  By default, we just use
492
     *   the base name.  'lst' has the same meaning as in listName().  
493
     */
494
    inventoryName(lst) { return listName(lst); }
495
496
    /* 
497
     *   The state name to use in listings of items being worn.  By
498
     *   default, we just use the base name.  'lst' has the same meaning as
499
     *   in listName().  
500
     */
501
    wornName(lst) { return listName(lst); }
502
503
    /* the relative listing order */
504
    listingOrder = 0
505
506
    /*
507
     *   Match the name of an object in this state.  'obj' is the object
508
     *   to be matched; 'origTokens' and 'adjustedTokens' have the same
509
     *   meanings they do for Thing.matchName; and 'states' is a list of
510
     *   all of the possible states the object can assume.
511
     *   
512
     *   Implementation of this is always language-specific.  In most
513
     *   cases, this should do something along the lines of checking for
514
     *   the presence (in the token list) of words that only apply to
515
     *   other states, rejecting the match if any such words are found.
516
     *   For example, the ThingState object representing the unlit state
517
     *   of a light source might check for the presence of 'lit' as an
518
     *   adjective, and reject the object if it's found.  
519
     */
520
    matchName(obj, origTokens, adjustedTokens, states)
521
    {
522
        /* by default, simply match the object */
523
        return obj;
524
    }
525
;
526
527
/* ------------------------------------------------------------------------ */
528
/*
529
 *   Object with vocabulary.  This is the base class for any object that
530
 *   can define vocabulary words.  
531
 */
532
class VocabObject: object
533
    /*
534
     *   Match a name as used in a noun phrase in a player's command to
535
     *   this object.  The parser calls this routine to test this object
536
     *   for a match to a noun phrase when all of the following conditions
537
     *   are true:
538
     *   
539
     *   - this object is in scope;
540
     *   
541
     *   - our vocabulary matches the noun phrase, which means that ALL of
542
     *   the words in the player's noun phrase are associated with this
543
     *   object with the corresponding parts of speech.  Note the special
544
     *   wildcard vocabulary words: '#' as an adjective matches any number
545
     *   used as an adjective; '*' as a noun matches any word used as any
546
     *   part of speech.
547
     *   
548
     *   'origTokens' is the list of the original input words making up
549
     *   the noun phrase, in canonical tokenizer format.  Each element of
550
     *   this list is a sublist representing one token.
551
     *   
552
     *   'adjustedTokens' is the "adjusted" token list, which provides
553
     *   more information on how the parser is analyzing the phrase but
554
     *   may not contain the exact original tokens of the command.  In the
555
     *   adjusted list, the tokens are represented by pairs of values in
556
     *   the list: the first value of each pair is a string giving the
557
     *   adjusted token text, and the second value of the pair is a
558
     *   property ID giving the part of speech of the parser's
559
     *   interpretation of the phrase.  For example, if the noun phrase is
560
     *   "red book", the list might look like ['red', &adjective, 'book',
561
     *   &noun].
562
     *   
563
     *   The adjusted token list in some cases contains different tokens
564
     *   than the original input.  For example, when the command contains
565
     *   a spelled-out number, the parser will translate the spelled-out
566
     *   number to a numeral format and provide the numeral string in the
567
     *   adjusted token list: 'a hundred and thirty-four' will become
568
     *   '134' in the adjusted token list.
569
     *   
570
     *   If this object does not match the noun phrase, this routine
571
     *   returns nil.  If the object is a match, the routine returns
572
     *   'self'.  The routine can also return a different object, or even
573
     *   a list of objects - in this case, the parser will consider the
574
     *   noun phrase to have matched the returned object or objects rather
575
     *   than this original match.
576
     *   
577
     *   Note that it isn't necessary to check that the input tokens match
578
     *   our defined vocabulary words, because the parser will already
579
     *   have done that for us.  This routine is only called after the
580
     *   parser has already determined that all of the noun phrase's words
581
     *   match ours.  
582
     *   
583
     *   By default, we do two things.  First, we check to see if ALL of
584
     *   our tokens are "weak" tokens, and if they are, we indicate that
585
     *   we do NOT match the phrase.  Second, if we pass the "weak token"
586
     *   test, we'll invoke the common handling in matchNameCommon(), and
587
     *   return the result.
588
     *   
589
     *   In most cases, games will want to override matchNameCommon()
590
     *   instead of this routine.  matchNameCommon() is the common handler
591
     *   for both normal and disambiguation-reply matching, so overriding
592
     *   that one routine will take care of both kinds of matching.  Games
593
     *   will only need to override matchName() separately in cases where
594
     *   they need to differentiate normal matching and disambiguation
595
     *   matching.  
596
     */
597
    matchName(origTokens, adjustedTokens)
598
    {
599
        /* if we have a weak-token list, check the tokens */
600
    weakTest:
601
        if (weakTokens != nil)
602
        {
603
            local sc = languageGlobals.dictComparator;
604
                
605
            /* check to see if all of our tokens are "weak" */
606
            for (local i = 1, local len = adjustedTokens.length() ;
607
                 i <= len ; i += 2)
608
            {
609
                /* get the current token and its type */
610
                local tok = adjustedTokens[i];
611
                local typ = adjustedTokens[i+1];
612
613
                /* 
614
                 *   if this is a miscWord token, skip it - these aren't
615
                 *   vocabulary matches, so we won't find them in either
616
                 *   our weak or strong vocabulary lists 
617
                 */
618
                if (typ == &miscWord)
619
                    continue;
620
621
                /* if this token isn't in our weak list, it's not weak */
622
                if (weakTokens.indexWhich({x: sc.matchValues(tok, x) != 0})
623
                    == nil)
624
                {
625
                    /* 
626
                     *   It's not in the weak list, so this isn't a weak
627
                     *   token; therefore the phrase isn't weak, so we do
628
                     *   not wish to veto the match.  There's no need to
629
                     *   keep looking; simply break out of the weak test
630
                     *   entirely, since we've now passed. 
631
                     */
632
                    break weakTest;
633
                }
634
            }
635
636
            /* 
637
             *   If we get here, it means we got through the loop without
638
             *   finding any non-weak tokens.  This means the entire
639
             *   phrase is weak, which means that we don't match it.
640
             *   Return nil to indicate that this is not a match.  
641
             */
642
            return nil;
643
         }
644
645
        /* invoke the common handling */
646
        return matchNameCommon(origTokens, adjustedTokens);
647
    }
648
649
    /*
650
     *   Match a name in a disambiguation response.  This is similar to
651
     *   matchName(), but is called for each object in an ambiguous object
652
     *   list for which a disambiguation response was provided.  As with
653
     *   matchName(), we only call this routine for objects that match the
654
     *   dictionary vocabulary.
655
     *   
656
     *   This routine is separate from matchName() because a
657
     *   disambiguation response usually only contains a partial name.
658
     *   For example, the exchange might go something like this:
659
     *   
660
     *   >take box
661
     *.  Which box do you mean, the cardboard box, or the wood box?
662
     *   >cardboard
663
     *   
664
     *   Note that it is not safe to assume that the disambiguation
665
     *   response can be prepended to the original noun phrase to make a
666
     *   complete noun phrase; if this were safe, we'd simply concatenate
667
     *   the two strings and call matchName().  This would work for the
668
     *   example above, since we'd get "cardboard box" as the new noun
669
     *   phrase, but it wouldn't work in general.  Consider these examples:
670
     *   
671
     *   >open post office box
672
     *.  Which post office box do you mean, box 100, box 101, or box 102?
673
     *   
674
     *   >take jewel
675
     *.  Which jewel do you mean, the emerald, or the diamond?
676
     *   
677
     *   There's no general way of assembling the disambiguation response
678
     *   and the original noun phrase together into a new noun phrase, so
679
     *   rather than trying to use matchName() for both purposes, we
680
     *   simply use a separate routine to match the disambiguation name.
681
     *   
682
     *   Note that, when this routine is called, this object will have
683
     *   been previously matched with matchName(), so there is no question
684
     *   that this object matches the original noun phrase.  The only
685
     *   question is whether or not this object matches the response to
686
     *   the "which one do you mean" question.
687
     *   
688
     *   The return value has the same meaning as for matchName().
689
     *   
690
     *   By default, we simply invoke the common handler.  Note that games
691
     *   will usually want to override matchNameCommon() instead of this
692
     *   routine, since matchNameCommon() provides common handling for the
693
     *   main match and disambiguation match cases.  Games should only
694
     *   override this routine when they need to do something different
695
     *   for normal vs disambiguation matching.    
696
     */
697
    matchNameDisambig(origTokens, adjustedTokens)
698
    {
699
        /* by default, use the same common processing */
700
        return matchNameCommon(origTokens, adjustedTokens);
701
    }
702
703
    /*
704
     *   Common handling for the main matchName() and the disambiguation
705
     *   handler matchNameDisambig().  By default, we'll check with our
706
     *   state object if we have a state object; if not, we'll simply
707
     *   return 'self' to indicate that we do indeed match the given
708
     *   tokens.
709
     *   
710
     *   In most cases, when a game wishes to customize name matching for
711
     *   an object, it can simply override this routine.  This routine
712
     *   provides common handling for matchName() and matchNameDisambig(),
713
     *   so overriding this routine will take care of both the normal and
714
     *   disambiguation matching cases.  In cases where a game needs to
715
     *   customize only normal matching or only disambiguation matching,
716
     *   it can override one of those other routines instead.  
717
     */
718
    matchNameCommon(origTokens, adjustedTokens)
719
    {
720
        local st;
721
        
722
        /* 
723
         *   if we have a state, ask our state object to check for words
724
         *   applying only to other states 
725
         */
726
        if ((st = getState()) != nil)
727
            return st.matchName(self, origTokens, adjustedTokens, allStates);
728
729
        /* by default, accept the parser's determination that we match */
730
        return self;
731
    }
732
733
    /*
734
     *   Plural resolution order.  When a command contains a plural noun
735
     *   phrase, we'll sort the items that match the plural phrase in
736
     *   ascending order of this property value.
737
     *   
738
     *   In most cases, the plural resolution order doesn't matter.  Once
739
     *   in a while, though, a set of objects will be named as "first,"
740
     *   "second," "third," and so on; in these cases, it's nice to have
741
     *   the order of resolution match the nominal ordering.
742
     *   
743
     *   Note that the sorting order only applies within the matches for a
744
     *   particular plural phrase, not globally throughout the entire list
745
     *   of objects in a command.  For example, if the player types TAKE
746
     *   BOXES AND BOOKS, we'll sort the boxes in one group, and then we'll
747
     *   sort the books as a separate group - but all of the boxes will
748
     *   come before any of the books, regardless of the plural orders.
749
     *   
750
     *   >take books and boxes
751
     *.  first book: Taken.
752
     *.  second book: Taken.
753
     *.  third book: Taken.
754
     *.  left box: Taken.
755
     *.  middle box: Taken.
756
     *.  right box: Taken.
757
     */
758
    pluralOrder = 100
759
760
    /*
761
     *   Disambiguation prompt order.  When we interactively prompt for
762
     *   help resolving an ambiguous noun phrase, we'll put the list of
763
     *   ambiguous matches in ascending order of this property value.
764
     *   
765
     *   In most cases, the prompt order doesn't matter, so most objects
766
     *   won't have to override the default setting.  Sometimes, though, a
767
     *   set of objects will be identified in the game as "first",
768
     *   "second", "third", etc., and in these cases it's desirable to have
769
     *   the objects presented in the same order as the names indicate:
770
     *   
771
     *   Which door do you mean, the first door, the second door, or the
772
     *   third door?
773
     *   
774
     *   By default, we use the same value as our pluralOrder, since the
775
     *   plural order has essentially the same purpose.  
776
     */
777
    disambigPromptOrder = (pluralOrder)
778
779
    /*
780
     *   Intrinsic parsing likelihood.  During disambiguation, if the
781
     *   parser finds two objects with equivalent logicalness (as
782
     *   determined by the 'verify' process for the particular Action being
783
     *   performed), it will pick the one with the higher intrinsic
784
     *   likelihood value.  The default value is zero, which makes all
785
     *   objects equivalent by default.  Set a higher value to make the
786
     *   parser prefer this object in cases of ambiguity.  
787
     */
788
    vocabLikelihood = 0
789
790
    /*
791
     *   Filter an ambiguous noun phrase resolution list.  The parser calls
792
     *   this method for each object that matches an ambiguous noun phrase
793
     *   or an ALL phrase, to allow the object to modify the resolution
794
     *   list.  This method allows the object to act globally on the entire
795
     *   list, so that the filtering can be sensitive to the presence or
796
     *   absence in the list of other objects, and can affect the presence
797
     *   of other objects.
798
     *   
799
     *   'lst' is a list of ResolveInfo objects describing the tentative
800
     *   resolution of the noun phrase.  'action' is the Action object
801
     *   representing the command.  'whichObj' is the object role
802
     *   identifier of the object being resolved (DirectObject,
803
     *   IndirectObject, etc).  'np' is the noun phrase production that
804
     *   we're resolving; this is usually a subclass of NounPhraseProd.
805
     *   'requiredNum' is the number of objects required, when an exact
806
     *   quantity is specified; this is nil in cases where the quantity is
807
     *   unspecified, as in 'all' or an unquantified plural ("take coins").
808
     *   
809
     *   The result is a new list of ResolveInfo objects, which need not
810
     *   contain any of the objects of the original list, and can add new
811
     *   objects not in the original list, as desired.
812
     *   
813
     *   By default, we simply return the original list.  
814
     */
815
    filterResolveList(lst, action, whichObj, np, requiredNum)
816
    {
817
        /* return the original list unchanged */
818
        return lst;
819
    }
820
821
    /*
822
     *   Expand a pronoun list.  This is essentially complementary to
823
     *   filterResolveList: the function is to "unfilter" a pronoun binding
824
     *   that contains this object so that it restores any objects that
825
     *   would have been filtered out by filterResolveList from the
826
     *   original noun phrase binding.
827
     *   
828
     *   This routine is called whenever the parser is called upon to
829
     *   resolve a pronoun ("TAKE THEM").  This routine is called for each
830
     *   object in the "raw" pronoun binding, which is simply the list of
831
     *   objects that was stored by the previous command as the antecedent
832
     *   for the pronoun.  After this routine has been called for each
833
     *   object in the raw pronoun binding, the final list will be passed
834
     *   through filterResolveList().
835
     *   
836
     *   'lst' is the raw pronoun binding so far, which might reflect
837
     *   changes made by this method called on previous objects in the
838
     *   list.  'typ' is the pronoun type (PronounIt, PronounThem, etc)
839
     *   describing the pronoun phrase being resolved.  The return value is
840
     *   the new pronoun binding list; if this routine doesn't need to make
841
     *   any changes, it should simply return 'lst'.
842
     *   
843
     *   In some cases, filterResolveList chooses which of two or more
844
     *   possible ways to bind a noun phrase, with the binding dependent
845
     *   upon other conditions, such as the current action.  In these
846
     *   cases, it's often desirable for a subsequent pronoun reference to
847
     *   make the same decision again, choosing from the full set of
848
     *   possible bindings.  This routine facilitates that by letting the
849
     *   object put back objects that were filtered out, so that the
850
     *   filtering can once again run on the full set of possible bindings
851
     *   for the pronoun reference.
852
     *   
853
     *   This base implementation just returns the original list unchanged.
854
     *   See CollectiveGroup for an override that uses this.  
855
     */
856
    expandPronounList(typ, lst) { return lst; }
857
858
    /*
859
     *   Our list of "weak" tokens.  This is a token that is acceptable in
860
     *   our vocabulary, but which we can only use in combination with one
861
     *   or more "strong" tokens.  (A token is strong if it's not weak, so
862
     *   we need only keep track of one or the other kind.  Weak tokens
863
     *   are much less common than strong tokens, so it takes a lot less
864
     *   space if we store the weak ones instead of the strong ones.)
865
     *   
866
     *   The purpose of weak tokens is to allow players to use more words
867
     *   to refer to some objects without creating ambiguity.  For
868
     *   example, if we have a house, and a front door of the house, we
869
     *   might want to allow the player to call the front door "front door
870
     *   of house."  If we just defined the door's vocabulary thus,
871
     *   though, we'd create ambiguity if the player tried to refer to
872
     *   "house," even though this obviously doesn't create any ambiguity
873
     *   to a human reader.  Weak tokens fix the problem: we define
874
     *   "house" as a weak token for the front door, which allows the
875
     *   player to refer to the front door as "front door of house", but
876
     *   prevents the front door from matching just "house".
877
     *   
878
     *   By default, this is nil to indicate that we don't have any weak
879
     *   tokens to check.  If the object has weak tokens, this should be
880
     *   set to a list of strings giving the weak tokens.  
881
     */
882
    weakTokens = nil
883
884
    /*
885
     *   By default, every object can be used as the resolution of a
886
     *   possessive qualifier phrase (e.g., "bob" in "bob's book").  If
887
     *   this property is set to nil for an object, that object can never
888
     *   be used as a possessive.  Note that has nothing to do with
889
     *   establishing ownership relationships between objects; it controls
890
     *   only the resolution of possessive phrases during parsing to
891
     *   concrete game objects.  
892
     */
893
    canResolvePossessive = true
894
895
    /*
896
     *   Throw an appropriate parser error when this object is used in a
897
     *   player command as a possessive qualifier (such as when 'self' is
898
     *   the "bob" in "take bob's key"), and we don't own anything matching
899
     *   the object name that we qualify.  This is only called when 'self'
900
     *   is in scope.  By default, we throw the standard parser error ("Bob
901
     *   doesn't appear to have any such thing").  'txt' is the
902
     *   lower-cased, HTMLified text that of the qualified object name
903
     *   ("key" in "bob's key").  
904
     */
905
    throwNoMatchForPossessive(txt)
906
        { throw new ParseFailureException(&noMatchForPossessive, self, txt); }
907
908
    /* 
909
     *   Throw an appropriate parser error when this object is used in a
910
     *   player command to locationally qualify another object (such as
911
     *   when we're the box in "examine the key in the box"), and there's
912
     *   no object among our contents with the given name.  By default, we
913
     *   throw the standard parser error ("You see no key in the box").  
914
     */
915
    throwNoMatchForLocation(txt)
916
        { throw new ParseFailureException(&noMatchForLocation, self, txt); }
917
918
    /*
919
     *   Throw an appropriate parser error when this object is used in a
920
     *   player command to locationally qualify "all" (such as when we're
921
     *   the box in "examine everything in the box"), and we have no
922
     *   contents.  By default, we throw the standard parser error ("You
923
     *   see nothing unusual in the box"). 
924
     */
925
    throwNothingInLocation()
926
        { throw new ParseFailureException(&nothingInLocation, self); }
927
928
    /*
929
     *   Get a list of the other "facets" of this object.  A facet is
930
     *   another program object that to the player looks like the same or
931
     *   part of the same physical object.  For example, it's often
932
     *   convenient to represent a door using two game objects - one for
933
     *   each side - but the two really represent the same door from the
934
     *   player's perspective.
935
     *   
936
     *   The parser uses an object's facets to resolve a pronoun when the
937
     *   original antecedent goes out of scope.  In our door example, if
938
     *   we refer to the door, then walk through it to the other side,
939
     *   then refer to 'it', the parser will realize from the facet
940
     *   relationship that 'it' now refers to the other side of the door.  
941
     */
942
    getFacets() { return []; }
943
944
    /*
945
     *   Ownership: a vocab-object can be marked as owned by a given Thing.
946
     *   This allows command input to refer to the owned object using
947
     *   possessive syntax (such as, in English, "x's y").
948
     *   
949
     *   This method returns true if 'self' is owned by 'obj'.  The parser
950
     *   generally tests for ownership in this direction, as opposed to
951
     *   asking for obj's owner, because a given object might have multiple
952
     *   owners, and might not be able to enumerate them all (or, at least,
953
     *   might not be able to enumerate them efficiently).  It's usually
954
     *   efficient to determine whether a given object qualifies as an
955
     *   owner, and from the parser's persepctive that's the question
956
     *   anyway, since it wants to determine if the "x" in "x's y"
957
     *   qualifies as my owner.
958
     *   
959
     *   By default, we simply return true if 'obj' matches our 'owner'
960
     *   property (and is not nil).  
961
     */
962
    isOwnedBy(obj) { return owner != nil && owner == obj; }
963
964
    /*
965
     *   Get our nominal owner.  This is the owner that we report for this
966
     *   object if we're asked to distinguish this object from another
967
     *   object in a disambiguation prompt.  The nominal owner isn't
968
     *   necessarily the only owner.  Note that if getNominalOwner()
969
     *   returns a non-nil value, isOwnedBy(getNominalOwner()) should
970
     *   always return true.
971
     *   
972
     *   By default, we'll simply return our 'owner' property.  
973
     */
974
    getNominalOwner() { return owner; }
975
976
    /* our explicit owner, if any */
977
    owner = nil
978
;
979
980
/* ------------------------------------------------------------------------ */
981
/*
982
 *   Find the best facet from the given list of facets, from the
983
 *   perspective of the given actor.  We'll find the facet that has the
984
 *   best visibility, or, visibilities being equal, the best touchability. 
985
 */
986
findBestFacet(actor, lst)
987
{
988
    local infoList;
989
    local best;
990
991
    /* 
992
     *   if the list has no elements, there's no result; if it has only one
993
     *   element, it's obviously the best one 
994
     */
995
    if (lst.length() == 0)
996
        return nil;
997
    if (lst.length() == 1)
998
        return lst[1];
999
    
1000
    /* make a list of the visibilities of the given facets */
1001
    infoList = lst.mapAll({x: new SightTouchInfo(actor, x)});
1002
1003
    /* scan the list and find the entry with the best results */
1004
    best = nil;
1005
    foreach (local cur in infoList)
1006
        best = SightTouchInfo.selectBetter(best, cur);
1007
1008
    /* return the best result */
1009
    return best.obj_;
1010
}
1011
1012
/*
1013
 *   A small data structure class recording SenseInfo objects for sight and
1014
 *   touch.  We use this to pick the best facet from a list of facets.  
1015
 */
1016
class SightTouchInfo: object
1017
    construct(actor, obj)
1018
    {
1019
        /* remember our object */
1020
        obj_ = obj;
1021
        
1022
        /* get the visual SenseInfo in the actor's best sight-like sense */
1023
        visInfo = actor.bestVisualInfo(obj);
1024
1025
        /* get the 'touch' SenseInfo for the object */
1026
        touchInfo = actor.senseObj(touch, obj);
1027
    }
1028
1029
    /* the object we're associated with */
1030
    obj_ = nil
1031
1032
    /* our SenseInfo objects for sight and touch */
1033
    visInfo = nil
1034
    touchInfo = nil
1035
1036
    /*
1037
     *   Class method: select the "better" of two SightTouchInfo's.
1038
     *   Returns the one with the more transparent visual status, or,
1039
     *   visual transparencies being equal, the one with the more
1040
     *   transparent touch status. 
1041
     */
1042
    selectBetter(a, b)
1043
    {
1044
        local d;
1045
        
1046
        /* if one or the other is nil, return the non-nil one */
1047
        if (a == nil)
1048
            return b;
1049
        if (b == nil)
1050
            return a;
1051
1052
        /* if the visual transparencies differ, compare visually */
1053
        d = a.visInfo.compareTransTo(b.visInfo);
1054
        if (d > 0)
1055
            return a;
1056
        else if (d < 0)
1057
            return b;
1058
        else
1059
        {
1060
            /* the visual status it the same, so compare by touch */
1061
            d = a.touchInfo.compareTransTo(b.touchInfo);
1062
            if (d >= 0)
1063
                return a;
1064
            else
1065
                return b;
1066
        }
1067
    }
1068
;
1069
1070
/* ------------------------------------------------------------------------ */
1071
/*
1072
 *   Thing: the basic class for game objects.  An object of this class
1073
 *   represents a physical object in the simulation.
1074
 */
1075
class Thing: VocabObject
1076
    /* 
1077
     *   on constructing a new Thing, initialize it as we would a
1078
     *   statically instantiated Thing 
1079
     */
1080
    construct()
1081
    {
1082
        /* 
1083
         *   If we haven't already been through here for this object, do
1084
         *   the static initialization.  (Because many library classes and
1085
         *   game objects inherit from multiple Thing subclasses, we'll
1086
         *   sometimes inherit this constructor multiple times in the
1087
         *   course of creating a single new object.  The set-up work we do
1088
         *   isn't meant to be repeated and can create internal
1089
         *   inconsistencies if it is.) 
1090
         */
1091
        if (!isThingConstructed)
1092
        {
1093
            /* inherit base class construction */
1094
            inherited();
1095
1096
            /* initialize the Thing properties */
1097
            initializeThing();
1098
1099
            /* note that we've been through this constructor now */
1100
            isThingConstructed = true;
1101
        }
1102
    }
1103
1104
    /* 
1105
     *   Have we been through Thing.construct() yet for this object?  Note
1106
     *   that this will only be set for dynamically created instances
1107
     *   (i.e., objects created with 'new'). 
1108
     */
1109
    isThingConstructed = nil
1110
1111
    /*
1112
     *   My global parameter name.  This is a name that can be used in
1113
     *   {xxx} parameters in messages to refer directly to this object.
1114
     *   This is nil by default, which means we have no global parameter
1115
     *   name.  Define this to a single-quoted string to set a global
1116
     *   parameter name.
1117
     *   
1118
     *   Global parameter names can be especially useful for objects whose
1119
     *   names change in the course of the game, such as actors who are
1120
     *   known by one name until introduced, after which they're known by
1121
     *   another name.  It's a little nicer to write "{He/the bob}" than
1122
     *   "<<bob.theName>>".  We can do this with a global parameter name,
1123
     *   because it allows us to use {bob} as a message parameter, even
1124
     *   when the actor isn't involved directly in any command.
1125
     *   
1126
     *   Note that these parameter names are global, so no two objects are
1127
     *   allowed to have the same name.  These names are also subordinate
1128
     *   to the parameter names in the current Action, so names that the
1129
     *   actions define, such as 'dobj' and 'actor', should usually be
1130
     *   avoided.  
1131
     */
1132
    globalParamName = nil
1133
1134
    /* 
1135
     *   Set the global parameter name dynamically.  If you need to add a
1136
     *   new global parameter name at run-time, call this rather than
1137
     *   setting the property directly, to ensure that the name is added to
1138
     *   the message builder's name table.  You can also use this to delete
1139
     *   an object's global parameter name, by passing nil for the new
1140
     *   name.
1141
     *   
1142
     *   (You only need to use this method if you want to add or change a
1143
     *   name dynamically at run-time, because the library automatically
1144
     *   initializes the table for objects with globalParamName settings
1145
     *   defined at compile-time.)  
1146
     */
1147
    setGlobalParamName(name)
1148
    {
1149
        /* if we already had a name in the table, remove our old name */
1150
        if (globalParamName != nil
1151
            && langMessageBuilder.nameTable_[globalParamName] == self)
1152
            langMessageBuilder.nameTable_.removeElement(globalParamName);
1153
1154
        /* remember our name locally */
1155
        globalParamName = name;
1156
1157
        /* add the name to the message builder's table */
1158
        if (name != nil)
1159
            langMessageBuilder.nameTable_[name] = self;
1160
    }
1161
1162
    /*
1163
     *   "Special" description.  This is the generic place to put a
1164
     *   description of the object that should appear in the containing
1165
     *   room's full description.  If the object defines a special
1166
     *   description, the object is NOT listed in the basic contents list
1167
     *   of the room, because listing it with the contents would be
1168
     *   redundant with the special description.
1169
     *   
1170
     *   By default, we have no special description.  If a special
1171
     *   description is desired, define this to a double-quoted string
1172
     *   containing the special description, or to a method that displays
1173
     *   the special description.  
1174
     */
1175
    specialDesc = nil
1176
1177
    /*
1178
     *   The special descriptions to use under obscured and distant
1179
     *   viewing conditions.  By default, these simply display the normal
1180
     *   special description, but these can be overridden if desired to
1181
     *   show different messages under these viewing conditions.
1182
     *   
1183
     *   Note that if you override these alternative special descriptions,
1184
     *   you MUST also provide a base specialDesc.  The library figures
1185
     *   out whether or not to show any sort of specialDesc first, based
1186
     *   on the presence of a non-nil specialDesc; only then does the
1187
     *   library figure out which particular variation to use.
1188
     *   
1189
     *   Note that remoteSpecialDesc takes precedence over these methods.
1190
     *   That is, when 'self' is in a separate top-level room from the
1191
     *   point-of-view actor, we'll use remoteSpecialDesc to generate our
1192
     *   description, even if the sense path to 'self' is distant or
1193
     *   obscured.  
1194
     */
1195
    distantSpecialDesc = (specialDesc)
1196
    obscuredSpecialDesc = (specialDesc)
1197
1198
    /*
1199
     *   The "remote" special description.  This is the special
1200
     *   description that will be used when this object is not in the
1201
     *   point-of-view actor's current top-level room, but is visible in a
1202
     *   connected room.  For example, if two top-level rooms are
1203
     *   connected by a window, so that an actor in one room can see the
1204
     *   objects in the other room, this method will be used to generate
1205
     *   the description of the object when the actor is in the other
1206
     *   room, viewing this object through the window.
1207
     *   
1208
     *   By default, we just use the special description.  It's usually
1209
     *   better to customize this to describe the object from the given
1210
     *   point of view.  'actor' is the point-of-view actor.  
1211
     */
1212
    remoteSpecialDesc(actor) { distantSpecialDesc; }
1213
1214
    /*
1215
     *   List order for the special description.  Whenever there's more
1216
     *   than one object showing a specialDesc at the same time (in a
1217
     *   single room description, for example), we'll use this to order the
1218
     *   specialDesc displays.  We'll display in ascending order of this
1219
     *   value.  By default, we use the same value for everything, so
1220
     *   listing order is arbitrary; when one specialDesc should appear
1221
     *   before or after another, this property can be used to control the
1222
     *   relative ordering.  
1223
     */
1224
    specialDescOrder = 100
1225
1226
    /*
1227
     *   Special description phase.  We list special descriptions for a
1228
     *   room's full description in two phases: one phase before we show
1229
     *   the room's portable contents list, and another phase after we show
1230
     *   the contents list.  This property controls the phase in which we
1231
     *   show this item's special description.  This only affects special
1232
     *   descriptions that are shown with room descriptions; in other
1233
     *   cases, such as "examine" descriptions of objects, all of the
1234
     *   special descriptions are usually shown together.
1235
     *   
1236
     *   By default, we show our special description (if any) before the
1237
     *   room's contents listing, because most special descriptions act
1238
     *   like extensions of the room's main description and thus should be
1239
     *   grouped directly with the room's descriptive text.  Objects with
1240
     *   special descriptions that are meant to indicate more ephemeral
1241
     *   properties of the location can override this to be listed after
1242
     *   the room's portable contents.
1243
     *   
1244
     *   One situation where you usually will want to list a special
1245
     *   description after contents is when the special description applies
1246
     *   to an item that's contained in a portable item.  Since the
1247
     *   container will be listed with the room contents, as it's portable,
1248
     *   we'll usually want the special description of this child item to
1249
     *   show up after the contents listing, so that it shows up after its
1250
     *   container is mentioned.
1251
     *   
1252
     *   Note that the specialDescOrder is secondary to this phase
1253
     *   grouping, because we essentially list special items in two
1254
     *   separate groups.  
1255
     */
1256
    specialDescBeforeContents = true
1257
1258
    /*
1259
     *   Show my special description, given a SenseInfo object for the
1260
     *   visual sense path from the point of view of the description.  
1261
     */
1262
    showSpecialDescWithInfo(info, pov)
1263
    {
1264
        local povActor = getPOVActorDefault(gActor);
1265
        
1266
        /* 
1267
         *   Determine what to show, based on the point-of-view location
1268
         *   and the sense path.  If the point of view isn't in the same
1269
         *   top-level location as 'self', OR the actor isn't the same as
1270
         *   the POV, use the remote special description; otherwise, select
1271
         *   the obscured, distant, or basic special description according
1272
         *   to the transparency of the sense path.  
1273
         */
1274
        if (getOutermostRoom() != povActor.getOutermostRoom()
1275
            || pov != povActor)
1276
        {
1277
            /* 
1278
             *   different top-level rooms, or different actor and POV -
1279
             *   use the remote description 
1280
             */
1281
            showRemoteSpecialDesc(povActor);
1282
        }
1283
        else if (info.trans == obscured)
1284
        {
1285
            /* we're obscured, so show our obscured special description */
1286
            showObscuredSpecialDesc();
1287
        }
1288
        else if (info.trans == distant)
1289
        {
1290
            /* we're at a distance, so use our distant special description */
1291
            showDistantSpecialDesc();
1292
        }
1293
        else if (canDetailsBeSensed(sight, info, pov))
1294
        {
1295
            /* 
1296
             *   we're not obscured or distant, and our details can be
1297
             *   sensed, so show our fully-visible special description 
1298
             */
1299
            showSpecialDesc();
1300
        }
1301
    }
1302
1303
    /*
1304
     *   Show the special description, if we have one.  If we are using
1305
     *   our initial description, we'll show that; otherwise, if we have a
1306
     *   specialDesc property, we'll show that.
1307
     *   
1308
     *   Note that the initial description overrides the specialDesc
1309
     *   property whenever useInitSpecialDesc() returns true.  This allows
1310
     *   an object to have both an initial description that is used until
1311
     *   the object is moved, and a separate special description used
1312
     *   thereafter.  
1313
     */
1314
    showSpecialDesc()
1315
    {
1316
        /* 
1317
         *   if we are to use our initial description, show that;
1318
         *   otherwise, show our special description 
1319
         */
1320
        if (useInitSpecialDesc())
1321
            initSpecialDesc;
1322
        else
1323
            specialDesc;
1324
    }
1325
1326
    /* show the special description under obscured viewing conditions */
1327
    showObscuredSpecialDesc()
1328
    {
1329
        if (useInitSpecialDesc())
1330
            obscuredInitSpecialDesc;
1331
        else
1332
            obscuredSpecialDesc;
1333
    }
1334
1335
    /* show the special description under distant viewing conditions */
1336
    showDistantSpecialDesc()
1337
    {
1338
        if (useInitSpecialDesc())
1339
            distantInitSpecialDesc;
1340
        else
1341
            distantSpecialDesc;
1342
    }
1343
1344
    /* show the remote special description */
1345
    showRemoteSpecialDesc(actor)
1346
    {
1347
        if (useInitSpecialDesc())
1348
            remoteInitSpecialDesc(actor);
1349
        else
1350
            remoteSpecialDesc(actor);
1351
    }
1352
1353
    /*
1354
     *   Determine if we should use a special description.  By default, we
1355
     *   have a special description if we have either a non-nil
1356
     *   specialDesc property, or we have an initial description.  
1357
     */
1358
    useSpecialDesc()
1359
    {
1360
        /* 
1361
         *   if we have a non-nil specialDesc, or we are to use an initial
1362
         *   description, we have a special description 
1363
         */
1364
        return propType(&specialDesc) != TypeNil || useInitSpecialDesc();
1365
    }
1366
1367
    /*
1368
     *   Determine if we should use our special description in the given
1369
     *   room's LOOK AROUND description.  By default, this simply returns
1370
     *   useSpecialDesc().  
1371
     */
1372
    useSpecialDescInRoom(room) { return useSpecialDesc(); }
1373
1374
    /*
1375
     *   Determine if we should use our special description in the given
1376
     *   object's contents listings, for the purposes of "examine <cont>"
1377
     *   or "look in <cont>".  By default, we'll use our special
1378
     *   description for a given container if we'd use our special
1379
     *   description in general, AND we're actually inside the container
1380
     *   being examined.  
1381
     */
1382
    useSpecialDescInContents(cont)
1383
    {
1384
        /* 
1385
         *   if we would otherwise use a special description, and we are
1386
         *   contained within the given object, use our special description
1387
         *   in the container 
1388
         */
1389
        return useSpecialDesc() && self.isIn(cont);
1390
    }
1391
1392
    /*
1393
     *   Show our special description as part of a parent's full
1394
     *   description.  
1395
     */
1396
    showSpecialDescInContentsWithInfo(info, pov, cont)
1397
    {
1398
        local povActor = getPOVActorDefault(gActor);
1399
        
1400
        /* determine what to show, based on the location and sense path */
1401
        if (getOutermostRoom() != povActor.getOutermostRoom()
1402
            || pov != povActor)
1403
            showRemoteSpecialDescInContents(povActor, cont);
1404
        else if (info.trans == obscured)
1405
            showObscuredSpecialDescInContents(povActor, cont);
1406
        else if (info.trans == distant)
1407
            showDistantSpecialDescInContents(povActor, cont);
1408
        else if (canDetailsBeSensed(sight, info, pov))
1409
            showSpecialDescInContents(povActor, cont);
1410
    }
1411
1412
    /* 
1413
     *   Show the special description in contents listings under various
1414
     *   sense conditions.  By default, we'll use the corresponding special
1415
     *   descriptions for full room descriptions.  These can be overridden
1416
     *   to show special versions of the description when we're examining
1417
     *   particular containers, if desired.  'actor' is the actor doing the
1418
     *   looking.  
1419
     */
1420
    showSpecialDescInContents(actor, cont)
1421
        { showSpecialDesc(); }
1422
    showObscuredSpecialDescInContents(actor, cont)
1423
        { showObscuredSpecialDesc(); }
1424
    showDistantSpecialDescInContents(actor, cont)
1425
        { showDistantSpecialDesc(); }
1426
    showRemoteSpecialDescInContents(actor, cont)
1427
        { showRemoteSpecialDesc(actor); }
1428
1429
    /*
1430
     *   If we define a non-nil initSpecialDesc, this property will be
1431
     *   called to describe the object in room listings as long as the
1432
     *   object is in its "initial" state (as determined by isInInitState:
1433
     *   this is usually true until the object is first moved to a new
1434
     *   location).  By default, objects don't have initial descriptions.
1435
     *   
1436
     *   If this is non-nil, and the object is portable, this will be used
1437
     *   (as long as the object is in its initial state) instead of
1438
     *   showing the object in an ordinary room-contents listing.  This
1439
     *   can be used to give the object a special mention in its initial
1440
     *   location in the game, rather than letting the ordinary
1441
     *   room-contents lister lump it in with all of the other portable
1442
     *   object lying around.  
1443
     */
1444
    initSpecialDesc = nil
1445
1446
    /*
1447
     *   The initial descriptions to use under obscured and distant
1448
     *   viewing conditions.  By default, these simply show the plain
1449
     *   initSpecialDesc; these can be overridden, if desired, to show
1450
     *   alternative messages when viewing conditions are less than ideal.
1451
     *   
1452
     *   Note that in order for one of these alternative initial
1453
     *   descriptions to be shown, the regular initSpecialDesc MUST be
1454
     *   defined, even if it's never actually used.  We make the decision
1455
     *   to display these other descriptions based on the existence of a
1456
     *   non-nil initSpecialDesc, so always define initSpecialDesc
1457
     *   whenever these are defined.  
1458
     */
1459
    obscuredInitSpecialDesc = (initSpecialDesc)
1460
    distantInitSpecialDesc = (initSpecialDesc)
1461
1462
    /* the initial remote special description */
1463
    remoteInitSpecialDesc(actor) { distantInitSpecialDesc; }
1464
1465
    /*
1466
     *   If we define a non-nil initDesc, and the object is in its initial
1467
     *   description state (as indicated by isInInitState), then we'll use
1468
     *   this property instead of "desc" to describe the object when
1469
     *   examined.  This can be used to customize the description the
1470
     *   player sees in parallel to initSpecialDesc.  
1471
     */
1472
    initDesc = nil
1473
1474
    /*
1475
     *   Am I in my "initial state"?  This is used to determine if we
1476
     *   should show the initial special description (initSpecialDesc) and
1477
     *   initial examine description (initDesc) when describing the
1478
     *   object.  By default, we consider the object to be in its initial
1479
     *   state until the first time it's moved.
1480
     *   
1481
     *   You can override this to achieve other effects.  For example, if
1482
     *   you want to use the initial description only the first time the
1483
     *   object is examined, and then revert to the ordinary description,
1484
     *   you could override this to return (!described).  
1485
     */
1486
    isInInitState = (!moved)
1487
1488
    /*
1489
     *   Determine whether or not I should be mentioned in my containing
1490
     *   room's description (on LOOK AROUND) using my initial special
1491
     *   description (initSpecialDesc).  This returns true if I have an
1492
     *   initial description that isn't nil, and I'm in my initial state.
1493
     *   If this returns nil, the object should be described in room
1494
     *   descriptions using the ordinary generated message (either the
1495
     *   specialDesc, if we have one, or the ordinary mention in the list
1496
     *   of portable room contents).  
1497
     */
1498
    useInitSpecialDesc()
1499
        { return isInInitState && propType(&initSpecialDesc) != TypeNil; }
1500
1501
    /* 
1502
     *   Determine if I should be described on EXAMINE using my initial
1503
     *   examine description (initDesc).  This returns true if I have an
1504
     *   initial examine desription that isn't nil, and I'm in my initial
1505
     *   state.  If this returns nil, we'll show our ordinary description
1506
     *   (given by the 'desc' property).  
1507
     */
1508
    useInitDesc()
1509
        { return isInInitState && propType(&initDesc) != TypeNil; }
1510
1511
1512
    /*
1513
     *   Flag: I've been moved out of my initial location.  Whenever we
1514
     *   move the object to a new location, we'll set this to true.  
1515
     */
1516
    moved = nil
1517
1518
    /*
1519
     *   Flag: I've been seen by the player character.  This is nil by
1520
     *   default; we set this to true whenever we show a room description
1521
     *   from the player character's perspective, and the object is
1522
     *   visible.  The object doesn't actually have to be mentioned in the
1523
     *   room description to be marked as seen - it merely has to be
1524
     *   visible to the player character.
1525
     *   
1526
     *   Note that this is only the DEFAULT 'seen' property, which all
1527
     *   Actor objects use by default.  The ACTUAL property that a given
1528
     *   Actor uses depends on the actor's seenProp, which allows a game to
1529
     *   keep track separately of what each actor has seen by using
1530
     *   different 'seen' properties for different actors.  
1531
     */
1532
    seen = nil
1533
1534
    /*
1535
     *   Flag: suppress the automatic setting of the "seen" status for this
1536
     *   object in room and object descriptions.  Normally, we'll
1537
     *   automatically mark every visible object as seen (by calling
1538
     *   gActor.setHasSeen()) whenever we do a LOOK AROUND.  We'll also
1539
     *   automatically mark as seen every visible object within an object
1540
     *   examined explicitly (such as with EXAMINE, LOOK IN, LOOK ON, or
1541
     *   OPEN).  This property can override this automatic status change:
1542
     *   when this property is true, we will NOT mark this object as seen
1543
     *   in any of these cases.  When this property is true, the game must
1544
     *   explicitly mark the object as seen, if and when desired, by
1545
     *   calling actor.setHasSeen().
1546
     *   
1547
     *   Sometimes, an object is not meant to be immediately obvious.  For
1548
     *   example, a puzzle box might have a hidden button that can't be
1549
     *   seen on casual examination.  In these cases, you can use
1550
     *   suppressAutoSeen to ensure that the object won't be marked as seen
1551
     *   merely by virtue of its being visible at the time of a LOOK AROUND
1552
     *   or EXAMINE command.
1553
     *   
1554
     *   Note that this property does NOT affect the object's actual
1555
     *   visibility or other sensory attributes.  This property merely
1556
     *   controls the automatic setting of the "seen" status of the object.
1557
     */
1558
    suppressAutoSeen = nil
1559
1560
    /*
1561
     *   Flag: I've been desribed.  This is nil by default; we set this to
1562
     *   true whenever the player explicitly examines the object. 
1563
     */
1564
    described = nil
1565
1566
    /*
1567
     *   Mark all visible contents of 'self' as having been seen.
1568
     *   'infoTab' is a LookupTable of sight information, as returned by
1569
     *   visibleInfoTable().  This should be called any time an object is
1570
     *   examined in such a way that its contents should be considered to
1571
     *   have been seen.
1572
     *   
1573
     *   We will NOT mark as seen any objects that have suppressAutoSeen
1574
     *   set to true.  
1575
     */
1576
    setContentsSeenBy(infoTab, actor)
1577
    {
1578
        /* 
1579
         *   run through the table of visible objects, and mark each one
1580
         *   that's contained within 'self' as having been seen 
1581
         */
1582
        infoTab.forEachAssoc(new function(obj, info)
1583
        {
1584
            if (obj.isIn(self) && !obj.suppressAutoSeen)
1585
                actor.setHasSeen(obj);
1586
        });
1587
    }
1588
1589
    /*
1590
     *   Mark everything visible as having been seen.  'infoTab' is a
1591
     *   LookupTable of sight information, as returned by
1592
     *   visibleInfoTable().  We'll mark everything visible in the table
1593
     *   as having been seen by the actor, EXCEPT objects that have
1594
     *   suppressAutoSeen set to true.  
1595
     */
1596
    setAllSeenBy(infoTab, actor)
1597
    {
1598
        /* mark everything as seen, except suppressAutoSeen items */
1599
        infoTab.forEachAssoc(new function(obj, info)
1600
        {
1601
            if (!obj.suppressAutoSeen)
1602
                actor.setHasSeen(obj);
1603
        });
1604
    }
1605
1606
    /*
1607
     *   Note that I've been seen by the given actor, setting the given
1608
     *   "seen" property.  This routine notifies the object that it's just
1609
     *   been observed by the given actor, allowing it to take any special
1610
     *   action it wants to take in such cases.  
1611
     */
1612
    noteSeenBy(actor, prop)
1613
    {
1614
        /* by default, simply set the given "seen" property to true */
1615
        self.(prop) = true;
1616
    }
1617
1618
    /*
1619
     *   Flag: this object is explicitly "known" to actors in the game,
1620
     *   even if it's never been seen.  This allows the object to be
1621
     *   resolved as a topic in ASK ABOUT commands and the like.
1622
     *   Sometimes, actors know about an object even before it's been seen
1623
     *   - they might simply know about it from background knowledge, or
1624
     *   they might hear about it from another character, for example.
1625
     *   
1626
     *   Like the 'seen' property, this is merely the DEFAULT 'known'
1627
     *   property that we use for actors.  Each actor can individually use
1628
     *   a separate property to track its own knowledge if it prefers; it
1629
     *   can do this simply by overriding its isKnownProp property.  
1630
     */
1631
    isKnown = nil
1632
1633
    /*
1634
     *   Hide from 'all' for a given action.  If this returns true, this
1635
     *   object will never be included in 'all' lists for the given action
1636
     *   class.  This should generally be set only for objects that serve
1637
     *   some sort of internal purpose and don't represent physical
1638
     *   objects in the model world.  By default, objects are not hidden
1639
     *   from 'all' lists.
1640
     *   
1641
     *   Note that returning nil doesn't put the object into an 'all'
1642
     *   list.  Rather, it simply *leaves* it in any 'all' list it should
1643
     *   happen to be in.  Each action controls its own selection criteria
1644
     *   for 'all', and different verbs use different criteria.  No matter
1645
     *   how an action chooses its 'all' list, though, an item will always
1646
     *   be excluded if hideFromAll() returns true for the item.  
1647
     */
1648
    hideFromAll(action) { return nil; }
1649
1650
    /*
1651
     *   Hide from defaulting for a given action.  By default, we're
1652
     *   hidden from being used as a default object for a given action if
1653
     *   we're hidden from the action for 'all'. 
1654
     */
1655
    hideFromDefault(action) { return hideFromAll(action); }
1656
    
1657
    /*
1658
     *   Determine if I'm to be listed at all in my room's description,
1659
     *   and in descriptions of objects containing my container.
1660
     *   
1661
     *   Most objects should be listed normally, but some types of objects
1662
     *   should be suppressed from the normal room listing.  For example,
1663
     *   fixed-in-place scenery objects are generally described in the
1664
     *   custom message for the containing room, so these are normally
1665
     *   omitted from the listing of the room's contents.
1666
     *   
1667
     *   By default, we'll return the same thing as isListedInContents -
1668
     *   that is, if this object is to be listed when its *direct*
1669
     *   container is examined, it'll also be listed by default when any
1670
     *   further enclosing container (including the enclosing room) is
1671
     *   described.  Individual objects can override this to use different
1672
     *   rules.
1673
     *   
1674
     *   Why would we want to be able to list an object when examining in
1675
     *   its direct container, but not when examining an enclosing
1676
     *   container, or the enclosing room?  The most common reason is to
1677
     *   control the level of detail, to avoid overloading the broad
1678
     *   description of the room and the main things in it with every
1679
     *   detail of every deeply-nested container.  
1680
     */
1681
    isListed { return isListedInContents; }
1682
1683
    /*
1684
     *   Determine if I'm listed in explicit "examine" and "look in"
1685
     *   descriptions of my direct container.
1686
     *   
1687
     *   By default, we return true as long as we're not using our special
1688
     *   description in this particular context.  Examining or looking in a
1689
     *   container will normally show special message for any contents of
1690
     *   the container, so we don't want to list the items with special
1691
     *   descriptions in the ordinary list as well.  
1692
     */
1693
    isListedInContents { return !useSpecialDescInContents(location); }
1694
1695
    /*
1696
     *   Determine if I'm listed in inventory listings.  By default, we
1697
     *   include every item in an inventory list.
1698
     */
1699
    isListedInInventory { return true; }
1700
1701
    /* 
1702
     *   by default, regular objects are not listed when they arrive
1703
     *   aboard vehicles (only actors are normally listed in this fashion) 
1704
     */
1705
    isListedAboardVehicle = nil
1706
1707
    /*
1708
     *   Determine if I'm listed as being located in the given room part.
1709
     *   We'll first check to make sure the object is nominally contained
1710
     *   in the room part; if not, it's not listed in the room part.  We'll
1711
     *   then ask the room part itself to make the final determination.  
1712
     */
1713
    isListedInRoomPart(part)
1714
    {
1715
        /* 
1716
         *   list me if I'm nominally contained in the room part AND the
1717
         *   room part wants me listed 
1718
         */
1719
        return (isNominallyInRoomPart(part)
1720
                && part.isObjListedInRoomPart(self));
1721
    }
1722
1723
    /*
1724
     *   Determine if we're nominally in the given room part (floor,
1725
     *   ceiling, wall, etc).  This returns true if we *appear* to be
1726
     *   located directly in/on the given room part object.
1727
     *   
1728
     *   In most cases, a portable object might start out with a special
1729
     *   initial room part location, but once moved and then dropped
1730
     *   somewhere, ends up nominally in the nominal drop destination of
1731
     *   the location where it was dropped.  For example, a poster might
1732
     *   start out being nominally attached to a wall, or a light bulb
1733
     *   might be nominally hanging from the ceiling; if these objects are
1734
     *   taken and then dropped somewhere, they'll simply end up on the
1735
     *   floor.
1736
     *   
1737
     *   Our default behavior models this.  If we've never been moved,
1738
     *   we'll indicate that we're in our initial room part location,
1739
     *   given by initNominalRoomPartLocation.  Once we've been moved (or
1740
     *   if our initNominalRoomPartLocation is nil), we'll figure out what
1741
     *   the nominal drop destination is for our current container, and
1742
     *   then see if we appear to be in that nominal drop destination.  
1743
     */
1744
    isNominallyInRoomPart(part)
1745
    {
1746
        /* 
1747
         *   If we're not directly in the apparent container of the given
1748
         *   room part, then we certainly are not even nominally in the
1749
         *   room part.  We only appear to be in a room part when we're
1750
         *   actually in the room directly containing the room part. 
1751
         */
1752
        if (location == nil
1753
            || location.getRoomPartLocation(part) != location)
1754
            return nil;
1755
1756
        /* if we've never been moved, use our initial room part location */
1757
        if (!moved && initNominalRoomPartLocation != nil)
1758
            return (part == initNominalRoomPartLocation);
1759
1760
        /* if we have an explicit special room part location, use that */
1761
        if (specialNominalRoomPartLocation != nil)
1762
            return (part == specialNominalRoomPartLocation);
1763
1764
        /*
1765
         *   We don't seem to have an initial or special room part
1766
         *   location, but there's still one more possibility: if the room
1767
         *   part is the nominal drop destination, then we are by default
1768
         *   nominally in that room part, because that's where we
1769
         *   otherwise end up in the absence of any special room part
1770
         *   location setting.  
1771
         */
1772
        if (location.getNominalDropDestination() == part)
1773
            return true;
1774
1775
        /* we aren't nominally in the given room part */
1776
        return nil;
1777
    }
1778
1779
    /*
1780
     *   Determine if I should show my special description with the
1781
     *   description of the given room part (floor, ceiling, wall, etc).
1782
     *   
1783
     *   By default, we'll include our special description with a room
1784
     *   part's description if either (1) we are using our initial
1785
     *   description, and our initNominalRoomPartLocation is the given
1786
     *   part; or (2) we are using our special description, and our
1787
     *   specialNominalRoomPartLocation is the given part.
1788
     *   
1789
     *   Note that, by default, we do NOT use our special description for
1790
     *   the "default" room part location - that is, for the nominal drop
1791
     *   destination for our containing room, which is where we end up by
1792
     *   default, in the absence of an initial or special room part
1793
     *   location setting.  We don't use our special description in this
1794
     *   default location because special descriptions are most frequently
1795
     *   used to describe an object that is specially situated, and hence
1796
     *   we don't want to assume a default situation.  
1797
     */
1798
    useSpecialDescInRoomPart(part)
1799
    {
1800
        /* if we're not nominally in the part, rule it out */
1801
        if (!isNominallyInRoomPart(part))
1802
            return nil;
1803
1804
        /* 
1805
         *   if we're using our initial description, and we are explicitly
1806
         *   in the given room part initially, use the special description
1807
         *   for the room part 
1808
         */
1809
        if (useInitSpecialDesc() && initNominalRoomPartLocation == part)
1810
            return true;
1811
1812
        /* 
1813
         *   if we're using our special description, and we are explicitly
1814
         *   in the given room part as our special location, use the
1815
         *   special description 
1816
         */
1817
        if (useSpecialDesc() && specialNominalRoomPartLocation == part)
1818
            return true;
1819
1820
        /* do not use the special description in the room part */
1821
        return nil;
1822
    }
1823
1824
    /* 
1825
     *   Our initial room part location.  By default, we set this to nil,
1826
     *   which means that we'll use the nominal drop destination of our
1827
     *   actual initial location when asked.  If desired, this can be set
1828
     *   to another part; for example, if a poster is initially described
1829
     *   as being "on the north wall," this should set to the default north
1830
     *   wall object.  
1831
     */
1832
    initNominalRoomPartLocation = nil
1833
1834
    /*
1835
     *   Our "special" room part location.  By default, we set this to
1836
     *   nil, which means that we'll use the nominal drop destination of
1837
     *   our actual current location when asked.
1838
     *   
1839
     *   This property has a function similar to
1840
     *   initNominalRoomPartLocation, but is used to describe the nominal
1841
     *   room part container of the object has been moved (and even before
1842
     *   it's been moved, if initNominalRoomPartLocation is nil).
1843
     *   
1844
     *   It's rare for an object to have a special room part location
1845
     *   after it's been moved, because most games simply don't provide
1846
     *   commands for things like re-attaching a poster to a wall or
1847
     *   re-hanging a fan from the ceiling.  When it is possible to move
1848
     *   an object to a new special location, though, this property can be
1849
     *   used to flag its new special location.  
1850
     */
1851
    specialNominalRoomPartLocation = nil
1852
1853
    /*
1854
     *   Determine if my contents are to be listed when I'm shown in a
1855
     *   listing (in a room description, inventory list, or a description
1856
     *   of my container).
1857
     *   
1858
     *   Note that this doesn't affect listing my contents when I'm
1859
     *   *directly* examined - use contentsListedInExamine to control that.
1860
     *   
1861
     *   By default, we'll list our contents in these situations if (1) we
1862
     *   have some kind of presence in the description of whatever it is
1863
     *   the player is looking at, either by being listed in the ordinary
1864
     *   way (that is, isListed is true) or in a custom way (via a
1865
     *   specialDesc), and (2) contentsListedInExamine is true.  The
1866
     *   reasoning behind this default is that if the object itself shows
1867
     *   up in the description, then we usually want its contents to be
1868
     *   mentioned as well, but if the object itself isn't mentioned, then
1869
     *   its contents probably shouldn't be, either.  And in any case, if
1870
     *   the contents aren't listed even when we *directly* examine the
1871
     *   object, then we almost certainly don't want its contents mentioned
1872
     *   when we only indirectly mention the object in the course of
1873
     *   describing something enclosing it.  
1874
     */
1875
    contentsListed = (contentsListedInExamine
1876
                      && (isListed || useSpecialDesc()))
1877
1878
    /*
1879
     *   Determine if my contents are to be listed when I'm directly
1880
     *   examined (with an EXAMINE/LOOK AT command).  By default, we
1881
     *   *always* list our contents when we're directly examined.  
1882
     */
1883
    contentsListedInExamine = true
1884
1885
    /*
1886
     *   Determine if my contents are listed separately from my own list
1887
     *   entry.  If this is true, then my contents will be listed in a
1888
     *   separate sentence from my own listing; for example, if 'self' is a
1889
     *   box, we'll be listed like so:
1890
     *   
1891
     *.    You are carrying a box. The box contains a key and a book.
1892
     *   
1893
     *   By default, this is nil, which means that we list our contents
1894
     *   parenthetically after our name:
1895
     *   
1896
     *.     You are carrying a box (which contains a key and a book).
1897
     *   
1898
     *   Using a separate listing is sometimes desirable for an object that
1899
     *   will routinely contain listable objects that in turn have listable
1900
     *   contents of their own, because it can help break up a long listing
1901
     *   that would otherwise use too many nested parentheticals.
1902
     *   
1903
     *   Note that this only applies to "wide" listings; "tall" listings
1904
     *   will show contents in the indented tree format regardless of this
1905
     *   setting.  
1906
     */
1907
    contentsListedSeparately = nil
1908
1909
    /*
1910
     *   The language-dependent part of the library must provide a couple
1911
     *   of basic descriptor methods that we depend upon.  We provide
1912
     *   several descriptor methods that we base on the basic methods.
1913
     *   The basic methods that must be provided by the language extension
1914
     *   are:
1915
     *   
1916
     *   listName - return a string giving the "list name" of the object;
1917
     *   that is, the name that should appear in inventory and room
1918
     *   contents lists.  This is called with the visual sense info set
1919
     *   according to the point of view.
1920
     *   
1921
     *   countName(count) - return a string giving the "counted name" of
1922
     *   the object; that is, the name as it should appear with the given
1923
     *   quantifier.  In English, this might return something like 'one
1924
     *   box' or 'three books'.  This is called with the visual sense info
1925
     *   set according to the point of view.  
1926
     */
1927
1928
    /*
1929
     *   Show this item as part of a list.  'options' is a combination of
1930
     *   ListXxx flags indicating the type of listing.  'infoTab' is a
1931
     *   lookup table of SenseInfo objects giving the sense information
1932
     *   for the point of view.  
1933
     */
1934
    showListItem(options, pov, infoTab)
1935
    {
1936
        /* show the list, using the 'listName' of the state */
1937
        showListItemGen(options, pov, infoTab, &listName);
1938
    }
1939
1940
    /*
1941
     *   General routine to show the item as part of a list.
1942
     *   'stateNameProp' is the property to use in any listing state
1943
     *   object to obtain the state name.  
1944
     */
1945
    showListItemGen(options, pov, infoTab, stateNameProp)
1946
    {
1947
        local info;
1948
        local st;
1949
        local stName;
1950
1951
        /* get my visual information from the point of view */
1952
        info = infoTab[self];
1953
1954
        /* show the item's list name */
1955
        say(withVisualSenseInfo(pov, info, &listName));
1956
1957
        /* 
1958
         *   If we have a list state with a name, show it.  Note that to
1959
         *   obtain the state name, we have to pass a list of the objects
1960
         *   being listed to the stateNameProp method of the state object;
1961
         *   we're the only object we're showing for this particular
1962
         *   display list element, so we simply pass a single-element list
1963
         *   containing 'self'.  
1964
         */
1965
        if ((st = getStateWithInfo(info, pov)) != nil
1966
            && (stName = st.(stateNameProp)([self])) != nil)
1967
        {
1968
            /* we have a state with a name - show it */
1969
            gLibMessages.showListState(stName);
1970
        }
1971
    }
1972
1973
    /*
1974
     *   Show this item as part of a list, grouped with a count of
1975
     *   list-equivalent items.
1976
     */
1977
    showListItemCounted(lst, options, pov, infoTab)
1978
    {
1979
        /* show the item using the 'listName' property of the state */
1980
        showListItemCountedGen(lst, options, pov, infoTab, &listName);
1981
    }
1982
1983
    /* 
1984
     *   General routine to show this item as part of a list, grouped with
1985
     *   a count of list-equivalent items.  'stateNameProp' is the
1986
     *   property of any list state object that we should use to obtain
1987
     *   the name of the listing state.  
1988
     */
1989
    showListItemCountedGen(lst, options, pov, infoTab, stateNameProp)
1990
    {
1991
        local info;
1992
        local stateList;
1993
        
1994
        /* get the visual information from the point of view */
1995
        info = infoTab[self];
1996
        
1997
        /* show our counted name */
1998
        say(countListName(lst.length(), pov, info));
1999
2000
        /* check for list states */
2001
        stateList = new Vector(10);
2002
        foreach (local cur in lst)
2003
        {
2004
            local st;
2005
2006
            /* get this item's sense information from the list */
2007
            info = infoTab[cur];
2008
            
2009
            /* 
2010
             *   if this item has a list state with a name, include it in
2011
             *   our list of states to show 
2012
             */
2013
            if ((st = cur.getStateWithInfo(info, pov)) != nil
2014
                && st.(stateNameProp)(lst) != nil)
2015
            {
2016
                local stInfo;
2017
2018
                /* 
2019
                 *   if this state is already in our list, simply
2020
                 *   increment the count of items in this state;
2021
                 *   otherwise, add the new state to the list 
2022
                 */
2023
                stInfo = stateList.valWhich({x: x.stateObj == st});
2024
                if (stInfo != nil)
2025
                {
2026
                    /* it's already in the list - count the new item */
2027
                    stInfo.addEquivObj(cur);
2028
                }
2029
                else
2030
                {
2031
                    /* it's not in the list - add it */
2032
                    stateList.append(
2033
                        new EquivalentStateInfo(st, cur, stateNameProp));
2034
                }
2035
            }
2036
        }
2037
2038
        /* if the state list is non-empty, show it */
2039
        if (stateList.length() != 0)
2040
        {
2041
            /* put the state list in its desired order */
2042
            stateList.sort(SortAsc, {a, b: (a.stateObj.listingOrder
2043
                                            - b.stateObj.listingOrder)});
2044
                                           
2045
            /*
2046
             *   If there's only one item in the state list, and all of
2047
             *   the objects are in that state, then show the "all in
2048
             *   state" message.  Otherwise, show the list of states with
2049
             *   counts.
2050
             *   
2051
             *   (Note that it's possible to have just one state in the
2052
             *   list without having all of the objects in this state,
2053
             *   because we could have some of the objects in an unnamed
2054
             *   state, in which case they wouldn't contribute to the list
2055
             *   at all.)  
2056
             */
2057
            if (stateList.length() == 1
2058
                && stateList[1].getEquivCount() == lst.length())
2059
            {
2060
                /* everything is in the same state */
2061
                gLibMessages.allInSameListState(stateList[1].getEquivList(),
2062
                                                stateList[1].getName());
2063
            }
2064
            else
2065
            {
2066
                /* list the state items */
2067
                equivalentStateLister.showListAll(stateList.toList(), 0, 0);
2068
            }
2069
        }
2070
    }
2071
2072
    /*
2073
     *   Single-item counted listing description.  This is used to display
2074
     *   an item with a count of equivalent items ("four gold coins").
2075
     *   'info' is the sense information from the current point of view
2076
     *   for 'self', which we take to be representative of the sense
2077
     *   information for all of the equivalent items.  
2078
     */
2079
    countListName(equivCount, pov, info)
2080
    {
2081
        return withVisualSenseInfo(pov, info, &countName, equivCount);
2082
    }
2083
2084
    /*
2085
     *   Show this item as part of an inventory list.  By default, we'll
2086
     *   show the regular list item name.  
2087
     */
2088
    showInventoryItem(options, pov, infoTab)
2089
    {
2090
        /* show the item, using the inventory state name */
2091
        showListItemGen(options, pov, infoTab, &inventoryName);
2092
    }
2093
    showInventoryItemCounted(lst, options, pov, infoTab)
2094
    {
2095
        /* show the item, using the inventory state name */
2096
        showListItemCountedGen(lst, options, pov, infoTab, &inventoryName);
2097
    }
2098
2099
    /*
2100
     *   Show this item as part of a list of items being worn.
2101
     */
2102
    showWornItem(options, pov, infoTab)
2103
    {
2104
        /* show the item, using the worn-listing state name */
2105
        showListItemGen(options, pov, infoTab, &wornName);
2106
    }
2107
    showWornItemCounted(lst, options, pov, infoTab)
2108
    {
2109
        /* show the item, using the worn-listing state name */
2110
        showListItemCountedGen(lst, options, pov, infoTab, &wornName);
2111
    }
2112
2113
    /*
2114
     *   Get the "listing state" of the object, given the visual sense
2115
     *   information for the object from the point of view for which we're
2116
     *   generating the listing.  This returns a ThingState object
2117
     *   describing the object's state for the purposes of listings.  This
2118
     *   should return nil if the object doesn't have varying states for
2119
     *   listings.
2120
     *   
2121
     *   By default, we return a list state if the visual sense path is
2122
     *   transparent or attenuated, or we have large visual scale.  In
2123
     *   other cases, we assume that the details of the object are not
2124
     *   visible under the current sense conditions; since the list state
2125
     *   is normally a detail of the object, we don't return a list state
2126
     *   when the details of the object are not visible.  
2127
     */
2128
    getStateWithInfo(info, pov)
2129
    {
2130
        /* 
2131
         *   if our details can be sensed, return the list state;
2132
         *   otherwise, return nil, since the list state is a detail 
2133
         */
2134
        if (canDetailsBeSensed(sight, info, pov))
2135
            return getState();
2136
        else
2137
            return nil;
2138
    }
2139
2140
    /* 
2141
     *   Get our state - returns a ThingState object describing the state.
2142
     *   By default, we don't have varying states, so we simply return
2143
     *   nil.  
2144
     */
2145
    getState = nil
2146
2147
    /*
2148
     *   Get a list of all of our possible states.  For an object that can
2149
     *   assume varying states as represented by getState, this should
2150
     *   return the list of all possible states that the object can
2151
     *   assume.  
2152
     */
2153
    allStates = []
2154
2155
    /*
2156
     *   The default long description, which is displayed in response to
2157
     *   an explicit player request to examine the object.  We'll use a
2158
     *   generic library message; most objects should override this to
2159
     *   customize the object's desription.
2160
     *   
2161
     *   Note that we show this as a "default descriptive report," because
2162
     *   this default message indicates merely that there's nothing
2163
     *   special to say about the object.  If we generate any additional
2164
     *   description messages, such as status reports ("it's open" or "it
2165
     *   contains a gold key") or special descriptions for things inside,
2166
     *   we clearly *do* have something special to say about the object,
2167
     *   so we'll want to suppress the nothing-special message.  To
2168
     *   accomplish this suppression, all we have to do is report our
2169
     *   generic description as a default descriptive report, and the
2170
     *   transcript will automatically filter it out if there are any
2171
     *   other reports for this same action.
2172
     *   
2173
     *   Note that any time this is overridden by an object with any sort
2174
     *   of actual description, the override should NOT use
2175
     *   defaultDescReport.  Instead, simply set this to display the
2176
     *   descriptive message directly:
2177
     *   
2178
     *   desc = "It's a big green box. " 
2179
     */
2180
    desc { defaultDescReport(&thingDescMsg, self); }
2181
2182
    /*
2183
     *   Our LOOK IN description.  This is shown when we explicitly LOOK IN
2184
     *   the object.  By default, we just report that there's nothing
2185
     *   unusual inside.  
2186
     */
2187
    lookInDesc { mainReport(&nothingInsideMsg); }
2188
2189
    /*
2190
     *   The default "distant" description.  If this is defined for an
2191
     *   object, then we evaluate it to display the description when an
2192
     *   actor explicitly examines this object from a point of view where
2193
     *   we have a "distant" sight path to the object.
2194
     *   
2195
     *   If this property is left undefined for an object, then we'll
2196
     *   describe this object when it's distant in one of two ways.  If
2197
     *   the object has its 'sightSize' property set to 'large', we'll
2198
     *   display the ordinary 'desc', because its large visual size makes
2199
     *   its details visible at a distance.  If the 'sightSize' is
2200
     *   anything else, we'll instead display the default library message
2201
     *   indicating that the object is too far away to see any details.
2202
     *   
2203
     *   To display a specific description when the object is distant, set
2204
     *   this to a double-quoted string, or to a method that displays a
2205
     *   message.  
2206
     */
2207
    // distantDesc = "the distant description"
2208
2209
    /* the default distant description */
2210
    defaultDistantDesc { gLibMessages.distantThingDesc(self); }
2211
2212
    /*
2213
     *   The "obscured" description.  If this is defined for an object,
2214
     *   then we call it to display the description when an actor
2215
     *   explicitly examines this object from a point of view where we
2216
     *   have an "obscured" sight path to the object.
2217
     *   
2218
     *   If this property is left undefined for an object, then we'll
2219
     *   describe this object when it's obscured in one of two ways.  If
2220
     *   the object has its 'sightSize' property set to 'large', we'll
2221
     *   display the ordinary 'desc', because its large visual size makes
2222
     *   its details visible even when the object is obscured.  If the
2223
     *   'sightSize' is anything else, we'll instead display the default
2224
     *   library message indicating that the object is too obscured to see
2225
     *   any details.
2226
     *   
2227
     *   To display a specific description when the object is visually
2228
     *   obscured, override this to a method that displays your message.
2229
     *   'obs' is the object that's obstructing the view - this will be
2230
     *   something on our sense path, such as a dirty window, that the
2231
     *   actor has to look through to see 'self'.  
2232
     */
2233
    // obscuredDesc(obs) { "the obscured description"; }
2234
2235
    /*
2236
     *   The "remote" description.  If this is defined for an object, then
2237
     *   we call it to display the description when an actor explicitly
2238
     *   examines this object from a separate top-level location.  That
2239
     *   is, when the actor's outermost enclosing room is different from
2240
     *   our own outermost enclosing room, we'll use this description.
2241
     *   
2242
     *   If this property is left undefined, then we'll describe this
2243
     *   object when it's distant as though it were in the same room.  So,
2244
     *   we'll select the obscured, distant, or ordinary description,
2245
     *   according to the sense path.  
2246
     */
2247
    // remoteDesc(pov) { "the remote description" }
2248
2249
    /* the default obscured description */
2250
    defaultObscuredDesc(obs) { gLibMessages.obscuredThingDesc(self, obs); }
2251
2252
    /* 
2253
     *   The "sound description," which is the description displayed when
2254
     *   an actor explicitly listens to the object.  This is used when we
2255
     *   have a transparent sense path and no associated "emanation"
2256
     *   object; when we have an associated emanation object, we use its
2257
     *   description instead of this one.  
2258
     */
2259
    soundDesc { defaultDescReport(&thingSoundDescMsg, self); }
2260
2261
    /* distant sound description */
2262
    distantSoundDesc { gLibMessages.distantThingSoundDesc(self); }
2263
2264
    /* obscured sound description */
2265
    obscuredSoundDesc(obs) { gLibMessages.obscuredThingSoundDesc(self, obs); }
2266
2267
    /*
2268
     *   The "smell description," which is the description displayed when
2269
     *   an actor explicitly smells the object.  This is used when we have
2270
     *   a transparent sense path to the object, and we have no
2271
     *   "emanation" object; when we have an associated emanation object,
2272
     *   we use its description instead of this one.  
2273
     */
2274
    smellDesc { defaultDescReport(&thingSmellDescMsg, self); }
2275
2276
    /* distant smell description */
2277
    distantSmellDesc { gLibMessages.distantThingSmellDesc(self); }
2278
2279
    /* obscured smell description */
2280
    obscuredSmellDesc(obs) { gLibMessages.obscuredThingSmellDesc(self, obs); }
2281
2282
    /*
2283
     *   The "taste description," which is the description displayed when
2284
     *   an actor explicitly tastes the object.  Note that, unlike sound
2285
     *   and smell, we don't distinguish levels of transparency or
2286
     *   distance with taste, because tasting an object requires direct
2287
     *   physical contact with it.  
2288
     */
2289
    tasteDesc { gLibMessages.thingTasteDesc(self); }
2290
2291
    /* 
2292
     *   The "feel description," which is the description displayed when
2293
     *   an actor explicitly feels the object.  As with taste, we don't
2294
     *   distinguish transparency or distance. 
2295
     */
2296
    feelDesc { gLibMessages.thingFeelDesc(self); }
2297
2298
    /*
2299
     *   Show the smell/sound description for the object as part of a room
2300
     *   description.  These are displayed when the object is in the room
2301
     *   and it has a presence in the corresponding sense.  By default,
2302
     *   these show nothing.
2303
     *   
2304
     *   In most cases, regular objects don't override these, because most
2305
     *   regular objects have no direct sensory presence of their own.
2306
     *   Instead, a Noise or Odor is created and added to the object's
2307
     *   direct contents, and the Noise or Odor provides the object's
2308
     *   sense presence.  
2309
     */
2310
    smellHereDesc() { }
2311
    soundHereDesc() { }
2312
2313
    /*
2314
     *   "Equivalence" flag.  If this flag is set, then all objects with
2315
     *   the same immediate superclass will be considered interchangeable;
2316
     *   such objects will be listed collectively in messages (so we would
2317
     *   display "five coins" rather than "a coin, a coin, a coin, a coin,
2318
     *   and a coin"), and will be treated as equivalent in resolving noun
2319
     *   phrases to objects in user input.
2320
     *   
2321
     *   By default, this property is nil, since we want most objects to
2322
     *   be treated as unique.  
2323
     */
2324
    isEquivalent = nil
2325
2326
    /*
2327
     *   My Distinguisher list.  This is a list of Distinguisher objects
2328
     *   that can be used to distinguish this object from other objects.
2329
     *   
2330
     *   Distinguishers are listed in order of priority.  The
2331
     *   disambiguation process looks for distinguishers capable of telling
2332
     *   objects apart, starting with the first in the list.  The
2333
     *   BasicDistinguisher is generally first in every object's list,
2334
     *   because any two objects can be told apart if they come from
2335
     *   different classes.
2336
     *   
2337
     *   By default, each object has the "basic" distinguisher, which tells
2338
     *   objects apart on the basis of the "isEquivalent" property and
2339
     *   their superclasses; the ownership distinguisher, which tells
2340
     *   objects apart based on ownership; and the location distinguisher,
2341
     *   which identifies objects by their immediate containers.  
2342
     */
2343
    distinguishers = [basicDistinguisher,
2344
                      ownershipDistinguisher,
2345
                      locationDistinguisher]
2346
2347
    /*
2348
     *   Get the distinguisher to use for printing this object's name in an
2349
     *   action announcement (such as a multi-object, default object, or
2350
     *   vague-match announcement).  We check the global option setting to
2351
     *   see if we should actually use distinguishers for this; if so, we
2352
     *   call getInScopeDistinguisher() to find the correct distinguisher,
2353
     *   otherwise we use the "null" distinguisher, which simply lists
2354
     *   objects by their base names.  
2355
     */
2356
    getAnnouncementDistinguisher()
2357
    {
2358
        return (gameMain.useDistinguishersInAnnouncements
2359
                ? getInScopeDistinguisher()
2360
                : nullDistinguisher);
2361
    }
2362
2363
    /*
2364
     *   Get a distinguisher that differentiates me from all of the other
2365
     *   objects in scope, if possible, or at least from some of the other
2366
     *   objects in scope.  We use this to generate object announcements,
2367
     *   since it will do the best job of distinguishing the object being
2368
     *   announced from other nearby objects.  
2369
     */
2370
    getInScopeDistinguisher()
2371
    {
2372
        local bestDist, bestCnt;
2373
2374
        /* get the list of objects in scope, excluding myself */
2375
        local s = gActor.scopeList() - self;
2376
2377
        /* 
2378
         *   now include only the objects that are basic equivalents -
2379
         *   these are the only ones we care about telling apart from us,
2380
         *   since all of the other objects can be distinguished by their
2381
         *   disambig name 
2382
         */
2383
        s = s.subset({obj: !basicDistinguisher.canDistinguish(self, obj)});
2384
2385
        /* if we're unique by vocabulary, don't bother */
2386
        if (s.length() == 0)
2387
            return basicDistinguisher;
2388
2389
        /* as a last resort, fall back on the basic distinguisher */
2390
        bestDist = basicDistinguisher;
2391
        bestCnt = s.countWhich({obj: bestDist.canDistinguish(self, obj)});
2392
2393
        /* 
2394
         *   run through my distinguisher list looking for the
2395
         *   distinguisher that can tell me apart from the largest number
2396
         *   of my vocab equivalents 
2397
         */
2398
        foreach (local dist in distinguishers)
2399
        {
2400
            /* if this is the default, skip it */
2401
            if (dist == bestDist)
2402
                continue;
2403
2404
            /* check to see how many objects 'dist' can distinguish me from */
2405
            local cnt = s.countWhich({obj: dist.canDistinguish(self, obj)});
2406
2407
            /* 
2408
             *   if it can distinguish me from every other object, use this
2409
             *   one - it uniquely identifies me 
2410
             */
2411
            if (cnt == s.length())
2412
                return dist;
2413
2414
            /* 
2415
             *   that can't distinguish us from everything else here, but
2416
             *   if it's the best so far, remember it; we'll fall back on
2417
             *   the best that we find if we fail to find a perfect
2418
             *   distinguisher 
2419
             */
2420
            if (cnt > bestCnt)
2421
            {
2422
                bestDist = dist;
2423
                bestCnt = cnt;
2424
            }
2425
        }
2426
2427
        /*
2428
         *   We didn't find any distinguishers that can tell me apart from
2429
         *   every other object, so choose the one that can tell me apart
2430
         *   from the most other objects. 
2431
         */
2432
        return bestDist;
2433
    }
2434
2435
    /*
2436
     *   Determine if I'm equivalent, for the purposes of command
2437
     *   vocabulary, to given object.
2438
     *   
2439
     *   We'll run through our list of distinguishers and check with each
2440
     *   one to see if it can tell us apart from the other object.  If we
2441
     *   can find at least one distinguisher that can tell us apart, we're
2442
     *   not equivalent.  If we have no distinguisher that can tell us
2443
     *   apart from the other object, we're equivalent.  
2444
     */
2445
    isVocabEquivalent(obj)
2446
    {
2447
        /* 
2448
         *   Check each distinguisher - if we can find one that can tell
2449
         *   us apart from the other object, we're not equivalent.  If
2450
         *   there are no distinguishers that can tell us apart, we're
2451
         *   equivalent.  
2452
         */
2453
        return (distinguishers.indexWhich(
2454
            {cur: cur.canDistinguish(self, obj)}) == nil);
2455
    }
2456
2457
    /*
2458
     *   My associated "collective group" objects.  A collective group is
2459
     *   an abstract object, not part of the simulation (i.e, not directly
2460
     *   manipulable by characters as a separate object) that can stand in
2461
     *   for an entire group of related objects in some actions.  For
2462
     *   example, we might have a collective "money" object that stands in
2463
     *   for any group of coins and paper bills for "examine" commands, so
2464
     *   that when the player says something like "look at money" or "count
2465
     *   money," we use the single collective money object to handle the
2466
     *   command rather than running the command iteratively on each of the
2467
     *   individual coins and bills present.
2468
     *   
2469
     *   We can have be associated with more than one collective group.
2470
     *   For example, a diamond could be in a "treasure" group as well as a
2471
     *   "jewelry" group.  
2472
     *   
2473
     *   The associated collective objects are normally of class
2474
     *   CollectiveGroup.  By default, if our 'collectiveGroup' property is
2475
     *   not nil, our list consists of that one item; otherwise we just use
2476
     *   an empty list.  
2477
     */
2478
    collectiveGroups = (collectiveGroup != nil ? [collectiveGroup] : [])
2479
2480
    /*
2481
     *   Our collective group.  Note - this property is obsolescent; it's
2482
     *   supported only for compatibility with old code.  New games should
2483
     *   use 'collectiveGroups' instead.  
2484
     */
2485
    collectiveGroup = nil
2486
2487
    /*
2488
     *   Are we associated with the given collective group?  We do if it's
2489
     *   in our collectiveGroups list.  
2490
     */
2491
    hasCollectiveGroup(g) { return collectiveGroups.indexOf(g) != nil; }
2492
2493
    /*
2494
     *   "List Group" objects.  This specifies a list of ListGroup objects
2495
     *   that we use to list this object in object listings, such as
2496
     *   inventory lists and room contents lists.
2497
     *   
2498
     *   An object can be grouped in more than one way.  When multiple
2499
     *   groups are specified here, the order is significant:
2500
     *   
2501
     *   - To the extent two groups entirely overlap, which is to say that
2502
     *   one of the pair entirely contains the other (for example, if
2503
     *   every coin is a kind of money, then the "money" listing group
2504
     *   would contain every object in the "coin" group, plus other
2505
     *   objects as well: the coin group is a subset of the money group),
2506
     *   the groups must be listed from most general to most specific (for
2507
     *   our money/coin example, then, money would come before coin in the
2508
     *   group list).
2509
     *   
2510
     *   - When two groups do not overlap, then the earlier one in our
2511
     *   list is given higher priority.
2512
     *   
2513
     *   By default, we return an empty list.
2514
     */
2515
    listWith = []
2516
2517
    /* 
2518
     *   Get the list group for my special description.  This works like
2519
     *   the ordinary listWith, but is used to group me with other objects
2520
     *   showing special descriptions, rather than in ordinary listings.
2521
     */
2522
    specialDescListWith = []
2523
2524
    /*
2525
     *   Our interior room name.  This is the status line name we display
2526
     *   when an actor is within this object and can't see out to the
2527
     *   enclosing room.  Since we can't rely on the enclosing room's
2528
     *   status line name if we can't see the enclosing room, we must
2529
     *   provide one of our own.
2530
     *   
2531
     *   By default, we'll use our regular name.
2532
     */
2533
    roomName = (name)
2534
2535
    /*
2536
     *   Show our interior description.  We use this to generate the long
2537
     *   "look" description for the room when an actor is within this
2538
     *   object and cannot see the enclosing room.
2539
     *   
2540
     *   Note that this is used ONLY when the actor cannot see the
2541
     *   enclosing room - when the enclosing room is visible (because the
2542
     *   nested room is something like a chair that doesn't enclose the
2543
     *   actor, or can enclose the actor but is open or transparent), then
2544
     *   we'll simply use the description of the enclosing room instead,
2545
     *   adding a note to the short name shown at the start of the room
2546
     *   description indicating that the actor is in the nested room.
2547
     *   
2548
     *   By default, we'll show the appropriate "actor here" description
2549
     *   for the posture, so we'll say something like "You are sitting on
2550
     *   the red chair" or "You are in the phone booth."  Instances can
2551
     *   override this to customize the description with something more
2552
     *   detailed, if desired.  
2553
     */
2554
    roomDesc { gActor.listActorPosture(getPOVActorDefault(gActor)); }
2555
2556
    /*
2557
     *   Show our first-time room description.  This is the description we
2558
     *   show when the actor is seeing our interior description for the
2559
     *   first time.  By default, we just show the ordinary room
2560
     *   description, but this can be overridden to provide a special
2561
     *   description the first time the actor sees the room. 
2562
     */
2563
    roomFirstDesc { roomDesc; }
2564
2565
    /*
2566
     *   Show our remote viewing description.  This is used when we show a
2567
     *   description of a room, via lookAroundWithin, and the actor who's
2568
     *   viewing the room is remote.  Note that the library never does this
2569
     *   itself, since there's no command in the basic library that lets a
2570
     *   player view a remote room.  However, a game might want to generate
2571
     *   such a description to handle a command like LOOK THROUGH KEYHOLE,
2572
     *   so we provide this extra description method for the game's use.
2573
     *   
2574
     *   By default, we simply show the normal room description.  You'll
2575
     *   probably want to override this any time you actually use it,
2576
     *   though, to describe what the actor sees from the remote point of
2577
     *   view.  
2578
     */
2579
    roomRemoteDesc(actor) { roomDesc; }
2580
2581
    /* our interior room name when we're in the dark */
2582
    roomDarkName = (gLibMessages.roomDarkName)
2583
2584
    /* show our interior description in the dark */
2585
    roomDarkDesc { gLibMessages.roomDarkDesc; }
2586
2587
    /*
2588
     *   Describe an actor in this location either from the point of view
2589
     *   of a separate top-level room, or at a distance.  This is called
2590
     *   when we're showing a room description (for LOOK AROUND), and the
2591
     *   given actor is in a remote room or at a distance visually from
2592
     *   the actor doing the looking, and the given actor is contained
2593
     *   within this room.
2594
     *   
2595
     *   By default, if we have a location, and the actor doing the
2596
     *   looking can see out into the enclosing room, we'll defer to the
2597
     *   location.  Otherwise, we'll show a default library message ("The
2598
     *   actor is nearby").  
2599
     */
2600
    roomActorThereDesc(actor)
2601
    {
2602
        local pov = getPOV();
2603
        
2604
        /* 
2605
         *   if we have an enclosing location, and the POV actor can see
2606
         *   out to our location, defer to our location; otherwise, show
2607
         *   the default library message 
2608
         */
2609
        if (location != nil && pov != nil && pov.canSee(location))
2610
            location.roomActorThereDesc(actor);
2611
        else
2612
            gLibMessages.actorInRemoteRoom(actor, self, pov);
2613
    }
2614
    
2615
    /*
2616
     *   Look around from the point of view of this object and on behalf of
2617
     *   the given actor.  This can be used to generate a description as
2618
     *   seen from this object by the given actor, and is suitable for
2619
     *   cases where the actor can use this object as a sensing device.  We
2620
     *   simply pass this through to lookAroundPov(), passing 'self' as the
2621
     *   point-of-view object.
2622
     *   
2623
     *   'verbose' is a combination of LookXxx flags (defined in adv3.h)
2624
     *   indicating what style of description we want.  This can also be
2625
     *   'true', in which case we'll show the standard verbose listing
2626
     *   (LookRoomName | LookRoomDesc | LookListSpecials |
2627
     *   LookListPortables); or 'nil', in which case we'll use the standard
2628
     *   terse listing (LookRoomName | LookListSpecials |
2629
     *   LookListPortables).  
2630
     */
2631
    lookAround(actor, verbose)
2632
    {
2633
        /* look around from my point of view */
2634
        lookAroundPov(actor, self, verbose);
2635
    }
2636
2637
    /*
2638
     *   Look around from within this object, looking from the given point
2639
     *   of view and on behalf of the given actor.
2640
     *   
2641
     *   'actor' is the actor doing the looking, and 'pov' is the point of
2642
     *   view of the description.  These are usually the same, but need not
2643
     *   be.  For example, an actor could be looking at a room through a
2644
     *   hole in the wall, in which case the POV would be the object
2645
     *   representing the "side" of the hole in the room being described.
2646
     *   Or, the actor could be observing a remote room via a
2647
     *   closed-circuit television system, in which case the POV would be
2648
     *   the camera in the remote room.  (The POV is the physical object
2649
     *   receiving the photons in the location being described, so it's the
2650
     *   camera, not the TV monitor that the actor's looking at.)
2651
     *   
2652
     *   'verbose' has the same meaning as it does in lookAround().  
2653
     *   
2654
     *   This routine checks to see if 'self' is the "look-around ceiling,"
2655
     *   which is for most purposes the outermost visible container of this
2656
     *   object; see isLookAroundCeiling() for more.  If this object is the
2657
     *   look-around ceiling, then we'll call lookAroundWithin() to
2658
     *   generate the description of the interior of 'self'; otherwise,
2659
     *   we'll recursively defer to our immediate container so that it can
2660
     *   make the same test.  In most cases, the outermost visible
2661
     *   container that actually generates the description will be a Room
2662
     *   or a NestedRoom.  
2663
     */
2664
    lookAroundPov(actor, pov, verbose)
2665
    {
2666
        /*
2667
         *   If we're the "look around ceiling," generate the description
2668
         *   within this room.  Otherwise, we have an enclosing location
2669
         *   that provides the interior description. 
2670
         */
2671
        if (isLookAroundCeiling(actor, pov))
2672
        {
2673
            /* we're the "ceiling," so describe our interior */
2674
            fromPOV(actor, pov, &lookAroundWithin, actor, pov, verbose);
2675
        }
2676
        else
2677
        {
2678
            /* our location provides the interior description */
2679
            location.lookAroundPov(actor, pov, verbose);
2680
        }
2681
    }
2682
2683
    /*
2684
     *   Are we the "look around ceiling"?  If so, it means that a LOOK
2685
     *   AROUND for an actor within this location (or from a point of view
2686
     *   within this location) will use our own interior description, via
2687
     *   lookAroundWithin().  If we're not the ceiling, then we defer to
2688
     *   our location, letting it describe its interior.
2689
     *   
2690
     *   By default, we're the ceiling if we're a top-level room (that is,
2691
     *   we have no enclosing location), OR it's not possible to see out to
2692
     *   our location.  In either of these cases, we can't see anything
2693
     *   outside of this room, so we have to generate our own interior
2694
     *   description.  However, if we do have a location that we can see,
2695
     *   then we'll assume that this object just represents a facet of its
2696
     *   enclosing location, so the enclosing location provides the room
2697
     *   interior description.
2698
     *   
2699
     *   In some cases, a room might want to provide its own LOOK AROUND
2700
     *   interior description directly even if its location is visible.
2701
     *   For example, if the player's inside a wooden booth with a small
2702
     *   window that can see out to the enclosing location, LOOK AROUND
2703
     *   should probably describe the interior of the booth rather than the
2704
     *   enclosing location: even though the exterior is technically
2705
     *   visible, the booth clearly dominates the view, from the player's
2706
     *   perspective.  In this case, we'd want to override this routine to
2707
     *   indicate that we're the LOOK AROUND ceiling, despite our
2708
     *   location's visibility.  
2709
     */
2710
    isLookAroundCeiling(actor, pov)
2711
    {
2712
        /* 
2713
         *   if we don't have a location, or we can't see our location,
2714
         *   then we have to provide our own description, so we're the LOOK
2715
         *   AROUND "ceiling" 
2716
         */
2717
        return (location == nil || !actor.canSee(location));
2718
    }
2719
2720
    /*
2721
     *   Provide a "room description" of the interior of this object.  This
2722
     *   routine is primarily intended as a subroutine of lookAround() and
2723
     *   lookAroundPov() - most game code shouldn't need to call this
2724
     *   routine directly.  Note that if you *do* call this routine
2725
     *   directly, you *must* set the global point-of-view variables, which
2726
     *   you can do most easily by calling this routine indirectly through
2727
     *   the fromPOV() method.
2728
     *   
2729
     *   The library calls this method when an actor performs a "look
2730
     *   around" command, and the actor is within this object, and the
2731
     *   actor can't see anything outside of this object; this can happen
2732
     *   simply because we're a top-level room, but it can also happen when
2733
     *   we're a closed opaque container or there's not enough light to see
2734
     *   the enclosing location.
2735
     *   
2736
     *   The parameters have the same meaning as they do in
2737
     *   lookAroundPov().
2738
     *   
2739
     *   Note that this method must be overridden if a room overrides the
2740
     *   standard mechanism for representing its contents list (i.e., it
2741
     *   doesn't store its complete set of direct contents in its
2742
     *   'contents' list property).  
2743
     *   
2744
     *   In most cases, this routine will only be called in Room and
2745
     *   NestedRoom objects, because actors can normally only enter those
2746
     *   types of objects.  However, it is possible to try to describe the
2747
     *   interior of other types of objects if (1) the game allows actors
2748
     *   to enter other types of objects, or (2) the game provides a
2749
     *   non-actor point-of-view object, such as a video camera, that can
2750
     *   be placed in ordinary containers and which transmit what they see
2751
     *   for remote viewing.  
2752
     */
2753
    lookAroundWithin(actor, pov, verbose)
2754
    {
2755
        local illum;
2756
        local infoTab;
2757
        local info;
2758
        local specialList;
2759
        local specialBefore, specialAfter;
2760
2761
        /* check for the special 'true' and 'nil' settings for 'verbose' */
2762
        if (verbose == true)
2763
        {
2764
            /* true -> show the standard verbose description */
2765
            verbose = (LookRoomName | LookRoomDesc
2766
                       | LookListSpecials | LookListPortables);
2767
        }
2768
        else if (verbose == nil)
2769
        {
2770
            /* nil -> show the standard terse description */
2771
            verbose = (LookRoomName | LookListSpecials | LookListPortables);
2772
        }
2773
2774
        /* 
2775
         *   if we've never seen the room before, include the room
2776
         *   description, even if it wasn't specifically requested 
2777
         */
2778
        if (!actor.hasSeen(self))
2779
            verbose |= LookRoomDesc;
2780
2781
        /* 
2782
         *   get a table of all of the objects that the viewer can sense
2783
         *   in the location using sight-like senses 
2784
         */
2785
        infoTab = actor.visibleInfoTableFromPov(pov);
2786
2787
        /* get the ambient illumination at the point of view */
2788
        info = infoTab[pov];
2789
        if (info != nil)
2790
        {
2791
            /* get the ambient illumination from the info list item */
2792
            illum = info.ambient;
2793
        }
2794
        else
2795
        {
2796
            /* the actor's not in the list, so it must be completely dark */
2797
            illum = 0;
2798
        }
2799
2800
        /* 
2801
         *   adjust the list of described items (usually, this removes the
2802
         *   point-of-view object from the list, to ensure that we don't
2803
         *   list it among the room's contents) 
2804
         */
2805
        adjustLookAroundTable(infoTab, pov, actor);
2806
        
2807
        /* if desired, display the room name */
2808
        if ((verbose & LookRoomName) != 0)
2809
        {
2810
            "<.roomname>";
2811
            lookAroundWithinName(actor, illum);
2812
            "<./roomname>";
2813
        }
2814
2815
        /* if we're in verbose mode, display the full description */
2816
        if ((verbose & LookRoomDesc) != 0)
2817
        {
2818
            /* display the full room description */
2819
            "<.roomdesc>";
2820
            lookAroundWithinDesc(actor, illum);
2821
            "<./roomdesc>";
2822
        }
2823
2824
        /* show the initial special descriptions, if desired */
2825
        if ((verbose & LookListSpecials) != 0)
2826
        {
2827
            local plst;
2828
2829
            /* 
2830
             *   Display any special description messages for visible
2831
             *   objects, other than those carried by the actor.  These
2832
             *   messages are part of the verbose description rather than
2833
             *   the portable item listing, because these messages are
2834
             *   meant to look like part of the room's full description
2835
             *   and thus should not be included in a non-verbose listing.
2836
             *   
2837
             *   Note that we only want to show objects that are currently
2838
             *   using special descriptions AND which aren't contained in
2839
             *   the actor doing the looking, so subset the list
2840
             *   accordingly.  
2841
             */
2842
            specialList = specialDescList(
2843
                infoTab,
2844
                {obj: obj.useSpecialDescInRoom(self) && !obj.isIn(actor)});
2845
2846
            /* 
2847
             *   partition the special list into the parts to be shown
2848
             *   before and after the portable contents list 
2849
             */
2850
            plst = partitionList(specialList,
2851
                                 {obj: obj.specialDescBeforeContents});
2852
            specialBefore = plst[1];
2853
            specialAfter = plst[2];
2854
2855
            /* 
2856
             *   at this point, describe only the items that are to be
2857
             *   listed specially before the room's portable contents list 
2858
             */
2859
            specialContentsLister.showList(pov, nil, specialBefore,
2860
                                           0, 0, infoTab, nil);
2861
        }
2862
2863
        /* show the portable items, if desired */
2864
        if ((verbose & LookListPortables) != 0)
2865
        {
2866
            /* 
2867
             *   Describe each visible object directly contained in the
2868
             *   room that wants to be listed - generally, we list any
2869
             *   mobile objects that don't have special descriptions.  We
2870
             *   list items in all room descriptions, verbose or not,
2871
             *   because the portable item list is more of a status display
2872
             *   than it is a part of the full description.
2873
             *   
2874
             *   Note that the infoTab has sense data that will ensure that
2875
             *   we don't show anything we shouldn't if the room is dark.  
2876
             */
2877
            lookAroundWithinContents(actor, illum, infoTab);
2878
        }
2879
2880
        /*
2881
         *   If we're showing special descriptions, show the special descs
2882
         *   for any items that want to be listed after the room's portable
2883
         *   contents.  
2884
         */
2885
        if ((verbose & LookListSpecials) != 0)
2886
        {
2887
            /* show the subset that's listed after the room contents */
2888
            specialContentsLister.showList(pov, nil, specialAfter,
2889
                                           0, 0, infoTab, nil);
2890
        }
2891
        
2892
        /* show all of the sounds we can hear */
2893
        lookAroundWithinSense(actor, pov, sound, roomListenLister);
2894
2895
        /* show all of the odors we can smell */
2896
        lookAroundWithinSense(actor, pov, smell, roomSmellLister);
2897
2898
        /* show the exits, if desired */
2899
        lookAroundWithinShowExits(actor, illum);
2900
2901
        /* 
2902
         *   If we're not in the dark, note that we've been seen.  Don't
2903
         *   count this as having seen the location if we're in the dark,
2904
         *   since we won't normally describe any detail in the dark.  
2905
         */
2906
        if (illum > 1)
2907
            actor.setHasSeen(self);
2908
    }
2909
2910
    /*
2911
     *   Display the "status line" name of the room.  This is normally a
2912
     *   brief, single-line description.
2913
     *   
2914
     *   By long-standing convention, each location in a game usually has a
2915
     *   distinctive name that's displayed here.  Players usually find
2916
     *   these names helpful in forming a mental map of the game.
2917
     *   
2918
     *   By default, if we have an enclosing location, and the actor can
2919
     *   see the enclosing location, we'll defer to the location.
2920
     *   Otherwise, we'll display our roo interior name.  
2921
     */
2922
    statusName(actor)
2923
    {
2924
        /* 
2925
         *   use the enclosing location's status name if there is an
2926
         *   enclosing location and its visible; otherwise, show our
2927
         *   interior room name 
2928
         */
2929
        if (location != nil && actor.canSee(location))
2930
            location.statusName(actor);
2931
        else
2932
            lookAroundWithinName(actor, actor.getVisualAmbient());
2933
    }
2934
2935
    /*
2936
     *   Adjust the sense table a "look around" command.  This is called
2937
     *   after we calculate the sense info table, but before we start
2938
     *   describing any of the room's contents, to give us a chance to
2939
     *   remove any items we don't want described (or, conceivably, to add
2940
     *   any items we do want described but which won't show up with the
2941
     *   normal sense calculations).
2942
     *   
2943
     *   By default, we simply remove the point-of-view object from the
2944
     *   list, to ensure that it's not included among the objects mentioned
2945
     *   as being in the room.  We don't want to mention the point of view
2946
     *   because the POV object because a POV isn't normally in its own
2947
     *   field of view.  
2948
     */
2949
    adjustLookAroundTable(tab, pov, actor)
2950
    {
2951
        /* remove the POV object from the table */
2952
        tab.removeElement(pov);
2953
2954
        /* give the actor a chance to adjust the table as well */
2955
        if (self not in (actor, pov))
2956
        {
2957
            /* let the POV object adjust the table */
2958
            pov.adjustLookAroundTable(tab, pov, actor);
2959
2960
            /* if the actor differs from the POV, let it adjust the table */
2961
            if (actor != pov)
2962
                actor.adjustLookAroundTable(tab, pov, actor);
2963
        }
2964
    }
2965
2966
    /*
2967
     *   Show my name for a "look around" command.  This is used to display
2968
     *   the location name when we're providing the room description.
2969
     *   'illum' is the ambient visual sense level at the point of view.
2970
     *   
2971
     *   By default, we show our interior room name or interior dark room
2972
     *   name, as appropriate to the ambient light level at the point of
2973
     *   view.  
2974
     */
2975
    lookAroundWithinName(actor, illum)
2976
    {
2977
        /* 
2978
         *   if there's light, show our name; otherwise, show our
2979
         *   in-the-dark name 
2980
         */
2981
        if (illum > 1)
2982
        {
2983
            /* we can see, so show our interior description */
2984
            "\^<<roomName>>";
2985
        }
2986
        else
2987
        {
2988
            /* we're in the dark, so show our default dark name */
2989
            say(roomDarkName);
2990
        }
2991
2992
        /* show the actor's posture as part of the description */
2993
        actor.actorRoomNameStatus(self);
2994
    }
2995
2996
    /*
2997
     *   Show our "look around" long description.  This is used to display
2998
     *   the location's full description when we're providing the room
2999
     *   description - that is, when the actor doing the looking is inside
3000
     *   me.  This displays only the room-specific portion of the room's
3001
     *   description; it does not display the status line or anything
3002
     *   about the room's dynamic contents.  
3003
     */
3004
    lookAroundWithinDesc(actor, illum)
3005
    {
3006
        /* 
3007
         *   check for illumination - we must have at least dim ambient
3008
         *   lighting (level 2) to see the room's long description 
3009
         */
3010
        if (illum > 1)
3011
        {
3012
            local pov = getPOVDefault(actor);
3013
            
3014
            /* 
3015
             *   Display the normal description of the room - use the
3016
             *   roomRemoteDesc if the actor isn't in the same room OR the
3017
             *   point of view isn't the same as the actor; use the
3018
             *   firstDesc if this is the first time in the room; and
3019
             *   otherwise the basic roomDesc.  
3020
             */
3021
            if (!actor.isIn(self) || actor != pov)
3022
            {
3023
                /* we're viewing the room remotely */
3024
                roomRemoteDesc(actor);
3025
            }
3026
            else if (actor.hasSeen(self))
3027
            {
3028
                /* we've seen it already - show the basic room description */
3029
                roomDesc;
3030
            }
3031
            else
3032
            {
3033
                /* 
3034
                 *   we've never seen this location before - show the
3035
                 *   first-time description 
3036
                 */
3037
                roomFirstDesc;
3038
            }
3039
        }
3040
        else
3041
        {
3042
            /* display the in-the-dark description of the room */
3043
            roomDarkDesc;
3044
        }
3045
    }
3046
3047
    /*
3048
     *   Show my room contents for a "look around" description within this
3049
     *   object. 
3050
     */
3051
    lookAroundWithinContents(actor, illum, infoTab)
3052
    {
3053
        local lst;
3054
        local lister;
3055
        local remoteLst;
3056
        local outer;
3057
        local recurse;
3058
3059
        /* get our outermost enclosing room */
3060
        outer = getOutermostRoom();
3061
3062
        /* mark everything visible from the room as having been seen */
3063
        setAllSeenBy(infoTab, actor);
3064
        
3065
        /* 
3066
         *   if the illumination is less than 'dim' (level 2), display
3067
         *   only self-illuminating items 
3068
         */
3069
        if (illum != nil && illum < 2)
3070
        {
3071
            /* 
3072
             *   We're in the dark - list only those objects that the actor
3073
             *   can sense via the sight-like senses, which will be the
3074
             *   list of self-illuminating objects.  Don't include items
3075
             *   being carried by the actor, though, since those don't
3076
             *   count as part of the actor's surroundings.  (To produce
3077
             *   this list, simply make a list consisting of all of the
3078
             *   objects in the sense info table that aren't contained in
3079
             *   the actor; since the table naturally includes only objects
3080
             *   that are illuminated, the entire contents of the table
3081
             *   will give us the visible objects.)  
3082
             */
3083
            lst = senseInfoTableSubset(
3084
                infoTab, {obj, info: !obj.isIn(actor)});
3085
            
3086
            /* use my dark contents lister */
3087
            lister = darkRoomContentsLister;
3088
3089
            /* there are no remote-room lists to generate */
3090
            remoteLst = nil;
3091
3092
            /* 
3093
             *   Since we can't see what we're looking at, we can't see the
3094
             *   positional relationships among the things we're looking
3095
             *   at, so we can't show the contents tree recursively.  We
3096
             *   just want to show our flat list of what's visible on
3097
             *   account of self-illumination.  
3098
             */
3099
            recurse = nil;
3100
        }
3101
        else
3102
        {
3103
            /* start with my contents list */
3104
            lst = contents;
3105
3106
            /* always remove the actor from the list */
3107
            lst -= actor;
3108
3109
            /* 
3110
             *   Use the normal (lighted) lister.  Note that if the actor
3111
             *   isn't in the same room, or the point of view isn't the
3112
             *   same as the actor, we want to generate remote
3113
             *   descriptions, so use the remote contents lister in these
3114
             *   cases.  
3115
             */
3116
            lister = (actor.isIn(self) && actor == getPOVDefault(actor)
3117
                      ? roomContentsLister
3118
                      : remoteRoomContentsLister(self));
3119
3120
            /*
3121
             *   Generate a list of all of the *other* top-level rooms
3122
             *   with contents visible from here.  To do this, build a
3123
             *   list of all of the unique top-level rooms, other than our
3124
             *   own top-level room, that contain items in the visible
3125
             *   information table.  
3126
             */
3127
            remoteLst = new Vector(5);
3128
            infoTab.forEachAssoc(new function(obj, info)
3129
            {
3130
                /* if this object isn't in our top-level room, note it */
3131
                if (obj != outer && !obj.isIn(outer))
3132
                {
3133
                    local objOuter;
3134
                    
3135
                    /* 
3136
                     *   it's not in our top-level room, so find its
3137
                     *   top-level room 
3138
                     */
3139
                    objOuter = obj.getOutermostRoom();
3140
3141
                    /* if this one isn't in our list yet, add it */
3142
                    if (remoteLst.indexOf(objOuter) == nil)
3143
                        remoteLst.append(objOuter);
3144
                }
3145
            });
3146
3147
            /* 
3148
             *   since we can see what we're looking at, show the
3149
             *   relationships among the contents by listing recursively 
3150
             */
3151
            recurse = true;
3152
        }
3153
3154
        /* add a paragraph before the room's contents */
3155
        "<.p>";
3156
3157
        /* show the contents */
3158
        lister.showList(actor, self, lst, (recurse ? ListRecurse : 0),
3159
                        0, infoTab, nil);
3160
3161
        /* 
3162
         *   if we can see anything in remote top-level rooms, show the
3163
         *   contents of each other room with visible contents 
3164
         */
3165
        if (remoteLst != nil)
3166
        {
3167
            /* show each remote room's contents */
3168
            for (local i = 1, local len = remoteLst.length() ; i <= len ; ++i)
3169
            {
3170
                local cur;
3171
                local cont;
3172
3173
                /* get this item */
3174
                cur = remoteLst[i];
3175
3176
                /* start with the direct contents of this remote room */
3177
                cont = cur.contents;
3178
3179
                /* 
3180
                 *   Remove any objects that are also contents of the
3181
                 *   local room or of any other room we've already listed.
3182
                 *   It's possible that we have a MultiLoc that's in one
3183
                 *   or more of the visible top-level locations, so we
3184
                 *   need to make sure we only list each one once. 
3185
                 */
3186
                cont = cont.subset({x: !x.isIn(outer)});
3187
                for (local j = 1 ; j < i ; ++j)
3188
                    cont = cont.subset({x: !x.isIn(remoteLst[j])});
3189
                
3190
                /* 
3191
                 *   list this remote room's contents using our remote
3192
                 *   lister for this remote room 
3193
                 */
3194
                outer.remoteRoomContentsLister(cur).showList(
3195
                    actor, cur, cont, ListRecurse, 0, infoTab, nil);
3196
            }
3197
        }
3198
    }
3199
3200
    /*
3201
     *   Add to the room description a list of things we notice through a
3202
     *   specific sense.  This is used to add things we can hear and smell
3203
     *   to a room description.
3204
     */
3205
    lookAroundWithinSense(actor, pov, sense, lister)
3206
    {
3207
        local infoTab;
3208
        local presenceList;
3209
3210
        /* 
3211
         *   get the information table for the desired sense from the
3212
         *   given point of view 
3213
         */
3214
        infoTab = pov.senseInfoTable(sense);
3215
3216
        /* 
3217
         *   Get the list of everything with a presence in this sense.
3218
         *   Include only items that aren't part of the actor's inventory,
3219
         *   since inventory items aren't part of the room and thus
3220
         *   shouldn't be described as part of the room.  
3221
         */
3222
        presenceList = senseInfoTableSubset(infoTab,
3223
            {obj, info: obj.(sense.presenceProp) && !obj.isIn(actor)});
3224
3225
        /* mark each item in the presence list as known */
3226
        foreach (local cur in presenceList)
3227
            actor.setKnowsAbout(cur);
3228
3229
        /* list the items */
3230
        lister.showList(pov, nil, presenceList, 0, 0, infoTab, nil);
3231
    }
3232
3233
    /*
3234
     *   Show the exits from this room as part of a description of the
3235
     *   room, if applicable.  By default, if we have an exit lister
3236
     *   object, we'll invoke it to show the exits.  
3237
     */
3238
    lookAroundWithinShowExits(actor, illum)
3239
    {
3240
        /* if we have an exit lister, have it show a list of exits */
3241
        if (gExitLister != nil)
3242
            gExitLister.lookAroundShowExits(actor, self, illum);
3243
    }
3244
3245
    /* 
3246
     *   Get my lighted room contents lister - this is the Lister object
3247
     *   that we use to display the room's contents when the room is lit.
3248
     *   We'll return the default library room lister.  
3249
     */
3250
    roomContentsLister { return roomLister; }
3251
3252
    /*
3253
     *   Get my lister for the contents of the given remote room.  A
3254
     *   remote room is a separate top-level room that an actor can see
3255
     *   from its current location; for example, if two top-level rooms
3256
     *   are connected by a window, so that an actor standing in one room
3257
     *   can see into the other room, then the other room is the remote
3258
     *   room, from the actor's perspective.
3259
     *   
3260
     *   This is called on the actor's outermost room to get the lister
3261
     *   for objects visible in the given remote room.  This lets each
3262
     *   room customize the way it describes the objects in adjoining
3263
     *   rooms as seen from its point of view.
3264
     *   
3265
     *   We provide a simple default lister that yields decent results in
3266
     *   most cases.  However, it's often desirable to customize this, to
3267
     *   be more specific about how we see the items: "Through the window,
3268
     *   you see..." or "Further down the hall, you see...".  An easy way
3269
     *   to provide such custom listings is to return a new
3270
     *   CustomRoomLister, supplying the prefix and suffix strings as
3271
     *   parameters:
3272
     *   
3273
     *.    return new CustomRoomLister(
3274
     *.       'Further down the hall, {you/he} see{s}', '.');
3275
     */
3276
    remoteRoomContentsLister(other) { return new RemoteRoomLister(other); }
3277
3278
    /* 
3279
     *   Get my dark room contents lister - this is the Lister object we'll
3280
     *   use to display the room's self-illuminating contents when the room
3281
     *   is dark.  
3282
     */
3283
    darkRoomContentsLister { return darkRoomLister; }
3284
3285
    /*
3286
     *   Get the outermost containing room.  We return our container, if
3287
     *   we have one, or self if not.  
3288
     */
3289
    getOutermostRoom()
3290
    {
3291
        /* return our container's outermost room, if we have one */
3292
        return (location != nil ? location.getOutermostRoom() : self);
3293
    }
3294
3295
    /* get the outermost room that's visible from the given point of view */
3296
    getOutermostVisibleRoom(pov)
3297
    {
3298
        local enc;
3299
        
3300
        /* 
3301
         *   Get our outermost enclosing visible room - this is the
3302
         *   outermost enclosing room that the POV can see.  If we manage
3303
         *   to find an enclosing visible room, return that, since it's
3304
         *   "more outer" than we are.  
3305
         */
3306
        if (location != nil
3307
            && (enc = location.getOutermostVisibleRoom(pov)) != nil)
3308
            return enc;
3309
3310
        /* 
3311
         *   We either don't have any enclosing rooms, or none of them are
3312
         *   visible.  So, our outermost enclosing room is one of two
3313
         *   things: if we're visible, it's us; if we're not even visible
3314
         *   ourselves, there's no visible room at all, so return nil.  
3315
         */
3316
        return (pov.canSee(self) ? self : nil);
3317
    }
3318
3319
    /*
3320
     *   Get the location traveler - this is the object that's actually
3321
     *   going to change location when a traveler within this location
3322
     *   performs a travel command to travel via the given connector.  By
3323
     *   default, we'll indicate what our containing room indicates.  (The
3324
     *   enclosing room might be a vehicle or an ordinary room; in any
3325
     *   case, it'll know what to do, so we merely have to ask it.)
3326
     *   
3327
     *   We defer to our enclosing room by default because this allows for
3328
     *   things like a seat in a car: the actor is sitting in the seat and
3329
     *   starts traveling in the car, so the seat calls the enclosing room,
3330
     *   which is the car, and the car returns itself, since it's the car
3331
     *   that will be traveling.  
3332
     */
3333
    getLocTraveler(trav, conn)
3334
    {
3335
        /* 
3336
         *   if we have a location, defer to it; otherwise, just return the
3337
         *   proposed traveler 
3338
         */
3339
        return (location != nil ? location.getLocTraveler(trav, conn) : trav);
3340
    }
3341
3342
    /*
3343
     *   Get the "location push traveler" - this is the object that's going
3344
     *   to travel for a push-travel action performed by a traveler within
3345
     *   this location.  This is called by a traveler within this location
3346
     *   to find out if the location wants to be involved in the travel, as
3347
     *   a vehicle might be.  By default, we let our location handle it, if
3348
     *   we have one.  
3349
     */
3350
    getLocPushTraveler(trav, obj)
3351
    {
3352
        /* 
3353
         *   defer to our location if we have one, otherwise just use the
3354
         *   proposed traveler 
3355
         */
3356
        return (location != nil
3357
                ? location.getLocPushTraveler(trav, obj)
3358
                : trav);
3359
    }
3360
3361
    /*
3362
     *   Get the travel preconditions for an actor in this location.  For
3363
     *   ordinary objects, we'll just defer to our container, if we have
3364
     *   one.  
3365
     */
3366
    roomTravelPreCond()
3367
    {
3368
        /* if we have a location, defer to it */
3369
        if (location != nil)
3370
            return location.roomTravelPreCond();
3371
3372
        /* 
3373
         *   we have no location, and we impose no conditions of our by
3374
         *   default, so simply return an empty list 
3375
         */
3376
        return [];
3377
    }
3378
3379
    /*
3380
     *   Determine if the current gActor, who is directly in this location,
3381
     *   is "travel ready."  This means that the actor is ready, as far as
3382
     *   this location is concerned, to traverse the given connector.  By
3383
     *   default, we'll defer to our location.
3384
     *   
3385
     *   Note that if you override this to return nil, you should also
3386
     *   provide a custom 'notTravelReadyMsg' property that explains the
3387
     *   objection.  
3388
     */
3389
    isActorTravelReady(conn)
3390
    {
3391
        /* if we have a location, defer to it */
3392
        if (location != nil)
3393
            return location.isActorTravelReady(conn);
3394
3395
        /* 
3396
         *   we have no location, and we impose no conditions of our own by
3397
         *   default, so we have no objection
3398
         */
3399
        return true;
3400
    }
3401
3402
    /* explain the reason isActorTravelReady() returned nil */
3403
    notTravelReadyMsg = (location != nil ? location.notTravelReadyMsg : nil)
3404
3405
    /* show a list of exits as part of failed travel - defer to location */
3406
    cannotGoShowExits(actor)
3407
    {
3408
        if (location != nil)
3409
            location.cannotGoShowExits(actor);
3410
    }
3411
3412
    /* show exits in the statusline - defer to our location */
3413
    showStatuslineExits()
3414
    {
3415
        if (location != nil)
3416
            location.showStatuslineExits();
3417
    }
3418
3419
    /* get the status line exit lister height - defer to our location */
3420
    getStatuslineExitsHeight()
3421
        { return location != nil ? location.getStatuslineExitsHeight() : 0; }
3422
3423
    /*
3424
     *   Get the travel connector from this location in the given
3425
     *   direction.
3426
     *   
3427
     *   Map all of the directional connections to their values in the
3428
     *   enclosing location, except for any explicitly defined in this
3429
     *   object.  (In most cases, ordinary objects won't define any
3430
     *   directional connectors directly, since those connections usually
3431
     *   apply only to top-level rooms.)
3432
     *   
3433
     *   If 'actor' is non-nil, we'll limit our search to enclosing
3434
     *   locations that the actor can currently see.  If 'actor' is nil,
3435
     *   we'll consider any connector in any enclosing location, whether
3436
     *   the actor can see the enclosing location or not.  It's useful to
3437
     *   pass in a nil actor in cases where we're interested in the
3438
     *   structure of the map and not actual travel, such as when
3439
     *   constructing an automatic map or computing possible courses
3440
     *   between locations.  
3441
     */
3442
    getTravelConnector(dir, actor)
3443
    {
3444
        local conn;
3445
        
3446
        /* 
3447
         *   if we explicitly define the link property, and the result is a
3448
         *   non-nil value, use that definition 
3449
         */
3450
        if (propDefined(dir.dirProp) && (conn = self.(dir.dirProp)) != nil)
3451
            return conn;
3452
3453
        /* 
3454
         *   We don't define the direction link explicitly.  If the actor
3455
         *   can see our container, or there's no actor involved, retrieve
3456
         *   the link from our container; otherwise, stop here, and simply
3457
         *   return the default link for the direction.  
3458
         */
3459
        if (location != nil && (actor == nil || actor.canSee(location)))
3460
        {
3461
            /* 
3462
             *   the actor can see out to our location (or there's no actor
3463
             *   involved at all), so get the connector for the location 
3464
             */
3465
            return location.getTravelConnector(dir, actor);
3466
        }
3467
        else
3468
        {
3469
            /* 
3470
             *   the actor can't see our location, so there's no way to
3471
             *   travel this way - return the default connector for the
3472
             *   direction 
3473
             */
3474
            return dir.defaultConnector(self);
3475
        }
3476
    }
3477
3478
    /*
3479
     *   Search our direction list for a connector that will lead the
3480
     *   given actor to the given destination.  
3481
     */
3482
    getConnectorTo(actor, dest)
3483
    {
3484
        local conn;
3485
        
3486
        /* search all of our directions */
3487
        foreach (local dir in Direction.allDirections)
3488
        {
3489
            /* get the connector for this direction */
3490
            conn = getTravelConnector(dir, actor);
3491
3492
            /* ask the connector for a connector to the given destination */
3493
            if (conn != nil)
3494
                conn = conn.connectorGetConnectorTo(self, actor, dest);
3495
3496
            /* if we found a valid connector, return it */
3497
            if (conn != nil)
3498
                return conn;
3499
        }
3500
3501
        /* didn't find a match */
3502
        return nil;
3503
    }
3504
3505
    /*
3506
     *   Find a direction linked to a given connector, for travel by the
3507
     *   given actor.  Returns the first direction (as a Direction object)
3508
     *   we find linked to the connector, or nil if we don't find any
3509
     *   direction linked to it.  
3510
     */
3511
    directionForConnector(conn, actor)
3512
    {
3513
        /* search all known directions */
3514
        foreach (local dir in Direction.allDirections)
3515
        {
3516
            /* if this direction is linked to the connector, return it */
3517
            if (self.getTravelConnector(dir, actor) == conn)
3518
                return dir;
3519
        }
3520
3521
        /* we didn't find any directions linked to the connector */
3522
        return nil;
3523
    }
3524
3525
    /*
3526
     *   Find the "local" direction link from this object to the given
3527
     *   travel connector.  We'll only consider our own local direction
3528
     *   links; we won't consider enclosing objects.  
3529
     */
3530
    localDirectionLinkForConnector(conn)
3531
    {
3532
        /* scan all direction links */
3533
        foreach (local dir in Direction.allDirections)
3534
        {
3535
            /* 
3536
             *   if we define this direction property to point to this
3537
             *   connector, we've found our required location 
3538
             */
3539
            if (self.(dir.dirProp) == conn)
3540
                return dir;
3541
        }
3542
3543
        /* we didn't find a direction linked to the given connector */
3544
        return nil;
3545
    }
3546
3547
    /*
3548
     *   Check that a traveler is directly in this room.  By default, a
3549
     *   Thing is not a room, so a connector within a Thing actually
3550
     *   requires the traveler to be in the Thing's container, not in the
3551
     *   Thing itself.  So, defer to our container.  
3552
     */
3553
    checkTravelerDirectlyInRoom(traveler, allowImplicit)
3554
    {
3555
        /* defer to our location */
3556
        return location.checkTravelerDirectlyInRoom(traveler, allowImplicit);
3557
    }
3558
3559
    /*
3560
     *   Check, using pre-condition rules, that the actor is removed from
3561
     *   this nested location and moved to its exit destination.
3562
     *   
3563
     *   This is called when the actor is attempting to move to an object
3564
     *   outside of this object while the actor is within this object (for
3565
     *   example, if we have a platform containing a box containing a
3566
     *   chair, 'self' is the box, and the actor is in the chair, an
3567
     *   attempt to move to the platform will trigger a call to
3568
     *   box.checkActorOutOfNested).
3569
     *   
3570
     *   By default, we're not a nested location, but we could *contain*
3571
     *   nested locations.  So what we need to do is defer to the child
3572
     *   object that actually contains the actor, to let it figure out what
3573
     *   it means to move the actor out of itself.  
3574
     */
3575
    checkActorOutOfNested(allowImplicit)
3576
    {
3577
        /* find the child containing the actor, and ask it to do the work */
3578
        foreach (local cur in contents)
3579
        {
3580
            if (gActor.isIn(cur))
3581
                return cur.checkActorOutOfNested(allowImplicit);
3582
        }
3583
3584
        /* didn't find a child containing the actor; just fail */
3585
        reportFailure(&cannotDoFromHereMsg);
3586
        exit;
3587
    }
3588
3589
    /*
3590
     *   Get my "room location."  If 'self' is capable of holding actors,
3591
     *   this should return 'self'; otherwise, it should return the
3592
     *   nearest enclosing container that's a room location.  By default,
3593
     *   we simply return our location's room location.  
3594
     */
3595
    roomLocation = (location != nil ? location.roomLocation : nil)
3596
3597
    /*
3598
     *   Get the apparent location of one of our room parts (the floor,
3599
     *   the ceiling, etc).  By default, we'll simply ask our container
3600
     *   about it, since a nested room by default doesn't have any of the
3601
     *   standard room parts.  
3602
     */
3603
    getRoomPartLocation(part)
3604
    {
3605
        if (location != nil)
3606
            return location.getRoomPartLocation(part);
3607
        else
3608
            return nil;
3609
    }
3610
3611
    /*
3612
     *   By default, an object containing the player character will forward
3613
     *   the roomDaemon() call up to the container. 
3614
     */
3615
    roomDaemon()
3616
    {
3617
        if (location != nil)
3618
            location.roomDaemon();
3619
    }
3620
3621
    /*
3622
     *   Our room atmospheric message list.  This can be set to an
3623
     *   EventList that provides messages to be displayed while the player
3624
     *   character is within this object.
3625
     *   
3626
     *   By default, if our container is visible to us, we'll use our
3627
     *   container's atmospheric messages.  This can be overridden to
3628
     *   provide our own atmosphere list when the player character is in
3629
     *   this object.  
3630
     */
3631
    atmosphereList()
3632
    {
3633
        if (location != nil && gPlayerChar.canSee(location))
3634
            return location.atmosphereList;
3635
        else
3636
            return nil;
3637
    }
3638
3639
    /* 
3640
     *   Get the notification list actions performed by actors within this
3641
     *   object.  Ordinary objects don't have room notification lists, but
3642
     *   we might be part of a nested set of objects that includes rooms,
3643
     *   so simply look to our container for the notification list.  
3644
     */
3645
    getRoomNotifyList()
3646
    {
3647
        local lst = [];
3648
        
3649
        /* get the notification lists for our immediate containers */
3650
        forEachContainer(
3651
            {cont: lst = lst.appendUnique(cont.getRoomNotifyList())});
3652
3653
        /* return the result */
3654
        return lst;
3655
    }
3656
3657
    /*
3658
     *   Are we aboard a ship?  By default, we'll return our location's
3659
     *   setting for this property; if we have no location, we'll return
3660
     *   nil.  In general, this should be overridden in shipboard rooms to
3661
     *   return true.
3662
     *   
3663
     *   The purpose of this property is to indicate to the travel system
3664
     *   that shipboard directions (fore, aft, port, starboard) make sense
3665
     *   here.  When this returns true, and an actor attempts travel in a
3666
     *   shipboard direction that doesn't allow travel here, we'll use the
3667
     *   standard noTravel message.  When this returns nil, attempting to
3668
     *   move in a shipboard direction will show a different response that
3669
     *   indicates that such directions are not meaningful when not aboard
3670
     *   a ship of some kind.
3671
     *   
3672
     *   Note that we look to our location unconditionally - we don't
3673
     *   bother to check to see if we're open, transparent, or anything
3674
     *   like that, so we'll check with our location even if the actor
3675
     *   can't see the location.  The idea is that, no matter how nested
3676
     *   within opaque containers we are, we should still know that we're
3677
     *   aboard a ship.  This might not always be appropriate; if the
3678
     *   actor is magically transported into an opaque container that
3679
     *   happens to be aboard a ship somewhere, the actor probably
3680
     *   shouldn't think of the location as shipboard until escaping the
3681
     *   container, unless they'd know because of the rocking of the ship
3682
     *   or some other sensory factor that would come through the opaque
3683
     *   container.  When the shipboard nature of an interior location
3684
     *   should not be known to the actor, this should simply be
3685
     *   overridden to return nil.  
3686
     */
3687
    isShipboard()
3688
    {
3689
        /* if we have a location, use its setting; otherwise return nil */
3690
        return (location != nil ? location.isShipboard() : nil);
3691
    }
3692
3693
    /* 
3694
     *   When an actor takes me, the actor will assign me a holding index
3695
     *   value, which is simply a serial number indicating the order in
3696
     *   which I was picked up.  This lets the actor determine which items
3697
     *   have been held longest: the item with the lowest holding index
3698
     *   has been held the longest.  
3699
     */
3700
    holdingIndex = 0
3701
3702
    /*
3703
     *   An object has "weight" and "bulk" specifying how heavy and how
3704
     *   large the object is.  These are in arbitrary units, and by
3705
     *   default everything has a weight of 1 and a bulk of 1.
3706
     */
3707
    weight = 1
3708
    bulk = 1
3709
3710
    /*
3711
     *   Calculate the bulk contained within this object.  By default,
3712
     *   we'll simply add up the bulks of all of our contents.
3713
     */
3714
    getBulkWithin()
3715
    {
3716
        local total;
3717
        
3718
        /* add up the bulks of our contents */
3719
        total = 0;
3720
        foreach (local cur in contents)
3721
            total += cur.getBulk();
3722
        
3723
        /* return the total */
3724
        return total;
3725
    }
3726
3727
    /* 
3728
     *   get my "destination name," for travel purposes; by default, we
3729
     *   simply defer to our location, if we have one 
3730
     */
3731
    getDestName(actor, origin)
3732
    {
3733
        if (location != nil)
3734
            return location.getDestName(actor, origin);
3735
        else
3736
            return '';
3737
    }
3738
    
3739
    /*
3740
     *   Get the effective location of an actor directly within me, for the
3741
     *   purposes of a "follow" command.  To follow someone, we must have
3742
     *   the same effective follow location that the target had when we
3743
     *   last observed the target leaving.
3744
     *   
3745
     *   For most objects, we simply defer to the location.  
3746
     */
3747
    effectiveFollowLocation = (location != nil
3748
                               ? location.effectiveFollowLocation
3749
                               : self)
3750
3751
    /*
3752
     *   Get the destination for an object dropped within me.  Ordinary
3753
     *   objects can't contain actors, so an actor can't directly drop
3754
     *   something within a regular Thing, but the same effect could occur
3755
     *   if an actor throws a projectile, since the projectile might reach
3756
     *   either the target or an intermediate obstruction, then bounce off
3757
     *   and land in whatever contains the object hit.
3758
     *   
3759
     *   By default, objects dropped within us won't stay within us, but
3760
     *   will land in our container's drop destination.
3761
     *   
3762
     *   'obj' is the object being dropped.  In some cases, the drop
3763
     *   destination might differ according to the object; for example, if
3764
     *   the floor is a metal grating, a large object might land on the
3765
     *   grating when dropped, but a small object such as a coin might drop
3766
     *   through the grating to the room below.  Note that 'obj' can be
3767
     *   nil, if the caller simply wants to determine the generic
3768
     *   destination where a *typical* object would land if dropped - this
3769
     *   routine must therefore not assume that 'obj' is non-nil.
3770
     *   
3771
     *   'path' is the sense path (as returned by selectPathTo and the
3772
     *   like) traversed by the operation that's seeking the drop
3773
     *   destination.  For ordinary single-location objects, the path is
3774
     *   irrelevant, but this information is sometimes useful for
3775
     *   multi-location objects to let them know which "side" we approached
3776
     *   them from.  Note that the path can be nil, so this routine must
3777
     *   not depend upon a path being supplied; if the path is nil, the
3778
     *   routine should assume that the ordinary "touch" sense path from
3779
     *   the current actor to 'self' is to be used, because the actor is
3780
     *   effectively reaching out and placing an object on self.  This
3781
     *   means that when calling this routine, you can supply a nil path
3782
     *   any time the actor is simply dropping an object.  
3783
     */
3784
    getDropDestination(obj, path)
3785
    {
3786
        /* 
3787
         *   by default, the object lands in our container's drop
3788
         *   destination; if we don't have a container, drop it here 
3789
         */
3790
        return location != nil
3791
            ? location.getDropDestination(obj, path)
3792
            : self;
3793
    }
3794
3795
    /*
3796
     *   Adjust a drop destination chosen in a THROW.  This is called on
3797
     *   the object that was chosen as the preliminary landing location for
3798
     *   the thrown object, as calculated by getHitFallDestination(), to
3799
     *   give the preliminary destination a chance to redirect the landing
3800
     *   to another object.  For example, if the preliminary destination
3801
     *   simply isn't a suitable surface or container where a projectile
3802
     *   could land, or it's not big enough to hold the projectile, the
3803
     *   preliminary destination could override this method to redirect the
3804
     *   landing to a more suitable object.
3805
     *   
3806
     *   By default, we don't need to make any adjustment, so we simply
3807
     *   return 'self' to indicate that this can be used as the actual
3808
     *   destination.  
3809
     */
3810
    adjustThrowDestination(thrownObj, path)
3811
    {
3812
        return self;
3813
    }
3814
3815
    /*
3816
     *   Receive a dropped object.  This is called when we are the actual
3817
     *   (not nominal) drop destination for an object being dropped,
3818
     *   thrown, or otherwise discarded.  This routine is responsible for
3819
     *   carrying out the drop operation, and reporting the result of the
3820
     *   command.
3821
     *   
3822
     *   'desc' is a "drop descriptor": an object of class DropType, which
3823
     *   describes how the object is being discarded.  This can be a
3824
     *   DropTypeDrop for a DROP command, a DropTypeThrow for a THROW
3825
     *   command, or custom types defined by the game or by library
3826
     *   extensions.
3827
     *   
3828
     *   In most cases, the actual *result* of dropping the object will
3829
     *   always be the same for a given location, regardless of which
3830
     *   command initiated the drop, so this routine won't usually have to
3831
     *   look at 'desc' at all to figure out what happens.
3832
     *   
3833
     *   However, the *message* we generate does usually vary according to
3834
     *   the drop type, because this is the report for the entire command.
3835
     *   There are three main ways of handling this variation.
3836
     *   
3837
     *   - First, you can check what kind of descriptor it is (using
3838
     *   ofKind, for example), and generate a custom message for each kind
3839
     *   of descriptor.  Be aware that any library extensions you're using
3840
     *   might have added new DropType subclasses.
3841
     *   
3842
     *   - Second, experienced programmers might prefer the arguably
3843
     *   cleaner OO "visitor" pattern: treat 'desc' as a visitor, calling a
3844
     *   single method that you define for your custom message.  You'll
3845
     *   have to define that custom method in each DropType subclass, of
3846
     *   course.
3847
     *   
3848
     *   - Third, you can build a custom message by combining the standard
3849
     *   "message fragment" prefix provided by the drop descriptor with
3850
     *   your own suffix.  The prefix will always be the start of a
3851
     *   sentence describing what the player did: "You drop the box", "The
3852
     *   ball hits the wall and bounces off", and so on.  Since the
3853
     *   fragment always has the form of the start of a sentence, you can
3854
     *   always add your own suffix: ", and falls to the floor", for
3855
     *   example, or ", and falls into the chasm".  Note that if you're
3856
     *   using one of the other approaches above, you'll probably want to
3857
     *   combine that approach with this one to handle cases where you
3858
     *   don't recognize the drop descriptor type.
3859
     *   
3860
     *   By default, we simply move the object into self and display the
3861
     *   standard message using the descriptor (we use the "visitor"
3862
     *   pattern to display that standard message).  This can be overridden
3863
     *   in cases where it's necessary to move the object to a different
3864
     *   location, or to invoke a side effect.  
3865
     */
3866
    receiveDrop(obj, desc)
3867
    {
3868
        /* simply move the object into self */
3869
        obj.moveInto(self);
3870
3871
        /* generate the standard message */
3872
        desc.standardReport(obj, self);
3873
    }
3874
3875
    /*
3876
     *   Get the nominal destination for an object dropped into this
3877
     *   object.  This is used to report where an object ends up when the
3878
     *   object is moved into me by some indirect route, such as throwing
3879
     *   the object.
3880
     *   
3881
     *   By default, most objects simply return themselves, so we'll
3882
     *   report something like "obj lands {in/on} self".  Some objects
3883
     *   might want to report a different object as the destination; for
3884
     *   example, an indoor room might want to report objects as falling
3885
     *   onto the floor.  
3886
     */
3887
    getNominalDropDestination() { return self; }
3888
3889
    /*
3890
     *   Announce myself as a default object for an action.  By default,
3891
     *   we'll show the standard library message announcing a default.  
3892
     */
3893
    announceDefaultObject(whichObj, action, resolvedAllObjects)
3894
    {
3895
        /* use the standard library message for the announcement */
3896
        return gLibMessages.announceDefaultObject(
3897
            self, whichObj, action, resolvedAllObjects);
3898
    }
3899
    
3900
    /*
3901
     *   Calculate my total weight.  Weight is generally inclusive of the
3902
     *   weights of contents or components, because anything inside an
3903
     *   object normally contributes to the object's total weight. 
3904
     */
3905
    getWeight()
3906
    {
3907
        local total;
3908
3909
        /* start with my own intrinsic weight */
3910
        total = weight;
3911
                
3912
        /* 
3913
         *   add the weights of my direct contents, recursively
3914
         *   calculating their total weights 
3915
         */
3916
        foreach (local cur in contents)
3917
            total += cur.getWeight();
3918
3919
        /* return the total */
3920
        return total;
3921
    }
3922
3923
    /*
3924
     *   Calculate my total bulk.  Most objects have a fixed external
3925
     *   shape (and thus bulk) that doesn't vary as contents are added or
3926
     *   removed, so our default implementation simply returns our
3927
     *   intrinsic bulk without considering any contents.
3928
     *   
3929
     *   Some objects do change shape as contents are added.  Such objects
3930
     *   can override this routine to provide the appropriate behavior.
3931
     *   (We don't try to provide a parameterized total bulk calculator
3932
     *   here because there are too many possible ways a container's bulk
3933
     *   could be affected by its contents or by other factors.)
3934
     *   
3935
     *   If an object's bulk depends on its contents, the object should
3936
     *   override notifyInsert() so that it calls checkBulkChange() - this
3937
     *   will ensure that an object won't suddenly become too large for
3938
     *   its container (or holding actor).  
3939
     */
3940
    getBulk()
3941
    {
3942
        /* by default, simply return my intrinsic bulk */
3943
        return bulk;
3944
    }
3945
3946
    /*
3947
     *   Calculate the amount of bulk that this object contributes towards
3948
     *   encumbering an actor directly holding the item.  By default, this
3949
     *   simply returns my total bulk.
3950
     *   
3951
     *   Some types of objects will override this to cause the object to
3952
     *   contribute more or less bulk to an actor holding the object.  For
3953
     *   example, an article of clothing being worn by an actor typically
3954
     *   contributes no bulk at all to the actor's encumbrance, because an
3955
     *   actor wearing an item typically doesn't also have to hold on to
3956
     *   the item.  Or, a small animal might encumber an actor more than
3957
     *   its normal bulk because it's squirming around trying to get free.
3958
     */
3959
    getEncumberingBulk(actor)
3960
    {
3961
        /* by default, return our normal total bulk */
3962
        return getBulk();
3963
    }
3964
3965
    /*
3966
     *   Calculate the amount of weight that this object contributes
3967
     *   towards encumbering an actor directly holding the item.  By
3968
     *   default, this simply returns my total weight.
3969
     *   
3970
     *   Note that we don't recursively sum the encumbering weights of our
3971
     *   contents - we simply sum the actual weights.  We do it this way
3972
     *   because items within a container aren't being directly held by
3973
     *   the actor, so any difference between the encumbering and actual
3974
     *   weights should not apply, even though the actor is indirectly
3975
     *   holding the items.  If a subclass does want the sum of the
3976
     *   encumbering weights, it should override this to make that
3977
     *   calculation.  
3978
     */
3979
    getEncumberingWeight(actor)
3980
    {
3981
        /* by default, return our normal total weight */
3982
        return getWeight();
3983
    }
3984
3985
    /*
3986
     *   "What if" test.  Make the given changes temporarily, bypassing
3987
     *   any side effects that would normally be associated with the
3988
     *   changes; invokes the given callback; then remove the changes.
3989
     *   Returns the result of calling the callback function.
3990
     *   
3991
     *   The changes are expressed as pairs of argument values.  The first
3992
     *   value in a pair is a property, and the second is a new value for
3993
     *   the property.  For each pair, we'll set the given property to the
3994
     *   given value.  The setting is direct - we don't invoke any method,
3995
     *   because we don't want to cause any side effects at this point;
3996
     *   we're interested only in what the world would look like if the
3997
     *   given changes were to go into effect.
3998
     *   
3999
     *   A special property value of 'moveInto' can be used to indicate
4000
     *   that the object should be moved into another object for the test.
4001
     *   In this case, the second element of the pair is not a value to be
4002
     *   stored into the moveInto property, but instead the value is a new
4003
     *   location for the object.  We'll call the baseMoveInto method to
4004
     *   move the object to the given new location.
4005
     *   
4006
     *   In any case, after making the changes, we'll invoke the given
4007
     *   callback function, which we'll call with no arguments.
4008
     *   
4009
     *   Finally, on our way out, we'll restore the properties we changed
4010
     *   to their original values.  We once again do this without any side
4011
     *   effects.  Note that we restore the old values even if we exit
4012
     *   with an exception.  
4013
     */
4014
    whatIf(func, [changes])
4015
    {
4016
        local oldList;
4017
        local cnt;
4018
        local i;
4019
        
4020
        /* 
4021
         *   Allocate a vector with one slot per change, so that we have a
4022
         *   place to store the old values of the properties we're
4023
         *   changing.  Note that the argument list has two entries (prop,
4024
         *   value) for each change. 
4025
         */
4026
        cnt = changes.length();
4027
        oldList = new Vector(cnt / 2);
4028
4029
        /* run through the change list and make each change */
4030
        for (i = 1 ; i <= cnt ; i += 2)
4031
        {
4032
            local curProp;
4033
            local newVal;
4034
            
4035
            /* get the current property and new value */
4036
            curProp = changes[i];
4037
            newVal = changes[i+1];
4038
            
4039
            /* see what we have */
4040
            switch(curProp)
4041
            {
4042
            case &moveInto:
4043
                /*
4044
                 *   It's the special moveInto property.  This indicates
4045
                 *   that we are to move self into the given new 
4046
                 *   location.  First save the current location, then 
4047
                 *   use baseMoveInto to make the change without 
4048
                 *   triggering any side effects.  Note that if this 
4049
                 *   would create circular containment, we won't make 
4050
                 *   the move.
4051
                 */
4052
                oldList.append(saveLocation());
4053
                if (newVal != self && !newVal.isIn(self))
4054
                    baseMoveInto(newVal);
4055
                break;
4056
4057
            default:
4058
                /* 
4059
                 *   Anything else is a property to which we want to
4060
                 *   assign the given new value.  Remember the old value
4061
                 *   in our original values vector, then set the new value
4062
                 *   from the argument.  
4063
                 */
4064
                oldList.append(self.(curProp));
4065
                self.(curProp) = newVal;
4066
                break;
4067
            }
4068
        }
4069
4070
        /* 
4071
         *   the changes are now in effect - invoke the given callback
4072
         *   function to check things out under the new conditions, but
4073
         *   protect the code so that we are sure to restore things on the
4074
         *   way out 
4075
         */
4076
        try
4077
        {
4078
            /* invoke the given callback, returning the result */
4079
            return (func)();
4080
        }
4081
        finally
4082
        {
4083
            /* run through the change list and undo each change */
4084
            for (i = 1, local j = 1 ; i <= cnt ; i += 2, ++j)
4085
            {
4086
                local curProp;
4087
                local oldVal;
4088
            
4089
                /* get the current property and old value */
4090
                curProp = changes[i];
4091
                oldVal = oldList[j];
4092
            
4093
                /* see what we have */
4094
                switch(curProp)
4095
                {
4096
                case &moveInto:
4097
                    /* restore the location that we saved */
4098
                    restoreLocation(oldVal);
4099
                    break;
4100
4101
                default:
4102
                    /* restore the property value */
4103
                    self.(curProp) = oldVal;
4104
                    break;
4105
                }
4106
            }
4107
        }
4108
    }
4109
4110
    /*
4111
     *   Run a what-if test to see what would happen if this object were
4112
     *   being held directly by the given actor.
4113
     */
4114
    whatIfHeldBy(func, actor)
4115
    {
4116
        /* 
4117
         *   by default, simply run the what-if test with this object
4118
         *   moved into the actor's inventory
4119
         */
4120
        return whatIf(func, &moveInto, actor);
4121
    }
4122
4123
    /*
4124
     *   Check a proposed change in my bulk.  When this is called, the new
4125
     *   bulk should already be in effect (the best way to do this when
4126
     *   just making a check is via whatIf).
4127
     *   
4128
     *   This routine can be called during the 'check' or 'action' stages
4129
     *   of processing a command to determine if a change in my bulk would
4130
     *   cause a problem.  If so, we'll add a failure report and exit the
4131
     *   command.
4132
     *   
4133
     *   By default, notify our immediate container of the change to see
4134
     *   if there's any objection.  A change in an object's bulk typically
4135
     *   only aaffects its container or containers.
4136
     *   
4137
     *   The usual way to invoke this routine in a 'check' or 'action'
4138
     *   routine is with something like this:
4139
     *   
4140
     *      whatIf({: checkBulkChange()}, &inflated, true);
4141
     *   
4142
     *   This checks to see if the change in my bulk implied by changing
4143
     *   self.inflated to true would present a problem for my container,
4144
     *   terminating with a reportFailure+exit if so.  
4145
     */
4146
    checkBulkChange()
4147
    {
4148
        /*
4149
         *   Notify each of our containers of a change in our bulk; if
4150
         *   any container has a problem with our new bulk, it can
4151
         *   report a failure and exit.
4152
         */
4153
        forEachContainer(
4154
            {loc: loc.checkBulkChangeWithin(self)});
4155
    }
4156
4157
    /*
4158
     *   Check a bulk change of one of my direct contents, given by obj.
4159
     *   When this is called, 'obj' will be (tentatively) set to reflect
4160
     *   its proposed new bulk; if this routine doesn't like the new bulk,
4161
     *   it should issue a failure report and exit the command, which will
4162
     *   cancel the command that would have caused the change and will
4163
     *   prevent the proposed change from taking effect.
4164
     *   
4165
     *   By default, we'll do nothing; subclasses that are sensitive to
4166
     *   the bulks of their contents should override this.  
4167
     */
4168
    checkBulkChangeWithin(changingObj)
4169
    {
4170
    }
4171
4172
    /*
4173
     *   Get "bag of holding" affinities for the given list of objects.
4174
     *   For each item in the list, we'll get the item's bulk, and get
4175
     *   each bag's affinity for containing the item.  We'll note the bag
4176
     *   with the highest affinity.  Once we have the list, we'll put it
4177
     *   in descending order of affinity, and return the result as a
4178
     *   vector.  
4179
     */
4180
    getBagAffinities(lst)
4181
    {
4182
        local infoVec;
4183
        local bagVec; 
4184
        
4185
        /*
4186
         *   First, build a list of all of the bags of holding within me.
4187
         *   We start with the list of bags because we want to check each
4188
         *   object to see which bag we might want to put it in.  
4189
         */
4190
        bagVec = new Vector(10);
4191
        getBagsOfHolding(bagVec);
4192
4193
        /* if there are no bags of holding, there will be no affinities */
4194
        if (bagVec.length() == 0)
4195
            return [];
4196
4197
        /* create a vector to hold information on each child item */
4198
        infoVec = new Vector(lst.length());
4199
4200
        /* look at each child item */
4201
        foreach (local cur in lst)
4202
        {
4203
            local maxAff;
4204
            local bestBag;
4205
4206
            /* find the bag with the highest affinity for this item */
4207
            maxAff = 0;
4208
            bestBag = nil;
4209
            foreach (local bag in bagVec)
4210
            {
4211
                local aff;
4212
4213
                /* get this bag's affinity for this item */                
4214
                aff = bag.affinityFor(cur);
4215
4216
                /* 
4217
                 *   If this is the best so far, note it.  If this bag has
4218
                 *   an affinity of zero for this item, it means it
4219
                 *   doesn't want it at all, so don't even consider it in
4220
                 *   this case. 
4221
                 */
4222
                if (aff != 0 && (bestBag == nil || aff > maxAff))
4223
                {
4224
                    /* this is the best so far - note it */
4225
                    maxAff = aff;
4226
                    bestBag = bag;
4227
                }
4228
            }
4229
4230
            /* 
4231
             *   if we found a bag that wants this item, add it to our
4232
             *   list of results 
4233
             */
4234
            if (bestBag != nil)
4235
                infoVec.append(new BagAffinityInfo(
4236
                    cur, cur.getEncumberingBulk(self),
4237
                    maxAff, bestBag));
4238
        }
4239
        
4240
        /* sort the list in descending order of affinity */
4241
        infoVec = infoVec.sort(SortDesc, {a, b: a.compareAffinityTo(b)});
4242
4243
        /*
4244
         *   Go through the list and ensure that any item in the list
4245
         *   that's destined to go inside another item that's also in the
4246
         *   list gets put in the other item before the second item gets
4247
         *   put in its bag.  For example, if we have a credit card that
4248
         *   goes in a wallet, and a wallet that goes in a pocket, we want
4249
         *   to ensure that we put the credit card in the wallet before we
4250
         *   put the wallet in the pocket.
4251
         *   
4252
         *   The point of this reordering is that, in some cases, we might
4253
         *   not be able to move the first item once the second item has
4254
         *   been moved.  In our credit-card/wallet/pocket example, the
4255
         *   wallet might have to be closed to go in the pocket, at which
4256
         *   point we'd have to take it out again to put the credit card
4257
         *   into it, defeating the purpose of having put it away in the
4258
         *   first place.  We avoid these kinds of circular traps by
4259
         *   making sure the list is ordered such that we dispose of the
4260
         *   credit card first, then the wallet.
4261
         *   
4262
         *   To avoid looping forever in cases of circular containment,
4263
         *   don't allow any more moves than there are items in the list.  
4264
         */
4265
        for (local i = 1, local len = infoVec.length(), local moves = 0 ;
4266
             i <= len && moves <= len ; ++i)
4267
        {
4268
            /* get this item and its destination */
4269
            local cur = infoVec[i];
4270
            local dest = cur.bag_;
4271
            
4272
            /* 
4273
             *   check to see if the destination itself appears in the
4274
             *   list as an item to be bagged 
4275
             */
4276
            local idx = infoVec.indexWhich({x: x.obj_ == dest});
4277
4278
            /* 
4279
             *   if the destination item appears in the list before the
4280
             *   current item, move the current item before its
4281
             *   destination 
4282
             */
4283
            if (idx != nil && idx < i)
4284
            {
4285
                /* remove the current item from the list */
4286
                infoVec.removeElementAt(i);
4287
4288
                /* re-insert it just before its destination item */
4289
                infoVec.insertAt(idx, cur);
4290
4291
                /* count the move */
4292
                ++moves;
4293
4294
                /* 
4295
                 *   Now, back up and resume the scan from the item just
4296
                 *   after the item that moves our destination bag.  This
4297
                 *   will ensure that any items destined to go in the bag
4298
                 *   we just moved are caught.  (Although note that we
4299
                 *   start just after the destination item, rather than at
4300
                 *   the destination item, because the destination item
4301
                 *   can't be legitimately moved at this point: if it is,
4302
                 *   it's because we have a circular containment plan.  We
4303
                 *   obviously can't resolve circular containment into a
4304
                 *   linear ordering, so there's no point in even looking
4305
                 *   at the destination item again at this point.  If we
4306
                 *   have a circular chain that's several elements long,
4307
                 *   this won't avoid getting stuck; for that, we have to
4308
                 *   count on our 'moves' limit.)  
4309
                 */
4310
                i = idx + 1;
4311
            }
4312
        }
4313
4314
        /* return the result */
4315
        return infoVec;
4316
    }
4317
    
4318
    /*
4319
     *   Find all of the bags of holding contained within this object and
4320
     *   add them to the given vector.  By default, we'll simply recurse
4321
     *   into our children so they can add their bags of holding.  
4322
     */
4323
    getBagsOfHolding(vec)
4324
    {
4325
        /* 
4326
         *   if my contents can't be touched from outside, there's no
4327
         *   point in traversing our children 
4328
         */
4329
        if (transSensingIn(touch) != transparent)
4330
            return;
4331
4332
        /* add each of my children's bags to the list */
4333
        foreach (local cur in contents)
4334
            cur.getBagsOfHolding(vec);
4335
    }
4336
4337
    /*
4338
     *   The strength of the light the object is giving off, if indeed it
4339
     *   is giving off light.  This value should be one of the following:
4340
     *   
4341
     *   0: The object is giving off no light at all.
4342
     *   
4343
     *   1: The object is self-illuminating, but doesn't give off enough
4344
     *   light to illuminate any other objects.  This is suitable for
4345
     *   something like an LED digital clock.
4346
     *   
4347
     *   2: The object gives off dim light.  This level is bright enough to
4348
     *   illuminate nearby objects, but not enough to go through obscuring
4349
     *   media, and not enough for certain activities requiring strong
4350
     *   lighting, such as reading.
4351
     *   
4352
     *   3: The object gives off medium light.  This level is bright enough
4353
     *   to illuminate nearby objects, and is enough for most activities,
4354
     *   including reading and the like.  Traveling through an obscuring
4355
     *   medium reduces this level to dim (2).
4356
     *   
4357
     *   4: The object gives off strong light.  This level is bright enough
4358
     *   to illuminate nearby objects, and travel through an obscuring
4359
     *   medium reduces it to medium light (3).
4360
     *   
4361
     *   Note that the special value -1 is reserved as an invalid level,
4362
     *   used to flag certain events (such as the need to recalculate the
4363
     *   ambient light level from a new point of view).
4364
     *   
4365
     *   Most objects do not give off light at all.  
4366
     */
4367
    brightness = 0
4368
    
4369
    /*
4370
     *   Sense sizes of the object.  Each object has an individual size for
4371
     *   each sense.  By default, objects are medium for all senses; this
4372
     *   allows them to be sensed from a distance or through an obscuring
4373
     *   medium, but doesn't allow their details to be sensed.  
4374
     */
4375
    sightSize = medium
4376
    soundSize = medium
4377
    smellSize = medium
4378
    touchSize = medium
4379
4380
    /*
4381
     *   Determine whether or not the object has a "presence" in each
4382
     *   sense.  An object has a presence in a sense if an actor
4383
     *   immediately adjacent to the object could detect the object by the
4384
     *   sense alone.  For example, an object has a "hearing presence" if
4385
     *   it is making some kind of noise, and does not if it is silent.
4386
     *   
4387
     *   Presence in a given sense is an intrinsic (which does not imply
4388
     *   unchanging) property of the object, in that presence is
4389
     *   independent of the relationship to any given actor.  If an alarm
4390
     *   clock is ringing, it has a hearing presence, unconditionally; it
4391
     *   doesn't matter if the alarm clock is sealed inside a sound-proof
4392
     *   box, because whether or not a given actor has a sense path to the
4393
     *   object is a matter for a different computation.
4394
     *   
4395
     *   Note that presence doesn't control access: an actor might have
4396
     *   access to an object for a sense even if the object has no
4397
     *   presence in the sense.  Presence indicates whether or not the
4398
     *   object is actively emitting sensory data that would make an actor
4399
     *   aware of the object without specifically trying to apply the
4400
     *   sense to the object.
4401
     *   
4402
     *   By default, an object is visible and touchable, but does not emit
4403
     *   any sound or odor.  
4404
     */
4405
    sightPresence = true
4406
    soundPresence = nil
4407
    smellPresence = nil
4408
    touchPresence = true
4409
4410
    /*
4411
     *   My "contents lister."  This is a Lister object that we use to
4412
     *   display the contents of this object for room descriptions,
4413
     *   inventories, and the like.  
4414
     */
4415
    contentsLister = thingContentsLister
4416
4417
    /* 
4418
     *   the Lister to use when showing my contents as part of my own
4419
     *   description (i.e., for Examine commands) 
4420
     */
4421
    descContentsLister = thingDescContentsLister
4422
4423
    /* 
4424
     *   the Lister to use when showing my contents in response to a
4425
     *   LookIn command 
4426
     */
4427
    lookInLister = thingLookInLister
4428
4429
    /* 
4430
     *   my "in-line" contents lister - this is the Lister object that
4431
     *   we'll use to display my contents parenthetically as part of my
4432
     *   list entry in a second-level contents listing
4433
     */
4434
    inlineContentsLister = inlineListingContentsLister
4435
4436
    /*
4437
     *   My "special" contents lister.  This is the Lister to use to
4438
     *   display the special descriptions for objects that have special
4439
     *   descriptions when we're showing a room description for this
4440
     *   object. 
4441
     */
4442
    specialContentsLister = specialDescLister
4443
4444
4445
    /*
4446
     *   Determine if I can be sensed under the given conditions.  Returns
4447
     *   true if the object can be sensed, nil if not.  If this method
4448
     *   returns nil, this object will not be considered in scope for the
4449
     *   current conditions.
4450
     *   
4451
     *   By default, we return nil if the ambient energy level for the
4452
     *   object is zero.  If the ambient level is non-zero, we'll return
4453
     *   true in 'transparent' conditions, nil for 'opaque', and we'll let
4454
     *   the sense decide via its canObjBeSensed() method for any other
4455
     *   transparency conditions.  Note that 'ambient' as given here is the
4456
     *   ambient level *at the object*, not as seen along my sense path -
4457
     *   so this should NOT be given as the ambient value from a SenseInfo,
4458
     *   which has already been adjusted for the sense path.  
4459
     */
4460
    canBeSensed(sense, trans, ambient)
4461
    {
4462
        /* 
4463
         *   adjust the ambient level for the transparency path, if the
4464
         *   sense uses ambience at all 
4465
         */
4466
        if (sense.ambienceProp != nil)
4467
        {
4468
            /* 
4469
             *   adjust the ambient level for the transparency - if that
4470
             *   leaves a level of zero, the object can't be sensed 
4471
             */
4472
            if (adjustBrightness(ambient, trans) == 0)
4473
                return nil;
4474
        }
4475
4476
        /* check the viewing conditions */
4477
        switch(trans)
4478
        {
4479
        case transparent:
4480
        case attenuated:
4481
            /* 
4482
             *   under transparent or attenuated conditions, I appear as
4483
             *   myself 
4484
             */
4485
            return true;
4486
4487
        case obscured:
4488
        case distant:
4489
            /* 
4490
             *   ask the sense to determine if I can be sensed under these
4491
             *   conditions 
4492
             */
4493
            return sense.canObjBeSensed(self, trans, ambient);
4494
4495
        default:
4496
            /* for any other conditions, I can't be sensed at all */
4497
            return nil;
4498
        }
4499
    }
4500
4501
    /*
4502
     *   Determine if I can be sensed IN DETAIL in the given sense, with
4503
     *   the given SenseInfo description.  By default, an object's details
4504
     *   can be sensed if the sense path is 'transparent' or 'attenuated',
4505
     *   OR the object's scale in the sense is 'large'.
4506
     */
4507
    canDetailsBeSensed(sense, info, pov)
4508
    {
4509
        /* if the sense path is opaque, we can be sensed */
4510
        if (info == nil || info.trans == opaque)
4511
            return nil;
4512
4513
        /* 
4514
         *   if we have 'large' scale in the sense, our details can be
4515
         *   sensed under any conditions 
4516
         */
4517
        if (self.(sense.sizeProp) == large)
4518
            return true;
4519
4520
        /* 
4521
         *   we don't have 'large' scale in the sense, so we can be sensed
4522
         *   in detail only if the sense path is 'transparent' or
4523
         *   'attenuated' 
4524
         */
4525
        return (info.trans is in (transparent, attenuated));
4526
    }
4527
4528
    /*
4529
     *   Call a method on this object from the given point of view.  We'll
4530
     *   push the current point of view, call the method, then restore the
4531
     *   enclosing point of view. 
4532
     */
4533
    fromPOV(actor, pov, propToCall, [args])
4534
    {
4535
        /* push the new point of view */
4536
        pushPOV(actor, pov);
4537
4538
        /* make sure we pop the point of view no matter how we leave */
4539
        try
4540
        {
4541
            /* call the method */
4542
            self.(propToCall)(args...);
4543
        }
4544
        finally
4545
        {
4546
            /* restore the enclosing point of view on the way out */
4547
            popPOV();
4548
        }
4549
    }
4550
4551
    /*
4552
     *   Every Thing has a location, which is the Thing that contains this
4553
     *   object.  A Thing's location can only be a simple object
4554
     *   reference, or nil; it cannot be a list, and it cannot be a method.
4555
     *   
4556
     *   If the location is nil, the object does not exist anywhere in the
4557
     *   simulation's physical model.  A nil location can be used to
4558
     *   remove an object from the game world, temporarily or permanently.
4559
     *   
4560
     *   In general, the 'location' property should be declared for each
4561
     *   statically defined object (explicitly or implicitly via the '+'
4562
     *   syntax).  'location' is a private property - it should never be
4563
     *   evaluated or changed by any subclass or by any other object.
4564
     *   Only Thing methods may evaluate or change the 'location'
4565
     *   property.  So, you can declare a 'location' property when
4566
     *   defining an object, but you should essentially never refer to
4567
     *   'location' directly in any other context; instead, use the
4568
     *   location and containment methods (isIn, etc) when you want to
4569
     *   know an object's containment relationship to another object.  
4570
     */
4571
    location = nil
4572
4573
    /*
4574
     *   Get the direct container we have in common with the given object,
4575
     *   if any.  Returns at most one common container.  Returns nil if
4576
     *   there is no common location.  
4577
     */
4578
    getCommonDirectContainer(obj)
4579
    {
4580
        local found;
4581
4582
        /* we haven't found one yet */
4583
        found = nil;
4584
        
4585
        /* scan each of our containers for one in common with 'loc' */
4586
        forEachContainer(new function(loc) {
4587
            /*
4588
             *   If this location of ours is a direct container of the
4589
             *   other object, it is a common location, so note it.  
4590
             */
4591
            if (obj.isDirectlyIn(loc))
4592
                found = loc;
4593
        });
4594
4595
        /* return what we found */
4596
        return found;
4597
    }
4598
4599
    /*
4600
     *   Get the container (direct or indirect) we have in common with the
4601
     *   object, if any.  
4602
     */
4603
    getCommonContainer(obj)
4604
    {
4605
        local found;
4606
4607
        /* we haven't found one yet */
4608
        found = nil;
4609
        
4610
        /* scan each of our containers for one in common with 'loc' */
4611
        forEachContainer(new function(loc) {
4612
            /*
4613
             *   If this location of ours is a direct container of the
4614
             *   other object, it is a common location, so note it.  
4615
             */
4616
            if (obj.isIn(loc))
4617
                found = loc;
4618
        });
4619
4620
        /* if we found a common container, return it */
4621
        if (found != nil)
4622
            return found;
4623
4624
        /* 
4625
         *   we didn't find obj's container among our direct containers,
4626
         *   so try our direct container's containers 
4627
         */
4628
        forEachContainer(new function(loc) {
4629
            local cur;
4630
4631
            /* try finding for a common container of this container */
4632
            cur = loc.getCommonContainer(obj);
4633
4634
            /* if we found it, note it */
4635
            if (cur != nil)
4636
                found = cur;
4637
        });
4638
4639
        /* return what we found */
4640
        return found;
4641
    }
4642
4643
    /*
4644
     *   Get our "identity" object.  This is the object that we appear to
4645
     *   be an inseparable part of.
4646
     *   
4647
     *   In most cases, an object is its own identity object.  However,
4648
     *   there are times when the object that a player sees isn't the same
4649
     *   as the object that the parser resolves, because we're modeling a
4650
     *   single physical object with several programming objects.  For
4651
     *   example, a complex container has one or more secret internal
4652
     *   program objects representing the different sub-containers; as far
4653
     *   as the player is concerned, all of the sub-containers are just
4654
     *   aspects of the parent complex container, not separate object, so
4655
     *   the sub-containers all have the identity of the parent complex
4656
     *   container.
4657
     *   
4658
     *   By default, this is simply 'self'.  Objects that take their
4659
     *   effective identities from other objects must override this
4660
     *   accordingly.  
4661
     */
4662
    getIdentityObject() { return self; }
4663
4664
    /*
4665
     *   Am I a component of the given object?  The base Thing is not a
4666
     *   component of anything, so we'll simply return nil. 
4667
     */
4668
    isComponentOf(obj) { return nil; }
4669
4670
    /*
4671
     *   General initialization - this will be called during preinit so
4672
     *   that we can set up the initial values of any derived internal
4673
     *   properties.  
4674
     */
4675
    initializeThing()
4676
    {
4677
        /* initialize our location settings */
4678
        initializeLocation();
4679
4680
        /* 
4681
         *   if we're marked 'equivalent', it means we're interchangeable
4682
         *   in parser input with other objects with the same name; set up
4683
         *   our equivalence group listing if so 
4684
         */
4685
        if (isEquivalent)
4686
            initializeEquivalent();
4687
4688
        /* if we have a global parameter name, add it to the global table */
4689
        if (globalParamName != nil)
4690
            langMessageBuilder.nameTable_[globalParamName] = self;
4691
    }
4692
4693
    /*
4694
     *   Initialize my location's contents list - add myself to my
4695
     *   container during initialization
4696
     */
4697
    initializeLocation()
4698
    {
4699
        if (location != nil)
4700
            location.addToContents(self);
4701
    }
4702
4703
    /*
4704
     *   Initialize this class object for listing its instances that are
4705
     *   marked with isEquivalent.  We'll initialize a list group that
4706
     *   lists our equivalent instances as a group.
4707
     *   
4708
     *   Objects are grouped by their equivalence key - each set of objects
4709
     *   with the same key is part of the same group.  The key is
4710
     *   determined by the language module, but is usually just the basic
4711
     *   disambiguation name (the 'disambigName' property in English, for
4712
     *   example).  
4713
     */
4714
    initializeEquivalent()
4715
    {
4716
        /* 
4717
         *   If we're already in an equivalence group (because our key has
4718
         *   changed, for example, or due to inheritance), remove the
4719
         *   existing group from the group list.  If the group isn't
4720
         *   changing after all, we'll just add it back, so removing it
4721
         *   should be harmless.  
4722
         */
4723
        if (equivalentGrouper != nil)
4724
            listWith -= equivalentGrouper;
4725
4726
        /* look up our equivalence key in the global table of groupers */
4727
        equivalentGrouper = equivalentGrouperTable[equivalenceKey];
4728
4729
        /* if there's no grouper for our key, create one */
4730
        if (equivalentGrouper == nil)
4731
        {
4732
            /* create a new grouper */
4733
            equivalentGrouper = equivalentGrouperClass.createInstance();
4734
4735
            /* add it to the table */
4736
            equivalentGrouperTable[equivalenceKey] = equivalentGrouper;
4737
        }
4738
4739
        /* 
4740
         *   Add it to our list, as long as our listWith isn't already
4741
         *   defined as a method.  Note that we intentionally add it as the
4742
         *   last element, because an equivalent group is the most specific
4743
         *   kind of group possible.  
4744
         */
4745
        if (propType(&listWith) != TypeCode)
4746
            listWith += equivalentGrouper;
4747
    }
4748
4749
    /*
4750
     *   A static game-wide table of equivalence groups.  This has a table
4751
     *   of ListGroupEquivalent-derived objects, keyed by equivalence name.
4752
     *   Each group of objects with the same equivalence name is listed in
4753
     *   the same group and so has the same grouper object.  
4754
     */
4755
    equivalentGrouperTable = static (new LookupTable(32, 64))
4756
4757
    /* 
4758
     *   my equivalence grouper class - when we initialize, we'll create a
4759
     *   grouper of this class and store it in equivalentGrouper 
4760
     */
4761
    equivalentGrouperClass = ListGroupEquivalent
4762
4763
    /* 
4764
     *   Our equivalent item grouper.  During initialization, we will
4765
     *   create an equivalent grouper and store it in this property for
4766
     *   each class object that has instances marked with isEquivalent.
4767
     *   Note that this is stored with the class, because we want each of
4768
     *   our equivalent instances to share the same grouper object so that
4769
     *   they are listed together as a group.  
4770
     */
4771
    equivalentGrouper = nil
4772
4773
    /*
4774
     *   My contents.  This is a list of the objects that this object
4775
     *   directly contains.
4776
     */
4777
    contents = []
4778
4779
    /*
4780
     *   Get my associated noise object.  By default, this looks for an
4781
     *   item of class Noise directly within me.
4782
     */
4783
    getNoise()
4784
    {
4785
        /* 
4786
         *   Look for a Noise object among my direct contents.  Only look
4787
         *   for a noise with an active sound presence.  
4788
         */
4789
        return contents.valWhich(
4790
            {obj: obj.ofKind(Noise) && obj.soundPresence});
4791
    }
4792
4793
    /*
4794
     *   Get my associated odor object.  By default, this looks for an
4795
     *   item of class Odor directly within me. 
4796
     */
4797
    getOdor()
4798
    {
4799
        /* 
4800
         *   Look for an Odor object among my direct contents.  Only look
4801
         *   for an odor with an active smell presence. 
4802
         */
4803
        return contents.valWhich(
4804
            {obj: obj.ofKind(Odor) && obj.smellPresence});
4805
    }
4806
4807
    /*
4808
     *   Get a vector of all of my contents, recursively including
4809
     *   contents of contents.  
4810
     */
4811
    allContents()
4812
    {
4813
        local vec;
4814
        
4815
        /* start with an empty vector */
4816
        vec = new Vector(32);
4817
4818
        /* add all of my contents to the vector */
4819
        addAllContents(vec);
4820
4821
        /* return the result */
4822
        return vec;
4823
    }
4824
4825
    /*
4826
     *   Get a list of objects suitable for matching ALL in TAKE ALL FROM
4827
     *   <self>.  By default, this simply returns the list of everything in
4828
     *   scope that's directly inside 'self'.  
4829
     */
4830
    getAllForTakeFrom(scopeList)
4831
    {
4832
        /* 
4833
         *   include only objects contained within 'self' that aren't
4834
         *   components of 'self' 
4835
         */
4836
        return scopeList.subset(
4837
            {x: x != self && x.isDirectlyIn(self) && !x.isComponentOf(self)});
4838
    }
4839
4840
    /* 
4841
     *   add myself and all of my contents, recursively including contents
4842
     *   of contents, to a vector 
4843
     */
4844
    addAllContents(vec)
4845
    {
4846
        /* visit everything in my contents */
4847
        foreach (local cur in contents)
4848
        {
4849
            /* 
4850
             *   if this item is already in the vector, skip it - we've
4851
             *   already visited it and its contents
4852
             */
4853
            if (vec.indexOf(cur) != nil)
4854
                continue;
4855
            
4856
            /* add this item */
4857
            vec.append(cur);
4858
4859
            /* add this item's contents recursively */
4860
            cur.addAllContents(vec);
4861
        }
4862
    }
4863
4864
    /*
4865
     *   Show the contents of this object, as part of a recursive listing
4866
     *   generated as part of the description of our container, our
4867
     *   container's container, or any further enclosing container.
4868
     *   
4869
     *   If the object has any contents, we'll display a listing of the
4870
     *   contents.  This is used to display the object's contents as part
4871
     *   of the description of a room ("look around"), of an object
4872
     *   ("examine box"), or of an object's contents ("look in box").
4873
     *   
4874
     *   'options' is the set of flags that we'll pass to showList(), and
4875
     *   has the same meaning as for that function.
4876
     *   
4877
     *   'infoTab' is a lookup table of SenseInfo objects for the objects
4878
     *   that the actor to whom we're showing the contents listing can see
4879
     *   via the sight-like senses.
4880
     *   
4881
     *   This method should be overridden by any object that doesn't store
4882
     *   its contents using a simple 'contents' list property.  
4883
     */
4884
    showObjectContents(pov, lister, options, indent, infoTab)
4885
    {
4886
        local cont;
4887
4888
        /* get my listable contents */
4889
        cont = lister.getListedContents(self, infoTab);
4890
        
4891
        /* if the surviving list isn't empty, show it */
4892
        if (cont != [])
4893
            lister.showList(pov, self, cont, options, indent, infoTab, nil);
4894
    }
4895
4896
    /*
4897
     *   Show the contents of this object as part of an inventory listing.
4898
     *   By default, we simply use the same listing we do for the normal
4899
     *   contents listing. 
4900
     */
4901
    showInventoryContents(pov, lister, options, indent, infoTab)
4902
    {
4903
        /* by default, use the normal room/object contents listing */
4904
        showObjectContents(pov, lister, options, indent, infoTab);
4905
    }
4906
4907
    /*
4908
     *   Get my listed contents for recursive object descriptions.  This
4909
     *   returns the list of the direct contents that we'll mention when
4910
     *   we're examining our container's container, a further enclosing
4911
     *   container, or the enclosing room.
4912
     *   
4913
     *   By default, we'll return the same list we'll display on direct
4914
     *   examination of this object.
4915
     */
4916
    getListedContents(lister, infoTab)
4917
    {
4918
        /* 
4919
         *   return the contents we'd list when directly examining self,
4920
         *   limited to the listable contents 
4921
         */
4922
        return getContentsForExamine(lister, infoTab)
4923
            .subset({x: lister.isListed(x)});
4924
    }
4925
4926
    /*
4927
     *   Get the listed contents in a direct examination of this object.
4928
     *   By default, this simply returns a list of all of our direct
4929
     *   contents that are included in the info table.  
4930
     */
4931
    getContentsForExamine(lister, infoTab)
4932
    {
4933
        /*
4934
         *   return only my direct contents that are found in the infoTab,
4935
         *   since these are the objects that can be sensed from the
4936
         *   actor's point of view 
4937
         */
4938
        return contents.subset({x: infoTab[x] != nil});
4939
    }
4940
4941
    /* 
4942
     *   Is this a "top-level" location?  A top-level location is an
4943
     *   object which doesn't have another container, so its 'location'
4944
     *   property is nil, but which is part of the game universe anyway.
4945
     *   In most cases, a top-level location is simply a Room, since the
4946
     *   network of rooms makes up the game's map.
4947
     *   
4948
     *   If an object has no location and is not itself a top-level
4949
     *   location, then the object is not part of the game world.  It's
4950
     *   sometimes useful to remove objects from the game world, such as
4951
     *   when they're destroyed within the context of the game.  
4952
     */
4953
    isTopLevel = nil
4954
4955
    /*
4956
     *   Determine if I'm is inside another Thing.  Returns true if this
4957
     *   object is contained within 'obj', directly or indirectly (that
4958
     *   is, this returns true if my immediate container is 'obj', OR my
4959
     *   immediate container is in 'obj', recursively defined).
4960
     *   
4961
     *   isIn(nil) returns true if this object is "outside" the game
4962
     *   world, which means that the object is not reachable from anywhere
4963
     *   in the game map and is thus not part of the simulation.  This is
4964
     *   the case if our outermost container is NOT a top-level object, as
4965
     *   indicated by its isTopLevel property.  If we're inside an object
4966
     *   marked as a top-level object, or we're inside an object that's
4967
     *   inside a top-level object (and so on), then we're part of the
4968
     *   game world, so isIn(nil) will return nil.  If our outermost
4969
     *   container is has a nil isTopLevel property, isIn(nil) will return
4970
     *   true.
4971
     *   
4972
     *   Note that our notion of "in" is not limited to enclosing
4973
     *   containment, because the same containment hierarchy is used to
4974
     *   represent all types of containment relationships, including
4975
     *   things being "on" other things and part of other things.  
4976
     */
4977
    isIn(obj)
4978
    {
4979
        local loc = location;
4980
        
4981
        /* if we have no location, we're not inside any object */
4982
        if (loc == nil)
4983
        {
4984
            /*
4985
             *   We have no container, so there are two possibilities:
4986
             *   either we're not part of the game world at all, or we're
4987
             *   a top-level object, which is an object that's explicitly
4988
             *   part of the game world and at the top of the containment
4989
             *   tree.  Our 'isTopLevel' property determines which it is.
4990
             *   
4991
             *   If they're asking us if we're inside 'nil', then what
4992
             *   they want to know is if we're outside the game world.  If
4993
             *   we're not a top-level object, we are outside the game
4994
             *   world, so isIn(nil) is true; if we are a top-level
4995
             *   object, then we're explicitly part of the game world, so
4996
             *   isIn(nil) is false.
4997
             *   
4998
             *   If they're asking us if we're inside any non-nil object,
4999
             *   then they simply want to know if we're inside that
5000
             *   object.  So, if 'obj' is not nil, we must return nil:
5001
             *   we're not in any object, so we can't be in 'obj'.  
5002
             */
5003
            if (obj == nil)
5004
            {
5005
                /* 
5006
                 *   they want to know if we're outside the simulation
5007
                 *   entirely: return true if we're NOT a top-level
5008
                 *   object, nil otherwise 
5009
                 */
5010
                return !isTopLevel;
5011
            }
5012
            else
5013
            {
5014
                /*
5015
                 *   they want to know if we're inside some specific
5016
                 *   object; we can't be, because we're not in any object 
5017
                 */
5018
                return nil;
5019
            }
5020
        }
5021
5022
        /* if obj is my immediate container, I'm obviously in it */
5023
        if (loc == obj)
5024
            return true;
5025
5026
        /* I'm in obj if my container is in obj */
5027
        return loc.isIn(obj);
5028
    }
5029
5030
    /*
5031
     *   Determine if I'm directly inside another Thing.  Returns true if
5032
     *   this object is contained directly within obj.  Returns nil if
5033
     *   this object isn't directly within obj, even if it is indirectly
5034
     *   in obj (i.e., its container is directly or indirectly in obj).  
5035
     */
5036
    isDirectlyIn(obj)
5037
    {
5038
        /* I'm directly in obj only if it's my immediate container */
5039
        return location == obj;
5040
    }
5041
5042
    /*
5043
     *   Determine if I'm "nominally" inside the given Thing.  Returns true
5044
     *   if the object is actually within the given object, OR the object
5045
     *   is a room part and I'm nominally in the room part.  
5046
     */
5047
    isNominallyIn(obj)
5048
    {
5049
        /* if I'm actually in the given object, I'm nominally in it as well */
5050
        if (isIn(obj))
5051
            return true;
5052
5053
        /* 
5054
         *   if the object is a room part, and we're nominally in the room
5055
         *   part, then we're nominally in the object 
5056
         */
5057
        if (obj.ofKind(RoomPart) && isNominallyInRoomPart(obj))
5058
            return true;
5059
5060
        /* we're not nominally in the object */
5061
        return nil;
5062
    }
5063
5064
    /*
5065
     *   Determine if this object is contained within an item fixed in
5066
     *   place within the given location.  We'll check our container to
5067
     *   see if its contents are within an object fixed in place in the
5068
     *   given location.
5069
     *   
5070
     *   This is a rather specific check that might seem a bit odd, but
5071
     *   for some purposes it's useful to treat objects within fixed
5072
     *   containers in a location as though they were in the location
5073
     *   itself, because fixtures of a location are to some extent parts
5074
     *   of the location.  
5075
     */
5076
    isInFixedIn(loc)
5077
    {
5078
        /* return true if my location's contents are in fixed things */
5079
        return location != nil && location.contentsInFixedIn(loc);
5080
    }
5081
5082
    /* Am I either inside 'obj', or equal to 'obj'?  */
5083
    isOrIsIn(obj) { return self == obj || isIn(obj); }
5084
5085
    /*
5086
     *   Are my contents within a fixed item that is within the given
5087
     *   location?  By default, we return nil because we are not ourselves
5088
     *   fixed. 
5089
     */
5090
    contentsInFixedIn(loc) { return nil; }
5091
5092
    /*
5093
     *   Determine if I'm "held" by an actor, for the purposes of being
5094
     *   manipulated in an action.  In most cases, an object is considered
5095
     *   held by an actor if it's directly within the actor's inventory,
5096
     *   because the actor's direct inventory represents the contents of
5097
     *   the actors hands (or equivalent).
5098
     *   
5099
     *   Some classes might override this to change the definition of
5100
     *   "held" to include things not directly in the actor's inventory or
5101
     *   exclude things directly in the inventory.  For example, an item
5102
     *   being worn is generally not considered held even though it might
5103
     *   be in the direct inventory, and a key on a keyring is considered
5104
     *   held if the keyring is being held.  
5105
     */
5106
    isHeldBy(actor)
5107
    {
5108
        /* 
5109
         *   by default, an object is held if and only if it's in the
5110
         *   direct inventory of the actor 
5111
         */
5112
        return isDirectlyIn(actor);
5113
    }
5114
5115
    /*
5116
     *   Are we being held by the given actor for the purposes of the
5117
     *   objHeld precondition?  By default, and in almost all cases, this
5118
     *   simply returns isHeldBy().  In some rare cases, it might make
5119
     *   sense to consider an object to meet the objHeld condition even if
5120
     *   isHeldBy returns nil; for example, an actor's hands and other
5121
     *   body parts can't be considered to be held, but they also don't
5122
     *   need to be for any command operating on them.  
5123
     */
5124
    meetsObjHeld(actor) { return isHeldBy(actor); }
5125
5126
    /*
5127
     *   Add to a vector all of my contents that are directly held when
5128
     *   I'm being directly held.  This is used to add the direct contents
5129
     *   of an item to scope when the item itself is being directly held.
5130
     *   
5131
     *   In most cases, we do nothing.  Certain types of objects override
5132
     *   this because they consider their contents to be held if they're
5133
     *   held.  For example, a keyring considers all of its keys to be
5134
     *   held if the keyring itself is held, because the keys are attached
5135
     *   to the keyring rather than contained within it.  
5136
     */
5137
    appendHeldContents(vec)
5138
    {
5139
        /* by default, do nothing */
5140
    }
5141
5142
    /*
5143
     *   Determine if I'm "owned" by another object.  By default, if we
5144
     *   have an explicit owner, then we are owned by 'obj' if and only if
5145
     *   'obj' is our explicit owner; otherwise, if 'obj' is our immediate
5146
     *   location, and our immediate location can own us (as reported by
5147
     *   obj.canOwn(self)), then 'obj' owns us; otherwise, 'obj' owns us
5148
     *   if our immediate location CANNOT own us AND our immediate
5149
     *   location is owned by 'obj'.  This last case is tricky: it means
5150
     *   that if we're inside something other than 'obj' that can own us,
5151
     *   such as another actor, then 'obj' doesn't own us because our
5152
     *   immediate location does; it also means that if we're inside an
5153
     *   object that has an explicit owner rather than an owner based on
5154
     *   location, we have the same explicit owner, so a dollar bill
5155
     *   inside Bob's wallet which is in turn being carried by Charlie is
5156
     *   owned by Bob, not Charlie.
5157
     *   
5158
     *   This is used to determine ownership for the purpose of
5159
     *   possessives in commands.  Ownership is not always exclusive: it
5160
     *   is possible for a given object to have multiple owners in some
5161
     *   cases.  For example, if Bob and Bill are both sitting on a couch,
5162
     *   the couch could be referred to as "bob's couch" or "bill's
5163
     *   couch", so the couch is owned by both Bob and Bill.  It is also
5164
     *   possible for an object to be unowned.
5165
     *   
5166
     *   In most cases, ownership is a function of location (possession is
5167
     *   nine-tenths of the law, as they say), but not always; in some
5168
     *   cases, an object has a particular owner regardless of its
5169
     *   location, such as "bob's wallet".  This default implementation
5170
     *   allows for ownership by location, as well as explicit ownership,
5171
     *   with explicit ownership (as indicated by the self.owner property)
5172
     *   taking precedence.  
5173
     */
5174
    isOwnedBy(obj)
5175
    {
5176
        local cont;
5177
        
5178
        /* 
5179
         *   if I have an explicit owner, then obj is my owner if it
5180
         *   matches my explicit owner 
5181
         */
5182
        if (owner != nil)
5183
            return owner == obj;
5184
        
5185
        /*
5186
         *   Check my immediate container to see if it's the owner in
5187
         *   question.  
5188
         */
5189
        if (isDirectlyIn(obj))
5190
        {
5191
            /* 
5192
             *   My immediate container is the owner of interest.  If this
5193
             *   container can own me, then I'm owned by it because we
5194
             *   didn't find any other owners first.  Otherwise, I'm not
5195
             *   owned by it because it can't own me in the first place. 
5196
             */
5197
            return obj.canOwn(self);
5198
        }
5199
5200
        /* presume we have a single-location container */
5201
        cont = location;
5202
5203
        /* check to see if we have no single location */
5204
        if (cont == nil)
5205
        {
5206
            /* 
5207
             *   I have no location, so either I'm not inside anything at
5208
             *   all, or I have multiple locations.  If we're indirectly
5209
             *   in 'obj', then proceed up the containment tree branch by
5210
             *   which 'obj' contains us.  If we're not even indirectly in
5211
             *   'obj', then there's no containment relationship that
5212
             *   establishes ownership.  
5213
             */
5214
            if (isIn(obj))
5215
            {
5216
                /* 
5217
                 *   we're indirectly in 'obj', so get the containment
5218
                 *   branch on which we're contained by 'obj' 
5219
                 */
5220
                forEachContainer(new function(x)
5221
                {
5222
                    if (x == obj || x.isIn(obj))
5223
                        cont = x;
5224
                });
5225
            }
5226
            else
5227
            {
5228
                /* 
5229
                 *   we're not in 'obj', so there's no containment
5230
                 *   relationship that establishes ownership 
5231
                 */
5232
                return nil;
5233
            }
5234
        }
5235
5236
        /*
5237
         *   My immediate container is not the object of interest.  If
5238
         *   this container can own me, then I'm owned by this container
5239
         *   and NOT by obj.  
5240
         */
5241
        if (cont.canOwn(self))
5242
            return nil;
5243
5244
        /*
5245
         *   My container can't own me, so it's not my owner, so our owner
5246
         *   is my container's owner - thus, I'm owned by obj if my
5247
         *   location is owned by obj.  
5248
         */
5249
        return cont.isOwnedBy(obj);
5250
    }
5251
5252
    /*
5253
     *   My explicit owner.  By default, objects do not have explicit
5254
     *   owners, which means that the owner at any given time is
5255
     *   determined by the object's location - my innermost container that
5256
     *   can own me is my owner.
5257
     *   
5258
     *   However, in some cases, an object is inherently owned by a
5259
     *   particular other object (usually an actor), and this is invariant
5260
     *   even when the object moves to a new location not within the
5261
     *   owner.  For such cases, this property can be set to the explicit
5262
     *   owner object, which will cause self.isOwnedBy(obj) to return true
5263
     *   if and only if obj == self.owner.  
5264
     */
5265
    owner = nil
5266
5267
    /*
5268
     *   Get the "nominal owner" of this object.  This is the owner that
5269
     *   we report for the object if asked to distinguish this object from
5270
     *   another via the OwnershipDistinguisher.  Note that the nominal
5271
     *   owner is not necessarily the only owner, because an object can
5272
     *   have multiple owners in some cases; however, the nominal owner
5273
     *   must always be an owner, in that isOwnedBy(getNominalOwner())
5274
     *   should always return true.
5275
     *   
5276
     *   By default, if we have an explicit owner, we'll return that.
5277
     *   Otherwise, if our immediate container can own us, we'll return
5278
     *   our immediate container.  Otherwise, we'll return our immediate
5279
     *   container's nominal owner.  Note that this last case means that a
5280
     *   dollar bill inside Bob's wallet will be Bob's dollar bill, even
5281
     *   if Bob's wallet is currently being carried by another actor.  
5282
     */
5283
    getNominalOwner()
5284
    {
5285
        /* if we have an explicit owner, return that */
5286
        if (owner != nil)
5287
            return owner;
5288
5289
        /* if we have no location, we have no owner */
5290
        if (location == nil)
5291
            return nil;
5292
5293
        /* if our immediate location can own us, return it */
5294
        if (location.canOwn(self))
5295
            return location;
5296
5297
        /* return our immediate location's owner */
5298
        return location.getNominalOwner();
5299
    }
5300
5301
    /*
5302
     *   Can I own the given object?  By default, objects cannot own other
5303
     *   objects.  This can be overridden when ownership is desired.
5304
     *   
5305
     *   This doesn't determine that we *do* own the given object, but
5306
     *   only that we *can* own the given object.
5307
     */
5308
    canOwn(obj) { return nil; }
5309
5310
    /*
5311
     *   Get the carrying actor.  This is the nearest enclosing location
5312
     *   that's an actor. 
5313
     */
5314
    getCarryingActor()
5315
    {
5316
        /* if I don't have a location, there's no carrier */
5317
        if (location == nil)
5318
            return nil;
5319
5320
        /* if my location is an actor, it's the carrying actor */
5321
        if (location.isActor)
5322
            return location;
5323
5324
        /* return my location's carrying actor */
5325
        return location.getCarryingActor();
5326
    }
5327
5328
    /*
5329
     *   Try making the current command's actor hold me.  By default,
5330
     *   we'll simply try a "take" command on the object.  
5331
     */
5332
    tryHolding()
5333
    {
5334
        /*   
5335
         *   Try an implicit 'take' command.  If the actor is carrying the
5336
         *   object indirectly, make the command "take from" instead,
5337
         *   since what we really want to do is take the object out of its
5338
         *   container.  
5339
         */
5340
        if (isIn(gActor))
5341
            return tryImplicitAction(TakeFrom, self, location);
5342
        else
5343
            return tryImplicitAction(Take, self);
5344
    }
5345
5346
    /*
5347
     *   Try moving the given object into this object, with an implied
5348
     *   command.  By default, since an ordinary Thing doesn't have a way
5349
     *   of adding new contents by a player command, this does nothing.
5350
     *   Containers and other objects that can hold new contents can
5351
     *   override this as appropriate.
5352
     */
5353
    tryMovingObjInto(obj) { return nil; }
5354
5355
    /* 
5356
     *   Report a failure of the condition that tryMovingObjInto tries to
5357
     *   bring into effect.  By default, this simply says that the object
5358
     *   must be in 'self'.  Some objects might want to override this when
5359
     *   they describe containment specially; for example, an actor might
5360
     *   want to say that the actor "must be carrying" the object.  
5361
     */
5362
    mustMoveObjInto(obj) { reportFailure(&mustBeInMsg, obj, self); }
5363
5364
    /*
5365
     *   Add an object to my contents.
5366
     *   
5367
     *   Note that this should NOT be overridden to cause side effects -
5368
     *   if side effects are desired when inserting a new object into my
5369
     *   contents, use notifyInsert().  This routine is not allowed to
5370
     *   cause side effects because it is sometimes necessary to bypass
5371
     *   side effects when moving an item.  
5372
     */
5373
    addToContents(obj)
5374
    {
5375
        /* add the object to my contents list */
5376
        contents += obj;
5377
    }
5378
5379
    /*
5380
     *   Remove an object from my contents.
5381
     *   
5382
     *   Do NOT override this routine to cause side effects.  If side
5383
     *   effects are desired when removing an object, use notifyRemove().  
5384
     */
5385
    removeFromContents(obj)
5386
    {
5387
        /* remove the object from my contents list */
5388
        contents -= obj;
5389
    }
5390
5391
    /*
5392
     *   Save my location for later restoration.  Returns a value suitable
5393
     *   for passing to restoreLocation. 
5394
     */
5395
    saveLocation()
5396
    {
5397
        /* 
5398
         *   I'm an ordinary object with only one location, so simply
5399
         *   return the location 
5400
         */
5401
        return location;
5402
    }
5403
5404
    /*
5405
     *   Restore a previously saved location.  Does not trigger any side
5406
     *   effects. 
5407
     */
5408
    restoreLocation(oldLoc)
5409
    {
5410
        /* move myself without side effects into my old container */
5411
        baseMoveInto(oldLoc);
5412
    }
5413
5414
    /*
5415
     *   Move this object to a new container.  Before the move is actually
5416
     *   performed, we notify the items in the movement path of the
5417
     *   change, then we send notifyRemove and notifyInsert messages to
5418
     *   the old and new containment trees, respectively.
5419
     *   
5420
     *   All notifications are sent before the object is actually moved.
5421
     *   This means that the current game state at the time of the
5422
     *   notifications reflects the state before the move.  
5423
     */
5424
    moveInto(newContainer)
5425
    {
5426
        /* notify the path */
5427
        moveIntoNotifyPath(newContainer);
5428
5429
        /* perform the main moveInto operations */
5430
        mainMoveInto(newContainer);
5431
    }
5432
5433
    /*
5434
     *   Move this object to a new container as part of travel.  This is
5435
     *   almost the same as the regular moveInto(), but does not attempt
5436
     *   to calculate and notify the sense path to the new location.  We
5437
     *   omit the path notification because travel is done via travel
5438
     *   connections, which are not necessarily the same as sense
5439
     *   connections.  
5440
     */
5441
    moveIntoForTravel(newContainer)
5442
    {
5443
        /* 
5444
         *   perform the main moveInto operations, omitting the path
5445
         *   notification 
5446
         */
5447
        mainMoveInto(newContainer);
5448
    }
5449
5450
    /*
5451
     *   Main moveInto - this is the mid-level containment changer; this
5452
     *   routine sends notifications to the old and new container, but
5453
     *   doesn't notify anything along the connecting sense path. 
5454
     */
5455
    mainMoveInto(newContainer)
5456
    {
5457
        /* notify my container that I'm being removed */
5458
        sendNotifyRemove(self, newContainer, &notifyRemove);
5459
5460
        /* notify my new container that I'm about to be added */
5461
        if (newContainer != nil)
5462
            newContainer.sendNotifyInsert(self, newContainer, &notifyInsert);
5463
5464
        /* notify myself that I'm about to be moved */
5465
        notifyMoveInto(newContainer);
5466
5467
        /* perform the basic containment change */
5468
        baseMoveInto(newContainer);
5469
5470
        /* note that I've been moved */
5471
        moved = true;
5472
    }
5473
5474
    /*
5475
     *   Base moveInto - this is the low-level containment changer; this
5476
     *   routine does not send any notifications to any containers, and
5477
     *   does not mark the object as moved.  This form should be used only
5478
     *   for internal library-initiated state changes, since it bypasses
5479
     *   all of the normal side effects of moving an object to a new
5480
     *   container.  
5481
     */
5482
    baseMoveInto(newContainer)
5483
    {
5484
        /* if I have a container, remove myself from its contents list */
5485
        if (location != nil)
5486
            location.removeFromContents(self);
5487
5488
        /* remember my new location */
5489
        location = newContainer;
5490
5491
        /*
5492
         *   if I'm not being moved into nil, add myself to the
5493
         *   container's contents
5494
         */
5495
        if (location != nil)
5496
            location.addToContents(self);
5497
    }
5498
5499
    /*
5500
     *   Notify each element of the move path of a moveInto operation. 
5501
     */
5502
    moveIntoNotifyPath(newContainer)
5503
    {
5504
        local path;
5505
        
5506
        /* calculate the path; if there isn't one, there's nothing to do */
5507
        if ((path = getMovePathTo(newContainer)) == nil)
5508
            return;
5509
5510
        /*
5511
         *   We must fix up the path's final element, depending on whether
5512
         *   we're moving into the new container from the outside or from
5513
         *   the inside.  
5514
         */
5515
        if (isIn(newContainer))
5516
        {
5517
            /*
5518
             *   We're already in the new container, so we're moving into
5519
             *   the new container from the inside.  The final element of
5520
             *   the path is the new container, but since we're stopping
5521
             *   within the new container, we don't need to traverse out
5522
             *   of it.  Simply remove the final two elements of the path,
5523
             *   since we're not going to make this traversal when moving
5524
             *   the object.  
5525
             */
5526
            path = path.sublist(1, path.length() - 2);
5527
        }
5528
        else
5529
        {
5530
            /*   
5531
             *   We're moving into the new container from the outside.
5532
             *   Since we calculated the path to the container, we must
5533
             *   now add a final element to traverse into the container;
5534
             *   the final object in the path doesn't matter, since it's
5535
             *   just a placeholder for the new item inside the container 
5536
             */
5537
            path += [PathIn, nil];
5538
        }
5539
5540
        /* traverse the path, sending a notification to each element */
5541
        traversePath(path, new function(target, op) {
5542
            /* notify this path element */
5543
            target.notifyMoveViaPath(self, newContainer, op);
5544
5545
            /* continue the traversal */
5546
            return true;
5547
        });
5548
    }
5549
5550
    /*
5551
     *   Call a function on each container.  If we have no location, we
5552
     *   won't invoke the function at all.  We'll invoke the function as
5553
     *   follows:
5554
     *   
5555
     *   (func)(location, args...)  
5556
     */
5557
    forEachContainer(func, [args])
5558
    {
5559
        /* call the function on our location, if we have one */
5560
        if (location != nil)
5561
            (func)(location, args...);
5562
    }
5563
5564
    /* 
5565
     *   Call a function on each *connected* container.  For most objects,
5566
     *   this is the same as forEachContainer, but objects that don't
5567
     *   connect their containers for sense purposes would do nothing
5568
     *   here. 
5569
     */
5570
    forEachConnectedContainer(func, [args])
5571
    {
5572
        /* by default, use the standard forEachContainer handling */
5573
        forEachContainer(func, args...);
5574
    }
5575
5576
    /* 
5577
     *   Get a list of all of my connected containers.  This simply returns
5578
     *   the list that forEachConnectedContainer() iterates over.  
5579
     */
5580
    getConnectedContainers()
5581
    {
5582
        local loc;
5583
5584
        /* 
5585
         *   if I have a non-nil location, return a list containing it;
5586
         *   otherwise, simply return an empty list 
5587
         */
5588
        return ((loc = location) == nil ? [] : [loc]);
5589
    }
5590
5591
    /*
5592
     *   Clone this object for inclusion in a MultiInstance's contents
5593
     *   tree.  When we clone an instance object for a MultiInstance, we'll
5594
     *   also clone its contents, and their contents, and so on.  This
5595
     *   routine creates a private copy of all of our contents.  
5596
     */
5597
    cloneMultiInstanceContents()
5598
    {
5599
        local origContents;
5600
        
5601
        /* 
5602
         *   Remember my current contents list, then clear it out.  We want
5603
         *   a private copy of everything in our contents list, so we want
5604
         *   to forget our references to the template contents and start
5605
         *   from scratch.  
5606
         */
5607
        origContents = contents;
5608
        contents = [];
5609
5610
        /* clone each entry in our contents list */
5611
        foreach (local cur in origContents)
5612
            cur.cloneForMultiInstanceContents(self);
5613
    }
5614
5615
    /* 
5616
     *   Create a clone for inclusion in MultiInstance contents.  We'll
5617
     *   recursively clone our own contents. 
5618
     */
5619
    cloneForMultiInstanceContents(loc)
5620
    {
5621
        /* create a new instance of myself */
5622
        local cl = createInstance();
5623
5624
        /* move it into the new location */
5625
        cl.baseMoveInto(loc);
5626
5627
        /* clone its contents */
5628
        cl.cloneMultiInstanceContents();
5629
    }
5630
5631
    /*
5632
     *   Determine if I'm a valid staging location for the given nested
5633
     *   room destination 'dest'.  This is called when the actor is
5634
     *   attempting to enter the nested room 'dest', and the travel
5635
     *   handlers find that we're the staging location for the room.  (A
5636
     *   "staging location" is the location the actor is required to
5637
     *   occupy immediately before moving into the destination.)
5638
     *   
5639
     *   If this object is a valid staging location, the routine should
5640
     *   simply do nothing.  If this object isn't valid as a staging
5641
     *   location, this routine should display an appropriate message and
5642
     *   terminate the command with 'exit'.
5643
     *   
5644
     *   An arbitrary object can't be a staging location, simply because
5645
     *   an actor can't enter an arbitrary object.  So, by default, we'll
5646
     *   explain that we can't enter this object.  If the destination is
5647
     *   contained within us, we'll provide a more specific explanation
5648
     *   indicating that the problem is that the destination is within us.
5649
     */
5650
    checkStagingLocation(dest)
5651
    {
5652
        /* 
5653
         *   if the destination is within us, explain specifically that
5654
         *   this is the problem 
5655
         */
5656
        if (dest.isIn(self))
5657
            reportFailure(&invalidStagingContainerMsg, self, dest);
5658
        else
5659
            reportFailure(&invalidStagingLocationMsg, self);
5660
5661
        /* terminate the command */
5662
        exit;
5663
    }
5664
5665
    /*
5666
     *   by default, objects don't accept commands 
5667
     */
5668
    acceptCommand(issuingActor)
5669
    {
5670
        /* report that we don't accept commands */
5671
        gLibMessages.cannotTalkTo(self, issuingActor);
5672
5673
        /* tell the caller we don't accept commands */
5674
        return nil;
5675
    }
5676
5677
    /*
5678
     *   by default, most objects are not logical targets for commands 
5679
     */
5680
    isLikelyCommandTarget = nil
5681
5682
    /*
5683
     *   Get my extra scope items.  This is a list of any items that we
5684
     *   want to add to command scope (i.e., the set of all items to which
5685
     *   an actor is allowed to refer with noun phrases) when we are
5686
     *   ourselves in the command scope.  Returns a list of the items to
5687
     *   add (or just [] if there are no items to add).
5688
     *   
5689
     *   By default, we add nothing.  
5690
     */
5691
    getExtraScopeItems(actor) { return []; }
5692
5693
    /*
5694
     *   Generate a lookup table of all of the objects connected by
5695
     *   containment to this object.  This table includes all containment
5696
     *   connections, even through closed containers and the like.
5697
     *   
5698
     *   The table is keyed by object; the associated values are
5699
     *   meaningless, as all that matters is whether or not an object is
5700
     *   in the table.  
5701
     */
5702
    connectionTable()
5703
    {
5704
        local tab;
5705
        local cache;
5706
5707
        /* if we already have a cached connection list, return it */
5708
        if ((cache = libGlobal.connectionCache) != nil
5709
            && (tab = cache[self]) != nil)
5710
            return tab;
5711
5712
        /* remember the point of view globally, in case anyone needs it */
5713
        senseTmp.pointOfView = self;
5714
5715
        /* create a lookup table for the results */
5716
        tab = new LookupTable(32, 64);
5717
5718
        /* add everything connected to me */
5719
        addDirectConnections(tab);
5720
5721
        /* cache the list, if we caching is active */
5722
        if (cache != nil)
5723
            cache[self] = tab;
5724
5725
        /* return the table */
5726
        return tab;
5727
    }
5728
5729
    /*
5730
     *   Add this item and its direct containment connections to the lookup
5731
     *   table.  This must recursively add all connected objects that
5732
     *   aren't already in the table.  
5733
     */
5734
    addDirectConnections(tab)
5735
    {
5736
        local cur;
5737
        
5738
        /* add myself */
5739
        tab[self] = true;
5740
5741
        /* 
5742
         *   Add my CollectiveGroup objects, if any.  We don't have to
5743
         *   traverse into the CollectiveGroup objects, since they don't
5744
         *   connect us or itself to anything else; our connection to a
5745
         *   collective group is one-way.  
5746
         */
5747
        foreach (cur in collectiveGroups)
5748
            tab[cur] = true;
5749
5750
        /* 
5751
         *   Add my contents to the table.  Loop over our contents list,
5752
         *   and add each item that's not already in the table, then
5753
         *   recursively everything connected to that item.
5754
         *   
5755
         *   Note that we use a C-style 'for' loop to iterate over the list
5756
         *   by index, rather than a more modern tads-style 'foreach' loop,
5757
         *   purely for performance reasons: this code is called an awful
5758
         *   lot, and the iteration by loop index is very slightly faster.
5759
         *   A 'foreach' requires calling a couple of methods of an
5760
         *   iterator object each time through the loop, whereas we can do
5761
         *   the index-based 'for' loop entirely with cached local
5762
         *   variables.  The performance difference is negligible for one
5763
         *   time through any given loop; it only matters to us here
5764
         *   because this routine tends to be invoked *extremely*
5765
         *   frequently (an average of roughly 500 times per turn in the
5766
         *   library sample game, for example).  Game authors are strongly
5767
         *   advised to consider 'foreach' and 'for' equivalent in
5768
         *   performance for most purposes - just choose the clearest
5769
         *   construct for your situation.  
5770
         */
5771
        for (local clst = contents, local i = 1,
5772
             local len = clst.length() ; i <= len ; ++i)
5773
        {
5774
            /* get the current item */
5775
            cur = clst[i];
5776
            
5777
            /* if it's not already in the table, add it (recursively) */
5778
            if (tab[cur] == nil)
5779
                cur.addDirectConnections(tab);
5780
        }
5781
5782
        /* add my container if it's not already in the table */
5783
        if ((cur = location) != nil && tab[cur] == nil)
5784
            cur.addDirectConnections(tab);
5785
    }
5786
5787
    /*
5788
     *   Try an implicit action that would remove this object as an
5789
     *   obstructor to 'obj' from the perspective of the current actor in
5790
     *   the given sense.  This is invoked when this object is acting as
5791
     *   an obstructor between the current actor and 'obj' for the given
5792
     *   sense, and the caller wants to perform a command that requires a
5793
     *   clear sense path to the given object in the given sense.
5794
     *   
5795
     *   If it is possible to perform an implicit command that would clear
5796
     *   the obstruction, try performing the command, and return true.
5797
     *   Otherwise, simply return nil.  The usual implied command rules
5798
     *   should be followed (which can be accomplished simply by using
5799
     *   tryImplictAction() to execute any implied command).
5800
     *   
5801
     *   The particular type of command that would remove this obstructor
5802
     *   can vary by obstructor class.  For a container, for example, an
5803
     *   "open" command is the usual remedy.  
5804
     */
5805
    tryImplicitRemoveObstructor(sense, obj)
5806
    {
5807
        /* by default, we have no way of clearing our obstruction */
5808
        return nil;
5809
    }
5810
5811
    /*
5812
     *   Display a message explaining why we are obstructing a sense path
5813
     *   to the given object.
5814
     */
5815
    cannotReachObject(obj)
5816
    {
5817
        /* 
5818
         *   Default objects have no particular obstructive capabilities,
5819
         *   so we can't do anything but show the default message.  This
5820
         *   is a last resort that should rarely be used; normally, we
5821
         *   will be able to identify a specific obstructor that overrides
5822
         *   this to explain precisely what kind of obstruction is
5823
         *   involved.  
5824
         */
5825
        gLibMessages.cannotReachObject(obj);
5826
    }
5827
5828
    /*
5829
     *   Display a message explaining that the source of a sound cannot be
5830
     *   seen because I am visually obstructing it.  By default, we show
5831
     *   nothing at all; subclasses can override this to provide a better
5832
     *   explanation when possible.  
5833
     */
5834
    cannotSeeSoundSource(obj) { }
5835
5836
    /* explain why we cannot see the source of an odor */
5837
    cannotSeeSmellSource(obj) { }
5838
5839
    /*
5840
     *   Get the path for this object reaching out and touching the given
5841
     *   object.  This can be used to determine whether or not an actor
5842
     *   can touch the given object.  
5843
     */
5844
    getTouchPathTo(obj)
5845
    {
5846
        local path;
5847
        local key = [self, obj];
5848
        local cache;
5849
        local info;
5850
5851
        /* if we have a cache, try finding the data in the cache */
5852
        if ((cache = libGlobal.canTouchCache) != nil
5853
            && (info = cache[key]) != nil)
5854
        {
5855
            /* we have a cache entry - return the path from the cache */
5856
            return info.touchPath;
5857
        }
5858
5859
        /* select the path from here to the target object */
5860
        path = selectPathTo(obj, &canTouchViaPath);
5861
5862
        /* if caching is enabled, add a cache entry for the path */
5863
        if (cache != nil)
5864
            cache[key] = new CanTouchInfo(path);
5865
5866
        /* return the path */
5867
        return path;
5868
    }
5869
5870
    /*
5871
     *   Determine if I can touch the given object.  By default, we can
5872
     *   always touch our immediate container; otherwise, we can touch
5873
     *   anything with a touch path that we can traverse.
5874
     */
5875
    canTouch(obj)
5876
    {
5877
        local path;
5878
        local result;
5879
        local key = [self, obj];
5880
        local cache;
5881
        local info;
5882
5883
        /* if we have a cache, check the cache for the desired data */
5884
        if ((cache = libGlobal.canTouchCache) != nil
5885
            && (info = cache[key]) != nil)
5886
        {
5887
            /* if we cached the canTouch result, return it */
5888
            if (info.propDefined(&canTouch))
5889
                return info.canTouch;
5890
5891
            /* 
5892
             *   we didn't calculate the canTouch result, but we at least
5893
             *   have a cached path that we can avoid recalculating 
5894
             */
5895
            path = info.touchPath;
5896
        }
5897
        else
5898
        {
5899
            /* we don't have a cached path, so calculate it anew */
5900
            path = getTouchPathTo(obj);
5901
        }
5902
        
5903
        /* if there's no 'touch' path, we can't touch the object */
5904
        if (path == nil)
5905
        {
5906
            /* there's no path */
5907
            result = nil;
5908
        }
5909
        else
5910
        {
5911
            /* we have a path - check to see if we can reach along it */
5912
            result = traversePath(path,
5913
                {ele, op: ele.canTouchViaPath(self, obj, op)});
5914
        }
5915
5916
        /* if caching is active, cache our result */
5917
        if (cache != nil)
5918
        {
5919
            /* if we don't already have a cache entry, create one */
5920
            if (info == nil)
5921
                cache[key] = info = new CanTouchInfo(path);
5922
5923
            /* save our canTouch result in the cache entry */
5924
            info.canTouch = result;
5925
        }
5926
5927
        /* return the result */
5928
        return result;
5929
    }
5930
5931
    /*
5932
     *   Find the object that prevents us from touching the given object.
5933
     */
5934
    findTouchObstructor(obj)
5935
    {
5936
        /* cache 'touch' sense path information */
5937
        cacheSenseInfo(connectionTable(), touch);
5938
        
5939
        /* return the opaque obstructor for the sense of touch */
5940
        return findOpaqueObstructor(touch, obj);
5941
    }
5942
5943
    /*
5944
     *   Get the path for moving this object from its present location to
5945
     *   the given new container. 
5946
     */
5947
    getMovePathTo(newLoc)
5948
    {
5949
        /* 
5950
         *   select the path from here to the new location, using the
5951
         *   canMoveViaPath method to discriminate among different path
5952
         *   possibilities. 
5953
         */
5954
        return selectPathTo(newLoc, &canMoveViaPath);
5955
    }
5956
5957
    /*
5958
     *   Get the path for throwing this object from its present location
5959
     *   to the given target object. 
5960
     */
5961
    getThrowPathTo(newLoc)
5962
    {
5963
        /*
5964
         *   select the path from here to the target, using the
5965
         *   canThrowViaPath method to discriminate among different paths 
5966
         */
5967
        return selectPathTo(newLoc, &canThrowViaPath);
5968
    }
5969
5970
    /*
5971
     *   Determine if we can traverse self for moving the given object in
5972
     *   the given manner.  This is used to determine if a containment
5973
     *   connection path can be used to move an object to a new location.
5974
     *   Returns a CheckStatus object indicating success if we can
5975
     *   traverse self, failure if not.
5976
     *   
5977
     *   By default, we'll simply return a success indicator.  Subclasses
5978
     *   might want to override this for particular conditions.  For
5979
     *   example, containers would normally override this to return nil
5980
     *   when attempting to move an object in or out of a closed
5981
     *   container.  Some special containers might also want to override
5982
     *   this to allow moving an object in or out only if the object is
5983
     *   below a certain size threshold, for example.
5984
     *   
5985
     *   'obj' is the object being moved, and 'dest' is the destination of
5986
     *   the move.
5987
     *   
5988
     *   'op' is one of the pathXxx operations - PathIn, PathOut, PathPeer
5989
     *   - specifying what kind of movement is being attempted.  PathIn
5990
     *   indicates that we're moving 'obj' from outside self to inside
5991
     *   self; PathOut indicates the opposite.  PathPeer indicates that
5992
     *   we're moving 'obj' entirely within self - this normally means
5993
     *   that we've moved the object out of one of our contents and will
5994
     *   move it into another of our contents.  
5995
     */
5996
    checkMoveViaPath(obj, dest, op) { return checkStatusSuccess; }
5997
5998
    /*
5999
     *   Determine if we can traverse this object for throwing the given
6000
     *   object in the given manner.  By default, this returns the same
6001
     *   thing as canMoveViaPath, since throwing is in most cases the same
6002
     *   as ordinary movement.  Objects can override this when throwing an
6003
     *   object through this path element should be treated differently
6004
     *   from ordinary movement.
6005
     */
6006
    checkThrowViaPath(obj, dest, op)
6007
        { return checkMoveViaPath(obj, dest, op); }
6008
6009
    /*
6010
     *   Determine if we can traverse self in the given manner for the
6011
     *   purposes of 'obj' touching another object.  'obj' is usually an
6012
     *   actor; this determines if 'obj' is allowed to reach through this
6013
     *   path element on the way to touching another object.
6014
     *   
6015
     *   By default, this returns the same thing as canMoveViaPath, since
6016
     *   touching is in most cases the same as ordinary movement of an
6017
     *   object from one location to another.  Objects can overridet his
6018
     *   when touching an object through this path element should be
6019
     *   treated differently from moving an object.  
6020
     */
6021
    checkTouchViaPath(obj, dest, op)
6022
        { return checkMoveViaPath(obj, dest, op); }
6023
6024
    /*
6025
     *   Determine if we can traverse this object for moving the given
6026
     *   object via a path.  Calls checkMoveViaPath(), and returns true if
6027
     *   checkMoveViaPath() indicates success, nil if it indicates failure.
6028
     *   
6029
     *   Note that this method should generally not be overridden; only
6030
     *   checkMoveViaPath() should usually need to be overridden.  
6031
     */
6032
    canMoveViaPath(obj, dest, op)
6033
        { return checkMoveViaPath(obj, dest, op).isSuccess; }
6034
6035
    /* determine if we can throw an object via this path */
6036
    canThrowViaPath(obj, dest, op)
6037
        { return checkThrowViaPath(obj, dest, op).isSuccess; }
6038
6039
    /* determine if we can reach out and touch an object via this path */
6040
    canTouchViaPath(obj, dest, op)
6041
        { return checkTouchViaPath(obj, dest, op).isSuccess; }
6042
6043
    /*
6044
     *   Check moving an object through this container via a path.  This
6045
     *   method is called during moveInto to notify each element along a
6046
     *   move path that the movement is about to occur.  We call
6047
     *   checkMoveViaPath(); if it indicates failure, we'll report the
6048
     *   failure encoded in the status object and terminate the command
6049
     *   with 'exit'.  
6050
     *   
6051
     *   Note that this method should generally not be overridden; only
6052
     *   checkMoveViaPath() should usually need to be overridden.  
6053
     */
6054
    notifyMoveViaPath(obj, dest, op)
6055
    {
6056
        local stat;
6057
6058
        /* check the move */
6059
        stat = checkMoveViaPath(obj, dest, op);
6060
6061
        /* if it's a failure, report the failure message and terminate */
6062
        if (!stat.isSuccess)
6063
        {
6064
            /* report the failure */
6065
            reportFailure(stat.msgProp, stat.msgParams...);
6066
6067
            /* terminate the command */
6068
            exit;
6069
        }
6070
    }
6071
6072
    /*
6073
     *   Choose a path from this object to a given object.  If no paths
6074
     *   are available, returns nil.  If any paths exist, we'll find the
6075
     *   shortest usable one, calling the given property on each object in
6076
     *   the path to determine if the traversals are allowed.
6077
     *   
6078
     *   If we can find a path, but there are no good paths, we'll return
6079
     *   the shortest unusable path.  This can be useful for explaining
6080
     *   why the traversal is impossible.  
6081
     */
6082
    selectPathTo(obj, traverseProp)
6083
    {
6084
        local allPaths;
6085
        local goodPaths;
6086
        local minPath;
6087
        
6088
        /* get the paths from here to the given object */
6089
        allPaths = getAllPathsTo(obj);
6090
6091
        /* if we found no paths, the answer is obvious */
6092
        if (allPaths.length() == 0)
6093
            return nil;
6094
6095
        /* start off with an empty vector for the good paths */
6096
        goodPaths = new Vector(allPaths.length());
6097
6098
        /* go through the paths and find the good ones */
6099
        for (local i = 1, local len = allPaths.length() ; i <= len ; ++i)
6100
        {
6101
            local path = allPaths[i];
6102
            local ok;
6103
            
6104
            /* 
6105
             *   traverse the path, calling the traversal check property
6106
             *   on each point in the path; if any check property returns
6107
             *   nil, it means that the traversal isn't allowed at that
6108
             *   point, so we can't use the path 
6109
             */
6110
            ok = true;
6111
            traversePath(path, new function(target, op) {
6112
                /*
6113
                 *   Invoke the check property on this target.  If it
6114
                 *   doesn't allow the traversal, the path is unusable. 
6115
                 */
6116
                if (target.(traverseProp)(self, obj, op))
6117
                {
6118
                    /* we're still okay - continue the path traversal */
6119
                    return true;
6120
                }
6121
                else
6122
                {
6123
                    /* failed - note that the path is no good */
6124
                    ok = nil;
6125
6126
                    /* there's no need to continue the path traversal */
6127
                    return nil;
6128
                }
6129
            });
6130
6131
            /* 
6132
             *   if we didn't find any objections to the path, add this to
6133
             *   the list of good paths 
6134
             */
6135
            if (ok)
6136
                goodPaths.append(path);
6137
        }
6138
6139
        /* if there are no good paths, take the shortest bad path */
6140
        if (goodPaths.length() == 0)
6141
            goodPaths = allPaths;
6142
6143
        /* find the shortest of the paths we're still considering */
6144
        minPath = nil;
6145
        for (local i = 1, local len = goodPaths.length() ; i <= len ; ++i)
6146
        {
6147
            /* get the current path */
6148
            local path = goodPaths[i];
6149
            
6150
            /* if this is the best so far, note it */
6151
            if (minPath == nil || path.length() < minPath.length())
6152
                minPath = path;
6153
        }
6154
6155
        /* return the shortest good path */
6156
        return minPath;
6157
    }
6158
6159
    /*
6160
     *   Traverse a containment connection path, calling the given
6161
     *   function for each element.  In each call to the callback, 'obj'
6162
     *   is the container object being traversed, and 'op' is the
6163
     *   operation being used to traverse it.
6164
     *   
6165
     *   At each stage, the callback returns true to continue the
6166
     *   traversal, nil if we are to stop the traversal.
6167
     *   
6168
     *   Returns nil if any callback returns nil, true if all callbacks
6169
     *   return true.  
6170
     */
6171
    traversePath(path, func)
6172
    {
6173
        local len;
6174
        local target;
6175
6176
        /* 
6177
         *   if there's no path at all, there's nothing to do - simply
6178
         *   return true in this case, because no traversal callback can
6179
         *   fail when there are no traversal callbacks to begin with 
6180
         */
6181
        if (path == nil || (len = path.length()) == 0)
6182
            return true;
6183
        
6184
        /* traverse from the path's starting point */
6185
        if (path[1] != nil && !(func)(path[1], PathFrom))
6186
            return nil;
6187
6188
        /* run through this path and see if the traversals are allowed */
6189
        for (target = nil, local i = 2 ; i <= len ; i += 2)
6190
        {
6191
            local op;
6192
            
6193
            /* get the next traversal operation */
6194
            op = path[i];
6195
            
6196
            /* check the next traversal to see if it's allowed */
6197
            switch(op)
6198
            {
6199
            case PathIn:
6200
                /* 
6201
                 *   traverse in - notify the previous object, since it's
6202
                 *   the container we're entering 
6203
                 */
6204
                target = path[i-1];
6205
                break;
6206
6207
            case PathOut:
6208
                /*
6209
                 *   traversing out of the current container - tell the
6210
                 *   next object, since it's the object we're leaving 
6211
                 */
6212
                target = path[i+1];
6213
                break;
6214
6215
            case PathPeer:
6216
                /*
6217
                 *   traversing from one object to a containment peer -
6218
                 *   notify the container in common to both peers 
6219
                 */
6220
                target = path[i-1].getCommonDirectContainer(path[i+1]);
6221
                break;
6222
6223
            case PathThrough:
6224
                /* 
6225
                 *   traversing through a multi-location connector (the
6226
                 *   previous and next object will always be the same in
6227
                 *   this case, so it doesn't really matter which we
6228
                 *   choose) 
6229
                 */
6230
                target = path[i-1];
6231
                break;
6232
            }
6233
6234
            /* call the traversal callback */
6235
            if (target != nil && !(func)(target, op))
6236
            {
6237
                /* the callback told us not to continue */
6238
                return nil;
6239
            }
6240
        }
6241
6242
        /* 
6243
         *   Traverse to the path's ending point, if we haven't already.
6244
         *   Some operations do traverse to the right-hand element
6245
         *   (PathOut in particular), in which case we'll already have
6246
         *   reached the target.  Most operations traverse the left-hand
6247
         *   element, though, so in these cases we need to visit the last
6248
         *   element explicitly. 
6249
         */
6250
        if (path[len] != nil
6251
            && path[len] != target
6252
            && !(func)(path[len], PathTo))
6253
            return nil;
6254
6255
        /* all callbacks told us to continue */
6256
        return true;
6257
    }
6258
6259
    /*
6260
     *   Build a vector containing all of the possible paths we can
6261
     *   traverse to get from me to the given object.  The return value is
6262
     *   a vector of paths; each path is a list of containment operations
6263
     *   needed to get from here to there.
6264
     *   
6265
     *   Each path item in the vector is a list arranged like so:
6266
     *   
6267
     *   [obj, op, obj, op, obj]
6268
     *   
6269
     *   Each 'obj' is an object, and each 'op' is an operation enum
6270
     *   (PathIn, PathOut, PathPeer) that specifies how to get from the
6271
     *   preceding object to the next object.  The first object in the
6272
     *   list is always the starting object (i.e., self), and the last is
6273
     *   always the target object ('obj').  
6274
     */
6275
    getAllPathsTo(obj)
6276
    {
6277
        local vec;
6278
        
6279
        /* create an empty vector to hold the return set */
6280
        vec = new Vector(10);
6281
6282
        /* look along each connection from me */
6283
        buildContainmentPaths(vec, [self], obj);
6284
6285
        /* if there's no path, check the object for a special path */
6286
        if (obj != nil && vec.length() == 0)
6287
            obj.specialPathFrom(self, vec);
6288
6289
        /* return the vector */
6290
        return vec;
6291
    }
6292
6293
    /*
6294
     *   Get a "special" path from the given starting object to me.
6295
     *   src.getAllPathsTo(obj) calls this on 'obj' when it can't find any
6296
     *   actual containment path from 'src' to 'obj'.  If desired, this
6297
     *   method should add the path or paths to the vector 'vec'.
6298
     *   
6299
     *   By default, we do nothing at all.  The purpose of this routine is
6300
     *   to allow special objects that exist outside the normal containment
6301
     *   model to insinuate themselves into the sense model under special
6302
     *   conditions of their choosing.  
6303
     */
6304
    specialPathFrom(src, vec) { }
6305
6306
    /*
6307
     *   Service routine for getAllPathsTo: build a vector of the
6308
     *   containment paths starting with this object.  
6309
     */
6310
    buildContainmentPaths(vec, pathHere, obj)
6311
    {
6312
        local i, len;
6313
        local cur;
6314
        
6315
        /* scan each of our contents */
6316
        for (i = 1, len = contents.length() ; i <= len ; ++i)
6317
        {
6318
            /* get the current item */
6319
            cur = contents[i];
6320
6321
            /*
6322
             *   If this item is the target, we've found what we're
6323
             *   looking for - simply add the path to here to the return
6324
             *   vector.
6325
             *   
6326
             *   Otherwise, if this item isn't already in the path,
6327
             *   traverse into it; if it's already in the path, there's no
6328
             *   need to look at it because we've already looked at it to
6329
             *   get here 
6330
             */
6331
            if (cur == obj)
6332
            {
6333
                /* 
6334
                 *   The path to here is the path to the target.  Append
6335
                 *   the target object itself with an 'in' operation.
6336
                 *   Before adding the path to the result set, normalize
6337
                 *   it.  
6338
                 */
6339
                vec.append(normalizePath(pathHere + [PathIn, obj]));
6340
            }
6341
            else if (pathHere.indexOf(cur) == nil)
6342
            {
6343
                /* 
6344
                 *   look at this item, adding it to the path with an
6345
                 *   'enter child' traversal 
6346
                 */
6347
                cur.buildContainmentPaths(vec, pathHere + [PathIn, cur], obj);
6348
            }
6349
        }
6350
6351
        /* scan each of our locations */
6352
        for (local clst = getConnectedContainers, i = 1, len = clst.length() ;
6353
             i <= len ; ++i)
6354
        {
6355
            /* get the current item */
6356
            cur = clst[i];
6357
6358
            /*
6359
             *   If this item is the target, we've found what we're
6360
             *   looking for.  Otherwise, if this item isn't already in
6361
             *   the path, traverse into it 
6362
             */
6363
            if (cur == obj)
6364
            {
6365
                /* 
6366
                 *   We have the path to the target.  Add the traversal to
6367
                 *   the container to the path, normalize the path, and
6368
                 *   add the path to the result set. 
6369
                 */
6370
                vec.append(normalizePath(pathHere + [PathOut, cur]));
6371
            }
6372
            else if (pathHere.indexOf(cur) == nil)
6373
            {
6374
                /* 
6375
                 *   Look at this container, adding it to the path with an
6376
                 *   'exit to container' traversal.
6377
                 */
6378
                cur.buildContainmentPaths(vec,
6379
                                          pathHere + [PathOut, cur], obj);
6380
            }
6381
        }
6382
    }
6383
6384
    /*
6385
     *   "Normalize" a containment path to remove redundant containment
6386
     *   traversals.
6387
     *   
6388
     *   First, we expand any sequence of in+out operations that take us
6389
     *   out of one root-level containment tree and into another to
6390
     *   include a "through" operation for the multi-location object being
6391
     *   traversed.  For example, if 'a' and 'c' do not share a common
6392
     *   container, then we will turn this:
6393
     *   
6394
     *     [a PathIn b PathOut c]
6395
     *   
6396
     *   into this:
6397
     *   
6398
     *     [a PathIn b PathThrough b PathOut c]
6399
     *   
6400
     *   This will ensure that when we traverse the path, we will
6401
     *   explicitly traverse through the connector material of 'b'.
6402
     *   
6403
     *   Second, we replace any sequence of out+in operations through a
6404
     *   common container with "peer" operations across the container's
6405
     *   contents directly.  For example, a path that looks like this
6406
     *   
6407
     *     [a PathOut b PathIn c]
6408
     *   
6409
     *   will be normalized to this:
6410
     *   
6411
     *     [a PathPeer c]
6412
     *   
6413
     *   This means that we go directly from a to c, traversing through
6414
     *   the fill medium of their common container 'b' but not actually
6415
     *   traversing out of 'b' and back into it.  
6416
     */
6417
    normalizePath(path)
6418
    {
6419
        /* 
6420
         *   Traverse the path looking for items to normalize with the
6421
         *   (in-out)->(through) transformation.  Start at the second
6422
         *   element, which is the first path operation code.  
6423
         */
6424
        for (local i = 2 ; i <= path.length() ; i += 2)
6425
        {
6426
            /*
6427
             *   If we're on an 'in' operation, and an 'out' operation
6428
             *   immediately follows, and the object before the 'in' and
6429
             *   the object after the 'out' do not share a common
6430
             *   container, we must add an explicit 'through' step for the
6431
             *   multi-location connector being traversed. 
6432
             */
6433
            if (path[i] == PathIn
6434
                && i + 2 <= path.length()
6435
                && path[i+2] == PathOut
6436
                && path[i-1].getCommonDirectContainer(path[i+3]) == nil)
6437
            {
6438
                /* we need to add a 'through' operation */
6439
                path = path.sublist(1, i + 1)
6440
                       + PathThrough
6441
                       + path.sublist(i + 1);
6442
            }
6443
        }
6444
6445
        /* 
6446
         *   make another pass, this time applying the (out-in)->peer
6447
         *   transformation 
6448
         */
6449
        for (local i = 2 ; i <= path.length() ; i += 2)
6450
        {
6451
            /*
6452
             *   If we're on an 'out' operation, and an 'in' operation
6453
             *   immediately follows, we can collapse the out+in sequence
6454
             *   to a single 'peer' operation.  
6455
             */
6456
            if (path[i] == PathOut
6457
                && i + 2 <= path.length()
6458
                && path[i+2] == PathIn)
6459
            {
6460
                /* 
6461
                 *   this sequence can be collapsed to a single 'peer'
6462
                 *   operation - rewrite the path accordingly 
6463
                 */
6464
                path = path.sublist(1, i - 1)
6465
                       + PathPeer
6466
                       + path.sublist(i + 3);
6467
            }
6468
        }
6469
6470
        /* return the normalized path */
6471
        return path;
6472
    }
6473
    
6474
    /*
6475
     *   Get the visual sense information for this object from the current
6476
     *   global point of view.  If we have explicit sense information set
6477
     *   with setSenseInfo, we'll return that; otherwise, we'll calculate
6478
     *   the current sense information for the given point of view.
6479
     *   Returns a SenseInfo object giving the information.  
6480
     */
6481
    getVisualSenseInfo()
6482
    {
6483
        local infoTab;
6484
        
6485
        /* if we have explicit sense information already set, use it */
6486
        if (explicitVisualSenseInfo != nil)
6487
            return explicitVisualSenseInfo;
6488
6489
        /* calculate the sense information for the point of view */
6490
        infoTab = getPOVDefault(gActor).visibleInfoTable();
6491
6492
        /* return the information on myself from the table */
6493
        return infoTab[self];
6494
    }
6495
6496
    /*
6497
     *   Call a description method with explicit point-of-view and the
6498
     *   related point-of-view sense information.  'pov' is the point of
6499
     *   view object, which is usually an actor; 'senseInfo' is a
6500
     *   SenseInfo object giving the sense information for this object,
6501
     *   which we'll use instead of dynamically calculating the sense
6502
     *   information for the duration of the routine called.  
6503
     */
6504
    withVisualSenseInfo(pov, senseInfo, methodToCall, [args])
6505
    {
6506
        local oldSenseInfo;
6507
        
6508
        /* push the sense information */
6509
        oldSenseInfo = setVisualSenseInfo(senseInfo);
6510
6511
        /* push the point of view */
6512
        pushPOV(pov, pov);
6513
6514
        /* make sure we restore the old value no matter how we leave */
6515
        try
6516
        {
6517
            /* 
6518
             *   call the method with the given arguments, and return the
6519
             *   result 
6520
             */
6521
            return self.(methodToCall)(args...);
6522
        }
6523
        finally
6524
        {
6525
            /* restore the old point of view */
6526
            popPOV();
6527
            
6528
            /* restore the old sense information */
6529
            setVisualSenseInfo(oldSenseInfo);
6530
        }
6531
    }
6532
6533
    /* 
6534
     *   Set the explicit visual sense information; if this is not nil,
6535
     *   getVisualSenseInfo() will return this rather than calculating the
6536
     *   live value.  Returns the old value, which is a SenseInfo or nil.  
6537
     */
6538
    setVisualSenseInfo(info)
6539
    {
6540
        local oldInfo;
6541
6542
        /* remember the old value */
6543
        oldInfo = explicitVisualSenseInfo;
6544
6545
        /* remember the new value */
6546
        explicitVisualSenseInfo = info;
6547
6548
        /* return the original value */
6549
        return oldInfo;
6550
    }
6551
6552
    /* current explicit visual sense information overriding live value */
6553
    explicitVisualSenseInfo = nil
6554
6555
    /*
6556
     *   Determine how accessible my contents are to a sense.  Any items
6557
     *   contained within a Thing are considered external features of the
6558
     *   Thing, hence they are transparently accessible to all senses.
6559
     */
6560
    transSensingIn(sense) { return transparent; }
6561
6562
    /*
6563
     *   Determine how accessible peers of this object are to the contents
6564
     *   of this object, via a given sense.  This has the same meaning as
6565
     *   transSensingIn(), but in the opposite direction: whereas
6566
     *   transSensingIn() determines how accessible my contents are from
6567
     *   the outside, this determines how accessible the outside is from
6568
     *   the contents.
6569
     *
6570
     *   By default, we simply return the same thing as transSensingIn(),
6571
     *   since most containers are symmetrical for sense passing from
6572
     *   inside to outside or outside to inside.  However, we distinguish
6573
     *   this as a separate method so that asymmetrical containers can
6574
     *   have different effects in the different directions; for example,
6575
     *   a box made of one-way mirrors might be transparent when looking
6576
     *   from the inside to the outside, but opaque in the other
6577
     *   direction.
6578
     */
6579
    transSensingOut(sense) { return transSensingIn(sense); }
6580
6581
    /*
6582
     *   Get my "fill medium."  This is an object of class FillMedium that
6583
     *   permeates our interior, such as fog or smoke.  This object
6584
     *   affects the transmission of senses from one of our children to
6585
     *   another, or between our interior and exterior.
6586
     *   
6587
     *   Note that the FillMedium object is usually a regular object in
6588
     *   scope, so that the player can refer to the fill medium.  For
6589
     *   example, if a room is filled with fog, the player might want to
6590
     *   be able to refer to the fog in a command.
6591
     *   
6592
     *   By default, our medium is the same as our parent's medium, on the
6593
     *   assumption that fill media diffuse throughout the location's
6594
     *   interior.  Note, though, that Container overrides this so that a
6595
     *   closed Container is isolated from its parent's fill medium -
6596
     *   think of a closed bottle within a room filled with smoke.
6597
     *   However, a fill medium doesn't expand from a child into its
6598
     *   containers - it only diffuses into nested containers, never out.
6599
     *   
6600
     *   An object at the outermost containment level has no fill medium
6601
     *   by default, so we return nil if our location is nil.
6602
     *   
6603
     *   Note that, unlike the "surface" material, the fill medium is
6604
     *   assumed to be isotropic - that is, it has the same sense-passing
6605
     *   characteristics regardless of the direction in which the energy
6606
     *   is traversing the medium.  Since we don't have any information in
6607
     *   our containment model about the positions of our objects relative
6608
     *   to one another, we have no way to express anisotropy in the fill
6609
     *   medium among our children anyway.
6610
     *   
6611
     *   Note further that energy going in or out of this object must
6612
     *   traverse both the fill medium and the surface of the object
6613
     *   itself.  Since we have no other information on the relative
6614
     *   positions of our contents, we can only assume that they're
6615
     *   uniformly distributed through our interior, so it is necessary to
6616
     *   traverse the same amount of fill material to go from one child to
6617
     *   any other or from a child to our inner surface.
6618
     *   
6619
     *   As a sense is transmitted, several consecutive traversals of a
6620
     *   single fill material (i.e., a single object reference) will be
6621
     *   treated as a single traversal of the material.  Since we don't
6622
     *   have a notion of distance in our containment model, we can't
6623
     *   assume that we cover a certain amount of distance just because we
6624
     *   traverse a certain number of containment levels.  So, if we have
6625
     *   three nested containment levels all inheriting a single fill
6626
     *   material from their outermost parent, traversing from the inner
6627
     *   container to the outer container will count as a single traversal
6628
     *   of the material.  
6629
     */
6630
    fillMedium()
6631
    {
6632
        local loc;
6633
        
6634
        return ((loc = location) != nil ? loc.fillMedium() : nil);
6635
    }
6636
6637
    /*
6638
     *   Can I see/hear/smell the given object?  By default, an object can
6639
     *   "see" (or "hear", etc) another if there's a clear path in the
6640
     *   corresponding basic sense to the other object.  Note that actors
6641
     *   override this, because they have a subjective view of the senses:
6642
     *   an actor might see in a special infrared vision sense rather than
6643
     *   (or in addition to) the normal 'sight' sense, for example.  
6644
     */
6645
    canSee(obj) { return senseObj(sight, obj).trans != opaque; }
6646
    canHear(obj) { return senseObj(sound, obj).trans != opaque; }
6647
    canSmell(obj) { return senseObj(smell, obj).trans != opaque; }
6648
6649
    /* can the given actor see/hear/smell/touch me? */
6650
    canBeSeenBy(actor) { return actor.canSee(self); }
6651
    canBeHeardBy(actor) { return actor.canHear(self); }
6652
    canBeSmelledBy(actor) { return actor.canSmell(self); }
6653
    canBeTouchedBy(actor) { return actor.canTouch(self); }
6654
6655
    /* can the player character see/hear/smell/touch me? */
6656
    canBeSeen = (canBeSeenBy(gPlayerChar))
6657
    canBeHeard = (canBeHeardBy(gPlayerChar))
6658
    canBeSmelled = (canBeSmelledBy(gPlayerChar))
6659
    canBeTouched = (canBeTouchedBy(gPlayerChar))
6660
6661
    /*
6662
     *   Am I occluded by the given Occluder when viewed in the given sense
6663
     *   from the given point of view?  The default Occluder implementation
6664
     *   calls this on each object involved in the sense path to determine
6665
     *   if it should occlude the object.  Returns true if we're occluded
6666
     *   by the given Occluder, nil if not.  By default, we simply return
6667
     *   nil to indicate that we're not occluded.  
6668
     */
6669
    isOccludedBy(occluder, sense, pov) { return nil; }
6670
6671
    /*
6672
     *   Determine how well I can sense the given object.  Returns a
6673
     *   SenseInfo object describing the sense path from my point of view
6674
     *   to the object.
6675
     *   
6676
     *   Note that, because 'distant', 'attenuated', and 'obscured'
6677
     *   transparency levels always compound (with one another and with
6678
     *   themselves) to opaque, there will never be more than a single
6679
     *   obstructor in a path, because any path with two or more
6680
     *   obstructors would be an opaque path, and hence not a path at all.
6681
     */
6682
    senseObj(sense, obj)
6683
    {
6684
        local info;
6685
6686
        /* get the sense information from the table */
6687
        info = senseInfoTable(sense)[obj];
6688
6689
        /* if we couldn't find the object, return an 'opaque' indication */
6690
        if (info == nil)
6691
            info = new SenseInfo(obj, opaque, nil, 0);
6692
6693
        /* return the sense data descriptor */
6694
        return info;
6695
    }
6696
6697
    /*
6698
     *   Find an opaque obstructor.  This can be called immediately after
6699
     *   calling senseObj() when senseObj() indicates that the object is
6700
     *   opaquely obscured.  We will find the nearest (by containment)
6701
     *   object where the sense status is non-opaque, and we'll return
6702
     *   that object.
6703
     *   
6704
     *   senseObj() by itself does not determine the obstructor when the
6705
     *   sense path is opaque, because doing so requires extra work.  The
6706
     *   sense path calculator that senseObj() uses cuts off its search
6707
     *   whenever it reaches an opaque point, because beyond that point
6708
     *   nothing can be sensed.
6709
     *   
6710
     *   This can only be called immediately after calling senseObj()
6711
     *   because we re-use the same cached sense path information that
6712
     *   senseObj() uses.  
6713
     */
6714
    findOpaqueObstructor(sense, obj)
6715
    {
6716
        local path;
6717
        
6718
        /* get all of the paths from here to there */
6719
        path = getAllPathsTo(obj);
6720
6721
        /* 
6722
         *   if there are no paths, we won't be able to find a specific
6723
         *   obstructor - the object simply isn't connected to us at all 
6724
         */
6725
        if (path == nil)
6726
            return nil;
6727
6728
        /* 
6729
         *   Arbitrarily take the first path - there must be an opaque
6730
         *   obstructor on every path or we never would have been called
6731
         *   in the first place.  One opaque obstructor is as good as any
6732
         *   other for our purposes.  
6733
         */
6734
        path = path[1];
6735
6736
        /* 
6737
         *   The last thing in the list that can be sensed is the opaque
6738
         *   obstructor.  Note that the path entries alternate between
6739
         *   objects and traversal operations.  
6740
         */
6741
        for (local i = 3, local len = path.length() ; i <= len ; i += 2)
6742
        {
6743
            local obj;
6744
            local trans;
6745
            local ambient;
6746
6747
            /* get this object */
6748
            obj = path[i];
6749
6750
            /* 
6751
             *   get the appropriate sense direction - if the path takes
6752
             *   us out, look at the interior sense data for the object;
6753
             *   otherwise look at its exterior sense data 
6754
             */
6755
            if (path[i-1] == PathOut)
6756
            {
6757
                /* we're looking outward, so use the interior data */
6758
                trans = obj.tmpTransWithin_;
6759
                ambient = obj.tmpAmbientWithin_;
6760
            }
6761
            else
6762
            {
6763
                /* we're looking inward, so use the exterior data */
6764
                trans = obj.tmpTrans_;
6765
                ambient = obj.tmpAmbient_;
6766
            }
6767
6768
            /* 
6769
             *   if this item cannot be sensed, the previous item is the
6770
             *   opaque obstructor 
6771
             */
6772
            if (!obj.canBeSensed(sense, trans, ambient))
6773
            {
6774
                /* can't sense it - the previous item is the obstructor */
6775
                return path[i-2];
6776
            }
6777
        }
6778
6779
        /* we didn't find any obstructor */
6780
        return nil;
6781
    }
6782
    
6783
    /*
6784
     *   Build a list of full information on all of the objects reachable
6785
     *   from me through the given sense, along with full information for
6786
     *   each object's sense characteristics.
6787
     *   
6788
     *   We return a lookup table of each object that can be sensed (in
6789
     *   the given sense) from the point of view of 'self'.  The key for
6790
     *   each entry in the table is an object, and the corresponding value
6791
     *   is a SenseInfo object describing the sense conditions for the
6792
     *   object.  
6793
     */
6794
    senseInfoTable(sense)
6795
    {
6796
        local objs;
6797
        local tab;
6798
        local siz;
6799
        local cache;
6800
        local key = [self, sense];
6801
6802
        /* if we have cached sense information, simply return it */
6803
        if ((cache = libGlobal.senseCache) != nil
6804
            && (tab = cache[key]) != nil)
6805
            return tab;
6806
        
6807
        /* 
6808
         *   get the list of objects connected to us by containment -
6809
         *   since the only way senses can travel between objects is via
6810
         *   containment relationships, this is the complete set of
6811
         *   objects that could be connected to us by any senses 
6812
         */
6813
        objs = connectionTable();
6814
6815
        /* we're the point of view for this path calculation */
6816
        senseTmp.pointOfView = self;
6817
6818
        /* cache the sensory information for all of these objects */
6819
        cacheSenseInfo(objs, sense);
6820
6821
        /* build a table of all of the objects we can reach */
6822
        siz = objs.getEntryCount();
6823
        tab = new LookupTable(32, siz == 0 ? 32 : siz);
6824
        objs.forEachAssoc(new function(cur, val)
6825
        {
6826
            /* add this object's sense information to the table */
6827
            cur.addToSenseInfoTable(sense, tab);
6828
        });
6829
6830
        /* add the information vector to the sense cache */
6831
        if (cache != nil)
6832
            cache[key] = tab;
6833
6834
        /* return the result table */
6835
        return tab;
6836
    }
6837
6838
    /*
6839
     *   Add myself to a sense info table.  This is called by
6840
     *   senseInfoTable() for each object connected by containment to the
6841
     *   source object 'src', after we've fully traversed the containment
6842
     *   tree to initialize our current-sense properties (tmpAmbient_,
6843
     *   tmpTrans_, etc).
6844
     *   
6845
     *   Our job is to figure out if 'src' can sense us, and add a
6846
     *   SenseInfo entry to the LookupTable 'tab' (which is keyed by
6847
     *   object, hence our key is simply 'self') if 'src' can indeed sense
6848
     *   us.
6849
     *   
6850
     *   Note that an object that wants to set up its own special sensory
6851
     *   data can do so by overriding this.  This routine will only be
6852
     *   called on objects connected to 'src' by containment, though, so if
6853
     *   an object overrides this in order to implement a special sensory
6854
     *   system that's outside of the normal containment model, it must
6855
     *   somehow ensure that it gets included in the containment connection
6856
     *   table in the first place.  
6857
     */
6858
    addToSenseInfoTable(sense, tab)
6859
    {
6860
        local trans;
6861
        local ambient;
6862
        local obs;
6863
6864
        /* 
6865
         *   consider the appropriate set of data, depending on whether the
6866
         *   source is looking out from within me, or in from outside of me
6867
         */
6868
        if (tmpPathIsIn_)
6869
        {
6870
            /* we're looking in from without, so use our exterior data */
6871
            trans = tmpTrans_;
6872
            ambient = tmpAmbient_;
6873
            obs = tmpObstructor_;
6874
        }
6875
        else
6876
        {
6877
            /* we're looking out from within, so use our interior data */
6878
            trans = tmpTransWithin_;
6879
            ambient = tmpAmbientWithin_;
6880
            obs = tmpObstructorWithin_;
6881
        }
6882
6883
        /* if the source can sense us, add ourself to the data */
6884
        if (canBeSensed(sense, trans, ambient))
6885
            tab[self] = new SenseInfo(self, trans, obs, ambient);
6886
    }
6887
6888
    /*
6889
     *   Build a list of the objects reachable from me through the given
6890
     *   sense and with a presence in the sense. 
6891
     */
6892
    sensePresenceList(sense)
6893
    {
6894
        local infoTab;
6895
        
6896
        /* get the full sense list */
6897
        infoTab = senseInfoTable(sense);
6898
6899
        /* 
6900
         *   return only the subset of items that have a presence in this
6901
         *   sense, and return only the items themselves, not the
6902
         *   SenseInfo objects 
6903
         */
6904
        return senseInfoTableSubset(infoTab,
6905
            {obj, info: obj.(sense.presenceProp)});
6906
    }
6907
6908
    /*
6909
     *   Determine the highest ambient sense level at this object for any
6910
     *   of the given senses.
6911
     *   
6912
     *   Note that this method changes certain global variables used during
6913
     *   sense and scope calculations.  Because of this, be careful not to
6914
     *   call this method *during* sense or scope calculations.  In
6915
     *   particular, don't call this from an object's canBeSensed() method
6916
     *   or anything it calls.  For example, don't call this from a
6917
     *   Hidden.discovered method.  
6918
     */
6919
    senseAmbientMax(senses)
6920
    {
6921
        local objs;
6922
        local maxSoFar;
6923
6924
        /* we don't have any level so far */
6925
        maxSoFar = 0;
6926
        
6927
        /* get the table of connected objects */
6928
        objs = connectionTable();
6929
6930
        /* go through each sense */
6931
        for (local i = 1, local lst = senses, local len = lst.length() ;
6932
             i <= len ; ++i)
6933
        {
6934
            /* 
6935
             *   cache the ambient level for this sense for everything
6936
             *   connected by containment 
6937
             */
6938
            cacheAmbientInfo(objs, lst[i]);
6939
6940
            /* 
6941
             *   if our cached level for this sense is the highest so far,
6942
             *   remember it 
6943
             */
6944
            if (tmpAmbient_ > maxSoFar)
6945
                maxSoFar = tmpAmbient_;
6946
        }
6947
6948
        /* return the highest level we found */
6949
        return maxSoFar;
6950
    }
6951
6952
    /*
6953
     *   Cache sensory information for all objects in the given list from
6954
     *   the point of view of self.  This caches the ambient energy level
6955
     *   at each object, if the sense uses ambient energy, and the
6956
     *   transparency and obstructor on the best path in the sense to the
6957
     *   object.  'objs' is the connection table, as generated by
6958
     *   connectionTable().  
6959
     */
6960
    cacheSenseInfo(objs, sense)
6961
    {
6962
        /* clear out the end-of-calculation notification list */
6963
        local nlst = senseTmp.notifyList;
6964
        if (nlst.length() != 0)
6965
            nlst.removeRange(1, nlst.length());
6966
6967
        /* first, calculate the ambient energy level at each object */
6968
        cacheAmbientInfo(objs, sense);
6969
6970
        /* next, cache the sense path from here to each object */
6971
        cacheSensePath(sense);
6972
6973
        /* notify each object in the notification list */
6974
        for (local i = 1, local len = nlst.length() ; i <= len ; ++i)
6975
            nlst[i].finishSensePath(objs, sense);
6976
    }
6977
6978
    /*
6979
     *   Cache the ambient energy level at each object in the table.  The
6980
     *   list must include everything connected by containment.  
6981
     */
6982
    cacheAmbientInfo(objs, sense)
6983
    {
6984
        local aprop;
6985
        
6986
        /* 
6987
         *   if this sense has ambience, transmit energy from sources to
6988
         *   all reachable objects; otherwise, just clear out the sense
6989
         *   data 
6990
         */
6991
        if ((aprop = sense.ambienceProp) != nil)
6992
        {
6993
            local sources;
6994
6995
            /* create a vector to hold the set of ambient energy sources */
6996
            sources = new Vector(16);
6997
6998
            /* 
6999
             *   Clear out any cached sensory information from past
7000
             *   calculations, and note objects that have ambient energy to
7001
             *   propagate.  
7002
             */
7003
            objs.forEachAssoc(new function(cur, val)
7004
            {
7005
                /* clear old sensory information for this object */
7006
                cur.clearSenseInfo();
7007
7008
                /* if it's an energy source, note it */
7009
                if (cur.(aprop) != 0)
7010
                    sources.append(cur);
7011
            });
7012
7013
            /* 
7014
             *   Calculate the ambient energy level at each object.  To do
7015
             *   this, start at each energy source and transmit its energy
7016
             *   to all objects within reach of the sense. 
7017
             */
7018
            for (local i = 1, local len = sources.length() ; i <= len ; ++i)
7019
            {
7020
                /* get this item */
7021
                local cur = sources[i];
7022
                
7023
                /* if this item transmits energy, process it */
7024
                if (cur.(aprop) != 0)
7025
                    cur.transmitAmbient(sense);
7026
            }
7027
        }
7028
        else
7029
        {
7030
            /* 
7031
             *   this sense doesn't use ambience - all we need to do is
7032
             *   clear out any old sense data for this object 
7033
             */
7034
            objs.forEachAssoc({cur, val: cur.clearSenseInfo()});
7035
        }
7036
    }
7037
7038
    /*
7039
     *   Transmit my radiating energy to everything within reach of the
7040
     *   sense.  
7041
     */
7042
    transmitAmbient(sense)
7043
    {
7044
        local ambient;
7045
        
7046
        /* get the energy level I'm transmitting */
7047
        ambient = self.(sense.ambienceProp);
7048
7049
        /* if this is greater than my ambient level so far, take it */
7050
        if (ambient > tmpAmbient_)
7051
        {
7052
            /* 
7053
             *   remember the new settings: start me with my own ambience,
7054
             *   and with no fill medium in the way (there's nothing
7055
             *   between me and myself, so I shine on myself with full
7056
             *   force and with no intervening fill medium) 
7057
             */
7058
            tmpAmbient_ = ambient;
7059
            tmpAmbientFill_ = nil;
7060
7061
            /* 
7062
             *   if the level is at least 2, transmit to adjacent objects
7063
             *   (level 1 is self-illumination only, so we don't transmit
7064
             *   to anything else) 
7065
             */
7066
            if (ambient >= 2)
7067
            {
7068
                /* transmit to my containers */
7069
                shineOnLoc(sense, ambient, nil);
7070
7071
                /* shine on my contents */
7072
                shineOnContents(sense, ambient, nil);
7073
            }
7074
        }
7075
7076
        /* 
7077
         *   Apply our interior self-illumination if necessary.  If we're
7078
         *   self-illuminating, and our interior surface doesn't already
7079
         *   have higher ambient energy from another source, then the
7080
         *   ambient energy at our inner surface is simply 1, since we
7081
         *   self-illuminate inside as well as outside. 
7082
         */
7083
        if (ambient == 1 && ambient > tmpAmbientWithin_)
7084
            tmpAmbientWithin_ = ambient;
7085
    }
7086
7087
    /*
7088
     *   Transmit ambient energy to my location or locations. 
7089
     */
7090
    shineOnLoc(sense, ambient, fill)
7091
    {
7092
        /* 
7093
         *   shine on my container, if I have one, and its immediate
7094
         *   children 
7095
         */
7096
        if (location != nil)
7097
            location.shineFromWithin(self, sense, ambient, fill);
7098
    }
7099
7100
    /*
7101
     *   Shine ambient energy at my surface onto my contents. 
7102
     */
7103
    shineOnContents(sense, ambient, fill)
7104
    {
7105
        local levelWithin;
7106
        
7107
        /*
7108
         *   Figure the level of energy to transmit to my contents.  To
7109
         *   reach my contents, the ambient energy here must traverse our
7110
         *   surface.  Since we want to know what the ambient light at our
7111
         *   surface looks like when viewed from our interior, we must use
7112
         *   the sensing-out transparency. 
7113
         */
7114
        levelWithin = adjustBrightness(ambient, transSensingOut(sense));
7115
7116
        /* 
7117
         *   Remember our ambient level at our inner surface, if the
7118
         *   adjusted transmission is higher than our tentative ambient
7119
         *   level already set from another source or path.  Note that the
7120
         *   tmpAmbientWithin_ caches the ambient level at our inner
7121
         *   surface, which comes before we adjust for our fill medium
7122
         *   (because our fill medium is enclosed by our inner surface).  
7123
         */
7124
        if (levelWithin >= 2 && levelWithin > tmpAmbientWithin_)
7125
        {
7126
            local fillWithin;
7127
7128
            /* note my new interior ambience */
7129
            tmpAmbientWithin_ = levelWithin;
7130
7131
            /*
7132
             *   If there's a new fill material in my interior that the
7133
             *   ambient energy here hasn't already just traversed, we must
7134
             *   further adjust the ambient level in my interior by the
7135
             *   fill transparency.  
7136
             */
7137
            fillWithin = tmpFillMedium_;
7138
            if (fillWithin != fill && fillWithin != nil)
7139
            {
7140
                /* 
7141
                 *   we're traversing a new fill material - adjust the
7142
                 *   brightness further for the fill material 
7143
                 */
7144
                levelWithin = adjustBrightness(levelWithin,
7145
                                               fillWithin.senseThru(sense));
7146
            }
7147
            
7148
            /* if that leaves any ambience in my interior, transmit it */
7149
            if (levelWithin >= 2)
7150
            {
7151
                /* shine on each object directly within me */
7152
                for (local clst = contents, local i = 1,
7153
                     local len = clst.length() ; i <= len ; ++i)
7154
                {
7155
                    /* transmit the ambient energy to this child item */
7156
                    clst[i].shineFromWithout(self, sense,
7157
                                             levelWithin, fillWithin);
7158
                }
7159
            }
7160
        }
7161
    }
7162
7163
    /*
7164
     *   Transmit ambient energy from an object within me.  This transmits
7165
     *   to my outer surface, and also to my own immediate children - in
7166
     *   other words, to the peers of the child shining on us.  We need to
7167
     *   transmit to the source's peers right now, because it might
7168
     *   degrade the ambient energy to go out through our surface.  
7169
     */
7170
    shineFromWithin(fromChild, sense, ambient, fill)
7171
    {
7172
        local levelWithout;
7173
        local levelWithin;
7174
        local fillWithin;
7175
        
7176
        /*
7177
         *   Calculate the change in energy as the sense makes its way to
7178
         *   our "inner surface," and to peers of the sender - in both
7179
         *   cases, the energy must traverse our fill medium to get to the
7180
         *   next object.
7181
         *   
7182
         *   As always, energy must never traverse a single fill medium
7183
         *   more than once consecutively, so if the last fill material is
7184
         *   the same as the fill material here, no further adjustment is
7185
         *   necessary for another traversal of the same material.  
7186
         */
7187
        levelWithin = ambient;
7188
        fillWithin = tmpFillMedium_;
7189
        if (fillWithin != fill && fillWithin != nil)
7190
        {
7191
            /* adjust the brightness for the fill traversal */
7192
            levelWithin = adjustBrightness(levelWithin,
7193
                                           fillWithin.senseThru(sense));
7194
        }
7195
7196
        /* if there's no energy left to transmit, we're done */
7197
        if (levelWithin < 2)
7198
            return;
7199
7200
        /* 
7201
         *   Since we're transmitting the energy from within us, calculate
7202
         *   any attenuation as the energy goes from our inner surface to
7203
         *   our outer surface - this is the energy that makes it through
7204
         *   to our exterior and thus is the new ambient level at our
7205
         *   surface.  We must calculate the attenuation that a viewer
7206
         *   from outside sees looking at an energy source within us, so
7207
         *   we must use the sensing-in transparency.
7208
         *   
7209
         *   Note that we start here with the level within that we've
7210
         *   already calculated: we assume that the energy from our child
7211
         *   must first traverse our interior medium before reaching our
7212
         *   "inner surface," at which point it must then further traverse
7213
         *   our surface material to reach our "outer surface," at which
7214
         *   point it's the ambient level at our exterior.  
7215
         */
7216
        levelWithout = adjustBrightness(levelWithin, transSensingIn(sense));
7217
7218
        /* 
7219
         *   The level at our outer surface is the new ambient level for
7220
         *   this object.  The last fill material traversed is the fill
7221
         *   material within me.  If it's the best yet, take it.
7222
         */
7223
        if (levelWithout > tmpAmbient_)
7224
        {
7225
            /* it's the best so far - cache it */
7226
            tmpAmbient_ = levelWithout;
7227
            tmpAmbientFill_ = fillWithin;
7228
7229
            /* transmit to our containers */
7230
            shineOnLoc(sense, levelWithout, fillWithin);
7231
        }
7232
7233
        /* transmit the level within to each peer of the sender */
7234
        if (levelWithin > tmpAmbientWithin_)
7235
        {
7236
            /* note our level within */
7237
            tmpAmbientWithin_ = levelWithin;
7238
7239
            /* transmit to each of our children */
7240
            for (local i = 1, local clst = contents,
7241
                 local len = clst.length() ; i <= len ; ++i)
7242
            {
7243
                /* get the current item */
7244
                local cur = clst[i];
7245
7246
                /* if it's not the source, shine on it */
7247
                if (cur != fromChild)
7248
                    cur.shineFromWithout(self, sense,
7249
                                         levelWithin, fillWithin);
7250
            }
7251
        }
7252
    }
7253
7254
    /*
7255
     *   Transmit ambient energy from an object immediately containing me.
7256
     */
7257
    shineFromWithout(fromParent, sense, level, fill)
7258
    {
7259
        /* if this is the best level yet, take it and transmit it */
7260
        if (level > tmpAmbient_)
7261
        {
7262
            /* cache this new best level */
7263
            tmpAmbient_ = level;
7264
            tmpAmbientFill_ = fill;
7265
7266
            /* transmit it down to my children */
7267
            shineOnContents(sense, level, fill);
7268
        }
7269
    }
7270
7271
    /*
7272
     *   Cache the sense path for each object reachable from this point of
7273
     *   view.  Fills in tmpTrans_ and tmpObstructor_ for each object with
7274
     *   the best transparency path from the object to me.  
7275
     */
7276
    cacheSensePath(sense)
7277
    {
7278
        /* the view from me to myself is unobstructed */
7279
        tmpPathIsIn_ = true;
7280
        tmpTrans_ = transparent;
7281
        tmpTransWithin_ = transparent;
7282
        tmpObstructor_ = nil;
7283
        tmpObstructorWithin_ = nil;
7284
7285
        /* build a path to my containers */
7286
        sensePathToLoc(sense, transparent, nil, nil);
7287
7288
        /* build a path to my contents */
7289
        sensePathToContents(sense, transparent, nil, nil);
7290
    }
7291
7292
    /*
7293
     *   Build a path to my location or locations 
7294
     */
7295
    sensePathToLoc(sense, trans, obs, fill)
7296
    {
7297
        /* 
7298
         *   proceed to my container, if I have one, and its immediate
7299
         *   children 
7300
         */
7301
        if (location != nil)
7302
            location.sensePathFromWithin(self, sense, trans, obs, fill);
7303
    }
7304
7305
    /*
7306
     *   Build a sense path to my contents 
7307
     */
7308
    sensePathToContents(sense, trans, obs, fill)
7309
    {
7310
        local transWithin;
7311
        local obsWithin;
7312
        local fillWithin;
7313
7314
        /*
7315
         *   Figure the transparency to my contents.  To reach my
7316
         *   contents, we must look in through our surface.  If we change
7317
         *   the transparency, we're the new obstructor.  
7318
         */
7319
        transWithin = transparencyAdd(trans, transSensingIn(sense));
7320
        obsWithin = (trans == transWithin ? obs : self);
7321
7322
        /*
7323
         *   If there's a new fill material in my interior that we haven't
7324
         *   already just traversed, we must further adjust the
7325
         *   transparency by the fill transparency. 
7326
         */
7327
        fillWithin = tmpFillMedium_;
7328
        if (fillWithin != fill && fillWithin != nil)
7329
        {
7330
            local oldTransWithin = transWithin;
7331
            
7332
            /* we're traversing a new fill material */
7333
            transWithin = transparencyAdd(transWithin,
7334
                                          fillWithin.senseThru(sense));
7335
            if (transWithin != oldTransWithin)
7336
                obsWithin = fill;
7337
        }
7338
7339
        /* if the path isn't opaque, proceed to my contents */
7340
        if (transWithin != opaque)
7341
        {
7342
            /* build a path to each child */
7343
            for (local clst = contents,
7344
                 local i = 1, local len = clst.length() ; i <= len ; ++i)
7345
            {
7346
                /* build a path to this child */
7347
                clst[i].sensePathFromWithout(self, sense, transWithin,
7348
                                             obsWithin, fillWithin);
7349
            }
7350
        }
7351
    }
7352
7353
    /*
7354
     *   Build a path from an object within me. 
7355
     */
7356
    sensePathFromWithin(fromChild, sense, trans, obs, fill)
7357
    {
7358
        local transWithin;
7359
        local fillWithin;
7360
        local transWithout;
7361
        local obsWithout;
7362
7363
        /* 
7364
         *   Calculate the transparency change along the path from the
7365
         *   child to our "inner surface" and to peers of the sender - in
7366
         *   both cases, we must traverse the fill material. 
7367
         *   
7368
         *   As always, energy must never traverse a single fill medium
7369
         *   more than once consecutively, so if the last fill material is
7370
         *   the same as the fill material here, no further adjustment is
7371
         *   necessary for another traversal of the same material.  
7372
         */
7373
        transWithin = trans;
7374
        fillWithin = tmpFillMedium_;
7375
        if (fillWithin != fill && fillWithin != nil)
7376
        {
7377
            /* adjust for traversing a new fill material */
7378
            transWithin = transparencyAdd(transWithin,
7379
                                          fillWithin.senseThru(sense));
7380
            if (transWithin != trans)
7381
                obs = fillWithin;
7382
        }
7383
7384
        /* if we're opaque at this point, we're done */
7385
        if (transWithin == opaque)
7386
            return;
7387
7388
        /*
7389
         *   Calculate the transparency going from our inner surface to
7390
         *   our outer surface - we must traverse our own material to
7391
         *   travel this segment. 
7392
         */
7393
        transWithout = transparencyAdd(transWithin, transSensingOut(sense));
7394
        obsWithout = (transWithout != transWithin ? self : obs);
7395
7396
        /*
7397
         *   We now have the path to our outer surface.  The last fill
7398
         *   material traversed is the fill material within me.  If this
7399
         *   is the best yet, remember it.  
7400
         */
7401
        if (transparencyCompare(transWithout, tmpTrans_) > 0)
7402
        {
7403
            /* it's the best so far - cache it */
7404
            tmpTrans_ = transWithout;
7405
            tmpObstructor_ = obsWithout;
7406
7407
            /* we're coming to this object from within */
7408
            tmpPathIsIn_ = nil;
7409
7410
            /* transmit to our containers */
7411
            sensePathToLoc(sense, transWithout, obsWithout, fillWithin);
7412
        }
7413
7414
        /* 
7415
         *   if this is the best interior transparency yet, build a path
7416
         *   to each peer of the sender 
7417
         */
7418
        if (transparencyCompare(transWithin, tmpTransWithin_) > 0)
7419
        {
7420
            /* it's the best so far - cache it */
7421
            tmpTransWithin_ = transWithin;
7422
            tmpObstructorWithin_ = obs;
7423
7424
            /* we're coming to this object from within */
7425
            tmpPathIsIn_ = nil;
7426
            
7427
            /* build a path to each peer of the sender */
7428
            for (local i = 1, local clst = contents,
7429
                 local len = clst.length() ; i <= len ; ++i)
7430
            {
7431
                /* get this item */
7432
                local cur = clst[i];
7433
                
7434
                /* if it's not the source, build a path to it */
7435
                if (cur != fromChild)
7436
                    cur.sensePathFromWithout(self, sense, transWithin,
7437
                                             obs, fillWithin);
7438
            }
7439
        }
7440
    }
7441
7442
    /*
7443
     *   Build a path from an object immediately containing me. 
7444
     */
7445
    sensePathFromWithout(fromParent, sense, trans, obs, fill)
7446
    {
7447
        /* if this is the best level yet, take it and keep going */
7448
        if (transparencyCompare(trans, tmpTrans_) > 0)
7449
        {
7450
            /* remember this new best level */
7451
            tmpTrans_ = trans;
7452
            tmpObstructor_ = obs;
7453
7454
            /* we're coming to this object from outside */
7455
            tmpPathIsIn_ = true;
7456
7457
            /* build a path down into my children */
7458
            sensePathToContents(sense, trans, obs, fill);
7459
        }
7460
    }
7461
    
7462
7463
    /*
7464
     *   Clear the sensory scratch-pad properties, in preparation for a
7465
     *   sensory calculation pass. 
7466
     */
7467
    clearSenseInfo()
7468
    {
7469
        tmpPathIsIn_ = true;
7470
        tmpAmbient_ = 0;
7471
        tmpAmbientWithin_ = 0;
7472
        tmpAmbientFill_ = nil;
7473
        tmpTrans_ = opaque;
7474
        tmpTransWithin_ = opaque;
7475
        tmpObstructor_ = nil;
7476
        tmpObstructorWithin_ = nil;
7477
7478
        /* pre-calculate my fill medium */
7479
        tmpFillMedium_ = fillMedium();
7480
    }
7481
7482
    /* 
7483
     *   Scratch-pad for calculating ambient energy level - valid only
7484
     *   after calcAmbience and until the game state changes in any way.
7485
     *   This is for internal use within the sense propagation methods
7486
     *   only.  
7487
     */
7488
    tmpAmbient_ = 0
7489
7490
    /*
7491
     *   Last fill material traversed by the ambient sense energy in
7492
     *   tmpAmbient_.  We must keep track of this so that we can treat
7493
     *   consecutive traversals of the same fill material as equivalent to
7494
     *   a single traversal.  
7495
     */
7496
    tmpAmbientFill_ = nil
7497
7498
    /*
7499
     *   Scrach-pad for the best transparency level to this object from
7500
     *   the current point of view.  This is used during cacheSenseInfo to
7501
     *   keep track of the sense path to this object. 
7502
     */
7503
    tmpTrans_ = opaque
7504
7505
    /*
7506
     *   Scratch-pad for the obstructor that contributed to a
7507
     *   non-transparent path to this object in tmpTrans_. 
7508
     */
7509
    tmpObstructor_ = nil
7510
7511
    /*
7512
     *   Scratch-pads for the ambient level, best transparency, and
7513
     *   obstructor to our *interior* surface.  We keep track of these
7514
     *   separately from the exterior data so that we can tell what we
7515
     *   look like from the persepctive of an object within us.  
7516
     */
7517
    tmpAmbientWithin_ = 0
7518
    tmpTransWithin_ = opaque
7519
    tmpObstructorWithin_ = nil
7520
7521
    /*
7522
     *   Scratch-pad for the sense path direction at this object.  If this
7523
     *   is true, the sense path is pointing inward - that is, the path
7524
     *   from the source object to here is entering from outside me.
7525
     *   Otherwise, the sense path is pointing outward.  
7526
     */
7527
    tmpPathIsIn_ = true
7528
7529
    /*
7530
     *   My fill medium.  We cache this during each sense path
7531
     *   calculation, since the fill medium calculation often requires
7532
     *   traversing several containment levels. 
7533
     */
7534
    tmpFillMedium_ = nil
7535
7536
    /*
7537
     *   Merge two senseInfoTable tables.  Merges the second table into
7538
     *   the first.  If an object appears only in the first table, the
7539
     *   entry is left unchanged; if an object appears only in the second
7540
     *   table, the entry is added to the first table.  If an object
7541
     *   appears in both tables, we'll keep the one with better detail or
7542
     *   brightness, adding it to the first table if it's the one in the
7543
     *   second table.  
7544
     */
7545
    mergeSenseInfoTable(a, b)
7546
    {
7547
        /* if either table is nil, return the other table */
7548
        if (a == nil)
7549
            return b;
7550
        else if (b == nil)
7551
            return a;
7552
        
7553
        /* 
7554
         *   iterate over the second table, merging each item from the
7555
         *   second table into the corresponding item in the first table 
7556
         */
7557
        b.forEachAssoc({obj, info: a[obj] = mergeSenseInfo(a[obj], info)});
7558
7559
        /* return the merged first table */
7560
        return a;
7561
    }
7562
7563
    /*
7564
     *   Merge two SenseInfo items.  Chooses the "better" of the two items
7565
     *   and returns it, where "better" is defined as more transparent,
7566
     *   or, transparencies being equal, brighter in ambient energy.  
7567
     */
7568
    mergeSenseInfo(a, b)
7569
    {
7570
        /* if one or the other is nil, return the non-nil one */
7571
        if (a == nil)
7572
            return b;
7573
        if (b == nil)
7574
            return a;
7575
7576
        /*
7577
         *   Both items are non-nil, so keep the better of the two.  If
7578
         *   the transparencies aren't equal, keep the one that's more
7579
         *   transparent.  Otherwise, keep the one with higher ambient
7580
         *   energy. 
7581
         */
7582
        if (a.trans == b.trans)
7583
        {
7584
            /* 
7585
             *   The transparencies are equal, so choose the one with
7586
             *   higher ambient energy.  If those are the same,
7587
             *   arbitrarily keep the first one. 
7588
             */
7589
            if (a.ambient >= b.ambient)
7590
                return a;
7591
            else
7592
                return b;
7593
        }
7594
        else
7595
        {
7596
            /* 
7597
             *   The transparencies are unequal, so pick the one with
7598
             *   better transparency. 
7599
             */
7600
            if (transparencyCompare(a.trans, b.trans) < 0)
7601
                return b;
7602
            else
7603
                return a;
7604
        }
7605
    }
7606
7607
    /*
7608
     *   Receive notification that a command is about to be performed.
7609
     *   This is called on each object connected by containment with the
7610
     *   actor performing the command, and on any objects explicitly
7611
     *   registered with the actor, the actor's location and its locations
7612
     *   up to the outermost container, or the directly involved objects.  
7613
     */
7614
    beforeAction()
7615
    {
7616
        /* by default, do nothing */
7617
    }
7618
7619
    /*
7620
     *   Receive notification that a command has just been performed.
7621
     *   This is called by the same rules as beforeAction(), but under the
7622
     *   conditions prevailing after the command has been completed. 
7623
     */
7624
    afterAction()
7625
    {
7626
        /* by default, do nothing */
7627
    }
7628
7629
    /*
7630
     *   Receive notification that a traveler (an actor or a vehicle, for
7631
     *   example) is about to depart via travelerTravelTo(), OR that an
7632
     *   actor is about to move among nested locations via travelWithin().
7633
     *   This notification is sent to each object connected to the traveler
7634
     *   by containment, just before the traveler departs.
7635
     *   
7636
     *   If the traveler is traveling between top-level locations,
7637
     *   'connector' is the TravelConnector object being traversed.  If an
7638
     *   actor is merely moving between nested locations, 'connector' will
7639
     *   be nil.  
7640
     */
7641
    beforeTravel(traveler, connector) { /* do nothing by default */ }
7642
7643
    /*
7644
     *   Receive notification that a traveler has just arrived via
7645
     *   travelerTravelTo().  This notification is sent to each object
7646
     *   connected to the traveler by containment, in its new location,
7647
     *   just after the travel completes. 
7648
     */
7649
    afterTravel(traveler, connector) { /* do nothing by default */ }
7650
7651
    /*
7652
     *   Get my notification list - this is a list of objects on which we
7653
     *   must call beforeAction and afterAction when this object is
7654
     *   involved in a command as the direct object, indirect object, or
7655
     *   any addition object (other than as the actor performing the
7656
     *   command).
7657
     *   
7658
     *   The general notification mechanism always includes in the
7659
     *   notification list all of the objects connected by containment to
7660
     *   the actor; this method allows for explicit registration of
7661
     *   additional objects that must be notified when commands are
7662
     *   performed on this object even when the other objects are nowhere
7663
     *   nearby.  
7664
     */
7665
    getObjectNotifyList()
7666
    {
7667
        /* return our registration list */
7668
        return objectNotifyList;
7669
    }
7670
7671
    /*
7672
     *   Add an item to our registered notification list for actions
7673
     *   involving this object as the direct object, indirect object, and
7674
     *   so on.
7675
     *   
7676
     *   Items can be added here if they must be notified of actions
7677
     *   involving this object regardless of the physical proximity of
7678
     *   this item and the notification item.  
7679
     */
7680
    addObjectNotifyItem(obj)
7681
    {
7682
        objectNotifyList += obj;
7683
    }
7684
7685
    /* remove an item from the registered notification list */
7686
    removeObjectNotifyItem(obj)
7687
    {
7688
        objectNotifyList -= obj;
7689
    }
7690
7691
    /* our list of registered notification items */
7692
    objectNotifyList = []
7693
7694
    /* -------------------------------------------------------------------- */
7695
    /*
7696
     *   Verify a proposed change of location of this object from its
7697
     *   current container hierarchy to the given new container.  We'll
7698
     *   verify removal from each container up to but not including a
7699
     *   parent that's in common with the new container - we stop upon
7700
     *   reaching the common parent because the object isn't leaving the
7701
     *   common parent, but merely repositioned around within it.  We'll
7702
     *   also verify insertion into each new parent from the first
7703
     *   non-common parent on down to the immediate new container.
7704
     *   
7705
     *   This routine is called any time an actor action would cause this
7706
     *   object to be moved to a new container, so it is the common point
7707
     *   at which to intercept any action that would attempt to move the
7708
     *   object.  
7709
     */
7710
    verifyMoveTo(newLoc)
7711
    {
7712
        /* check removal up to the common parent */
7713
        sendNotifyRemove(self, newLoc, &verifyRemove);
7714
7715
        /* check insertion into parents up to the common parent */
7716
        if (newLoc != nil)
7717
            newLoc.sendNotifyInsert(self, newLoc, &verifyInsert);
7718
    }
7719
7720
    /*
7721
     *   Send the given notification to each direct parent, each of their
7722
     *   direct parents, and so forth, stopping when we reach parents that
7723
     *   we have in common with our new location.  We don't notify parents
7724
     *   in common with new location (or their parents) because we're not
7725
     *   actually removing the object from the common parents.  
7726
     */
7727
    sendNotifyRemove(obj, newLoc, msg)
7728
    {
7729
        /* send notification to each container, as appropriate */
7730
        forEachContainer(new function(loc) {
7731
            /*
7732
             *   If this container contains the new location, don't send it
7733
             *   (or its parents) notification, since we're not leaving it.
7734
             *   Otherwise, send the notification and proceed to its
7735
             *   parents.  
7736
             */
7737
            if (newLoc == nil
7738
                || (loc != newLoc && !newLoc.isIn(loc)))
7739
            {
7740
                /* notify this container of the removal */
7741
                loc.(msg)(obj);
7742
7743
                /* recursively notify this container's containers */
7744
                loc.sendNotifyRemove(obj, newLoc, msg);
7745
            }
7746
        });
7747
    }
7748
7749
    /*
7750
     *   Send the given notification to each direct parent, each of their
7751
     *   direct parents, and so forth, stopping when we reach parents that
7752
     *   we have in common with our new location.  We don't notify parents
7753
     *   in common with new location (or their parents).
7754
     *   
7755
     *   This should always be called *before* a change of location is
7756
     *   actually put into effect, so that we will still be in our old
7757
     *   container when this is called.  'obj' is the object being
7758
     *   inserted, and 'newCont' is the new direct container.  
7759
     */
7760
    sendNotifyInsert(obj, newCont, msg)
7761
    {
7762
        /* 
7763
         *   If the object is already in me, there's no need to notify
7764
         *   myself of the insertion; otherwise, send the notification.
7765
         *   
7766
         *   If the object is already inside me indirectly, and we're
7767
         *   moving it directly in me, still notify myself, since we're
7768
         *   still picking up a new direct child.  
7769
         */
7770
        if (!obj.isIn(self))
7771
        {
7772
            /* 
7773
             *   before we notify ourselves, notify my own parents - this
7774
             *   sends the notifications from the outside in 
7775
             */
7776
            forEachContainer({loc: loc.sendNotifyInsert(obj, newCont, msg)});
7777
7778
            /* notify this potential container of the insertion */
7779
            self.(msg)(obj, newCont);
7780
        }
7781
        else if (newCont == self && !obj.isDirectlyIn(self))
7782
        {
7783
            /* notify myself of the new direct insertion */
7784
            self.(msg)(obj, self);
7785
        }
7786
    }
7787
7788
    /*
7789
     *   Verify removal of an object from my contents or a child object's
7790
     *   contents.  By default we allow the removal.  This is to be called
7791
     *   during verification only, so gVerifyResult is valid when this is
7792
     *   called.  
7793
     */
7794
    verifyRemove(obj)
7795
    {
7796
    }
7797
7798
    /*
7799
     *   Verify insertion of an object into my contents.  By default we
7800
     *   allow it, unless I'm already inside the other object.  This is to
7801
     *   be called only during verification.  
7802
     */
7803
    verifyInsert(obj, newCont)
7804
    {
7805
        /* 
7806
         *   If I'm inside the other object, don't allow it, since this
7807
         *   would create circular containment. 
7808
         */
7809
        if (isIn(obj))
7810
            illogicalNow(obj.circularlyInMessage, newCont, obj);
7811
    }
7812
7813
    /* 
7814
     *   my message indicating that another object x cannot be put into me
7815
     *   because I'm already in x 
7816
     */
7817
    circularlyInMessage = &circularlyInMsg
7818
7819
    /*
7820
     *   Receive notification that we are about to remove an object from
7821
     *   this container.  This is normally called during the action()
7822
     *   phase.
7823
     *   
7824
     *   When an object is about to be moved via moveInto(), the library
7825
     *   calls notifyRemove on the old container tree, then notifyInsert on
7826
     *   the new container tree, then notifyMoveInto on the object being
7827
     *   moved.  Any of these routines can cancel the operation by
7828
     *   displaying an explanatory message and calling 'exit'.  
7829
     */
7830
    notifyRemove(obj)
7831
    {
7832
    }
7833
7834
    /*
7835
     *   Receive notification that we are about to insert a new object into
7836
     *   this container.  'obj' is the object being moved, and 'newCont' is
7837
     *   the new direct container (which might be a child of ours).  This
7838
     *   is normally called during the action() phase.
7839
     *   
7840
     *   During moveInto(), this is called on the new container tree after
7841
     *   notifyRemove has been called on the old container tree.  This
7842
     *   routine can cancel the move by displaying an explanatory message
7843
     *   and calling 'exit'.  
7844
     */
7845
    notifyInsert(obj, newCont)
7846
    {
7847
    }
7848
7849
    /*
7850
     *   Receive notification that I'm about to be moved to a new
7851
     *   container.  By default, we do nothing; subclasses can override
7852
     *   this to do any special processing when this object is moved.  This
7853
     *   is normally called during the action() phase.
7854
     *   
7855
     *   During moveInto(), this routine is called after notifyRemove() and
7856
     *   notifyInsert() have been called on the old and new container trees
7857
     *   (respectively).  This routine can cancel the move by displaying an
7858
     *   explanatory message and calling 'exit'.
7859
     *   
7860
     *   This routine is the last in the notification sequence, so if this
7861
     *   routine doesn't cancel the move, then the move will definitely
7862
     *   happen (at least to the extent that we'll definitely call
7863
     *   baseMoveInto() to carry out the move).  
7864
     */
7865
    notifyMoveInto(newCont)
7866
    {
7867
    }
7868
7869
    /* -------------------------------------------------------------------- */
7870
    /*
7871
     *   Determine if one property on this object effectively "hides"
7872
     *   another.  This is a sort of override check for two distinct
7873
     *   properties.
7874
     *   
7875
     *   We look at the object to determine where prop1 and prop2 are
7876
     *   defined in the class hierarchy.  If prop1 isn't defined, it
7877
     *   definitely doesn't hide prop2.  If prop2 isn't defined, prop1
7878
     *   definitely hides it.  If both are defined, then prop1 hides prop2
7879
     *   if and only if it is defined at a point in the class hierarchy
7880
     *   that is "more specialized" than prop2.  That is, for prop1 to
7881
     *   hide prop2, the class that defines prop1 must either be the same
7882
     *   as the class that defines prop2, or the class where prop1 is
7883
     *   defined must inherit from the class that defines prop2, or the
7884
     *   class where prop1 is defined must be earlier in a multiple
7885
     *   inheritance list than the class defining prop2.  
7886
     */
7887
    propHidesProp(prop1, prop2)
7888
    {
7889
        local definer1;
7890
        local definer2;
7891
        
7892
        /* 
7893
         *   get the classes in our hierarchy where the two properties are
7894
         *   defined 
7895
         */
7896
        definer1 = propDefined(prop1, PropDefGetClass);
7897
        definer2 = propDefined(prop2, PropDefGetClass);
7898
7899
        /* if prop1 isn't defined, it definitely doesn't hide prop2 */
7900
        if (definer1 == nil)
7901
            return nil;
7902
7903
        /* if prop1 isn't defined, prop1 definitely hides it */
7904
        if (definer2 == nil)
7905
            return true;
7906
7907
        /* 
7908
         *   They're both defined.  If definer1 inherits from definer2,
7909
         *   then prop1 definitely hides prop2. 
7910
         */
7911
        if (definer1.ofKind(definer2))
7912
            return true;
7913
7914
        /* 
7915
         *   if definer2 inherits from definer1, then prop2 hides prop1,
7916
         *   so prop1 doesn't hide prop2 
7917
         */
7918
        if (definer2.ofKind(definer1))
7919
            return nil;
7920
7921
        /*
7922
         *   The two classes don't have an inheritance relation among
7923
         *   themselves, but they might still be related in our own
7924
         *   hierarchy by multiple inheritance.  In particular, if there
7925
         *   is some point in the hierarchy where we have multiple base
7926
         *   classes, and one class among the multiple bases inherits from
7927
         *   definer1 and the other from definer2, then the one that's
7928
         *   earlier in the multiple inheritance list is the hider. 
7929
         */
7930
        return superHidesSuper(definer1, definer2);
7931
    }
7932
7933
    /*
7934
     *   Determine if a given superclass of ours hides another superclass
7935
     *   of ours, by being inherited (directly or indirectly) in our class
7936
     *   list ahead of the other. 
7937
     */
7938
    superHidesSuper(s1, s2)
7939
    {
7940
        local lst;
7941
        local idx1, idx2;
7942
        
7943
        /* get our superclass list */
7944
        lst = getSuperclassList();
7945
7946
        /* if we have no superclass, there is no hiding */
7947
        if (lst.length() == 0)
7948
            return nil;
7949
7950
        /* 
7951
         *   if we have only one element, there's obviously no hiding
7952
         *   going on at this level, so simply traverse into the
7953
         *   superclass to see if the hiding happens there 
7954
         */
7955
        if (lst.length() == 1)
7956
            return lst[1].superHidesSuper(s1, s2);
7957
7958
        /*
7959
         *   Scan the superclass list to determine the first superclass to
7960
         *   which each of the two superclasses is related.  Stop looking
7961
         *   when we find both superclasses or exhaust our list.  
7962
         */
7963
        for (local i = 1, idx1 = idx2 = nil ;
7964
             i <= lst.length() && (idx1 == nil || idx2 == nil) ; ++i)
7965
        {
7966
            /* 
7967
             *   if we haven't found s1 yet, and this superclass of ours
7968
             *   inherits from s1, this is the earliest at which we've
7969
             *   found s1 
7970
             */
7971
            if (idx1 == nil && lst[i].ofKind(s1))
7972
                idx1 = i;
7973
7974
            /* likewise for the other superclass */
7975
            if (idx2 == nil && lst[i].ofKind(s2))
7976
                idx2 = i;
7977
        }
7978
7979
        /* 
7980
         *   if we found the two superclasses at different points in our
7981
         *   hierarchy, the one that comes earlier hides the other; so, if
7982
         *   idx1 is less than idx2, s1 hides s2, and if idx1 is greater
7983
         *   than idx2, s2 hides s1 (equivalently, s1 doesn't hide s2) 
7984
         */
7985
        if (idx1 != idx2)
7986
            return idx1 < idx2;
7987
7988
        /*
7989
         *   We found both superclasses at the same point in our
7990
         *   hierarchy, so they must be multiply inherited by this base
7991
         *   class or one of its classes.  Keep looking up the hierarchy
7992
         *   for our answer. 
7993
         */
7994
        return lst[idx1].superHidesSuper(s1, s2);
7995
    }
7996
7997
    /* 
7998
     *   Generic "check" failure routine.  This displays the given failure
7999
     *   message, then performs an 'exit' to cancel the current command.
8000
     *   'msg' is the message to display - it can be a single-quoted string
8001
     *   with the message text, or a property pointer.  If 'msg' is a
8002
     *   property pointer, any necessary message parameters can be supplied
8003
     *   as additional 'params' arguments.  
8004
     */
8005
    failCheck(msg, [params])
8006
    {
8007
        reportFailure(msg, params...);
8008
        exit;
8009
    }
8010
8011
    /* -------------------------------------------------------------------- */
8012
    /*
8013
     *   "Examine" action 
8014
     */
8015
    dobjFor(Examine)
8016
    {
8017
        preCond = [objVisible]
8018
        verify()
8019
        {
8020
            /* give slight preference to an object being held */
8021
            if (!isIn(gActor))
8022
                logicalRank(80, 'not held');
8023
        }
8024
        action()
8025
        {
8026
            /* 
8027
             *   call our mainExamine method from the current actor's point
8028
             *   of view 
8029
             */
8030
            fromPOV(gActor, gActor, &mainExamine);
8031
        }
8032
    }
8033
8034
    /*
8035
     *   Main examination processing.  This is called with the current
8036
     *   global POV set to the actor performing the 'examine' command. 
8037
     */
8038
    mainExamine()
8039
    {
8040
        /* perform the basic 'examine' action */
8041
        basicExamine();
8042
8043
        /* 
8044
         *   listen to and smell the object, but only show a message if we
8045
         *   have an explicitly associated Noise/Odor object 
8046
         */
8047
        basicExamineListen(nil);
8048
        basicExamineSmell(nil);
8049
        
8050
        /* show special descriptions for any contents */
8051
        examineSpecialContents();
8052
    }
8053
8054
    /*
8055
     *   Perform the basic 'examine' action.  This shows either the normal
8056
     *   or initial long description (the latter only if the object hasn't
8057
     *   been moved yet, and it has a special initial examine
8058
     *   description), and marks the object as having been described at
8059
     *   least once.  
8060
     */
8061
    basicExamine()
8062
    {
8063
        local info;
8064
        local t;
8065
        
8066
        /* check the transparency to viewing this object */
8067
        info = getVisualSenseInfo();
8068
        t = info.trans;
8069
8070
        /*
8071
         *   If the viewing conditions are 'obscured' or 'distant', use
8072
         *   the special, custom messages for those conditions.
8073
         *   Otherwise, if the details are visible (which will be the case
8074
         *   even for a distant or obscured object if its sightSize is set
8075
         *   to 'large'), use the ordinary 'desc' description.  Otherwise,
8076
         *   use an appropriate default description indicating that the
8077
         *   object's details aren't visible.  
8078
         */
8079
        if (getOutermostRoom() != getPOVDefault(gActor).getOutermostRoom()
8080
            && propDefined(&remoteDesc))
8081
        {
8082
            /* we're remote, so show our remote description */
8083
            remoteDesc(getPOVDefault(gActor));
8084
        }
8085
        else if (t == obscured && propDefined(&obscuredDesc))
8086
        {
8087
            /* we're obscured, so show our special obscured description */
8088
            obscuredDesc(info.obstructor);
8089
        }
8090
        else if (t == distant && propDefined(&distantDesc))
8091
        {
8092
            /* we're distant, so show our special distant description */
8093
            distantDesc;
8094
        }
8095
        else if (canDetailsBeSensed(sight, info, getPOVDefault(gActor)))
8096
        {
8097
            /* 
8098
             *   Viewing conditions and/or scale are suitable for showing
8099
             *   full details, so show my normal long description.  Use
8100
             *   the initDesc if desired, otherwise show the normal "desc"
8101
             *   description.  
8102
             */
8103
            if (useInitDesc())
8104
                initDesc;
8105
            else
8106
                desc;
8107
8108
            /* note that we've examined it */
8109
            described = true;
8110
8111
            /* show any subclass-specific status */
8112
            examineStatus();
8113
        }
8114
        else if (t == obscured)
8115
        {
8116
            /* 
8117
             *   we're obscured, and we're not large enough to see the
8118
             *   details anyway, and we don't have a special description
8119
             *   for when we're obscured; show the default description of
8120
             *   an obscured object 
8121
             */
8122
            defaultObscuredDesc(info.obstructor);
8123
        }
8124
        else if (t == distant)
8125
        {
8126
            /* 
8127
             *   we're distant, and we're not large enough to see the
8128
             *   details anyway, and we don't have a special distant
8129
             *   description; show the default distant description 
8130
             */
8131
            defaultDistantDesc;
8132
        }
8133
    }
8134
8135
    /*
8136
     *   Show any status associated with the object as part of the full
8137
     *   description.  This is shown after the basicExamine() message, and
8138
     *   is only displayed if we can see full details of the object
8139
     *   according to the viewing conditions.
8140
     *   
8141
     *   By default, we simply show our contents.  Even though this base
8142
     *   class isn't set up as a container from the player's perspective,
8143
     *   we could contain components which are themselves containers.  So,
8144
     *   to ensure that we properly describe any contents of our contents,
8145
     *   we need to list our children.  
8146
     */
8147
    examineStatus()
8148
    {
8149
        /* list my contents */
8150
        examineListContents();
8151
    }
8152
8153
    /* 
8154
     *   List my contents as part of Examine processing.  We'll recursively
8155
     *   list contents of contents; this will ensure that even if we have
8156
     *   no listable contents, we'll list any listable contents of our
8157
     *   contents.  
8158
     */
8159
    examineListContents()
8160
    {
8161
        /* show our contents with our normal contents lister */
8162
        examineListContentsWith(descContentsLister);
8163
    }
8164
8165
    /* list my contents for an "examine", using the given lister */
8166
    examineListContentsWith(lister)
8167
    {
8168
        local tab;
8169
        local lst;
8170
8171
        /* get the actor's visual sense information */
8172
        tab = gActor.visibleInfoTable();
8173
8174
        /* mark my contents as having been seen */
8175
        setContentsSeenBy(tab, gActor);
8176
8177
        /* if we don't list our contents on Examine, do nothing */
8178
        if (!contentsListedInExamine)
8179
            return;
8180
8181
        /* get my listed contents for 'examine' */
8182
        lst = getContentsForExamine(lister, tab);
8183
        
8184
        /* show my listable contents, if I have any */
8185
        lister.showList(gActor, self, lst, ListRecurse, 0, tab, nil);
8186
    }
8187
8188
    /*
8189
     *   Basic examination of the object for sound.  If the object has an
8190
     *   associated noise object, we'll describe it.
8191
     *   
8192
     *   If 'explicit' is true, we'll show our soundDesc if we have no
8193
     *   associated Noise object; otherwise, we'll show nothing at all
8194
     *   unless we have a Noise object.  
8195
     */
8196
    basicExamineListen(explicit)
8197
    {
8198
        local obj;
8199
        local info;
8200
        local t;
8201
8202
        /* get my associated Noise object, if we have one */
8203
        obj = getNoise();
8204
8205
        /* 
8206
         *   If this is not an explicit LISTEN command, only add the sound
8207
         *   description if we have a Noise object that is not marked as
8208
         *   "ambient."  
8209
         */
8210
        if (!explicit && (obj == nil || obj.isAmbient))
8211
            return;
8212
8213
        /* get our sensory information from the actor's point of view */
8214
        info = getPOVDefault(gActor).senseObj(sound, self);
8215
        t = info.trans;
8216
8217
        /*
8218
         *   If we have a transparent path to the object, or we have
8219
         *   'large' sound scale, show full details.  Otherwise, show the
8220
         *   appropriate non-detailed message for the listening conditions.
8221
         */
8222
        if (canDetailsBeSensed(sound, info, getPOVDefault(gActor)))
8223
        {
8224
            /* 
8225
             *   We can hear full details.  If we have a Noise object, show
8226
             *   its "listen to source" description; otherwise, show our
8227
             *   own default sound description.  
8228
             */
8229
            if (obj != nil)
8230
            {
8231
                /* note the explicit display of the Noise description */
8232
                obj.noteDisplay();
8233
                
8234
                /* show the examine-source description of the Noise */
8235
                obj.sourceDesc;
8236
            }
8237
            else
8238
            {
8239
                /* we have no Noise, so use our own description */
8240
                soundDesc;
8241
            }
8242
        }
8243
        else if (t == obscured)
8244
        {
8245
            /* show our 'obscured' description */
8246
            obscuredSoundDesc(info.obstructor);
8247
        }
8248
        else if (t == distant)
8249
        {
8250
            /* show our 'distant' description */
8251
            distantSoundDesc;
8252
        }
8253
8254
        /* 
8255
         *   If this is an explicit LISTEN TO directed at this object, also
8256
         *   mention any sounds we can hear from objects inside me, other
8257
         *   than the Noise object we've explicitly mentioned, that have a
8258
         *   sound presence.  
8259
         */
8260
        if (explicit)
8261
        {
8262
            local senseTab;
8263
            local presenceList;
8264
8265
            /* get the set of objects we can hear */
8266
            senseTab = getPOVDefault(gActor).senseInfoTable(sound);
8267
8268
            /* get the subset with a sound presence that are inside me */
8269
            presenceList = senseInfoTableSubset(
8270
                senseTab,
8271
                {x, info: x.soundPresence && x.isIn(self) && x != obj});
8272
8273
            /* show the soundHereDesc for each of these */
8274
            foreach (local cur in presenceList)
8275
                cur.soundHereDesc();
8276
        }
8277
    }
8278
8279
    /*
8280
     *   Basic examination of the object for odor.  If the object has an
8281
     *   associated odor object, we'll describe it.  
8282
     */
8283
    basicExamineSmell(explicit)
8284
    {
8285
        local obj;
8286
        local info;
8287
        local t;
8288
8289
        /* get our associated Odor object, if any */
8290
        obj = getOdor();
8291
8292
        /* 
8293
         *   If this is not an explicit SMELL command, only add the odor
8294
         *   description if we have an Odor object that is not marked as
8295
         *   "ambient."  
8296
         */
8297
        if (!explicit && (obj == nil || obj.isAmbient))
8298
            return;
8299
        
8300
        /* get our sensory information from the actor's point of view */
8301
        info = getPOVDefault(gActor).senseObj(smell, self);
8302
        t = info.trans;
8303
8304
        /*
8305
         *   If we have a transparent path to the object, or we have
8306
         *   'large' sound scale, show full details.  Otherwise, show the
8307
         *   appropriate non-detailed message for the listening conditions.
8308
         */
8309
        if (canDetailsBeSensed(smell, info, getPOVDefault(gActor)))
8310
        {
8311
            /* if we have a Noise object, show its "listen to source" */
8312
            if (obj != nil)
8313
            {
8314
                /* note the explicit display of the Odor description */
8315
                obj.noteDisplay();
8316
                
8317
                /* show the examine-source description of the Odor */
8318
                obj.sourceDesc;
8319
            }
8320
            else
8321
            {
8322
                /* we have no associated Odor; show our default description */
8323
                smellDesc;
8324
            }
8325
        }
8326
        else if (t == obscured)
8327
        {
8328
            /* show our 'obscured' description */
8329
            obscuredSmellDesc(info.obstructor);
8330
        }
8331
        else if (t == distant)
8332
        {
8333
            /* show our 'distant' description */
8334
            distantSmellDesc;
8335
        }
8336
8337
        /* 
8338
         *   If this is an explicit SMELL command directed at this object,
8339
         *   also mention any odors we can detect from objects inside me,
8340
         *   other than the Odor object we've explicitly mentioned, that
8341
         *   have an odor presence.  
8342
         */
8343
        if (explicit)
8344
        {
8345
            local senseTab;
8346
            local presenceList;
8347
8348
            /* get the set of objects we can smell */
8349
            senseTab = getPOVDefault(gActor).senseInfoTable(smell);
8350
8351
            /* get the subset with a smell presence that are inside me */
8352
            presenceList = senseInfoTableSubset(
8353
                senseTab,
8354
                {x, info: x.smellPresence && x.isIn(self) && x != obj});
8355
8356
            /* show the smellHereDesc for each of these */
8357
            foreach (local cur in presenceList)
8358
                cur.smellHereDesc();
8359
        }
8360
    }
8361
8362
    /*
8363
     *   Basic examination of an object for taste.  Unlike the
8364
     *   smell/listen examination routines, we don't bother using a
8365
     *   separate sensory emanation object for tasting, as tasting is
8366
     *   always an explicit action, never passive.  Furthermore, since
8367
     *   tasting requires direct physical contact with the object, we
8368
     *   don't differentiate levels of transparency or distance.  
8369
     */
8370
    basicExamineTaste()
8371
    {
8372
        /* simply show our taste description */
8373
        tasteDesc;
8374
    }
8375
8376
    /*
8377
     *   Basic examination of an object for touch.  As with the basic
8378
     *   taste examination, we don't use an emanation object or
8379
     *   distinguish transparency levels, because feeling an object
8380
     *   requires direct physical contact.  
8381
     */
8382
    basicExamineFeel()
8383
    {
8384
        /* simply show our touch description */
8385
        feelDesc;
8386
    }
8387
8388
    /*
8389
     *   Show the special descriptions of any contents.  We'll run through
8390
     *   the visible information list for the location; for any visible
8391
     *   item inside me that is using its special description, we'll
8392
     *   display the special description as a separate paragraph.  
8393
     */
8394
    examineSpecialContents()
8395
    {
8396
        local lst;
8397
        local infoTab;
8398
        
8399
        /* get the actor's table of visible items */
8400
        infoTab = gActor.visibleInfoTable();
8401
8402
        /* 
8403
         *   get the objects using special descriptions and contained
8404
         *   within this object 
8405
         */
8406
        lst = specialDescList(infoTab,
8407
                              {obj: obj.useSpecialDescInContents(self)});
8408
8409
        /* show the special descriptions */
8410
        (new SpecialDescContentsLister(self)).showList(
8411
            gActor, nil, lst, 0, 0, infoTab, nil);
8412
    }
8413
8414
    /*
8415
     *   Given a visible object info table (from Actor.visibleInfoTable()),
8416
     *   get the list of objects, filtered by the given condition and
8417
     *   sorted by specialDescOrder.  
8418
     */
8419
    specialDescList(infoTab, cond)
8420
    {
8421
        local lst;
8422
        
8423
        /* 
8424
         *   get a list of all of the objects in the table - the objects
8425
         *   are the keys, so we just want a list of the keys 
8426
         */
8427
        lst = infoTab.keysToList();
8428
8429
        /* subset the list for the given condition */
8430
        lst = lst.subset(cond);
8431
8432
        /* 
8433
         *   Sort the list in ascending order of specialDescOrder, and
8434
         *   return the result.  Where the specialDescOrder is the same,
8435
         *   and one object is inside another, put the outer object earlier
8436
         *   in the list; this will ensure that we'll generally list
8437
         *   objects before their contents, except when the special desc
8438
         *   list order specifically says otherwise.  
8439
         */
8440
        return lst.sort(SortAsc, new function(a, b) {
8441
            /* if the list orders are different, go by list order */
8442
            if (a.specialDescOrder != b.specialDescOrder)
8443
                return a.specialDescOrder - b.specialDescOrder;
8444
8445
            /* 
8446
             *   the list order is the same; if one is inside the other,
8447
             *   list the outer one first 
8448
             */
8449
            if (a.isIn(b))
8450
                return 1;
8451
            else if (b.isIn(a))
8452
                return -1;
8453
            else
8454
                return 0;
8455
        });
8456
    }
8457
    
8458
8459
    /* -------------------------------------------------------------------- */
8460
    /*
8461
     *   "Read" 
8462
     */
8463
    dobjFor(Read)
8464
    {
8465
        preCond = [objVisible]
8466
        verify()
8467
        {
8468
            /* 
8469
             *   reduce the likelihood that they want to read an ordinary
8470
             *   item, but allow it 
8471
             */
8472
            logicalRank(50, 'not readable');
8473
8474
            /* give slight preference to an object being held */
8475
            if (!isIn(gActor))
8476
                logicalRank(80, 'not held');
8477
        }
8478
        action()
8479
        {
8480
            /* simply show the ordinary description */
8481
            actionDobjExamine();
8482
        }
8483
    }
8484
8485
    /* -------------------------------------------------------------------- */
8486
    /*
8487
     *   "Look in" 
8488
     */
8489
    dobjFor(LookIn)
8490
    {
8491
        preCond = [objVisible]
8492
        verify()
8493
        {
8494
            /* give slight preference to an object being held */
8495
            if (!isIn(gActor))
8496
                logicalRank(80, 'not held');
8497
        }
8498
        action()
8499
        {
8500
            /* show our LOOK IN description */
8501
            lookInDesc;
8502
        }
8503
    }
8504
8505
    /* -------------------------------------------------------------------- */
8506
    /*
8507
     *   "Search".  By default, we make "search obj" do the same thing as
8508
     *   "look in obj".  
8509
     */
8510
    dobjFor(Search) asDobjFor(LookIn)
8511
8512
    /* -------------------------------------------------------------------- */
8513
    /*
8514
     *   "Look under" 
8515
     */
8516
    dobjFor(LookUnder)
8517
    {
8518
        preCond = [objVisible]
8519
        verify() { }
8520
        action() { mainReport(&nothingUnderMsg); }
8521
    }
8522
8523
    /* -------------------------------------------------------------------- */
8524
    /*
8525
     *   "Look behind" 
8526
     */
8527
    dobjFor(LookBehind)
8528
    {
8529
        preCond = [objVisible]
8530
        verify() { }
8531
        action() { mainReport(&nothingBehindMsg); }
8532
    }
8533
8534
    /* -------------------------------------------------------------------- */
8535
    /*
8536
     *   "Look through" 
8537
     */
8538
    dobjFor(LookThrough)
8539
    {
8540
        verify() { }
8541
        action() { mainReport(&nothingThroughMsg); }
8542
    }
8543
8544
    /* -------------------------------------------------------------------- */
8545
    /*
8546
     *   "Listen to"
8547
     */
8548
    dobjFor(ListenTo)
8549
    {
8550
        preCond = [objAudible]
8551
        verify() { }
8552
        action()
8553
        {
8554
            /* show our "listen" description explicitly */
8555
            fromPOV(gActor, gActor, &basicExamineListen, true);
8556
        }
8557
    }
8558
8559
    /* -------------------------------------------------------------------- */
8560
    /*
8561
     *   "Smell"
8562
     */
8563
    dobjFor(Smell)
8564
    {
8565
        preCond = [objSmellable]
8566
        verify() { }
8567
        action()
8568
        {
8569
            /* 
8570
             *   show our 'smell' description, explicitly showing our
8571
             *   default description if we don't have an Odor association 
8572
             */
8573
            fromPOV(gActor, gActor, &basicExamineSmell, true);
8574
        }
8575
    }
8576
8577
    /* -------------------------------------------------------------------- */
8578
    /*
8579
     *   "Taste"
8580
     */
8581
    dobjFor(Taste)
8582
    {
8583
        /* to taste an object, we have to be able to touch it */
8584
        preCond = [touchObj]
8585
        verify()
8586
        {
8587
            /* you *can* taste anything, but for most things it's unlikely */
8588
            logicalRank(50, 'not edible');
8589
        }
8590
        action()
8591
        {
8592
            /* show our "taste" description */
8593
            fromPOV(gActor, gActor, &basicExamineTaste);
8594
        }
8595
    }
8596
8597
    /* -------------------------------------------------------------------- */
8598
    /*
8599
     *   "Feel" 
8600
     */
8601
    dobjFor(Feel)
8602
    {
8603
        /* to feel an object, we have to be able to touch it */
8604
        preCond = [touchObj]
8605
        verify() { }
8606
        action()
8607
        {
8608
            /* show our "feel" description */
8609
            fromPOV(gActor, gActor, &basicExamineFeel);
8610
        }
8611
    }
8612
    
8613
8614
    /* -------------------------------------------------------------------- */
8615
    /*
8616
     *   "Take" action
8617
     */
8618
8619
    dobjFor(Take)
8620
    {
8621
        preCond = [touchObj, objNotWorn, roomToHoldObj]
8622
        verify()
8623
        {
8624
            /*      
8625
             *   if the object is already being held by the actor, it
8626
             *   makes no sense at the moment to take it 
8627
             */
8628
            if (isDirectlyIn(gActor))
8629
            {
8630
                /* I'm already holding it, so this is not logical */
8631
                illogicalAlready(&alreadyHoldingMsg);
8632
            }
8633
            else
8634
            {
8635
                local carrier;
8636
                
8637
                /*
8638
                 *   If the object isn't being held, it's logical to take
8639
                 *   it.  However, rank objects being carried as less
8640
                 *   likely than objects not being carried, because in an
8641
                 *   ambiguous situation, it's more likely that an actor
8642
                 *   would want to take something not being carried at all
8643
                 *   than something already being carried inside another
8644
                 *   object.  
8645
                 */
8646
                if (isIn(gActor))
8647
                    logicalRank(70, 'already in');
8648
8649
                /*
8650
                 *   If the object is being carried by another actor,
8651
                 *   reduce the likelihood, since taking something from
8652
                 *   another actor is usually less likely than taking
8653
                 *   something out of one's own possessions or from the
8654
                 *   location.  
8655
                 */
8656
                carrier = getCarryingActor();
8657
                if (carrier != nil && carrier != gActor)
8658
                    logicalRank(60, 'other owner');
8659
            }
8660
8661
            /* 
8662
             *   verify transfer from the current container hierarchy to
8663
             *   the new container hierarchy 
8664
             */
8665
            verifyMoveTo(gActor);
8666
        }
8667
    
8668
        action()
8669
        {
8670
            /* move me into the actor's direct contents */
8671
            moveInto(gActor);
8672
8673
            /* issue our default acknowledgment of the command */
8674
            defaultReport(&okayTakeMsg);
8675
        }
8676
    }
8677
8678
    /* -------------------------------------------------------------------- */
8679
    /*
8680
     *   "Remove" processing.  We'll treat "remove dobj" as meaning "take
8681
     *   dobj from <something>", where <something> is elided and must be
8682
     *   determined.
8683
     *   
8684
     *   This should be overridden in certain cases.  For clothing,
8685
     *   "remove dobj" should be the same as "doff dobj".  For removable
8686
     *   components, this should imply removing the component from its
8687
     *   container.  
8688
     */
8689
    dobjFor(Remove)
8690
    {
8691
        preCond = [touchObj, objNotWorn, roomToHoldObj]
8692
        verify()
8693
        {
8694
            /* if we're already held, there's nothing to remove me from */
8695
            if (isHeldBy(gActor))
8696
                illogicalNow(&cannotRemoveHeldMsg);
8697
        }
8698
        action() { askForIobj(TakeFrom); }
8699
    }
8700
8701
    /* -------------------------------------------------------------------- */
8702
    /*
8703
     *   "Take from" processing 
8704
     */
8705
    dobjFor(TakeFrom)
8706
    {
8707
        preCond = [touchObj, objNotWorn, roomToHoldObj]
8708
        verify()
8709
        {
8710
            /* 
8711
             *   we can only take something from something else if the thing
8712
             *   is inside the other thing 
8713
             */
8714
            if (gIobj != nil && !self.isIn(gIobj))
8715
                illogicalAlready(gIobj.takeFromNotInMessage);
8716
8717
            /* treat this otherwise like a regular "take" */
8718
            verifyDobjTake();
8719
        }
8720
        check()
8721
        {
8722
            /* apply the same checks as for a regular "take" */
8723
            checkDobjTake();
8724
        }
8725
        action()
8726
        {
8727
            /* we've passed our checks, so process it as a regular "take" */
8728
            replaceAction(Take, self);
8729
        }
8730
    }
8731
    iobjFor(TakeFrom)
8732
    {
8733
        verify()
8734
        {
8735
            /* check what we know about the dobj */
8736
            if (gDobj == nil)
8737
            {
8738
                /* 
8739
                 *   We haven't yet resolved the direct object; check the
8740
                 *   tentative direct object list, and count us as
8741
                 *   illogical if none of the possible direct objects are
8742
                 *   in me.  
8743
                 */
8744
                if (gTentativeDobj.indexWhich({x: x.obj_.isIn(self)}) == nil)
8745
                    illogicalAlready(takeFromNotInMessage);
8746
                else if (gTentativeDobj.indexWhich(
8747
                    {x: x.obj_.isDirectlyIn(self)}) != nil)
8748
                    logicalRank(150, 'directly in');
8749
            }
8750
            else if (!gDobj.isIn(self))
8751
            {
8752
                /* 
8753
                 *   the dobj isn't in me, so it's obviously not logical
8754
                 *   to take the dobj out of me 
8755
                 */
8756
                illogicalAlready(takeFromNotInMessage);
8757
            }
8758
            else if (gDobj.isDirectlyIn(self))
8759
            {
8760
                /* 
8761
                 *   it's slightly more likely that they want to remove
8762
                 *   the object from its direct container 
8763
                 */
8764
                logicalRank(150, 'directly in');
8765
            }
8766
        }
8767
    }
8768
8769
    /* general message for "take from" when an object isn't in me */
8770
    takeFromNotInMessage = &takeFromNotInMsg
8771
8772
    /* -------------------------------------------------------------------- */
8773
    /*
8774
     *   "Drop" verb processing 
8775
     */
8776
    dobjFor(Drop)
8777
    {
8778
        preCond = [objHeld]
8779
        verify()
8780
        {
8781
            /* the object must be held by the actor, at least indirectly */
8782
            if (isIn(gActor))
8783
            {
8784
                /* 
8785
                 *   it's being held, so dropping it makes sense; verify
8786
                 *   transfer from the current container hierarchy to the
8787
                 *   new one 
8788
                 */
8789
                verifyMoveTo(gActor.getDropDestination(self, nil));
8790
            }
8791
            else
8792
            {
8793
                /* it's not being held, so this is simply not logical */
8794
                illogicalAlready(&notCarryingMsg);
8795
            }
8796
        }
8797
8798
        action()
8799
        {
8800
            /* send the object to the actor's drop destination */
8801
            gActor.getDropDestination(self, nil)
8802
                .receiveDrop(self, dropTypeDrop);
8803
        }
8804
    }
8805
8806
    /* -------------------------------------------------------------------- */
8807
    /*
8808
     *   "Put In" verb processing.  Default objects cannot contain other
8809
     *   objects, but they can be put in arbitrary containers.  
8810
     */
8811
    dobjFor(PutIn)
8812
    {
8813
        preCond = [objHeld]
8814
        verify()
8815
        {
8816
            /* 
8817
             *   It makes no sense to put us in a container we're already
8818
             *   directly in.  (It's fine to put it in something it's
8819
             *   indirectly in, though - doing so takes it out of the
8820
             *   intermediate container and moves it directly into the
8821
             *   indirect object.) 
8822
             */
8823
            if (gIobj != nil && isDirectlyIn(gIobj))
8824
                illogicalAlready(&alreadyPutInMsg);
8825
8826
            /* can't put in self, obviously */
8827
            if (gIobj == self)
8828
                illogicalSelf(&cannotPutInSelfMsg);
8829
8830
            /* verify the transfer */
8831
            verifyMoveTo(gIobj);
8832
        }
8833
    }
8834
    iobjFor(PutIn)
8835
    {
8836
        preCond = [touchObj]
8837
        verify()
8838
        {
8839
            /* by default, objects cannot be put in this object */
8840
            illogical(&notAContainerMsg);
8841
        }
8842
    }
8843
8844
    /* -------------------------------------------------------------------- */
8845
    /*
8846
     *   "Put On" processing.  Default objects cannot have other objects
8847
     *   put on them, but they can be put on surfaces.
8848
     */
8849
    dobjFor(PutOn)
8850
    {
8851
        preCond = [objHeld]
8852
        verify()
8853
        {
8854
            /* it makes no sense to put us on a surface we're already on */
8855
            if (gIobj != nil && isDirectlyIn(gIobj))
8856
                illogicalAlready(&alreadyPutOnMsg);
8857
8858
            /* can't put on self, obviously */
8859
            if (gIobj == self)
8860
                illogicalSelf(&cannotPutOnSelfMsg);
8861
8862
            /* verify the transfer */
8863
            verifyMoveTo(gIobj);
8864
        }
8865
    }
8866
8867
    iobjFor(PutOn)
8868
    {
8869
        preCond = [touchObj]
8870
        verify()
8871
        {
8872
            /* by default, objects cannot be put on this object */
8873
            illogical(&notASurfaceMsg);
8874
        }
8875
    }
8876
8877
    /* -------------------------------------------------------------------- */
8878
    /*
8879
     *   "PutUnder" action 
8880
     */
8881
    dobjFor(PutUnder)
8882
    {
8883
        preCond = [objHeld]
8884
        verify() { }
8885
    }
8886
8887
    iobjFor(PutUnder)
8888
    {
8889
        preCond = [touchObj]
8890
        verify() { illogical(&cannotPutUnderMsg); }
8891
    }
8892
8893
    /* -------------------------------------------------------------------- */
8894
    /*
8895
     *   "PutBehind" action 
8896
     */
8897
    dobjFor(PutBehind)
8898
    {
8899
        preCond = [objHeld]
8900
        verify() { }
8901
    }
8902
8903
    iobjFor(PutBehind)
8904
    {
8905
        preCond = [touchObj]
8906
        verify() { illogical(&cannotPutBehindMsg); }
8907
    }
8908
8909
    /* -------------------------------------------------------------------- */
8910
    /*
8911
     *   "Wear" action 
8912
     */
8913
    dobjFor(Wear)
8914
    {
8915
        verify() { illogical(&notWearableMsg); }
8916
    }
8917
8918
    /* -------------------------------------------------------------------- */
8919
    /*
8920
     *   "Doff" action 
8921
     */
8922
    dobjFor(Doff)
8923
    {
8924
        verify() { illogical(&notDoffableMsg); }
8925
    }
8926
8927
    /* -------------------------------------------------------------------- */
8928
    /*
8929
     *   "Kiss" 
8930
     */
8931
    dobjFor(Kiss)
8932
    {
8933
        preCond = [touchObj]
8934
        verify() { logicalRank(50, 'not kissable'); }
8935
        action() { mainReport(&cannotKissMsg); }
8936
    }
8937
8938
    /* -------------------------------------------------------------------- */
8939
    /*
8940
     *   "Ask for" action 
8941
     */
8942
    dobjFor(AskFor)
8943
    {
8944
        verify() { illogical(&notAddressableMsg, self); }
8945
    }
8946
8947
    /* -------------------------------------------------------------------- */
8948
    /*
8949
     *   "Talk to" 
8950
     */
8951
    dobjFor(TalkTo)
8952
    {
8953
        verify() { illogical(&notAddressableMsg, self); }
8954
    }
8955
8956
    /* -------------------------------------------------------------------- */
8957
    /* 
8958
     *   "Give to" action 
8959
     */
8960
    dobjFor(GiveTo)
8961
    {
8962
        preCond = [objHeld]
8963
        verify()
8964
        {
8965
            /* 
8966
             *   if the intended recipient already has the object, there's
8967
             *   no point in trying this 
8968
             */
8969
            if (gIobj != nil && isHeldBy(gIobj))
8970
                illogicalAlready(&giveAlreadyHasMsg);
8971
8972
            /* inherit any further processing */
8973
            inherited();
8974
        }
8975
    }
8976
    iobjFor(GiveTo)
8977
    {
8978
        preCond = [touchObj]
8979
        verify() { illogical(&cannotGiveToMsg); }
8980
    }
8981
8982
    /* -------------------------------------------------------------------- */
8983
    /* 
8984
     *   "Show to" action 
8985
     */
8986
    dobjFor(ShowTo)
8987
    {
8988
        preCond = [objHeld]
8989
        verify()
8990
        {
8991
            /* it's more likely that we want to show something held */
8992
            if (isHeldBy(gActor))
8993
            {
8994
                /* I'm being held - use the default logical ranking */
8995
            }
8996
            else if (isIn(gActor))
8997
            {
8998
                /* 
8999
                 *   the actor isn't hold me, but I am in the actor's
9000
                 *   inventory, so reduce the likelihood only slightly 
9001
                 */
9002
                logicalRank(80, 'not held');
9003
            }
9004
            else
9005
            {
9006
                /* 
9007
                 *   the actor isn't even carrying me, so reduce our
9008
                 *   likelihood even more 
9009
                 */
9010
                logicalRank(70, 'not carried');
9011
            }
9012
        }
9013
        check()
9014
        {
9015
            /*
9016
             *   The direct object must be visible to the indirect object
9017
             *   in order for the indirect object to be shown the direct
9018
             *   object. 
9019
             */
9020
            if (!gIobj.canSee(self))
9021
            {
9022
                reportFailure(&actorCannotSeeMsg, gIobj, self);
9023
                exit;
9024
            }
9025
9026
            /*
9027
             *   The actor performing the showing must also be visible to
9028
             *   the indirect object, otherwise the actor wouldn't be able
9029
             *   to attract the indirect object's attention to do the
9030
             *   showing.  
9031
             */
9032
            if (!gIobj.canSee(gActor))
9033
            {
9034
                reportFailure(&actorCannotSeeMsg, gIobj, gActor);
9035
                exit;
9036
            }
9037
        }
9038
    }
9039
    iobjFor(ShowTo)
9040
    {
9041
        verify() { illogical(&cannotShowToMsg); }
9042
    }
9043
9044
    /* -------------------------------------------------------------------- */
9045
    /*
9046
     *   "Ask about" action 
9047
     */
9048
    dobjFor(AskAbout)
9049
    {
9050
        verify() { illogical(&notAddressableMsg, self); }
9051
    }
9052
9053
    /* -------------------------------------------------------------------- */
9054
    /*
9055
     *   "Tell about" action 
9056
     */
9057
    dobjFor(TellAbout)
9058
    {
9059
        verify() { illogical(&notAddressableMsg, self); }
9060
    }
9061
9062
    /* -------------------------------------------------------------------- */
9063
    /*
9064
     *   Vague "ask" and "tell" - these are for syntactically incorrect ASK
9065
     *   and TELL phrasings, so that we can provide better error feedback.
9066
     */
9067
    dobjFor(AskVague)
9068
    {
9069
        verify() { illogical(&askVagueMsg); }
9070
    }
9071
    dobjFor(TellVague)
9072
    {
9073
        verify() { illogical(&tellVagueMsg); }
9074
    }
9075
9076
    /* -------------------------------------------------------------------- */
9077
    /*
9078
     *   "Follow" action 
9079
     */
9080
    dobjFor(Follow)
9081
    {
9082
        verify()
9083
        {
9084
            /* make sure I'm followable to start with */
9085
            if (!verifyFollowable())
9086
                return;
9087
9088
            /* it makes no sense to follow myself */
9089
            if (gActor == gDobj)
9090
                illogical(&cannotFollowSelfMsg);
9091
9092
            /* ask the actor to verify following the object */
9093
            gActor.actorVerifyFollow(self);
9094
        }
9095
        action()
9096
        {
9097
            /* ask the actor to carry out the follow */
9098
            gActor.actorActionFollow(self);
9099
        }
9100
    }
9101
9102
    /*
9103
     *   Verify that I'm a followable object.  By default, it's not
9104
     *   logical to follow an arbitrary object.  If I'm not followable,
9105
     *   this routine will generate an appropriate illogical() explanation
9106
     *   and return nil.  If I'm followable, we'll return true.  
9107
     */
9108
    verifyFollowable()
9109
    {
9110
        illogical(&notFollowableMsg);
9111
        return nil;
9112
    }
9113
9114
    /* -------------------------------------------------------------------- */
9115
    /*
9116
     *   "Attack" action.
9117
     */
9118
    dobjFor(Attack)
9119
    {
9120
        preCond = [touchObj]
9121
        verify() { }
9122
        action() { mainReport(&uselessToAttackMsg); }
9123
    }
9124
9125
    /* -------------------------------------------------------------------- */
9126
    /*
9127
     *   "Attack with" action - attack with (presumably) a weapon.
9128
     */
9129
    dobjFor(AttackWith)
9130
    {
9131
        preCond = [touchObj]
9132
9133
        /* 
9134
         *   it makes as much sense to attack any object as any other, but
9135
         *   by default attacking an object has no effect 
9136
         */
9137
        verify() { }
9138
        action() { mainReport(&uselessToAttackMsg); }
9139
    }
9140
    iobjFor(AttackWith)
9141
    {
9142
        preCond = [objHeld]
9143
        verify() { illogical(&notAWeaponMsg); }
9144
    }
9145
9146
    /* -------------------------------------------------------------------- */
9147
    /*
9148
     *   "Throw" action.  By default, we'll simply re-route this to a
9149
     *   "throw at" action.  Objects that can meaningfully be thrown
9150
     *   without any specific target can override this.
9151
     *   
9152
     *   Note that we don't apply an preconditions or verification, since
9153
     *   we don't really do anything with the action ourselves.  If an
9154
     *   object overrides this, it should add any preconditions and
9155
     *   verifications that are appropriate.  
9156
     */
9157
    dobjFor(Throw)
9158
    {
9159
        verify() { }
9160
        action() { askForIobj(ThrowAt); }
9161
    }
9162
9163
    /* -------------------------------------------------------------------- */
9164
    /*
9165
     *   "Throw <direction>".  By default, we simply reject this and
9166
     *   explain that the command to use is THROW AT.  With one exception:
9167
     *   we treat THROW <down> as equivalent to THROW AT FLOOR, and use the
9168
     *   default library message for that command instead.  
9169
     */
9170
    dobjFor(ThrowDir)
9171
    {
9172
        verify()
9173
        {
9174
            if (gAction.getDirection() == downDirection)
9175
                illogicalAlready(&notCarryingMsg);
9176
        }
9177
        action()
9178
        {
9179
            /* 
9180
             *   explain that we should use THROW AT (or DROP, in the case
9181
             *   of THROW DOWN) 
9182
             */
9183
            reportFailure(gAction.getDirection() == downDirection
9184
                          ? &shouldNotThrowAtFloorMsg
9185
                          : &dontThrowDirMsg);
9186
        }
9187
    }
9188
9189
9190
    /* -------------------------------------------------------------------- */
9191
    /*
9192
     *   "Throw at" action 
9193
     */
9194
    dobjFor(ThrowAt)
9195
    {
9196
        preCond = [objHeld]
9197
        verify()
9198
        {
9199
            /* by default, we can throw something if we can drop it */
9200
            verifyMoveTo(gActor.getDropDestination(self, nil));
9201
9202
            /* can't throw something at itself */
9203
            if (gIobj == self)
9204
                illogicalSelf(&cannotThrowAtSelfMsg);
9205
9206
            /* can't throw dobj at iobj if iobj is in dobj */
9207
            if (gIobj != nil && gIobj.isIn(self))
9208
                illogicalNow(&cannotThrowAtContentsMsg);
9209
        }
9210
        action()
9211
        {
9212
            /* 
9213
             *   process a 'throw' operation, finishing with hitting the
9214
             *   target if we get that far 
9215
             */
9216
            processThrow(gIobj, &throwTargetHitWith);
9217
        }
9218
    }
9219
    iobjFor(ThrowAt)
9220
    {
9221
        /* by default, anything can be a target */
9222
        verify() { }
9223
    }
9224
9225
    /*
9226
     *   Process a 'throw' command.  This is common handling that can be
9227
     *   used for any sort of throwing (throw at, throw to, throw in,
9228
     *   etc).  The projectile is self, and 'target' is the thing we're
9229
     *   throwing at or to.  'hitProp' is the property to call on 'target'
9230
     *   if we reach the target.  
9231
     */
9232
    processThrow(target, hitProp)
9233
    {
9234
        local path;
9235
        local stat;
9236
        
9237
        /* get the throw path */
9238
        path = getThrowPathTo(target);
9239
        
9240
        /* traverse the path with throwViaPath */
9241
        stat = traversePath(
9242
            path, {obj, op: throwViaPath(obj, op, target, path)});
9243
        
9244
        /*
9245
         *   If we made it all the way through the path without complaint,
9246
         *   process hitting the target.  If something along the path
9247
         *   finished the traversal (by returning nil), we'll consider the
9248
         *   action complete - the object along that path that canceled
9249
         *   the traversal is responsible for having displayed an
9250
         *   appropriate message.  
9251
         */
9252
        if (stat)
9253
            target.(hitProp)(self, path);
9254
    }
9255
9256
    /*
9257
     *   Carry out a 'throw' operation along a path.  'self' is the
9258
     *   projectile; 'obj' is the path element being traversed, and 'op' is
9259
     *   the operation being used to traverse the element.  'target' is the
9260
     *   object we're throwing 'self' at.  'path' is the projectile's full
9261
     *   path (in getThrowPathTo format).
9262
     *   
9263
     *   By default, we'll use the standard canThrowViaPath handling (which
9264
     *   invokes the even more basic checkThrowViaPath) to determine if we
9265
     *   can make this traversal.  If so, we'll proceed with the throw;
9266
     *   otherwise, we'll stop the throw by calling stopThrowViaPath() and
9267
     *   returning the result.  
9268
     */
9269
    throwViaPath(obj, op, target, path)
9270
    {
9271
        /*
9272
         *   By default, if we can throw the object through self, return
9273
         *   true to allow the caller to proceed; otherwise, describe the
9274
         *   object as hitting this element and falling to the appropriate
9275
         *   point.  
9276
         */
9277
        if (obj.canThrowViaPath(self, target, op))
9278
        {
9279
            /* no objection - allow the traversal to proceed */
9280
            return true;
9281
        }
9282
        else
9283
        {
9284
            /* can't do it - stop the throw and return the result */
9285
            return obj.stopThrowViaPath(self, path);
9286
        }
9287
    }
9288
9289
    /*
9290
     *   Process the effect of throwing the object 'projectile' at the
9291
     *   target 'self'.  By default, we'll move the projectile to the
9292
     *   target's drop location, and display a message saying that there
9293
     *   was no effect other than the projectile dropping to the floor (or
9294
     *   whatever it drops to).  'path' is the path we took to reach the
9295
     *   target, as returned from getThrowPathTo(); this lets us determine
9296
     *   how we approached the target.  
9297
     */
9298
    throwTargetHitWith(projectile, path)
9299
    {
9300
        /* 
9301
         *   figure out where we fall to when we hit this object, then send
9302
         *   the object being thrown to that location 
9303
         */
9304
        getHitFallDestination(projectile, path)
9305
            .receiveDrop(projectile, new DropTypeThrow(self, path));
9306
    }
9307
9308
    /*
9309
     *   Stop a 'throw' operation along a path.  'self' is the object in
9310
     *   the path that is impassable by 'projectile' according to
9311
     *   canThrowViaPath(), and 'path' is the getThrowPathTo-style path of
9312
     *   objects traversed in the projectile's trajectory.
9313
     *   
9314
     *   The return value is taken as a path traversal continuation
9315
     *   indicator: nil means to stop the traversal, which is to say that
9316
     *   the 'throw' command finishes here.  If we don't really want to
9317
     *   stop the traversal, we can return 'true' to let the traversal
9318
     *   continue.
9319
     *   
9320
     *   By default, we'll stop the throw by doing the same thing we would
9321
     *   have done if we had successfully thrown the object at 'self' - the
9322
     *   whole reason we're stopping the throw is that we're in the way, so
9323
     *   the effect is the same as though we were the intended target to
9324
     *   begin with.  This is the normal handling when we can't throw
9325
     *   through 'obj' because 'obj' is a closed container or is otherwise
9326
     *   impassable by self when thrown.  This can be overridden to provide
9327
     *   different handling if needed.  
9328
     */
9329
    stopThrowViaPath(projectile, path)
9330
    {
9331
        /* we've been hit */
9332
        throwTargetHitWith(projectile, path);
9333
        
9334
        /* tell the caller to stop the traversal  */
9335
        return nil;
9336
    }
9337
    
9338
    /*
9339
     *   Get the "hit-and-fall" destination for a thrown object.  This is
9340
     *   called when we interrupt a thrown object's trajectory because
9341
     *   we're in the way of its trajectory.
9342
     *   
9343
     *   For example, if the actor is inside a cage, and tries to throw a
9344
     *   projectile at an object outside the cage, and the cage blocks the
9345
     *   projectile's passage, then this routine is called on the cage to
9346
     *   determine where the projectile ends up.  The projectile's ultimate
9347
     *   destination is the hit-and-fall destination for the cage: it's
9348
     *   where the project ends up when it hits me and then falls to the
9349
     *   ground, its trajectory cut short.
9350
     *   
9351
     *   'thrownObj' is the projectile thrown, 'self' is the target object,
9352
     *   and 'path' is the path the projectile took to reach us.  The path
9353
     *   is of the form returned by getThrowPathTo().  Note that the path
9354
     *   could extend beyond 'self', because the original target might have
9355
     *   been a different object - we could simply have interrupted the
9356
     *   projectile's course.  
9357
     */
9358
    getHitFallDestination(thrownObj, path)
9359
    {
9360
        local prvCont;
9361
        local prvOp;
9362
        local idx;
9363
        local dest;
9364
        local common;
9365
9366
        /* find myself in the path */
9367
        idx = path.indexOf(self);
9368
9369
        /* 
9370
         *   get the container traversed just before us in the path (it's
9371
         *   two positions before us in the path list, because the path
9372
         *   consists of alternating objects and operators) 
9373
         */
9374
        prvCont = path[idx - 2];
9375
        
9376
        /* get the operation that got from the last container to us */
9377
        prvOp = path[idx - 1];
9378
9379
        /* 
9380
         *   If the previous container is within us, we're throwing from
9381
         *   inside to the outside, so the object falls within me;
9382
         *   otherwise, the object bounces off the outside and falls
9383
         *   outside me.
9384
         *   
9385
         *   If the projectile traversed *inward* from the previous item in
9386
         *   the path, then we should simply land in the drop destination
9387
         *   of the previous item itself, since we're coming in through
9388
         *   that item.  If the previous item is a peer of ours, though, we
9389
         *   traversed *across* the item, so land in our common direct
9390
         *   container.
9391
         *   
9392
         *   Note that in certain cases, the previous "container" will be
9393
         *   the projectile itself; this happens when the thrower is
9394
         *   throwing the object at itself (as in "throw it at me").  In
9395
         *   these cases, we'll assume that the the thrower is *holding*
9396
         *   rather than containing the object, in which case the object
9397
         *   isn't actually inside the thrower, in which case we want to
9398
         *   use the location's drop destination.  
9399
         */
9400
        if (prvCont == thrownObj)
9401
        {
9402
            /* throwing object at self - land in my location's drop dest */
9403
            dest = (location != nil
9404
                    ? location.getDropDestination(thrownObj, path)
9405
                    : self);
9406
        }
9407
        else if (prvCont.isIn(self))
9408
        {
9409
            /* throwing from within - land in my own drop destination */
9410
            dest = getDropDestination(thrownObj, path);
9411
        }
9412
        else if (prvOp == PathPeer
9413
                 && (common = getCommonDirectContainer(prvCont)) != nil)
9414
        {
9415
            /* 
9416
             *   We're coming over from a peer, and we found a container in
9417
             *   common with the peer.  Land in the common container's drop
9418
             *   destination.  
9419
             */
9420
            dest = common.getDropDestination(thrownObj, path);
9421
        }
9422
        else
9423
        {
9424
            /*
9425
             *   Either we're coming in from a container, or we weren't
9426
             *   able to find a container in common with the peer.  In
9427
             *   either case, the projectile lands in the 'drop'
9428
             *   destination of the previous object in the path.  
9429
             */
9430
            dest = prvCont.getDropDestination(thrownObj, path);
9431
        }
9432
9433
        /*
9434
         *   Whatever we found, give the destination itself a chance to
9435
         *   make any necessary adjustments.  
9436
         */
9437
        return dest.adjustThrowDestination(thrownObj, path);
9438
    }
9439
9440
    /* -------------------------------------------------------------------- */
9441
    /*
9442
     *   "Throw to" action 
9443
     */
9444
    dobjFor(ThrowTo)
9445
    {
9446
        preCond = [objHeld]
9447
        verify()
9448
        {
9449
            /* 
9450
             *   by default, we can throw an object to someone if we can
9451
             *   throw it at them 
9452
             */
9453
            verifyDobjThrowAt();
9454
        }
9455
        action()
9456
        {
9457
            /* 
9458
             *   process a 'throw' operation, finishing with the target
9459
             *   trying to catch the object if we get that far
9460
             */
9461
            processThrow(gIobj, &throwTargetCatch);
9462
        }
9463
    }
9464
9465
    iobjFor(ThrowTo)
9466
    {
9467
        verify()
9468
        {
9469
            /* by default, we don't want to catch anything */
9470
            illogical(&cannotThrowToMsg);
9471
        }
9472
    }
9473
9474
    /*
9475
     *   Process the effect of throwing the object 'obj' to the catcher
9476
     *   'self'.  By default, we'll simply move the projectile into self.
9477
     */
9478
    throwTargetCatch(obj, path)
9479
    {
9480
        /* take the object */
9481
        obj.moveInto(self);
9482
9483
        /* generate the default message if we successfully took the object */
9484
        if (obj.isDirectlyIn(self))
9485
            mainReport(&throwCatchMsg, obj, self);
9486
    }
9487
9488
    /* -------------------------------------------------------------------- */
9489
    /*
9490
     *   "Dig" action - by default, simply re-reoute to dig-with, since we
9491
     *   generally need a digging implement to dig in anything.  Some
9492
     *   objects might want to override this to allow digging without any
9493
     *   implement; a sandy beach, for example, might allow digging in the
9494
     *   sand without a shovel.  
9495
     */
9496
    dobjFor(Dig)
9497
    {
9498
        preCond = [touchObj]
9499
        verify() { }
9500
        action() { askForIobj(DigWith); }
9501
    }
9502
9503
    /* -------------------------------------------------------------------- */
9504
    /*
9505
     *   "DigWith" action 
9506
     */
9507
    dobjFor(DigWith)
9508
    {
9509
        preCond = [touchObj]
9510
        verify() { illogical(&cannotDigMsg); }
9511
    }
9512
    iobjFor(DigWith)
9513
    {
9514
        preCond = [objHeld]
9515
        verify() { illogical(&cannotDigWithMsg); }
9516
    }
9517
9518
    /* -------------------------------------------------------------------- */
9519
    /*
9520
     *   "jump over" 
9521
     */
9522
    dobjFor(JumpOver)
9523
    {
9524
        verify() { illogical(&cannotJumpOverMsg); }
9525
    }
9526
9527
    /* -------------------------------------------------------------------- */
9528
    /*
9529
     *   "jump off" 
9530
     */
9531
    dobjFor(JumpOff)
9532
    {
9533
        verify() { illogical(&cannotJumpOffMsg); }
9534
    }
9535
9536
    /* -------------------------------------------------------------------- */
9537
    /*
9538
     *   "Push" action 
9539
     */
9540
    dobjFor(Push)
9541
    {
9542
        preCond = [touchObj]
9543
        verify() { logicalRank(50, 'not pushable'); }
9544
        action() { reportFailure(&pushNoEffectMsg); }
9545
    }
9546
9547
    /* -------------------------------------------------------------------- */
9548
    /*
9549
     *   "Pull" action 
9550
     */
9551
    dobjFor(Pull)
9552
    {
9553
        preCond = [touchObj]
9554
        verify() { logicalRank(50, 'not pullable'); }
9555
        action() { reportFailure(&pullNoEffectMsg); }
9556
    }
9557
9558
    /* -------------------------------------------------------------------- */
9559
    /*
9560
     *   "Move" action 
9561
     */
9562
    dobjFor(Move)
9563
    {
9564
        preCond = [touchObj]
9565
        verify() { logicalRank(50, 'not movable'); }
9566
        action() { reportFailure(&moveNoEffectMsg); }
9567
    }
9568
9569
    /* -------------------------------------------------------------------- */
9570
    /*
9571
     *   "MoveWith" action 
9572
     */
9573
    dobjFor(MoveWith)
9574
    {
9575
        preCond = [iobjTouchObj]
9576
        verify() { logicalRank(50, 'not movable'); }
9577
        action() { reportFailure(&moveNoEffectMsg); }
9578
    }
9579
    iobjFor(MoveWith)
9580
    {
9581
        preCond = [objHeld]
9582
        verify() { illogical(&cannotMoveWithMsg); }
9583
    }
9584
9585
    /* -------------------------------------------------------------------- */
9586
    /*
9587
     *   "MoveTo" action 
9588
     */
9589
    dobjFor(MoveTo)
9590
    {
9591
        preCond = [touchObj]
9592
        verify() { logicalRank(50, 'not movable'); }
9593
        action() { reportFailure(&moveToNoEffectMsg); }
9594
    }
9595
9596
    /* -------------------------------------------------------------------- */
9597
    /*
9598
     *   "Turn" action 
9599
     */
9600
    dobjFor(Turn)
9601
    {
9602
        preCond = [touchObj]
9603
        verify() { illogical(&cannotTurnMsg); }
9604
    }
9605
9606
    /* -------------------------------------------------------------------- */
9607
    /*
9608
     *   "Turn to" action 
9609
     */
9610
    dobjFor(TurnTo)
9611
    {
9612
        preCond = [touchObj]
9613
        verify() { illogical(&cannotTurnMsg); }
9614
    }
9615
9616
    /* -------------------------------------------------------------------- */
9617
    /*
9618
     *   "TurnWith" action 
9619
     */
9620
    dobjFor(TurnWith)
9621
    {
9622
        preCond = [iobjTouchObj]
9623
        verify() { illogical(&cannotTurnMsg); }
9624
    }
9625
    iobjFor(TurnWith)
9626
    {
9627
        preCond = [objHeld]
9628
        verify() { illogical(&cannotTurnWithMsg); }
9629
    }
9630
9631
    /* -------------------------------------------------------------------- */
9632
    /*
9633
     *   "Set" action
9634
     */
9635
    dobjFor(Set)
9636
    {
9637
        verify() { }
9638
        action() { askForIobj(PutOn); }
9639
    }
9640
9641
    /* -------------------------------------------------------------------- */
9642
    /*
9643
     *   "SetTo" action 
9644
     */
9645
    dobjFor(SetTo)
9646
    {
9647
        preCond = [touchObj]
9648
        verify() { illogical(&cannotSetToMsg); }
9649
    }
9650
9651
    /* -------------------------------------------------------------------- */
9652
    /*
9653
     *   "Consult" action 
9654
     */
9655
    dobjFor(Consult)
9656
    {
9657
        preCond = [touchObj]
9658
        verify() { illogical(&cannotConsultMsg); }
9659
    }
9660
9661
    dobjFor(ConsultAbout)
9662
    {
9663
        preCond = [touchObj]
9664
        verify() { illogical(&cannotConsultMsg); }
9665
    }
9666
9667
    /* -------------------------------------------------------------------- */
9668
    /*
9669
     *   "Type on" action 
9670
     */
9671
    dobjFor(TypeOn)
9672
    {
9673
        preCond = [touchObj]
9674
        verify() { illogical(&cannotTypeOnMsg); }
9675
9676
        /* 
9677
         *   if the verifier is overridden to allow typing on this object,
9678
         *   by default just ask for a missing literal phrase, since we
9679
         *   need something to type on this object 
9680
         */
9681
        action() { askForLiteral(TypeLiteralOn); }
9682
    }
9683
9684
    /*
9685
     *   "Type <literal> on" action 
9686
     */
9687
    dobjFor(TypeLiteralOn)
9688
    {
9689
        preCond = [touchObj]
9690
        verify() { illogical(&cannotTypeOnMsg); }
9691
    }
9692
9693
    /* -------------------------------------------------------------------- */
9694
    /*
9695
     *   "Enter on" action 
9696
     */
9697
    dobjFor(EnterOn)
9698
    {
9699
        preCond = [touchObj]
9700
        verify() { illogical(&cannotEnterOnMsg); }
9701
    }
9702
9703
    /* -------------------------------------------------------------------- */
9704
    /*
9705
     *   "Switch" action 
9706
     */
9707
    dobjFor(Switch)
9708
    {
9709
        preCond = [touchObj]
9710
        verify() { illogical(&cannotSwitchMsg); }
9711
    }
9712
9713
    /* -------------------------------------------------------------------- */
9714
    /*
9715
     *   "Flip" action 
9716
     */
9717
    dobjFor(Flip)
9718
    {
9719
        preCond = [touchObj]
9720
        verify() { illogical(&cannotFlipMsg); }
9721
    }
9722
9723
    /* -------------------------------------------------------------------- */
9724
    /*
9725
     *   "TurnOn" action 
9726
     */
9727
    dobjFor(TurnOn)
9728
    {
9729
        preCond = [touchObj]
9730
        verify() { illogical(&cannotTurnOnMsg); }
9731
    }
9732
9733
    /* -------------------------------------------------------------------- */
9734
    /*
9735
     *   "TurnOff" action 
9736
     */
9737
    dobjFor(TurnOff)
9738
    {
9739
        preCond = [touchObj]
9740
        verify() { illogical(&cannotTurnOffMsg); }
9741
    }
9742
9743
    /* -------------------------------------------------------------------- */
9744
    /*
9745
     *   "Light" action.  By default, we treat this as equivalent to
9746
     *   "burn".  
9747
     */
9748
    dobjFor(Light) asDobjFor(Burn)
9749
9750
    /* -------------------------------------------------------------------- */
9751
    /*
9752
     *   "Burn".  By default, we ask for something to use to burn the
9753
     *   object, since most objects are not self-igniting.  
9754
     */
9755
    dobjFor(Burn)
9756
    {
9757
        preCond = [touchObj]
9758
        verify()
9759
        {
9760
            /* 
9761
             *   although we can in principle burn anything, most things
9762
             *   are unlikely choices for burning
9763
             */
9764
            logicalRank(50, 'not flammable');
9765
        }
9766
        action()
9767
        {
9768
            /* rephrase this as a "burn with" command */
9769
            askForIobj(BurnWith);
9770
        }
9771
    }
9772
9773
    /* -------------------------------------------------------------------- */
9774
    /*
9775
     *   "Burn with" 
9776
     */
9777
    dobjFor(BurnWith)
9778
    {
9779
        preCond = [touchObj]
9780
        verify() { illogical(&cannotBurnMsg); }
9781
    }
9782
    iobjFor(BurnWith)
9783
    {
9784
        preCond = [objHeld]
9785
        verify() { illogical(&cannotBurnWithMsg); }
9786
    }
9787
9788
    /* -------------------------------------------------------------------- */
9789
    /*
9790
     *   "Extinguish" 
9791
     */
9792
    dobjFor(Extinguish)
9793
    {
9794
        preCond = [touchObj]
9795
        verify() { illogical(&cannotExtinguishMsg); }
9796
    }
9797
9798
    /* -------------------------------------------------------------------- */
9799
    /*
9800
     *   "AttachTo" action 
9801
     */
9802
    dobjFor(AttachTo)
9803
    {
9804
        preCond = [touchObj]
9805
        verify() { illogical(&cannotAttachMsg); }
9806
    }
9807
    iobjFor(AttachTo)
9808
    {
9809
        preCond = [touchObj]
9810
        verify() { illogical(&cannotAttachToMsg); }
9811
    }
9812
9813
    /* -------------------------------------------------------------------- */
9814
    /*
9815
     *   "DetachFrom" action 
9816
     */
9817
    dobjFor(DetachFrom)
9818
    {
9819
        preCond = [touchObj]
9820
        verify() { illogical(&cannotDetachMsg); }
9821
    }
9822
    iobjFor(DetachFrom)
9823
    {
9824
        preCond = [touchObj]
9825
        verify() { illogical(&cannotDetachFromMsg); }
9826
    }
9827
9828
    /* -------------------------------------------------------------------- */
9829
    /*
9830
     *   "Detach" action 
9831
     */
9832
    dobjFor(Detach)
9833
    {
9834
        preCond = [touchObj]
9835
        verify() { illogical(&cannotDetachMsg); }
9836
    }
9837
9838
    /* -------------------------------------------------------------------- */
9839
    /*
9840
     *   "Break" action 
9841
     */
9842
    dobjFor(Break)
9843
    {
9844
        preCond = [touchObj]
9845
        verify() { illogical(&shouldNotBreakMsg); }
9846
    }
9847
9848
    /* -------------------------------------------------------------------- */
9849
    /*
9850
     *   "Cut with" action 
9851
     */
9852
    dobjFor(CutWith)
9853
    {
9854
        preCond = [touchObj]
9855
        verify() { logicalRank(50, 'not cuttable'); }
9856
        action() { reportFailure(&cutNoEffectMsg); }
9857
    }
9858
9859
    iobjFor(CutWith)
9860
    {
9861
        preCond = [touchObj]
9862
        verify() { illogical(&cannotCutWithMsg); }
9863
    }
9864
9865
    /* -------------------------------------------------------------------- */
9866
    /*
9867
     *   "Climb", "climb up", and "climb down" actions
9868
     */
9869
    dobjFor(Climb)
9870
    {
9871
        preCond = [touchObj]
9872
        verify() { illogical(&cannotClimbMsg); }
9873
    }
9874
9875
    dobjFor(ClimbUp)
9876
    {
9877
        preCond = [touchObj]
9878
        verify() { illogical(&cannotClimbMsg); }
9879
    }
9880
9881
    dobjFor(ClimbDown)
9882
    {
9883
        preCond = [touchObj]
9884
        verify() { illogical(&cannotClimbMsg); }
9885
    }
9886
9887
    /* -------------------------------------------------------------------- */
9888
    /*
9889
     *   "Open" action 
9890
     */
9891
    dobjFor(Open)
9892
    {
9893
        preCond = [touchObj]
9894
        verify() { illogical(&cannotOpenMsg); }
9895
    }
9896
9897
    /* -------------------------------------------------------------------- */
9898
    /*
9899
     *   "Close" action 
9900
     */
9901
    dobjFor(Close)
9902
    {
9903
        preCond = [touchObj]
9904
        verify() { illogical(&cannotCloseMsg); }
9905
    }
9906
9907
    /* -------------------------------------------------------------------- */
9908
    /*
9909
     *   "Lock" action 
9910
     */
9911
    dobjFor(Lock)
9912
    {
9913
        preCond = [touchObj]
9914
        verify() { illogical(&cannotLockMsg); }
9915
    }
9916
9917
    /* -------------------------------------------------------------------- */
9918
    /*
9919
     *   "Unlock" action 
9920
     */
9921
    dobjFor(Unlock)
9922
    {
9923
        preCond = [touchObj]
9924
        verify() { illogical(&cannotUnlockMsg); }
9925
    }
9926
9927
    /* -------------------------------------------------------------------- */
9928
    /*
9929
     *   "LockWith" action 
9930
     */
9931
    dobjFor(LockWith)
9932
    {
9933
        preCond = [touchObj]
9934
        verify() { illogical(&cannotLockMsg); }
9935
    }
9936
    iobjFor(LockWith)
9937
    {
9938
        preCond = [objHeld]
9939
        verify() { illogical(&cannotLockWithMsg); }
9940
    }
9941
9942
    /* -------------------------------------------------------------------- */
9943
    /*
9944
     *   "UnlockWith" action 
9945
     */
9946
    dobjFor(UnlockWith)
9947
    {
9948
        preCond = [touchObj]
9949
        verify() { illogical(&cannotUnlockMsg); }
9950
    }
9951
    iobjFor(UnlockWith)
9952
    {
9953
        preCond = [objHeld]
9954
        verify() { illogical(&cannotUnlockWithMsg); }
9955
    }
9956
9957
    /* -------------------------------------------------------------------- */
9958
    /*
9959
     *   "Eat" action 
9960
     */
9961
    dobjFor(Eat)
9962
    {
9963
        /* 
9964
         *   generally, an object must be held to be eaten; this can be
9965
         *   overridden on an object-by-object basis as desired 
9966
         */
9967
        preCond = [objHeld]
9968
        verify() { illogical(&cannotEatMsg); }
9969
    }
9970
9971
    /* -------------------------------------------------------------------- */
9972
    /*
9973
     *   "Drink" action 
9974
     */
9975
    dobjFor(Drink)
9976
    {
9977
        preCond = [objHeld]
9978
        verify() { illogical(&cannotDrinkMsg); }
9979
    }
9980
9981
    /* -------------------------------------------------------------------- */
9982
    /*
9983
     *   "Pour" 
9984
     */
9985
    dobjFor(Pour)
9986
    {
9987
        preCond = [touchObj]
9988
        verify() { illogical(&cannotPourMsg); }
9989
    }
9990
9991
    /* -------------------------------------------------------------------- */
9992
    /*
9993
     *   "Pour into" 
9994
     */
9995
    dobjFor(PourInto)
9996
    {
9997
        preCond = [touchObj]
9998
        verify() { illogical(&cannotPourMsg); }
9999
    }
10000
    iobjFor(PourInto)
10001
    {
10002
        preCond = [touchObj]
10003
        verify() { illogical(&cannotPourIntoMsg); }
10004
    }
10005
10006
    /* -------------------------------------------------------------------- */
10007
    /*
10008
     *   "Pour onto" 
10009
     */
10010
    dobjFor(PourOnto)
10011
    {
10012
        preCond = [touchObj]
10013
        verify() { illogical(&cannotPourMsg); }
10014
    }
10015
    iobjFor(PourOnto)
10016
    {
10017
        preCond = [touchObj]
10018
        verify() { illogical(&cannotPourOntoMsg); }
10019
    }
10020
10021
    /* -------------------------------------------------------------------- */
10022
    /*
10023
     *   "Clean" action 
10024
     */
10025
    dobjFor(Clean)
10026
    {
10027
        preCond = [touchObj]
10028
        verify() { illogical(&cannotCleanMsg); }
10029
    }
10030
10031
    /* -------------------------------------------------------------------- */
10032
    /*
10033
     *   "CleanWith" action 
10034
     */
10035
    dobjFor(CleanWith)
10036
    {
10037
        preCond = [touchObj]
10038
        verify() { illogical(&cannotCleanMsg); }
10039
    }
10040
    iobjFor(CleanWith)
10041
    {
10042
        preCond = [objHeld]
10043
        verify() { illogical(&cannotCleanWithMsg); }
10044
    }
10045
10046
    /* -------------------------------------------------------------------- */
10047
    /*
10048
     *   "SitOn" action 
10049
     */
10050
    dobjFor(SitOn)
10051
    {
10052
        preCond = [touchObj]
10053
        verify() { illogical(&cannotSitOnMsg); }
10054
    }
10055
10056
    /* -------------------------------------------------------------------- */
10057
    /*
10058
     *   "LieOn" action 
10059
     */
10060
    dobjFor(LieOn)
10061
    {
10062
        preCond = [touchObj]
10063
        verify() { illogical(&cannotLieOnMsg); }
10064
    }
10065
10066
    /* -------------------------------------------------------------------- */
10067
    /*
10068
     *   "StandOn" action 
10069
     */
10070
    dobjFor(StandOn)
10071
    {
10072
        preCond = [touchObj]
10073
        verify() { illogical(&cannotStandOnMsg); }
10074
    }
10075
10076
    /* -------------------------------------------------------------------- */
10077
    /*
10078
     *   "Board" action 
10079
     */
10080
    dobjFor(Board)
10081
    {
10082
        preCond = [touchObj]
10083
        verify() { illogical(&cannotBoardMsg); }
10084
    }
10085
10086
    /* -------------------------------------------------------------------- */
10087
    /*
10088
     *   "Get out of" (unboard) action 
10089
     */
10090
    dobjFor(GetOutOf)
10091
    {
10092
        verify() { illogical(&cannotUnboardMsg); }
10093
    }
10094
10095
    /*
10096
     *   "Get off of" action 
10097
     */
10098
    dobjFor(GetOffOf)
10099
    {
10100
        verify() { illogical(&cannotGetOffOfMsg); }
10101
    }
10102
10103
    /* -------------------------------------------------------------------- */
10104
    /*
10105
     *   "Fasten" action 
10106
     */
10107
    dobjFor(Fasten)
10108
    {
10109
        preCond = [touchObj]
10110
        verify() { illogical(&cannotFastenMsg); }
10111
    }
10112
10113
    /* -------------------------------------------------------------------- */
10114
    /*
10115
     *   "Fasten to" action 
10116
     */
10117
    dobjFor(FastenTo)
10118
    {
10119
        preCond = [touchObj]
10120
        verify() { illogical(&cannotFastenMsg); }
10121
    }
10122
    iobjFor(FastenTo)
10123
    {
10124
        preCond = [touchObj]
10125
        verify() { illogical(&cannotFastenToMsg); }
10126
    }
10127
10128
    /* -------------------------------------------------------------------- */
10129
    /*
10130
     *   "Unfasten" action 
10131
     */
10132
    dobjFor(Unfasten)
10133
    {
10134
        preCond = [touchObj]
10135
        verify() { illogical(&cannotUnfastenMsg); }
10136
    }
10137
10138
    /* -------------------------------------------------------------------- */
10139
    /*
10140
     *   "Unfasten from" action 
10141
     */
10142
    dobjFor(UnfastenFrom)
10143
    {
10144
        preCond = [touchObj]
10145
        verify() { illogical(&cannotUnfastenMsg); }
10146
    }
10147
    iobjFor(UnfastenFrom)
10148
    {
10149
        preCond = [touchObj]
10150
        verify() { illogical(&cannotUnfastenFromMsg); }
10151
    }
10152
10153
    /* -------------------------------------------------------------------- */
10154
    /*
10155
     *   "PlugIn" action 
10156
     */
10157
    dobjFor(PlugIn)
10158
    {
10159
        preCond = [touchObj]
10160
        verify() { illogical(&cannotPlugInMsg); }
10161
    }
10162
10163
    /* -------------------------------------------------------------------- */
10164
    /*
10165
     *   "PlugInto" action 
10166
     */
10167
    dobjFor(PlugInto)
10168
    {
10169
        preCond = [touchObj]
10170
        verify() { illogical(&cannotPlugInMsg); }
10171
    }
10172
    iobjFor(PlugInto)
10173
    {
10174
        preCond = [touchObj]
10175
        verify() { illogical(&cannotPlugInToMsg); }
10176
    }
10177
10178
    /* -------------------------------------------------------------------- */
10179
    /*
10180
     *   "Unplug" action 
10181
     */
10182
    dobjFor(Unplug)
10183
    {
10184
        preCond = [touchObj]
10185
        verify() { illogical(&cannotUnplugMsg); }
10186
    }
10187
10188
    /* -------------------------------------------------------------------- */
10189
    /*
10190
     *   "UnplugFrom" action 
10191
     */
10192
    dobjFor(UnplugFrom)
10193
    {
10194
        preCond = [touchObj]
10195
        verify() { illogical(&cannotUnplugMsg); }
10196
    }
10197
    iobjFor(UnplugFrom)
10198
    {
10199
        preCond = [touchObj]
10200
        verify() { illogical(&cannotUnplugFromMsg); }
10201
    }
10202
10203
    /* -------------------------------------------------------------------- */
10204
    /*
10205
     *   "Screw" action 
10206
     */
10207
    dobjFor(Screw)
10208
    {
10209
        preCond = [touchObj]
10210
        verify() { illogical(&cannotScrewMsg); }
10211
    }
10212
10213
    /* -------------------------------------------------------------------- */
10214
    /*
10215
     *   "ScrewWith" action 
10216
     */
10217
    dobjFor(ScrewWith)
10218
    {
10219
        preCond = [iobjTouchObj]
10220
        verify() { illogical(&cannotScrewMsg); }
10221
    }
10222
    iobjFor(ScrewWith)
10223
    {
10224
        preCond = [objHeld]
10225
        verify() { illogical(&cannotScrewWithMsg); }
10226
    }
10227
10228
    /* -------------------------------------------------------------------- */
10229
    /*
10230
     *   "Unscrew" action 
10231
     */
10232
    dobjFor(Unscrew)
10233
    {
10234
        preCond = [touchObj]
10235
        verify() { illogical(&cannotUnscrewMsg); }
10236
    }
10237
10238
    /* -------------------------------------------------------------------- */
10239
    /*
10240
     *   "UnscrewWith" action 
10241
     */
10242
    dobjFor(UnscrewWith)
10243
    {
10244
        preCond = [iobjTouchObj]
10245
        verify() { illogical(&cannotUnscrewMsg); }
10246
    }
10247
    iobjFor(UnscrewWith)
10248
    {
10249
        preCond = [objHeld]
10250
        verify() { illogical(&cannotUnscrewWithMsg); }
10251
    }
10252
10253
    /* -------------------------------------------------------------------- */
10254
    /*
10255
     *   "Enter" 
10256
     */
10257
    dobjFor(Enter)
10258
    {
10259
        preCond = [touchObj]
10260
        verify() { illogical(&cannotEnterMsg); }
10261
    }
10262
10263
    /* 
10264
     *   "Go through" 
10265
     */
10266
    dobjFor(GoThrough)
10267
    {
10268
        preCond = [touchObj]
10269
        verify() { illogical(&cannotGoThroughMsg); }
10270
    }
10271
10272
    /* -------------------------------------------------------------------- */
10273
    /*
10274
     *   Push in Direction action - this is for commands like "push
10275
     *   boulder north" or "drag sled into cave". 
10276
     */
10277
    dobjFor(PushTravel)
10278
    {
10279
        preCond = [touchObj]
10280
        verify() { illogical(&cannotPushTravelMsg); }
10281
    }
10282
10283
    /*   
10284
     *   For all of the two-object forms, map these using our general
10285
     *   push-travel mapping.  We do all of this mapping here, rather than
10286
     *   in the action definition, so that individual objects can change
10287
     *   the meanings of these verbs for special cases as appropriate.  
10288
     */
10289
    mapPushTravelHandlers(PushTravelThrough, GoThrough)
10290
    mapPushTravelHandlers(PushTravelEnter, Enter)
10291
    mapPushTravelHandlers(PushTravelGetOutOf, GetOutOf)
10292
    mapPushTravelHandlers(PushTravelClimbUp, ClimbUp)
10293
    mapPushTravelHandlers(PushTravelClimbDown, ClimbDown)
10294
;
10295