cfad47cfa3/t3compiler/tads3/test/data/adv3.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
/* Copyright (c) 2000, 2002 Michael J. Roberts.  All Rights Reserved. */
2
/*
3
 *   adv3.t - TADS 3 library main source file 
4
 */
5
6
/* include the library header */
7
#include "adv3.h"
8
9
10
/* ------------------------------------------------------------------------ */
11
/*
12
 *   Library Pre-Initializer.  This object performs the following
13
 *   initialization operations immediately after compilation is completed:
14
 *   
15
 *   - adds each defined Thing to its container's contents list
16
 *   
17
 *   - adds each defined Sense to the global sense list
18
 *   
19
 *   This object is named so that other libraries and/or user code can
20
 *   create initialization order dependencies upon it.  
21
 */
22
adv3LibPreinit: PreinitObject
23
    execute()
24
    {
25
        /*
26
         *   visit every Thing, and move each item into its location's
27
         *   contents list
28
         */
29
        forEachInstance(Thing, { obj: obj.initializeLocation() });
30
31
        /* 
32
         *   visit every Sense, and add each one into the global sense
33
         *   list 
34
         */
35
        forEachInstance(Sense, { obj: libGlobal.allSenses += obj });
36
37
        /* initialize each Actor */
38
        forEachInstance(Actor, { obj: obj.initializeActor() });
39
40
        /* initialize the globals */
41
        libGlobal.initialize();
42
    }
43
;
44
45
/*
46
 *   Library Initializer.  This object performs the following
47
 *   initialization operations each time the game is started:
48
 *   
49
 *   - sets up the library's default output function 
50
 */
51
adv3LibInit: InitObject
52
    execute()
53
    {
54
        /* set oup our default output function */
55
        t3SetSay(say);
56
    }
57
;
58
59
/* ------------------------------------------------------------------------ */
60
/*
61
 *   The library output processor. 
62
 */
63
say(val)
64
{
65
    local idx;
66
67
    /* if it's a string, check for substitutions */
68
    if (dataType(val) == TypeSString)
69
    {
70
        local tagPattern = '<nocase>[<]%.(/?[a-z]+)[>]';
71
        
72
        /* search for our special '<.xxx>' tags, and expand any we find */
73
        idx = rexSearch(tagPattern, val);
74
        while (idx != nil)
75
        {
76
            local xlat;
77
            local afterOfs;
78
            local afterStr;
79
            
80
            /* ask the formatter to translate it */
81
            xlat = libFormatter.translateTag(rexGroup(1)[3]);
82
83
            /* get the part of the string that follows the tag */
84
            afterOfs = idx[1] + idx[2];
85
            afterStr = val.substr(idx[1] + idx[2]);
86
                
87
            /* 
88
             *   if we got a translation, replace it; otherwise, leave the
89
             *   original text intact 
90
             */
91
            if (xlat != nil)
92
            {
93
                /* replace the tag with its translation */
94
                val = val.substr(1, idx[1] - 1) + xlat + afterStr;
95
96
                /* 
97
                 *   figure the offset of the remainder of the string in
98
                 *   the replaced version of the string - this is the
99
                 *   length of the original part up to the replacement
100
                 *   text plus the length of the replacement text 
101
                 */
102
                afterOfs = idx[1] + xlat.length();
103
            }
104
105
            /* 
106
             *   search for the next tag, considering only the part of
107
             *   the string following the replacement text - we do not
108
             *   want to re-scan the replacement text for tags 
109
             */
110
            idx = rexSearch(tagPattern, afterStr);
111
                
112
            /* 
113
             *   If we found it, adjust the starting index of the match to
114
             *   its position in the actual string.  Note that we do this
115
             *   by adding the OFFSET of the remainder of the string,
116
             *   which is 1 less than its INDEX, because idx[1] is already
117
             *   a string index.  (An offset is one less than an index
118
             *   because the index of the first character is 1.)  
119
             */
120
            if (idx != nil)
121
                idx[1] += afterOfs - 1;
122
        }
123
    }
124
    
125
    /* write the value to the console */
126
    tadsSay(val);
127
}
128
129
/*
130
 *   Library text formatter 
131
 */
132
libFormatter: object
133
    /*
134
     *   Translate a tag 
135
     */
136
    translateTag(tag)
137
    {
138
        /* make sure the tag is lower-case only */
139
        tag = tag.toLower();
140
        
141
        /* scan each tag/translation pair for the translation */
142
        for (local i = 1, local cnt = tagList.length() ; i < cnt ; i += 2)
143
        {
144
            /* 
145
             *   if this tag name matches, return its expansion; the tag
146
             *   is the first element of the pair, which is the current
147
             *   element, and the expansion is the second of the pair,
148
             *   thus the next element 
149
             */
150
            if (tag == tagList[i])
151
                return tagList[i + 1];
152
        }
153
154
        /* didn't find it */
155
        return nil;
156
    }
157
158
    /*
159
     *   our tag translation list - entries are in pairs: first the tag
160
     *   name, then the expansion 
161
     */
162
    tagList =
163
    [
164
        /* room name as part of room description */
165
        'roomname', '<p><b>',
166
        '/roomname', '</b>',
167
168
        /* body of room description */
169
        'roomdesc', '<br>\n',
170
        '/roomdesc', '',
171
172
        /* paragraph break within a room description */
173
        'roompara', '<p>\n'
174
    ]
175
;
176
177
178
179
/* ------------------------------------------------------------------------ */
180
/*
181
 *   Interface for showList() - this class is used to provide information
182
 *   to showList() on how the list should be displayed. 
183
 */
184
class ShowListInterface: object
185
    /* 
186
     *   Show the prefix for a 'wide' listing - this is a message that
187
     *   appears just before we start listing the objects.  'itemCount' is
188
     *   the number of items to be listed; the items might be grouped in
189
     *   the listing, so a list that comes out as "three boxes and two
190
     *   books" will have an itemCount of 5.  (The purpose of itemCount is
191
     *   to allow the message to have grammatical agreement in number.)
192
     *   
193
     *   This will never be called with an itemCount of zero, because we
194
     *   will instead use showListEmpty() to display an empty list.  
195
     */
196
    showListPrefixWide(itemCount, pov, parent) { }
197
198
    /* 
199
     *   show the suffix for a 'wide' listing - this is a message that
200
     *   appears just after we finish listing the objects 
201
     */
202
    showListSuffixWide(itemCount, pov, parent) { }
203
204
    /* 
205
     *   Show the list prefix for a 'tall' listing.  Note that there is no
206
     *   list suffix for a tall listing, because the format doesn't allow
207
     *   it. 
208
     */
209
    showListPrefixTall(itemCount, pov, parent) { }
210
211
    /*
212
     *   Show an empty list.  If the list to be displayed has no items at
213
     *   all, this is called instead of the prefix/suffix routines.  This
214
     *   can be left empty if no message is required for an empty list, or
215
     *   can display the complete message appropriate for an empty list
216
     *   (such as "You are carrying nothing").  
217
     */
218
    showListEmpty(pov, parent) { }
219
;
220
221
/*
222
 *   Show a list, showing all items in the list as though they were fully
223
 *   visible, regardless of their actual sense status.  
224
 */
225
showListAll(lister, lst, options, indent)
226
{
227
    local infoList;
228
    
229
    /* create an infoList with each item in full view */
230
    infoList = new Vector(lst.length());
231
    foreach (local cur in lst)
232
    {
233
        /* add a plain view sensory description to the info list */
234
        infoList += new SenseInfoListEntry(cur, transparent, nil, 3);
235
    }
236
237
    /* convert the info vector to a regular list */
238
    infoList = infoList.toList();
239
240
    /* show the list from the current global point of view */
241
    showList(getPOV(), nil, lister, lst, options, indent, infoList);
242
}
243
244
/*
245
 *   Display a list of items, grouping together members of a list group
246
 *   and equivalent items.  We will only list items for which isListed()
247
 *   returns true.
248
 *   
249
 *   'pov' is the point of view of the listing, which is usually an actor
250
 *   (and usually the player character actor).
251
 *   
252
 *   'parent' is the parent (container) of the list being shown.  This
253
 *   should be nil if the listed objects are not all within a single
254
 *   object.
255
 *   
256
 *   'lister' is an object implementing the methods in ShowListInterface -
257
 *   this object displays the before and after messages for the list.
258
 *   
259
 *   'lst' is the list of items to display.
260
 *   
261
 *   'options' gives a set of LIST_xxx option flags.
262
 *   
263
 *   'indent' gives the indentation level.  This is used only for "tall"
264
 *   lists (specified by including LIST_TALL in the options flags).  An
265
 *   indentation level of zero indicates no indentation.
266
 *   
267
 *   'infoList' the list of SenseInfoListEntry objects for all of the
268
 *   objects that can be sensed from the perspective of the actor
269
 *   performing the action that's causing the listing.  
270
 */
271
showList(pov, parent, lister, lst, options, indent, infoList)
272
{
273
    local cur;
274
    local i, cnt;
275
    local itemCount;
276
    local groups;
277
    local groupCount;
278
    local singles;
279
    local uniqueSingles;
280
    local sublists;
281
    local groupOptions;
282
283
    /* no groups yet */
284
    groupCount = 0;
285
    groups = new Vector(10);
286
287
    /* no singles yet */
288
    singles = new Vector(10);
289
290
    /* 
291
     *   First, scan the list to determine how many objects we're going to
292
     *   display. 
293
     */
294
    for (i = 1, cnt = lst.length(), itemCount = 0 ; i <= cnt ; ++i)
295
    {
296
        /* get this object into a local for easy reference */
297
        cur = lst[i];
298
299
        /* if the item isn't listed, skip it */
300
        if (!cur.isListed())
301
            continue;
302
303
        /* count the object */
304
        ++itemCount;
305
306
        /* check for groupings */
307
        if (cur.listWith != nil)
308
        {
309
        findPriorGroup:
310
            {
311
                /* 
312
                 *   look through our list of grouped objects for other
313
                 *   objects in the same list group 
314
                 */
315
                for (local j = 1 ; j <= groups.length() ; ++j)
316
                {
317
                    /* 
318
                     *   check the first object in this sublist (we only need
319
                     *   to check the first item in a sublist, since
320
                     *   everything in a sublist will have the same list group
321
                     *   - that's the way we construct the list) 
322
                     */
323
                    if (groups[j][1].listWith == cur.listWith)
324
                    {
325
                        /* found it - add this item to this group list */
326
                        groups[j] += cur;
327
                        
328
                        /* no need to look any further */
329
                        break findPriorGroup;
330
                    }
331
                }
332
333
                /* 
334
                 *   if we get here, it means that we didn't find this
335
                 *   item in an existing group - create a new group
336
                 *   sublist, and add this item as the first item in the
337
                 *   sublist 
338
                 */
339
                groups = groups.append([cur]);
340
341
                /* we have a new group */
342
                ++groupCount;
343
            }
344
        }
345
        else
346
        {
347
            /* 
348
             *   the object is not part of a group - add it to the list of
349
             *   items to be listed singly 
350
             */
351
            singles += cur;
352
        }
353
    }
354
        
355
    /*
356
     *   We now know how many items we're listing, so we can call the list
357
     *   interface object to set up the list.  If we're displaying nothing
358
     *   at all, just ask the interface object to display the message for
359
     *   an empty list, and we're done.
360
     */
361
    if (itemCount == 0)
362
    {
363
        lister.showListEmpty(pov, parent);
364
        return;
365
    }
366
367
    /* 
368
     *   If any of the groups have only one item, move them to the singles
369
     *   list.  Rebuild the groups list with the new list.  
370
     */
371
    if (groups.length() != 0)
372
    {
373
        /* scan the groups for single item entries */
374
        for (local i = 1 ; i <= groups.length() ; )
375
        {
376
            /* get this item */
377
            local cur = groups[i];
378
            
379
            /* if this group has only one member, it's effectively a single */
380
            if (cur.length() == 1)
381
            {
382
                /* move the single item in this group to the singles list */
383
                singles += cur[1];
384
385
                /* delete the entry from the groups list */
386
                groups.removeElementAt(i);
387
            }
388
            else
389
            {
390
                /* 
391
                 *   it's a legitimate group - keep it, and move on to the
392
                 *   next entry in the group list
393
                 */
394
                ++i;
395
            }
396
        }
397
    }
398
399
400
    /* 
401
     *   Check to see if we have one or more group sublists - if we do, we
402
     *   must use the "long" list format for our overall list, otherwise
403
     *   we can use the normal "short" list format.  The long list format
404
     *   uses semicolons to separate items.
405
     */
406
    for (i = 1, cnt = groups.length(), sublists = nil ; i <= cnt ; ++i)
407
    {
408
        /* 
409
         *   if this group's lister item displays a sublist, we must use
410
         *   the long format 
411
         */
412
        if (groups[i][1].listWith.groupDisplaysSublist)
413
        {
414
            /* note that we are using the long format */
415
            sublists = true;
416
417
            /* 
418
             *   one is enough to make us use the long format, so we need
419
             *   not look any further 
420
             */
421
            break;
422
        }
423
    }
424
425
    /* generate the prefix message */
426
    if ((options & LIST_TALL) != 0)
427
    {
428
        /* indent the prefix */
429
        showListIndent(options, indent);
430
431
        /* show the prefix */
432
        lister.showListPrefixTall(itemCount, pov, parent);
433
434
        /* go to a new line for the list contents */
435
        "\n";
436
437
        /* indent the items one level now, since we showed a prefix */
438
        ++indent;
439
    }
440
    else
441
    {
442
        /* show the prefix */
443
        lister.showListPrefixWide(itemCount, pov, parent);
444
    }
445
446
    /* 
447
     *   calculate the number of unique items in the singles list - we
448
     *   need to know this because we need to know how many items will
449
     *   follow the group list in order to figure out what type of
450
     *   separator to use after the last couple of group items 
451
     */
452
    uniqueSingles = getUniqueCount(singles, options);
453
454
    /* 
455
     *   regardless of whether we're adding long formatting to the main
456
     *   list, display the group sublists with whatever formatting we were
457
     *   originally using 
458
     */
459
    groupOptions = options;
460
461
    /* if we're using sublists, show the main list using long separators */
462
    if (sublists)
463
        options |= LIST_LONG;
464
465
    /* show each group */
466
    for (i = 1, cnt = groups.length() ; i <= cnt ; ++i)
467
    {
468
        /* in tall mode, indent before the group description */
469
        showListIndent(options, indent);
470
        
471
        /* ask the listGroup object to display the list */
472
        groups[i][1].listWith.
473
            showGroupList(pov, lister, groups[i], groupOptions,
474
                          indent, infoList);
475
        
476
        /* 
477
         *   Show the post-group separator.  For the purposes of figuring
478
         *   out what separator to use here, each group and each single
479
         *   item counts as one item. 
480
         */
481
        showListSeparator(options, i, cnt + uniqueSingles);
482
    }
483
484
    /* show the individual items in the list */
485
    showListSimple(pov, lister, singles, options, indent, cnt, infoList);
486
487
    /* 
488
     *   generate the suffix message if we're in 'wide' mode ('tall' lists
489
     *   have no suffix; the format only allows for a prefix) 
490
     */
491
    if ((options & LIST_TALL) == 0)
492
        lister.showListSuffixWide(itemCount, pov, parent);
493
}
494
495
/*
496
 *   Show a list indent if necessary.  If LIST_TALL is included in the
497
 *   options, we'll indent to the given level; otherwise we'll do nothing.
498
 */
499
showListIndent(options, indent)
500
{
501
    /* show the indent only if we're in "tall" mode */
502
    if ((options & LIST_TALL) != 0)
503
    {
504
        for (local i = 0 ; i < indent ; ++i)
505
            "\t";
506
    }
507
}
508
509
/*
510
 *   Show a newline after a list item if we're in a tall list; does
511
 *   nothing for a wide list. 
512
 */
513
showTallListNewline(options)
514
{
515
    if ((options & LIST_TALL) != 0)
516
        "\n";
517
}
518
519
/*
520
 *   Show a simple list, grouping together equivalent items.  This
521
 *   "simple" list formatter doesn't pay any attention to list groups.
522
 *   
523
 *   'prevCnt' is the number of items already displayed, if anything has
524
 *   already been displayed for this list.  This should be zero if this
525
 *   will display the entire list.  
526
 */
