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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *.  Portions based on work by Kevin Forchione, used by permission.  
6
 *   
7
 *   TADS 3 Library - objects
8
 *   
9
 *   This module defines the basic physical simulation objects (apart from
10
 *   Thing, the base class for most game objects, which is so large that
11
 *   it's defined in its own separate module for convenience).  We define
12
 *   such basic classes as containers, surfaces, fixed-in-place objects,
13
 *   openables, and lockables.  
14
 */
15
16
/* include the library header */
17
#include "adv3.h"
18
19
20
/* ------------------------------------------------------------------------ */
21
/*
22
 *   LocateInParent - this is a mix-in superclass that defines the location
23
 *   of the object as the object's lexical parent.  This is useful for
24
 *   nested object definitions where the next object should be located
25
 *   within the enclosing object.
26
 *   
27
 *   When this class is mixed with Thing or its subclasses, LocateInParent
28
 *   should go first, so that the location we define here takes precedence.
29
 */
30
class LocateInParent: object
31
    location = (lexicalParent)
32
;
33
34
/* ------------------------------------------------------------------------ */
35
/*
36
 *   Intangible - this is an object that represents something that can be
37
 *   sensed but which has no tangible existence, such as a ray of light, a
38
 *   sound, or an odor. 
39
 */
40
class Intangible: Thing
41
    /*
42
     *   The base intangible object has no presence in any sense,
43
     *   including sight.  Subclasses should override these as appropriate
44
     *   for the senses in which the object can be sensed.  
45
     */
46
    sightPresence = nil
47
    soundPresence = nil
48
    smellPresence = nil
49
    touchPresence = nil
50
51
    /* intangibles aren't included in regular room/inventory/contents lists */
52
    isListed = nil
53
    isListedInInventory = nil
54
    isListedInContents = nil
55
56
    /* hide intangibles from 'all' for all actions by default */
57
    hideFromAll(action) { return true; }
58
59
    /* don't hide from defaults, though */
60
    hideFromDefault(action) { return nil; }
61
62
    /*
63
     *   Essentially all verbs are meaningless on intangibles.  Each
64
     *   subclass should re-enable verbs that are meaningful for that
65
     *   specific type of intangible; to re-enable an action, just define
66
     *   a verify() handler for the action.
67
     *   
68
     *   Note that the verbs we handle via the Default handlers have no
69
     *   preconditions; since these verbs don't do anything anyway,
70
     *   there's no need to apply any preconditions to them.  
71
     */
72
    dobjFor(Default)
73
    {
74
        preCond = []
75
        verify() { illogical(&notWithIntangibleMsg, self); }
76
    }
77
    iobjFor(Default)
78
    {
79
        preCond = []
80
        verify() { illogical(&notWithIntangibleMsg, self); }
81
    }
82
;
83
84
/*
85
 *   A "vaporous" object is a visible but intangible object: something
86
 *   visible, and possibly with an odor and a sound, but not something that
87
 *   can be touched or otherwise physically manipulated.  Fire, smoke, and
88
 *   fog are examples of this kind of object.  
89
 */
90
class Vaporous: Intangible
91
    /* we have a sight presence */
92
    sightPresence = true
93
94
    /* 
95
     *   EXAMINE ALL, LISTEN TO ALL, and SMELL ALL apply to us, but hide
96
     *   from ALL for other actions, as not much else makes sense on us 
97
     */
98
    hideFromAll(action)
99
    {
100
        return !(action.ofKind(ExamineAction)
101
                 || action.ofKind(ListenToAction)
102
                 || action.ofKind(SmellAction));
103
    }
104
105
    /* 
106
     *   We can examine, smell, and listen to these objects, as normal for
107
     *   any Thing.  To make these verbs work as normal for Thing, we need
108
     *   to explicitly override the corresponding verifiers, so that we
109
     *   bypass the dobjFor(Default) verifier in Intangible.  We don't need
110
     *   to do anything special in the overrides, so just inherit the
111
     *   default handling; what's important is that we do override the
112
     *   methods at all. 
113
     */
114
    dobjFor(Examine) { verify() { inherited(); } }
115
    dobjFor(Smell) { verify() { inherited(); } }
116
    dobjFor(ListenTo) { verify() { inherited(); } }
117
118
    /* 
119
     *   look in, look through, look behind, look under, search: since
120
     *   vaporous objects are usually essentially transparent, these
121
     *   commands reveal nothing interesting 
122
     */
123
    lookInDesc { mainReport(&lookInVaporousMsg, self); }
124
125
    /* 
126
     *   downgrade the likelihood of these slightly, and map everything to
127
     *   LOOK IN 
128
     */
129
    dobjFor(LookIn) { verify() { logicalRank(70, 'look in vaporous'); } }
130
    dobjFor(LookThrough) asDobjFor(LookIn)
131
    dobjFor(LookBehind) asDobjFor(LookIn)
132
    dobjFor(LookUnder) asDobjFor(LookIn)
133
    dobjFor(Search) asDobjFor(LookIn)
134
135
    /* the message we display for commands we disallow */
136
    notWithIntangibleMsg = &notWithVaporousMsg
137
;
138
139
140
/*
141
 *   A sensory emanation.  This is an intangible object that represents a
142
 *   sound, odor, or the like. 
143
 */
144
class SensoryEmanation: Intangible
145
    /*
146
     *   Are we currently emanating our sensory information?  This can be
147
     *   used as an on/off switch to control when we're active.  
148
     */
149
    isEmanating = true
150
    
151
    /* 
152
     *   The description shown when the *source* is examined (with "listen
153
     *   to", "smell", or whatever verb is appropriate to the type of sense
154
     *   the subclass involves).  This will also be appended to the regular
155
     *   "examine" description, if we're not marked as ambient.  
156
     */
157
    sourceDesc = ""
158
159
    /* our description, with and without being able to see the source */
160
    descWithSource = ""
161
    descWithoutSource = ""
162
163
    /* 
164
     *   Our "I am here" message, with and without being able to see the
165
     *   source.  These are displayed in room descriptions, inventory
166
     *   descriptions, and by the daemon that schedules background messages
167
     *   for sensory emanations.
168
     *   
169
     *   If different messages are desired as the emanation is mentioned
170
     *   repeatedly while the emanation remains continuously within sense
171
     *   range of the player character ("A phone is ringing", "The phone is
172
     *   still ringing", etc), you can do one of two things.  The easier
173
     *   way is to use a Script object; each time we need to show a
174
     *   message, we'll invoke the script.  The other way, which is more
175
     *   manual but gives you greater control, is to write a method that
176
     *   checks the displayCount property of self to determine which
177
     *   iteration of the message is being shown.  displayCount is set to 1
178
     *   the first time a message is displayed for the object when the
179
     *   object can first be sensed, and is incremented each we invoke one
180
     *   of these display routines.  Note that displayCount resets to nil
181
     *   when the object leaves sense scope, so the sequence of messages
182
     *   will automatically start over each time the object comes back into
183
     *   scope.
184
     *   
185
     *   The manual way (writing a method that checks the displayCount)
186
     *   might be desirable if you want the emanation to fade into the
187
     *   background gradually as the player character stays in the same
188
     *   location repeatedly.  This mimics human perception: we notice a
189
     *   noise or odor most when we first hear it, but if it continues for
190
     *   an extended period without changing, we'll eventually stop
191
     *   noticing it.  
192
     */
193
    hereWithSource = ""
194
    hereWithoutSource = ""
195
196
    /* 
197
     *   A message to display when the emanation ceases to be within sense
198
     *   range.  In most cases, this displays nothing at all, but some
199
     *   emanations might want to note explicitly when the noise/etc
200
     *   stops.
201
     */
202
    noLongerHere = ""
203
204
    /*
205
     *   Flag: I'm an "ambient" emanation.  This means we essentially are
206
     *   part of the background, and are not worth mentioning in our own
207
     *   right.  If this is set to true, then we won't mention this
208
     *   emanation at all when it first becomes reachable in its sense.
209
     *   This should be used for background noises and the like: we won't
210
     *   ever make an unsolicited mention of them, but they'll still show
211
     *   up in explicit 'listen' commands and so on.  
212
     */
213
    isAmbient = nil
214
215
    /*
216
     *   The schedule for displaying messages about the emanation.  This
217
     *   is a list of intervals between messages, in game clock times.
218
     *   When the player character can repeatedly sense this emanation for
219
     *   multiple consecutive turns, we'll use this schedule to display
220
     *   messages periodically about the noise/odor/etc.
221
     *   
222
     *   Human sensory perception tends to be "edge-sensitive," which
223
     *   means that we tend to perceive sensory input most acutely when
224
     *   something changes.  When a sound or odor is continually present
225
     *   without variation for an extended period, it tends to fade into
226
     *   the background of our awareness, so that even though it remains
227
     *   audible, we gradually stop noticing it.  This message display
228
     *   schedule mechanism is meant to approximate this perceptual model
229
     *   by allowing the sensory emanation to specify how noticeable the
230
     *   emanation remains during continuous exposure.  Typically, a
231
     *   continuous emanation would have relatively frequent messages
232
     *   (every two turns, say) for a couple of iterations, then would
233
     *   switch to infrequent messages.  Emanations that are analogous to
234
     *   white noise would probably not be mentioned at all after the
235
     *   first couple of messages, because the human senses are especially
236
     *   given to treating such input as background.
237
     *   
238
     *   We use this list by applying each interval in the list once and
239
     *   then moving to the next entry in the list.  The first entry in
240
     *   the list is the interval between first sensing the emanation and
241
     *   displaying the first "still here" message.  When we reach the end
242
     *   of the list, we simply repeat the last interval in the list
243
     *   indefinitely.  If the last entry in the list is nil, though, we
244
     *   simply never produce another message.  
245
     */
246
    displaySchedule = [nil]
247
248
    /*
249
     *   Show our "I am here" description.  This is the description shown
250
     *   as part of our room's description.  We show our hereWithSource or
251
     *   hereWithoutSource message, according to whether or not we can see
252
     *   the source object.  
253
     */
254
    emanationHereDesc()
255
    {
256
        local actor;
257
        local prop;
258
259
        /* if we're not currently emanating, there's nothing to do */
260
        if (!isEmanating)
261
            return;
262
        
263
        /* note that we're mentioning the emanation */
264
        noteDisplay();
265
266
        /* 
267
         *   get the actor driving the description - if there's a command
268
         *   active, use the command's actor; otherwise use the player
269
         *   character
270
         */
271
        if ((actor = gActor) == nil)
272
            actor = gPlayerChar;
273
274
        /* our display varies according to our source's visibility */
275
        prop = (canSeeSource(actor) ? &hereWithSource : &hereWithoutSource);
276
277
        /* 
278
         *   if it's a Script object, invoke the script; otherwise, just
279
         *   invoke the property 
280
         */
281
        if (propType(prop) == TypeObject && self.(prop).ofKind(Script))
282
            self.(prop).doScript();
283
        else
284
            self.(prop);
285
    }
286
287
    /*
288
     *   Show a message describing that we cannot see the source of this
289
     *   emanation because the given obstructor is in the way.  This
290
     *   should be overridden for each subclass. 
291
     */
292
    cannotSeeSource(obs) { }
293
294
    /* 
295
     *   Get the source of the noise/odor/whatever, as perceived by the
296
     *   current actor.  This is the object we appear to be coming from.
297
     *   By default, an emanation is generated by its direct container,
298
     *   and by default this is apparent to actors, so we'll simply return
299
     *   our direct container.
300
     *   
301
     *   If the source is not apparent, this should simply return nil.  
302
     */
303
    getSource() { return location; }
304
305
    /* determine if our source is apparent and visible */
306
    canSeeSource(actor)
307
    {
308
        local src;
309
        
310
        /* get our source */
311
        src = getSource();
312
313
        /* 
314
         *   return true if we have an apparent source, and the apparent
315
         *   source is visible to the current actor 
316
         */
317
        return src != nil && actor.canSee(src);
318
    }
319
320
    /*
321
     *   Note that we're displaying a message about the emanation.  This
322
     *   method should be called any time a message about the emanation is
323
     *   displayed, either by an explicit action or by our background
324
     *   daemon.
325
     *   
326
     *   We'll adjust our next display time so that we wait the full
327
     *   interval at the current point in the display schedule before we
328
     *   show any background message about this object.  Note we do not
329
     *   advance through the schedule list; instead, we merely delay any
330
     *   further message by the interval at the current point in the
331
     *   schedule list.  
332
     */
333
    noteDisplay()
334
    {
335
        /* calculate our next display time */
336
        calcNextDisplayTime();
337
338
        /* count the display */
339
        if (displayCount == nil)
340
            displayCount = 1;
341
        else
342
            ++displayCount;
343
    }
344
345
    /*
346
     *   Note an indirect message about the emanation.  This can be used
347
     *   when we don't actually display a message ourselves, but another
348
     *   object (usually our source object) describes the emanation; for
349
     *   example, if our source object mentions the noise it's making when
350
     *   it is examined, it should call this method to let us know we have
351
     *   been described indirectly.  This method advances our next display
352
     *   time, just as noteDisplay() does, but this method doesn't count
353
     *   the display as a direct display. 
354
     */
355
    noteIndirectDisplay()
356
    {
357
        /* calculate our next display time */
358
        calcNextDisplayTime();
359
    }
360
    
361
    /*
362
     *   Begin the emanation.  This is called from the sense change daemon
363
     *   when the item first becomes noticeable to the player character -
364
     *   for example, when the player character first enters the room
365
     *   containing the emanation, or when the emanation is first
366
     *   activated.  
367
     */
368
    startEmanation()
369
    {
370
        /* if we're an ambient emanation only, don't mention it */
371
        if (isAmbient)
372
            return;
373
374
        /* 
375
         *   if we've already initialized our scheduling, we must have
376
         *   been explicitly mentioned, such as by a room description - in
377
         *   this case, act as though we're continuing our emanation 
378
         */
379
        if (scheduleIndex != nil)
380
        {
381
            continueEmanation();
382
            return;
383
        }
384
385
        /* show our message */
386
        emanationHereDesc;
387
    }
388
389
    /*
390
     *   Continue the emanation.  This is called on each turn in which the
391
     *   emanation remains continuously within sense range of the player
392
     *   character.  
393
     */
394
    continueEmanation()
395
    {
396
        /* 
397
         *   if we are not to run again, our next display time will be set
398
         *   to zero - do nothing in this case 
399
         */
400
        if (nextDisplayTime == 0 || nextDisplayTime == nil)
401
            return;
402
403
        /* if we haven't yet reached our next display time, do nothing */
404
        if (Schedulable.gameClockTime < nextDisplayTime)
405
            return;
406
407
        /* 
408
         *   Advance to the next schedule interval, if we have one.  If
409
         *   we're already on the last schedule entry, simply repeat it
410
         *   forever. 
411
         */
412
        if (scheduleIndex < displaySchedule.length())
413
            ++scheduleIndex;
414
415
        /* show our description */
416
        emanationHereDesc;
417
    }
418
419
    /*
420
     *   End the emanation.  This is called when the player character can
421
     *   no longer sense the emanation. 
422
     */
423
    endEmanation()
424
    {
425
        /* show our "no longer here" message */
426
        noLongerHere;
427
428
        /* uninitialize the display scheduling */
429
        scheduleIndex = nil;
430
        nextDisplayTime = nil;
431
432
        /* reset the display count */
433
        displayCount = nil;
434
    }
435
436
    /*
437
     *   Calculate our next display time.  The caller must set our
438
     *   scheduleIndex to the correct index prior to calling this.  
439
     */
440
    calcNextDisplayTime()
441
    {
442
        local delta;
443
444
        /* if our scheduling isn't initialized, set it up now */
445
        if (scheduleIndex == nil)
446
        {
447
            /* start at the first display schedule interval */
448
            scheduleIndex = 1;
449
        }
450
        
451
        /* get the next display interval from the schedule list */
452
        delta = displaySchedule[scheduleIndex];
453
454
        /* 
455
         *   if the current display interval is nil, it means that we're
456
         *   never to display another message 
457
         */
458
        if (delta == nil)
459
        {
460
            /* 
461
             *   we're not to display again - simply set the next display
462
             *   time to zero and return 
463
             */
464
            nextDisplayTime = 0;
465
            return;
466
        }
467
468
        /* 
469
         *   our next display time is the current game clock time plus the
470
         *   interval 
471
         */
472
        nextDisplayTime = Schedulable.gameClockTime + delta;
473
    }
474
475
    /*
476
     *   Internal counters that keep track of our display scheduling.
477
     *   scheduleIndex is the index in the displaySchedule list of the
478
     *   interval we're waiting to expire; nextDisplayTime is the game
479
     *   clock time of our next display.  noiseList and odorList are lists
480
     *   of senseInfo entries for the sound and smell senses,
481
     *   respectively, indicating which objects were within sense range on
482
     *   the last turn.  displayCount is the number of times in a row
483
     *   we've displayed a message already.  
484
     */
485
    scheduleIndex = nil
486
    nextDisplayTime = nil
487
    noiseList = nil
488
    odorList = nil
489
    displayCount = nil
490
491
    /*
492
     *   Class method implementing the sensory change daemon.  This runs
493
     *   on each turn to check for changes in the set of objects the
494
     *   player can hear and smell, and to generate "still here" messages
495
     *   for objects continuously within sense range for multiple turns.  
496
     */
497
    noteSenseChanges()
498
    {
499
        /* emanations don't change anything, so turn on caching */
500
        libGlobal.enableSenseCache();
501
502
        /* note sound changes */
503
        noteSenseChangesFor(sound, &noiseList, Noise);
504
505
        /* note odor changes */
506
        noteSenseChangesFor(smell, &odorList, Odor);
507
508
        /* done with sense caching */
509
        libGlobal.disableSenseCache();
510
    }
511
512
    /*
513
     *   Note sense changes for a particular sense.  'listProp' is the
514
     *   property of SensoryEmanation giving the list of SenseInfo entries
515
     *   for the sense on the previous turn.  'sub' is a subclass of ours
516
     *   (such as Noise) giving the type of sensory emanation used for
517
     *   this sense. 
518
     */
519
    noteSenseChangesFor(sense, listProp, sub)
520
    {
521
        local newInfo;
522
        local oldInfo;
523
524
        /* get the old table of SenseInfo entries for the sense */
525
        oldInfo = self.(listProp);
526
527
        /* 
528
         *   Get the new table of items we can reach in the given sense,
529
         *   and reduce it to include only emanations of the subclass of
530
         *   interest.  
531
         */
532
        newInfo = gPlayerChar.senseInfoTable(sense);
533
        newInfo.forEachAssoc(new function(obj, info)
534
        {
535
            /* 
536
             *   remove this item if it's not of the subclass of interest,
537
             *   or if it's not currently emanating 
538
             */
539
            if (!obj.ofKind(sub) || !obj.isEmanating)
540
                newInfo.removeElement(obj);
541
        });
542
543
        /* run through the new list and note each change */
544
        newInfo.forEachAssoc(new function(obj, info)
545
        {
546
            /* treat this as a new command visually */
547
            "<.commandsep>";
548
        
549
            /* 
550
             *   Check to see whether the item is starting anew or was
551
             *   already here on the last turn.  If the item was in our
552
             *   list from the previous turn, it was already here.  
553
             */
554
            if (oldInfo == nil || oldInfo[obj] == nil)
555
            {
556
                /* 
557
                 *   the item wasn't in sense range on the last turn, so
558
                 *   it is becoming newly noticeable 
559
                 */
560
                obj.startEmanation();
561
            }
562
            else
563
            {
564
                /* the item was already here - continue its emanation */
565
                obj.continueEmanation();
566
            }
567
        });
568
569
        /* run through the old list and note each item no longer sensed */
570
        if (oldInfo != nil)
571
        {
572
            oldInfo.forEachAssoc(new function(obj, info)
573
            {
574
                /* if this item isn't in the new list, note its departure */
575
                if (newInfo[obj] == nil)
576
                {
577
                    /* treat this as a new command visually */
578
                    "<.commandsep>";
579
                    
580
                    /* note the departure */
581
                    obj.endEmanation();
582
                }
583
            });
584
        }
585
586
        /* store the current list for comparison the next time we run */
587
        self.(listProp) = newInfo;
588
    }
589
590
    /* 
591
     *   Examine the sensory emanation.  We'll show our descWithSource or
592
     *   descWithoutSource, according to whether or not we can see the
593
     *   source object. 
594
     */
595
    dobjFor(Examine)
596
    {
597
        verify() { inherited(); }
598
        action()
599
        {
600
            /* note that we're displaying a message about us */
601
            noteDisplay();
602
            
603
            /* display our sound description */
604
            if (canSeeSource(gActor))
605
            {
606
                /* we can see the source */
607
                descWithSource;
608
            }            
609
            else
610
            {
611
                local src;
612
            
613
                /* show the unseen-source version of the description */
614
                descWithoutSource;
615
616
                /* 
617
                 *   If we have a source, find out what's keeping us from
618
                 *   seeing the source; in other words, find the opaque
619
                 *   visual obstructor on the sense path to the source.  
620
                 */
621
                if ((src = getSource()) != nil)
622
                {
623
                    local obs;
624
                    
625
                    /* get the visual obstructor */
626
                    obs = gActor.findVisualObstructor(src);
627
                    
628
                    /* 
629
                     *   If we found an obstructor, and we can see it, add
630
                     *   a message describing the obstruction.  If we
631
                     *   can't see the obstructor, we can't localize the
632
                     *   sensory emanation at all.  
633
                     */
634
                    if (obs != nil && gActor.canSee(obs))
635
                        cannotSeeSource(obs);
636
                }
637
            }
638
        }
639
    }
640
;
641
642
/*
643
 *   Noise - this is an intangible object representing a sound.
644
 *   
645
 *   A Noise object is generally placed directly within the object that is
646
 *   generating the noise.  This will ensure that the noise is
647
 *   automatically in scope whenever the object is in scope (or, more
648
 *   precisely, whenever the object's contents are in scope) and with the
649
 *   same sense attributes.
650
 *   
651
 *   By default, when a noise is specifically examined via "listen to",
652
 *   and the container is visible, we'll mention that the noise is coming
653
 *   from the container.
654
 */
655
class Noise: SensoryEmanation
656
    /* 
657
     *   by default, we have a definite presence in the sound sense if
658
     *   we're emanating our noise
659
     */
660
    soundPresence = (isEmanating)
661
662
    /* 
663
     *   By default, a noise is listed in a room description (i.e., on LOOK
664
     *   or entry to a room) unless it's an ambient background noise..  Set
665
     *   this to nil to omit the noise from the room description, while
666
     *   still allowing it to be heard in an explicit LISTEN command.  
667
     */
668
    isSoundListedInRoom = (!isAmbient && isEmanating)
669
670
    /* show our description as part of a room description */
671
    soundHereDesc() { emanationHereDesc(); }
672
673
    /* explain that we can't see the source because of the obstructor */
674
    cannotSeeSource(obs) { obs.cannotSeeSoundSource(self); }
675
676
    /* treat "listen to" the same as "examine" */
677
    dobjFor(ListenTo) asDobjFor(Examine)
678
679
    /* "examine" requires that the object is audible */
680
    dobjFor(Examine)
681
    {
682
        preCond = [objAudible]
683
    }
684
;
685
686
/*
687
 *   Odor - this is an intangible object representing an odor. 
688
 */
689
class Odor: SensoryEmanation
690
    /* 
691
     *   by default, we have a definite presence in the smell sense if
692
     *   we're currently emanating our odor 
693
     */
694
    smellPresence = (isEmanating)
695
696
    /* 
697
     *   By default, an odor is listed in a room description (i.e., on LOOK
698
     *   or entry to a room) unless it's an ambient background odor.  Set
699
     *   this to nil to omit the odor from the room description, while
700
     *   still allowing it to be listed in an explicit SMELL command.  
701
     */
702
    isSmellListedInRoom = (!isAmbient && isEmanating)
703
704
    /* mention the odor as part of a room description */
705
    smellHereDesc() { emanationHereDesc(); }
706
707
    /* explain that we can't see the source because of the obstructor */
708
    cannotSeeSource(obs) { obs.cannotSeeSmellSource(self); }
709
710
    /* handle "smell" using our "examine" handler */
711
    dobjFor(Smell) asDobjFor(Examine)
712
713
    /* "examine" requires that the object is smellable */
714
    dobjFor(Examine)
715
    {
716
        preCond = [objSmellable]
717
    }
718
;
719
720
/*
721
 *   SimpleNoise is for cases where a noise is an ongoing part of a
722
 *   location, so (1) it's not necessary to distinguish source and
723
 *   sourceless versions of the description, and (2) there are no
724
 *   scheduled reports for the noise.  For these cases, all of the
725
 *   messages default to the basic 'desc' property.  Note that we make
726
 *   this type of noise "ambient" by default, which means that we won't
727
 *   automatically include it in room descriptions.  
728
 */
729
class SimpleNoise: Noise
730
    isAmbient = true
731
    sourceDesc { desc; }
732
    descWithSource { desc; }
733
    descWithoutSource { desc; }
734
    hereWithSource { desc; }
735
    hereWithoutSource { desc; }
736
;
737
738
/* SimpleOdor is the olfactory equivalent of SimpleNoise */
739
class SimpleOdor: Odor
740
    isAmbient = true
741
    sourceDesc { desc; }
742
    descWithSource { desc; }
743
    descWithoutSource { desc; }
744
    hereWithSource { desc; }
745
    hereWithoutSource { desc; }
746
;
747
748
/* ------------------------------------------------------------------------ */
749
/*
750
 *   Sensory Event.  This is an object representing a transient event,
751
 *   such as a sound, visual display, or odor, to which some objects
752
 *   observing the event might react.
753
 *   
754
 *   A sensory event differs from a sensory emanation in that an emanation
755
 *   is ongoing and passive, while an event is isolated in time and
756
 *   actively notifies observers.  
757
 */
758
class SensoryEvent: object
759
    /* 
760
     *   Trigger the event.  This routine must be called at the time when
761
     *   the event is to occur.  We'll notify every interested observer
762
     *   capable of sensing the event that the event is occurring, so
763
     *   observers can take appropriate action in response to the event.
764
     *   
765
     *   'source' is the source object - this is the physical object in
766
     *   the simulation that is causing the event.  For example, if the
767
     *   event is the sound of a phone ringing, the phone would probably
768
     *   be the source object.  The source is used to determine which
769
     *   observers are capable of detecting the event: an observer must be
770
     *   able to sense the source object in the appropriate sense to be
771
     *   notified of the event.  
772
     */
773
    triggerEvent(source)
774
    {
775
        /* 
776
         *   Run through all objects connected to the source object by
777
         *   containment, and notify any that are interested and can
778
         *   detect the event.  Containment is the only way sense
779
         *   information can propagate, so we can limit our search
780
         *   accordingly.
781
         *   
782
         *   Connection by containment is no guarantee of a sense
783
         *   connection: it's a necessary, but not sufficient, condition.
784
         *   Because it's a necessary condition, though, we can use it to
785
         *   limit the number of objects we have to test with a more
786
         *   expensive sense path calculation.  
787
         */
788
        source.connectionTable().forEachAssoc(new function(cur, val)
789
        {
790
            /* 
791
             *   If this object defines the observer notification method,
792
             *   then it might be interested in the event.  If the object
793
             *   doesn't define this method, then there's no way it could
794
             *   be interested.  (We make this test before checking the
795
             *   sense path because checking to see if an object defines a
796
             *   property is fast and simple, while the sense path
797
             *   calculation could be expensive.) 
798
             */
799
            if (cur.propDefined(notifyProp, PropDefAny))
800
            {
801
                local info;
802
                
803
                /* 
804
                 *   This object might be interested in the event, so
805
                 *   check to see if the object can sense the event.  If
806
                 *   this object can sense the source object at all (i.e.,
807
                 *   the sense path isn't 'opaque'), then notify the
808
                 *   object of the event.  
809
                 */
810
                info = cur.senseObj(sense, source);
811
                if (info.trans != opaque)
812
                {
813
                    /* 
814
                     *   this observer object can sense the source of the
815
                     *   event, so notify it of the event 
816
                     */
817
                    cur.(notifyProp)(self, source, info);
818
                }
819
            }
820
        });
821
    }
822
823
    /* the sense in which the event is observable */
824
    sense = nil
825
826
    /* 
827
     *   the notification property - this is the property we'll invoke on
828
     *   each observer to notify it of the event 
829
     */
830
    notifyProp = nil
831
;
832
833
/*
834
 *   Visual event 
835
 */
836
class SightEvent: SensoryEvent
837
    sense = sight
838
    notifyProp = &notifySightEvent
839
;
840
841
/* 
842
 *   Visual event observer.  This is a mix-in that can be added to any
843
 *   other classes.  
844
 */
845
class SightObserver: object
846
    /*
847
     *   Receive notification of a sight event.  This routine is called
848
     *   whenever a SightEvent occurs within view of this object.
849
     *   
850
     *   'event' is the SightEvent object; 'source' is the physical
851
     *   simulation object that is making the visual display; and 'info'
852
     *   is a SenseInfo object describing the viewing conditions from this
853
     *   object to the source object.  
854
     */
855
    notifySightEvent(event, source, info) { }
856
;
857
858
/*
859
 *   Sound event 
860
 */
861
class SoundEvent: SensoryEvent
862
    sense = sound
863
    notifyProp = &notifySoundEvent
864
;
865
866
/* 
867
 *   Sound event observer.  This is a mix-in that can be added to any
868
 *   other classes.  
869
 */
870
class SoundObserver: object
871
    /*
872
     *   Receive notification of a sound event.  This routine is called
873
     *   whenever a SoundEvent occurs within hearing range of this object.
874
     */
875
    notifySoundEvent(event, source, info) { }
876
;
877
878
/*
879
 *   Smell event 
880
 */
881
class SmellEvent: SensoryEvent
882
    sense = smell
883
    notifyProp = &notifySmellEvent
884
;
885
886
/* 
887
 *   Smell event observer.  This is a mix-in that can be added to any
888
 *   other classes.  
889
 */
890
class SmellObserver: object
891
    /*
892
     *   Receive notification of a smell event.  This routine is called
893
     *   whenever a SmellEvent occurs within smelling range of this
894
     *   object.  
895
     */
896
    notifySmellEvent(event, source, info) { }
897
;
898
899
900
/* ------------------------------------------------------------------------ */
901
/*
902
 *   Hidden - this is an object that's present but not visible to any
903
 *   actors.  The object will simply not be visible in the 'sight' sense
904
 *   until discovered. 
905
 */
906
class Hidden: Thing
907
    /* we can't be seen until discovered */
908
    canBeSensed(sense, trans, ambient)
909
    {
910
        /* 
911
         *   If the sense is sight, and we haven't been discovered yet, we
912
         *   cannot be sensed.  Otherwise, inherit the normal handling. 
913
         */
914
        if (sense == sight && !discovered)
915
            return nil;
916
        else
917
            return inherited(sense, trans, ambient);
918
    }
919
920
    /* 
921
     *   Have we been discovered yet?
922
     *   
923
     *   Note that this should be a simple property value, not a method.
924
     *   It's risky to make this a method because it's evaluated from
925
     *   within some of the low-level scope/sense calculations, and those
926
     *   calculations depend upon certain global variables.  If you make
927
     *   this property into a method, you could indirectly call another
928
     *   method that changes some of the same globals, which could disrupt
929
     *   the main scope/sense calculations and cause other, seemingly
930
     *   unrelated objects to mysteriously appear or disappear at the wrong
931
     *   times.  If you need to calculate this value dynamically, you could
932
     *   explicitly assign the property a new value in something like a
933
     *   daemon or an afterAction() method.
934
     *   
935
     *   (The warning above is a bit more conservative than is strictly
936
     *   necessary.  It actually is safe to make 'discovered' a method,
937
     *   *provided* that the method doesn't ever call anything that's
938
     *   involved in the scope/sense calculations.  For example, never call
939
     *   methods like senseObj(), senseAmbientMax(), or
940
     *   sensePresenceList(), or anything that calls those.  In most cases,
941
     *   it's safe to call non-sense-related methods, like isOpen() or
942
     *   isIn().)  
943
     */
944
    discovered = nil
945
946
    /* mark the object as discovered */
947
    discover()
948
    {
949
        local pc;
950
        
951
        /* note that we've been discovered */
952
        discovered = true;
953
954
        /* mark me and my contents as having been seen */
955
        if ((pc = gPlayerChar).canSee(self))
956
        {
957
            /* mark me as seen */
958
            pc.setHasSeen(self);
959
960
            /* mark my visible contents as see */
961
            setContentsSeenBy(pc.visibleInfoTable(), pc);
962
        }
963
    }
964
;
965
 
966
967
/* ------------------------------------------------------------------------ */
968
/*
969
 *   Collective - this is an object that can be used to refer to a group of
970
 *   other (usually equivalent) objects collectively.  In most cases, this
971
 *   object will be a separate game object that contains or can contain the
972
 *   individuals: a bag of marbles can be a collective for the marbles, or
973
 *   a book of matches can be a collective for the matchsticks.
974
 *   
975
 *   A collective object is usually given the same plural vocabulary as its
976
 *   individuals.  When we use that plural vocabulary, we will filter for
977
 *   or against the collective, as determined by the noun phrase
978
 *   production, when the player uses the collective term.
979
 *   
980
 *   This is a mix-in class, intended to be used along with other (usually
981
 *   Thing-derived) superclasses.  
982
 */
983
class Collective: object
984
    filterResolveList(lst, action, whichObj, np, requiredNum)
985
    {
986
        /* scan for my matching individuals */
987
        foreach (local cur in lst)
988
        {
989
            /* if this one's a matching individual, decide what to do */
990
            if (isCollectiveFor(cur.obj_))
991
            {
992
                /* 
993
                 *   We're a collective for this object.  If the noun
994
                 *   phrase production wants us to filter for collectives,
995
                 *   remove the individual and keep me (the collective);
996
                 *   otherwise, keep the individual and remove me. 
997
                 */
998
                if (np.filterForCollectives)
999
                {
1000
                    /* 
1001
                     *   we want to keep the collective, so remove this
1002
                     *   individual item 
1003
                     */
1004
                    lst -= cur;
1005
                }
1006
                else
1007
                {
1008
                    /* 
1009
                     *   we want to keep individuals, so remove the
1010
                     *   collective (i.e., myself) 
1011
                     */
1012
                    lst -= lst.valWhich({x: x.obj_ == self});
1013
1014
                    /* 
1015
                     *   we can only be in the list once, so there's no
1016
                     *   need to keep looking - if we found another item
1017
                     *   for which we're a collective, all we'd do is try
1018
                     *   to remove myself again, which would be pointless
1019
                     *   since I'm already gone 
1020
                     */
1021
                    break;
1022
                }
1023
            }
1024
        }
1025
1026
        /* return the result */
1027
        return lst;
1028
    }
1029
1030
    /*
1031
     *   Determine if I'm a collective object for the given object.
1032
     *   
1033
     *   In order to be a collective for some objects, an object must have
1034
     *   vocubulary for the plural name, and must return true from this
1035
     *   method for the collected objects.  
1036
     */
1037
    isCollectiveFor(obj) { return nil; }
1038
;
1039
1040
/*
1041
 *   A "collective group" object.  This is an abstract object: the player
1042
 *   doesn't think of this as a physically separate object, but rather as a
1043
 *   collection of a bunch of individual objects.  For example, if you had
1044
 *   a group of floor-number buttons in an elevator, you might create a
1045
 *   CollectiveGroup to represent the buttons as a collection - from the
1046
 *   player's perspective, there's not a separate physical object called
1047
 *   "the buttons," but it might nonetheless be handy to refer to "the
1048
 *   buttons" collectively as a single entity in commands.  CollectiveGroup
1049
 *   is designed for such situations.
1050
 *   
1051
 *   There are two ways to use CollectiveGroup: as a non-physical,
1052
 *   non-simulation object whose only purpose is to field a few specific
1053
 *   commands; or as a physical simulation object that shows up separately
1054
 *   as an object in its own right.
1055
 *   
1056
 *   First: you can use a CollectiveGroup as a non-physical object, which
1057
 *   essentially means it has a nil 'location'.  The group object doesn't
1058
 *   actually appear in any location.  Instead, it'll be brought into the
1059
 *   sensory system automatically by its individuals, and it'll have the
1060
 *   same effective sensory status as the most visible/audible/etc of its
1061
 *   individuals.  This choice is appropriate when the individuals are
1062
 *   mobile, so they might be scattered around the game map, hence the
1063
 *   group object might need to be invoked anywhere.  With this option, you
1064
 *   normally won't want to make the CollectiveGroup handle very many
1065
 *   commands, because you'll have to completely customize each command you
1066
 *   want it to handle, in order to properly account for the possible
1067
 *   scattering of the individuals.  For example, if you want the group
1068
 *   object to handle the TAKE command, you'll have to figure out which
1069
 *   individuals are in reach, and specially program the procedure for
1070
 *   taking each of the available individuals.
1071
 *   
1072
 *   Second: you can use CollectiveGroup as a simulation object, and
1073
 *   actually set its 'location' to the location of its individuals.  The
1074
 *   group object in this case shows up in the simulation alongside its
1075
 *   individuals.  This is a good choice if the individuals are fixed in
1076
 *   place, all in one place, because you can simply put the group object
1077
 *   in the same location as the individuals without worrying that the
1078
 *   individuals will move around the game later on.  This is much easier
1079
 *   to handle than the first case above, mostly because commands that
1080
 *   physically manipulate the individuals (such as TAKE) aren't a factor.
1081
 *   In this set-up, you can easily let the group object handle many
1082
 *   actions, since it won't have to do much apart from showing the default
1083
 *   failure messages that a Fixed would generate in any other situation.
1084
 *   Note that if you use this approach, the CollectiveGroup should *also*
1085
 *   inherit from Fixture or the like, so that the group object is fixed in
1086
 *   place just like its corresponding individuals.
1087
 *   
1088
 *   The parser will substitute a CollectiveGroup object for its
1089
 *   individuals when (1) any of the individuals are in scope, (2) the
1090
 *   CollectiveGroup has vocabulary that matches a noun phrase in the
1091
 *   player's input, and (3) the conditions for substitution, defined by
1092
 *   isCollectiveQuant and isCollectiveAction, are met.
1093
 *   
1094
 *   (The substitution itself is handled in two steps.  First, an
1095
 *   individual will add the group object to the sense connection list
1096
 *   whenever the individual is in the connection list, which will bring
1097
 *   the object into scope, so the parser will be able to match the
1098
 *   vocabulary from the group object any time an individual is in scope.
1099
 *   Once the group object is matched, its filterResolveList method will
1100
 *   throw out either the group object or all of the individuals, depending
1101
 *   on whether or not the isCollectiveQuant and isCollectiveAction tests
1102
 *   are met.)
1103
 *   
1104
 *   For example, we might have a bunch of coins and paper bills in a game,
1105
 *   and give them all a plural word 'money'.  We then also create a
1106
 *   collective group object with plural word 'money'.  We set the
1107
 *   collectiveGroup property of each coin and bill object to refer to the
1108
 *   collective group object.  Whenever the player uses 'money' in a
1109
 *   command, the individual coins and bills will initially match, and the
1110
 *   group object will also match.  The group object will then either throw
1111
 *   itself out, keeping only the individuals, or will throw out the
1112
 *   individuals.  If the group object decides to field the command, it
1113
 *   will be the only matching object, so a command like "examine money"
1114
 *   will be directed to the single collective group object, rather than
1115
 *   directed to the matching individuals one at a time.  This allows the
1116
 *   game to present simpler, more elegant responses to commands on the
1117
 *   individuals as a group.
1118
 *   
1119
 *   By default, the only action we handle is Examine.  Each instance must
1120
 *   provide a suitable description so that when the collective is
1121
 *   examined, we describe the group of individuals appropriately.  
1122
 */
1123
class CollectiveGroup: Thing
1124
    /* collective group objects are usually named in plural terms */
1125
    isPlural = true
1126
1127
    /*
1128
     *   Filter a noun phrase resolution list.
1129
     *   
1130
     *   If there are any objects in the resolution list for which we're a
1131
     *   collective, we'll check to see whether we want to the collective
1132
     *   or keep the individuals.  We want to keep the collective if the
1133
     *   action is one we can handle collectively; otherwise, we want to
1134
     *   drop the collective and let the individuals handle the action
1135
     *   instead.
1136
     *   
1137
     *   Note that, when any of our individuals are in scope, we're in
1138
     *   scope.  This means that the collective is always in the
1139
     *   resolution list, along with the individuals, if (1) any
1140
     *   individuals are in scope, and (2) the vocabulary used in the noun
1141
     *   phrase matches the collective object.  If the vocabulary doesn't
1142
     *   match the collective, the parser simply won't include the
1143
     *   collective in the resolution list by virtue of the normal
1144
     *   vocabulary selection mechanism, so we'll never reach this point.
1145
     *   
1146
     *   By default, the collective object will be ignored if a specific
1147
     *   number of objects is required.  When the player explicitly
1148
     *   specifies a quantity (by a phrase like "the five coins" or "both
1149
     *   coins"), we'll assume they want to iterate over individuals
1150
     *   rather than operate on the collection.    
1151
     */
1152
    filterResolveList(lst, action, whichObj, np, requiredNum)
1153
    {
1154
        /*
1155
         *   If we want to use the collective for the current action and
1156
         *   the required quantity, keep the collective; otherwise, if
1157
         *   there are any individuals, keep the individuals and filter
1158
         *   out the collective group.  If there are no matching
1159
         *   individuals, keep the collective group object, since there's
1160
         *   nothing to replace it.  
1161
         */
1162
        if (isCollectiveQuant(np, requiredNum)
1163
            && isCollectiveAction(action, whichObj))
1164
        {
1165
            /* 
1166
             *   We can handle the action collectively, so keep myself, and
1167
             *   get rid of the individuals.  We want to discard the
1168
             *   individuals because we want the entire action to be
1169
             *   handled by the collective object, rather than iterating
1170
             *   over the individuals.  So, discard each object that has
1171
             *   'self' as a collectiveGroup (which is to say, keep each
1172
             *   object that *doesn't* have collectiveGroup 'self').  
1173
             */
1174
            lst = lst.subset({x: !x.obj_.hasCollectiveGroup(self)});
1175
        }
1176
        else if (lst.indexWhich({x: x.obj_.hasCollectiveGroup(self)}) != nil)
1177
        {
1178
            /* 
1179
             *   We can't handle the action collectively, and the list
1180
             *   includes at least one of our individuals, so let the
1181
             *   individuals handle it.  Simply remove myself from the
1182
             *   list.  
1183
             */
1184
            lst = lst.removeElementAt(lst.indexWhich({x: x.obj_ == self}));
1185
        }
1186
1187
        /* return the updated list */
1188
        return lst;
1189
    }
1190
1191
    /*
1192
     *   "Unfilter" a pronoun antecedent list.  We'll restore the
1193
     *   individuals to the list so that we can choose anew, for the new
1194
     *   command, whether to select the group object or the individuals.
1195
     *   
1196
     *   For example, suppose there's a CollectiveGroup for a set of
1197
     *   elevator buttons that handles the Examine command, but no other
1198
     *   commands.  Now suppose the player types in these commands:
1199
     *   
1200
     *.  >examine buttons
1201
     *.  >push them
1202
     *   
1203
     *   On the first command, the CollectiveGroup object will filter out
1204
     *   the individual buttons in filterResolveList, because the group
1205
     *   object handles the Examine command on behalf of the individuals.
1206
     *   This will set the pronoun antecedent for IT and THEM to the group
1207
     *   object, because that's the program object that handled the
1208
     *   action.  On the second command, if the player had typed simply
1209
     *   PUSH BUTTONS, the collective group object would have filtered
1210
     *   *itself* out, keeping the individuals.  However, the raw pronoun
1211
     *   binding for THEM is the group object; if we did nothing to change
1212
     *   this, we'd get a different response for PUSH THEM than we'd get
1213
     *   for PUSH BUTTONS.  That's where this routine comes in: by
1214
     *   restoring the individuals, we let filterResolveList() make the
1215
     *   decision about what to keep anew for the pronoun.  
1216
     */
1217
    expandPronounList(typ, lst)
1218
    {
1219
        /* restore our individuals to the list */
1220
        forEachInstance(Thing, new function(obj) {
1221
            if (obj.hasCollectiveGroup(self))
1222
                lst += obj;
1223
        });
1224
1225
        /* return the list */
1226
        return lst;
1227
    }
1228
    
1229
    /*
1230
     *   Check the action to determine if it's one that we want to handle
1231
     *   collectively.  If so, return true; if not, return nil. 
1232
     */
1233
    isCollectiveAction(action, whichObj)
1234
    {
1235
        /* we handle 'Examine' */
1236
        if (action.ofKind(ExamineAction))
1237
            return true;
1238
1239
        /* it's not one of ours */
1240
        return nil;
1241
    }
1242
1243
    /*
1244
     *   Check to see if we're a collective for the given quantity.  By
1245
     *   default, we return true only when no quantity is specified.  
1246
     */
1247
    isCollectiveQuant(np, requiredNum)
1248
    {
1249
        /* if no quantity was specified, use the collective */
1250
        return (requiredNum == nil);
1251
    }
1252
1253
    /*
1254
     *   Get a list of the individuals that can be sensed, given the
1255
     *   information table for the desired sense (for visible items, this
1256
     *   can be obtained by calling gActor.visibleInfoTable()).  This is a
1257
     *   service routine that can be useful for purposes such as writing a
1258
     *   description routine for the collective.  For example, a "money"
1259
     *   collective object might want to count up the sum of money visible
1260
     *   and show that.
1261
     *   
1262
     *   Note that it's possible for this to return an empty list.  The
1263
     *   caller can deal with this in a description, for example, by
1264
     *   indicating that the collection cannot be seen.  
1265
     */
1266
    getVisibleIndividuals(tab)
1267
    {
1268
        /* keep only those items that are individuals of this collective */
1269
        tab.forEachAssoc(new function(key, val)
1270
        {
1271
            /* remove this item if it's not an individual of mine */
1272
            if (!key.hasCollectiveGroup(self))
1273
                tab.removeElement(key);
1274
        });
1275
1276
        /* return a list of the objects (i.e., the table's keys) */
1277
        return tab.keysToList();
1278
    }
1279
1280
    /*
1281
     *   When we have no location, we're an abstract object without any
1282
     *   physical presence in the game world.  However, we still want to
1283
     *   show up in the senses to the same extent our individuals do.  To
1284
     *   do this, we override this method so that we use the same sense
1285
     *   data as the most visible (or whatever) of our individuals.  
1286
     */
1287
    addToSenseInfoTable(sense, tab)
1288
    {
1289
        /* if we have no location, mimic our best individual */
1290
        if (location == nil && !ofKind(BaseMultiLoc))
1291
        {
1292
            /* check everything in the connection table */
1293
            tab.forEachAssoc(new function(cur, val) {
1294
                /* if this is one of our individuals, check it */
1295
                if (cur.hasCollectiveGroup(self))
1296
                {
1297
                    local t;
1298
                    
1299
                    /* 
1300
                     *   If it's the best or only one so far, adopt its
1301
                     *   sense status.  Consider it the best if it has a
1302
                     *   more transparent transparency than the best so
1303
                     *   far, or its transparency is the same and it has a
1304
                     *   high ambient level.  
1305
                     */
1306
                    t = transparencyCompare(cur.tmpTrans_, tmpTrans_);
1307
                    if (t > 0 || (t == 0 && cur.tmpAmbient_ > tmpAmbient_))
1308
                    {
1309
                        /* it's better than our settings; mimic it */
1310
                        tmpTrans_ = cur.tmpTrans_;
1311
                        tmpAmbient_ = cur.tmpAmbient_;
1312
                        tmpObstructor_ = cur.tmpObstructor_;
1313
                    }
1314
                }
1315
            });
1316
        }
1317
1318
        /* inherit the standard handling */
1319
        inherited(sense, tab);
1320
    }
1321
1322
    /*
1323
     *   When we have no location, we want to create our own special
1324
     *   containment path, just as we create our own special SenseInfo.  
1325
     */
1326
    specialPathFrom(src, vec)
1327
    {
1328
        /* if we have a location, use the normal handling */
1329
        if (location != nil || ofKind(BaseMultiLoc))
1330
            inherited(src, vec);
1331
1332
        /* look for an individual among the source object's connections */
1333
        src.connectionTable().forEachAssoc(new function(cur, val) {
1334
            /* if this is one of our individuals, check it */
1335
            if (cur.hasCollectiveGroup(self))
1336
            {
1337
                /* add this individual's paths to the vector */
1338
                vec.appendAll(src.getAllPathsTo(cur));
1339
            }
1340
        });
1341
    }
1342
1343
    /*
1344
     *   CollectiveGroup objects are not normally listable in any
1345
     *   situations.  Since a collective group is merely a parser stand-in
1346
     *   for its individuals, we don't want it to appear as a separate
1347
     *   object in the game. 
1348
     */
1349
    isListedInContents = nil
1350
    isListedInInventory = nil
1351
;
1352
1353
/*
1354
 *   An "itemizing" collective group is like a regular collective group,
1355
 *   but the Examine action itemizes the individual visible items making up
1356
 *   the group.  We itemize the individuals instead of showing the 'desc'
1357
 *   for the overall group object, as the basic collective group class
1358
 *   does.  
1359
 */
1360
class ItemizingCollectiveGroup: CollectiveGroup
1361
    /*
1362
     *   Override the main Examine handling.  By default, we'll list the
1363
     *   individuals that are visible, and separately list those that are
1364
     *   being carried by the actor.  If none of our individuals are
1365
     *   visible, simply say so.
1366
     */
1367
    mainExamine()
1368
    {
1369
        local info;
1370
        local vis;
1371
        local carried, here;
1372
1373
        /* get the visible info table */
1374
        info = gActor.visibleInfoTable();
1375
        
1376
        /* get the list of visible individuals */
1377
        vis = getVisibleIndividuals(info);
1378
1379
        /* if any individuals are visible, list them */
1380
        if (vis.length() != 0)
1381
        {
1382
            /* separate out the individuals being carried */
1383
            carried = vis.subset({x: x.isIn(gActor)});
1384
            here = vis - carried;
1385
1386
            /* show the items that are here but not being carried, if any */
1387
            if (here.length() != 0)
1388
            {
1389
                /* get the room contents lister */
1390
                local lister = gActor.location.roomContentsLister;
1391
                
1392
                /* get the subset that the room contents lister won't list */
1393
                local xlist = here.subset({x: !lister.isListed(x)});
1394
1395
                /* show the list through the room contents lister */
1396
                lister.showList(gActor, nil, here, 0, 0, info, nil);
1397
1398
                /* Examine any objects not part of the room description */
1399
                foreach (local x in xlist)
1400
                    examineUnlisted(x);
1401
1402
                /* 
1403
                 *   if that showed anything, add a paragraph break before
1404
                 *   the carried list 
1405
                 */
1406
                if (xlist.length() != 0 && carried.length() != 0)
1407
                    "<.p>";
1408
            }
1409
1410
            /* separately, show the items being carried, if any */
1411
            if (carried.length() != 0)
1412
                gActor.inventoryLister.showList(
1413
                    gActor, gActor, carried, 0, 0, info, nil);
1414
        }
1415
        else
1416
        {
1417
            /* 
1418
             *   None are visible.  If it's dark in the location, simply
1419
             *   say so; otherwise, say that we can't see any of me. 
1420
             */
1421
            if (!gActor.isLocationLit())
1422
                reportFailure(&tooDarkMsg);
1423
            else
1424
                reportFailure(&mustBeVisibleMsg, self);
1425
        }
1426
    }
1427
1428
    /*
1429
     *   Examine an unlisted individual object.  This will be called for
1430
     *   each object in the room that's not listable via the room contents
1431
     *   lister. 
1432
     */
1433
    examineUnlisted(x)
1434
    {
1435
        "<.p>";
1436
        nestedAction(Examine, x);
1437
    }
1438
;
1439
1440
/* ------------------------------------------------------------------------ */
1441
/*
1442
 *   A readable object.  Any ordinary object will show its normal full
1443
 *   description when read, but an object that is explicitly readable will
1444
 *   have elevated logicalness for the "read" action, and can optionally
1445
 *   show a separate description when read. 
1446
 */
1447
class Readable: Thing
1448
    /* 
1449
     *   Show my special reading desription.  By default, we set this to
1450
     *   nil to indicate that we should use our default "examine"
1451
     *   description; objects can override this to show a special message
1452
     *   for reading the object as desired.  
1453
     */
1454
    readDesc = nil
1455
1456
    /* our reading description when obscured */
1457
    obscuredReadDesc() { gLibMessages.obscuredReadDesc(self); }
1458
1459
    /* our reading description in dim light */
1460
    dimReadDesc() { gLibMessages.dimReadDesc(self); }
1461
1462
    /* "Read" action */
1463
    dobjFor(Read)
1464
    {
1465
        verify()
1466
        {
1467
            /* give slight preference to an object being held */
1468
            if (!isIn(gActor))
1469
                logicalRank(80, 'not held');
1470
        }
1471
        action()
1472
        {
1473
            /* 
1474
             *   if we have a special reading description defined, show
1475
             *   it; otherwise, use the same handling as "examine"
1476
             */
1477
            if (propType(&readDesc) != TypeNil)
1478
            {
1479
                local info;
1480
                
1481
                /* 
1482
                 *   Reading requires a transparent sight path and plenty
1483
                 *   of light; in the absence of either of these, we can't
1484
                 *   make out the details. 
1485
                 */
1486
                info = gActor.bestVisualInfo(self);
1487
                if (info.trans != transparent)
1488
                    obscuredReadDesc;
1489
                else if (info.ambient < 3)
1490
                    dimReadDesc;
1491
                else
1492
                    readDesc;
1493
            }
1494
            else
1495
            {
1496
                /* 
1497
                 *   we have no special reading description, so use the
1498
                 *   default "examine" handling 
1499
                 */
1500
                actionDobjExamine();
1501
            }
1502
        }
1503
    }
1504
;
1505
1506
1507
/* ------------------------------------------------------------------------ */
1508
/*
1509
 *   A "consultable" object.  This is an inanimate object that can be
1510
 *   consulted about various topics, almost the way an actor can be asked
1511
 *   about topics.  Examples include individual objects that contain
1512
 *   voluminous information, such as books, phone directories, and maps, as
1513
 *   well as collections of individual information-carrying objects, such
1514
 *   as file cabinets or bookcases.
1515
 *   
1516
 *   A consultable keeps a database of TopicEntry objects; this works in
1517
 *   much the same way as the topic database system that actors use.
1518
 *   Create one or more ConsultTopic objects and place them inside the
1519
 *   Consultable (using the 'location' property, or using the '+' syntax).
1520
 *   When an actor consults the object about a topic, we'll search our
1521
 *   database for a ConsultTopic object that matches the topic and is
1522
 *   currently active, and show the response for the best one we can find.
1523
 *   
1524
 *   From an IF design perspective, consultables have two nice properties.
1525
 *   
1526
 *   First, they hide the boundaries of implementation, by letting the game
1527
 *   *suggest* that there's an untold wealth of information in a particular
1528
 *   book (or whatever) without the need to actually implement all of it.
1529
 *   We only have to show the entries the player specifically asks for, so
1530
 *   the game never has to admit when it's run out of things to show, and
1531
 *   the player can never know for sure that there's not more to find.  Be
1532
 *   careful, though, because this is a double-edge sword, design-wise;
1533
 *   it's easy to abuse this property to hide information gratuitously from
1534
 *   the player.
1535
 *   
1536
 *   Second, consultables help "match impedances" between the narrative
1537
 *   level of detail and the underlying world model.  At the narrative
1538
 *   level, we paint in fairly broad strokes: when we visit a new location,
1539
 *   we describe the *important* features of the setting, not every last
1540
 *   detail.  If the player wants to examine something in closer detail, we
1541
 *   zoom in on that detail, assuming we've implemented it, but it's up to
1542
 *   the player to determine where the attention is focused.  Consultable
1543
 *   objects give us the same capability for books and the like.  With a
1544
 *   consultable, we can describe the way a book looks without immediately
1545
 *   dumping the literal contents of the book onto the screen; but when the
1546
 *   player chooses some aspect of the book to read in detail, we can zoom
1547
 *   in on that page or chapter and show that literal content, if we
1548
 *   choose.
1549
 *   
1550
 *   Also, note that we assume that consultables convey their information
1551
 *   through visual information, such as printed text or a display screen.
1552
 *   Because of this, we by default require that the object be visible to
1553
 *   be consulted.  This might not be appropriate in some cases, such as
1554
 *   Braille books or talking PDA's; to remove the visual condition,
1555
 *   override the pre-condition for the Consult action.  
1556
 */
1557
class Consultable: Thing, TopicDatabase
1558
    /* 
1559
     *   If they consult us without a topic, just ask for a topic.  Treat
1560
     *   it as logical, but rank it as improbable, in case there's
1561
     *   anything else around that can be consulted without any topic
1562
     *   specified.  
1563
     */
1564
    dobjFor(Consult)
1565
    {
1566
        preCond = [touchObj, objVisible]
1567
        verify() { logicalRank(50, 'need a topic'); }
1568
        action() { askForTopic(ConsultAbout); }
1569
    }
1570
1571
    /* consult about a topic */
1572
    dobjFor(ConsultAbout)
1573
    {
1574
        verify() { }
1575
        action()
1576
        {
1577
            /* remember that we're the last object the actor consulted */
1578
            gActor.noteConsultation(self);
1579
1580
            /* try handling the topic through our topic database */
1581
            if (!handleTopic(gActor, gTopic, consultConvType, nil))
1582
                topicNotFound();
1583
        }
1584
    }
1585
1586
    /* show the default response for a topic we couldn't find */
1587
    topicNotFound()
1588
    {
1589
        /* 
1590
         *   Report the absence of the topic.  Note that we use an
1591
         *   ordinary, successful report, not a failure report, because
1592
         *   the consultation really did succeed in the sense of the
1593
         *   physical action of consulting: we successfully flipped
1594
         *   through the book, scanned the file cabinet, or whatever.  We
1595
         *   didn't find what we were looking for, but in terms of the
1596
         *   physical action undertaken, we successfully did exactly what
1597
         *   we were asked to do.  
1598
         */
1599
        mainReport(&cannotFindTopicMsg);
1600
    }
1601
1602
    /*
1603
     *   Resolve the topic phrase for a CONSULT ABOUT command.  The CONSULT
1604
     *   ABOUT action refers this to the direct object of the action, so
1605
     *   that the direct object can filter the topic match according to
1606
     *   what makes sense for the consultable.
1607
     *   
1608
     *   By default, we resolve the topic phrase a little differently than
1609
     *   we would for conversational commands, such as ASK ABOUT.  By
1610
     *   default, we don't differentiate objects at all based on physical
1611
     *   scope or actor knowledge when deciding on a match for a topic
1612
     *   phrase.  For example, if you create a Consultable representing a
1613
     *   phone book, and the player enters a command like FIND BOB IN PHONE
1614
     *   BOOK, the topic BOB will be found even if the 'bob' object isn't
1615
     *   known to the player character.  The reason for this difference
1616
     *   from ASK ABOUT et al is that consultables are generally the kinds
1617
     *   of objects where, in real life, a person could browse through the
1618
     *   object and come across entries whether or not the person knew
1619
     *   enough to look for them.  For example, you could go through a
1620
     *   phone book and find an entry for "Bob" even if you didn't know
1621
     *   anyone named Bob.
1622
     *   
1623
     *   'lst' is the list of ResolveInfo objects giving the full set of
1624
     *   matches for the vocabulary words; 'np' is the grammar production
1625
     *   object for the topic phrase; and 'resolver' is the TopicResolver
1626
     *   that's resolving the topic phrase.  Note that 'lst' contains
1627
     *   ResolveInfo objects, so to get the game-world object for a given
1628
     *   list entry, use lst[i].obj_.
1629
     *   
1630
     *   We return a ResolvedTopic object that encapsulates the matching
1631
     *   objects.  
1632
     *   
1633
     *   Note that the resolver object can be used to get certain useful
1634
     *   information.  The resolver's getAction() method returns the action
1635
     *   (which you should use instead of gAction, since this routine is
1636
     *   called during the resolution process, not during command
1637
     *   execution); its getTargetActor() method returns the actor
1638
     *   performing the action; and its objInPhysicalScope(obj) method lets
1639
     *   you determine if an object is in physical scope for the actor.  
1640
     */
1641
    resolveConsultTopic(lst, np, resolver)
1642
    {
1643
        /* 
1644
         *   by default, simply return an undifferentiated list with
1645
         *   everything given equal weight, whether known or not, and
1646
         *   whether in scope or not 
1647
         */
1648
        return new ResolvedTopic(lst, [], [], np);
1649
    }
1650
1651
    /* 
1652
     *   Our topic entry database for consultatation topics.  This will be
1653
     *   automatically built during initialization from the set of
1654
     *   ConsultTopic objects located within me, so there's usually no
1655
     *   need to initialize this manually.  
1656
     */
1657
    consultTopics = nil
1658
;
1659
1660
/*
1661
 *   A consultation topic.  You can place one or more of these inside a
1662
 *   Consultable object (using the 'location' property, or the '+'
1663
 *   notation), to create a database of topics that can be looked up in
1664
 *   the consultable.  
1665
 */
1666
class ConsultTopic: TopicMatchTopic
1667
    /* include in the consultation list */
1668
    includeInList = [&consultTopics]
1669
1670
    /* 
1671
     *   don't set any pronouns for the topic - the consultable itself
1672
     *   should be the pronoun antecedent 
1673
     */
1674
    setTopicPronouns(fromActor, obj) { }
1675
;
1676
1677
/* 
1678
 *   A default topic entry for a consultable.  You can include one (or
1679
 *   more) of these in a consultable's database to provide a topic of last
1680
 *   resort that answers to any topics that aren't in the database
1681
 *   themselves.  
1682
 */
1683
class DefaultConsultTopic: DefaultTopic
1684
    includeInList = [&consultTopics]
1685
    setTopicPronouns(fromActor, obj) { }
1686
;
1687
1688
1689
/* ------------------------------------------------------------------------ */
1690
/*
1691
 *   A common, abstract base class for things that cannot be moved.  You
1692
 *   shouldn't use this class to create game objects directly; you should
1693
 *   always use one of the concrete subclasses, such as Fixture or
1694
 *   Immovable.  This base class doesn't provide the full behavior
1695
 *   necessary to make an object immovable; it's just here as a
1696
 *   programming abstraction for the common elements of all immovable
1697
 *   objects.
1698
 *   
1699
 *   This class has two purposes.  First, it defines some behavior common
1700
 *   to all non-portable objects.  Second, you can test an object to see
1701
 *   if it's based on this class to determine whether it's a portable or
1702
 *   unportable type of Thing.  
1703
 */
1704
class NonPortable: Thing
1705
    /* 
1706
     *   An immovable objects is not listed in room or container contents
1707
     *   listings.  Since the object is immovable, it's in effect a
1708
     *   permanent feature of its location, so it should be described as
1709
     *   such: either directly as part of its location's description text,
1710
     *   or via its own specialDesc.  
1711
     */
1712
    isListed = nil
1713
    isListedInContents = nil
1714
    isListedInInventory = nil
1715
1716
    /* 
1717
     *   By default, if the object's contents would be listed in a direct
1718
     *   examination, then also list them when showing an inventory list,
1719
     *   or describing the enclosing room or an enclosing object.  
1720
     */
1721
    contentsListed = (contentsListedInExamine)
1722
1723
    /*
1724
     *   Are my contents within a fixed item that is within the given
1725
     *   location?  Since we're fixed in place, our contents are certainly
1726
     *   within a fixed item, so we merely need to check if we're fixed in
1727
     *   place within the given location.  We are if we're in the given
1728
     *   location or we ourselves are fixed in place in the given location.
1729
     */
1730
    contentsInFixedIn(loc)
1731
    {
1732
        return isDirectlyIn(loc) || isInFixedIn(loc);
1733
    }
1734
1735
    /*
1736
     *   Since non-portables aren't carried, their weight and bulk are
1737
     *   largely irrelevant.  Even so, when a non-portable is a component
1738
     *   of another object, or otherwise contained in another object, its
1739
     *   weight and/or bulk can affect the behavior of the parent object.
1740
     *   So, it's simplest to use a default of zero for these so that there
1741
     *   are no surprises about the parent's behavior.  
1742
     */
1743
    weight = 0
1744
    bulk = 0
1745
1746
    /*
1747
     *   Non-portable objects can't be held, since they can't be carried.
1748
     *   However, in some cases, it's useful to include non-portable
1749
     *   objects within an actor, such as when creating component parts of
1750
     *   an actor (hands, say).  In these cases, the non-portables aren't
1751
     *   held, but rather are components or similar.  
1752
     */
1753
    isHeldBy(actor) { return nil; }
1754
1755
    /*
1756
     *   We're not being held, but if our location is an actor, then we're
1757
     *   as good as held because we're effectively part of the actor.
1758
     */
1759
    meetsObjHeld(actor) { return actor == location; }
1760
1761
    /* 
1762
     *   showing an immovable to someone simply requires that it be in
1763
     *   sight: we're not holding it up to show it, we're simply pointing
1764
     *   it out 
1765
     */
1766
    dobjFor(ShowTo) { preCond = [objVisible] }
1767
1768
    /*
1769
     *   Thing decreases the likelihood that we want to examine an object
1770
     *   when the object isn't being held.  That's fine for portable
1771
     *   objects, but nonportables can never be held, so we don't want that
1772
     *   decrease in logicalness. 
1773
     */
1774
    dobjFor(Examine)
1775
    {
1776
        /* override Thing's likelihood downgrade for un-held items */
1777
        verify() { }
1778
    }
1779
;
1780
1781
1782
/* ------------------------------------------------------------------------ */
1783
/*
1784
 *   A "fixture," which is something that's obviously a part of the room.
1785
 *   These objects cannot be removed from their containers.  This class is
1786
 *   meant for permanent features of rooms that obviously cannot be moved
1787
 *   to a new container, such as walls, floors, doors, built-in bookcases,
1788
 *   light switches, buildings, and the like.
1789
 *   
1790
 *   The important feature of a Fixture is that it's *obvious* that it's
1791
 *   part of its container, so it should be safe to assume that a character
1792
 *   normally wouldn't even try to take it or move it.  For objects that
1793
 *   might appear portable but turn out to be immovable, other classes are
1794
 *   more appropriate: use Heavy for objects that are immovable simply
1795
 *   because they're very heavy, for example, or Immovable for objects that
1796
 *   are immovable for some non-obvious reason.  
1797
 */
1798
class Fixture: NonPortable
1799
    /* 
1800
     *   Hide fixtures from "all" for certain commands.  Fixtures are
1801
     *   obviously part of the location, so a reaonable person wouldn't
1802
     *   even consider trying to do things like take them or move them.  
1803
     */
1804
    hideFromAll(action)
1805
    {
1806
        return (action.ofKind(TakeAction)
1807
                || action.ofKind(DropAction)
1808
                || action.ofKind(PutInAction)
1809
                || action.ofKind(PutOnAction));
1810
    }
1811
1812
    /* don't hide from defaults, though */
1813
    hideFromDefault(action) { return nil; }
1814
1815
    /* a fixed item can't be moved by an actor action */
1816
    verifyMoveTo(newLoc)
1817
    {
1818
        /* it's never possible to do this */
1819
        illogical(cannotMoveMsg);
1820
    }
1821
1822
    /* 
1823
     *   a fixed item can't be taken - this would be caught by
1824
     *   verifyMoveTo anyway, but provide a more explicit message when a
1825
     *   fixed item is explicitly taken 
1826
     */
1827
    dobjFor(Take) { verify() { illogical(cannotTakeMsg); }}
1828
    dobjFor(TakeFrom) { verify() { illogical(cannotTakeMsg); }}
1829
1830
    /* fixed objects can't be put anywhere */
1831
    dobjFor(PutIn) { verify() { illogical(cannotPutMsg); }}
1832
    dobjFor(PutOn) { verify() { illogical(cannotPutMsg); }}
1833
    dobjFor(PutUnder) { verify() { illogical(cannotPutMsg); }}
1834
    dobjFor(PutBehind) { verify() { illogical(cannotPutMsg); }}
1835
1836
    /* fixed objects can't be pushed, pulled, or moved */
1837
    dobjFor(Push) { verify() { illogical(cannotMoveMsg); }}
1838
    dobjFor(Pull) { verify() { illogical(cannotMoveMsg); }}
1839
    dobjFor(Move) { verify() { illogical(cannotMoveMsg); }}
1840
    dobjFor(MoveWith) { verify() { illogical(cannotMoveMsg); }}
1841
    dobjFor(MoveTo) { verify() { illogical(cannotMoveMsg); }}
1842
    dobjFor(PushTravel) { verify() { illogical(cannotMoveMsg); }}
1843
    dobjFor(ThrowAt) { verify() { illogical(cannotMoveMsg); }}
1844
    dobjFor(ThrowDir) { verify() { illogical(cannotMoveMsg); }}
1845
1846
    /*
1847
     *   The messages to use for illogical messages.  These can be
1848
     *   overridden with new properties (of playerActionMessages and the
1849
     *   like), or simply with single-quoted strings to display.  
1850
     */
1851
    cannotTakeMsg = &cannotTakeFixtureMsg
1852
    cannotMoveMsg = &cannotMoveFixtureMsg
1853
    cannotPutMsg = &cannotPutFixtureMsg
1854
1855
    /*
1856
     *   A component can be said to be owned by its location's owner or by
1857
     *   its location. 
1858
     */
1859
    isOwnedBy(obj)
1860
    {
1861
        /* 
1862
         *   if I'm owned by the object under the normal rules, then we
1863
         *   won't say otherwise 
1864
         */
1865
        if (inherited(obj))
1866
            return true;
1867
1868
        /* 
1869
         *   we can be said to be owned by our location, since we're a
1870
         *   direct and permanent part of the location 
1871
         */
1872
        if (obj == location)
1873
            return true;
1874
1875
        /* 
1876
         *   if my location is owned by the given object, consider
1877
         *   ourselves owned by it as well, as we're an extension of our
1878
         *   location 
1879
         */
1880
        if (location != nil && location.isOwnedBy(obj))
1881
            return true;
1882
1883
        /* we didn't find anything that establishes ownership */
1884
        return nil;
1885
    }
1886
;
1887
1888
/*
1889
 *   A component object.  These objects cannot be removed from their
1890
 *   containers because they are permanent features of other objects, which
1891
 *   may themselves be portable: the hands of a watch, a tuning dial on a
1892
 *   radio.  This class behaves essentially the same way as Fixture, but
1893
 *   its messages are more suitable for objects that are component parts of
1894
 *   other objects rather than fixed features of rooms.  
1895
 */
1896
class Component: Fixture
1897
    /* a component cannot be removed from its container by an actor action */
1898
    verifyMoveTo(newLoc)
1899
    {
1900
        /* it's never possible to do this */
1901
        illogical(&cannotMoveComponentMsg, location);
1902
    }
1903
1904
    /*
1905
     *   Hide components from EXAMINE ALL, as well as any commands hidden
1906
     *   from ALL for ordinary fixtures.  Components are small parts of
1907
     *   larger objects, so when we EXAMINE ALL, it's enough to examine the
1908
     *   larger objects of which we're a part; we don't want components to
1909
     *   show up separately in these cases.  
1910
     */
1911
    hideFromAll(action)
1912
    {
1913
        /* hide from EXAMINE ALL, plus anything the base class hides */
1914
        return (action.ofKind(ExamineAction)
1915
                || inherited(action));
1916
    }
1917
1918
    /* 
1919
     *   We are a component of our direct cotnainer, and we're indirectly a
1920
     *   component of anything that it's a component of.  
1921
     */
1922
    isComponentOf(obj)
1923
    {
1924
        return (obj == location
1925
                || (location != nil && location.isComponentOf(obj)));
1926
    }
1927
1928
    /*
1929
     *   Consider ourself to be held by the given actor if we're a
1930
     *   component of the actor.  
1931
     */
1932
    meetsObjHeld(actor) { return isComponentOf(actor); }
1933
1934
    /* a component cannot be taken separately */
1935
    dobjFor(Take)
1936
        { verify() { illogical(&cannotTakeComponentMsg, location); }}
1937
    dobjFor(TakeFrom)
1938
        { verify() { illogical(&cannotTakeComponentMsg, location); }}
1939
1940
    /* a component cannot be separately put somewhere */
1941
    dobjFor(PutIn)
1942
        { verify() { illogical(&cannotPutComponentMsg, location); }}
1943
    dobjFor(PutOn)
1944
        { verify() { illogical(&cannotPutComponentMsg, location); }}
1945
    dobjFor(PutUnder)
1946
        { verify() { illogical(&cannotPutComponentMsg, location); }}
1947
    dobjFor(PutBehind)
1948
        { verify() { illogical(&cannotPutComponentMsg, location); }}
1949
;
1950
1951
/*
1952
 *   A "secret fixture" is a kind of fixture that we use for internal
1953
 *   implementation purposes, and which we don't intend to be visible to
1954
 *   the player.  Objects of this type usually have no vocabulary, since we
1955
 *   don't want the player to be able to refer to them.  
1956
 */
1957
class SecretFixture: Fixture
1958
    /* 
1959
     *   this kind of object is internal to the game's implementation, so
1960
     *   we don't want it to show up in "all" lists 
1961
     */
1962
    hideFromAll(action) { return true; }
1963
;
1964
1965
/*
1966
 *   A fixture that uses the same custom message for taking, moving, and
1967
 *   putting.  In many cases, it's useful to customize the message for a
1968
 *   fixture, using the same custom message for all sorts of moving.  Just
1969
 *   override cannotTakeMsg, and the other messages will copy it.  
1970
 */
1971
class CustomFixture: Fixture
1972
    cannotMoveMsg = (cannotTakeMsg)
1973
    cannotPutMsg = (cannotTakeMsg)
1974
;
1975
1976
/* ------------------------------------------------------------------------ */
1977
/*
1978
 *   An Immovable is an object that can't be moved, but not because it's
1979
 *   obviously a fixture or component of another object.  This class is
1980
 *   suitable for things like furniture, which are in principle portable
1981
 *   but which actors aren't actually allowed to pick up or move around.
1982
 *   
1983
 *   Note that Immovable is a lot like Fixture.  The difference is that
1984
 *   Fixture is for objects that are *obviously* fixed in place by their
1985
 *   very nature, whereas Immovable is for objects that common sense would
1986
 *   tell us are portable, but which the game doesn't in fact allow the
1987
 *   player to move.
1988
 *   
1989
 *   The practical difference between Immovable and Fixture is that Fixture
1990
 *   considers taking or moving to be illogical actions, whereas Immovable
1991
 *   considers these actions logical but simply doesn't allow them.  To be
1992
 *   more specific, Fixture disallows taking and moving in the verify()
1993
 *   methods for those actions, while Immovable disallows the actions in
1994
 *   the check() methods.  This means, for example, that Fixture objects
1995
 *   will be removed from consideration during the noun resolution phase
1996
 *   when there are more logical choices.
1997
 */
1998
class Immovable: NonPortable
1999
    /* an Immovable can't be taken */
2000
    dobjFor(Take) { check() { failCheck(cannotTakeMsg); }}
2001
2002
    /* Immovables can't be put anywhere */
2003
    dobjFor(PutIn) { check() { failCheck(cannotPutMsg); }}
2004
    dobjFor(PutOn) { check() { failCheck(cannotPutMsg); }}
2005
    dobjFor(PutUnder) { check() { failCheck(cannotPutMsg); }}
2006
    dobjFor(PutBehind) { check() { failCheck(cannotPutMsg); }}
2007
2008
    /* Immovables can't be pushed, pulled, or otherwise moved */
2009
    dobjFor(Drop) { action() { reportFailure(cannotMoveMsg); }}
2010
    dobjFor(Push) { action() { reportFailure(cannotMoveMsg); }}
2011
    dobjFor(Pull) { action() { reportFailure(cannotMoveMsg); }}
2012
    dobjFor(Move) { action() { reportFailure(cannotMoveMsg); }}
2013
    dobjFor(MoveWith) { check() { failCheck(cannotMoveMsg); }}
2014
    dobjFor(MoveTo) { check() { failCheck(cannotMoveMsg); }}
2015
    dobjFor(PushTravel) { action() { reportFailure(cannotMoveMsg); }}
2016
    dobjFor(ThrowAt) { verify() { illogical(cannotMoveMsg); }}
2017
    dobjFor(ThrowDir) { verify() { illogical(cannotMoveMsg); }}
2018
    dobjFor(Turn)
2019
    {
2020
        verify() { logicalRank(50, 'turn heavy'); }
2021
        action() { reportFailure(cannotMoveMsg); }
2022
    }
2023
2024
    /*
2025
     *   The messages to use for the failure messages.  These can be
2026
     *   overridden with new properties (of playerActionMessages and the
2027
     *   like), or simply with single-quoted strings to display.  
2028
     */
2029
    cannotTakeMsg = &cannotTakeImmovableMsg
2030
    cannotMoveMsg = &cannotMoveImmovableMsg
2031
    cannotPutMsg = &cannotPutImmovableMsg
2032
;
2033
2034
/*
2035
 *   An immovable that uses the same custom message for taking, moving, and
2036
 *   putting.  In many cases, it's useful to customize the message for an
2037
 *   immovable, using the same custom message for all sorts of moving.
2038
 *   Just override cannotTakeMsg, and the other messages will copy it. 
2039
 */
2040
class CustomImmovable: Immovable
2041
    cannotMoveMsg = (cannotTakeMsg)
2042
    cannotPutMsg = (cannotTakeMsg)
2043
;
2044
2045
/*
2046
 *   Heavy: an object that's immovable because it's very heavy.  This is
2047
 *   suitable for things like large boulders, heavy furniture, or the like:
2048
 *   things that aren't nailed down, but nonetheless are too heavy to be
2049
 *   carried or otherwise move.
2050
 *   
2051
 *   This is a simple specialization of Immovable; the only thing we change
2052
 *   is the messages we use to describe why the object can't be moved.  
2053
 */
2054
class Heavy: Immovable
2055
    cannotTakeMsg = &cannotTakeHeavyMsg
2056
    cannotMoveMsg = &cannotMoveHeavyMsg
2057
    cannotPutMsg = &cannotPutHeavyMsg
2058
;
2059
2060
2061
/* ------------------------------------------------------------------------ */
2062
/*
2063
 *   Decoration.  This is an object that is included for scenery value but
2064
 *   which has no other purpose, and which the author wants to make clear
2065
 *   is not important.  We use the catch-all action routine to respond to
2066
 *   any command on this object with a flat "that's not important"
2067
 *   message, so that the player can plainly see that there's no point
2068
 *   wasting any time trying to manipulate this object.
2069
 *   
2070
 *   We use the "default" catch-all verb verify handling to report our
2071
 *   "that's not important" message, so a decoration can be made
2072
 *   responsive to specific verbs simply by defining an action handler for
2073
 *   those verbs.  
2074
 */
2075
class Decoration: Fixture
2076
    /* don't include decorations in 'all' */
2077
    hideFromAll(action) { return true; }
2078
2079
    /* don't hide from defaults */
2080
    hideFromDefault(action) { return nil; }
2081
2082
    /* 
2083
     *   use the default response "this object isn't important" when we're
2084
     *   used as either a direct or indirect object 
2085
     */
2086
    dobjFor(Default)
2087
    {
2088
        verify() { illogical(notImportantMsg, self); }
2089
    }
2090
    iobjFor(Default)
2091
    {
2092
        verify() { illogical(notImportantMsg, self); }
2093
    }
2094
2095
    /* use the standard not-important message for decorations */
2096
    notImportantMsg = &decorationNotImportantMsg
2097
2098
    /*
2099
     *   The catch-all Default verifier makes all actions illogical, but we
2100
     *   can override this to allow specific actions by explicitly defining
2101
     *   them here so that they hide the Default verify handlers.  In
2102
     *   addition, give decorations a reduced logical rank, so that any
2103
     *   in-scope non-decoration object with similar vocabulary will be
2104
     *   matched for an Examine command ahead of a decoration.  
2105
     */
2106
    dobjFor(Examine)
2107
        { verify() { inherited(); logicalRank(70, 'decoration'); } }
2108
2109
    /* 
2110
     *   likewise for LISTEN TO and SMELL, which are the auditory and
2111
     *   olfactory equivalents of EXAMINE 
2112
     */
2113
    dobjFor(ListenTo)
2114
        { verify() { inherited(); logicalRank(70, 'decoration'); } }
2115
    dobjFor(Smell)
2116
        { verify() { inherited(); logicalRank(70, 'decoration'); } }
2117
2118
    /* likewise for READ */
2119
    dobjFor(Read)
2120
        { verify() { inherited(); logicalRank(70, 'decoration'); } }
2121
2122
    /* likewise for LOOK IN and SEARCH */
2123
    dobjFor(LookIn)
2124
        { verify() { inherited(); logicalRank(70, 'decoration'); } }
2125
    dobjFor(Search)
2126
        { verify() { inherited(); logicalRank(70, 'decoration'); } }
2127
2128
    /* the default LOOK IN response is our standard "that's not important" */
2129
    lookInDesc { mainReport(&notImportantMsg, self); }
2130
;
2131
2132
/* ------------------------------------------------------------------------ */
2133
/*
2134
 *   An "unthing" is an object that represents the *absence* of an object.
2135
 *   It's occasionally useful to respond specially when the player mentions
2136
 *   an object that isn't present, especially when the player is likely to
2137
 *   assume that something is present.
2138
 *   
2139
 *   An unthing is essentially a decoration, but we use a customized
2140
 *   message that says "that isn't here" rather than "that isn't
2141
 *   important".  
2142
 */
2143
class Unthing: Decoration
2144
    /* 
2145
     *   The message to display when the player refers to this object.
2146
     *   This can be a library message property, or a single-quoted string.
2147
     *   This message will probably always be overridden in practice, since
2148
     *   the point of this class is to provide a more specific explanation
2149
     *   of why the object isn't here.  
2150
     */
2151
    notHereMsg = &unthingNotHereMsg
2152
2153
    /* an Unthing shouldn't be picked as a default */
2154
    hideFromDefault(action) { return true; }
2155
2156
    /* 
2157
     *   by default, use our 'not here' message for our descriptions (in
2158
     *   all of the standard senses) 
2159
     */
2160
    basicExamine() { mainReport(notHereMsg, self); }
2161
    basicExamineListen(explicit)
2162
    {
2163
        if (explicit)
2164
            mainReport(notHereMsg, self);
2165
    }
2166
    basicExamineSmell(explicit)
2167
    {
2168
        if (explicit)
2169
            mainReport(notHereMsg, self);
2170
    }
2171
2172
    /* use our custom message for the inherited Decoration responses */
2173
    notImportantMsg = (notHereMsg)
2174
2175
    /*
2176
     *   Because we're not actually here, use custom error messages when
2177
     *   we're used as a possessive or locational qualifier.  The standard
2178
     *   messages say things like "Bob doesn't appear to have that" or "You
2179
     *   don't see that in the box," but these don't make sense for an
2180
     *   Unthing - we're not actually here, so we can't "appear" or "seem"
2181
     *   to own or contain anything.  Instead, we need to indicate that the
2182
     *   qualifying object itself (i.e., 'self') isn't here at all.  
2183
     */
2184
    throwNoMatchForPossessive(txt) { throwUnthingAsQualifier(); }
2185
    throwNoMatchForLocation(txt) { throwUnthingAsQualifier(); }
2186
    throwNothingInLocation() { throwUnthingAsQualifier(); }
2187
2188
    /* 
2189
     *   throw a generic message when we're used as a qualifier - we'll
2190
     *   simply get our "not here" message and display that 
2191
     */
2192
    throwUnthingAsQualifier()
2193
    {
2194
        local msg;
2195
        
2196
        /* 
2197
         *   resolve our "not here" message to a string - we need to do
2198
         *   this here, since we're too early in the parsing sequence for
2199
         *   the normal "mainResult" type of processing 
2200
         */
2201
        msg = MessageResult.resolveMessageText([self], &notHereMsg, [self]);
2202
2203
        /* throw a parser exception that will display this literal text */
2204
        throw new ParseFailureException(&parserErrorString, msg);
2205
    }
2206
2207
    /* 
2208
     *   if there's anything at all in a resolve list other than me, always
2209
     *   remove me 
2210
     */
2211
    filterResolveList(lst, action, whichObj, np, requiredNum)
2212
    {
2213
        /* if the list has anything else in it, remove myself */
2214
        if (lst.length() != 1)
2215
            lst = lst.removeElementAt(lst.indexWhich({x: x.obj_ == self}));
2216
2217
        /* return the list */
2218
        return lst;
2219
    }
2220
2221
    /* 
2222
     *   trying to given an order to an Unthing acts the same way as any
2223
     *   other kind of interaction 
2224
     */
2225
    acceptCommand(issuingActor) { mainReport(notHereMsg, self); }
2226
;
2227
    
2228
2229
/* ------------------------------------------------------------------------ */
2230
/*
2231
 *   Distant item.  This is an object that's too far away to manipulate,
2232
 *   but can be seen.  This is useful for scenery objects that are at a
2233
 *   great distance within a large location.
2234
 *   
2235
 *   A Distant item is essentially just like a decoration, but the default
2236
 *   message is different.  Note that this class is based on Fixture, which
2237
 *   means that it should be *obvious* that the object is too far away to
2238
 *   take or move.  
2239
 */
2240
class Distant: Fixture
2241
    /* don't include in 'all' */
2242
    hideFromAll(action) { return true; }
2243
2244
    dobjFor(Default)
2245
    {
2246
        verify() { illogical(&tooDistantMsg, self); }
2247
    }
2248
    iobjFor(Default)
2249
    {
2250
        verify() { illogical(&tooDistantMsg, self); }
2251
    }
2252
2253
    /* 
2254
     *   Explicitly allow examining and listening to a Distant item.  To
2255
     *   do this, override the 'verify' methods explicitly; we only need
2256
     *   to inherit the base class handling, but we need to explicitly do
2257
     *   so to 'override' the catch-all default handlers. 
2258
     */
2259
    dobjFor(Examine) { verify { inherited() ; } }
2260
    dobjFor(ListenTo) { verify() { inherited(); } }
2261
2262
    /* similarly, allow showing a distant item */
2263
    dobjFor(ShowTo) { verify() { inherited(); } }
2264
;
2265
2266
/*
2267
 *   Out Of Reach - this is a special mix-in that can be used to create an
2268
 *   object that places its *contents* out of reach under customizable
2269
 *   conditions, and can optionally place itself out of reach as well.
2270
 */
2271
class OutOfReach: object
2272
    checkTouchViaPath(obj, dest, op)
2273
    {
2274
        /* check how we're traversing the object */
2275
        if (op == PathTo)
2276
        {
2277
            /* 
2278
             *   we're reaching from outside for this object itself -
2279
             *   check to see if the source can reach me
2280
             */
2281
            if (!canObjReachSelf(obj))
2282
                return new CheckStatusFailure(
2283
                    cannotReachFromOutsideMsg(dest), dest);
2284
        }
2285
        else if (op == PathIn)
2286
        {
2287
            /* 
2288
             *   we're reaching in to touch one of my contents - check to
2289
             *   see if the source object is within reach of my contents 
2290
             */
2291
            if (!canObjReachContents(obj))
2292
                return new CheckStatusFailure(
2293
                    cannotReachFromOutsideMsg(dest), dest);
2294
        }
2295
        else if (op == PathOut)
2296
        {
2297
            local ok;
2298
            
2299
            /*
2300
             *   We're reaching out.  If we're reaching for the object
2301
             *   itself, check to see if we're reachable from within;
2302
             *   otherwise, check to see if we can reach objects outside
2303
             *   us from within.  
2304
             */
2305
            if (dest == self)
2306
                ok = canReachSelfFromInside(obj);
2307
            else
2308
                ok = canReachFromInside(obj, dest);
2309
2310
            /* if we can't reach the object, say so */
2311
            if (!ok)
2312
                return new CheckStatusFailure(
2313
                    cannotReachFromInsideMsg(dest), dest);
2314
        }
2315
        
2316
        /* if we didn't find a problem, allow the operation */
2317
        return checkStatusSuccess;
2318
    }
2319
2320
    /* 
2321
     *   The message to use to indicate that we can't reach an object,
2322
     *   because the actor is outside me and the target is inside, or vice
2323
     *   versa.  Each of these can return a property ID giving an actor
2324
     *   action message property, or can simply return a string with the
2325
     *   message text.  
2326
     */
2327
    cannotReachFromOutsideMsg(dest) { return &tooDistantMsg; }
2328
    cannotReachFromInsideMsg(dest) { return  &tooDistantMsg; }
2329
2330
    /*
2331
     *   Determine if the given object can reach my contents.  'obj' is
2332
     *   the object (usually an actor) attempting to reach my contents
2333
     *   from outside of me.
2334
     *   
2335
     *   By default, we'll return nil, so that nothing within me can be
2336
     *   reached from anyone outside.  This can be overridden to allow my
2337
     *   contents to become reachable from some external locations but not
2338
     *   others; for example, a high shelf could allow an actor standing
2339
     *   on a chair to reach my contents. 
2340
     */
2341
    canObjReachContents(obj) { return nil; }
2342
2343
    /*
2344
     *   Determine if the given object can reach me.  'obj' is the object
2345
     *   (usually an actor) attempting to reach this object.
2346
     *   
2347
     *   By default, make this object subject to the same rules as its
2348
     *   contents.  
2349
     */
2350
    canObjReachSelf(obj) { return canObjReachContents(obj); }
2351
2352
    /*
2353
     *   Determine if the given object outside of me is reachable from
2354
     *   within me.  'obj' (usually an actor) is attempting to reach
2355
     *   'dest'.
2356
     *   
2357
     *   By default, we return nil, so nothing outside of me is reachable
2358
     *   from within me.  This can be overridden as needed.  This should
2359
     *   usually behave symmetrically with canObjReachContents().  
2360
     */
2361
    canReachFromInside(obj, dest) { return nil; }
2362
2363
    /* 
2364
     *   Determine if we can reach this object itself from within.  This
2365
     *   is used when 'obj' tries to touch this object when 'obj' is
2366
     *   located within this object.
2367
     *   
2368
     *   By default, we we use the same rules as we use to reach an
2369
     *   external object from within.  
2370
     */
2371
    canReachSelfFromInside(obj) { return canReachFromInside(obj, self); }
2372
2373
    /*
2374
     *   We cannot implicitly remove this obstruction, so simply return
2375
     *   nil when asked.  
2376
     */
2377
    tryImplicitRemoveObstructor(sense, obj) { return nil; }
2378
;
2379
2380
/* ------------------------------------------------------------------------ */
2381
/*
2382
 *   A Fill Medium - this is the class of object returned from
2383
 *   Thing.fillMedium().  
2384
 */
2385
class FillMedium: Thing
2386
    /*
2387
     *   Get the transparency sensing through this medium. 
2388
     */
2389
    senseThru(sense)
2390
    {
2391
        /* 
2392
         *   if I have a meterial, use its transparency; otherwise, we're
2393
         *   transparent 
2394
         */
2395
        return (material != nil ? material.senseThru(sense) : transparent);
2396
    }
2397
2398
    /* my material */
2399
    material = nil
2400
;
2401
2402
/* ------------------------------------------------------------------------ */
2403
/*
2404
 *   Base multi-location item with automatic initialization.  This is the
2405
 *   base class for various multi-located object classes.
2406
 *   
2407
 *   We provide four ways of initializing a multi-located object's set of
2408
 *   locations.
2409
 *   
2410
 *   First, the object can simply enumerate the locations explicitly, by
2411
 *   setting the 'locationList' property to the list of locations.
2412
 *   
2413
 *   Second, the object can indicate that it's located in every object of a
2414
 *   given class, by setting the 'initialLocationClass' property to the
2415
 *   desired class.
2416
 *   
2417
 *   Third, the object can define a rule that specifies which objects are
2418
 *   its initial locations, by defining the 'isInitiallyIn(obj)' method to
2419
 *   return true if 'obj' is an initial location, nil if not.  This can be
2420
 *   combined with the 'initialLocationClass' mechanism: if
2421
 *   'initialLocationClass' is non-nil, then only objects of the given
2422
 *   class will be tested with 'isInitiallyIn()'; if 'initialLocationClass'
2423
 *   is nil, then every object in the entire game will be tested.
2424
 *   
2425
 *   Fourth, you can override the method buildLocationList() to build an
2426
 *   return the initial list of locations.  You can use this approach if
2427
 *   you have a complex set of rules for determining the initial location
2428
 *   list, and none of the above approaches are flexible enough to
2429
 *   implement it.  If you override buildLocationList(), simply compute and
2430
 *   return the list of initial locations; the library will automatically
2431
 *   call the method during pre-initialization.
2432
 *   
2433
 *   If you don't define any of these, then the object simply has no
2434
 *   initial locations by default.  
2435
 */
2436
class BaseMultiLoc: object
2437
    /* 
2438
     *   The location list.  Instances can override this to manually
2439
     *   enumerate our initial locations.  By default, we'll call
2440
     *   buildLocationList() the first time this is invoked, and store the
2441
     *   result. 
2442
     */
2443
    locationList = perInstance(buildLocationList())
2444
2445
    /*
2446
     *   The class of our initial locations.  If this is nil, then our
2447
     *   default buildLocationList() method will test every object in the
2448
     *   entire game with our isInitiallyIn() method; otherwise, we'll test
2449
     *   only objects of the given class.  
2450
     */
2451
    initialLocationClass = nil
2452
2453
    /*
2454
     *   Test an object for inclusion in our initial location list.  By
2455
     *   default, we'll simply return true to include every object.  We
2456
     *   return true by default so that an instance can merely specify a
2457
     *   value for initialLocationClass in order to place this object in
2458
     *   every instance of the given class.
2459
     */
2460
    isInitiallyIn(obj) { return true; }
2461
2462
    /*
2463
     *   Build my list of locations, and return the list.  This default
2464
     *   implementation looks for an 'initialLocationClass' property value,
2465
     *   and if one is found, looks at every object of that class;
2466
     *   otherwise, it looks at every object in the entire game.  In either
2467
     *   case, each object is then passed to our isInitiallyIn() method,
2468
     *   and is included in our result list if isInitiallyIn() returns
2469
     *   true.  
2470
     */
2471
    buildLocationList()
2472
    {
2473
        local lst;
2474
2475
        /*
2476
         *   If the object doesn't define any of the standard rules, which
2477
         *   it would do by overriding initialLocationClass and/or
2478
         *   isInitiallyIn(), then simply return an empty list.  We take
2479
         *   the absence of overrides for any of the rules to mean that the
2480
         *   object simply has no initial locations.  
2481
         */
2482
        if (initialLocationClass == nil
2483
            && !overrides(self, BaseMultiLoc, &isInitiallyIn))
2484
            return [];
2485
2486
        /* we have nothing in our list yet */
2487
        lst = new Vector(16);
2488
2489
        /*
2490
         *   if initialLocationClass is defined, loop over all objects of
2491
         *   that class; otherwise, loop over all objects
2492
         */
2493
        if (initialLocationClass != nil)
2494
        {
2495
            /* loop over all instances of the given class */
2496
            for (local obj = firstObj(initialLocationClass) ; obj != nil ;
2497
                 obj = nextObj(obj, initialLocationClass))
2498
            {
2499
                /* if the object passes the test, include it */
2500
                if (isInitiallyIn(obj))
2501
                    lst.append(obj);
2502
            }
2503
        }
2504
        else
2505
        {
2506
            /* loop over all objects */
2507
            for (local obj = firstObj() ; obj != nil ; obj = nextObj(obj))
2508
            {
2509
                /* if the object passes the test, include it */
2510
                if (isInitiallyIn(obj))
2511
                    lst.append(obj);
2512
            }
2513
        }
2514
2515
        /* return the list of locations */
2516
        return lst.toList();
2517
    }
2518
2519
    /* determine if I'm in a given object, directly or indirectly */
2520
    isIn(obj)
2521
    {
2522
        /* first, check to see if I'm directly in the given object */
2523
        if (isDirectlyIn(obj))
2524
            return true;
2525
2526
        /*
2527
         *   Look at each object in my location list.  For each location
2528
         *   object, if the location is within the object, I'm within the
2529
         *   object.
2530
         */
2531
        return locationList.indexWhich({loc: loc.isIn(obj)}) != nil;
2532
    }
2533
2534
    /* determine if I'm directly in the given object */
2535
    isDirectlyIn(obj)
2536
    {
2537
        /*
2538
         *   we're directly in the given object only if the object is in
2539
         *   my list of immediate locations
2540
         */
2541
        return (locationList.indexOf(obj) != nil);
2542
    }
2543
2544
    /* Am I either inside 'obj', or equal to 'obj'?  */
2545
    isOrIsIn(obj) { return self == obj || isIn(obj); }
2546
;
2547
2548
/* ------------------------------------------------------------------------ */
2549
/*
2550
 *   MultiLoc: this class can be multiply inherited by any object that
2551
 *   must exist in more than one place at a time.  To use this class, put
2552
 *   it BEFORE Thing (or any subclass of Thing) in the object's superclass
2553
 *   list, to ensure that we override the default containment
2554
 *   implementation for the object.
2555
 *   
2556
 *   Note that a MultiLoc object appears *in its entirety* in each of its
2557
 *   locations.  This means that MultiLoc is most suitable for a couple of
2558
 *   specific situations:
2559
 *   
2560
 *   - several locations overlap slightly so that they include a common
2561
 *   object: a large statue at the center of a public square, for example;
2562
 *   
2563
 *   - an object forms a sense connection among its location: a window;
2564
 *   
2565
 *   - a distant object that is seen in its entirety from several
2566
 *   locations: the moon, say, or a mountain range.
2567
 *   
2568
 *   Note that MultiLoc is NOT suitable for cases where an object spans
2569
 *   several locations but isn't contained entirely in any one of them:
2570
 *   it's not good for something like a rope or a river, for example.
2571
 *   MultiLoc also isn't good for cases where you simply want to avoid
2572
 *   creating a bunch of repeated decorations in different locations.
2573
 *   MultiLoc isn't good for these cases because a MultiLoc is treated as
2574
 *   though it exists ENTIRELY and SIMULTANEOUSLY in all of its locations,
2575
 *   which means that all of its sense information and internal state is
2576
 *   shared among all of its locations.
2577
 *   
2578
 *   MultiInstance is better than MultiLoc for cases where you want to
2579
 *   share a decoration object across several locations.  MultiInstance is
2580
 *   better because it creates individual copies of the object in the
2581
 *   different locations, so each copy has its own separate sense
2582
 *   information and its own separate identity.
2583
 *   
2584
 *   MultiFaceted is better for objects that span several locations, such
2585
 *   as a river or a long rope.  Like MultiInstance, MultiFaceted creates
2586
 *   a separate copy in each location; in addition, MultiFaceted relates
2587
 *   the copies together as "facets" of the same object, so that the
2588
 *   parser knows they're all actually parts of one larger object.  
2589
 */
2590
class MultiLoc: BaseMultiLoc
2591
    /*
2592
     *   Initialize my location's contents list - add myself to my
2593
     *   container during initialization
2594
     */
2595
    initializeLocation()
2596
    {
2597
        /* add myself to each of my container's contents lists */
2598
        locationList.forEach({loc: loc.addToContents(self)});
2599
    }
2600
2601
    /*
2602
     *   Re-initialize the location list.  This calls buildLocationList()
2603
     *   to re-evaluate the location rules, then updates the locationList
2604
     *   to match the new results.  We'll remove the MultiLoc from any old
2605
     *   locations that are no longer part of the location list, and we'll
2606
     *   add it to any new locations that weren't previously in the
2607
     *   location list.  You can call this at any time to update the
2608
     *   MutliLoc's presence to reflect applying our location rules to the
2609
     *   current game state.
2610
     *   
2611
     *   Note that this doesn't trigger any moveInto notifications.  This
2612
     *   routine is a re-initialization rather than an in-game action, so
2613
     *   it's not meant to behave as though an actor in the game were
2614
     *   walking around moving the MultiLoc around; thus no notifications
2615
     *   are sent.  Note also that we attempt to minimize our work by
2616
     *   computing the "delta" from the old state - hence we only move the
2617
     *   MultiLoc into containers it wasn't in previously, and we only
2618
     *   remove it from existing containers that it's no longer in.  
2619
     */
2620
    reInitializeLocation()
2621
    {
2622
        local newList;
2623
2624
        /* build the new location list */
2625
        newList = buildLocationList();
2626
2627
        /*
2628
         *   Update any containers that are not in the intersection of the
2629
         *   two lists.  Note that we don't simply move ourselves out of
2630
         *   the old list and into the new list, because the two lists
2631
         *   could have common members; to avoid unnecessary work that
2632
         *   might result from removing ourselves from a container and
2633
         *   then adding ourselves right back in to the same container, we
2634
         *   only notify containers when we're actually moving out or
2635
         *   moving in. 
2636
         */
2637
2638
        /* 
2639
         *   For each item in the old list, if it's not in the new list,
2640
         *   notify the old container that we're being removed.
2641
         */
2642
        foreach (local loc in locationList)
2643
        {
2644
            /* if it's not in the new list, remove me from the container */
2645
            if (newList.indexOf(loc) == nil)
2646
                loc.removeFromContents(self);
2647
        }
2648
2649
        /* 
2650
         *   for each item in the new list, if we weren't already in this
2651
         *   location, add ourselves to the location 
2652
         */
2653
        foreach (local loc in newList)
2654
        {
2655
            /* if it's not in the old list, add me to the new container */
2656
            if (!isDirectlyIn(loc) == nil)
2657
                loc.addToContents(self);
2658
        }
2659
        
2660
        /* make the new location list current */
2661
        locationList = newList;
2662
    }
2663
2664
    /*
2665
     *   Note that we don't need to override any of the contents
2666
     *   management methods, since we provide special handling for our
2667
     *   location relationships, not for our contents relationships.
2668
     */
2669
2670
    /* save my location for later restoration */
2671
    saveLocation()
2672
    {
2673
        /* return my list of locations */
2674
        return locationList;
2675
    }
2676
2677
    /* restore a previously saved location */
2678
    restoreLocation(oldLoc)
2679
    {
2680
        /* remove myself from each current location not in the saved list */
2681
        foreach (local cur in locationList)
2682
        {
2683
            /* 
2684
             *   if this present location isn't in the saved list, remove
2685
             *   myself from the location 
2686
             */
2687
            if (oldLoc.indexOf(cur) == nil)
2688
                cur.removeFromContents(self);
2689
        }
2690
2691
        /* add myself to each saved location not in the current list */
2692
        foreach (local cur in oldLoc)
2693
        {
2694
            /* if I'm not already in this location, add me to it */
2695
            if (locationList.indexOf(cur) == nil)
2696
                cur.addToContents(self);
2697
        }
2698
2699
        /* set my own list to the original list */
2700
        locationList = oldLoc;
2701
    }
2702
2703
    /*
2704
     *   Basic routine to move this object into a given single container.
2705
     *   Removes the object from all of its other containers.  Performs no
2706
     *   notifications.  
2707
     */
2708
    baseMoveInto(newContainer)
2709
    {
2710
        /* remove myself from all of my current contents */
2711
        locationList.forEach({loc: loc.removeFromContents(self)});
2712
2713
        /* set my location list to include only the new location */
2714
        if (newContainer != nil)
2715
        {
2716
            /* set my new location */
2717
            locationList = [newContainer];
2718
2719
            /* add myself to my new container's contents */
2720
            newContainer.addToContents(self);
2721
        }
2722
        else
2723
        {
2724
            /* we have no new locations */
2725
            locationList = [];
2726
        }
2727
    }
2728
2729
    /*
2730
     *   Add this object to a new location - base version that performs no
2731
     *   notifications.  
2732
     */
2733
    baseMoveIntoAdd(newContainer)
2734
    {
2735
        /* add the new container to my list of locations */
2736
        locationList += newContainer;
2737
2738
        /* add myself to my new container's contents */
2739
        newContainer.addToContents(self);
2740
    }
2741
2742
    /*
2743
     *   Add this object to a new location.
2744
     */
2745
    moveIntoAdd(newContainer)
2746
    {
2747
        /* notify my new container that I'm about to be added */
2748
        if (newContainer != nil)
2749
            newContainer.sendNotifyInsert(self, newContainer, &notifyInsert);
2750
2751
        /* perform base move-into-add operation */
2752
        baseMoveIntoAdd(newContainer);
2753
        
2754
        /* note that I've been moved */
2755
        moved = true;
2756
    }
2757
2758
    /*
2759
     *   Base routine to move myself out of a given container.  Performs
2760
     *   no notifications. 
2761
     */
2762
    baseMoveOutOf(cont)
2763
    {
2764
        /* remove myself from this container's contents list */
2765
        cont.removeFromContents(self);
2766
2767
        /* remove this container from my location list */
2768
        locationList -= cont;
2769
    }
2770
2771
    /*
2772
     *   Remove myself from a given container, leaving myself in any other
2773
     *   containers.
2774
     */
2775
    moveOutOf(cont)
2776
    {
2777
        /* if I'm not actually directly in this container, do nothing */
2778
        if (!isDirectlyIn(cont))
2779
            return;
2780
2781
        /* 
2782
         *   notify this container (and only this container) that we're
2783
         *   being removed from it 
2784
         */
2785
        cont.sendNotifyRemove(obj, nil, &notifyRemove);
2786
2787
        /* perform base operation */
2788
        baseMoveOutOf(cont);
2789
2790
        /* note that I've been moved */
2791
        moved = true;
2792
    }
2793
2794
    /*
2795
     *   Call a function on each container.  We'll invoke the function as
2796
     *   follows for each container 'cont':
2797
     *   
2798
     *   (func)(cont, args...)  
2799
     */
2800
    forEachContainer(func, [args])
2801
    {
2802
        /* call the function for each location in our list */
2803
        foreach(local cur in locationList)
2804
            (func)(cur, args...);
2805
    }
2806
2807
    /* 
2808
     *   Call a function on each connected container.  By default, we
2809
     *   don't connect our containers for sense purposes, so we do nothing
2810
     *   here. 
2811
     */
2812
    forEachConnectedContainer(func, ...) { }
2813
2814
    /* 
2815
     *   get a list of my connected containers; by default, we don't
2816
     *   connect our containers, so this is an empty list 
2817
     */
2818
    getConnectedContainers = []
2819
2820
    /* 
2821
     *   Clone this object's contents for inclusion in a MultiInstance's
2822
     *   contents tree.  A MultiLoc is capable of being in multiple places
2823
     *   at once, so we can just use our original contents tree as is.
2824
     */
2825
    cloneMultiInstanceContents(loc) { }
2826
2827
    /*
2828
     *   Create a clone of this object for inclusion in a MultiInstance's
2829
     *   contents tree.  We don't actually need to make a copy of the
2830
     *   object, because a MultiLoc can be in several locations
2831
     *   simultaneously; all we need to do is add ourselves to the new
2832
     *   location. 
2833
     */
2834
    cloneForMultiInstanceContents(loc)
2835
    {
2836
        /* add myself into the new container */
2837
        baseMoveIntoAdd(loc);
2838
    }
2839
2840
    /*
2841
     *   Add the direct containment connections for this item to a lookup
2842
     *   table. 
2843
     *   
2844
     *   A MultiLoc does not, by default, connect its multiple locations
2845
     *   together.  This means that if we're traversing in from a point of
2846
     *   view outside the MultiLoc object, we don't add any of our other
2847
     *   containers to the connection table.  However, the MultiLoc
2848
     *   itself, and its contents, *can* see out to all of its locations;
2849
     *   so if we're traversing from a point of view inside self, we will
2850
     *   add all of our containers to the connection list. 
2851
     */
2852
    addDirectConnections(tab)
2853
    {
2854
        /* add myself */
2855
        tab[self] = true;
2856
2857
        /* add my CollectiveGroup objects */
2858
        foreach (local cur in collectiveGroups)
2859
            tab[cur] = true;
2860
2861
        /* add my contents */
2862
        foreach (local cur in contents)
2863
        {
2864
            if (tab[cur] == nil)
2865
                cur.addDirectConnections(tab);
2866
        }
2867
        
2868
        /* 
2869
         *   If we're traversing from the outside in, don't connect any of
2870
         *   our other containers.  However, if we're traversing from our
2871
         *   own point of view, or from a point of view inside us, we do
2872
         *   get to see out to all of our containers.  
2873
         */
2874
        if (senseTmp.pointOfView == self || senseTmp.pointOfView.isIn(self))
2875
        {
2876
            /* add my locations */
2877
            foreach (local cur in locationList)
2878
            {
2879
                if (tab[cur] == nil)
2880
                    cur.addDirectConnections(tab);
2881
            }
2882
        }
2883
    }
2884
2885
    /*
2886
     *   Transmit ambient energy to my location or locations.  Note that
2887
     *   even though we don't by default shine light from one of our
2888
     *   containers to another, we still shine light from within me to
2889
     *   each of our containers.  
2890
     */
2891
    shineOnLoc(sense, ambient, fill)
2892
    {
2893
        /* shine on each of my containers and their immediate children */
2894
        foreach (local cur in locationList)
2895
            cur.shineFromWithin(self, sense, ambient, fill);
2896
    }
2897
2898
2899
    /*
2900
     *   Build a sense path to my location or locations.  Note that even
2901
     *   though we don't by default connect our different containers
2902
     *   together, we still build a sense path from within to outside,
2903
     *   because we can see from within out to all of our containers.  
2904
     */
2905
    sensePathToLoc(sense, trans, obs, fill)
2906
    {
2907
        /* build a path to each of my containers and their children */
2908
        foreach (local cur in locationList)
2909
            cur.sensePathFromWithin(self, sense, trans, obs, fill);
2910
    }
2911
2912
2913
    /*
2914
     *   Get the drop destination.  The default implementation in Thing
2915
     *   won't work for us, because it delegates to its location to find
2916
     *   the drop destination; we can't do that because we could have
2917
     *   several locations.  To figure out which of our multiple locations
2918
     *   to delegate to, we'll look for 'self' in the supplied sense path;
2919
     *   if we can find it, and the previous path element is a container or
2920
     *   peer of ours, then we'll delegate to that container, because it's
2921
     *   the "side" we approached from.  If there's no path, or if we're
2922
     *   not preceded in the path by a container of ours, we'll arbitrarily
2923
     *   delegate to our first container.
2924
     *   
2925
     *   Note that when we don't have a path, or there's no container of
2926
     *   ours preceding us in the path, the object being dropped must be
2927
     *   starting inside us.  It would be highly unusual for this to happen
2928
     *   with a multi-location object, because MutliLoc isn't designed for
2929
     *   use as a "nested room" or the like.  However, it's not an
2930
     *   impossible situation; if the game does want to create such a
2931
     *   scenario, then the game simply needs to override this routine so
2932
     *   that it does whatever makes sense in the game scenario.  There's
2933
     *   no general way to handle such situations, but it should be
2934
     *   possible to determine the correct handling for specific scenarios.
2935
     */
2936
    getDropDestination(obj, path)
2937
    {
2938
        local idx;
2939
2940
        /* 
2941
         *   if there's no path, get the ordinary "touch" path from the
2942
         *   current actor to us, since this is how the actor would reach
2943
         *   out and touch this object 
2944
         */
2945
        if (path == nil)
2946
            path = gActor.getTouchPathTo(self);
2947
        
2948
        /* 
2949
         *   if there's a path, check to see if we're in it; if so, and
2950
         *   we're not the first element, and the preceding element is a
2951
         *   container or peer of ours, delegate to the preceding element 
2952
         */
2953
        if (path != nil
2954
            && (idx = path.indexOf(self)) != nil
2955
            && idx >= 3
2956
            && path[idx - 1] is in (PathIn, PathPeer))
2957
        {
2958
            /* 
2959
             *   we're preceded in the path by a container or peer of ours,
2960
             *   so we know that we're approaching from that "side" -
2961
             *   delegate to that container, since we're coming from that
2962
             *   direction 
2963
             */
2964
            return path[idx - 2].getDropDestination(obj, path);
2965
        }
2966
2967
        /* 
2968
         *   We either don't have a path, or we're not preceded in the path
2969
         *   by one of our containers or peers, so we don't have any idea
2970
         *   which "side" we're approaching from.  This means we have no
2971
         *   good basis for deciding where the object being dropped will
2972
         *   fall.  Arbitrarily delegate to our first container, if we have
2973
         *   one.  
2974
         */
2975
        return locationList.length() > 0
2976
            ? locationList[1].getDropDestination(obj, path)
2977
            : nil;
2978
    }
2979
;
2980
2981
/* ------------------------------------------------------------------------ */
2982
/*
2983
 *   A "multi-instance" object is a simple way of creating copies of an
2984
 *   object in several places.  This is often useful for decorations and
2985
 *   other features that recur in a whole group of rooms. 
2986
 *   
2987
 *   You define a multi-instance object in two parts.
2988
 *   
2989
 *   First, you define a MultiInstance object, which is just a hollow
2990
 *   shell of an object that sets up the location relationships.  This
2991
 *   shell object doesn't have any presence in the game world; it's just a
2992
 *   programming abstraction.
2993
 *   
2994
 *   Second, as part of the shell object, you define an example of the
2995
 *   object that will actually show up in the game in each of the multiple
2996
 *   locations.  You do this by defining a nested object under the
2997
 *   'instanceObject' property of the shell object.  This is otherwise a
2998
 *   perfectly ordinary object.  In most cases, you'll want to make this a
2999
 *   Decoration, Fixture, or some other non-portable object class, since
3000
 *   the "cloned" nature of these objects means that you usually won't
3001
 *   want them moving around (if they did, you might run into situations
3002
 *   where you had several of them in the same place, leading to
3003
 *   disambiguation headaches for the player).
3004
 *   
3005
 *   Here's an example of how you set up a multi-instance object:
3006
 *   
3007
 *   trees: MultiInstance
3008
 *.    locationList = [forest1, forest2, forest3]
3009
 *.    instanceObject: Fixture { 'tree/trees' 'trees'
3010
 *.      "Many tall, old trees grow here. "
3011
 *.      isPlural = true
3012
 *.    }
3013
 *.  ;
3014
 *   
3015
 *   Note that the instanceObject itself has no location, because it
3016
 *   doesn't appear in the game-world model itself - it's just a template
3017
 *   for the real objects. 
3018
 *   
3019
 *   During initialization, the library will automatically create several
3020
 *   instances (i.e., subclasses) of the example object - one instance per
3021
 *   location, to be exact.  These instances are the real objects that
3022
 *   show up in the game world.
3023
 *   
3024
 *   MultiInstance has one more helpful feature: it lets you dynamically
3025
 *   change the set of locations where the instances appear.  You do this
3026
 *   using the same interface that you use to move around MultiLoc objects
3027
 *   - moveInto(), moveIntoAdd(), moveOutOf().  When you call these
3028
 *   routines on the MultiInstance shell object, it will add and remove
3029
 *   object instances as needed to keep everything consistent.  Thanks to
3030
 *   a little manipulation we do on the instance objects when we set them up,
3031
 *   you can also move the instance objects around directly using
3032
 *   moveInto(), and they'll update the MultiInstance parent to keep its
3033
 *   location list consistent.  
3034
 */
3035
class MultiInstance: BaseMultiLoc
3036
    /* the template object */
3037
    instanceObject = nil
3038
3039
    /* initialize my locations */
3040
    initializeLocation()
3041
    {
3042
        /* create a copy of our template object for each of our locations */
3043
        locationList.forEach({loc: addInstance(loc)});
3044
    }
3045
3046
    /* 
3047
     *   Move the MultiInstance into the given location.  This removes us
3048
     *   from any other existing locations and adds us (if we're not
3049
     *   already there) to the given location.  
3050
     */
3051
    moveInto(loc)
3052
    {
3053
        /* remove all instances that aren't in the new location */
3054
        foreach (local cur in instanceList)
3055
        {
3056
            /* if this instance isn't directly in 'loc', remove it */
3057
            if (!cur.isDirectlyIn(loc))
3058
                cur.moveInto(nil);
3059
        }
3060
3061
        /* 
3062
         *   If I don't have an instance object in the new location, add
3063
         *   one.  Since I've dropped every other instance already, we
3064
         *   either have exactly one location now, which is in the new
3065
         *   location, or we have no locations at all; so we need only
3066
         *   check to see if we have any instances and add one in the new
3067
         *   location if not.  
3068
         */
3069
        if (loc != nil && locationList.length() == 0)
3070
            addInstance(loc);
3071
    }
3072
3073
    /* 
3074
     *   Add the new location to our set of locations.  Any existing
3075
     *   locations are unaffected. 
3076
     */
3077
    moveIntoAdd(loc)
3078
    {
3079
        /* if I'm not already in the location, add an instance there */
3080
        if (locationList.indexOf(loc) == nil)
3081
            addInstance(loc);
3082
    }
3083
3084
    /* 
3085
     *   Remove me from the given location.  Other locations are
3086
     *   unaffected.  
3087
     */
3088
    moveOutOf(loc)
3089
    {
3090
        local inst;
3091
        
3092
        /* find our instance that's in the given location */
3093
        inst = getInstanceIn(loc);
3094
3095
        /* if we found it, remove this instance from its location */
3096
        if (inst != nil)
3097
            inst.moveInto(nil);
3098
    }
3099
3100
    /* get our instance object (if any) that's in the given location */
3101
    getInstanceIn(loc)
3102
        { return instanceList.valWhich({x: x.isDirectlyIn(loc)}); }
3103
3104
    /* internal service routine - add an instance for a given location */
3105
    addInstance(loc)
3106
    {
3107
        local inst;
3108
3109
        /* 
3110
         *   Create an instance of the template object, mixing in our
3111
         *   special instance superclass using multiple inheritance.  The
3112
         *   MultiInstanceInstance superclass overrides the location
3113
         *   manipulation methods so that we keep the MultiInstance parent
3114
         *   (i.e., us) synchronized if we move around the instance object
3115
         *   directly (by calling its moveInto() method directly, for
3116
         *   example).  
3117
         */
3118
        inst = TadsObject.createInstanceOf(
3119
            [instanceMixIn, self], [instanceObject]);
3120
3121
        /* add it to our list of active instances */
3122
        instanceList.append(inst);
3123
3124
        /* move the instance into its new location */
3125
        inst.moveInto(loc);
3126
    }
3127
3128
    /* 
3129
     *   If any contents are added to the MultiInstance object, they must
3130
     *   be contents of the template object, so add them to the template
3131
     *   object instead of the MultiInstance parent. 
3132
     */
3133
    addToContents(obj) { instanceObject.addToContents(obj); }
3134
3135
    /* 
3136
     *   remove an object from our contents - we'll delegate this to our
3137
     *   template object just like we delegate addToContents 
3138
     */
3139
    removeFromContents(obj) { instanceObject.removeFromContents(obj); }
3140
3141
    /* the mix-in superclass for our instance objects */
3142
    instanceMixIn = MultiInstanceInstance
3143
3144
    /* our vector of active instance objects */
3145
    instanceList = perInstance(new Vector(5))
3146
;
3147
3148
/*
3149
 *   An instance of a MultiInstance object.  This is a mix-in class that
3150
 *   we add (using mutiple inheritance) to each instance.  This overrides
3151
 *   the location manipulation methods, to ensure that we keep the
3152
 *   MultiInstance parent object in sync with any changes made directly to
3153
 *   the instance objects.
3154
 *   
3155
 *   IMPORTANT - the library adds this class to each instance object
3156
 *   *automatically*.  Game code shouldn't ever have to use this class
3157
 *   directly.  
3158
 */
3159
class MultiInstanceInstance: object
3160
    construct(parent)
3161
    {
3162
        /* remember our MultiInstance parent object */
3163
        miParent = parent;
3164
3165
        /* 
3166
         *   clone my contents tree for the new instance, so that we have a
3167
         *   private copy of any components within the instance 
3168
         */
3169
        cloneMultiInstanceContents();
3170
    }
3171
3172
    /* move to a new location */
3173
    baseMoveInto(newCont)
3174
    {
3175
        /* 
3176
         *   if we currently have a location, take the location out of our
3177
         *   MultiInstance parent's location list 
3178
         */
3179
        if (location != nil)
3180
            miParent.locationList -= location;
3181
3182
        /* inherit the standard behavior */
3183
        inherited(newCont);
3184
3185
        /* 
3186
         *   if we have a new location, add the new location to our
3187
         *   MultiInstance parent's location list; otherwise, drop out of
3188
         *   the parent's instance list 
3189
         */
3190
        if (newCont != nil)
3191
        {
3192
            /* 
3193
             *   add the new location to the parent's location list, if
3194
             *   we're not already there 
3195
             */
3196
            if (miParent.locationList.indexOf(newCont) == nil)
3197
                miParent.locationList += newCont;
3198
        }
3199
        else
3200
        {
3201
            /* 
3202
             *   we're being removed from the game world, so remove this
3203
             *   instance from the parent's instance list 
3204
             */
3205
            miParent.instanceList.removeElement(self);
3206
        }
3207
    }
3208
3209
    /* 
3210
     *   All instances of a given MultiInstance are equivalent to one
3211
     *   another, for parsing purposes.  
3212
     */
3213
    isEquivalent = true
3214
3215
    /* our MultiInstance parent */
3216
    miParent = nil
3217
;
3218
3219
3220
/* ------------------------------------------------------------------------ */
3221
/*
3222
 *   A "multi-faceted" object is similar to a MultiInstance object, with
3223
 *   the addition that the instance objects are "facets" of one another.
3224
 *   This means that they have the same identity, from the perspective of
3225
 *   a character in the scenario: all of the instance objects are part of
3226
 *   the same conceptual object, not separate objects.
3227
 *   
3228
 *   This is especially useful for large objects that span multiple
3229
 *   locations, such as a river or a long rope.
3230
 *   
3231
 *   You define a multi-faceted object the same way you set up a
3232
 *   MultiInstance: definfe a MultiFaceted shell object, and as part of
3233
 *   the shell, define the facet object using the instanceObject property.
3234
 *   Here's an example:
3235
 *   
3236
 *   river: MultiFaceted
3237
 *.    locationList = [riverBank, meadow, canyon]
3238
 *.    instanceObject: Fixture { 'river' 'river'
3239
 *.      "The river meanders by. "
3240
 *.    }
3241
 *.  ;
3242
 *   
3243
 *   The main difference between MultiInstance and MultiFaceted is that
3244
 *   the "facet" objects of a MultiFaceted are related as facets of a
3245
 *   common object from the parser's perspective.  For example, if a
3246
 *   player refers to one facet, then travels to another location that
3247
 *   contains a different facet, then refers to "it", the parser will
3248
 *   realize that the pronoun refers to the new facet in the new location.
3249
 */
3250
class MultiFaceted: MultiInstance
3251
    /* our instance objects represent our facets for parsing purposes */
3252
    getFacets() { return instanceList; }
3253
3254
    /* the mix-in superclass for our instance objects */
3255
    instanceMixIn = MultiFacetedFacet
3256
;
3257
3258
/* 
3259
 *   The mix-in superclass for MultiFaceted facet instances.
3260
 *   
3261
 *   IMPORTANT - the library adds this class to each instance object
3262
 *   *automatically*.  Game code shouldn't ever have to use this class
3263
 *   directly.  
3264
 */
3265
class MultiFacetedFacet: MultiInstanceInstance
3266
    /* 
3267
     *   Get our other facets for parsing purposes - our parent maintains
3268
     *   the list of all of its facets, so simply return that list.  (Note
3269
     *   that we'll be in the list as well, but that's harmless, so don't
3270
     *   bother removing us.) 
3271
     */
3272
    getFacets() { return miParent.getFacets(); }
3273
;
3274
3275
/* ------------------------------------------------------------------------ */
3276
/*
3277
 *   A "linkable" object is one that can participate in a master/slave
3278
 *   relationship.  This kind of relationship means that the state of both
3279
 *   objects in the pair is controlled by one of the objects, called the
3280
 *   master; the other object defers to the other to get and set all of
3281
 *   its linkable state.
3282
 *   
3283
 *   Note that this base class doesn't provide for the management of any
3284
 *   of the actual linked state.  Subclasses are responsible for doing
3285
 *   this.  The general pattern is to create a getter/setter method pair
3286
 *   for each bit of linked state, and in these methods refer to
3287
 *   masterObject.xxx rather than just self.xxx.
3288
 *   
3289
 *   This is useful for objects such as doors that have two separate
3290
 *   objects representing the two sides of the door.  The two sides are
3291
 *   always linked for things like open/closed and locked/unlocked state;
3292
 *   this can be handled by linking the two sides, and managing all state
3293
 *   of both sides in one side designated as the master.  
3294
 */
3295
class Linkable: object
3296
    /*
3297
     *   Get the master object, which holds our state.  By default, this
3298
     *   is simply 'self', but some objects might want to override this.
3299
     *   For example, doors are usually implemented with two separate
3300
     *   objects, representing the two sides of the door, which share
3301
     *   common state; in such cases, one of the pair can be designated as
3302
     *   the master, which holds the common state of the door, and this
3303
     *   method can be overridden so that all state operations on the lock
3304
     *   are performed on the master side of the door.
3305
     *   
3306
     *   We return self by default so that a linkable object can stand
3307
     *   alone if desired.  That is, a linkable object doesn't have to be
3308
     *   part of a pair; it can just as well be a single object.  
3309
     */
3310
    masterObject()
3311
    {
3312
        /* 
3313
         *   inherit from the next superclass, if possible; otherwise, use
3314
         *   'self' as the default master object 
3315
         */
3316
        if (canInherit())
3317
            return inherited();
3318
        else
3319
            return self;
3320
    }
3321
3322
    /*
3323
     *   We're normally mixed into a Thing; do some extra work in
3324
     *   initialization. 
3325
     */
3326
    initializeThing()
3327
    {
3328
        /* inherit the default handling */
3329
        inherited();
3330
3331
        /* 
3332
         *   If we're tied to a separate master object, check the master
3333
         *   object to see if it's tied back to us as its master object.
3334
         *   Only one can be the master; if each says the other is the
3335
         *   master, we'll get stuck in infinite loops as each tries to
3336
         *   defer to the other.  To avoid this, break the loop by
3337
         *   arbitrarily choosing one or the other as the master.  Note
3338
         *   that we don't have to worry about the other object making a
3339
         *   different decision and breaking the relationship, because if
3340
         *   we detect the loop, it means we're going first - if the other
3341
         *   object had gone first then it would have detected and broken
3342
         *   the loop itself, and we wouldn't be finding the loop now.
3343
         */
3344
        if (masterObject != self && masterObject.masterObject == self)
3345
        {
3346
            /* 
3347
             *   We're tied together in a loop - break the loop by
3348
             *   arbitrarily electing myself as the master object.
3349
             *   Because these relationships are symmetric, it shouldn't
3350
             *   matter which we choose.  
3351
             */
3352
            masterObject = self;
3353
        }
3354
    }
3355
;
3356
3357
3358
/* ------------------------------------------------------------------------ */
3359
/*
3360
 *   A "basic openable" is an object that keeps open/closed status, and
3361
 *   which can be linked to another object to maintain that status.  This
3362
 *   basic class doesn't handle any special commands; it's purely for
3363
 *   keeping track of internal open/closed state.  
3364
 */
3365
class BasicOpenable: Linkable
3366
    /* 
3367
     *   Initial open/closed setting.  Set this to true to make the object
3368
     *   open initially.  If this object is linked to another object (as
3369
     *   in the two sides of a door), you only need to set this property
3370
     *   in the *master* object - the other side will automatically link
3371
     *   up to the master object during initialization.  
3372
     */
3373
    initiallyOpen = nil
3374
3375
    /*
3376
     *   Flag: door is open.  Travel is only possible when the door is
3377
     *   open.  Return the master's status.
3378
     */
3379
    isOpen()
3380
    {
3381
        /* 
3382
         *   If we're the master, simply use our isOpen_ property;
3383
         *   otherwise, call our master's isOpen method.  This way, if the
3384
         *   master has a different way of calculating isOpen, we'll defer
3385
         *   to its different handling. 
3386
         */
3387
        if (masterObject == self)
3388
            return isOpen_;
3389
        else
3390
            return masterObject.isOpen();
3391
    }
3392
3393
    /*
3394
     *   Make the object open or closed.  By default, we'll simply set the
3395
     *   isOpen flag to the new status.  Objects can override this to
3396
     *   apply side effects of opening or closing the object.  
3397
     */
3398
    makeOpen(stat)
3399
    {
3400
        /* 
3401
         *   if we're the master, simply set our isOpen_ property;
3402
         *   otherwise, defer to the master 
3403
         */
3404
        if (masterObject == self)
3405
            isOpen_ = stat;
3406
        else
3407
            masterObject.makeOpen(stat);
3408
3409
        /* inherit the next superclass's handling */
3410
        inherited(stat);
3411
    }
3412
3413
    /*
3414
     *   Open status name.  This is an adjective describing whether the
3415
     *   object is opened or closed.  In English, this will return "open"
3416
     *   or "closed."  
3417
     */
3418
    openDesc = (isOpen ? gLibMessages.openMsg(self)
3419
                       : gLibMessages.closedMsg(self))
3420
3421
    /* initialization */
3422
    initializeThing()
3423
    {
3424
        /* inherit the default handling */
3425
        inherited();
3426
3427
        /* if we're the master, set our initial open/closed state */
3428
        if (masterObject == self)
3429
            isOpen_ = initiallyOpen;
3430
    }
3431
3432
    /*
3433
     *   If we're obstructing a sense path, it must be because we're
3434
     *   closed.  Try implicitly opening.  
3435
     */
3436
    tryImplicitRemoveObstructor(sense, obj)
3437
    {
3438
        /* 
3439
         *   If I'm not already open, try opening me.  As usual for 'try'
3440
         *   routines, we return true if we attempt a command, nil if not.
3441
         *   
3442
         *   Note that we might be creating an obstruction despite already
3443
         *   being open; in this case, we don't want to do anything, since
3444
         *   an implied 'open' won't help when we're already open.  
3445
         */
3446
        return isOpen ? nil : tryImplicitAction(Open, self);
3447
    }
3448
3449
    /* 
3450
     *   if we can't reach or move something through the container, it
3451
     *   must be because we're closed 
3452
     */
3453
    cannotTouchThroughMsg = &cannotTouchThroughClosedMsg
3454
    cannotMoveThroughMsg = &cannotMoveThroughClosedMsg
3455
3456
    /* 
3457
     *   Internal open/closed status.  Do not use this for initialization
3458
     *   - set initiallyOpen in the master object instead. 
3459
     */
3460
    isOpen_ = nil
3461
;
3462
3463
/* ------------------------------------------------------------------------ */
3464
/*
3465
 *   Openable: a mix-in class that can be combined with an object's other
3466
 *   superclasses to make the object respond to the verbs "open" and
3467
 *   "close."  We also add some extra features for other related verbs,
3468
 *   such as a must-be-open precondition "look in" and "board".  
3469
 */
3470
class Openable: BasicOpenable
3471
    /*
3472
     *   Describe our contents using a special version of the contents
3473
     *   lister, so that we add our open/closed status to the listing.  The
3474
     *   message we add is given by our openStatus method, so if all you
3475
     *   want to change is the "it's open" status message, you can just
3476
     *   override openStatus rather than providing a whole new lister.  
3477
     */
3478
    descContentsLister = openableContentsLister
3479
3480
    /*
3481
     *   Contents lister to use when we're opening the object.  This
3482
     *   lister shows the items that are newly revealed when the object is
3483
     *   opened. 
3484
     */
3485
    openingLister = openableOpeningLister
3486
3487
    /* 
3488
     *   Get our "open status" message - this is a complete sentence saying
3489
     *   that we're open or closed.  By default, in English, we just say
3490
     *   "it's open" (adjusted for number and gender, of course).
3491
     *   
3492
     *   Note that this message has to be a stand-alone independent clause.
3493
     *   In particular note that we don't put any spacing after it, since
3494
     *   we need to be able to add sentence-ending or clause-ending
3495
     *   punctuation immediately after it.  
3496
     */
3497
    openStatus() { return gLibMessages.openStatusMsg(self); }
3498
3499
    /*
3500
     *   By default, an Openable that's also a Lockable must be closed to
3501
     *   be locked.  This means that when it's open, the object is
3502
     *   implicitly unlocked, in which case "It's unlocked" isn't worth
3503
     *   mentioning when the description says "It's open."
3504
     */
3505
    lockStatusReportable = (!isOpen)
3506
3507
    /*
3508
     *   Action handlers 
3509
     */
3510
    dobjFor(Open)
3511
    {
3512
        verify()
3513
        {
3514
            /* it makes no sense to open something that's already open */
3515
            if (isOpen)
3516
                illogicalAlready(&alreadyOpenMsg);
3517
        }
3518
        action()
3519
        {
3520
            local trans;
3521
            
3522
            /* 
3523
             *   note the effect we have currently, while still closed, on
3524
             *   sensing from outside into our contents 
3525
             */
3526
            trans = transSensingIn(sight);
3527
            
3528
            /* make it open */
3529
            makeOpen(true);
3530
3531
            /* 
3532
             *   make the default report - if we make a non-default
3533
             *   report, the default will be ignored, so we don't need to
3534
             *   worry about whether or not we'll make a non-default
3535
             *   report now 
3536
             */
3537
            defaultReport(&okayOpenMsg);
3538
3539
            /*
3540
             *   If the actor is outside me, and we have any listable
3541
             *   contents, and our sight transparency is now better than it
3542
             *   was before we were open, reveal the new contents.
3543
             *   Otherwise, just show our default 'opened' message.
3544
             *   
3545
             *   As a special case, if we're running as an implied command
3546
             *   within a LookIn or Search action on this same object,
3547
             *   don't bother showing this result.  Doing so would be
3548
             *   redundant with the explicit examination of the contents
3549
             *   that we'll be doing anyway with the main action.  
3550
             */
3551
            if (!gActor.isIn(self)
3552
                && transparencyCompare(transSensingIn(sight), trans) > 0
3553
                && !(gAction.isImplicit
3554
                     && (gAction.parentAction.ofKind(LookInAction)
3555
                         || gAction.parentAction.ofKind(SearchAction))
3556
                     && gAction.parentAction.getDobj() == self))
3557
            {
3558
                local tab;
3559
3560
                /* get the table of visible objects */
3561
                tab = gActor.visibleInfoTable();
3562
                
3563
                /* show my contents list, if I have any */
3564
                openingLister.showList(gActor, self, contents, ListRecurse,
3565
                                       0, tab, nil);
3566
3567
                /* mark my contents as having been seen */
3568
                setContentsSeenBy(tab, gActor);
3569
3570
                /* show any special contents as well */
3571
                examineSpecialContents();
3572
            }
3573
        }
3574
    }
3575
3576
    dobjFor(Close)
3577
    {
3578
        verify()
3579
        {
3580
            /* it makes no sense to close something that's already closed */
3581
            if (!isOpen)
3582
                illogicalAlready(&alreadyClosedMsg);
3583
        }
3584
        action()
3585
        {
3586
            /* make it closed */
3587
            makeOpen(nil);
3588
3589
            /* show the default report */
3590
            defaultReport(&okayCloseMsg);
3591
        }
3592
    }
3593
3594
    dobjFor(LookIn)
3595
    {
3596
        /* 
3597
         *   to look in an openable object, we must be open, unless the
3598
         *   object is transparent or the actor is inside us 
3599
         */
3600
        preCond
3601
        {
3602
            local lst;
3603
3604
            /* get the inherited preconditions */
3605
            lst = nilToList(inherited());
3606
3607
            /* 
3608
             *   if I'm not transparent looking in, and the actor isn't
3609
             *   already inside me, try opening me 
3610
             */
3611
            if (transSensingIn(sight) != transparent && !gActor.isIn(self))
3612
                lst += objOpen;
3613
3614
            /* return the result */
3615
            return lst;
3616
        }
3617
    }
3618
3619
    dobjFor(Search)
3620
    {
3621
        /* 
3622
         *   To search an openable object, we must be open - unlike LOOK
3623
         *   IN, this applies even if the object is transparent, since
3624
         *   SEARCH is inherently more aggressive than LOOK IN, and implies
3625
         *   physically picking through the contents.  This doesn't apply
3626
         *   if the actor is already inside me.  
3627
         */
3628
        preCond
3629
        {
3630
            /* get the inherited preconditions */
3631
            local lst = nilToList(inherited());
3632
3633
            /* if the actor isn't in me, make sure I'm open */
3634
            if (!gActor.isIn(self))
3635
                lst += objOpen;
3636
3637
            /* 
3638
             *   searching implies physically sifting through the contents,
3639
             *   so we need to be able to touch the object 
3640
             */
3641
            lst += touchObj;
3642
3643
            /* return the updated list */
3644
            return lst;
3645
        }
3646
    }
3647
3648
    /*
3649
     *   Generate a precondition to make sure gActor can reach the interior
3650
     *   of the container.  We consider the inside reachable if either the
3651
     *   actor is located inside the container, or the actor is outside and
3652
     *   the container is open.  
3653
     */
3654
    addInteriorReachableCond(lst)
3655
    {
3656
        /* 
3657
         *   If the actor's inside us, they can reach our interior whether
3658
         *   we're open or not, so there's no need for any additional
3659
         *   condition.  If not, we need to be open for the actor to be
3660
         *   able to reach our interior.  
3661
         */
3662
        if (!gActor.isIn(self))
3663
            lst = nilToList(lst) + objOpen;
3664
3665
        /* return the result */
3666
        return lst;
3667
    }
3668
3669
    iobjFor(PutIn)
3670
    {
3671
        /* make sure that our interior is reachable */
3672
        preCond { return addInteriorReachableCond(inherited()); }
3673
    }
3674
3675
    iobjFor(PourInto)
3676
    {
3677
        /* make sure that our interior is reachable */
3678
        preCond { return addInteriorReachableCond(inherited()); }
3679
    }
3680
3681
    /* can't lock an openable that isn't closed */
3682
    dobjFor(Lock)
3683
    {
3684
        preCond { return nilToList(inherited()) + objClosed; }
3685
    }
3686
    dobjFor(LockWith)
3687
    {
3688
        preCond { return nilToList(inherited()) + objClosed; }
3689
    }
3690
3691
    /* must be open to get out of a nested room */
3692
    dobjFor(GetOutOf)
3693
    {
3694
        preCond()
3695
        {
3696
            return nilToList(inherited())
3697
                + new ObjectPreCondition(self, objOpen);
3698
        }
3699
    }
3700
3701
    /* must be open to get into a nested room */
3702
    dobjFor(Board)
3703
    {
3704
        preCond()
3705
        {
3706
            return nilToList(inherited())
3707
                + new ObjectPreCondition(self, objOpen);
3708
        }
3709
    }
3710
;
3711
3712
/* ------------------------------------------------------------------------ */
3713
/*
3714
 *   Lockable: a mix-in class that can be combined with an object's other
3715
 *   superclasses to make the object respond to the verbs "lock" and
3716
 *   "unlock."  A Lockable requires no key.
3717
 *   
3718
 *   Note that Lockable should usually go BEFORE a Thing-derived class in
3719
 *   the superclass list.  
3720
 */
3721
class Lockable: Linkable
3722
    /* 
3723
     *   Our initial locked state (i.e., at the start of the game).  By
3724
     *   default, we start out locked. 
3725
     */
3726
    initiallyLocked = true
3727
3728
    /*
3729
     *   Current locked state.  Use our isLocked_ status if we're the
3730
     *   master, otherwise defer to the master.  
3731
     */
3732
    isLocked()
3733
    {
3734
        if (masterObject == self)
3735
            return isLocked_;
3736
        else
3737
            return masterObject.isLocked();
3738
    }
3739
3740
    /*
3741
     *   Make the object locked or unlocked.  Objects can override this to
3742
     *   apply side effects of locking or unlocking.  By default, if we're
3743
     *   the master, we'll simply set our isLocked_ property to the new
3744
     *   status, and otherwise defer to the master object.  
3745
     */
3746
    makeLocked(stat)
3747
    {
3748
        /* apply to self or the master object, as appropriate */
3749
        if (masterObject == self)
3750
            isLocked_ = stat;
3751
        else
3752
            masterObject.makeLocked(stat);
3753
3754
        /* inherit the next superclass's handling */
3755
        inherited(stat);
3756
    }
3757
3758
    /* show our status */
3759
    examineStatus()
3760
    {
3761
        /* inherit the default handling */
3762
        inherited();
3763
3764
        /* 
3765
         *   if our lock status is visually apparent, and we want to
3766
         *   mention the lock status in our current state, show the lock
3767
         *   status 
3768
         */
3769
        if (lockStatusObvious && lockStatusReportable)
3770
            say(isLocked ? gLibMessages.currentlyLocked
3771
                         : gLibMessages.currentlyUnlocked);
3772
    }
3773
3774
    /* description of the object as locked or unlocked */
3775
    lockedDesc = (isLocked() ? gLibMessages.lockedMsg(self)
3776
                             : gLibMessages.unlockedMsg(self))
3777
3778
    /*
3779
     *   Is our 'locked' status obvious?  This should be set to true for an
3780
     *   object whose locked/unlocked status can be visually observed, nil
3781
     *   for an object whose status is not visuall apparent.  For example,
3782
     *   you can usually tell from the inside that a door is locked by
3783
     *   looking at the position of the lock's paddle, but on the outside
3784
     *   of a door there's usually no way to see the status.
3785
     *   
3786
     *   By default, since we can be locked and unlocked with simple LOCK
3787
     *   and UNLOCK commands, we assume the status is as obvious as the
3788
     *   mechanism must be to allow such simple commands.  
3789
     */
3790
    lockStatusObvious = true
3791
3792
    /*
3793
     *   Is our 'locked' status reportable in our current state?  This is
3794
     *   similar to lockStatusObvious, but serves a separate purpose: this
3795
     *   tells us if we wish to report the lock status for aesthetic
3796
     *   reasons.
3797
     *   
3798
     *   This property is primarily of interest to mix-ins.  To allow
3799
     *   mix-ins to get a say, regardless of the order of superclasses,
3800
     *   we'll by default defer to any inherited value if there is in fact
3801
     *   an inherited value.  If there's no inherited value, we'll simply
3802
     *   return true.
3803
     *   
3804
     *   We use this in the library for one case in particular: when we're
3805
     *   mixed with Openable, we don't want to report the lock status for
3806
     *   an open object because an Openable must by default be closed to be
3807
     *   locked.  That is, when an Openable is open, it's always unlocked,
3808
     *   so reporting that it's unlocked is essentially redundant
3809
     *   information.  
3810
     */
3811
    lockStatusReportable = (canInherit() ? inherited() : true)
3812
3813
    /* 
3814
     *   Internal locked state.  Do not use this to set the initial state
3815
     *   - set initiallyLocked in the master object instead. 
3816
     */
3817
    isLocked_ = nil
3818
    
3819
    /* initialization */
3820
    initializeThing()
3821
    {
3822
        /* inherit the default handling */
3823
        inherited();
3824
        
3825
        /* if we're the master, set our initial state */
3826
        if (masterObject == self)
3827
            isLocked_ = initiallyLocked;
3828
    }
3829
3830
    /*
3831
     *   Action handling 
3832
     */
3833
3834
    /* "lock" */
3835
    dobjFor(Lock)
3836
    {
3837
        preCond = (nilToList(inherited()) + [touchObj])
3838
        verify()
3839
        {
3840
            /* if we're already locked, there's no point in locking us */
3841
            if (isLocked)
3842
                illogicalAlready(&alreadyLockedMsg);
3843
        }
3844
        action()
3845
        {
3846
            /* make it locked */
3847
            makeLocked(true);
3848
3849
            /* make the default report */
3850
            defaultReport(&okayLockMsg);
3851
        }
3852
    }
3853
3854
    /* "unlock" */
3855
    dobjFor(Unlock)
3856
    {
3857
        preCond = (nilToList(inherited()) + [touchObj])
3858
        verify()
3859
        {
3860
            /* if we're already unlocked, there's no point in doing this */
3861
            if (!isLocked)
3862
                illogicalAlready(&alreadyUnlockedMsg);
3863
        }
3864
        action()
3865
        {
3866
            /* make it unlocked */
3867
            makeLocked(nil);
3868
3869
            /* make the default report */
3870
            defaultReport(&okayUnlockMsg);
3871
        }
3872
    }
3873
3874
    /* "lock with" */
3875
    dobjFor(LockWith)
3876
    {
3877
        preCond = (nilToList(inherited()) + [touchObj])
3878
        verify() { illogical(&noKeyNeededMsg); }
3879
    }
3880
3881
    /* "unlock with" */
3882
    dobjFor(UnlockWith)
3883
    {
3884
        preCond = (nilToList(inherited()) + [touchObj])
3885
        verify() { illogical(&noKeyNeededMsg); }
3886
    }
3887
3888
    /*
3889
     *   Should we automatically unlock this door on OPEN?  By default, we
3890
     *   do this only if the lock status is obvious.  
3891
     */
3892
    autoUnlockOnOpen = (lockStatusObvious)
3893
    
3894
    /* 
3895
     *   A locked object can't be opened - apply a precondition and a check
3896
     *   for "open" that ensures that we unlock this object before we can
3897
     *   open it.
3898
     *   
3899
     *   If the lock status isn't obvious, don't try to unlock the object
3900
     *   as a precondition.  Instead, test to make sure it's unlocked in
3901
     *   the 'check' routine, and fail.  
3902
     */
3903
    dobjFor(Open)
3904
    {
3905
        preCond()
3906
        {
3907
            /* start with the inherited preconditions */
3908
            local ret = nilToList(inherited());
3909
3910
            /* automatically unlock on open, if appropriate */
3911
            if (autoUnlockOnOpen)
3912
                ret += objUnlocked;
3913
3914
            /* return the result */
3915
            return ret;
3916
        }
3917
3918
        check()
3919
        {
3920
            /* make sure we're unlocked */
3921
            if (isLocked)
3922
            {
3923
                /* let them know we're locked */
3924
                reportFailure(&cannotOpenLockedMsg);
3925
3926
                /* set 'it' to me, so UNLOCK IT works */
3927
                gActor.setPronounObj(self);
3928
3929
                /* we cannot proceed */
3930
                exit;
3931
            }
3932
3933
            /* inherit the default handling */
3934
            inherited();
3935
        }
3936
    }
3937
;
3938
3939
/* ------------------------------------------------------------------------ */
3940
/*
3941
 *   A lockable that can't be locked and unlocked by direct action.  The
3942
 *   LOCK and UNLOCK commands cannot be used with this kind of lockable.
3943
 *   
3944
 *   This is useful for a couple of situations.  First, it's useful when we
3945
 *   want to create a locked object that simply can't be unlocked, such as
3946
 *   a locked door that forms a permanent boundary of the map.  Second,
3947
 *   it's useful for locked objects that must be unlocked by some other
3948
 *   means, such as manipulating an external mechanism (pulling a lever,
3949
 *   say).  In these cases, the trick is to figure out the separate means
3950
 *   of unlocking the door, so we don't want the LOCK and UNLOCK commands
3951
 *   to work directly.  
3952
 */
3953
class IndirectLockable: Lockable
3954
    dobjFor(Lock)
3955
    {
3956
        check()
3957
        {
3958
            reportFailure(cannotLockMsg);
3959
            exit;
3960
        }
3961
    }
3962
    dobjFor(LockWith) asDobjFor(Lock)
3963
    dobjFor(Unlock)
3964
    {
3965
        check()
3966
        {
3967
            reportFailure(cannotUnlockMsg);
3968
            exit;
3969
        }
3970
    }
3971
    dobjFor(UnlockWith) asDobjFor(Unlock)
3972
3973
    /*
3974
     *   Since we can't be locked and unlocked with simple LOCK and UNLOCK
3975
     *   commands, presume that the lock status isn't obvious.  If the
3976
     *   alternative mechanism that locks and unlocks the object makes the
3977
     *   current status readily apparent, this should be overridden and set
3978
     *   to true.  
3979
     */
3980
    lockStatusObvious = nil
3981
3982
    /* the message we display in response to LOCK/UNLOCK */
3983
    cannotLockMsg = &unknownHowToLockMsg
3984
    cannotUnlockMsg = &unknownHowToUnlockMsg
3985
;
3986
3987
3988
/* ------------------------------------------------------------------------ */
3989
/*
3990
 *   LockableWithKey: a mix-in class that can be combined with an object's
3991
 *   other superclasses to make the object respond to the verbs "lock" and
3992
 *   "unlock," with a key as an indirect object.  A LockableWithKey cannot
3993
 *   be locked or unlocked except with the keys listed in the keyList
3994
 *   property.
3995
 *   
3996
 *   Note that LockableWithKey should usually go BEFORE a Thing-derived
3997
 *   class in the superclass list.  
3998
 */
3999
class LockableWithKey: Lockable
4000
    /*
4001
     *   Determine if the key fits this lock.  Returns true if so, nil if
4002
     *   not.  By default, we'll return true if the key is in my keyList.
4003
     *   This can be overridden to use other key selection criteria.  
4004
     */
4005
    keyFitsLock(key) { return keyList.indexOf(key) != nil; }
4006
4007
    /*
4008
     *   Determine if the key is plausibly of the right type for this
4009
     *   lock.  This doesn't check to see if the key actually fits the
4010
     *   lock - rather, this checks to see if the key is generally the
4011
     *   kind of object that might plausibly be used with this lock.
4012
     *   
4013
     *   The point of this routine is to make this class concerned only
4014
     *   with the abstract notion of objects that serve to lock and unlock
4015
     *   other objects, without requiring that the key objects resemble
4016
     *   little notched metal sticks or that the lock objects resemble
4017
     *   cylinders with pins - or, more specifically, without requiring
4018
     *   that all of the kinds of keys in a game remotely resemble one
4019
     *   another.
4020
     *   
4021
     *   For example, one kind of "key" in a game might be a plastic card
4022
     *   with a magnetic stripe, and the corresponding lock would be a
4023
     *   card slot; another kind of key might the traditional notched
4024
     *   metal stick.  Clearly, no one would ever think to use a plastic
4025
     *   card with a conventional door lock, nor would one try to put a
4026
     *   house key into a card slot (not with the expectation that it
4027
     *   would actually work, anyway).  This routine is meant to
4028
     *   facilitate this kind of distinction: the card slot can use this
4029
     *   routine to indicate that only plastic card objects are plausible
4030
     *   as keys, and door locks can indicate that only metal keys are
4031
     *   plausible.
4032
     *   
4033
     *   This routine can be used for disambiguation and other purposes
4034
     *   when we must programmatically select a key that is not specified
4035
     *   or is only vaguely specified.  For example, the keyring searcher
4036
     *   uses it so that, when we're searching for a key on a keyring to
4037
     *   open this lock, we implicitly try only the kinds of keys that
4038
     *   would be plausibly useful for this kind of lock.
4039
     *   
4040
     *   By default, we'll simply return true.  Subclasses specific to a
4041
     *   game (such as the "card reader" base class or the "door lock"
4042
     *   base class) can override this to discriminate among the
4043
     *   game-specific key classes.  
4044
     */
4045
    keyIsPlausible(key) { return true; }
4046
4047
    /* the list of objects that can serve as keys for this object */
4048
    keyList = []
4049
4050
    /* 
4051
     *   The list of keys which the player knows will fit this lock.  This
4052
     *   is used to make key disambiguation automatic once the player
4053
     *   knows the correct key for a lock.  
4054
     */
4055
    knownKeyList = []
4056
4057
    /* 
4058
     *   Get my known key list.  This simply returns the known key list
4059
     *   from the known key owner. 
4060
     */
4061
    getKnownKeyList() { return getKnownKeyOwner().knownKeyList; }
4062
4063
    /* 
4064
     *   Get the object that own our known key list.  If we explicitly have
4065
     *   our own non-empty known key list, we own the key list; otherwise,
4066
     *   our master object owns the list, as long as it has a non-nil key
4067
     *   list at all.  
4068
     */
4069
    getKnownKeyOwner()
4070
    {
4071
        /* 
4072
         *   if we have a non-empty key list, or our master object doesn't
4073
         *   have a key list at all, use our list; otherwise, use our
4074
         *   master object's list so use our list 
4075
         */
4076
        if (knownKeyList.length() != 0 || masterObject.knownKeyList == nil)
4077
            return self;
4078
        else
4079
            return masterObject;
4080
    }
4081
4082
    /*
4083
     *   Flag: remember my keys after they're successfully used.  If this
4084
     *   is true, whenever a key is successfully used to lock or unlock
4085
     *   this object, we'll add the key to our known key list;
4086
     *   subsequently, whenever we try to use a key in this lock, we will
4087
     *   automatically disambiguate the key based on the keys known to
4088
     *   work previously.
4089
     *   
4090
     *   Some authors might prefer not to assume that the player should
4091
     *   remember which keys operate which locks, so this property can be
4092
     *   changed to nil to eliminate this memory feature.  By default we
4093
     *   set this to true, since it shouldn't generally give away any
4094
     *   secrets or puzzles for the game to assume that a key that was
4095
     *   used successfully once with a given lock is the one to be used
4096
     *   subsequently with the same lock.  
4097
     */
4098
    rememberKnownKeys = true
4099
4100
    /*
4101
     *   Determine if the player knows that the given key operates this
4102
     *   lock.  Returns true if the key is in our known key list, nil if
4103
     *   not.  
4104
     */
4105
    isKeyKnown(key) { return getKnownKeyList().indexOf(key) != nil; }
4106
4107
    /* 
4108
     *   By default, the locked/unlocked status of a keyed lockable is nil.
4109
     *   In most cases, an object that's locked and unlocked using a key
4110
     *   doesn't have a visible indication of the status; for example, you
4111
     *   usually can't tell just by looking at it from the outside whether
4112
     *   or not an exterior door to a building is locked.  Usually, the
4113
     *   only way to tell from the outside that an exterior door is locked
4114
     *   is to try opening it and see if it opens.  
4115
     */
4116
    lockStatusObvious = nil
4117
4118
    /*
4119
     *   Should we automatically unlock on OPEN?  We will if our inherited
4120
     *   handling says so, OR if the current actor is carrying a key
4121
     *   that's known to work with this object.  We automatically unlock
4122
     *   when a known key is present as a convenience: if we have a known
4123
     *   key, then there's no mystery in unlocking this object, and thus
4124
     *   for playability we want to make its operation fully automatic.  
4125
     */
4126
    autoUnlockOnOpen()
4127
    {
4128
        return (inherited()
4129
                || getKnownKeyList.indexWhich({x: x.isIn(gActor)}) != nil);
4130
    }
4131
4132
    /*
4133
     *   Action handling 
4134
     */
4135
4136
    dobjFor(Lock)
4137
    {
4138
        preCond
4139
        {
4140
            /* 
4141
             *   remove any objClosed from our precondition - since we
4142
             *   won't actually do any locking but will instead merely ask
4143
             *   for an indirect object, we don't want to apply the normal
4144
             *   closed precondition here 
4145
             */
4146
            return inherited() - objClosed;
4147
        }
4148
        verify()
4149
        {
4150
            /* if we're already locked, there's no point in locking us */
4151
            if (isLocked)
4152
                illogicalAlready(&alreadyLockedMsg);
4153
        }
4154
        action()
4155
        {
4156
            /* ask for an indirect object to use as the key */
4157
            askForIobj(LockWith);
4158
        }
4159
    }
4160
4161
    /* "unlock" */
4162
    dobjFor(Unlock)
4163
    {
4164
        verify()
4165
        {
4166
            /* if we're not locked, there's no point in unlocking us */
4167
            if (!isLocked)
4168
                illogicalAlready(&alreadyUnlockedMsg);
4169
        }
4170
        action()
4171
        {
4172
            /*
4173
             *   We need a key.  If we're running as an implied action, the
4174
             *   player hasn't specifically proposed unlocking the object,
4175
             *   so it's a little weird to ask a follow-up question about
4176
             *   what key to use.  So, if the action is implicit and
4177
             *   there's no default key, don't proceed; simply fail with an
4178
             *   explanation.  
4179
             */
4180
            if (gAction.isImplicit
4181
                && !UnlockWithAction.testRetryDefaultIobj(gAction))
4182
            {
4183
                /* explain that we need a key, and we're done */
4184
                reportFailure(&unlockRequiresKeyMsg);
4185
                return;
4186
            }
4187
            
4188
            /* ask for a key */
4189
            askForIobj(UnlockWith);
4190
        }
4191
    }
4192
4193
    /* 
4194
     *   perform the action processing for LockWith or UnlockWith - these
4195
     *   are highly symmetrical, in that the only thing that varies is the
4196
     *   new lock state we establish 
4197
     */
4198
    lockOrUnlockAction(lock)
4199
    {
4200
        /* 
4201
         *   If it's a keyring, let the keyring's action handler do the
4202
         *   work.  Otherwise, if it's my key, lock/unlock; it's not a
4203
         *   key, fail.  
4204
         */
4205
        if (gIobj.ofKind(Keyring))
4206
        {
4207
            /* 
4208
             *   do nothing - let the indirect object action handler do
4209
             *   the work 
4210
             */
4211
        }
4212
        else if (keyFitsLock(gIobj))
4213
        {
4214
            local ko;
4215
4216
            /* 
4217
             *   get the object (us or our master object) that owns the
4218
             *   known key list 
4219
             */
4220
            ko = getKnownKeyOwner();
4221
            
4222
            /* 
4223
             *   if the key owner remembers known keys, and it doesn't know
4224
             *   about this working key yet, remember this in the list of
4225
             *   known keys 
4226
             */
4227
            if (ko.rememberKnownKeys
4228
                && ko.knownKeyList.indexOf(gIobj) == nil)
4229
                ko.knownKeyList += gIobj;
4230
            
4231
            /* set my new state and issue a default report */
4232
            makeLocked(lock);
4233
            defaultReport(lock ? &okayLockMsg : &okayUnlockMsg);
4234
        }
4235
        else
4236
        {
4237
            /* the key doesn't work in this lock */
4238
            reportFailure(&keyDoesNotFitLockMsg);
4239
        }
4240
    }
4241
4242
    /* "lock with" */
4243
    dobjFor(LockWith)
4244
    {
4245
        verify()
4246
        {
4247
            /* if we're already locked, there's no point in locking us */
4248
            if (isLocked)
4249
                illogicalAlready(&alreadyLockedMsg);
4250
        }
4251
        action()
4252
        {
4253
            /* perform the generic lock/unlock action processing */
4254
            lockOrUnlockAction(true);
4255
        }
4256
    }
4257
4258
    /* "unlock with" */
4259
    dobjFor(UnlockWith)
4260
    {
4261
        verify()
4262
        {
4263
            /* if we're not locked, there's no point in unlocking us */
4264
            if (!isLocked)
4265
                illogicalAlready(&alreadyUnlockedMsg);
4266
        }
4267
        action()
4268
        {
4269
            /* perform the generic lock/unlock action processing */
4270
            lockOrUnlockAction(nil);
4271
        }
4272
    }
4273
;
4274
4275
/* ------------------------------------------------------------------------ */
4276
/*
4277
 *   The common base class for containers and surfaces: things that have
4278
 *   limited bulk capacities.  This class isn't usually used directly;
4279
 *   subclasses such as Surface and Container are usually used instead.  
4280
 */
4281
class BulkLimiter: Thing
4282
    /*
4283
     *   A container can limit the cumulative amount of bulk of its
4284
     *   contents, and the maximum bulk of any one object, using
4285
     *   bulkCapacity and maxSingleBulk.  We count the cumulative and
4286
     *   single-item limits separately, since we want to allow modelling
4287
     *   some objects as so large that they won't fit in this container at
4288
     *   all, even if the container is carrying nothing else, without
4289
     *   limiting the number of small items we can carry.
4290
     *   
4291
     *   By default, we set bulkCapacity to a very large number, making
4292
     *   the total capacity of the object essentially unlimited.  However,
4293
     *   we set maxSingleBulk to a relatively low number - this way, if an
4294
     *   author wants to designate certain objects as especially large and
4295
     *   thus unable to fit in ordinary containers, the author merely
4296
     *   needs to set the bulk of those large items to something greater
4297
     *   than 10.  On the other hand, if an author doesn't want to worry
4298
     *   about bulk and limited carrying capacities and simply uses
4299
     *   library defaults for everything, we will be able to contain
4300
     *   anything and everything.
4301
     *   
4302
     *   In a game that models bulk realistically, a container's bulk
4303
     *   should generally be equal to or slightly greater than its
4304
     *   bulkCapacity, because a container shouldn't be smaller on the
4305
     *   outside than on the inside.  If bulkCapacity exceeds bulk, the
4306
     *   player can work around a holding bulk limit by piling objects
4307
     *   into the container, thus "hiding" the bulks of the contents
4308
     *   behind the smaller bulk of the container.  
4309
     */
4310
    bulkCapacity = 10000
4311
    maxSingleBulk = 10
4312
4313
    /* 
4314
     *   receive notification that we're about to insert an object into
4315
     *   this container 
4316
     */
4317
    notifyInsert(obj, newCont)
4318
    {
4319
        /* if I'm the new direct container, check our bulk limit */
4320
        if (newCont == self)
4321
        {
4322
            /* 
4323
             *   do a 'what if' test to see what would happen to our
4324
             *   contained bulk if we moved this item into me 
4325
             */
4326
            obj.whatIf({: checkBulkInserted(obj)}, &moveInto, self);
4327
        }
4328
4329
        /* inherit base class handling */
4330
        inherited(obj, newCont);
4331
    }
4332
4333
    /*
4334
     *   Check to see if a proposed insertion - already tentatively in
4335
     *   effect when this routine is called - would overflow our bulk
4336
     *   limits.  Reports failure and exits if the inserted object would
4337
     *   exceed our capacity. 
4338
     */
4339
    checkBulkInserted(insertedObj)
4340
    {
4341
        local objBulk;
4342
4343
        /* get the bulk of the inserted object itself */
4344
        objBulk = insertedObj.getBulk();
4345
4346
        /*
4347
         *   Check the object itself to see if it fits by itself.  If it
4348
         *   doesn't, we can report the simple fact that the object is too
4349
         *   big for the container.  
4350
         */
4351
        if (objBulk > maxSingleBulk || objBulk > bulkCapacity)
4352
        {
4353
            reportFailure(&tooLargeForContainerMsg, insertedObj, self);
4354
            exit;
4355
        }
4356
            
4357
        /* 
4358
         *   If our contained bulk is over our maximum, don't allow it.
4359
         *   Note that we merely need to check our current bulk within,
4360
         *   since this routine is called with the insertion already
4361
         *   tentatively in effect. 
4362
         */
4363
        if (getBulkWithin() > bulkCapacity)
4364
        {
4365
            reportFailure(tooFullMsg, insertedObj, self);
4366
            exit;
4367
        }
4368
    }
4369
4370
    /* 
4371
     *   the message property to use when we're too full to hold a new
4372
     *   object (i.e., the object's bulk would push us over our bulk
4373
     *   capacity limit) 
4374
     */
4375
    tooFullMsg = &containerTooFullMsg
4376
4377
    /* 
4378
     *   the message property to use when doing something to one of our
4379
     *   contents would make it too large to fit all by itself into this
4380
     *   container (that is, it would cause that object's bulk to exceed
4381
     *   our maxSingleBulk) 
4382
     */
4383
    becomingTooLargeMsg = &becomingTooLargeForContainerMsg
4384
4385
    /* 
4386
     *   the message property to use when doing something to one of our
4387
     *   contents would cause our overall contents to exceed our capacity 
4388
     */
4389
    becomingTooFullMsg = &containerBecomingTooFullMsg
4390
4391
    /*
4392
     *   Check a bulk change of one of my direct contents. 
4393
     */
4394
    checkBulkChangeWithin(obj)
4395
    {
4396
        local objBulk;
4397
        
4398
        /* get the object's new bulk */
4399
        objBulk = obj.getBulk();
4400
        
4401
        /* 
4402
         *   if this change would cause the object to exceed our
4403
         *   single-item bulk limit, don't allow it 
4404
         */
4405
        if (objBulk > maxSingleBulk || objBulk > bulkCapacity)
4406
        {
4407
            reportFailure(becomingTooLargeMsg, obj, self);
4408
            exit;
4409
        }
4410
4411
        /* 
4412
         *   If our total carrying capacity is exceeded with this change,
4413
         *   don't allow it.  Note that 'obj' is already among our
4414
         *   contents when this routine is called, so we can simply check
4415
         *   our current total bulk within.  
4416
         */
4417
        if (getBulkWithin() > bulkCapacity)
4418
        {
4419
            reportFailure(becomingTooFullMsg, obj, self);
4420
            exit;
4421
        }
4422
    }
4423
4424
    /*
4425
     *   Adjust a THROW destination.  Since we only allow a limited amount
4426
     *   of bulk within our contents, we need to make sure the thrown
4427
     *   object would fit if it landed here.  If it doesn't, we'll redirect
4428
     *   the landing site to our container.  
4429
     */
4430
    adjustThrowDestination(thrownObj, path)
4431
    {
4432
        local thrownBulk = thrownObj.getBulk();
4433
        local newBulk;
4434
        local dest;
4435
4436
        /* 
4437
         *   do a 'what if' test to test our total bulk with the projectile
4438
         *   added to my contents 
4439
         */
4440
        newBulk = thrownObj.whatIf({: getBulkWithin()}, &moveInto, self);
4441
4442
        /* 
4443
         *   If that exceeds our maximum bulk, or the object's bulk
4444
         *   individually is over our limit, we can't be the landing site.
4445
         *   In this case, defer to our location's drop destination, if it
4446
         *   has one.  
4447
         */
4448
        if ((newBulk > bulkCapacity
4449
            || thrownBulk > bulkCapacity
4450
            || thrownBulk > maxSingleBulk)
4451
            && location != nil
4452
            && (dest = location.getDropDestination(thrownObj, path)) != nil)
4453
        {
4454
            /* 
4455
             *   It won't fit, so defer to our container's drop
4456
             *   destination.  Give the new destination a chance to further
4457
             *   adjust the destination.  
4458
             */
4459
            return dest.adjustThrowDestination(thrownObj, path);
4460
        }
4461
4462
        /* 
4463
         *   the projectile fits, or we just can't find a container to
4464
         *   defer to; use the original destination, i.e., self 
4465
         */
4466
        return self;
4467
    }
4468
4469
    /*
4470
     *   Examine my interior.  This can be used to handle the action() for
4471
     *   LOOK IN, or for other commands appropriate to the subclass.  
4472
     */
4473
    examineInterior()
4474
    {
4475
        /* examine the interior with our normal look-in lister */
4476
        examineInteriorWithLister(lookInLister);
4477
4478
        /* 
4479
         *   Anything that the an overriding caller (a routine that called
4480
         *   us with 'inherited') wants to add is an addendum to our
4481
         *   description, so add a transcript marker to indicate that the
4482
         *   main description is now finished.
4483
         *   
4484
         *   The important thing about this is that any message that an
4485
         *   overriding caller wants to add is not considered part of the
4486
         *   description, in the sense that we don't want it to suppress
4487
         *   any default description we've already generated.  One of the
4488
         *   transformations we apply to the transcript is to suppress any
4489
         *   default descriptive text if there's any more specific
4490
         *   descriptive text following (for example, we suppress "It's an
4491
         *   ordinary <thing>" if we also are going to say "it's open" or
4492
         *   "it contains three coins").  If we have an overriding caller
4493
         *   who's going to add anything, then we must assume that what the
4494
         *   caller's adding is something about the act of examining the
4495
         *   object, rather than a description of the object, so we don't
4496
         *   want it to suppress a default description.  
4497
         */
4498
        gTranscript.endDescription();
4499
    }
4500
4501
    /* examine my interior, listing the contents with the given lister */
4502
    examineInteriorWithLister(lister)
4503
    {
4504
        local tab;
4505
4506
        /* if desired, reveal any "Hidden" items concealed within */
4507
        if (revealHiddenItems)
4508
        {
4509
            /* scan our contents and reveal each Hidden item */
4510
            foreach (local cur in contents)
4511
            {
4512
                /* if it's a Hidden item, reveal it */
4513
                if (cur.ofKind(Hidden))
4514
                    cur.discover();
4515
            }
4516
        }
4517
4518
        /* get my visible sense info */
4519
        tab = gActor.visibleInfoTable();
4520
            
4521
        /* show my contents, if I have any */
4522
        lister.showList(gActor, self, contents, ListRecurse,  0, tab, nil);
4523
4524
        /* mark my contents as having been seen */
4525
        setContentsSeenBy(tab, gActor);
4526
4527
        /* examine my special contents */
4528
        examineSpecialContents();
4529
    }
4530
4531
    /*
4532
     *   Verify putting something new in my interior.  This is suitable
4533
     *   for use as a verify() method for a command like PutIn or PutOn.
4534
     *   Note that this routine assumes and requires that gDobj be the
4535
     *   object to be added, and gIobj be self.  
4536
     */
4537
    verifyPutInInterior()
4538
    {
4539
        /* 
4540
         *   if we haven't resolved the direct object yet, we can at least
4541
         *   check to see if all of the potential direct objects are
4542
         *   already in me, and rule out this indirect object as illogical
4543
         *   if so 
4544
         */
4545
        if (gDobj == nil)
4546
        {
4547
            /* 
4548
             *   check the tentative direct objects to see if (1) all of
4549
             *   them are directly inside me already, or (2) all of them
4550
             *   are at least indirectly inside me already 
4551
             */
4552
            if (gTentativeDobj.indexWhich(
4553
                {x: !x.obj_.isDirectlyIn(self)}) == nil)
4554
            {
4555
                /*
4556
                 *   All of the potential direct objects are already
4557
                 *   directly inside me.  This makes this object
4558
                 *   illogical, since there's no need to move any of these
4559
                 *   objects into me.  
4560
                 */
4561
                illogicalAlready(&alreadyPutInMsg);
4562
            }
4563
            else if (gTentativeDobj.indexWhich(
4564
                {x: !x.obj_.isIn(self)}) == nil)
4565
            {
4566
                /* 
4567
                 *   All of the potential direct objects are already in
4568
                 *   me, at least indirectly.  This makes this object
4569
                 *   somewhat less likely, since we're more likely to want
4570
                 *   to put something in here that wasn't already within.
4571
                 *   Note that this isn't actually illogical, though,
4572
                 *   since we could be moving something from deeper inside
4573
                 *   me to directly inside me.  
4574
                 */
4575
                logicalRank(50, 'dobjs already inside');
4576
            }
4577
        }
4578
        else
4579
        {
4580
            /* 
4581
             *   We can't put myself in myself, obviously.  We also can't
4582
             *   put something into any component of itself, so the command
4583
             *   is illogical if we're a component of the direct object. 
4584
             */
4585
            if (gDobj == self || isComponentOf(gDobj))
4586
                illogicalSelf(&cannotPutInSelfMsg);
4587
4588
            /* if it's already directly inside me, this is illogical */
4589
            if (gDobj.isDirectlyIn(self))
4590
                illogicalAlready(&alreadyPutInMsg);
4591
        }
4592
4593
        /* 
4594
         *   if I'm not held by the actor, give myself a slightly lower
4595
         *   ranking than fully logical, so that objects being held are
4596
         *   preferred 
4597
         */
4598
        if (!isIn(gActor))
4599
            logicalRank(60, 'not indirectly held');
4600
        else if (!isHeldBy(gActor))
4601
            logicalRank(70, 'not held');
4602
    }
4603
4604
    /*
4605
     *   Flag: reveal any hidden items contained directly within me when
4606
     *   my interior is explicitly examined, via a command such as LOOK IN
4607
     *   <self>.  By default, we reveal our hidden contents on
4608
     *   examination; hidden objects are in most cases meant to be more
4609
     *   inconspicuous than actually camouflaged, so a careful, explicit
4610
     *   examination would normally reveal them.  If our hidden objects
4611
     *   are so concealed that even explicit examination of our interior
4612
     *   wouldn't reveal them, set this to nil.  
4613
     */
4614
    revealHiddenItems = true
4615
;
4616
4617
4618
/* ------------------------------------------------------------------------ */
4619
/*
4620
 *   A basic container is an object that can enclose its contents.  This is
4621
 *   the core of the Container type, but this class only has the bare-bones
4622
 *   sense-related enclosing features, without any action implementation.
4623
 *   This can be used for cases where an object isn't meant to have its
4624
 *   contents be manipulable by the player (so we don't want to allow "put
4625
 *   in" and so on), but where we do want the ability to conceal our
4626
 *   contents when we're closed.  
4627
 */
4628
class BasicContainer: BulkLimiter
4629
    /* 
4630
     *   My current open/closed state.  By default, this state never
4631
     *   changes, but is fixed in the object's definition; for example, a
4632
     *   box without a lid would always be open, while a hollow glass cube
4633
     *   would always be closed.  Our default state is open. 
4634
     */
4635
    isOpen = true
4636
4637
    /* the material that we're made of */
4638
    material = adventium
4639
4640
    /* prepositional phrase for objects being put into me */
4641
    putDestMessage = &putDestContainer
4642
4643
    /*
4644
     *   Determine if I can move an object via a path through this
4645
     *   container. 
4646
     */
4647
    checkMoveViaPath(obj, dest, op)
4648
    {
4649
        /* 
4650
         *   if we're moving the object in or out of me, we must consider
4651
         *   our openness and whether or not the object fits through our
4652
         *   opening 
4653
         */
4654
        if (op is in (PathIn, PathOut))
4655
        {
4656
            /* if we're closed, we can't move anything in or out */
4657
            if (!isOpen)
4658
                return new CheckStatusFailure(cannotMoveThroughMsg,
4659
                                              obj, self);
4660
4661
            /* if it doesn't fit through our opening, don't allow it */
4662
            if (!canFitObjThruOpening(obj))
4663
                return new CheckStatusFailure(op == PathIn
4664
                                              ? &cannotFitIntoOpeningMsg
4665
                                              : &cannotFitOutOfOpeningMsg,
4666
                                              obj, self);
4667
        }
4668
        
4669
        /* in any other cases, allow the operation */
4670
        return checkStatusSuccess;
4671
    }
4672
4673
    /* 
4674
     *   The message property we use when we can't move an object through
4675
     *   the containment boundary.  This is a playerActionMessages
4676
     *   property.  
4677
     */
4678
    cannotMoveThroughMsg = &cannotMoveThroughContainerMsg
4679
4680
    /*
4681
     *   Determine if an actor can touch an object via a path through this
4682
     *   container.  
4683
     */
4684
    checkTouchViaPath(obj, dest, op)
4685
    {
4686
        /* 
4687
         *   if we're reaching from inside directly to me, allow it -
4688
         *   treat this as touching our interior, which we allow from
4689
         *   within regardless of our open/closed status 
4690
         */
4691
        if (op == PathOut && dest == self)
4692
            return checkStatusSuccess;
4693
4694
        /* 
4695
         *   if we're reaching in or out of me, consider our openness and
4696
         *   whether or not the actor's hand fits through our opening 
4697
         */
4698
        if (op is in (PathIn, PathOut))
4699
        {
4700
            /* if we're closed, we can't reach into/out of the container */
4701
            if (!isOpen)
4702
                return new CheckStatusFailure(cannotTouchThroughMsg,
4703
                                              obj, self);
4704
4705
            /* 
4706
             *   if the object's "hand" doesn't fit through our opening,
4707
             *   don't allow it 
4708
             */
4709
            if (!canObjReachThruOpening(obj))
4710
                return new CheckStatusFailure(op == PathIn
4711
                                              ? &cannotReachIntoOpeningMsg
4712
                                              : &cannotReachOutOfOpeningMsg,
4713
                                              obj, self);
4714
        }
4715
        
4716
        /* in any other cases, allow the operation */
4717
        return checkStatusSuccess;
4718
    }
4719
4720
    /* 
4721
     *   Library message (in playerActionMessages) explaining why we can't
4722
     *   touch an object through this container.  This is used when an
4723
     *   actor on the outside tries to reach something on the inside, or
4724
     *   vice versa.  
4725
     */
4726
    cannotTouchThroughMsg = &cannotTouchThroughContainerMsg
4727
4728
    /*
4729
     *   Determine if the given object fits through our opening.  This is
4730
     *   only called when we're open; this determines if the object can be
4731
     *   moved in or out of this container.  By default, we'll return
4732
     *   true; some objects might want to override this to disallow
4733
     *   objects over a certain size from being moved in or out of this
4734
     *   container.
4735
     *   
4736
     *   Note that this method doesn't care whether or not the object can
4737
     *   actually fit inside the container once through the opening; we
4738
     *   only care about whether or not the object can fit through the
4739
     *   opening itself.  This allows for things like narrow-mouthed
4740
     *   bottles which have greater capacity within than in their
4741
     *   openings.  
4742
     */
4743
    canFitObjThruOpening(obj) { return true; }
4744
4745
    /*
4746
     *   Determine if the given object can "reach" through our opening,
4747
     *   for the purposes of touching an object on the other side of the
4748
     *   opening.  This is used to determine if the object, which is
4749
     *   usually an actor, can its "hand" (or whatever appendange 'obj'
4750
     *   uses to reach things) through our opening.  This is only called
4751
     *   when we're open.  By default, we'll simply return true.
4752
     *   
4753
     *   This differs from canFitObjThruOpening() in that we don't care if
4754
     *   all of 'obj' is able to fit through the opening; we only care
4755
     *   whether obj's hand (or whatever it uses for reaching) can fit.  
4756
     */
4757
    canObjReachThruOpening(obj) { return true; }
4758
4759
    /*
4760
     *   Determine how a sense passes to my contents.  If I'm open, the
4761
     *   sense passes through directly, since there's nothing in the way.
4762
     *   If I'm closed, the sense must pass through my material.  
4763
     */
4764
    transSensingIn(sense)
4765
    {
4766
        if (isOpen)
4767
        {
4768
            /* I'm open, so the sense passes through without interference */
4769
            return transparent;
4770
        }
4771
        else
4772
        {
4773
            /* I'm closed, so the sense must pass through my material */
4774
            return material.senseThru(sense);
4775
        }
4776
    }
4777
4778
    /*
4779
     *   Get my fill medium.  If I'm open, inherit my parent's medium,
4780
     *   assuming that the medium behaves like fog or smoke and naturally
4781
     *   disperses to fill any nested open containers.  If I'm closed, I
4782
     *   am by default filled with no medium.  
4783
     */
4784
    fillMedium()
4785
    {
4786
        if (isOpen && location != nil)
4787
        {
4788
            /* I'm open, so return my location's medium */
4789
            return location.fillMedium();
4790
        }
4791
        else
4792
        {
4793
            /* 
4794
             *   I'm closed, so we're cut off from the parent - assume
4795
             *   we're filled with nothing 
4796
             */
4797
            return nil;
4798
        }
4799
    }
4800
4801
    /*
4802
     *   Display a message explaining why we are obstructing a sense path
4803
     *   to the given object.
4804
     */
4805
    cannotReachObject(obj)
4806
    {
4807
        /* 
4808
         *   We must be obstructing by containment.  Show an appropriate
4809
         *   message depending on whether the object is inside me or not -
4810
         *   if not, then the actor trying to reach the object must be
4811
         *   inside me. 
4812
         */
4813
        if (obj.isIn(self))
4814
            gLibMessages.cannotReachContents(obj, self);
4815
        else
4816
            gLibMessages.cannotReachOutside(obj, self);
4817
    }
4818
4819
    /* explain why we can't see the source of a sound */
4820
    cannotSeeSoundSource(obj)
4821
    {
4822
        /* we must be obstructing by containment */
4823
        if (obj.isIn(self))
4824
            gLibMessages.soundIsFromWithin(obj, self);
4825
        else
4826
            gLibMessages.soundIsFromWithout(obj, self);
4827
    }
4828
4829
    /* explain why we can't see the source of an odor */
4830
    cannotSeeSmellSource(obj)
4831
    {
4832
        /* we must be obstructing by containment */
4833
        if (obj.isIn(self))
4834
            gLibMessages.smellIsFromWithin(obj, self);
4835
        else
4836
            gLibMessages.smellIsFromWithout(obj, self);
4837
    }
4838
4839
;
4840
4841
/* ------------------------------------------------------------------------ */
4842
/*
4843
 *   Container: an object that can have other objects placed within it.  
4844
 */
4845
class Container: BasicContainer
4846
    /*
4847
     *   Our fixed "look in" description, if any.  This is shown on LOOK
4848
     *   IN before our normal listing of our portable contents; it can be
4849
     *   used to describe generally what the interior looks like, for
4850
     *   example.  By default, we show nothing here.  
4851
     */
4852
    lookInDesc = nil
4853
4854
    /* 
4855
     *   Show our status for "examine".  This shows our open/closed status,
4856
     *   and lists our contents. 
4857
     */
4858
    examineStatus()
4859
    {
4860
        /* show any special container-specific status */
4861
        examineContainerStatus();
4862
4863
        /* inherit the default handling to show my contents */
4864
        inherited();
4865
    }
4866
4867
    /*
4868
     *   mention my open/closed status for Examine processing 
4869
     */
4870
    examineContainerStatus()
4871
    {
4872
        /*
4873
         *   By default, show nothing extra.  This can be overridden by
4874
         *   subclasses as needed to show any extra status before our
4875
         *   contents list.  
4876
         */
4877
    }
4878
4879
    /*
4880
     *   Try putting an object into me when I'm serving as a bag of
4881
     *   holding.  For a container, this simply does a "put obj in bag".  
4882
     */
4883
    tryPuttingObjInBag(target)
4884
    {
4885
        /* if the object won't fit all by itself, don't even try */
4886
        if (target.getBulk() > maxSingleBulk)
4887
            return nil;
4888
4889
        /* if we can't fit the object with other contents, don't try */
4890
        if (target.whatIf({: getBulkWithin() > bulkCapacity},
4891
                          &moveInto, self))
4892
            return nil;
4893
4894
        /* we're a container, so use "put in" to get the object */
4895
        return tryImplicitActionMsg(&announceMoveToBag, PutIn, target, self);
4896
    }
4897
4898
    /* 
4899
     *   Try moving an object into this container.  For a container, this
4900
     *   performs a PUT IN command to move the object into self.  
4901
     */
4902
    tryMovingObjInto(obj) { return tryImplicitAction(PutIn, obj, self); }
4903
4904
    /* -------------------------------------------------------------------- */
4905
    /*
4906
     *   "Look in" 
4907
     */
4908
    dobjFor(LookIn)
4909
    {
4910
        verify() { }
4911
        check()
4912
        {
4913
            /* 
4914
             *   If I'm closed, and I can't see my contents when closed, we
4915
             *   can't go on.  Unless, of course, the actor is inside us,
4916
             *   in which case our external boundary isn't relevant.  
4917
             */
4918
            if (!isOpen
4919
                && transSensingIn(sight) == opaque
4920
                && !gActor.isIn(self))
4921
            {
4922
                /* we can't see anything because we're closed */
4923
                reportFailure(&cannotLookInClosedMsg);
4924
                exit;
4925
            }
4926
        }
4927
        action()
4928
        {
4929
            /* show our fixed "look in" description, if any */
4930
            lookInDesc;
4931
4932
            /* examine my interior */
4933
            examineInterior();
4934
        }
4935
    }
4936
4937
    /*
4938
     *   "Search".  This is mostly like Open, except that the actor has to
4939
     *   be able to reach into the object, not just see into it - searching
4940
     *   implies a more thorough sort of examination, usually including
4941
     *   physically poking through the object's contents.  
4942
     */
4943
    dobjFor(Search)
4944
    {
4945
        preCond = (nilToList(inherited()) + [touchObj])
4946
        check()
4947
        {
4948
            /* 
4949
             *   if I'm closed, and the actor isn't inside me, make sure my
4950
             *   contents are reachable from the outside 
4951
             */
4952
            if (!isOpen
4953
                && transSensingIn(touch) != transparent
4954
                && !gActor.isIn(self))
4955
            {
4956
                /* we can't search an object that we can't reach into */
4957
                reportFailure(&cannotTouchThroughMsg, gActor, self);
4958
                exit;
4959
            }
4960
        }
4961
    }
4962
    
4963
4964
    /* -------------------------------------------------------------------- */
4965
    /*
4966
     *   Put In processing.  A container can accept new contents. 
4967
     */
4968
4969
    iobjFor(PutIn)
4970
    {
4971
        verify()
4972
        {
4973
            /* use the standard verification for adding new contents */
4974
            verifyPutInInterior();
4975
        }
4976
4977
        action()
4978
        {
4979
            /* move the direct object into me */
4980
            gDobj.moveInto(self);
4981
            
4982
            /* issue our default acknowledgment of the command */
4983
            defaultReport(&okayPutInMsg);
4984
        }
4985
    }
4986
;
4987
4988
4989
/*
4990
 *   A "restricted holder" is a generic mix-in class for various container
4991
 *   types (Containers, Surfaces, Undersides, RearContainers, RearSurfaces)
4992
 *   that adds a restriction to what can be contained.  
4993
 */
4994
class RestrictedHolder: object
4995
    /* 
4996
     *   A list of acceptable items for the container.  This list can be
4997
     *   used to identify the objects that can be put in the container (or
4998
     *   on the surface, under the underside, or behind the rear container
4999
     *   or surface).  
5000
     */
5001
    validContents = []
5002
5003
    /*
5004
     *   Is the given object allowed to go in this container (or
5005
     *   on/under/behind it, as appropriate for the type)?  Returns true if
5006
     *   so, nil if not.  By default, we'll return true if the object is
5007
     *   found in our validContents list, nil if not.  This can be
5008
     *   overridden if a subclass wants to determine which objects are
5009
     *   acceptable with some other kind of per-object test; for example, a
5010
     *   subclass might accept only objects of a given class as contents,
5011
     *   or might accept only contents with some particular attribute.  
5012
     */
5013
    canPutIn(obj) { return validContents.indexOf(obj) != nil; }
5014
5015
    /*
5016
     *   Check a PUT IN/ON/UNDER/BEHIND action to ensure that the direct
5017
     *   object is in our approved-contents list.  
5018
     */
5019
    checkPutDobj(msgProp)
5020
    {
5021
        /* validate the direct object */
5022
        if (!canPutIn(gDobj))
5023
        {
5024
            /* explain the problem */
5025
            reportFailure(self.(msgProp)(gDobj));
5026
5027
            /* terminate the command */
5028
            exit;
5029
        }
5030
    }
5031
;
5032
5033
5034
/*
5035
 *   A special kind of container that only accepts specific contents.  The
5036
 *   acceptable contents can be specified by a list of enumerated items,
5037
 *   or by a method that indicates whether or not an item is allowed.  
5038
 */
5039
class RestrictedContainer: RestrictedHolder, Container
5040
    /* 
5041
     *   A message that explains why the direct object can't be put in this
5042
     *   container.  In most cases, the rather generic default message
5043
     *   should be overridden to provide a specific reason that the dobj
5044
     *   can't be put in this object.  The rejected object is provided as a
5045
     *   parameter in case the message needs to vary by object, but we
5046
     *   ignore this and just use a single blanket failure message by
5047
     *   default.  
5048
     */
5049
    cannotPutInMsg(obj) { return &cannotPutInRestrictedMsg; }
5050
5051
    /* override PutIn to enforce our contents restriction */
5052
    iobjFor(PutIn) { check() { checkPutDobj(&cannotPutInMsg); } }
5053
;
5054
5055
/*
5056
 *   A single container is a special kind of container that can only
5057
 *   contain a single item.  If another object is put into this container,
5058
 *   we'll remove any current contents.  
5059
 */
5060
class SingleContainer: Container
5061
    /* override PutIn to enforce our single-contents rule */
5062
    iobjFor(PutIn)
5063
    {
5064
        preCond { return inherited() + objEmpty; }
5065
    }
5066
;
5067
5068
/* ------------------------------------------------------------------------ */
5069
/*
5070
 *   OpenableContainer: an object that can contain things, and which can
5071
 *   be opened and closed.  
5072
 */
5073
class OpenableContainer: Openable, Container
5074
;
5075
5076
/* ------------------------------------------------------------------------ */
5077
/*
5078
 *   LockableContainer: an object that can contain things, and that can be
5079
 *   opened and closed as well as locked and unlocked.  
5080
 */
5081
class LockableContainer: Lockable, OpenableContainer
5082
;
5083
5084
/* ------------------------------------------------------------------------ */
5085
/*
5086
 *   KeyedContainer: an openable container that can be locked and
5087
 *   unlocked, but only with a specified key.  
5088
 */
5089
class KeyedContainer: LockableWithKey, OpenableContainer
5090
;
5091
5092
5093
/* ------------------------------------------------------------------------ */
5094
/*
5095
 *   Surface: an object that can have other objects placed on top of it.
5096
 *   A surface is essentially the same as a regular container, but the
5097
 *   contents of a surface behave as though they are on the surface's top
5098
 *   rather than contained within the object.  
5099
 */
5100
class Surface: BulkLimiter
5101
    /* 
5102
     *   Our fixed LOOK IN description.  This is shown in response to LOOK
5103
     *   IN before we list our portable contents; it can be used to show
5104
     *   generally what the surface looks like.  By default, we say
5105
     *   nothing here.  
5106
     */
5107
    lookInDesc = nil
5108
5109
    /* my contents lister */
5110
    contentsLister = surfaceContentsLister
5111
    descContentsLister = surfaceDescContentsLister
5112
    lookInLister = surfaceLookInLister
5113
    inlineContentsLister = surfaceInlineContentsLister
5114
5115
    /* 
5116
     *   we're a surface, so taking something from me that's not among my
5117
     *   contents shows the message as "that's not on the iobj" 
5118
     */
5119
    takeFromNotInMessage = &takeFromNotOnMsg
5120
5121
    /* 
5122
     *   my message indicating that another object x cannot be put into me
5123
     *   because I'm already in x 
5124
     */
5125
    circularlyInMessage = &circularlyOnMsg
5126
5127
    /* message phrase for objects put into me */
5128
    putDestMessage = &putDestSurface
5129
5130
    /* message when we're too full for another object */
5131
    tooFullMsg = &surfaceTooFullMsg
5132
5133
    /* 
5134
     *   Try moving an object into this container.  For a surface, this
5135
     *   performs a PUT ON command to move the object onto self.  
5136
     */
5137
    tryMovingObjInto(obj) { return tryImplicitAction(PutOn, obj, self); }
5138
5139
    /* -------------------------------------------------------------------- */
5140
    /*
5141
     *   Put On processing 
5142
     */
5143
    iobjFor(PutOn)
5144
    {
5145
        verify()
5146
        {
5147
            /* use the standard put-in verification */
5148
            verifyPutInInterior();
5149
        }
5150
5151
        action()
5152
        {
5153
            /* move the direct object onto me */
5154
            gDobj.moveInto(self);
5155
            
5156
            /* issue our default acknowledgment */
5157
            defaultReport(&okayPutOnMsg);
5158
        }
5159
    }
5160
5161
    /*
5162
     *   Looking "in" a surface simply shows the surface's contents. 
5163
     */
5164
    dobjFor(LookIn)
5165
    {
5166
        verify() { }
5167
        action()
5168
        {
5169
            /* show our fixed lookInDesc */
5170
            lookInDesc;
5171
5172
            /* show our contents */
5173
            examineInterior();
5174
        }
5175
    }
5176
5177
    /* use the PUT ON forms of the verifier messages */
5178
    cannotPutInSelfMsg = &cannotPutOnSelfMsg
5179
    alreadyPutInMsg = &alreadyPutOnMsg
5180
;
5181
5182
/*
5183
 *   A special kind of surface that only accepts specific contents.
5184
 */
5185
class RestrictedSurface: RestrictedHolder, Surface
5186
    /* 
5187
     *   A message that explains why the direct object can't be put on this
5188
     *   surface.  In most cases, the rather generic default message should
5189
     *   be overridden to provide a specific reason that the dobj can't be
5190
     *   put on this surface.  The rejected object is provided as a
5191
     *   parameter in case the message needs to vary by object, but we
5192
     *   ignore this and just use a single blanket failure message by
5193
     *   default.  
5194
     */
5195
    cannotPutOnMsg(obj) { return &cannotPutOnRestrictedMsg; }
5196
5197
    /* override PutOn to enforce our contents restriction */
5198
    iobjFor(PutOn) { check() { checkPutDobj(&cannotPutOnMsg); } }
5199
;
5200
5201
/* ------------------------------------------------------------------------ */
5202
/*
5203
 *   Food - something you can eat.  By default, when an actor eats a food
5204
 *   item, the item disappears.  
5205
 */
5206
class Food: Thing
5207
    dobjFor(Taste)
5208
    {
5209
        /* tasting food is perfectly logical */
5210
        verify() { }
5211
    }
5212
5213
    dobjFor(Eat)
5214
    {
5215
        verify() { }
5216
        action()
5217
        {
5218
            /* describe the consumption */
5219
            defaultReport(&okayEatMsg);
5220
5221
            /* the object disappears */
5222
            moveInto(nil);
5223
        }
5224
    }
5225
;
5226
5227
/* ------------------------------------------------------------------------ */
5228
/*
5229
 *   OnOffControl - a generic control that can be turned on and off.  We
5230
 *   keep track of an internal on/off state, and recognize the commands
5231
 *   "turn on" and "turn off".  
5232
 */
5233
class OnOffControl: Thing
5234
    /*
5235
     *   The current on/off setting.  We'll start in the 'off' position by
5236
     *   default.  
5237
     */
5238
    isOn = nil
5239
5240
    /*
5241
     *   On/off status name.  This returns the appropriate name ('on' or
5242
     *   'off' in English) for our current status. 
5243
     */
5244
    onDesc = (isOn ? gLibMessages.onMsg(self) : gLibMessages.offMsg(self))
5245
5246
    /*
5247
     *   Change our on/off setting.  Subclasses can override this to apply
5248
     *   any side effects of changing the value. 
5249
     */
5250
    makeOn(val)
5251
    {
5252
        /* remember the new value */
5253
        isOn = val;
5254
    }
5255
5256
    dobjFor(TurnOn)
5257
    {
5258
        verify()
5259
        {
5260
            /* if it's already on, complain */
5261
            if (isOn)
5262
                illogicalAlready(&alreadySwitchedOnMsg);
5263
        }
5264
        action()
5265
        {
5266
            /* set to 'on' and generate a default report */
5267
            makeOn(true);
5268
            defaultReport(&okayTurnOnMsg);
5269
        }
5270
    }
5271
5272
    dobjFor(TurnOff)
5273
    {
5274
        verify()
5275
        {
5276
            /* if it's already off, complain */
5277
            if (!isOn)
5278
                illogicalAlready(&alreadySwitchedOffMsg);
5279
        }
5280
        action()
5281
        {
5282
            /* set to 'off' and generate a default report */
5283
            makeOn(nil);
5284
            defaultReport(&okayTurnOffMsg);
5285
        }
5286
    }
5287
;
5288
5289
/*
5290
 *   Switch - a simple extension of the generic on/off control that can be
5291
 *   used with a "switch" command without specifying "on" or "off", and
5292
 *   treats "flip" synonymously.  
5293
 */
5294
class Switch: OnOffControl
5295
    /* "switch" with no specific new setting - reverse our setting */
5296
    dobjFor(Switch)
5297
    {
5298
        verify() { }
5299
        action()
5300
        {
5301
            /* reverse our setting and generate a report */
5302
            makeOn(!isOn);
5303
            defaultReport(isOn ? &okayTurnOnMsg : &okayTurnOffMsg);
5304
        }
5305
    }
5306
5307
    /* "flip" is the same as "switch" for our purposes */
5308
    dobjFor(Flip) asDobjFor(Switch)
5309
;
5310
5311
/* ------------------------------------------------------------------------ */
5312
/*
5313
 *   Settable - an abstract class for things you can set to different
5314
 *   settings; the settings can be essentially anything, such as numbers
5315
 *   (or other markers) on a dial, or stops on a sliding switch. 
5316
 */
5317
class Settable: Thing
5318
    /*
5319
     *   Our current setting.  This is an arbitrary string value.  The
5320
     *   value initially assigned here is our initial setting; we'll
5321
     *   update this whenever we're set to another setting.  
5322
     */
5323
    curSetting = '1'
5324
5325
    /*
5326
     *   Canonicalize a proposed setting.  This ensures that the setting is
5327
     *   in a specific primary format when there are superficially
5328
     *   different ways of expressing the same value.  For example, if the
5329
     *   setting is numeric, this could do things like trim off leading
5330
     *   zeroes; for a text value, it could ensure the value is in the
5331
     *   proper case.  
5332
     */
5333
    canonicalizeSetting(val)
5334
    {
5335
        /* 
5336
         *   by default, we don't have any special canonical format, so
5337
         *   just return the value as it is 
5338
         */
5339
        return val;
5340
    }
5341
5342
    /*
5343
     *   Change our setting.  This is always called with the canonical
5344
     *   version of the new setting, as returned by canonicalizeSetting().
5345
     *   Subclasses can override this routine to apply any side effects of
5346
     *   changing the value.  
5347
     */
5348
    makeSetting(val)
5349
    {
5350
        /* remember the new value */
5351
        curSetting = val;
5352
    }
5353
5354
    /*
5355
     *   Is the given text a valid setting?  Returns true if so, nil if
5356
     *   not.  This should not display any messages; simply indicate
5357
     *   whether or not the setting is valid.
5358
     *   
5359
     *   This is always called with the *canonical* value of the proposed
5360
     *   new setting, as returned by canonicalizeSetting().  
5361
     */
5362
    isValidSetting(val)
5363
    {
5364
        /* 
5365
         *   By default, allow anything; subclasses should override to
5366
         *   enforce our valid set of values.  
5367
         */
5368
        return true;
5369
    }
5370
5371
    /*
5372
     *   "set <self>" action 
5373
     */
5374
    dobjFor(Set)
5375
    {
5376
        verify() { logicalRank(150, 'settable'); }
5377
        action() { askForLiteral(SetTo); }
5378
    }
5379
5380
    /*
5381
     *   "set <self> to <literal>" action
5382
     */
5383
    dobjFor(SetTo)
5384
    {
5385
        preCond = [touchObj]
5386
        verify()
5387
        {
5388
            local txt;
5389
            
5390
            /* 
5391
             *   If we already know our literal text, and it's not valid,
5392
             *   reduce the logicalness.  Don't actually make it
5393
             *   illogical, as it's probably still more logical to set a
5394
             *   settable to an invalid setting than to set something that
5395
             *   isn't settable at all.  
5396
             */
5397
            if ((txt = gAction.getLiteral()) != nil
5398
                && !isValidSetting(canonicalizeSetting(txt)))
5399
                logicalRank(50, 'invalid setting');
5400
        }
5401
        check()
5402
        {
5403
            /* if the setting is not valid, don't allow it */
5404
            if (!isValidSetting(canonicalizeSetting(gAction.getLiteral())))
5405
            {
5406
                /* there is no such setting */
5407
                reportFailure(setToInvalidMsgProp);
5408
                exit;
5409
            }
5410
        }
5411
        action()
5412
        {
5413
            /* set the new value */
5414
            makeSetting(canonicalizeSetting(gAction.getLiteral()));
5415
5416
            /* remark on the change */
5417
            defaultReport(okaySetToMsgProp, curSetting);
5418
        }
5419
    }
5420
5421
    /* our message property for an invalid setting */
5422
    setToInvalidMsgProp = &setToInvalidMsg
5423
5424
    /* our message property for acknowledging a new setting */
5425
    okaySetToMsgProp = &okaySetToMsg
5426
;
5427
5428
/*
5429
 *   Dial - something you can turn to different settings.  Note that dials
5430
 *   are usually used as components of larger objects; since our base
5431
 *   class is the basic Settable, component dials should be created to
5432
 *   inherit multiply from Dial and Component, in that order.
5433
 *   
5434
 *   This is almost hte same as a regular Settable; the only thing we add
5435
 *   is that we make "turn <self> to <literal>" equivalent to "set <self>
5436
 *   to <literal>", as this is the verb most people would use to set a
5437
 *   dial.  
5438
 */
5439
class Dial: Settable
5440
    /* "turn" with no destination - indicate that we need a setting */
5441
    dobjFor(Turn)
5442
    {
5443
        verify() { illogical(&mustSpecifyTurnToMsg); }
5444
    }
5445
5446
    /* treat "turn <self> to <literal>" the same as "set to" */
5447
    dobjFor(TurnTo) asDobjFor(SetTo)
5448
5449
    /* refer to setting the dial as turning it in our messages */
5450
    setToInvalidMsgProp = &turnToInvalidMsg
5451
    okaySetToMsgProp = &okayTurnToMsg
5452
;
5453
5454
/*
5455
 *   Numbered Dial - something you can turn to a range of numeric values. 
5456
 */
5457
class NumberedDial: Dial
5458
    /*
5459
     *   The range of settings - the dial can be set to values from the
5460
     *   minimum to the maximum, inclusive. 
5461
     */
5462
    minSetting = 1
5463
    maxSetting = 10
5464
5465
    /*
5466
     *   Canonicalize a proposed setting value.  For numbers, strip off any
5467
     *   leading zeroes, since these don't change the meaning of the value.
5468
     */
5469
    canonicalizeSetting(val)
5470
    {
5471
        local num;
5472
        
5473
        /* try parsing it as a digit string or a spelled-out number */
5474
        if ((num = parseInt(val)) != nil)
5475
        {
5476
            /* 
5477
             *   we parsed it successfully - return the string
5478
             *   representation of the numeric value 
5479
             */
5480
            return toString(num);
5481
        }
5482
5483
        /* it didn't parse as a number, so just return it as-is */
5484
        return val;
5485
    }
5486
5487
    /* 
5488
     *   Check a setting for validity.  A setting is valid only if it's a
5489
     *   number within the allowed range for the dial. 
5490
     */
5491
    isValidSetting(val)
5492
    {
5493
        local num;
5494
        
5495
        /* if it doesn't look like a number, it's not valid */
5496
        if (rexMatch('<digit>+', val) != val.length())
5497
            return nil;
5498
5499
        /* get the numeric value */
5500
        num = toInteger(val);
5501
5502
        /* it's valid if it's within range */
5503
        return num >= minSetting && num <= maxSetting;
5504
    }
5505
;
5506
5507
/*
5508
 *   Labeled Dial - something you can turn to a set of arbitrary text
5509
 *   labels. 
5510
 */
5511
class LabeledDial: Dial
5512
    /*
5513
     *   The list of valid settings.  Each entry in this list should be a
5514
     *   string value.  We ignore the case of these labels (we convert
5515
     *   everything to upper-case when comparing labels).  
5516
     */
5517
    validSettings = []
5518
5519
    /*
5520
     *   Canonicalize the setting.  We consider case insignificant in
5521
     *   matching our labels, but the canonical version of a setting is the
5522
     *   one that appears in the validSettings list - so if the player
5523
     *   types in SET DIAL TO EXTRA LOUD, and the validSettings list
5524
     *   contains 'Extra Loud', we'll want to convert the 'EXTRA LOUD' to
5525
     *   the capitalization of the validSettings entry.  
5526
     */
5527
    canonicalizeSetting(val)
5528
    {
5529
        local txt;
5530
        
5531
        /* 
5532
         *   convert it to upper-case, so that we can compare it to our
5533
         *   valid labels without regard to case
5534
         */
5535
        txt = val.toUpper();
5536
5537
        /* 
5538
         *   if we find a match in the validSettings list, return the match
5539
         *   from the list, since that's the canonical format 
5540
         */
5541
        if ((txt = validSettings.valWhich({x: x.toUpper() == txt})) != nil)
5542
            return txt;
5543
5544
        /* we didn't find a match, so leave the original value unchanged */
5545
        return val;
5546
    }
5547
5548
    /* 
5549
     *   Check a setting for validity.  A setting is valid only if it
5550
     *   appears in the validSettings list for this dial.
5551
     */
5552
    isValidSetting(val)
5553
    {
5554
        /* 
5555
         *   If the given value appears in our validSettings list, it's a
5556
         *   valid setting; otherwise, it's not valid.  Ignore case when
5557
         *   comparing values by converting the valid labels to upper case;
5558
         *   we've already converted the value we're testing to upper case,
5559
         *   so the case mix won't matter in our comparison.
5560
         *   
5561
         *   Note that we're handed a canonical setting value, so we don't
5562
         *   have to worry about case differences.  
5563
         */
5564
        return validSettings.indexOf(val) != nil;
5565
    }
5566
;
5567
5568
5569
/* ------------------------------------------------------------------------ */
5570
/*
5571
 *   Button - something you can push to activate, as a control for a
5572
 *   mechanical device.  
5573
 */
5574
class Button: Thing
5575
    dobjFor(Push)
5576
    {
5577
        verify() { }
5578
        action()
5579
        {
5580
            /* 
5581
             *   individual buttons should override this to carry out any
5582
             *   special action for the button; by default, we'll just
5583
             *   show a simple acknowledgment  
5584
             */
5585
            defaultReport(&okayPushButtonMsg);
5586
        }
5587
    }
5588
;
5589
5590
/* ------------------------------------------------------------------------ */
5591
/*
5592
 *   Lever - something you can push, pull, or move, generally as a control
5593
 *   for a mechanical device.  Our basic lever has two states, "pushed"
5594
 *   and "pulled".  
5595
 */
5596
class Lever: Thing
5597
    /*
5598
     *   The current state.  We have two states: "pushed" and "pulled".
5599
     *   We start in the pushed state, so the lever can initially be
5600
     *   pulled, since "pull" is the verb most people would first think to
5601
     *   apply to a lever.  
5602
     */
5603
    isPulled = nil
5604
5605
    /* 
5606
     *   Set the state.  This can be overridden to apply side effects as
5607
     *   needed. 
5608
     */
5609
    makePulled(pulled)
5610
    {
5611
        /* note the new state */
5612
        isPulled = pulled;
5613
    }
5614
5615
    /*
5616
     *   Action handlers.  We handle push and pull, and we treat "move" as
5617
     *   equivalent to whichever of push or pull is appropriate to reverse
5618
     *   the current state.  
5619
     */
5620
    dobjFor(Push)
5621
    {
5622
        verify()
5623
        {
5624
            /* if it's already pushed, pushing it again makes no sense */
5625
            if (!isPulled)
5626
                illogicalAlready(&alreadyPushedMsg);
5627
        }
5628
        action()
5629
        {
5630
            /* set the new state to pushed (i.e., not pulled) */
5631
            makePulled(nil);
5632
5633
            /* make the default report */
5634
            defaultReport(&okayPushLeverMsg);
5635
        }
5636
    }
5637
    dobjFor(Pull)
5638
    {
5639
        verify()
5640
        {
5641
            /* if it's already pulled, pulling it again makes no sense */
5642
            if (isPulled)
5643
                illogicalAlready(&alreadyPulledMsg);
5644
        }
5645
        action()
5646
        {
5647
            /* set the new state to pulled */
5648
            makePulled(true);
5649
5650
            /* make the default report */
5651
            defaultReport(&okayPullLeverMsg);
5652
        }
5653
    }
5654
    dobjFor(Move)
5655
    {
5656
        verify() { }
5657
        check()
5658
        {
5659
            /* run the check for pushing or pulling, as appropriate */
5660
            if (isPulled)
5661
                checkDobjPush();
5662
            else
5663
                checkDobjPull();
5664
        }
5665
        action()
5666
        {
5667
            /* if we're pulled, push the lever; otherwise pull it */
5668
            if (isPulled)
5669
                actionDobjPush();
5670
            else
5671
                actionDobjPull();
5672
        }
5673
    }
5674
;
5675
5676
/*
5677
 *   A spring-loaded lever is a lever that bounces back to its starting
5678
 *   position after being pulled.  This is essentially equivalent in terms
5679
 *   of functionality to a button, but can at least provide superficial
5680
 *   variety.  
5681
 */
5682
class SpringLever: Lever
5683
    dobjFor(Pull)
5684
    {
5685
        action()
5686
        {
5687
            /*
5688
             *   Individual objects should override this to perform the
5689
             *   appropriate action when the lever is pulled.  By default,
5690
             *   we'll do nothing except show a default report. 
5691
             */
5692
            defaultReport(&okayPullSpringLeverMsg);
5693
        }
5694
    }
5695
;
5696
    
5697
5698
/* ------------------------------------------------------------------------ */
5699
/*
5700
 *   An item that can be worn
5701
 */
5702
class Wearable: Thing
5703
    /* is the item currently being worn? */
5704
    isWorn()
5705
    {
5706
        /* it's being worn if the wearer is non-nil */
5707
        return wornBy != nil;
5708
    }
5709
5710
    /* 
5711
     *   make the item worn by the given actor; if actor is nil, the item
5712
     *   isn't being worn by anyone 
5713
     */
5714
    makeWornBy(actor)
5715
    {
5716
        /* remember who's wearing the item */
5717
        wornBy = actor;
5718
    }
5719
5720
    /*
5721
     *   An item being worn is not considered to be held in the wearer's
5722
     *   hands. 
5723
     */
5724
    isHeldBy(actor)
5725
    {
5726
        if (isWornBy(actor))
5727
        {
5728
            /* it's being worn by the actor, so it's not also being held */
5729
            return nil;
5730
        }
5731
        else
5732
        {
5733
            /* 
5734
             *   it's not being worn by this actor, so use the default
5735
             *   interpretation of being held 
5736
             */
5737
            return inherited(actor);
5738
        }
5739
    }
5740
5741
    /*
5742
     *   A wearable is not considered held by an actor when it is being
5743
     *   worn, so we must do a what-if test for removing the item if the
5744
     *   actor is currently wearing the item.  If the actor isn't wearing
5745
     *   the item, we can use the default test of moving the item into the
5746
     *   actor's inventory.  
5747
     */
5748
    whatIfHeldBy(func, newLoc)
5749
    {
5750
        /*
5751
         *   If the article is being worn, and it's already in the same
5752
         *   location we're moving it to, simply test with the article no
5753
         *   longer being worn.  Otherwise, inherit the default handling.  
5754
         */
5755
        if (location == newLoc && wornBy != nil)
5756
            return whatIf(func, &wornBy, nil);
5757
        else
5758
            return inherited(func, newLoc);
5759
    }
5760
5761
    /*
5762
     *   Try making the current command's actor hold me.  If I'm already
5763
     *   directly in the actor's inventory and I'm being worn, we'll try a
5764
     *   'doff' command; otherwise, we'll use the default handling.  
5765
     */
5766
    tryHolding()
5767
    {
5768
        /*   
5769
         *   Try an implicit 'take' command.  If the actor is carrying the
5770
         *   object indirectly, make the command "take from" instead,
5771
         *   since what we really want to do is take the object out of its
5772
         *   container.  
5773
         */
5774
        if (location == gActor && isWornBy(gActor))
5775
            return tryImplicitAction(Doff, self);
5776
        else
5777
            return inherited();
5778
    }
5779
5780
    /* 
5781
     *   The object wearing this object, if any; if I'm not being worn,
5782
     *   this is nil.  The wearer should always be a container (direct or
5783
     *   indirect) of this object - in order to wear something, you must
5784
     *   be carrying it.  In most cases, the wearer should be the direct
5785
     *   container of the object.
5786
     *   
5787
     *   The reason we keep track of who's wearing the object (rather than
5788
     *   simply keeping track of whether it's being worn) is to allow for
5789
     *   cases where an actor is carrying another actor.  Since this
5790
     *   object will be (indirectly) inside both actors in such cases, we
5791
     *   would have to inspect intermediate containers to determine
5792
     *   whether or not the outer actor was wearing the object if we
5793
     *   didn't keep track of the wearer directly.  
5794
     */
5795
    wornBy = nil
5796
5797
    /* am I worn by the given object? */
5798
    isWornBy(actor)
5799
    {
5800
        return wornBy == actor;
5801
    }
5802
5803
    /*
5804
     *   An article of clothing that is being worn by an actor does not
5805
     *   typically encumber the actor at all, so by default we'll return
5806
     *   zero if we're being worn by the actor, and our normal bulk
5807
     *   otherwise.  
5808
     */
5809
    getEncumberingBulk(actor)
5810
    {
5811
        /* 
5812
         *   if we're being worn by the actor, we create no encumbrance at
5813
         *   all; otherwise, return our normal bulk 
5814
         */
5815
        return isWornBy(actor) ? 0 : getBulk();
5816
    }
5817
5818
    /*
5819
     *   An article of clothing typically encumbers an actor with the same
5820
     *   weight whether or not the actor is wearing the item.  However,
5821
     *   this might not apply to all objects; a suit of armor, for
5822
     *   example, might be slightly less encumbering in terms of weight
5823
     *   when worn than it is when held because the distribution of weight
5824
     *   is more manageable when worn.  By default, we simply return our
5825
     *   normal weight, whether worn or not; subclasses can override as
5826
     *   needed to differentiate.  
5827
     */
5828
    getEncumberingWeight(actor)
5829
    {
5830
        return getWeight();
5831
    }
5832
5833
    /* get my state */
5834
    getState = (isWorn() ? wornState : unwornState)
5835
5836
    /* my list of possible states */
5837
    allStates = [wornState, unwornState]
5838
5839
5840
    /* -------------------------------------------------------------------- */
5841
    /*
5842
     *   Action processing 
5843
     */
5844
5845
    dobjFor(Wear)
5846
    {
5847
        preCond = [objHeld]
5848
        verify()
5849
        {
5850
            /* make sure the actor isn't already wearing the item */
5851
            if (isWornBy(gActor))
5852
                illogicalAlready(&alreadyWearingMsg);
5853
        }
5854
        action()
5855
        {
5856
            /* make the item worn and describe what happened */
5857
            makeWornBy(gActor);
5858
            defaultReport(&okayWearMsg);
5859
        }
5860
    }
5861
5862
    dobjFor(Doff)
5863
    {
5864
        preCond = [roomToHoldObj]
5865
        verify()
5866
        {
5867
            /* 
5868
             *   Make sure the actor is actually wearing the item.  If
5869
             *   they're not, it's illogical, but if they are, it's an
5870
             *   especially likely thing to remove. 
5871
             */
5872
            if (!isWornBy(gActor))
5873
                illogicalAlready(&notWearingMsg);
5874
            else
5875
                logicalRank(150, 'worn');
5876
        }
5877
        action()
5878
        {
5879
            /* un-wear the item and describe what happened */
5880
            makeWornBy(nil);
5881
            defaultReport(&okayDoffMsg);
5882
        }
5883
    }
5884
5885
    /* "remove <wearable>" is the same as "doff <wearable>" */
5886
    dobjFor(Remove) asDobjFor(Doff)
5887
5888
    /* 
5889
     *   if a wearable is being worn, showing it off to someone doesn't
5890
     *   require holding it 
5891
     */
5892
    dobjFor(ShowTo)
5893
    {
5894
        preCond()
5895
        {
5896
            /* get the standard handling */
5897
            local lst = inherited();
5898
5899
            /* if we're being worn, don't require us to be held */
5900
            if (isWornBy(gActor))
5901
                lst -= objHeld;
5902
5903
            /* return the result */
5904
            return lst;
5905
        }
5906
    }
5907
;
5908
5909
/* ------------------------------------------------------------------------ */
5910
/*
5911
 *   An item that can provide light.
5912
 *   
5913
 *   Any Thing can provide light, but this class should be used for
5914
 *   objects that explicitly serve as light sources from the player's
5915
 *   perspective.  Objects of this class display a "providing light"
5916
 *   status message in inventory listings, and can be turned on and off
5917
 *   via the isLit property.  
5918
 */
5919
class LightSource: Thing
5920
    /* is the light source currently turned on? */
5921
    isLit = true
5922
5923
    /*
5924
     *   Turn the light source on or off.  Note that we don't have to make
5925
     *   any special check for a change to the light level, because the
5926
     *   main action handler always checks for a change in light/dark
5927
     *   status over the course of the turn.  
5928
     */
5929
    makeLit(lit)
5930
    {
5931
        /* change the status */
5932
        isLit = lit;
5933
    }
5934
5935
    /* 
5936
     *   We can distinguish light sources according to their isLit status.
5937
     *   Give the lit/unlit distinction higher priority than the normal
5938
     *   ownership/containment distinction. 
5939
     */
5940
    distinguishers = [basicDistinguisher, litUnlitDistinguisher,
5941
                      ownershipDistinguisher, locationDistinguisher]
5942
5943
    /* the brightness that the object has when it is on and off */
5944
    brightnessOn = 3
5945
    brightnessOff = 0
5946
5947
    /* 
5948
     *   return the appropriate on/off brightness, depending on whether or
5949
     *   not we're currently lit 
5950
     */
5951
    brightness { return isLit ? brightnessOn : brightnessOff; }
5952
5953
    /* get our current state: lit or unlit */
5954
    getState = (brightness > 1 ? lightSourceStateOn : lightSourceStateOff)
5955
5956
    /* get our set of possible states */
5957
    allStates = [lightSourceStateOn, lightSourceStateOff]
5958
;
5959
5960
/*
5961
 *   A Flashlight is a special kind of light source that can be switched
5962
 *   on and off.
5963
 *   
5964
 *   To create a limited-use flashlight (with a limited battery life, for
5965
 *   example), you can combine this class with FueledLightSource.  The
5966
 *   flashlight's on/off switch status is a separate property from its
5967
 *   lit/unlit light-source status, so combining Flashlight with
5968
 *   FueledLightSource will actually allow the two to become decoupled: a
5969
 *   flashlight can be on without providing light, when the battery is
5970
 *   dead.  For this reason, you might want to override the decription,
5971
 *   and possibly the TurnOn action() handler, to customize the messages
5972
 *   for the case when the flashlight is switched on but out of power.  
5973
 */
5974
class Flashlight: LightSource, Switch
5975
    /* our switch status - start in the 'off' position */
5976
    isOn = nil
5977
5978
    /* 
5979
     *   Change the on/off status.  Note that switching the flashlight on
5980
     *   or off should always be done via makeOn - the makeLit inherited
5981
     *   from the LightSource should never be called directly on a
5982
     *   Flashlight object, because it doesn't keep the switch on/off and
5983
     *   flashlight lit/unlit status in sync.  This routine is the one to
5984
     *   call because it keeps everything properly synchronized.  
5985
     */
5986
    makeOn(stat)
5987
    {
5988
        /* inherit the default handling */
5989
        inherited(stat);
5990
5991
        /* 
5992
         *   Set the 'lit' status to track the on/off status.  Note that
5993
         *   we don't simply do this by deriving isLit from isOn because
5994
         *   we want to invoke the side effects of changing the status by
5995
         *   calling makeLit explicitly.  We also want to allow the two to
5996
         *   be decoupled when necessary, such as might happen when the
5997
         *   flashlight's bulb is burned out, or its battery has run down.
5998
         */
5999
        makeLit(stat);
6000
    }
6001
6002
    /* initialize */
6003
    initializeThing()
6004
    {
6005
        /* inherit default handling */
6006
        inherited();
6007
6008
        /* 
6009
         *   Make sure our initial isLit setting (for the LightSource)
6010
         *   matches our initial isOn steting (for the Switch).  The
6011
         *   switch status drives the light source status, so initialize
6012
         *   the latter from the former.  
6013
         */
6014
        isLit = isOn;
6015
    }
6016
6017
    /* treat 'light' and 'extinguish' as 'turn on' and 'turn off' */
6018
    dobjFor(Light) asDobjFor(TurnOn)
6019
    dobjFor(Extinguish) asDobjFor(TurnOff)
6020
6021
    /* if we turn on the flashlight, but it doesn't light, mention this */
6022
    dobjFor(TurnOn)
6023
    {
6024
        action()
6025
        {
6026
            /* do the normal work */
6027
            inherited();
6028
6029
            /* 
6030
             *   If we're now on but not lit, mention this.  This can
6031
             *   happen when we run out of power in the battery, or our
6032
             *   bulb is missing or burned out, or we're simply broken. 
6033
             */
6034
            if (isOn && !isLit)
6035
                mainReport(&flashlightOnButDarkMsg);
6036
        }
6037
    }
6038
;
6039