527
showListSimple(pov, lister, lst, options, indent, prevCnt, infoList)
528
{
529
    local i;
530
    local cnt;
531
    local dispCount;
532
    local uniqueCount;
533
534
    /* count the unique entries in the list */
535
    uniqueCount = getUniqueCount(lst, options) + prevCnt;
536
537
    /* display the items */
538
dispLoop:
539
    for (i = 1, cnt = lst.length(), dispCount = prevCnt ; i <= cnt ; ++i)
540
    {
541
        local cur;
542
        local curSenseInfo;
543
        local dispSingle;
544
545
        /* get the item */
546
        cur = lst[i];
547
548
        /* presume we'll display it as a single item */
549
        dispSingle = true;
550
        
551
        /* 
552
         *   If this is an item with equivalents, search the list for
553
         *   other items that match.  If this is the first one, list it,
554
         *   with the count of matching items.  If this isn't the first
555
         *   one, don't bother listing it, since we've already included it
556
         *   in the earlier item's listing. 
557
         */
558
        if (cur.isEquivalent)
559
        {
560
            local equivCount;
561
562
            /* search for an earlier instance of the item in the list */
563
            for (local j = 1 ; j < i ; ++j)
564
            {
565
                /* 
566
                 *   check to see if this item lists the same as the first
567
                 *   item we found - if so, we can skip to the next item
568
                 *   in the main loop, since we've already listed this
569
                 *   item 
570
                 */
571
                if (lst[j].isEquivalent
572
                    && lst[j].isListEquivalent(cur, options))
573
                    continue dispLoop;
574
            }
575
576
            /* 
577
             *   we didn't find it, so it hasn't been listed yet - count
578
             *   the number of instances of this item in the rest of the
579
             *   list, so we can include all of them in the count of this
580
             *   item 
581
             */
582
            for (local j = i + 1, equivCount = 1 ; j <= cnt ; ++j)
583
            {
584
                /* if it matches, count it */
585
                if (lst[j].isEquivalent
586
                    && lst[j].isListEquivalent(cur, options))
587
                    ++equivCount;
588
            }
589
590
            /* 
591
             *   if we found more than one such item, display the entire
592
             *   set of them with a count 
593
             */
594
            if (equivCount > 1)
595
            {
596
                /* show the list indent if necessary */
597
                showListIndent(options, indent);
598
599
                /* get the sense information for this item */
600
                curSenseInfo = infoList.valWhich({x: x.obj == cur});
601
                
602
                /* display the whole group */
603
                cur.showListItemCounted(equivCount, options,
604
                                        pov, curSenseInfo);
605
                
606
                /* note that we don't need to display it as a single */
607
                dispSingle = nil;
608
            }
609
        }
610
611
        /* if necessary, display the item as a single item */
612
        if (dispSingle)
613
        {
614
            /* show the list indent if necessary */
615
            showListIndent(options, indent);
616
            
617
            /* get the sense information for this item */
618
            curSenseInfo = infoList.valWhich({x: x.obj == cur});
619
                
620
            /* show the item */
621
            cur.showListItem(options, pov, curSenseInfo);
622
        }
623
624
        /* count the item displayed */
625
        ++dispCount;
626
627
        /* show the list separator */
628
        showListSeparator(options, dispCount, uniqueCount);
629
630
        /* if this is a tall recursive list, show the item's contents */
631
        if ((options & LIST_TALL) != 0 && (options & LIST_RECURSE) != 0)
632
            cur.showObjectContents(pov, lister, options,
633
                                   indent + 1, infoList);
634
    }
635
}
636
637
/*
638
 *   Show a list separator after displaying an item.  curItemNum is the
639
 *   number of the item just displayed (1 is the first item), and
640
 *   totalItems is the total number of items that will be displayed in the
641
 *   list.  
642
 */
643
showListSeparator(options, curItemNum, totalItems)
644
{
645
    local useLong = ((options & LIST_LONG) != 0);
646
647
    /* if this is a tall list, the separator is simply a newline */
648
    if ((options & LIST_TALL) != 0)
649
    {
650
        "\n";
651
        return;
652
    }
653
    
654
    /* if that was the last item, there are no separators */
655
    if (curItemNum == totalItems)
656
        return;
657
658
    /* check to see if the next item is the last */
659
    if (curItemNum + 1 == totalItems)
660
    {
661
        /* 
662
         *   We just displayed the penultimate item in the list, so we
663
         *   need to use the special last-item separator.  If we're only
664
         *   displaying two items total, we use an even more special
665
         *   separator.  
666
         */
667
        if (totalItems == 2)
668
        {
669
            /* use the two-item separator */
670
            if (useLong)
671
                libMessages.longListSepTwo;
672
            else
673
                libMessages.listSepTwo;
674
        }
675
        else
676
        {
677
            /* use the normal last-item separator */
678
            if (useLong)
679
                libMessages.longListSepEnd;
680
            else
681
                libMessages.listSepEnd;
682
        }
683
    }
684
    else
685
    {
686
        /* we're in the middle of the list - display the normal separator */
687
        if (useLong)
688
            libMessages.longListSepMiddle;
689
        else
690
            libMessages.listSepMiddle;
691
    }
692
}
693
694
/*
695
 *   Calculate the number of unique items in the list. 
696
 */
697
getUniqueCount(lst, options)
698
{
699
    local uniqueCount;
700
    
701
    /* run through the list and count the unique entries */
702
    for (local i = 1, local cnt = lst.length(), uniqueCount = 0 ;
703
         i <= cnt ; ++i)
704
    {
705
        local cur;
706
707
        /* get this item into a local for easier access */
708
        cur = lst[i];
709
        
710
        /* tentatively assume this item is unique */
711
        ++uniqueCount;
712
713
        /* 
714
         *   if it's marked as interchangeable with other items of its
715
         *   class, note its class list, then try to find other objects
716
         *   marked the same way 
717
         */
718
        if (cur.isEquivalent)
719
        {
720
            /* look for equivalents among the previous items */
721
            for (local j = 1 ; j < i ; ++j)
722
            {
723
                /* check for a match */
724
                if (lst[j].isEquivalent
725
                     && lst[j].isListEquivalent(cur, options))
726
                {
727
                    /* it doesn't count as unique after all */
728
                    --uniqueCount;
729
730
                    /* no need to look any further */
731
                    break;
732
                }
733
            }
734
        }
735
    }
736
737
    /* return the number of unique items we counted */
738
    return uniqueCount;
739
}
740
741
/* ------------------------------------------------------------------------ */
742
/*
743
 *   Show the contents of a set of items.  For each item in the list, if
744
 *   the object's contents can be sensed, we'll display them.  We don't
745
 *   display anything for the objects in the lists themselves - only for
746
 *   the contents of the objects in the list.
747
 *   
748
 *   'pov' is the point of view, which is usually an actor (and usually
749
 *   the player character actor).
750
 *   
751
 *   'lst' is the list of objects to display.
752
 *   
753
 *   'options' is the set of flags that we'll pass to showList(), and has
754
 *   the same meaning as for that function.
755
 *   
756
 *   'infoList' is a list of SenseInfoListEntry objects giving the sense
757
 *   information for all of the objects that the actor to whom we're
758
 *   showing the contents listing can sense.  
759
 */
760
showContentsList(pov, lst, options, indent, infoList)
761
{
762
    /* go through each item and show its contents */
763
    foreach (local cur in lst)
764
        cur.showObjectContents(pov, cur.contentsLister, options,
765
                               indent, infoList);
766
}
767
768
/* ------------------------------------------------------------------------ */
769
/*
770
 *   Sense Info List entry.  Thing.senseInfoList() returns a list of these
771
 *   objects to provide full sensory detail on the objects within range of
772
 *   a sense.  
773
 */
774
class SenseInfoListEntry: object
775
    construct(obj, trans, obstructor, ambient)
776
    {
777
        self.obj = obj;
778
        self.trans = trans;
779
        self.obstructor = obstructor;
780
        self.ambient = ambient;
781
    }
782
    
783
    /* the object being sensed */
784
    obj = nil
785
786
    /* the transparency from the point of view to this object */
787
    trans = nil
788
789
    /* the obstructor that introduces a non-transparent value of trans */
790
    obstructor = nil
791
792
    /* the ambient sense energy level at this object */
793
    ambient = nil
794
;
795
796
797
/* ------------------------------------------------------------------------ */
798
/*
799
 *   List Group Interface.  This object is instantiated for each set of
800
 *   non-equivalent items that should be grouped together in listings.  
801
 */
802
class ListGroup: object
803
    /*
804
     *   Show a list of items from this group.  All of the items in the
805
     *   list will be members of this list group; we are to display a
806
     *   sentence fragment showing the items in the list, suitable for
807
     *   embedding in a larger list.
808
     *   
809
     *   'options', 'indent', and 'infoList' have the same meaning as they
810
     *   do for showList().
811
     *   
812
     *   Note that we are not to display any separator before or after our
813
     *   list; the caller is responsible for that.  
814
     */
815
    showGroupList(pov, lister, lst, options, indent, infoList) { }
816
817
    /* 
818
     *   Determine if showing the group list will introduce a sublist into
819
     *   an enclosing list.  This should return true if we will show a
820
     *   sublist without some kind of grouping, so that the caller knows
821
     *   to introduce some extra grouping into its enclosing list.  This
822
     *   should return nil if the sublist we display will be clearly set
823
     *   off in some way (for example, by being enclosed in parentheses). 
824
     */
825
    groupDisplaysSublist = true
826
;
827
828
/*
829
 *   List Group implementation: parenthesized sublist.  Displays the
830
 *   number of items collectively, then displays the list of items in
831
 *   parentheses. 
832
 */
833
class ListGroupParen: ListGroup
834
    /* 
835
     *   show the group list 
836
     */
837
    showGroupList(pov, lister, lst, options, indent, infoList)
838
    {
839
        /* show the collective count of the object */
840
        showGroupCountDesc(lst);
841
842
        /* show the tall or wide sublist */
843
        if ((options & LIST_TALL) != 0)
844
        {
845
            /* tall list - show the items as a sublist */
846
            "\n";
847
            showListSimple(pov, lister, lst, options | LIST_GROUP, indent,
848
                           0, infoList);
849
        }
850
        else
851
        {
852
            /* wide list - add a space and a paren for the sublist */
853
            " (";
854
855
            /* show the sublist */
856
            showListSimple(pov, lister, lst, options | LIST_GROUP, indent,
857
                           0, infoList);
858
859
            /* end the sublist */
860
            ")";
861
        }
862
    }
863
864
    /*
865
     *   Show the collective count for the list of objects.  By default,
866
     *   we'll simply display the countDesc of the first item in the list,
867
     *   on the assumption that each object has the same plural
868
     *   description.  However, in most cases this should be overridden to
869
     *   provide a more general collective name for the group. 
870
     */
871
    showGroupCountDesc(lst)
872
    {
873
        /* show the first item's countDesc */
874
        lst[1].countDesc(lst.length());
875
    }
876
877
    /* we don't add a sublist, since we enclose our list in parentheses */
878
    groupDisplaysSublist = nil
879
;
880
881
/*
882
 *   List Group implementation: simple prefix/suffix lister.  Shows a
883
 *   prefix message, then shows the list, then shows a suffix message.
884
 */
885
class ListGroupPrefixSuffix: ListGroup
886
    showGroupList(pov, lister, lst, options, indent, infoList)
887
    {
888
        /* show the prefix */
889
        showGroupPrefix;
890
891
        /* if we're in tall mode, start a new line */
892
        showTallListNewline(options);
893
894
        /* show the list */
895
        showListSimple(pov, lister, lst, options | LIST_GROUP, indent + 1,
896
                       0, infoList);
897
898
        /* show the suffix */
899
        showGroupSuffix;
900
    }
901
902
    /* the prefix - subclasses should override this if desired */
903
    showGroupPrefix = ""
904
905
    /* the suffix */
906
    showGroupSuffix = ""
907
;
908
909
/* ------------------------------------------------------------------------ */
910
/*
911
 *   Library global variables 
912
 */
913
libGlobal: object
914
    /* initialize the library globals */
915
    initialize()
916
    {
917
        /* set up a vector for our point of view stack */
918
        povStack = new Vector(32);
919
920
        /* set up the full-score vectors */
921
        fullScorePoints = new Vector(32);
922
        fullScoreDesc = new Vector(32);
923
    }
924
    
925
    /*
926
     *   List of all of the senses.  The library pre-initializer will load
927
     *   this list with a reference to each instance of class Sense. 
928
     */
929
    allSenses = []
930
931
    /*
932
     *   The current player character 
933
     */
934
    playerChar = nil
935
936
    /*
937
     *   The current perspective object.  This is usually the actor
938
     *   performing the current command. 
939
     */
940
    pointOfView = nil
941
942
    /*
943
     *   The stack of point of view objects.  We initialize this during
944
     *   start-up to a vector.  The last element of the vector is the most
945
     *   recent point of view after the current point of view.  
946
     */
947
    povStack = nil
948
949
    /*
950
     *   Vectors for the score.  The first stores the number of points for
951
     *   each item scored, and the second stores the description
952
     *   corresponding to each point count.  We'll allocate these at
953
     *   startup.  
954
     */
955
    fullScorePoints = nil
956
    fullScoreDesc = nil
957
958
    /* the total number of points scored so far */
959
    totalScore = 0
960
961
    /* 
962
     *   the maximum number of points possible - the game should set this
963
     *   to the appropriate value at startup 
964
     */
965
    maxScore = 100
966
967
    /* the total number of turns so far */
968
    totalTurns = 0
969
;
970
971
/* ------------------------------------------------------------------------ */
972
/*
973
 *   An Achievement is an object used to award points in the score.  For
974
 *   most purposes, an achievement can be described simply by a string,
975
 *   but the Achievement object provides more flexibility in describing
976
 *   combined scores when a similar set of achievements are to be grouped.
977
 */
978
class Achievement: object
979
    /* 
980
     *   The number of times the achievement has been awarded.  Each time
981
     *   the achievement is passed to addToScore(), this is incremented.
982
     *   Note that this is distinct from the number of points.  
983
     */
984
    scoreCount = 0
985
    
986
    /* 
987
     *   Describe the achievement - this must display a string explaining
988
     *   the reason the points associated with this achievement were
989
     *   awarded.
990
     *   
991
     *   Note that this description can make use of the scoreCount
992
     *   information to show different descriptions depending on how many
993
     *   times the item has scored.  For example, an achievement for
994
     *   finding various treasure items might want to display "finding a
995
     *   treasure" if only one treasure was found and "finding five
996
     *   treasures" if five were found.
997
     *   
998
     *   In some cases, it might be desirable to keep track of additional
999
     *   custom information, and use that information in generating the
1000
     *   description.  For example, the game might keep a list of
1001
     *   treasures found with the achievement, adding to the list each
1002
     *   time the achievement is scored, and displaying the contents of
1003
     *   the list when the description is shown.  
1004
     */
1005
    lDesc = ""
1006
;
1007
1008
/*
1009
 *   Add to the score.  'points' is the number of points to add to the
1010
 *   score, and 'desc' is a string describing the reason the points are
1011
 *   being awarded, or an Achievement object describing the points.
1012
 *   
1013
 *   We keep a list of each unique description.  If 'desc' is already in
1014
 *   this list, we'll simply add the given number of points to the
1015
 *   existing entry for the same description.
1016
 *   
1017
 *   Note that, if 'desc' is an Achievement object, it will match a
1018
 *   previous item only if it's exactly the same Achievement instance.  
1019
 */
1020
addToScore(points, desc)
1021
{
1022
    local idx;
1023
    
1024
    /* if 'desc' is an Achievement item, increase its use count */
1025
    if (desc.ofKind(Achievement))
1026
        desc.scoreCount++;
1027
1028
    /* add the points to the total */
1029
    libGlobal.totalScore += points;
1030
1031
    /* try to find a matching achievement in our list of past score items */
1032
    idx = libGlobal.fullScoreDesc.indexOf(desc);
1033
1034
    /* if we found it, merely increase the number of points for the item */
1035
    if (idx != nil)
1036
    {
1037
        /* 
1038
         *   it's already in there - simply combine the points into the
1039
         *   existing entry
1040
         */
1041
        libGlobal.fullScorePoints[idx] += points;
1042
    }
1043
    else
1044
    {
1045
        /* it's not in the list yet - add it */
1046
        libGlobal.fullScoreDesc.append(desc);
1047
        libGlobal.fullScorePoints.append(points);
1048
    }
1049
}
1050
1051
/*
1052
 *   Show the simple score
1053
 */
1054
showScore()
1055
{
1056
    /* show the basic score statistics */
1057
    libMessages.showScoreMessage(libGlobal.totalScore, libGlobal.maxScore,
1058
                                 libGlobal.totalTurns);
1059
1060
    /* show the score ranking */
1061
    showScoreRank(libGlobal.totalScore);
1062
}
1063
1064
/* 
1065
 *   show the score rank message 
1066
 */
1067
showScoreRank(points)
1068
{
1069
    local idx;
1070
1071
    /* if there's no rank table, skip the ranking */
1072
    if (libMessages.scoreRankTable == nil)
1073
        return;
1074
    
1075
    /*
1076
     *   find the last item for which our score is at least the minimum -
1077
     *   the table is in ascending order of minimum score, so we want the
1078
     *   last item for which our score is sufficient 
1079
     */
1080
    idx = libMessages.scoreRankTable.lastIndexWhich({x: points >= x[1]});
1081
    
1082
    /* if we didn't find an item, use the first by default */
1083
    if (idx == nil)
1084
        idx = 1;
1085
    
1086
    /* show the description from the item we found */
1087
    libMessages.showScoreRankMessage(libMessages.scoreRankTable[idx][2]);
1088
}
1089
1090
/*
1091
 *   Display the full score 
1092
 */
1093
showFullScore()
1094
{
1095
    /* show the basic score statistics */
1096
    showScore();
1097
1098
    /* if any points have been awarded, show the full score */
1099
    if (libGlobal.totalScore != 0)
1100
    {
1101
        /* show a blank line before the full list */
1102
        "\b";
1103
1104
        /* show the list prefix */
1105
        libMessages.showFullScorePrefix;
1106
        "\n";
1107
1108
        /* show each item in the full score list */
1109
        for (local i = 1, local cnt = libGlobal.fullScoreDesc.length() ;
1110
             i <= cnt ; ++i)
1111
        {
1112
            local desc;
1113
            
1114
            /* show the number of points */
1115
            libMessages.fullScoreItemPoints(libGlobal.fullScorePoints[i]);
1116
1117
            /* 
1118
             *   if this description item is an Achivement, call its lDesc
1119
             *   to display it; otherwise, it must simply be a string, so
1120
             *   we can display it directly 
1121
             */
1122
            desc = libGlobal.fullScoreDesc[i];
1123
            if (desc.ofKind(Achievement))
1124
            {
1125
                /* it's an Achievement - use its lDesc to display it */
1126
                desc.lDesc;
1127
            }
1128
            else
1129
            {
1130
                /* it's simply a string - display it directly */
1131
                say(desc);
1132
            }
1133
1134
            /* one item per line - end the line here */
1135
            "\n";
1136
        }
1137
    }
1138
}
1139
1140
1141
/* ------------------------------------------------------------------------ */
1142
/*
1143
 *   Get the current point of view.  This is the actor object that should
1144
 *   be used when generating names of objects.  
1145
 */
1146
getPOV()
1147
{
1148
    return libGlobal.pointOfView;
1149
}
1150
1151
/*
1152
 *   Change the point of view without altering the point-of-view stack 
1153
 */
1154
setPOV(pov)
1155
{
1156
    /* set the new point of view */
1157
    libGlobal.pointOfView = pov;
1158
}
1159
1160
/*
1161
 *   Set the root point of view.  This doesn't affect the current point of
1162
 *   view unless there is no current point of view; this merely sets the
1163
 *   outermost default point of view.  
1164
 */
1165
setRootPOV(pov)
1166
{
1167
    /* 
1168
     *   if there's nothing in the stacked list, set the current point of
1169
     *   view; otherwise, just set the innermost stacked element 
1170
     */
1171
    if (libGlobal.povStack.length() == 0)
1172
    {
1173
        /* there is no point of view, so set the current point of view */
1174
        libGlobal.pointOfView = pov;
1175
    }
1176
    else
1177
    {
1178
        /* set the innermost stacked point of view */
1179
        libGlobal.povStack[1] = pov;
1180
    }
1181
}
1182
1183
/*
1184
 *   Push the current point of view 
1185
 */
1186
pushPOV(pov)
1187
{
1188
    /* stack the current one */
1189
    libGlobal.povStack += libGlobal.pointOfView;
1190
1191
    /* set the new point of view */
1192
    setPOV(pov);
1193
}
1194
1195
/*
1196
 *   Pop the most recent point of view pushed 
1197
 */
1198
popPOV()
1199
{
1200
    local len;
1201
    
1202
    /* check if there's anything left on the stack */
1203
    len = libGlobal.povStack.length();
1204
    if (len != 0)
1205
    {
1206
        /* take the most recent element off the stack */
1207
        libGlobal.pointOfView = libGlobal.povStack[len];
1208
1209
        /* take it off the stack */
1210
        libGlobal.povStack.removeElementAt(len);
1211
    }
1212
    else
1213
    {
1214
        /* nothing on the stack - clear the point of view */
1215
        libGlobal.pointOfView = nil;
1216
    }
1217
}
1218
1219
/*
1220
 *   Clear the point of view and all stacked elements
1221
 */
1222
clearPOV()
1223
{
1224
    local len;
1225
    
1226
    /* forget the current point of view */
1227
    setPOV(nil);
1228
1229
    /* drop everything on the stack */
1230
    len = libGlobal.povStack.length();
1231
    libGlobal.povStack.removeRange(1, len);
1232
}
1233
1234
/*
1235
 *   Call a function from a point of view.  We'll set the new point of
1236
 *   view, call the function with the given arguments, then restore the
1237
 *   original point of view. 
1238
 */
1239
callFromPOV(pov, funcToCall, [args])
1240
{
1241
    /* push the new point of view */
1242
    pushPOV(pov);
1243
1244
    /* make sure we pop the point of view no matter how we leave */
1245
    try
1246
    {
1247
        /* call the function */
1248
        (funcToCall)(args...);
1249
    }
1250
    finally
1251
    {
1252
        /* restore the enclosing point of view on the way out */
1253
        popPOV();
1254
    }
1255
}
1256
1257
1258
/* ------------------------------------------------------------------------ */
1259
/*
1260
 *   "Add" two transparency levels, yielding a new transparency level.
1261
 *   This function can be used to determine the result of passing a sense
1262
 *   through multiple layers of material.  
1263
 */
1264
transparencyAdd(a, b)
1265
{
1266
    /* transparent + x -> x for all x */
1267
    if (a == transparent)
1268
        return b;
1269
    if (b == transparent)
1270
        return a;
1271
1272
    /* opaque + x -> opaque for all x */
1273
    if (a == opaque || b == opaque)
1274
        return opaque;
1275
1276
    /*
1277
     *   distant + distant, obscured + obscured, and distant + obscured
1278
     *   all yield opaque - since neither is transparent or opaque, both
1279
     *   must be obscured or distant, so the result must be opaque
1280
     */
1281
    return opaque;
1282
}
1283
1284
/*
1285
 *   Compare two transparency levels to determine which one is more
1286
 *   transparent.  Returns 0 if the two levels are equally transparent, 1
1287
 *   if the first one is more transparent, and -1 if the second one is
1288
 *   more transparent.  The comparison follows this rule:
1289
 *   
1290
 *   transparent > distant == obscured > opaque 
1291
 */
1292
transparencyCompare(a, b)
1293
{
1294
    /*
1295
     *   for the purposes of the comparison, consider obscured to be
1296
     *   identical to distant
1297
     */
1298
    if (a == obscured)
1299
        a = distant;
1300
    if (b == obscured)
1301
        b = distant;
1302
1303
    /* if they're the same, return zero to so indicate */
1304
    if (a == b)
1305
        return 0;
1306
1307
    /*
1308
     *   We know they're not equal, so if one is transparent, then the
1309
     *   other one isn't.  Thus, if either one is transparent, it's the
1310
     *   winner.
1311
     */
1312
    if (a == transparent)
1313
        return 1;
1314
    if (b == transparent)
1315
        return -1;
1316
1317
    /*
1318
     *   We now know neither one is transparent, and we've already
1319
     *   transformed obscured into distant, so the only possible values
1320
     *   remaining are distant and opaque.  We know also they're not
1321
     *   equal, because we would have already returned if that were the
1322
     *   case.  So, we can conclude that one must be distant and the other
1323
     *   must be opaque.  Hence, the one that's opaque is the less
1324
     *   transparent one.
1325
     */
1326
    if (a == opaque)
1327
        return -1;
1328
    else
1329
        return 1;
1330
}
1331
1332
/*
1333
 *   Given a brightness level and a transparency level, compute the
1334
 *   brightness as modified by the transparency level. 
1335
 */
1336
adjustBrightness(br, trans)
1337
{
1338
    switch(trans)
1339
    {
1340
    case transparent:
1341
        /* transparent medium - this doesn't modify brightness at all */
1342
        return br;
1343
1344
    case obscured:
1345
    case distant:
1346
        /* 
1347
         *   Distant or obscured.  We reduce self-illuminating light
1348
         *   (level 1) and dim light (level 2) to nothing (level 0), we
1349
         *   leave nothing as nothing (obviously), and we reduce all other
1350
         *   levels one step.  So, everything below level 3 goes to 0, and
1351
         *   everything at or above level 3 gets decremented by 1.  
1352
         */
1353
        return (br >= 3 ? br - 1 : 0);
1354
1355
    case opaque:
1356
        /* opaque medium - nothing makes it through */
1357
        return 0;
1358
1359
    default:
1360
        /* shouldn't get to other cases */
1361
        return nil;
1362
    }
1363
}
1364
1365
1366
/* ------------------------------------------------------------------------ */
1367
/*
1368
 *   Material: the base class for library objects that specify the way
1369
 *   senses pass through objects.  
1370
 */
1371
class Material: object
1372
    /*
1373
     *   Determine how a sense passes through the material.  We'll return
1374
     *   a transparency level.  (Individual materials should not need to
1375
     *   override this method, since it simply dispatches to the various
1376
     *   xxxThru methods.)
1377
     */
1378
    senseThru(sense)
1379
    {
1380
        /* dispatch to the xxxThru method for the sense */
1381
        return self.(sense.thruProp);
1382
    }
1383
1384
    /*
1385
     *   For each sense, each material must define an appropriate xxxThru
1386
     *   property that returns the transparency level for that sense
1387
     *   through the material.  Any xxxThru property not defined in an
1388
     *   individual material defaults to opaque.
1389
     */
1390
    seeThru = opaque
1391
    hearThru = opaque
1392
    smellThru = opaque
1393
    touchThru = opaque
1394
;
1395
1396
/*
1397
 *   Adventium is the basic stuff of the game universe.  This is the
1398
 *   default material for any object that doesn't specify a different
1399
 *   material.  This type of material is opaque to all senses.  
1400
 */
1401
adventium: Material
1402
    seeThru = opaque
1403
    hearThru = opaque
1404
    smellThru = opaque
1405
    touchThru = opaque
1406
;
1407
1408
/*
1409
 *   Paper is opaque to sight and touch, but allows sound and smell to
1410
 *   pass.  
1411
 */
1412
paper: Material
1413
    seeThru = opaque
1414
    hearThru = transparent
1415
    smellThru = transparent
1416
    touchThru = opaque
1417
;
1418
1419
/*
1420
 *   Glass is transparent to light, but opaque to touch, sound, and smell.
1421
 *   
1422
 */
1423
glass: Material
1424
    seeThru = transparent
1425
    hearThru = opaque
1426
    smellThru = opaque
1427
    touchThru = opaque
1428
;
1429
1430
/*
1431
 *   Fine Mesh is transparent to all senses except touch.  
1432
 */
1433
fineMesh: Material
1434
    seeThru = transparent
1435
    hearThru = transparent
1436
    smellThru = transparent
1437
    touchThru = opaque
1438
;
1439
1440
/*
1441
 *   Coarse Mesh is transparent to all senses, including touch, but
1442
 *   doesn't allow large objects to pass through.  
1443
 */
1444
coarseMesh: Material
1445
    seeThru = transparent
1446
    hearThru = transparent
1447
    smellThru = transparent
1448
    touchThru = transparent
1449
;
1450
1451
/* ------------------------------------------------------------------------ */
1452
/*
1453
 *   Sense: the basic class for senses.  
1454
 */
1455
class Sense: object
1456
    /*
1457
     *   Each sense must define the property thruProp as a property
1458
     *   pointer giving the xxxThru property for the sense.  The xxxThru
1459
     *   property is the property of a material which determines how the
1460
     *   sense passes through that material.  
1461
     */
1462
    thruProp = nil
1463
1464
    /*
1465
     *   Each sense must define the property sizeProp as a property
1466
     *   pointer giving the xxxSize property for the sense.  The xxxSize
1467
     *   property is the property of a Thing which determines how "large"
1468
     *   the object is with respect to the sense.  For example, sightSize
1469
     *   indicates how large the object is visually, while soundSize
1470
     *   indicates how loud the object is.
1471
     *   
1472
     *   The purpose of an object's size in a given sense is to determine
1473
     *   how well the object can be sensed through an obscuring medium or
1474
     *   at a distance.  
1475
     */
1476
    sizeProp = nil
1477
1478
    /*
1479
     *   Each sense must define the property presenceProp as a property
1480
     *   pointer giving the xxxPresence property for the sense.  The
1481
     *   xxxPresence property is the property of a Thing which determines
1482
     *   whether or not the object has a "presence" in this sense, which
1483
     *   is to say whether or not the object is emitting any detectable
1484
     *   sensory data for the sense.  For example, soundPresence indicates
1485
     *   whether or not a Thing is making any noise.  
1486
     */
1487
    presenceProp = nil
1488
1489
    /*
1490
     *   Each sense can define this property to specify a property pointer
1491
     *   used to define a Thing's "ambient" energy emissions.  Senses
1492
     *   which do not use ambient energy should define this to nil.
1493
     *   
1494
     *   Some senses work only on directly emitted sensory data; human
1495
     *   hearing, for example, has no (at least effectively no) use for
1496
     *   reflected sound, and can sense objects only by the sounds they're
1497
     *   actually emitting.  Sight, on the other hand, can make use not
1498
     *   only of light emitted by an object but of light reflected by the
1499
     *   object.  So, sight defines an ambience property, whereas hearing,
1500
     *   touch, and smell do not.  
1501
     */
1502
    ambienceProp = nil
1503
1504
    /*
1505
     *   Determine if, in general, the given object can be sensed under
1506
     *   the given conditions.  Returns true if so, nil if not.  By
1507
     *   default, if the ambient level is zero, we'll return nil;
1508
     *   otherwise, if the transparency level is 'transparent', we'll
1509
     *   return true; otherwise, we'll consult the object's size:
1510
     *   
1511
     *   - Small objects cannot be sensed under less than transparent
1512
     *   conditions.
1513
     *   
1514
     *   - Medium or large objects can be sensed in any conditions other
1515
     *   than opaque.  
1516
     */
1517
    canSenseObj(obj, trans, ambient)
1518
    {
1519
        /* if the ambient energy level is zero, we can't sense it */
1520
        if (ambient == 0)
1521
            return nil;
1522
1523
        /* check the transparency level */
1524
        switch(trans)
1525
        {
1526
        case transparent:
1527
            /* we can always sense under transparent conditions */
1528
            return true;
1529
1530
        case distant:
1531
        case obscured:
1532
            /* 
1533
             *   we can only sense medium and large objects under less
1534
             *   than transparent conditions 
1535
             */
1536
            return obj.(self.sizeProp) != small;
1537
1538
        default:
1539
            /* we can never sense under other conditions */
1540
            return nil;
1541
        }
1542
    }
1543
;
1544
1545
/*
1546
 *   The senses.  We define sight, sound, smell, and touch.  We do not
1547
 *   define a separate sense for taste, since it would add nothing to our
1548
 *   model: you can taste something if and only if you can touch it.
1549
 *   
1550
 *   To add a new sense, you must do the following:
1551
 *   
1552
 *   - Define the sense object itself, in parallel to the senses defined
1553
 *   below.
1554
 *   
1555
 *   - Modify class Material to set the default transparency level for
1556
 *   this sense by defining the property xxxThru - for most senses, the
1557
 *   default transparency level is 'opaque', but you must decide on the
1558
 *   appropriate default for your new sense.
1559
 *   
1560
 *   - Modify class Thing to set the default xxxSize setting, if desired.
1561
 *   
1562
 *   - Modify class Thing to set the default xxxPresence setting, if
1563
 *   desired.
1564
 *   
1565
 *   - Modify each instance of class 'Material' that should have a
1566
 *   non-default transparency for the sense by defining the property
1567
 *   xxxThru for the material.
1568
 *   
1569
 *   - Modify class Actor to add the sense to the default mySenses list;
1570
 *   this is only necessary if the sense is one that all actors should
1571
 *   have by default.  
1572
 */
1573
1574
sight: Sense
1575
    thruProp = &seeThru
1576
    sizeProp = &sightSize
1577
    presenceProp = &sightPresence
1578
    ambienceProp = &brightness
1579
;
1580
1581
sound: Sense
1582
    thruProp = &hearThru
1583
    sizeProp = &soundSize
1584
    presenceProp = &soundPresence
1585
;
1586
1587
smell: Sense
1588
    thruProp = &smellThru
1589
    sizeProp = &smellSize
1590
    presenceProp = &smellPresence
1591
;
1592
1593
touch: Sense
1594
    thruProp = &touchThru
1595
    sizeProp = &touchSize
1596
    presenceProp = &touchPresence
1597
1598
    /*
1599
     *   Override canSenseObj for touch.  Unlike other senses, touch
1600
     *   requires physical contact with an object, so it cannot operate at
1601
     *   a distance, regardless of the size of an object.  
1602
     */
1603
    canSenseObj(obj, trans, ambient)
1604
    {
1605
        /* if it's distant, we can't sense the object no matter how large */
1606
        if (trans == distant)
1607
            return nil;
1608
1609
        /* for other cases, inherit the default handling */
1610
        return inherited.canSenseObj(obj, trans, ambient);
1611
    }
1612
;
1613
1614
1615
/* ------------------------------------------------------------------------ */
1616
/*
1617
 *   Thing: the basic class for game objects.  An object of this class
1618
 *   represents a physical object in the simulation.
1619
 *   
1620
 *   The LangThing base class defines some language-specific methods and
1621
 *   properties, which are separated out from Thing to make it easy for a
1622
 *   translator to substitute a translated implementation without changing
1623
 *   the rest of the library code.  
1624
 */
1625
class Thing: LangThing
1626
    /*
1627
     *   If we define a non-nil initDesc, this property will be called to
1628
     *   describe the object in room listings until the object is first
1629
     *   moved to a new location.  By default, objects don't have initial
1630
     *   descriptions.  
1631
     */
1632
    initDesc = nil
1633
1634
    /*
1635
     *   Flag: I've been moved out of my initial location.  Whenever we
1636
     *   move the object to a new location, we'll set this to true.  
1637
     */
1638
    isMoved = nil
1639
1640
    /*
1641
     *   Determine if I should be described using my initial description.
1642
     *   This returns true if I have an initial description that isn't
1643
     *   nil, and I have never been moved out of my initial location.  If
1644
     *   this returns nil, the object should be described in room
1645
     *   descriptions using the ordinary generated message.  
1646
     */
1647
    useInitDesc()
1648
    {
1649
        return !isMoved && propType(&initDesc) != TypeNil;
1650
    }
1651
1652
    /*
1653
     *   Determine if I'm to be listed at all in my room description.
1654
     *   Most objects should be listed normally, but some types of objects
1655
     *   should be suppressed from the normal listing.  For example,
1656
     *   fixed-in-place scenery objects are generally described in the
1657
     *   custom message for the containing room, so these are normally
1658
     *   omitted from the listing of the room's contents.
1659
     *   
1660
     *   By default, we'll return true, unless we are using our initial
1661
     *   description; we return nil if our initial description is to be
1662
     *   used because the initial description overrides the default
1663
     *   listing.
1664
     *   
1665
     *   Individual objects are free to override this as needed to control
1666
     *   their listing status.  
1667
     */
1668
    isListed() { return !useInitDesc(); }
1669
1670
    /*
1671
     *   The default long description, which is displayed in response to
1672
     *   an explicit player request to examine the object.  We'll use a
1673
     *   generic library message; most objects should override this to
1674
     *   customize the object's desription.  
1675
     */
1676
    lDesc { libMessages.thingLDesc(self); }
1677
1678
    /*
1679
     *   "Equivalence" flag.  If this flag is set, then all objects with
1680
     *   the same immediate superclass will be considered interchangeable;
1681
     *   such objects will be listed collectively in messages (so we would
1682
     *   display "five coins" rather than "a coin, a coin, a coin, a coin,
1683
     *   and a coin"), and will be treated as equivalent in resolving noun
1684
     *   phrases to objects in user input.
1685
     *   
1686
     *   By default, this property is nil, since we want most objects to
1687
     *   be treated as unique.  
1688
     */
1689
    isEquivalent = nil
1690
1691
    /*
1692
     *   Listing equivalence test.  This returns true if we should be
1693
     *   treated as equivalent to the given item in a list, which is to
1694
     *   say that we should be combined with the given item and reported
1695
     *   as one of several such items.
1696
     *   
1697
     *   'options' is the set of LIST_xxx listing options for the object.
1698
     *   In some cases, equivalency might depend upon the listing mode;
1699
     *   for example, in 'tall' and 'recursive' listing mode, an object
1700
     *   with children to display should probably not be marked as
1701
     *   equivalent, since it would have to show its contents list in the
1702
     *   recursive listing.
1703
     *   
1704
     *   By default, this returns true if this object is of the same class
1705
     *   or classes as the given object.  This should be overridden when
1706
     *   an object's listing format will show some kind of additional
1707
     *   state about the item; for example, if the item is shown as
1708
     *   "(providing light)" in listings, but its light can be turned on
1709
     *   and off, items should be grouped only when their on/off states
1710
     *   are the same.  
1711
     */
1712
    isListEquivalent(obj, options)
1713
    {
1714
        /* 
1715
         *   by default, list myself with any other object of the
1716
         *   identical set of superclasses 
1717
         */
1718
        return getSuperclassList() == obj.getSuperclassList();
1719
    }
1720
1721
    /*
1722
     *   "List Group" object.  If this property is set, then this object
1723
     *   is grouped with other objects in the same list group for listing
1724
     *   purposes.  This should be an object of class ListGroup.
1725
     *   
1726
     *   By default, we set this to nil, which makes an object ungrouped
1727
     *   in listings.  
1728
     */
1729
    listWith = nil
1730
1731
    /*
1732
     *   The strength of the light the object is giving off, if indeed it
1733
     *   is giving off light.  This value should be one of the following:
1734
     *   
1735
     *   0: The object is giving off no light at all.
1736
     *   
1737
     *   1: The object is self-illuminating, but doesn't give off enough
1738
     *   light to illuminate any other objects.  This is suitable for
1739
     *   something like an LED digital clock.
1740
     *   
1741
     *   2: The object gives off dim light.  This level is bright enough
1742
     *   to illuminate nearby objects, but not enough to reach distant
1743
     *   objects or go through obscuring media, and not enough for certain
1744
     *   activities requiring strong lighting, such as reading.
1745
     *   
1746
     *   3: The object gives off medium light.  This level is bright
1747
     *   enough to illuminate nearby objects, and is enough for most
1748
     *   activities, including reading and the like.  Traveling a distance
1749
     *   or through an obscuring medium reduces this level to dim (2).
1750
     *   
1751
     *   4: The object gives off strong light.  This level is bright
1752
     *   enough to illuminate nearby objects, and travel through an
1753
     *   obscuring medium or over a distance reduces it to medium light
1754
     *   (3).
1755
     *   
1756
     *   Note that the special value -1 is reserved as an invalid level,
1757
     *   used to flag certain events (such as the need to recalculate the
1758
     *   ambient light level from a new point of view).
1759
     *   
1760
     *   Most objects do not give off light at all.  
1761
     */
1762
    brightness = 0
1763
    
1764
    /*
1765
     *   Sense sizes of the object.  Each object has an individual size
1766
     *   for each sense.  By default, objects are medium for all senses;
1767
     *   this allows them to be sensed from a distance or through an obscuring
1768
     *   medium, but doesn't allow their details to be sensed.
1769
     */
1770
    sightSize = medium
1771
    soundSize = medium
1772
    smellSize = medium
1773
    touchSize = medium
1774
1775
    /*
1776
     *   Determine whether or not the object has a "presence" in each
1777
     *   sense.  An object has a presence in a sense if an actor
1778
     *   immediately adjacent to the object could detect the object by the
1779
     *   sense alone.  For example, an object has a "hearing presence" if
1780
     *   it is making some kind of noise, and does not if it is silent.
1781
     *   
1782
     *   Presence in a given sense is an intrinsic (which does not imply
1783
     *   unchanging) property of the object, in that presence is
1784
     *   independent of the relationship to any given actor.  If an alarm
1785
     *   clock is ringing, it has a hearing presence, unconditionally; it
1786
     *   doesn't matter if the alarm clock is sealed inside a sound-proof
1787
     *   box, because whether or not a given actor has a sense path to the
1788
     *   object is a matter for a different computation.
1789
     *   
1790
     *   Note that presence doesn't control access: an actor might have
1791
     *   access to an object for a sense even if the object has no
1792
     *   presence in the sense.  Presence indicates whether or not the
1793
     *   object is actively emitting sensory data that would make an actor
1794
     *   aware of the object without specifically trying to apply the
1795
     *   sense to the object.
1796
     *   
1797
     *   By default, an object is visible and touchable, but does not emit
1798
     *   any sound or odor.  
1799
     */
1800
    sightPresence = true
1801
    soundPresence = nil
1802
    smellPresence = nil
1803
    touchPresence = true
1804
1805
    /*
1806
     *   My "contents lister."  This is a ShowListInterface object that we
1807
     *   use to display the contents of this object for room descriptions,
1808
     *   inventories, and the like. 
1809
     */
1810
    contentsLister = thingContentsLister
1811
1812
    /*
1813
     *   Determine if I can be sensed under the given conditions.  Returns
1814
     *   true if the object can be sensed, nil if not.  If this method
1815
     *   returns nil, this object will not be considered in scope for the
1816
     *   current conditions.
1817
     *   
1818
     *   By default, we return nil if the ambient energy level for the
1819
     *   object is zero.  If the ambient level is non-zero, we'll return
1820
     *   true in 'transparent' conditions, nil for 'opaque', and we'll let
1821
     *   the sense decide via its canSenseObj() method for any other
1822
     *   transparency conditions.  
1823
     */
1824
    canBeSensed(sense, trans, ambient)
1825
    {
1826
        /* if the ambient level is zero, I can't be sensed this way */
1827
        if (ambient == 0)
1828
            return nil;
1829
        
1830
        /* check the viewing conditions */
1831
        switch(trans)
1832
        {
1833
        case transparent:
1834
            /* under transparent conditions, I appear as myself */
1835
            return true;
1836
1837
        case obscured:
1838
        case distant:
1839
            /* 
1840
             *   ask the sense to determine if I can be sensed under these
1841
             *   conditions 
1842
             */
1843
            return sense.canSenseObj(self, trans, ambient);
1844
1845
        default:
1846
            /* for any other conditions, I can't be sensed at all */
1847
            return nil;
1848
        }
1849
    }
1850
1851
    /*
1852
     *   Call a method on this object from the given point of view.  We'll
1853
     *   push the current point of view, call the method, then restore the
1854
     *   enclosing point of view. 
1855
     */
1856
    fromPOV(pov, propToCall, [args])
1857
    {
1858
        /* push the new point of view */
1859
        pushPOV(pov);
1860
1861
        /* make sure we pop the point of view no matter how we leave */
1862
        try
1863
        {
1864
            /* call the method */
1865
            self.(propToCall)(args...);
1866
        }
1867
        finally
1868
        {
1869
            /* restore the enclosing point of view on the way out */
1870
            popPOV();
1871
        }
1872
    }
1873
1874
    /*
1875
     *   Every Thing has a location, which is the Thing that contains this
1876
     *   object.  A Thing's location can only be a simple object
1877
     *   reference, or nil; it cannot be a list, and it cannot be a method.
1878
     *
1879
     *   If the location is nil, the object does not exist anywhere in the
1880
     *   simulation's physical model.  A nil location can be used to
1881
     *   remove an object from the game world, temporarily or permanently.
1882
     *
1883
     *   In general, the 'location' property should be declared for each
1884
     *   statically defined object (explicitly or implicitly via the '+'
1885
     *   syntax).  'location' is a private property - it should never be
1886
     *   evaluated or changed by any subclass or by any other object.
1887
     *   Only Thing methods may evaluate or change the 'location'
1888
     *   property.  So, you can declare a 'location' property when
1889
     *   defining an object, but you should essentially never refer to
1890
     *   'location' directly in any other context; instead, use the
1891
     *   location and containment methods (isIn, etc) when you want to
1892
     *   know an object's location.
1893
     */
1894
    location = nil
1895
1896
    /*
1897
     *   Initialize my location's contents list - add myself to my
1898
     *   container during initialization
1899
     */
1900
    initializeLocation()
1901
    {
1902
        if (location != nil)
1903
            location.addToContents(self);
1904
    }
1905
1906
    /*
1907
     *   My contents.  This is a list of the objects that this object
1908
     *   directly contains.
1909
     */
1910
    contents = []
1911
1912
    /*
1913
     *   Show the contents of this object.  If the object has any
1914
     *   contents, we'll display a listing of the contents.  
1915
     *   
1916
     *   'options' is the set of flags that we'll pass to showList(), and
1917
     *   has the same meaning as for that function.
1918
     *   
1919
     *   'infoList' is a list of SenseInfoListEntry objects for the
1920
     *   objects that the actor to whom we're showing the contents listing
1921
     *   can see via the sight-like senses.
1922
     *   
1923
     *   This method should be overridden by any object that doesn't store
1924
     *   its contents using a simple 'contents' list property.  
1925
     */
1926
    showObjectContents(pov, lister, options, indent, infoList)
1927
    {
1928
        local cont;
1929
1930
        /* add in the CONTENTS flag to the options */
1931
        options |= LIST_CONTENTS;
1932
1933
        /* start with my direct contents */
1934
        cont = contents;
1935
        
1936
        /* 
1937
         *   keep only the visible objects - only objects in the infoList
1938
         *   are visible from the listing actor's point of view 
1939
         */
1940
        cont = cont.subset({x: infoList.indexWhich({y: y.obj == x}) != nil});
1941
1942
        /* if the surviving list isn't empty, show it */
1943
        if (cont != [])
1944
            showList(pov, self, lister, cont, options, indent, infoList);
1945
    }
1946
1947
    /*
1948
     *   Determine if I'm is inside another Thing.  Returns true if this
1949
     *   object is contained within obj.
1950
     */
1951
    isIn(obj)
1952
    {
1953
        /* if obj is my immediate container, I'm obviously in it */
1954
        if (location == obj)
1955
            return true;
1956
1957
        /* if I have no location, I'm obviously not in obj */
1958
        if (location == nil)
1959
            return nil;
1960
1961
        /* I'm in obj if my container is in obj */
1962
        return location.isIn(obj);
1963
    }
1964
1965
    /*
1966
     *   Determine if I'm directly inside another Thing.  Returns true if
1967
     *   this object is contained directly within obj.  Returns nil if
1968
     *   this object isn't directly within obj, even if it is indirectly
1969
     *   in obj (i.e., its container is directly or indirectly in obj).  
1970
     */
1971
    isDirectlyIn(obj)
1972
    {
1973
        /* I'm directly in obj only if it's my immediate container */
1974
        return location == obj;
1975
    }
1976
1977
    /*
1978
     *   Add an object to my contents.
1979
     */
1980
    addToContents(obj)
1981
    {
1982
        /* add the object to my contents list */
1983
        contents += obj;
1984
    }
1985
1986
    /*
1987
     *   Remove an object from my contents.
1988
     */
1989
    removeFromContents(obj)
1990
    {
1991
        /* remove the object from my contents list */
1992
        contents -= obj;
1993
    }
1994
1995
    /*
1996
     *   Move this object to a new container.
1997
     */
1998
    moveInto(newContainer)
1999
    {
2000
        /* if I have a container, remove myself from its contents list */
2001
        if (location != nil)
2002
            location.removeFromContents(self);
2003
2004
        /* remember my new location */
2005
        location = newContainer;
2006
2007
        /* note that I've been moved */
2008
        isMoved = true;
2009
2010
        /*
2011
         *   if I'm not being moved into nil, add myself to the
2012
         *   container's contents
2013
         */
2014
        if (location != nil)
2015
            location.addToContents(self);
2016
    }
2017
2018
    /*
2019
     *   Get the visual sense information for this object from the current
2020
     *   global point of view.  If we have explicit sense information set
2021
     *   with setSenseInfo, we'll return that; otherwise, we'll calculate
2022
     *   the current sense information for the given point of view.
2023
     *   Returns a SenseInfoListEntry object giving the information.  
2024
     */
2025
    getVisualSenseInfo()
2026
    {
2027
        local lst;
2028
        
2029
        /* if we have explicit sense information already set, use it */
2030
        if (explicitVisualSenseInfo != nil)
2031
            return explicitVisualSenseInfo;
2032
2033
        /* calculate the sense information for the point of view */
2034
        lst = getPOV().visibleInfoList();
2035
2036
        /* find and return the information for myself */
2037
        return lst.valWhich({x: x.obj == self});
2038
    }
2039
2040
    /*
2041
     *   Call a description method with explicit point-of-view and the
2042
     *   related point-of-view sense information.  'pov' is the point of
2043
     *   view object, which is usually an actor; 'senseInfo' is a
2044
     *   SenseInfoListEntry object giving the sense information for this
2045
     *   object, which getSenseInfo() will use instead of dynamically
2046
     *   calculating the sense information for the duration of the routine
2047
     *   called.  
2048
     */
2049
    withVisualSenseInfo(pov, senseInfo, methodToCall, [args])
2050
    {
2051
        local oldSenseInfo;
2052
        
2053
        /* push the sense information */
2054
        oldSenseInfo = setVisualSenseInfo(senseInfo);
2055
2056
        /* push the point of view */
2057
        pushPOV(pov);
2058
2059
        /* make sure we restore the old value no matter how we leave */
2060
        try
2061
        {
2062
            /* call the method with the given arguments */
2063
            self.(methodToCall)(args...);
2064
        }
2065
        finally
2066
        {
2067
            /* restore the old point of view */
2068
            popPOV();
2069
            
2070
            /* restore the old sense information */
2071
            setVisualSenseInfo(oldSenseInfo);
2072
        }
2073
    }
2074
2075
    /* 
2076
     *   Set the explicit visual sense information; if this is not nil,
2077
     *   getVisualSenseInfo() will return this rather than calculating the
2078
     *   live value.  Returns the old value, which is a SenseInfoListEntry
2079
     *   or nil.  
2080
     */
2081
    setVisualSenseInfo(info)
2082
    {
2083
        local oldInfo;
2084
2085
        /* remember the old value */
2086
        oldInfo = explicitVisualSenseInfo;
2087
2088
        /* remember the new value */
2089
        explicitVisualSenseInfo = info;
2090
2091
        /* return the original value */
2092
        return oldInfo;
2093
    }
2094
2095
    /* current explicit visual sense information overriding live value */
2096
    explicitVisualSenseInfo = nil
2097
2098
    /*
2099
     *   Determine how accessible my contents are to a sense.  Any items
2100
     *   contained within a Thing are considered external features of the
2101
     *   Thing, hence they are transparently accessible to all senses.
2102
     */
2103
    transSensingIn(sense) { return transparent; }
2104
2105
    /*
2106
     *   Determine how accessible peers of this object are to the contents
2107
     *   of this object, via a given sense.  This has the same meaning as
2108
     *   transSensingIn(), but in the opposite direction: whereas
2109
     *   transSensingIn() determines how accessible my contents are from
2110
     *   the outside, this determines how accessible the outside is from
2111
     *   the contents.
2112
     *
2113
     *   By default, we simply return the same thing as transSensingIn(),
2114
     *   since most containers are symmetrical for sense passing from
2115
     *   inside to outside or outside to inside.  However, we distinguish
2116
     *   this as a separate method so that asymmetrical containers can
2117
     *   have different effects in the different directions; for example,
2118
     *   a box made of one-way mirrors might be transparent when looking
2119
     *   from the inside to the outside, but opaque in the other
2120
     *   direction.
2121
     */
2122
    transSensingOut(sense) { return transSensingIn(sense); }
2123
2124
    /*
2125
     *   Determine how well I can sense the given object.  Returns a
2126
     *   transparency level indicating how the sense passes from me to the
2127
     *   given object.
2128
     *   
2129
     *   Returns a list consisting of the transparency level, the
2130
     *   obstructor object, and the ambient sense energy level.  If the
2131
     *   path's transparency level is 'transparent' or 'opaque', the
2132
     *   obstructor will always be nil; if the level is 'distant' or
2133
     *   'obscured', the obstructor will be the first object in the path
2134
     *   that reduces the level.
2135
     *   
2136
     *   Note that, because 'distant' and 'obscured' transparency levels
2137
     *   always compound (with one another and with themselves) to opaque,
2138
     *   there will never be more than a single obstructor in a path,
2139
     *   because any path with two or more obstructors would be an opaque
2140
     *   path, and hence not a path at all.  
2141
     */
2142
    senseObj(sense, obj)
2143
    {
2144
        local bestTrans;
2145
        local bestObstructor;
2146
        local bestAmbient;
2147
2148
        /* we haven't found any path yet */
2149
        bestTrans = opaque;
2150
        bestObstructor = nil;
2151
        bestAmbient = nil;
2152
2153
        /* 
2154
         *   Iterate over everything reachable through the sense.  Keep
2155
         *   track of the best path we find, and stop entirely if we find
2156
         *   a transparent path. 
2157
         */
2158
        senseIter(sense, new function(cur, trans, obstructor, ambient) {
2159
            /*
2160
             *   If this is the object we're looking for, and this is the
2161
             *   best transparency so far, note the transparency to this
2162
             *   point.  
2163
             */
2164
            if (cur == obj && transparencyCompare(trans, bestTrans) > 0)
2165
            {
2166
                /* this is the best one yet - note it */
2167
                bestTrans = trans;
2168
                bestObstructor = obstructor;
2169
                bestAmbient = ambient;
2170
2171
                /* 
2172
                 *   if this path is completely transparent, we will never
2173
                 *   find anything better, so return nil to tell the
2174
                 *   caller to stop iterating 
2175
                 */
2176
                if (trans == transparent)
2177
                    return nil;
2178
            }
2179
2180
            /* tell the caller to keep searching */
2181
            return true;
2182
        });
2183
2184
        /* return the best transparency we found */
2185
        return [bestTrans, bestObstructor, bestAmbient];
2186
    }
2187
2188
    /*
2189
     *   Determine this object's level of illumination for the given
2190
     *   senses.  This returns the highest level of brightness of any
2191
     *   energy source that this object can sense with the given senses,
2192
     *   adjusted for transparency along the path to the energy source.
2193
     *   
2194
     *   This returns a brightness level with the same meaning as the
2195
     *   'brightness' property.  
2196
     */
2197
    illuminationLevel(senses)
2198
    {
2199
        local illum;
2200
2201
        /* we have no illumination so far */
2202
        illum = 0;
2203
        
2204
        /* check each of the requested senses */
2205
        foreach (local sense in senses)
2206
        {
2207
            local curIllum;
2208
            
2209
            /* get the ambient energy level for this sense */
2210
            curIllum = senseAmbient(sense);
2211
2212
            /* if this is the highest so far, note it */
2213
            if (curIllum != nil && curIllum > illum)
2214
                illum = curIllum;
2215
        }
2216
2217
        /* return the highest illumination level we found */
2218
        return illum;
2219
    }
2220
2221
    /*
2222
     *   Build a list of all of the objects reachable from me through the
2223
     *   given sense.  
2224
     */
2225
    senseList(sense)
2226
    {
2227
        local lst;
2228
2229
        /* 
2230
         *   start out with an empty vector (use a vector for efficiency -
2231
         *   since we'll be continually adding objects, a vector is a lot
2232
         *   more efficient than a list since a vector can expand without
2233
         *   being reallocated) 
2234
         */
2235
        lst = new Vector(32);
2236
        
2237
        /*
2238
         *   Iterate over everything reachable through the sense, and add
2239
         *   each reachable object to the list 
2240
         */
2241
        senseIter(sense, new function(cur, trans, obstructor, ambient) {
2242
            
2243
            /* add the object to the list so far */
2244
            lst += cur;
2245
2246
            /* tell the caller to proceed with the iteration */
2247
            return true;
2248
        });
2249
2250
        /* return the list we built (as an ordinary list) */
2251
        return lst.toList();
2252
    }
2253
2254
    /*
2255
     *   Build a list of full information on all of the objects reachable
2256
     *   from me through the given sense, along with full information for
2257
     *   each object's sense characteristics.  For each object, the
2258
     *   returned list will contain a SenseInfoList entry describing the
2259
     *   sense conditions from the point of view of 'self' to the object.  
2260
     */
2261
    senseInfoList(sense)
2262
    {
2263
        local lst;
2264
2265
        /* start out with an empty vector */
2266
        lst = new Vector(64);
2267
        
2268
        /*
2269
         *   Iterate over everything reachable through the sense, and add
2270
         *   each reachable object to the list 
2271
         */
2272
        senseIter(sense, new function(cur, trans, obstructor, ambient) {
2273
            
2274
            /* add the information to the list */
2275
            lst += new SenseInfoListEntry(cur, trans, obstructor, ambient);
2276
2277
            /* tell the caller to proceed with the iteration */
2278
            return true;
2279
        });
2280
2281
        /* return the list we built (as an ordinary list) */
2282
        return lst.toList();
2283
    }
2284
2285
    /*
2286
     *   Find an item in a senseInfoList list.  Returns the
2287
     *   SenseInfoListEntry which describes the given item, or nil if
2288
     *   there is no such entry.  
2289
     */
2290
    findSenseInfo(lst, item)
2291
    {
2292
        /* scan the items in the list */
2293
        return lst.valWhich({x: x.obj == item});
2294
    }
2295
2296
    /*
2297
     *   Merge two senseInfoList lists.  Returns a new list containing
2298
     *   only one instance of each item.  If the same object appears in
2299
     *   more than one of the lists, the result list will have the
2300
     *   occurrence with better detail or brightness.  
2301
     */
2302
    mergeSenseInfoList(a, b)
2303
    {
2304
        local lst;
2305
2306
        /* if either list is empty, just return the other list */
2307
        if (a == [])
2308
            return b;
2309
        else if (b == [])
2310
            return a;
2311
        
2312
        /* create a vector for the result list */
2313
        lst = new Vector(max(a.length(), b.length()));
2314
        
2315
        /* 
2316
         *   loop over the items in the first list - keep each item in the
2317
         *   first list, but if it also appears in the second list then
2318
         *   merge it to keep the better of the two occurrences 
2319
         */
2320
        foreach (local aEle in a)
2321
        {
2322
            local keepA;
2323
            local bEle;
2324
2325
            /* assume we'll keep the occurrence from the first list */
2326
            keepA = true;
2327
            
2328
            /* try finding this same item in the second list */
2329
            if ((bEle = findSenseInfo(b, aEle)) != nil)
2330
            {
2331
                /* 
2332
                 *   found it - keep the one with better transparency, or
2333
                 *   better ambient sense energy if the transparencies are
2334
                 *   the same 
2335
                 */
2336
                if (aEle.trans == bEle.trans)
2337
                {
2338
                    /* same transparency - compare energy levels */
2339
                    if (aEle.ambient < bEle.ambient)
2340
                        keepA = nil;
2341
                }
2342
                else if (transparencyCompare(aEle.trans, bEle.trans) < 0)
2343
                {
2344
                    /* a is less transparent than b */
2345
                    keepA = nil;
2346
                }
2347
            }
2348
2349
            /* keep the item from the appropriate list */
2350
            if (keepA)
2351
            {
2352
                /* keep the item from the first list */
2353
                lst += aEle;
2354
            }
2355
            else
2356
            {
2357
                /* keep the item from the second list */
2358
                lst += bEle;
2359
            }
2360
        }
2361
2362
        /* 
2363
         *   loop over the second list, keeping only the items that aren't
2364
         *   in the first list (any item that's also in the first list
2365
         *   will already have been merged from the first pass) 
2366
         */
2367
        foreach (local bEle in b)
2368
        {
2369
            /* 
2370
             *   find the item in the first list - if it's not in the
2371
             *   first list, we need to add this item to the result list 
2372
             */
2373
            if (findSenseInfo(a, bEle) == nil)
2374
            {
2375
                /* keep this item */
2376
                lst += bEle;
2377
            }
2378
        }
2379
2380
        /* return the merged result in list format */
2381
        return lst.toList();
2382
    }
2383
2384
    /*
2385
     *   Iterate over all objects reachable via the given sense.  For each
2386
     *   object, invokes the callback function.
2387
     *   
2388
     *   The callback returns true to continue the iteration, false to
2389
     *   terminate it.  
2390
     */
2391
    senseIter(sense, func)
2392
    {
2393
        local ambient;
2394
        
2395
        /* 
2396
         *   compute the ambient sense energy level (such as the light
2397
         *   level) at the starting point 
2398
         */
2399
        ambient = senseAmbient(sense);
2400
2401
        /* 
2402
         *   perform the iteration, starting with an empty path, an
2403
         *   initial path transparency level of 'transparent', and no
2404
         *   obstructor so far 
2405
         */
2406
        senseIterPath(sense, func, [], transparent, nil, ambient);
2407
    }
2408
2409
    /*
2410
     *   Determine the ambient sense energy level for the given sense from
2411
     *   my point of view.  This is useful for senses such as sight, which
2412
     *   can sense objects not only by the light they emit but also by the
2413
     *   light they reflect.  For sight, this returns the ambient energy
2414
     *   level at me, taking into account all light sources visible from
2415
     *   my point of view.  
2416
     */
2417
    senseAmbient(sense)
2418
    {
2419
        local best;
2420
        
2421
        /* if the sense doesn't use ambient energy, we can ignore this */
2422
        if (sense.ambienceProp == nil)
2423
            return nil;
2424
2425
        /* we haven't seen any energy yet */
2426
        best = 0;
2427
2428
        /* 
2429
         *   Iterate over all objects in line of sight from me, looking
2430
         *   for energy sources.  This is the correct direction to look,
2431
         *   because we want to know if we can see an energy source - if
2432
         *   we can, its energy reaches us and we are illuminated by it.  
2433
         */
2434
        senseIterPath(sense, new function(cur, trans, obstructor, ambient) {
2435
2436
            local br;
2437
                
2438
            /* 
2439
             *   if this object is a energy source, and the amount of
2440
             *   energy that reaches us is greater than the brightest
2441
             *   energy we've seen so far, note it as the strongest energy
2442
             *   level reaching us 
2443
             */
2444
            br = adjustBrightness(cur.(sense.ambienceProp), trans);
2445
            if (br > best)
2446
                best = br;
2447
2448
            /* tell the caller to continue */
2449
            return true;
2450
            
2451
        }, [], transparent, nil, nil);
2452
2453
        /* 
2454
         *   if the ambient level is level 1 (self-illuminating only),
2455
         *   reduce it to nothing, since this doesn't cast energy on any
2456
         *   of our surroundings and hence doesn't count as ambient energy 
2457
         */
2458
        if (best == 1)
2459
            best = 0;
2460
2461
        /* return the best brightness level we found */
2462
        return best;
2463
    }
2464
2465
    /*
2466
     *   Invoke the callback function for a sense iteration, passing self
2467
     *   as the object.  We'll call this for each object we visit in the
2468
     *   course of the iteration.  
2469
     */
2470
    senseIterCallFunc(func, sense, trans, obstructor, ambient)
2471
    {
2472
        /* 
2473
         *   make some ambient energy adjustments if we have an ambient
2474
         *   level at all (some senses do, some senses don't) 
2475
         */
2476
        if (ambient != nil)
2477
        {
2478
            local selfIllum;
2479
            
2480
            /* 
2481
             *   if this object's own self-illumination in this sense is
2482
             *   greater than the ambient level, use the object's
2483
             *   self-illumination level instead 
2484
             */
2485
            selfIllum = self.(sense.ambienceProp);
2486
            if (selfIllum > ambient)
2487
                ambient = selfIllum;
2488
2489
            /* adjust the energy level for the transparency */
2490
            ambient = adjustBrightness(ambient, trans);
2491
2492
            /* 
2493
             *   if this leaves us with no energy at all, we can't sense
2494
             *   the object after all 
2495
             */
2496
            if (ambient == 0)
2497
                return true;
2498
        }
2499
2500
        /* 
2501
         *   Check to see if we can be sensed at all under the current
2502
         *   conditions.  If we can, invoke the callback on this object.
2503
         *   If we can't be sensed at all under these conditions, there's
2504
         *   nothing more to do.  
2505
         */
2506
        if (canBeSensed(sense, trans, ambient))
2507
        {
2508
            /* I can be sensed under these conditions - call the callback */
2509
            return func(self, trans, obstructor, ambient);
2510
        }
2511
        else
2512
        {
2513
            /* 
2514
             *   I can't be sensed under these conditions - skip the
2515
             *   callback and simply tell the caller to keep going 
2516
             */
2517
            return true;
2518
        }
2519
    }
2520
2521
    /*
2522
     *   Iterate over objects that can be reached via the sense from this
2523
     *   vantage.
2524
     *   
2525
     *   'path' is a list of objects that we have already visited and thus
2526
     *   must exclude from further searching.
2527
     *   
2528
     *   'transToHere' is the transparency level up to this point.  If
2529
     *   we're being called recursively, this is the cumulative
2530
     *   transparency level along the path so far.  On the initial call,
2531
     *   this should simply be 'transparent'.
2532
     *   
2533
     *   'obstructor' is the object along the path so far that introduced
2534
     *   a non-transparent level.  On the initial call, this should be nil.
2535
     *   
2536
     *   'ambient' is the ambient sense level along the path so far.  Each
2537
     *   time we traverse a transparent connection, we use the fact that
2538
     *   the ambient level is the same on both sides of a transparent
2539
     *   connection to avoid re-computing the ambient light level on the
2540
     *   other side.  On the initial call, this should be the ambient
2541
     *   light level from self's point of view, as computed with
2542
     *   senseAmbient().  
2543
     */
2544
    senseIterPath(sense, func, path, transToHere, obstructor, ambient)
2545
    {
2546
        /* invoke the callback on myself */
2547
        if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient))
2548
            return nil;
2549
2550
        /* iterate up the containment hierarchy into my location */
2551
        if (!senseIterUp(sense, func, path, transToHere, obstructor, ambient))
2552
            return nil;
2553
2554
        /* sense down the hierarchy into my contents */
2555
        return senseIterDown(sense, func, path,
2556
                             transToHere, obstructor, ambient);
2557
    }
2558
2559
    /*
2560
     *   Iterate over objects reachable up the hierarchy from here.
2561
     *   
2562
     *   Note that we do not invoke the function on self - the caller is
2563
     *   responsible for doing this.
2564
     *   
2565
     *   This is a single-parent implementation.  Multi-parent objects
2566
     *   must override this method.  
2567
     */
2568
    senseIterUp(sense, func, path, transToHere, obstructor, ambient)
2569
    {
2570
        /* 
2571
         *   if I have no location, there's no way up from here - simply
2572
         *   return true in this case, so the caller knows to proceed with
2573
         *   further iteration 
2574
         */
2575
        if (location == nil)
2576
            return true;
2577
2578
        /* 
2579
         *   if my container is already in the path, don't consider it,
2580
         *   since we'd be getting into an infinite loop by going back to
2581
         *   a location we've already considered 
2582
         */
2583
        if (path.indexOf(location) != nil)
2584
            return true;
2585
2586
        /* add myself to the path, so that we don't loop back into me */
2587
        path += self;
2588
2589
        /* look up into the container */
2590
        return location.
2591
            senseIterFromBelow(sense, func, path,
2592
                               transToHere, obstructor, ambient);
2593
    }
2594
2595
    /*
2596
     *   Iterate down the containment hierarchy into my contents, visiting
2597
     *   all of the objects reachable through the sense.
2598
     *   
2599
     *   Note that we do not invoke the callback on self - the caller is
2600
     *   responsible for doing this.  
2601
     */
2602
    senseIterDown(sense, func, path, transToHere, obstructor, ambient)
2603
    {
2604
        local myTrans;
2605
2606
        /* 
2607
         *   figure my transparency looking into my contents, and note if
2608
         *   I'm the first obstructor of the sense so far 
2609
         */
2610
        myTrans = transSensingIn(sense);
2611
        if (myTrans != transparent && obstructor == nil)
2612
            obstructor = self;
2613
2614
        /* 
2615
         *   add the transparency so far to the transparency to my own
2616
         *   contents to get the cumulative transparency from the starting
2617
         *   point to my contents 
2618
         */
2619
        transToHere = transparencyAdd(transToHere, myTrans);
2620
        
2621
        /* 
2622
         *   if I am opaque from the outside, we cannot iterate into my
2623
         *   contents - in this case, just return true to tell the caller
2624
         *   to proceed with its own further iteration 
2625
         */
2626
        if (transToHere == opaque)
2627
            return true;
2628
2629
        /* 
2630
         *   if we have an ambient level, check the transparency looking
2631
         *   out from my contents (we look out rather than in, because in
2632
         *   this case the sense is traveling from the outside to the
2633
         *   inside, hence we want to know what it looks like from the
2634
         *   point of view of the contents) 
2635
         */
2636
        if (ambient != nil)
2637
        {
2638
            /* 
2639
             *   get the transparency looking from inside to outside - if
2640
             *   it's not transparent, we must re-calculate the ambient
2641
             *   sense level within our contents - do so for our first
2642
             *   child only, since all of our children will be at the same
2643
             *   ambient level 
2644
             */
2645
            if (transSensingOut(sense) != transparent
2646
                && contents.length() != 0)
2647
            {
2648
                /* re-calculate the ambient level for our first child */
2649
                ambient = contents[1].senseAmbient(sense);
2650
            }
2651
        }
2652
        
2653
        /* iterate through my contents */
2654
        return senseIterList(sense, func, path, transToHere, obstructor,
2655
                             ambient, contents, &senseIterFromAbove);
2656
    }
2657
2658
    /*
2659
     *   Iterate through a given list of objects related to us in some way
2660
     *   (contents, for example, or multiple containers), invoking the
2661
     *   callback on the objects in the list and the objects further
2662
     *   related to them. 
2663
     */
2664
    senseIterList(sense, func, path, transToHere, obstructor, ambient,
2665
                  objList, objMethod)
2666
    {
2667
        /* add myself to the path, so that we don't loop back into me */
2668
        path += self;
2669
2670
        /* iterate over the list */
2671
        foreach (local cur in objList)
2672
        {
2673
            /* 
2674
             *   if this object is in the path already, don't consider it
2675
             *   - this would get us into a loop, since we've been this
2676
             *   way once before 
2677
             */
2678
            if (path.indexOf(cur) != nil)
2679
                continue;
2680
2681
            /* proceed into this object */
2682
            if (!cur.(objMethod)(sense, func, path,
2683
                                 transToHere, obstructor, ambient))
2684
                return nil;
2685
        }
2686
2687
        /* 
2688
         *   we didn't have anyone tell us to stop, so tell the caller to
2689
         *   proceed 
2690
         */
2691
        return true;
2692
    }
2693
2694
    /*
2695
     *   Iterate over objects reachable from here, traversing into this
2696
     *   object from below us in the containment hierarchy - in other
2697
     *   words, from a child of this object (something contained within
2698
     *   this object).
2699
     *   
2700
     *   When traversing the tree from below, we'll scan both up and down
2701
     *   the hierarchy from here. 
2702
     */
2703
    senseIterFromBelow(sense, func, path, transToHere, obstructor, ambient)
2704
    {
2705
        local myTrans;
2706
2707
        /* 
2708
         *   If the ambient level is -1, this is a special flag indicating
2709
         *   that we need to recalculate the ambient level from our point
2710
         *   of view.  Callers will set this value when traversing a
2711
         *   connection that invalidates the ambient level and they are
2712
         *   unable to calculate the new level themselves.  
2713
         */
2714
        if (ambient == -1)
2715
            ambient = senseAmbient(sense);
2716
2717
        /*
2718
         *   Iterate over other children of the same container.  Items in
2719
         *   the same container are not affected by the transparency
2720
         *   properties of the container itself, since the sense doesn't
2721
         *   have to pass through the container to go from one child to
2722
         *   another.  
2723
         */
2724
        if (!senseIterList(sense, func, path, transToHere, obstructor,
2725
                           ambient, contents, &senseIterFromAbove))
2726
            return nil;
2727
2728
        /*
2729
         *   Regardless of whether we can sense through the container or
2730
         *   not, we can certainly sense the container itself (its
2731
         *   insides, anyway), so invoke the callback on the container
2732
         *   without further loss of transparency 
2733
         */
2734
        if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient))
2735
            return nil;
2736
2737
        /*
2738
         *   Note the transparency going through my containment boundary,
2739
         *   passing from inside to outside; if I'm the first
2740
         *   non-transparent boundary on this path, note that I'm the
2741
         *   obstructor 
2742
         */
2743
        myTrans = transSensingOut(sense);
2744
        if (myTrans != transparent && obstructor == nil)
2745
            obstructor = self;
2746
2747
        /* 
2748
         *   Accumulate the transparency of my boundary into the total
2749
         *   transparency level to this point.  If this makes the total
2750
         *   path opaque, we cannot sense anything above us.  
2751
         */
2752
        transToHere = transparencyAdd(transToHere, myTrans);
2753
        if (transToHere != opaque)
2754
        {
2755
            /*
2756
             *   if we have an ambient level, check the transparency
2757
             *   looking in to my contents (we look in rather than out,
2758
             *   because in this case the sense is traveling from the
2759
             *   inside to the outside, hence we want to know what it
2760
             *   looks like from the point of view of the container) 
2761
             */
2762
            if (ambient != nil
2763
                && transSensingIn(sense) != transparent)
2764
            {
2765
                /* re-calculate the ambient level here */
2766
                ambient = senseAmbient(sense);
2767
            }
2768
            
2769
            /* iterate up through the container */
2770
            if (!senseIterUp(sense, func, path,
2771
                             transToHere, obstructor, ambient))
2772
                return nil;
2773
        }
2774
2775
        /* we didn't find any reason to stop the caller from continuing */
2776
        return true;
2777
    }
2778
2779
    /*
2780
     *   Iterate into this object, coming from above the object in the
2781
     *   containment hierarchy - in other words, from a parent (a
2782
     *   container) of this object.
2783
     *   
2784
     *   When traversing the tree from above, we'll scan only down from
2785
     *   here.  An object that exists in multiple containers does not by
2786
     *   default serve as a sense conduit across those containers, so the
2787
     *   fact that it has other locations is irrelevant to the search.
2788
     *   Connectors override this, because they do connect all of their
2789
     *   locations as sense conduits and hence must look back up the tree
2790
     *   as well as down when entered from above.  
2791
     */
2792
    senseIterFromAbove(sense, func, path, transToHere, obstructor, ambient)
2793
    {
2794
        /* invoke the callback on myself */
2795
        if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient))
2796
            return nil;
2797
        
2798
        /* iterate into my contents */
2799
        return senseIterDown(sense, func, path,
2800
                             transToHere, obstructor, ambient);
2801
    }
2802
;
2803
2804
2805
/* ------------------------------------------------------------------------ */
2806
/*
2807
 *   MultiLoc: this class can be multiply inherited by any object that
2808
 *   must exist in more than one place at a time.  To use this class, put
2809
 *   it BEFORE Thing (or any subclass of Thing) in the object's superclass
2810
 *   list, to ensure that we override the default containment
2811
 *   implementation for the object.  
2812
 */
2813
class MultiLoc: object
2814
    /*
2815
     *   We can be in any number of locations.  Our location must be given
2816
     *   as a list.
2817
     */
2818
    locationList = []
2819
2820
    /*
2821
     *   Initialize my location's contents list - add myself to my
2822
     *   container during initialization
2823
     */
2824
    initializeLocation()
2825
    {
2826
        /*
2827
         *   Add myself to each of my container's contents lists
2828
         */
2829
        locationList.forEach({loc: loc.addToContents(self)});
2830
    }
2831
2832
    /*
2833
     *   Determine if I'm in a given object, directly or indirectly
2834
     */
2835
    isIn(obj)
2836
    {
2837
        /* first, check to see if I'm directly in the given object */
2838
        if (isDirectlyIn(obj))
2839
            return true;
2840
2841
        /*
2842
         *   Look at each object in my location list.  For each location
2843
         *   object, if the location is within the object, I'm within the
2844
         *   object.
2845
         */
2846
        return locationList.indexWhich({loc: loc.isIn(obj)}) != nil;
2847
    }
2848
2849
    /*
2850
     *   Determine if I'm directly in the given object
2851
     */
2852
    isDirectlyIn(obj)
2853
    {
2854
        /*
2855
         *   we're directly in the given object only if the object is in
2856
         *   my list of immediate locations
2857
         */
2858
        return (locationList.indexOf(obj) != nil);
2859
    }
2860
2861
    /*
2862
     *   Note that we don't need to override any of the contents
2863
     *   management methods, since we provide special handling for our
2864
     *   location relationships, not for our contents relationships.
2865
     */
2866
2867
    /*
2868
     *   Move this object into a given single container.  Removes the
2869
     *   object from all of its other containers.
2870
     */
2871
    moveInto(newContainer)
2872
    {
2873
        /* remove myself from all of my current contents */
2874
        locationList.forEach({loc: loc.removeFromContents(self)});
2875
2876
        /* set my location list to include only the new location */
2877
        locationList = [newContainer];
2878
2879
        /* note that I've been moved */
2880
        isMoved = true;
2881
2882
        /* add myself to my new container's contents */
2883
        newContainer.addToContents(self);
2884
    }
2885
2886
    /*
2887
     *   Add this object to a new location.
2888
     */
2889
    moveIntoAdd(newContainer)
2890
    {
2891
        /* note that I've been moved */
2892
        isMoved = true;
2893
2894
        /* add the new container to my list of locations */
2895
        locationList += newContainer;
2896
2897
        /* add myself to my new container's contents */
2898
        newContainer.addToContents(self);
2899
    }
2900
2901
    /*
2902
     *   Remove myself from a given container, leaving myself in any other
2903
     *   containers.
2904
     */
2905
    moveOutOf(cont)
2906
    {
2907
        /* if I'm not actually directly in this container, do nothing */
2908
        if (!isDirectlyIn(cont))
2909
            return;
2910
2911
        /* remove myself from this container's contents list */
2912
        cont.removeFromContents(self);
2913
2914
        /* note that I've been moved */
2915
        isMoved = true;
2916
2917
        /* remove this container from my location list */
2918
        locationList -= cont;
2919
    }
2920
2921
    /*
2922
     *   Look up the containment hierarchy at my parents, trying to find a
2923
     *   path to the given object.  
2924
     */
2925
    senseIterUp(sense, func, path, transToHere, obstructor, ambient)
2926
    {
2927
        /* iterate over all of my locations */
2928
        return senseIterList(sense, func, path, transToHere, obstructor,
2929
                             ambient, locationList, &senseIterFromBelow);
2930
    }
2931
;
2932
2933
/* ------------------------------------------------------------------------ */
2934
/*
2935
 *   Multi-Location item with automatic initialization.  This is a
2936
 *   subclass of MultiLoc for objects with computed initial locations.
2937
 *   Each instance must provide one of the following:
2938
 *   
2939
 *   - Override initializeLocation() with a method that initializes the
2940
 *   location list by calling moveIntoAdd() for each location.  If this
2941
 *   method isn't overridden, the default implementation will initialize
2942
 *   the location list using initialLocationClass and/or isInitiallyIn(),
2943
 *   as described below.
2944
 *   
2945
 *   - Define the method isInitiallyIn(loc) to return true if the 'loc' is
2946
 *   one of the object's initial containers, nil if not.  The default
2947
 *   implementation of this method simply returns true, so if this isn't
2948
 *   overridden, every object matching the initialLocationClass() will be
2949
 *   part of the contents list.
2950
 *   
2951
 *   - Define the property initialLocationClass as a class object.  We
2952
 *   will add each instance of the class that passes the isInitiallyIn()
2953
 *   test to the location list.  If this is nil, we'll test every object
2954
 *   instance with the isInitiallyIn() method.  
2955
 */
2956
class AutoMultiLoc: MultiLoc
2957
    /* initialize the location */
2958
    initializeLocation()
2959
    {
2960
        /* get the list of locations */
2961
        locationList = buildLocationList();
2962
2963
        /* add ourselves into each of our containers */
2964
        foreach (local loc in locationList)
2965
            loc.addToContents(self);
2966
    }
2967
2968
    /*
2969
     *   build my list of locations, and return the list 
2970
     */
2971
    buildLocationList()
2972
    {
2973
        local lst;
2974
2975
        /* we have nothing in our list yet */
2976
        lst = new Vector(16);
2977
2978
        /*
2979
         *   if initialLocationClass is defined, loop over all objects of
2980
         *   that class; otherwise, loop over all objects
2981
         */
2982
        if (initialLocationClass != nil)
2983
        {
2984
            /* loop over all instances of the given class */
2985
            for (local obj = firstObj(initialLocationClass) ; obj != nil ;
2986
                 obj = nextObj(obj, initialLocationClass))
2987
            {
2988
                /* if the object passes the test, include it */
2989
                if (isInitiallyIn(obj))
2990
                    lst += obj;
2991
            }
2992
        }
2993
        else
2994
        {
2995
            /* loop over all objects */
2996
            for (local obj = firstObj() ; obj != nil ; obj = nextObj(obj))
2997
            {
2998
                /* if the object passes the test, include it */
2999
                if (isInitiallyIn(obj))
3000
                    lst += obj;
3001
            }
3002
        }
3003
3004
        /* return the list of locations */
3005
        return lst.toList();
3006
    }
3007
3008
    /*
3009
     *   Class of our locations.  If this is nil, we'll test every object
3010
     *   in the entire game with our isInitiallyIn() method; otherwise,
3011
     *   we'll test only objects of the given class.
3012
     */
3013
    initialLocationClass = nil
3014
3015
    /*
3016
     *   Test an object for inclusion in our initial location list.  By
3017
     *   default, we'll simply return true to include every object.  We
3018
     *   return true by default so that an instance can merely specify a
3019
     *   value for initialLocationClass in order to place this object in
3020
     *   every instance of the given class.
3021
     */
3022
    isInitiallyIn(obj) { return true; }
3023
;
3024
3025
/*
3026
 *   Dynamic Multi Location Item.  This is almost exactly the same as a
3027
 *   regular multi-location item with automatic initialization, but the
3028
 *   library will re-initialize the location of these items, by calling
3029
 *   the object's reInitializeLocation(), at the start of every turn.  
3030
 */
3031
class DynamicMultiLoc: AutoMultiLoc
3032
    reInitializeLocation()
3033
    {
3034
        local newList;
3035
3036
        /* build the new location list */
3037
        newList = buildLocationList();
3038
3039
        /*
3040
         *   Update any containers that are not in the intersection of the
3041
         *   two lists.  Note that we don't simply move ourselves out of
3042
         *   the old list and into the new list, because the two lists
3043
         *   could have common members; to avoid unnecessary work that
3044
         *   might result from removing ourselves from a container and
3045
         *   then adding ourselves right back in to the same container, we
3046
         *   only notify containers when we're actually moving out or
3047
         *   moving in. 
3048
         */
3049
3050
        /* 
3051
         *   For each item in the old list, if it's not in the new list,
3052
         *   notify the old container that we're being removed.
3053
         */
3054
        foreach (local loc in locationList)
3055
        {
3056
            /* if it's not in the new list, remove me from the container */
3057
            if (newList.indexOf(loc) == nil)
3058
                loc.removeFromContents(self);
3059
        }
3060
3061
        /* 
3062
         *   for each item in the new list, if we weren't already in this
3063
         *   location, add ourselves to the location 
3064
         */
3065
        foreach (local loc in newList)
3066
        {
3067
            /* if it's not in the old list, add me to the new container */
3068
            if (!isDirectlyIn(loc) == nil)
3069
                loc.addToContents(self);
3070
        }
3071
        
3072
        /* make the new location list current */
3073
        locationList = newList;
3074
    }
3075
;
3076
3077
3078
/* ------------------------------------------------------------------------ */
3079
/*
3080
 *   Openable: a mix-in class that can be combined with an object's other
3081
 *   superclasses to make the object respond to the verbs "open" and
3082
 *   "close."  
3083
 */
3084
class Openable: object
3085
;
3086
3087
/* ------------------------------------------------------------------------ */
3088
/*
3089
 *   Lockable: a mix-in class that can be combined with an object's other
3090
 *   superclasses to make the object respond to the verbs "lock" and
3091
 *   "unlock."  A Lockable requires no key.
3092
 *   
3093
 *   Note that Lockable and LockableWithKey are mutually exclusive - an
3094
 *   object can inherit from only one of these classes.  
3095
 */
3096
class Lockable: object
3097
;
3098
3099
/* ------------------------------------------------------------------------ */
3100
/*
3101
 *   LockableWithKey: a mix-in class that can be combined with an object's
3102
 *   other superclasses to make the object respond to the verbs "lock" and
3103
 *   "unlock," with a key as an indirect object.  A LockableWithKey cannot
3104
 *   be locked or unlocked except with the keys listed in the keyList
3105
 *   property.
3106
 *   
3107
 *   Note that Lockable and LockableWithKey are mutually exclusive - an
3108
 *   object can inherit from only one of these classes.  
3109
 */
3110
class LockableWithKey: object
3111
    /* the list of objects that can serve as keys for this object */
3112
    keyList = []
3113
;
3114
3115
/* ------------------------------------------------------------------------ */
3116
/*
3117
 *   Container: an object that can have other objects placed within it.  
3118
 */
3119
class Container: Thing
3120
    /* 
3121
     *   My current open/closed state.  By default, this state never
3122
     *   changes, but is fixed in the object's definition; for example, a
3123
     *   box without a lid would always be open, while a hollow glass cube
3124
     *   would always be closed.  Our default state is open. 
3125
     */
3126
    isOpen = true
3127
3128
    /* the material that the container is made of */
3129
    material = adventium
3130
3131
    /*
3132
     *   Determine how a sense passes to my contents.  If I'm open, the
3133
     *   sense passes through directly, since there's nothing in the way.
3134
     *   If I'm closed, the sense must pass through my material.  
3135
     */
3136
    transSensingIn(sense)
3137
    {
3138
        if (isOpen)
3139
        {
3140
            /* I'm open, so the sense passes through without interference */
3141
            return transparent;
3142
        }
3143
        else
3144
        {
3145
            /* I'm closed, so the sense must pass through my material */
3146
            return material.senseThru(sense);
3147
        }
3148
    }
3149
;
3150
3151
/* ------------------------------------------------------------------------ */
3152
/*
3153
 *   OpenableContainer: an object that can contain things, and which can
3154
 *   be opened and closed.  
3155
 */
3156
class OpenableContainer: Openable, Container
3157
;
3158
3159
/* ------------------------------------------------------------------------ */
3160
/*
3161
 *   LockableContainer: an object that can contain things, and that can be
3162
 *   opened and closed as well as locked and unlocked.  
3163
 */
3164
class LockableContainer: Lockable, OpenableContainer
3165
;
3166
3167
/* ------------------------------------------------------------------------ */
3168
/*
3169
 *   KeyedContainer: an openable container that can be locked and
3170
 *   unlocked, but only with a specified key.  
3171
 */
3172
class KeyedContainer: LockableWithKey, OpenableContainer
3173
;
3174
3175
3176
/* ------------------------------------------------------------------------ */
3177
/*
3178
 *   Surface: an object that can have other objects placed on top of it.
3179
 *   A surface is essentially the same as a regular container, but the
3180
 *   contents of a surface behave as though they are on the surface's top
3181
 *   rather than contained within the object.  
3182
 */
3183
class Surface: Thing
3184
    /* my contents lister */
3185
    contentsLister = surfaceContentsLister
3186
;
3187
3188
3189
/* ------------------------------------------------------------------------ */
3190
/*
3191
 *   Room: the basic class for game locations.  This is the smallest unit
3192
 *   of movement; we do not distinguish among locations within a room,
3193
 *   even if a Room represents a physically large location.  If it is
3194
 *   necessary to distinguish among different locations in a large
3195
 *   physical room, simply divide the physical room into sections and
3196
 *   represent each section with a separate Room object.
3197
 *   
3198
 *   A Room is not necessarily indoors; it is simply a location where an
3199
 *   actor can be located.  This peculiar usage of "room" to denote any
3200
 *   atomic location, even outdoors, was adopted by the authors of the
3201
 *   earliest adventure games, and has been the convention ever since.
3202
 *   
3203
 *   A room's contents are the objects contained directly within the room.
3204
 *   These include fixed features of the room as well as loose items in
3205
 *   the room, which are effectively "on the floor" in the room.  
3206
 */
3207
class Room: Thing
3208
    /*
3209
     *   Most rooms provide their own implicit lighting.  We'll use
3210
     *   'medium' lighting (level 3) by default, which provides enough
3211
     *   light for all activities, but is reduced to dim light (level 2)
3212
     *   when it goes through obscuring media or over distance.  
3213
     */
3214
    brightness = 3
3215
3216
    /*
3217
     *   Flag: we've seen this location before.  We'll set this to true
3218
     *   the first time we see the location (as long as the location isn't
3219
     *   dark). 
3220
     */
3221
    isSeen = nil
3222
3223
    /*
3224
     *   The room's long description.  This is displayed in a "verbose"
3225
     *   display of the room.  
3226
     */
3227
    lDesc = ""
3228
3229
    /* 
3230
     *   The room's first description.  This is the description we'll
3231
     *   display the first time we see the room when it isn't dark (it's
3232
     *   the first time if isSeen is nil).  By default, we'll just display
3233
     *   the normal long description, but a room can override this if it
3234
     *   wants a special description on the first non-dark arrival.  
3235
     */
3236
    firstDesc { lDesc; }
3237
3238
    /*
3239
     *   Display my short description when the room is dark.  By default,
3240
     *   we display a standard library message. 
3241
     */
3242
    darkSDesc { libMessages.roomDarkSDesc; }
3243
3244
    /* 
3245
     *   Display my long description when the room is dark.  Note that
3246
     *   authors must take special care to list items that are both
3247
     *   self-illuminating and marked as non-listed (isListed = nil) here,
3248
     *   because such items will not be otherwise displayed (non-listed
3249
     *   items are non-listed in a dark room, just like in a lit room).  
3250
     */
3251
    darkLDesc { libMessages.roomDarkLDesc; }
3252
3253
    /*
3254
     *   Describe the room.  Produces the full description of the room if
3255
     *   'verbose' is true, or a briefer description if not.
3256
     *   
3257
     *   Note that this method must be overridden if the room uses a
3258
     *   non-conventional contents mechanism (i.e., it doesn't store its
3259
     *   complete set of direct contents in a 'contents' list property).  
3260
     */
3261
    lookAround(actor, verbose)
3262
    {
3263
        local illum;
3264
        local infoList;
3265
3266
        /* 
3267
         *   get a list of all of the objects that the actor can sense in
3268
         *   the location using sight-like senses (such as sight) 
3269
         */
3270
        infoList = actor.visibleInfoList();
3271
3272
        /* 
3273
         *   drop the actor, to ensure that we don't list the actor doing
3274
         *   the looking among the room's contents 
3275
         */
3276
        infoList = infoList.subset({x: x.obj != actor});
3277
        
3278
        /* 
3279
         *   check for ambient illumination in the room for the actor's
3280
         *   sight-like senses 
3281
         */
3282
        illum = illuminationLevel(actor.sightlikeSenses);
3283
        
3284
        /* display the short description */
3285
        "<.roomname>";
3286
        statusDescIllum(actor, illum);
3287
        "<./roomname>";
3288
3289
        /* if we're in verbose mode, display the full description */
3290
        if (verbose)
3291
        {
3292
            local initDescItems;
3293
3294
            "<.roomdesc>";
3295
            
3296
            /* 
3297
             *   check for illumination - we must have at least dim
3298
             *   ambient lighting (level 2) to see the room's long
3299
             *   description 
3300
             */
3301
            if (illum > 1)
3302
            {
3303
                /* 
3304
                 *   display the normal description of the room - use the
3305
                 *   firstDesc if this is the first time in the room,
3306
                 *   otherwise the lDesc 
3307
                 */
3308
                if (isSeen)
3309
                {
3310
                    /* we've seen it already - show the regular description */
3311
                    lDesc;
3312
                }
3313
                else
3314
                {
3315
                    /* 
3316
                     *   we've never seen this location before - show the
3317
                     *   first-time description 
3318
                     */
3319
                    firstDesc;
3320
3321
                    /* note that we've seen it now */
3322
                    isSeen = true;
3323
                }
3324
            }
3325
            else
3326
            {
3327
                /* display the in-the-dark description of the room */
3328
                darkLDesc;
3329
            }
3330
3331
            "<./roomdesc>";
3332
3333
            /* 
3334
             *   Display any initial-location messages for objects
3335
             *   directly within the room.  These messages are part of the
3336
             *   verbose description rather than the portable item
3337
             *   listing, because these messages are meant to look like
3338
             *   part of the room's full description and thus should not
3339
             *   be included in a non-verbose listing.
3340
             *   
3341
             *   Start out with everything in the room's contents.  
3342
             */
3343
            initDescItems = contents;
3344
3345
            /* 
3346
             *   If the room isn't at least dimly lit (level 2), only keep
3347
             *   items that are self-illuminating.  
3348
             */
3349
            if (illum < 2)
3350
            {
3351
                /* 
3352
                 *   keep only the items sensible to the actor - note that
3353
                 *   it's faster to compute the entire list of sensible
3354
                 *   objects and then intersect it with our contents list
3355
                 *   than it is to test each item in the contents list
3356
                 *   separately, because the list computation is optimized
3357
                 *   to carry light levels throughout the calculation,
3358
                 *   whereas testing each item individually requires
3359
                 *   starting from scratch on each item 
3360
                 */
3361
                initDescItems = initDescItems.subset(
3362
                    {x: infoList.indexWhich({y: y.obj == x}) != nil});
3363
            }
3364
3365
            /* show each initial description item */
3366
            foreach (local cur in initDescItems)
3367
            {
3368
                /* if this item uses an initial description, display it */
3369
                if (cur.useInitDesc())
3370
                {
3371
                    /* start a new paragraph */
3372
                    "<.roompara>";
3373
                    
3374
                    /* display the item's initial description */
3375
                    cur.initDesc;
3376
                }
3377
            }
3378
        }
3379
3380
        /* 
3381
         *   Describe each visible object directly contained in the room
3382
         *   that wants to be listed - generally, we list any mobile
3383
         *   objects that don't have special initial descriptions.  We
3384
         *   list items in all room descriptions, verbose or not, because
3385
         *   the portable item list is more of a status display than it is
3386
         *   a part of the full description.
3387
         */
3388
        showRoomContents(actor, illum, infoList);
3389
    }
3390
3391
    /*
3392
     *   Display the contents of the room.  The illumination level is
3393
     *   provided so we know how to construct the list.
3394
     *   
3395
     *   Note that this method must be overridden if the room uses a
3396
     *   non-conventional contents mechanism (i.e., it doesn't store its
3397
     *   complete set of direct contents in a 'contents' list property).  
3398
     */
3399
    showRoomContents(actor, illum, infoList)
3400
    {
3401
        local lst;
3402
        local lister;
3403
3404
        /* 
3405
         *   if the illumination is less than 'dim' (level 2), display
3406
         *   only self-illuminating items 
3407
         */
3408
        if (illum != nil && illum < 2)
3409
        {
3410
            /* 
3411
             *   We're in the dark - list only those objects that the
3412
             *   actor can sense via the sight-like senses, which will be
3413
             *   the list of self-illuminating objects.  (To produce this
3414
             *   list, simply make a list consisting of only the 'obj'
3415
             *   from each sense info entry in the sense info list.)  
3416
             */
3417
            lst = infoList.mapAll({x: x.obj});
3418
            
3419
            /* use my dark lister */
3420
            lister = darkRoomLister;
3421
        }
3422
        else
3423
        {
3424
            /* start with my contents list */
3425
            lst = contents;
3426
3427
            /* always remove the actor from the list */
3428
            lst -= actor;
3429
3430
            /* use the normal (lighted) lister */
3431
            lister = roomLister;
3432
        }
3433
3434
        /* start a new paragraph if the list isn't empty */
3435
        if (lst != [])
3436
            "<.roompara>";
3437
3438
        /* show the contents */
3439
        showList(actor, self, lister, lst, 0, 0, infoList);
3440
3441
        /* show the (visible) contents of the contents */
3442
        showContentsList(actor, lst, 0, 0, infoList);
3443
    }
3444
3445
    /* 
3446
     *   Get my lighted room lister - this is the ShowListInterface object
3447
     *   that we use to display the room's contents when the room is lit.
3448
     *   We'll return the default library room lister.
3449
     */
3450
    roomLister { return libMessages.roomLister; }
3451
3452
    /* 
3453
     *   Get my dark room lister - this is the ShowListInterface object
3454
     *   we'll use to display the room's self-illuminating contents when
3455
     *   the room is dark. 
3456
     */
3457
    darkRoomLister { return libMessages.darkRoomLister; }
3458
3459
    /*
3460
     *   Display the "status line" description of the room.  This is
3461
     *   normally a brief, single-line description giving, effectively,
3462
     *   the name of the room.
3463
     *   
3464
     *   By long-standing convention, each location in a game usually has
3465
     *   a distinctive name that's displayed here.  Players usually find
3466
     *   these names helpful in forming a mental map of the game.
3467
     *   
3468
     *   By default, we'll simply display the room's short description.
3469
     */
3470
    statusDesc(actor)
3471
    {
3472
        /* the description depends on whether we're illuminated or not */
3473
        statusDescIllum(actor, illuminationLevel(actor.sightlikeSenses));
3474
    }
3475
3476
    /* 
3477
     *   Display the status line description given our illumination
3478
     *   status.  (This separate version of statusDesc is provided so that
3479
     *   we can avoid re-calculating our illumination status if the caller
3480
     *   has already done so for its own purposes.) 
3481
     */
3482
    statusDescIllum(actor, illum)
3483
    {
3484
        /* 
3485
         *   Check for illumination.  We must have at least dim light
3486
         *   (level 2) to show the room's description. 
3487
         */
3488
        if (illum > 1)
3489
        {
3490
            /* there's light - display my short description */
3491
            sDesc;
3492
        }
3493
        else
3494
        {
3495
            /* no light - display the in-the-dark message */
3496
            darkSDesc;
3497
        }
3498
    }
3499
;
3500
3501
/*
3502
 *   A dark room, which provides no light of its own 
3503
 */
3504
class DarkRoom: Room
3505
    /* 
3506
     *   turn off the lights 
3507
     */
3508
    brightness = 0
3509
;
3510
3511
3512
/* ------------------------------------------------------------------------ */
3513
/*
3514
 *   Connector: an object that can pass senses across room boundaries.
3515
 *   This is a mix-in class.
3516
 *   
3517
 *   A Connector acts as a sense conduit across all of its locations, so
3518
 *   to establish a connection between locations, simply place a Connector
3519
 *   in each location.  Since a Connector is useful only when placed
3520
 *   placed in multiple locations, Connector is based on MultiLoc.  
3521
 */
3522
class Connector: MultiLoc
3523
    /*
3524
     *   A Connector's material generally determines how senses pass
3525
     *   through the connection.
3526
     */
3527
    connectorMaterial = adventium
3528
3529
    /*
3530
     *   Determine how senses pass through this connection.  By default,
3531
     *   we simply use the material's transparency.
3532
     */
3533
    transSensingThru(sense) { return connectorMaterial.senseThru(sense); }
3534
3535
    /*
3536
     *   A Connector is a sense conduit connecting all of the locations in
3537
     *   which it exists.  Therefore, when traversing the containment tree
3538
     *   looking for a sense path, we must always traverse all of a
3539
     *   Connector's parents, even when we are coming into this object
3540
     *   from one of our parents.  (The 'path' exclusion prevents us from
3541
     *   traversing back into the same parent we just came from, of
3542
     *   course.)  
3543
     */
3544
    senseIterFromAbove(sense, func, path, transToHere, obstructor, ambient)
3545
    {
3546
        local myTrans;
3547
        local transUp;
3548
        local obstructorUp = obstructor;
3549
3550
        /* 
3551
         *   First, try looking up the containment hierarchy at my other
3552
         *   locations.  This has the effect of looking from one of our
3553
         *   containers, through our connection material, to each of our
3554
         *   other containers.
3555
         *   
3556
         *   Since we're looking through our connection material, we must
3557
         *   find the cumulative transparency of the full path through the
3558
         *   material.
3559
         *   
3560
         *   Note that if I'm the first non-transparent item in the path
3561
         *   so far, I'm the obstructor on this path.  
3562
         */
3563
        myTrans = transSensingThru(sense);
3564
        if (myTrans != transparent && obstructor == nil)
3565
            obstructorUp = self;
3566
3567
        /* note the accumulated transparency along the through path */
3568
        transUp = transparencyAdd(myTrans, transToHere);
3569
3570
        /* 
3571
         *   iterate up the hierarchy into my other containers - note that
3572
         *   we need only do this if the total path so far is not opaque 
3573
         */
3574
        if (transUp != opaque)
3575
        {
3576
            local myAmbient = ambient;
3577
            
3578
            /* 
3579
             *   if the connection is not transparent, we must
3580
             *   re-calculate the ambient light level when get to each
3581
             *   other connected location 
3582
             */
3583
            if (ambient != nil && myTrans != transparent)
3584
            {
3585
                /* 
3586
                 *   Set the ambient level to -1 to indicate that we must
3587
                 *   re-calculate the ambient level for each location.  We
3588
                 *   can't calculate it ourselves, because each location
3589
                 *   will need to calculate it separately. 
3590
                 */
3591
                myAmbient = -1;
3592
            }
3593
            
3594
            /* iterate up through my other containers */
3595
            if (!senseIterUp(sense, func, path,
3596
                             transUp, obstructorUp, myAmbient))
3597
                return nil;
3598
        }
3599
3600
        /*
3601
         *   We can sense this object itself.  Note that our connection
3602
         *   material's transparency is not relevant here, since we do not
3603
         *   have to look through ourself to see ourself! 
3604
         */
3605
        if (!senseIterCallFunc(func, sense, transToHere, obstructor, ambient))
3606
            return nil;
3607
3608
        /* 
3609
         *   Finally, try looking down the hierarchy at my children.  Note
3610
         *   that, when looking at my children, we do not consider our
3611
         *   connection material's transparency, because we do not see
3612
         *   children through the connecting material.  
3613
         */
3614
        return senseIterDown(sense, func, path,
3615
                             transToHere, obstructor, ambient);
3616
    }
3617
;
3618
3619
/* ------------------------------------------------------------------------ */
3620
/*
3621
 *   An item that can be worn
3622
 */
3623
class Wearable: Thing
3624
    /* is the item currently being worn? */
3625
    isWorn()
3626
    {
3627
        /* it's being worn if the wearer is non-nil */
3628
        return wornBy != nil;
3629
    }
3630
3631
    /* 
3632
     *   The object wearing this object, if any; if I'm not being worn,
3633
     *   this is nil.  The wearer should always be a container (direct or
3634
     *   indirect) of this object - in order to wear something, you must
3635
     *   be carrying it.  In most cases, the wearer should be the direct
3636
     *   container of the object.
3637
     *   
3638
     *   The reason we keep track of who's wearing the object (rather than
3639
     *   simply keeping track of whether it's being worn) is to allow for
3640
     *   cases where an actor is carrying another actor.  Since this
3641
     *   object will be (indirectly) inside both actors in such cases, we
3642
     *   would have to inspect intermediate containers to determine
3643
     *   whether or not the outer actor was wearing the object if we
3644
     *   didn't keep track of the wearer directly.  
3645
     */
3646
    wornBy = nil
3647
3648
    /* am I worn by the given object? */
3649
    isWornBy(actor)
3650
    {
3651
        return wornBy == actor;
3652
    }
3653
3654
    /*
3655
     *   Determine if I'm equivalent to another object of the same class
3656
     *   for listing purposes.  If we're showing the "(being worn)"
3657
     *   status, then to be equivalent, two wearables must have the same
3658
     *   being-worn state to be listed together.  
3659
     */
3660
    isListEquivalent(obj, options)
3661
    {
3662
        /* 
3663
         *   if we're not equivalent by the standard test, we're not
3664
         *   equivalent 
3665
         */
3666
        if (!inherited.isListEquivalent(obj, options))
3667
            return nil;
3668
3669
        /* 
3670
         *   if we're displaying an explicit list of worn items, then we
3671
         *   won't need a "being worn" status message, so this doesn't
3672
         *   differentiate objects - in this case, the objects are
3673
         *   equivalent regardless of their 'worn' status 
3674
         */
3675
        if (!(options & LIST_WORN))
3676
            return true;
3677
        
3678
        /* 
3679
         *   we pass the standard test, but we're showing the 'worn'
3680
         *   status in the display - the objects are thus equivalent for
3681
         *   listing purposes only if their states are the same 
3682
         */
3683
        return isWorn() == obj.isWorn();
3684
    }
3685
    
3686
    /* show myself in a listing */
3687
    showListItem(options, pov, senseInfo)
3688
    {
3689
        /* inherit the default handling */
3690
        inherited.showListItem(options, pov, senseInfo);
3691
3692
        /* show our 'worn' status */
3693
        wornDesc(options);
3694
    }
3695
3696
    /* show myself in a counted listing */
3697
    showListItemCounted(equivCount, options, pov, senseInfo)
3698
    {
3699
        /* inherit the default handling */
3700
        inherited.showListItemCounted(equivCount, options, pov, senseInfo);
3701
3702
        /* show our 'worn' status */
3703
        wornDesc(options);
3704
    }
3705
3706
    /* show my 'worn' status */
3707
    wornDesc(options)
3708
    {
3709
        /* 
3710
         *   show our status only if we're being worn and this isn't an
3711
         *   explicit list of items being worn (if it is, the list will
3712
         *   announce that all of the items are being worn, hence the
3713
         *   individual items don't need to be marked as such) 
3714
         */
3715
        if (isWorn() && !(options & LIST_WORN))
3716
            libMessages.wornDesc;
3717
    }
3718
;
3719
3720
/* ------------------------------------------------------------------------ */
3721
/*
3722
 *   An item that can provide light.
3723
 *   
3724
 *   Any Thing can provide light, but this class should be used for
3725
 *   objects that explicitly serve as light sources from the player's
3726
 *   perspective.  Objects of this class display a "providing light"
3727
 *   status message in inventory listings, and can be turned on and off
3728
 *   via the isLit property.  
3729
 */
3730
class LightSource: Thing
3731
    /* is the light source currently turned on? */
3732
    isLit = true
3733
3734
    /* the brightness that the object has when it is on and off */
3735
    brightnessOn = 3
3736
    brightnessOff = 0
3737
3738
    /* 
3739
     *   return the appropriate on/off brightness, depending on whether or
3740
     *   not we're currently lit 
3741
     */
3742
    brightness { return isLit ? brightnessOn : brightnessOff; }
3743
3744
    /*
3745
     *   Determine if I'm equivalent to another object of the same class
3746
     *   for listing purposes.  To be equivalent, two light sources must
3747
     *   have the same isLit status, because we show the status (via a
3748
     *   "providing light" message) in listings.  
3749
     */
3750
    isListEquivalent(obj, options)
3751
    {
3752
        /* 
3753
         *   if we're not equivalent by the standard test, we're not
3754
         *   equivalent 
3755
         */
3756
        if (!inherited.isListEquivalent(obj, options))
3757
            return nil;
3758
        
3759
        /* 
3760
         *   we pass the standard test, so check to see if the 'lit'
3761
         *   states of the two objects are the same - if they are, we're
3762
         *   equivalent, otherwise we're not 
3763
         */
3764
        return isLit == obj.isLit;
3765
    }
3766
3767
    /* show myself in a listing */
3768
    showListItem(options, pov, senseInfo)
3769
    {
3770
        /* inherit the default handling */
3771
        inherited.showListItem(options, pov, senseInfo);
3772
3773
        /* show our light status */
3774
        providingLightDesc;
3775
    }
3776
3777
    /* show myself in a counted listing */
3778
    showListItemCounted(equivCount, options, pov, senseInfo)
3779
    {
3780
        /* inherit the default handling */
3781
        inherited.showListItemCounted(equivCount, options, pov, senseInfo);
3782
3783
        /* show our light status */
3784
        providingLightDesc;
3785
    }
3786
3787
    /* 
3788
     *   show our "providing light" status, but only if we actually are
3789
     *   providing light 
3790
     */
3791
    providingLightDesc
3792
    {
3793
        /* 
3794
         *   if we're providing light to surrounding objects, say so -
3795
         *   note that we do not provide light to any nearby objects if
3796
         *   we're merely self-illuminating (which is the case if our
3797
         *   brightness is 1) 
3798
         */
3799
        if (brightness > 1)
3800
            libMessages.providingLightDesc;
3801
    }
3802
;
3803
3804
/* ------------------------------------------------------------------------ */
3805
/*
3806
 *   An Actor is a living person, animal, or other entity with a will of
3807
 *   its own.
3808
 *   
3809
 *   An actor's contents are the things the actor is carrying or wearing.  
3810
 */
3811
class Actor: Thing
3812
    /*
3813
     *   Actors usually have proper names, so they don't need articles
3814
     *   when their names are used (just "Bob", not "a Bob" or "the Bob") 
3815
     */
3816
    aDesc { sDesc; }
3817
    theDesc { sDesc; }
3818
    
3819
    /*
3820
     *   The senses that determine scope for this actor.  An actor might
3821
     *   possess only a subset of the defined sense.
3822
     *   
3823
     *   By default, we give each actor all of the human senses that we
3824
     *   define, except touch.  In general, merely being able to touch an
3825
     *   object doesn't put the object in scope, because an actor can
3826
     *   usually touch a number of objects that aren't immediately within
3827
     *   reach.  
3828
     */
3829
    scopeSenses = [sight, sound, smell]
3830
3831
    /*
3832
     *   "Sight-like" senses: these are the senses that operate like sight
3833
     *   for the actor, and which the actor can use to determine the names
3834
     *   of objects and the spatial relationships between objects.  These
3835
     *   senses should operate passively, in the sense that they should
3836
     *   tend to collect sensory input continuously and without explicit
3837
     *   action by the actor, the way sight does and the way touch, for
3838
     *   example, does not.  These senses should also operate instantly,
3839
     *   in the sense that the sense can reasonably take in most or all of
3840
     *   a location at one time.
3841
     *   
3842
     *   These senses are used to determine what objects should be listed
3843
     *   in room descriptions, for example.
3844
     *   
3845
     *   By default, the only sight-like sense is sight, since other human
3846
     *   senses don't normally provide a clear picture of the spatial
3847
     *   relationships among objects.  (Touch could with some degree of
3848
     *   effort, but it can't operate passively or instantly, since
3849
     *   deliberate and time-consuming action would be necessary.)
3850
     *   
3851
     *   An actor can have more than one sight-like sense, in which case
3852
     *   the senses will act effectively as one sense that can reach the
3853
     *   union of objects reachable through the individual senses.  
3854
     */
3855
    sightlikeSenses = [sight]
3856
3857
    /*
3858
     *   Build a list of all of the objects of which an actor is aware.
3859
     *   
3860
     *   An actor is aware of an object if the object is within reach of
3861
     *   the actor's senses, and has some sort of presence in that sense.
3862
     *   Note that both of these conditions must be true for at least one
3863
     *   sense possessed by the actor; an object that is within earshot,
3864
     *   but not within reach of any other sense, is in scope only if the
3865
     *   object is making some kind of noise.  
3866
     */
3867
    scopeList()
3868
    {
3869
        local lst;
3870
3871
        /* we have nothing in our master list yet */
3872
        lst = [];
3873
3874
        /* iterate over each sense */
3875
        foreach (local sense in scopeSenses)
3876
        {
3877
            /* 
3878
             *   get everything reachable to this sense, and add the
3879
             *   unique elements into the result list 
3880
             */
3881
            lst = lst.appendUnique(senseScopeList(sense));
3882
        }
3883
3884
        /* return the result */
3885
        return lst;
3886
    }
3887
3888
    /*
3889
     *   Build a list of all of the objects of which the actor is aware
3890
     *   through the sight-like senses.  
3891
     */
3892
    visibleList()
3893
    {
3894
        local lst;
3895
3896
        /* we have nothing in our master list yet */
3897
        lst = [];
3898
3899
        /* iterate over each sense */
3900
        foreach (local sense in sightlikeSenses)
3901
        {
3902
            local cur;
3903
            
3904
            /* get information for all objects for the current sense */
3905
            cur = senseList(sense);
3906
3907
            /* add the unique elements */
3908
            lst = lst.appendUnique(cur);
3909
        }
3910
3911
        /* return the result */
3912
        return lst;
3913
    }
3914
3915
    /*
3916
     *   Build a list of full sight-like sensory information for all of
3917
     *   the objects visible to the actor through the actor's sight-like
3918
     *   senses.  Returns a list with the same set of information as
3919
     *   senseInfoList().  
3920
     */
3921
    visibleInfoList()
3922
    {
3923
        local lst;
3924
3925
        /* we have nothing in our master list yet */
3926
        lst = [];
3927
3928
        /* iterate over each sense */
3929
        foreach (local sense in sightlikeSenses)
3930
        {
3931
            local cur;
3932
            
3933
            /* get information for all objects for the current sense */
3934
            cur = senseInfoList(sense);
3935
3936
            /* merge the two lists */
3937
            lst = mergeSenseInfoList(cur, lst);
3938
        }
3939
3940
        /* return the result */
3941
        return lst;
3942
    }
3943
3944
3945
    /*
3946
     *   Build a list of all of the objects that are in scope for this
3947
     *   actor through the given sense.  
3948
     */
3949
    senseScopeList(sense)
3950
    {
3951
        local lst;
3952
3953
        /* we don't have any objects in our list yet */
3954
        lst = new Vector(32);
3955
        
3956
        /*
3957
         *   Iterate over everything reachable through the sense, and add
3958
         *   each reachable object to the list 
3959
         */
3960
        senseIter(sense, new function(cur, trans, obstructor, ambient) {
3961
            /* 
3962
             *   include the object only if it has a presence in this
3963
             *   sense - if it doesn't, the actor is not made aware of the
3964
             *   object by this sense, because even though it is within
3965
             *   reach of the sense, the object would not be detectable by
3966
             *   this sense alone no matter what its relation to the actor 
3967
             */
3968
            if (cur.(sense.presenceProp))
3969
            {
3970
                /* add the object to the list so far */
3971
                lst += cur;
3972
            }
3973
3974
            /* tell the caller to proceed with the iteration */
3975
            return true;
3976
        });
3977
3978
        /* return the list we built */
3979
        return lst.toList();
3980
    }
3981
3982
    /*
3983
     *   Show what the actor is carrying.
3984
     *   
3985
     *   Note that this method must be overridden if the actor does not
3986
     *   use a conventional 'contents' list property to store its full set
3987
     *   of contents.  
3988
     */
3989
    showInventory(tall)
3990
    {
3991
        local infoList;
3992
3993
        /* 
3994
         *   Get the list of objects visible to the actor through the
3995
         *   actor's sight-like senses, along with the sense information
3996
         *   for each object.  We'll only worry about this for the
3997
         *   contents of the items being carried; we'll assume that the
3998
         *   actor has at some point taken note of each item being
3999
         *   directly carried (by virtue of having picked it up at some
4000
         *   point in the past) and can still identify each by touch, even
4001
         *   if it's too dark to see it.  
4002
         */
4003
        infoList = visibleInfoList();
4004
4005
        /* list in the appropriate mode ("wide" or "tall") */
4006
        if (tall)
4007
        {
4008
            /* 
4009
             *   show the list of items being carried, recursively
4010
             *   displaying the contents; show items being worn in-line
4011
             *   with the regular listing 
4012
             */
4013
            showList(self, self, inventoryLister, contents,
4014
                     LIST_TALL | LIST_RECURSE | LIST_INVENT,
4015
                     0, infoList);
4016
        }
4017
        else
4018
        {
4019
            local carrying;
4020
            local wearing;
4021
4022
            /* 
4023
             *   paragraph style ("wide") inventory - go through the
4024
             *   direct contents, and separate the list into what's being
4025
             *   carried and what's being worn 
4026
             */
4027
            carrying = new Vector(32);
4028
            wearing = new Vector(32);
4029
            foreach (local cur in contents)
4030
            {
4031
                if (cur.isWornBy(self))
4032
                    wearing += cur;
4033
                else
4034
                    carrying += cur;
4035
            }
4036
4037
            /* convert the carrying and wearing vectors to lists */
4038
            carrying = carrying.toList();
4039
            wearing = wearing.toList();
4040
            
4041
            /* show the list of items being carried */
4042
            showList(self, self, inventoryLister, carrying, LIST_INVENT,
4043
                     0, infoList);
4044
4045
            /* show the visible contents of the items being carried */
4046
            showContentsList(self, carrying, LIST_INVENT, 0, infoList);
4047
            
4048
            /* show the list of items being worn */
4049
            if (wearing.length() != 0)
4050
            {
4051
                /* show the items being worn */
4052
                showList(self, self, wearingLister, wearing, LIST_WORN, 0,
4053
                         infoList);
4054
4055
                /* show the contents of the items being worn */
4056
                showContentsList(self, wearing, LIST_WORN, 0, infoList);
4057
            }
4058
        }
4059
    }
4060
4061
    /*
4062
     *   The ShowListInterface object that we use for inventory listings 
4063
     */
4064
    inventoryLister
4065
    {
4066
        /* 
4067
         *   if I'm the player character, use the player character lister;
4068
         *   otherwise use the non-player lister 
4069
         */
4070
        if (libGlobal.playerChar == self)
4071
            return libMessages.playerInventoryLister;
4072
        else
4073
            return self.myInventoryLister;
4074
    }
4075
4076
    /*
4077
     *   The ShowListInterface object that we use for inventory listings
4078
     *   of items being worn 
4079
     */
4080
    wearingLister
4081
    {
4082
        if (libGlobal.playerChar == self)
4083
            return libMessages.playerWearingLister;
4084
        else
4085
            return self.myWearingLister;
4086
    }
4087
4088
    /* 
4089
     *   My inventory lister item.  During pre-initialization, the library
4090
     *   will automatically create an instance of NonPlayerInventoryLister
4091
     *   for each actor that doesn't define a non-nil value for this
4092
     *   property.  Note that we never use this for the current player
4093
     *   character, but rather use libMessages.playerInventoryLister.  
4094
     */
4095
    myInventoryLister = nil
4096
4097
    /* my inventory lister for items being worn */
4098
    myWearingLister = nil
4099
4100
    /*
4101
     *   Perform library pre-initialization on the actor 
4102
     */
4103
    initializeActor() { }
4104
;
4105
4106
/* ------------------------------------------------------------------------ */
4107
/*
4108
 *   Set the current player character 
4109
 */
4110
setPlayer(actor)
4111
{
4112
    /* remember the new player character */
4113
    libGlobal.playerChar = actor;
4114
4115
    /* set the root global point of view to this actor */
4116
    setRootPOV(actor);
4117
}
4118