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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved.
5
 *   
6
 *   TADS 3 Library - actors
7
 *   
8
 *   This module provides definitions related to actors, which represent
9
 *   characters in the game.  
10
 */
11
12
/* include the library header */
13
#include "adv3.h"
14
15
16
/* ------------------------------------------------------------------------ */
17
/*
18
 *   Implied command modes 
19
 */
20
enum ModePlayer, ModeNPC;
21
22
23
/* ------------------------------------------------------------------------ */
24
/*
25
 *   A Topic is an object representing some piece of knowledge in the
26
 *   story.  Actors can use Topic objects in commands such as "ask" and
27
 *   "tell".
28
 *   
29
 *   A physical simulation object can be a Topic through multiple
30
 *   inheritance.  In addition, a game can define Topic objects for
31
 *   abstract conversation topics that don't correspond to simulation
32
 *   objects; for example, a topic could be created for "the meaning of
33
 *   life" to allow a command such as "ask guru about meaning of life."
34
 *   
35
 *   The key distinction between Topic objects and regular objects is that
36
 *   a Topic can represent an abstract, non-physical concept that isn't
37
 *   connected to any "physical" object in the simulation.  
38
 */
39
class Topic: VocabObject
40
    /*
41
     *   Is the topic known?  If this is true, the topic is in scope for
42
     *   actions that operate on topics, such as "ask about" and "tell
43
     *   about."  If this is nil, the topic isn't known.  
44
     *   
45
     *   By default, we mark all topics as known to begin with, which
46
     *   allows discussion of any topic at any time.  Some authors prefer
47
     *   to keep track of which topics the player character actually has
48
     *   reason to know about within the context of the game, making topics
49
     *   available for conversation only after they become known for some
50
     *   good reason, such as another character mentioning them in
51
     *   conversation.
52
     *   
53
     *   Note that, as with Thing.isKnown, this is only the DEFAULT 'known'
54
     *   property.  Each actor can have its own separate 'known' property
55
     *   by defining the actor's 'knownProp' to a different property name.
56
     */
57
    isKnown = true
58
59
    /* 
60
     *   Topics are abstract objects, so they can't be sensed with any of
61
     *   the physical senses, even if they're ever included as part of a
62
     *   containment hierarchy (which might be convenient in some cases
63
     *   for purposes of associating a topic with a physical object, for
64
     *   example).
65
     */
66
    canBeSensed(sense, trans, ambient) { return nil; }
67
68
    /* a topic cannot by default be used to resolve a possessive phrase */
69
    canResolvePossessive = nil
70
;
71
72
73
/* ------------------------------------------------------------------------ */
74
/*
75
 *   FollowInfo - this is an object that tracks an actor's knowledge of
76
 *   the objects that the actor can follow, which are objects that actor
77
 *   has witnessed leaving the current location.  We keep track of each
78
 *   followable object and the direction we saw it depart.  
79
 */
80
class FollowInfo: object
81
    /* the object we can follow */
82
    obj = nil
83
84
    /* the TravelConnector the object traversed to leave */
85
    connector = nil
86
87
    /* 
88
     *   The source location - this is the location we saw the object
89
     *   depart.  We keep track of this because an actor can follow an
90
     *   object only if the actor is starting from the same location where
91
     *   the actor saw the object depart.  
92
     */
93
    sourceLocation = nil
94
;
95
96
97
/* ------------------------------------------------------------------------ */
98
/*
99
 *   Postures.  A posture describes how an actor is internally positioned:
100
 *   standing, lying, sitting.  We represent postures with objects of
101
 *   class Posture to make it easier to add new game-specific postures.  
102
 */
103
class Posture: object
104
    /* 
105
     *   Try getting the current actor into this posture within the given
106
     *   location, by running an appropriate implied command.  
107
     */
108
    tryMakingPosture(loc) { }
109
110
    /* put the actor into our posture via a nested action */
111
    setActorToPosture(actor, loc) { }
112
;
113
114
/*
115
 *   Standing posture - this is the default posture, which an actor
116
 *   normally uses for travel.  Actors are generally in this posture any
117
 *   time they are not sitting on something, lying on something, or
118
 *   similar. 
119
 */
120
standing: Posture
121
    tryMakingPosture(loc) { return tryImplicitAction(StandOn, loc); }
122
    setActorToPosture(actor, loc) { nestedActorAction(actor, StandOn, loc); }
123
;
124
125
/*
126
 *   Sitting posture. 
127
 */
128
sitting: Posture
129
    tryMakingPosture(loc) { return tryImplicitAction(SitOn, loc); }
130
    setActorToPosture(actor, loc) { nestedActorAction(actor, SitOn, loc); }
131
;
132
133
/*
134
 *   Lying posture. 
135
 */
136
lying: Posture
137
    tryMakingPosture(loc) { return tryImplicitAction(LieOn, loc); }
138
    setActorToPosture(actor, loc) { nestedActorAction(actor, LieOn, loc); }
139
;
140
141
142
/* ------------------------------------------------------------------------ */
143
/*
144
 *   Conversation manager output filter.  We look for special tags in the
145
 *   output stream:
146
 *   
147
 *   <.reveal key> - add 'key' to the knowledge token lookup table.  The
148
 *   'key' is an arbitrary string, which we can look up in the table to
149
 *   determine if the key has even been revealed.  This can be used to make
150
 *   a response conditional on another response having been displayed,
151
 *   because the key will only be added to the table when the text
152
 *   containing the <.reveal key> sequence is displayed.
153
 *   
154
 *   <.convnode name> - switch the current responding actor to conversation
155
 *   node 'name'.  
156
 *   
157
 *   <.convstay> - keep the responding actor in the same conversation node
158
 *   as it was in at the start of the current response
159
 *   
160
 *   <.topics> - schedule a topic inventory for the end of the turn (just
161
 *   before the next command prompt) 
162
 */
163
conversationManager: OutputFilter, PreinitObject
164
    /*
165
     *   Custom extended tags.  Games and library extensions can add their
166
     *   own tag processing as needed, by using 'modify' to extend this
167
     *   object.  There are two things you have to do to add your own tags:
168
     *   
169
     *   First, add a 'customTags' property that defines a regular
170
     *   expression for your added tags.  This will be incorporated into
171
     *   the main pattern we use to look for tags.  Simply specify a
172
     *   string that lists your tags separated by "|" characters, like
173
     *   this:
174
     *   
175
     *   customTags = 'foo|bar'
176
     *   
177
     *   Second, define a doCustomTag() method to process the tags.  The
178
     *   filter routine will call your doCustomTag() method whenever it
179
     *   finds one of your custom tags in the output stream.  
180
     */
181
    customTags = nil
182
    doCustomTag(tag, arg) { /* do nothing by default */ }
183
184
    /* filter text written to the output stream */
185
    filterText(ostr, txt)
186
    {
187
        local start;
188
        
189
        /* scan for our special tags */
190
        for (start = 1 ; ; )
191
        {
192
            local match;
193
            local arg;
194
            local actor;
195
            local sp;
196
            local tag;
197
            local nxtOfs;
198
            
199
            /* scan for the next tag */
200
            match = rexSearch(tagPat, txt, start);
201
202
            /* if we didn't find it, we're done */
203
            if (match == nil)
204
                break;
205
206
            /* note the next offset */
207
            nxtOfs = match[1] + match[2];
208
209
            /* get the argument (the third group from the match) */
210
            arg = rexGroup(3);
211
            if (arg != nil)
212
                arg = arg[3];
213
214
            /* pick out the tag */
215
            tag = rexGroup(1)[3].toLower();
216
217
            /* check which tag we have */
218
            switch (tag)
219
            {
220
            case 'reveal':
221
                /* reveal the key by adding it to our database */
222
                setRevealed(arg);
223
                break;
224
225
            case 'convbegin':
226
                /* 
227
                 *   Internal tag - starting a conversational response for
228
                 *   an actor, identified by an index in our idToActor
229
                 *   vector.  Get the actor.  
230
                 */
231
                actor = idToActor[toInteger(arg)];
232
233
                /* 
234
                 *   since we're just starting a response, clear the flag
235
                 *   in the actor indicating that a ConvNode has been set
236
                 *   in the course of this response 
237
                 */
238
                actor.responseSetConvNode = nil;
239
240
                /* remember the new responding actor */
241
                respondingActor = actor;
242
243
                /* done */
244
                break;
245
246
            case 'convend':
247
                /*
248
                 *   Ending a conversational response for a given actor,
249
                 *   identified by the first argument, which is an index in
250
                 *   our idToActor vector. 
251
                 */
252
                sp = arg.find(' ');
253
                actor = idToActor[toInteger(arg.substr(1, sp - 1))];
254
255
                /* the rest of the argument is the default new ConvNode */
256
                arg = arg.substr(sp + 1);
257
258
                /* if the new ConvNode is empty, it means no ConvNode */
259
                if (arg == '')
260
                    arg = nil;
261
262
                /* 
263
                 *   if we didn't explicitly set a new ConvNode in the
264
                 *   course of this response, apply the default 
265
                 */
266
                if (!actor.responseSetConvNode)
267
                    actor.setConvNodeReason(arg, 'convend');
268
269
                /*
270
                 *   Since we've just finished showing a message that
271
                 *   specifically refers to this actor, the player should
272
                 *   be able to refer to this actor using a pronoun on the
273
                 *   next command.  Set the responding actor as the
274
                 *   antecedent for the appropriate singular pronouns for
275
                 *   the player character.  Note that we do this at the end
276
                 *   of the response, so that the antecedent is the last
277
                 *   one if we have more than one.  
278
                 */
279
                gPlayerChar.setPronounObj(actor);
280
281
                /* done */
282
                break;
283
284
            case 'convnode':
285
                /* 
286
                 *   If there's a current responding actor, set its current
287
                 *   conversation node.
288
                 */
289
                if (respondingActor != nil)
290
                {
291
                    /* 
292
                     *   Set the new node.  While we're working, capture
293
                     *   any output that occurs so that we can insert it
294
                     *   into the output stream just after the <.convnode>
295
                     *   tag, so that any text displayed within the
296
                     *   ConvNode's activation method (noteActive) is
297
                     *   displayed in the proper order.  
298
                     */
299
                    local ctxt = mainOutputStream.captureOutput(
300
                        {: respondingActor.setConvNodeReason(arg, 'convnode') });
301
302
                    /* re-insert any text we captured */
303
                    txt = txt.substr(1, nxtOfs - 1)
304
                        + ctxt
305
                        + txt.substr(nxtOfs);
306
                }
307
                break;
308
309
            case 'convstay':
310
                /* 
311
                 *   leave the responding actor in the old conversation
312
                 *   node - we don't need to change the ConvNode, but we do
313
                 *   need to note that we've explicitly set it 
314
                 */
315
                if (respondingActor != nil)
316
                    respondingActor.responseSetConvNode = true;
317
                break;
318
319
            case 'topics':
320
                /* schedule a topic inventory listing */
321
                scheduleTopicInventory();
322
                break;
323
324
            default:
325
                /* check for an extended tag */
326
                doCustomTag(tag, arg);
327
                break;
328
            }
329
330
            /* continue the search after this match */
331
            start = nxtOfs;
332
        }
333
334
        /* 
335
         *   remove the tags from the text by replacing every occurrence
336
         *   with an empty string, and return the result 
337
         */
338
        return rexReplace(tagPat, txt, '', ReplaceAll);
339
    }
340
341
    /* regular expression pattern for our tags */
342
    tagPat = static new RexPattern(
343
        '<nocase><langle><dot>'
344
        + '(reveal|convbegin|convend|convnode|convstay|topics'
345
        + (customTags != nil ? '|' + customTags : '')
346
        + ')'
347
        + '(<space>+(<^rangle>+))?'
348
        + '<rangle>')
349
350
    /*
351
     *   Schedule a topic inventory request.  Game code can call this at
352
     *   any time to request that the player character's topic inventory
353
     *   be shown automatically just before the next command prompt.  In
354
     *   most cases, game code won't call this directly, but will request
355
     *   the same effect using the <.topics> tag in topic response text.  
356
     */
357
    scheduleTopicInventory()
358
    {
359
        /* note that we have a request for a prompt-time topic inventory */
360
        pendingTopicInventory = true;
361
    }
362
363
    /*
364
     *   Show or schedule a topic inventory request.  If the current
365
     *   action has a non-default command report, schedule it; otherwise,
366
     *   show it now.
367
     *   
368
     *   If there's a non-default report, don't suggest the topics now;
369
     *   instead, schedule a topic inventory for the end of the turn.
370
     *   When we have a non-default report, the report could change the
371
     *   ConvNode for the actor, so we don't want to show the topic
372
     *   inventory until we've had a chance to process all of the reports.
373
     */
374
    showOrScheduleTopicInventory(actor, otherActor)
375
    {
376
        /* check for a non-default command report in the current action */
377
        if (gTranscript.currentActionHasReport(
378
            {x: x.ofKind(MainCommandReport)}))
379
        {
380
            /* we have a non-default report - defer the topic inventory */
381
            scheduleTopicInventory();
382
        }
383
        else
384
        {
385
            /* we have only a default report, so show the inventory now */
386
            actor.suggestTopicsFor(otherActor, nil);
387
        }
388
    }
389
390
    /*
391
     *   Note that an actor is about to give a response through a
392
     *   TopicEntry object.  We'll remember the actor so that we'll know
393
     *   which actor is involved in a <.convnode> operation.  
394
     */
395
    beginResponse(actor)
396
    {
397
        /* if the actor doesn't have an ID yet, assign one */
398
        if (actor.convMgrID == nil)
399
        {
400
            /* add the actor to our vector of actors */
401
            idToActor.append(actor);
402
403
            /* the ID is simply the index in this vector */
404
            actor.convMgrID = idToActor.length();
405
        }
406
407
        /* output a <.convbegin> for the actor */
408
        gTranscript.addReport(new ConvBeginReport(actor.convMgrID));
409
    }
410
411
    /* 
412
     *   Finish the response - call this after we finish handling the
413
     *   response.  There must be a subsequent matching call to this
414
     *   routine whenever beginResponse() is called.
415
     *   
416
     *   'node' is the default new ConvNode the actor for the responding
417
     *   actor.  If another ConvNode was explicitly set in the course of
418
     *   handling the response, this is ignored, since the explicit
419
     *   setting overrides this default.  
420
     */
421
    finishResponse(actor, node)
422
    {
423
        local prv;
424
        local oldNode;
425
        
426
        /* if the node is a ConvNode object, use its name */
427
        if (node != nil && node.ofKind(ConvNode))
428
            node = node.name;
429
430
        /* 
431
         *   if the previous report was our ConvBeginReport, the
432
         *   conversation display was empty, so ignore the whole thing 
433
         */
434
        if ((prv = gTranscript.getLastReport()) != nil
435
            && prv.ofKind(ConvBeginReport)
436
            && prv.actorID == actor.convMgrID)
437
        {
438
            /* remove the <.convbegin> report - we're canceling it out */
439
            gTranscript.deleteLastReport();
440
441
            /* we're done - do not generate the <.convend> */
442
            return;
443
        }
444
445
        /* 
446
         *   if the actor has a current ConvNode, and our default next
447
         *   node is nil, and the current node is marked as "sticky," stay
448
         *   in the current node rather than switching to a nil default 
449
         */
450
        if (node == nil
451
            && (oldNode = actor.curConvNode) != nil
452
            && oldNode.isSticky)
453
        {
454
            /* it's sticky, so stay at this node */
455
            node = oldNode.name;
456
        }
457
458
        /* output a <.convend> for the actor */
459
        gTranscript.addReport(new ConvEndReport(actor.convMgrID, node));
460
    }
461
462
    /* 
463
     *   The current responding actor.  Actors should set this when they're
464
     *   about to show a response to an ASK, TELL, etc. 
465
     */
466
    respondingActor = nil
467
468
    /*
469
     *   Mark a tag as revealed.  This adds an entry for the tag to the
470
     *   revealedNameTab table.  We simply set the table entry to 'true';
471
     *   the presence of the tag in the table constitutes the indication
472
     *   that the tag has been revealed.
473
     *   
474
     *   (Games and library extensions can use 'modify' to override this
475
     *   and store more information in the table entry.  For example, you
476
     *   could store the time when the information was first revealed, or
477
     *   the location where it was learned.  If you do override this, just
478
     *   be sure to set the revealedNameTab entry for the tag to a non-nil
479
     *   and non-zero value, so that any code testing the presence of the
480
     *   table entry will see that the slot is indeed set.)  
481
     */
482
    setRevealed(tag)
483
    {
484
        revealedNameTab[tag] = true;
485
    }
486
487
    /* 
488
     *   The global lookup table of all revealed keys.  This table is keyed
489
     *   by the string naming the revelation; the value associated with
490
     *   each key is not used (we always just set it to true).  
491
     */
492
    revealedNameTab = static new LookupTable(32, 32)
493
494
    /* a vector of actors, indexed by their convMgrID values */
495
    idToActor = static new Vector(32)
496
497
    /* preinitialize */
498
    execute()
499
    {
500
        /* add every ConvNode object to our master table */
501
        forEachInstance(ConvNode,
502
                        { obj: obj.getActor().convNodeTab[obj.name] = obj });
503
504
        /* 
505
         *   set up the prompt daemon that makes automatic topic inventory
506
         *   suggestions when appropriate 
507
         */
508
        new PromptDaemon(self, &topicInventoryDaemon);
509
    }
510
511
    /*
512
     *   Prompt daemon: show topic inventory when appropriate.  When a
513
     *   response explicitly asks us to show a topic inventory using the
514
     *   <.topics> tag, or when other game code asks us to show topic
515
     *   inventory by calling scheduleTopicInventory(), we'll show the
516
     *   inventory just before the command input prompt.  
517
     */
518
    topicInventoryDaemon()
519
    {
520
        /* if we have a topic inventory scheduled, show it now */
521
        if (pendingTopicInventory)
522
        {
523
            /* 
524
             *   Show the player character's topic inventory.  This is not
525
             *   an explicit inventory request, since the player didn't ask
526
             *   for it.  
527
             */
528
            gPlayerChar.suggestTopics(nil);
529
530
            /* we no longer have a pending inventory request */
531
            pendingTopicInventory = nil;
532
        }
533
    }
534
535
    /* flag: we have a pending prompt-time topic inventory request */
536
    pendingTopicInventory = nil
537
;
538
539
/* ------------------------------------------------------------------------ */
540
/*
541
 *   A plug-in topic database.  The topic database is a set of TopicEntry
542
 *   objects that specify the responses to queries on particular topics.
543
 *   The exact nature of the queries that a particular topic database
544
 *   handles is up to the database subclass to define; we just provide the
545
 *   abstract mechanism for finding and displaying responses.
546
 *   
547
 *   This is a "plug-in" database in that it's meant to be added into other
548
 *   classes using multiple inheritance.  This isn't meant to be used as a
549
 *   stand-alone abstract topic entry container.  
550
 */
551
class TopicDatabase: object
552
    /* 
553
     *   Is the topic group active?  A TopicEntry always checks with its
554
     *   container to see if the children of the container are active.  By
555
     *   default, everything in the database is active.  
556
     */
557
    topicGroupActive = true
558
559
    /*
560
     *   Get the score adjustment for all topic entries contained within.
561
     *   The default adjustment is zero; TopicGroup objects can use this to
562
     *   adjust the score for their nested entries.  
563
     */
564
    topicGroupScoreAdjustment = 0
565
566
    /*
567
     *   Handle a topic.  Look up the topic in our topic list for the
568
     *   given conversational action type.  If we find a match, we'll
569
     *   invoke the matching topic list entry to handle it.  We'll return
570
     *   true if we find a match, nil if not.  
571
     */
572
    handleTopic(fromActor, topic, convType, path)
573
    {
574
        local resp;
575
576
        /* find the best response */
577
        resp = findTopicResponse(fromActor, topic, convType, path);
578
        
579
        /* if we found a match, let it handle the topic */
580
        if (resp != nil)
581
        {
582
            /* show the response */
583
            showTopicResponse(fromActor, topic, resp);
584
            
585
            /* tell the caller we handled it */
586
            return true;
587
        }
588
        else
589
        {
590
            /* tell the caller we didn't handle it */
591
            return nil;
592
        }
593
    }
594
595
    /* show the response we found for a topic */
596
    showTopicResponse(fromActor, topic, resp)
597
    {
598
        /* let the response object handle it */
599
        resp.handleTopic(fromActor, topic);
600
    }
601
602
    /* find the best response (a TopicEntry object) for the given topic */
603
    findTopicResponse(fromActor, topic, convType, path)
604
    {
605
        local topicList;
606
        local best, bestScore;
607
608
        /* 
609
         *   Get the list of possible topics for this conversation type.
610
         *   The topic list is contained in one of our properties; exactly
611
         *   which property is determined by the conversation type. 
612
         */
613
        topicList = self.(convType.topicListProp);
614
        
615
        /* if the topic list is nil, we obviously won't find the topic */
616
        if (topicList == nil)
617
            return nil;
618
619
        /* scan our topic list for the best match */
620
        best = nil;
621
        foreach (local cur in topicList)
622
        {
623
            /* get this item's score */
624
            local score = cur.adjustScore(cur.matchTopic(fromActor, topic));
625
626
            /* 
627
             *   If this item has a score at all, and the topic entry is
628
             *   marked as active, and it's best (or only) score so far,
629
             *   note it.  Ignore topics marked as not active, since
630
             *   they're in the topic database only provisionally.  
631
             */
632
            if (score != nil
633
                && cur.checkIsActive()
634
                && (best == nil || score > bestScore))
635
            {
636
                best = cur;
637
                bestScore = score;
638
            }
639
        }
640
641
        /*
642
         *   If there's a hierarchical search path, AND this topic entry
643
         *   defines a deferToEntry() method, look for matches in the
644
         *   inferior databases on the path and check to see if we want to
645
         *   defer to one of them.  
646
         */
647
        if (best != nil && path != nil && best.propDefined(&deferToEntry))
648
        {
649
            /* look for a match in each inferior database */
650
            for (local i = 1, local len = path.length() ; i <= len ; ++i)
651
            {
652
                local inf;
653
                
654
                /* 
655
                 *   Look up an entry in this inferior database.  Pass in
656
                 *   the remainder of the path, so that the inferior
657
                 *   database can consider further deferral to its own
658
                 *   inferior databases.  
659
                 */
660
                inf = path[i].findTopicResponse(fromActor, topic, convType,
661
                                                path.sublist(i + 1));
662
663
                /* 
664
                 *   if we found an entry in this inferior database, and
665
                 *   our entry defers to the inferior entry, then ignore
666
                 *   the match in our own database 
667
                 */
668
                if (inf != nil && best.deferToEntry(inf))
669
                    return nil;
670
            }
671
        }
672
673
        /* return the best matching response object, if any */
674
        return best;
675
    }
676
677
    /* show our suggested topic list */
678
    showSuggestedTopicList(lst, asker, askee, explicit)
679
    {
680
        /* get the asking actor's scope list for use later */
681
        scopeList = asker.scopeList();
682
        
683
        /* remove items that have redundant list groups and full names */
684
        for (local i = 1, local len = lst.length() ; i <= len ; ++i)
685
        {
686
            local a = lst[i];
687
            
688
            /* check for redundant elements */
689
            for (local j = i + 1 ; j <= len ; ++j)
690
            {
691
                local b = lst[j];
692
                
693
                /* 
694
                 *   If item 'a' matches item 'b', and both are active,
695
                 *   remove item 'b'.  We only need to remove redundant
696
                 items if they're both active, since inactive items
697
                 */
698
                if (a.suggestionGroup == b.suggestionGroup
699
                    && a.fullName == b.fullName
700
                    && a.isSuggestionActive(asker, scopeList)
701
                    && b.isSuggestionActive(asker, scopeList))
702
                {
703
                    /* delete item 'b' from the list */
704
                    lst.removeElementAt(j);
705
706
                    /* adjust our indices for the deletion */
707
                    --j;
708
                    --len;
709
                }
710
            }
711
        }
712
713
        /* show our list */
714
        new SuggestedTopicLister(asker, askee, explicit)
715
            .showList(asker, nil, lst, 0, 0, nil, nil);
716
    }
717
718
    /*
719
     *   Flag: this database level should limit topic suggestions (for the
720
     *   TOPICS and TALK TO commands) to its own topics, excluding any
721
     *   topics inherited from the "broader" context.  If this property is
722
     *   set to true, then we won't include suggestions from any lower
723
     *   level of the database hierarchy.  If this property is nil, we'll
724
     *   also include any topic suggestions from the broader context.
725
     *   
726
     *   Topic databases are arranged into a fixed hierarchy for an actor.
727
     *   At the top level is the current ConvNode object; at the next level
728
     *   is the ActorState; and at the bottom level is the Actor itself.
729
     *   So, if the ConvNode's limitSuggestions property is set to true,
730
     *   then the suggestions for the actor will include ONLY the ConvNode.
731
     *   If the ConvNode has the property set to nil, but the ActorState
732
     *   has it set to true, then we'll include the ConvNode and the
733
     *   ActorState suggestions.
734
     *   
735
     *   By default, we set this to nil.  This should usually be set to
736
     *   true for any ConvNode or ActorState where the NPC won't allow the
737
     *   player to stray from the subject.  For example, if a ConvNode only
738
     *   accepts a YES or NO response to a question, then this property
739
     *   should probably be set to true in the ConvNode, since other
740
     *   suggested topics won't be accepted as conversation topics as long
741
     *   as the ConvNode is active.  
742
     */
743
    limitSuggestions = nil
744
745
    /*
746
     *   Add a topic to our topic database.  We'll add it to the
747
     *   appropriate list or lists as indicated in the topic itself.
748
     *   'topic' is a TopicEntry object.  
749
     */
750
    addTopic(topic)
751
    {
752
        /* add the topic to each list indicated in the topic */
753
        foreach (local cur in topic.includeInList)
754
            addTopicToList(topic, cur);
755
    }
756
757
    /* remove a topic from our topic database */
758
    removeTopic(topic)
759
    {
760
        /* remove the topic from each of its lists */
761
        foreach (local cur in topic.includeInList)
762
            removeTopicFromList(topic, cur);
763
    }
764
765
    /* add a suggested topic */
766
    addSuggestedTopic(topic)
767
    {
768
        /* add the topic to our suggestion list */
769
        addTopicToList(topic, &suggestedTopics);
770
    }
771
772
    /* remove a suggested topic */
773
    removeSuggestedTopic(topic)
774
    {
775
        /* add the topic to our suggestion list */
776
        removeTopicFromList(topic, &suggestedTopics);
777
    }
778
779
    /*
780
     *   Add a topic to the given topic list.  The topic list is given as a
781
     *   property point; for example, we'd specify &askTopics to add the
782
     *   topic to our ASK list. 
783
     */
784
    addTopicToList(topic, listProp)
785
    {
786
        /* if we haven't created this topic list vector yet, create it now */
787
        if (self.(listProp) == nil)
788
            self.(listProp) = new Vector(8);
789
790
        /* add the topic */
791
        self.(listProp).append(topic);
792
    }
793
794
    /* remove a topic from the given topic list */
795
    removeTopicFromList(topic, listProp)
796
    {
797
        /* if the list exists, remove the topic from it */
798
        if (self.(listProp) != nil)
799
            self.(listProp).removeElement(topic);
800
    }
801
802
    /*
803
     *   Our list of suggested topics.  These are SuggestedTopic objects
804
     *   that describe things that another actor wants to ask or tell this
805
     *   actor about.  
806
     */
807
    suggestedTopics = nil
808
809
    /*
810
     *   Get the "owner" of the topics in this database.  The meaning of
811
     *   "owner" varies according to the topic database type; for actor
812
     *   topic databases, for example, this is the actor.  Generally, the
813
     *   owner is the object being queried about the topic, from the
814
     *   player's perspective.  Each type of database should define this
815
     *   method to return the appropriate object.  
816
     */
817
    getTopicOwner() { return nil; }
818
;
819
820
/*
821
 *   A TopicDatabase for an Actor.  This is used not only directly for an
822
 *   Actor but also for an actor's sub-databases, in ActorState and
823
 *   ConvNode.
824
 *   
825
 *   Actor topic databases field queries for the various types of
826
 *   topic-based interactions an actor can participate in: ASK, TELL, SHOW,
827
 *   GIVE, and so on.
828
 *   
829
 *   Each actor has its own topic database, which means each actor can have
830
 *   its own set of responses.  Actor states can also have their own
831
 *   separate topic databases; this makes it easy to make an actor's
832
 *   response to a particular question vary according to the actor's state.
833
 *   Conversation nodes can also have their own separate databases, which
834
 *   allows for things like threaded conversations.
835
 */
836
class ActorTopicDatabase: TopicDatabase
837
    /*
838
     *   Initiate conversation on the given simulation object.  If we can
839
     *   find an InitiateTopic matching the given object, we'll show its
840
     *   topic response and return true; if we can't find a topic to
841
     *   initiate, we'll simply return nil.  
842
     */
843
    initiateTopic(obj)
844
    {
845
        /* find an initiate topic for the given object */
846
        if (handleTopic(gPlayerChar, obj, initiateConvType, nil))
847
        {
848
            /* 
849
             *   we handled the topic, so note that we're in conversation
850
             *   with the player character now 
851
             */
852
            getTopicOwner().noteConversation(gPlayerChar);
853
854
            /* indicate that we found a topic to initiate */
855
            return true;
856
        }
857
858
        /* we didn't find a topic to initiate */
859
        return nil;
860
    }
861
862
    /* show a topic response */
863
    showTopicResponse(fromActor, topic, resp)
864
    {
865
        local actor = getTopicOwner();
866
        local newNode;
867
868
        /* 
869
         *   note whether the response is conversational - we need to do
870
         *   this ahead of time, since invoking the response can sometimes
871
         *   have the side effect of changing the response's status
872
         */
873
        local isConv = resp.isConversational;
874
        
875
        /* tell the conversation manager we're starting a response */
876
        conversationManager.beginResponse(actor);
877
            
878
        /* let the response object handle it */
879
        resp.handleTopic(fromActor, topic);
880
881
        /* 
882
         *   By default, after showing a response, we want to leave the
883
         *   conversation node tree entirely if we didn't explicitly set
884
         *   the next node in the course of the response.  So, set the
885
         *   default new node to 'nil'.  However, if the topic is
886
         *   non-conversational, it shouldn't affect the conversation
887
         *   thread at all, so leave the current node unchanged.  
888
         */
889
        if (isConv)
890
            newNode = nil;
891
        else
892
            newNode = actor.curConvNode;
893
894
        /* tell the conversation manager we're done with the response */
895
        conversationManager.finishResponse(actor, newNode);
896
    }
897
898
    /* 
899
     *   Our 'ask about', 'ask for', 'tell about', 'give', 'show',
900
     *   miscellaneous, command, and self-initiated topic databases - these
901
     *   are vectors we initialize as needed.  Since every actor and every
902
     *   actor state has its own separate topic database, it's likely that
903
     *   the bulk of these databases will be empty, so we don't bother even
904
     *   creating a vector for a topic list until the first topic is added.
905
     *   This means we have to be able to cope with these being nil
906
     *   anywhere we use them.  
907
     */
908
    askTopics = nil
909
    askForTopics = nil
910
    tellTopics = nil
911
    showTopics = nil
912
    giveTopics = nil
913
    miscTopics = nil
914
    commandTopics = nil
915
    initiateTopics = nil
916
917
    /* our special command database */
918
    specialTopics = nil
919
;
920
921
/* ------------------------------------------------------------------------ */
922
/*
923
 *   A "suggested" topic.  These provide suggestions for things the player
924
 *   might want to ASK or TELL another actor about.  At certain times
925
 *   (specifically, when starting a conversation with HELLO or TALK TO, or
926
 *   when the player enters a TOPICS command to explicitly ask for a list
927
 *   of topic suggestions), we'll look for these objects in the actor or
928
 *   actor state for the actor to whom we're talking.  We'll show a list
929
 *   of each currently active suggestion we find.  This gives the player
930
 *   some guidance of what to talk about.  For example:
931
 *   
932
 *   >talk to bob
933
 *.  "Excuse me," you say.
934
 *   
935
 *   Bob looks up from his newspaper.  "Yes?  Oh, you again."
936
 *   
937
 *   (You'd like to ask him about the black book, the candle, and the
938
 *   bell, and tell him about the crypt.)
939
 *   
940
 *   Topic suggestions are entirely optional.  Some authors don't like the
941
 *   idea, since they think it's too much like a menu system, and just
942
 *   gives away the solution to the game.  If you don't want to have
943
 *   anything to do with topic suggestions, we won't force you - simply
944
 *   don't define any SuggestedTopic objects, and the library will never
945
 *   offer suggestions and will even disable the TOPICS command.
946
 *   
947
 *   If you do want to use topic suggestions, the easiest way to use this
948
 *   class is to combine it using multiple inheritance with a TopicEntry
949
 *   object.  You just have to add SuggestedTopic to the superclass list
950
 *   for your topic entry object, and give the suggested topic a name
951
 *   string (using a property and format defined by the language-specific
952
 *   library) to display in suggestions lists.  Doing this, the suggestion
953
 *   will automatically be enabled whenever the topic entry is available,
954
 *   and will automatically be removed from the suggestions when the topic
955
 *   is invoked in conversation (in other words, we'll only suggest asking
956
 *   about the topic until it's been asked about once).
957
 *   
958
 *   Topic suggestions can be associated with an actor or an actor state;
959
 *   these are topics that a given character would like to talk to the
960
 *   associated actor about.  The association is a bit tricky: suggested
961
 *   topic objects are stored with the actor being *talked to*.  For
962
 *   example, if we want to suggest topics that the player character might
963
 *   want to ASK BILL ABOUT, we store these suggestions with *Bill*.  We
964
 *   do NOT store the suggestions with the player character.  This might
965
 *   seem backwards at first glance, since fundamentally the suggestions
966
 *   belong in the player character's "brain" - they are, after all,
967
 *   things the player character wants to talk about.  In practice,
968
 *   though, there are two things that make it easier to keep the
969
 *   information with the character being asked.  First, in most games,
970
 *   there's just one player character, so one of the two actors in each
971
 *   association will always be the player character; by storing the
972
 *   objects with the NPC, we can just let the PC be assumed as the other
973
 *   actor as a default, saving us some typing that would be necessary if
974
 *   we had to specify each object in the other direction.  Second, we
975
 *   keep the *response* objects associated with the character being asked
976
 *   - that association is intuitive, at least.  The thing is, we can
977
 *   usually combine the suggestion and response into a single object,
978
 *   saving another bunch of typing; if we didn't keep the suggestion with
979
 *   the character being asked, we couldn't combine the suggestions and
980
 *   responses this way, since they'd have to be associated with different
981
 *   actors.  
982
 */
983
class SuggestedTopic: object
984
    /*
985
     *   The name of the suggestion.  The rules for setting this vary by
986
     *   language; in the English version, we'll display the fullName when
987
     *   we show a stand-alone item, and the groupName when we appear in a
988
     *   list group (such as a group of ASK ABOUT or TELL ABOUT
989
     *   suggestions).
990
     *   
991
     *   In English, the fullName should be suitable for use after
992
     *   'could': "You could <fullName>, <fullName>, or <fullName>".
993
     *   
994
     *   In English, the phrasing where the 'name' property is used
995
     *   depends on the specific subclass, but it should usually be a
996
     *   qualified noun phrase (that is, it should include a qualifier
997
     *   such as "a" or "the" or a possessive).  For ASK and TELL, for
998
     *   example, the 'name' should be suitable for use after ABOUT: "You
999
     *   could ask him about <the lighthouse>, <Bob's black book>, or <the
1000
     *   weather>."
1001
     *   
1002
     *   By default, we'll walk up our 'location' tree looking for another
1003
     *   suggested topic; if we find one, we'll use its corresponding name
1004
     *   values.  
1005
     */
1006
    fullName = (fromEnclosingSuggestedTopic(&fullName, ''))
1007
    name = (fromEnclosingSuggestedTopic(&name, ''))
1008
1009
    /*
1010
     *   Our associated topic.  In most cases, this will be initialized
1011
     *   automatically: if this suggested topic object is also a
1012
     *   TopicEntry object (using multiple inheritance), we'll set this
1013
     *   during start-up to 'self', or if our location is a TopicEntry,
1014
     *   we'll set this to our location.  This only needs to be
1015
     *   initialized manually if neither of those conditions is true.  
1016
     */
1017
    associatedTopic = nil
1018
1019
    /*
1020
     *   Set the location to the actor to ask or tell about this topic.
1021
     *   This is the target of the ASK ABOUT or TELL ABOUT command, NOT
1022
     *   the actor who's doing the asking.  This can also be set to a
1023
     *   TopicEntry object, in which case we'll be associated with the
1024
     *   actor with which the topic entry is associated, and we'll also
1025
     *   automatically tie the topic entry to this suggestion.
1026
     *   
1027
     *   Because we're using the location property, you can use the '+'
1028
     *   notation to add a suggested topic to the target actor, state
1029
     *   objects, or topic entry.  
1030
     */
1031
    location = nil
1032
1033
    /*
1034
     *   The actor who *wants* to ask or tell about this topic.  Our
1035
     *   location property gives the actor to be asked or told, because
1036
     *   we're associated with the target actor - the same actor who has
1037
     *   the TopicEntry information for the topic.  This property, in
1038
     *   contrast, gives the actor who's doing the asking.
1039
     *   
1040
     *   By default, we return the player character; in most cases, you
1041
     *   won't have to override this.  In most games, only the player
1042
     *   character uses the suggested topic mechanism, because there's no
1043
     *   reason to suggest topics for NPC's - they're just automata, after
1044
     *   all, so if we want them to ask something, we can just program
1045
     *   them to ask it directly.  Also, most games have only one player
1046
     *   character.  Games that meet these criteria won't ever have to
1047
     *   override this.  If you do have multiple player characters, you'll
1048
     *   probably want to override this for each suggested topic to
1049
     *   indicate which character wants to ask about the topic, as the
1050
     *   different player characters might have different things they'd
1051
     *   want to talk about.  
1052
     */
1053
    suggestTo = (gPlayerChar)
1054
1055
    /* the ListGroup with which we're to list this suggestion */
1056
    suggestionGroup = []
1057
1058
    /* find the nearest enclosing SuggestedTopic parent */
1059
    findEnclosingSuggestedTopic()
1060
    {
1061
        /* walk up our location list */
1062
        for (local loc = location ; loc != nil ; loc = loc.location)
1063
        {
1064
            /* if this is a suggested topic, it's what we're looking for */
1065
            if (loc.ofKind(SuggestedTopic))
1066
                return loc;
1067
        }
1068
1069
        /* didn't find anything */
1070
        return nil;
1071
    }
1072
1073
    /* find the outermost enclosing SuggestedTopic parent */
1074
    findOuterSuggestedTopic()
1075
    {
1076
        local outer;
1077
        
1078
        /* walk up our location list */
1079
        for (local loc = self, outer = nil ; loc != nil ; loc = loc.location)
1080
        {
1081
            /* if this is a suggested topic, it's the outermost so far */
1082
            if (loc.ofKind(SuggestedTopic))
1083
                outer = loc;
1084
        }
1085
1086
        /* return the outermost suggested topic we found */
1087
        return outer;
1088
    }
1089
1090
    /* 
1091
     *   get a property from the nearest enclosing SuggestedTopic, or
1092
     *   return the given default value if there is no enclosing
1093
     *   SuggestedTopic 
1094
     */
1095
    fromEnclosingSuggestedTopic(prop, defaultVal)
1096
    {
1097
        /* look for the nearest enclosing suggested topic */
1098
        local enc = findEnclosingSuggestedTopic();
1099
1100
        /* 
1101
         *   return the desired property from the enclosing suggested
1102
         *   topic object if we found one, or the default if there is no
1103
         *   enclosing object 
1104
         */
1105
        return (enc != nil ? enc.(prop) : defaultVal);
1106
    }
1107
1108
    /*
1109
     *   Should we suggest this topic to the given actor?  We'll return
1110
     *   true if the actor is the same actor for which this suggestion is
1111
     *   intended, and the associated topic entry is currently active, and
1112
     *   we haven't already satisfied our curiosity about the topic.  
1113
     */
1114
    isSuggestionActive(actor, scopeList)
1115
    {
1116
        /* 
1117
         *   Check to see if this is our target actor; that the associated
1118
         *   topic itself is active; that our curiosity hasn't already been
1119
         *   satisfied; and that it's at least possible to match the
1120
         *   associated topic right now.  If all of these conditions are
1121
         *   met, we can make this suggestion.  
1122
         */
1123
        return (actor == suggestTo
1124
                && associatedTopicIsActive()
1125
                && associatedTopicCanMatch(actor, scopeList)
1126
                && !curiositySatisfied);
1127
    }
1128
1129
    /* 
1130
     *   The number of times to suggest asking about our topic.  When
1131
     *   we've asked about our associated topic this many times, we'll
1132
     *   have satisfied our curiosity.  In most cases, we'll only want to
1133
     *   suggest a topic until it's asked about once, since most topics
1134
     *   only have a single meaningful response, so we'll use 1 as the
1135
     *   default.  This should be overridden in cases where a topic will
1136
     *   reveal more information when asked several times.  If this is
1137
     *   nil, it means that there's no limit to the number of times to
1138
     *   suggest asking about this.  
1139
     */
1140
    timesToSuggest = 1
1141
1142
    /* 
1143
     *   Have we satisfied our curiosity about this topic?  Returns true
1144
     *   if so, nil if not.  We'll never suggest a topic when this returns
1145
     *   true, because this means that the player no longer feels the need
1146
     *   to ask about the topic.
1147
     */
1148
    curiositySatisfied = (timesToSuggest != nil
1149
                          && associatedTopicTalkCount() >= timesToSuggest)
1150
1151
    /* initialize - this is called automatically during pre-initialization */
1152
    initializeSuggestedTopic()
1153
    {
1154
        /* if we have a location, link up with our location */
1155
        if (location != nil)
1156
            location.addSuggestedTopic(self);
1157
1158
        /* 
1159
         *   if we're also a TopicEntry (using multiple inheritance), then
1160
         *   we are our own associated topic object 
1161
         */
1162
        if (ofKind(TopicEntry))
1163
            associatedTopic = self;
1164
    }
1165
1166
    /*
1167
     *   Methods that rely on the associated topic.  We isolate these in a
1168
     *   few methods here so that the rest of class doesn't depend on the
1169
     *   exact nature of our topic association.  In particular, this allows
1170
     *   for subclasses that don't have an associated topic at all, or that
1171
     *   have multiple associated topics.  Subclasses with specialized
1172
     *   topic relationships can simply override these methods to define
1173
     *   these methods appropriately.  
1174
     */
1175
1176
    /* is the associated topic active? */
1177
    associatedTopicIsActive() { return associatedTopic.checkIsActive(); }
1178
1179
    /* get the number of previous invocations of the associated topic */
1180
    associatedTopicTalkCount() { return associatedTopic.talkCount; }
1181
1182
    /* is it possible to match the associated topic? */
1183
    associatedTopicCanMatch(actor, scopeList)
1184
        { return associatedTopic.isMatchPossible(actor, scopeList); }
1185
1186
    /* 
1187
     *   Note that we're being shown in a topic inventory listing.  By
1188
     *   default, we don't do anything here, but subclasses can use this to
1189
     *   do any extra work they want to do on being listed.  
1190
     */
1191
    noteSuggestion() { }
1192
;
1193
1194
/*
1195
 *   A suggested topic that applies to an entire AltTopic group.
1196
 *   
1197
 *   Normally, a suggestion is tied to an individual TopicEntry.  This
1198
 *   means that when a topic has several AltTopic alternatives, each
1199
 *   AltTopic can be its own separate, independent suggestion.  A
1200
 *   particular alternative can be a suggestion or not, independently of
1201
 *   the other alternatives for the same TopicEntry.  Since each AltTopic
1202
 *   is a separate suggestion, asking about one of the alternatives won't
1203
 *   have any effect on the "curiosity" about the other alternatives - in
1204
 *   other words, the other alternatives will be separately suggested when
1205
 *   they become active.
1206
 *   
1207
 *   In many cases, it's better for an entire set of alternatives to be
1208
 *   treated as a single suggested topic.  That is, we want to suggest the
1209
 *   topic when ANY of the alternatives is active, and asking about any one
1210
 *   of the alternatives will satisfy the PC's curiosity for ALL of the
1211
 *   alternatives.  This sort of arrangement is usually better for cases
1212
 *   where the conditions that trigger the different alternatives aren't
1213
 *   things that ought to make the PC think to ask the same question again.
1214
 *   
1215
 *   Use this class by associating it with the *root* TopicEntry of the
1216
 *   group of alternatives.  You can do this most simply by mixing this
1217
 *   class into the superclass list of the root TopicEntry:
1218
 *   
1219
 *.  + AskTellTopic, SuggestedTopicTree, SuggestedAskTopic
1220
 *.     // ...
1221
 *.  ;
1222
 *   ++ AltTopic ... ;
1223
 *   ++ AltTopic ... ;
1224
 *   
1225
 *   This makes the entire group of AltTopics part of the same suggestion.
1226
 *   Note that you must *also* include SuggestedAsk, SuggestedTellTopic, or
1227
 *   one of the other specialized types among the superclass, to indicate
1228
 *   which kind of suggestion this is.  
1229
 */
1230
class SuggestedTopicTree: SuggestedTopic
1231
    /* is the associated topic active? */
1232
    associatedTopicIsActive()
1233
    {
1234
        /* the topic is active if anything in the AltTopic group is active */
1235
        return associatedTopic.anyAltIsActive;
1236
    }
1237
1238
    /* get the number of previous invocations of the associated topic */
1239
    associatedTopicTalkCount()
1240
    {
1241
        /* return the number of invocations of any alternative */
1242
        return associatedTopic.altTalkCount;
1243
    }
1244
;
1245
1246
/* 
1247
 *   A suggested ASK ABOUT topic.  We'll list ASK ABOUT topics together in
1248
 *   a subgroup ("you'd like to ask him about the book, the candle, and
1249
 *   the bell...").  
1250
 */
1251
class SuggestedAskTopic: SuggestedTopic
1252
    suggestionGroup = [suggestionAskGroup]
1253
;
1254
1255
/*
1256
 *   A suggested TELL ABOUT topic.  We'll list TELL ABOUT topics together
1257
 *   in a subgroup. 
1258
 */
1259
class SuggestedTellTopic: SuggestedTopic
1260
    suggestionGroup = [suggestionTellGroup]
1261
;
1262
1263
/*
1264
 *   A suggested ASK FOR topic.  We'll list ASK FOR topics together as a
1265
 *   group. 
1266
 */
1267
class SuggestedAskForTopic: SuggestedTopic
1268
    suggestionGroup = [suggestionAskForGroup]
1269
;
1270
1271
/*
1272
 *   A suggested GIVE TO topic. 
1273
 */
1274
class SuggestedGiveTopic: SuggestedTopic
1275
    suggestionGroup = [suggestionGiveGroup]
1276
;
1277
1278
/*
1279
 *   A suggested SHOW TO topic. 
1280
 */
1281
class SuggestedShowTopic: SuggestedTopic
1282
    suggestionGroup = [suggestionShowGroup]
1283
;
1284
1285
/*
1286
 *   A suggested YES/NO topic 
1287
 */
1288
class SuggestedYesTopic: SuggestedTopic
1289
    suggestionGroup = [suggestionYesNoGroup]
1290
;
1291
class SuggestedNoTopic: SuggestedTopic
1292
    suggestionGroup = [suggestionYesNoGroup]
1293
;
1294
1295
/* ------------------------------------------------------------------------ */
1296
/*
1297
 *   A conversation node.  Conversation nodes are supplemental topic
1298
 *   databases that represent a point in time in a conversation - a
1299
 *   particular context that arises from what came immediately before in
1300
 *   the conversation.  A conversation node is used to set up a group of
1301
 *   special responses that make sense only in a momentary context within a
1302
 *   conversation.
1303
 *   
1304
 *   A ConvNode object must be nested (via the 'location' property) within
1305
 *   an actor or an ActorState.  This is how we associate the ConvNode with
1306
 *   its actor.  Note that putting a ConvNode inside an ActorState doesn't
1307
 *   do anything different from putting the node directly inside the
1308
 *   ActorState's actor - we allow it only for convenience, to allow
1309
 *   greater flexibility arranging source code.  
1310
 */
1311
class ConvNode: ActorTopicDatabase
1312
    /*
1313
     *   Every ConvNode must have a name property.  This is a string
1314
     *   identifying the object.  Use this name string instead of a regular
1315
     *   object name (so ConvNode instances can essentially always be
1316
     *   anonymous, as far as the compiler is concerned).  This string is
1317
     *   used to find the ConvNode in the master ConvNode database
1318
     *   maintained in the conversationManager object.
1319
     *   
1320
     *   A ConvNode name should be unique with respect to all other
1321
     *   ConvNode objects - no two ConvNode objects should have the same
1322
     *   name string.  Other than this, the name strings are arbitrary.
1323
     *   (However, they shouldn't contain any '>' characters, because this
1324
     *   would prevent them from being used in <.convnode> tags, which is
1325
     *   the main place ConvNode's are usually used.)  
1326
     */
1327
    name = ''
1328
1329
    /*
1330
     *   Is this node "sticky"?  If so, we'll stick to this node if we
1331
     *   show a response that doesn't set a new node.  By default, we're
1332
     *   not sticky, so if we show a response that doesn't set a new node
1333
     *   and doesn't use a <.convstay> tag, we'll simply forget the node
1334
     *   and set the actor to no current ConvNode.
1335
     *   
1336
     *   Sticky nodes are useful when you want the actor to stay
1337
     *   on-subject even when the player digresses to talk about other
1338
     *   things.  This is useful when the actor has a particular thread
1339
     *   they want to drive the conversation along.  
1340
     */
1341
    isSticky = nil
1342
1343
    /*
1344
     *   Show our NPC-initiated greeting.  This is invoked when our actor's
1345
     *   initiateConversation() method is called to cause our actor to
1346
     *   initiate a conversation with the player character.  This method
1347
     *   should show what our actor says to initiate the conversation.  By
1348
     *   default, we'll invoke our npcGreetingList's script, if the
1349
     *   property is non-nil.
1350
     *   
1351
     *   A greeting should always be defined for any ConvNode that's used
1352
     *   in an initiateConversation() call.
1353
     *   
1354
     *   To define a greeting when defining a ConvNode, you can override
1355
     *   this method with a simple double-quoted string message, or you can
1356
     *   define an npcGreetingList property as an EventList of some kind.  
1357
     */
1358
    npcGreetingMsg()
1359
    {
1360
        /* if we have an npcGreetingList property, invoke the script */
1361
        if (npcGreetingList != nil)
1362
            npcGreetingList.doScript();
1363
    }
1364
1365
    /* an optional EventList containing our NPC-initiated greetings */
1366
    npcGreetingList = nil
1367
1368
    /*
1369
     *   Our NPC-initiated conversation continuation message.  This is
1370
     *   invoked on each turn (during the NPC's takeTurn() daemon
1371
     *   processing) that we're in this conversation node and the player
1372
     *   character doesn't do anything conversational.  This allows the NPC
1373
     *   to carry on the conversation of its own volition.  Define this as
1374
     *   a double-quoted string if you want the NPC to say something to
1375
     *   continue the conversation.  
1376
     */
1377
    npcContinueMsg = nil
1378
1379
    /* 
1380
     *   An optional EventList containing NPC-initiated continuation
1381
     *   messages.  You can define an EventList here instead of defining
1382
     *   npcContinueMsg, if you want more than one continuation message.  
1383
     */
1384
    npcContinueList = nil
1385
1386
    /*
1387
     *   Flag: automatically show a topic inventory on activating this
1388
     *   conversation node.  Some conversation nodes have sufficiently
1389
     *   obscure entries that it's desirable to show a topic inventory
1390
     *   automatically when the node becomes active.
1391
     *   
1392
     *   By default, we automatically show a topic inventory if the node
1393
     *   contains an active SpecialTopic entry.  Since special topics are
1394
     *   inherently obscure, in that they use non-standard commands, we
1395
     *   always want to show topics when one of these becomes active.  
1396
     */
1397
    autoShowTopics()
1398
    {
1399
        /* if we have an active special topic, show the topic inventory */
1400
        return (specialTopics != nil
1401
                && specialTopics.indexWhich({x: x.checkIsActive()}) != nil);
1402
    }
1403
1404
    /* our NPC is initiating a conversation starting with this node */
1405
    npcInitiateConversation()
1406
    {
1407
        local actor = getActor();
1408
        
1409
        /* tell the conversation manager we're the actor who's talking */
1410
        conversationManager.beginResponse(actor);
1411
1412
        /* note that we're in conversation with the player character now */
1413
        getActor().noteConversation(gPlayerChar);
1414
        
1415
        /* show our NPC greeting */
1416
        npcGreetingMsg();
1417
1418
        /* look for an ActorHelloTopic within the node */
1419
        handleTopic(gPlayerChar, actorHelloTopicObj, helloConvType, nil);
1420
1421
        /* end the response, staying in the current ConvNode by default */
1422
        conversationManager.finishResponse(actor, self);
1423
    }
1424
1425
    /* 
1426
     *   Continue the conversation of the NPC's own volition.  Returns
1427
     *   true if we displayed anything, nil if not. 
1428
     */
1429
    npcContinueConversation()
1430
    {
1431
        local actor = getActor();
1432
        local disp;
1433
        
1434
        /* tell the conversation manager we're starting a response */
1435
        conversationManager.beginResponse(actor);
1436
1437
        /* show our text, watching to see if we generate any output */
1438
        disp = outputManager.curOutputStream.watchForOutput(new function()
1439
        {
1440
            /* 
1441
             *   if we have a continuation list, invoke it; otherwise if we
1442
             *   have a continuation message, show it; otherwise, just
1443
             *   return nil to let the caller know we have nothing to add 
1444
             */
1445
            if (npcContinueList != nil)
1446
                npcContinueList.doScript();
1447
            else
1448
                npcContinueMsg;
1449
        });
1450
1451
        /* end the response, staying in the current ConvNode by default */
1452
        conversationManager.finishResponse(actor, self);
1453
1454
        /* 
1455
         *   if we actually said anything, note that we're in conversation
1456
         *   with the player character 
1457
         */
1458
        if (disp)
1459
            getActor().noteConversation(gPlayerChar);
1460
1461
        /* return the display indication */
1462
        return disp;
1463
    }
1464
1465
    /* our actor is our location, or our location's actor */
1466
    getActor()
1467
    {
1468
        /* if our location is an actor state, return the state's actor */
1469
        if (location.ofKind(ActorState))
1470
            return location.getActor();
1471
1472
        /* otherwise, our location must be our actor */
1473
        return location;
1474
    }
1475
1476
    /* our actor is the "owner" of our topics */
1477
    getTopicOwner() { return getActor(); }
1478
1479
    /*
1480
     *   Handle a conversation topic.  The actor state object will call
1481
     *   this to give the ConvNode the first crack at handling a
1482
     *   conversation command.  We'll return true if we handle the command,
1483
     *   nil if not.  Our default handling is to look up the topic in the
1484
     *   given database list property, and handle it through the TopicEntry
1485
     *   we find there, if any.  
1486
     */
1487
    handleConversation(otherActor, topic, convType, path)
1488
    {
1489
        /* try handling it, returning the handled/not-handled result */
1490
        return handleTopic(otherActor, topic, convType, path);
1491
    }
1492
1493
    /*
1494
     *   Can we end the conversation?  If so, return true; our caller will
1495
     *   invoke our endConversation() to let us know that the conversation
1496
     *   is over.
1497
     *   
1498
     *   To prevent the conversation from ending, simply return nil.
1499
     *   
1500
     *   In most cases, you won't want to force the conversation to keep
1501
     *   going without any comment.  Instead, you'll want to display some
1502
     *   message to let the player know what's going on - something like
1503
     *   "Hey! We're not through here!"  If you do display a message, then
1504
     *   rather than returning nil, return the special value blockEndConv -
1505
     *   this tells the caller that the actor said something, so the caller
1506
     *   will call noteConvAction() to prevent further generated
1507
     *   conversation output on this same turn.
1508
     *   
1509
     *   'reason' gives the reason the conversation is ending, as an
1510
     *   endConvXxx enum code.  
1511
     */
1512
    canEndConversation(actor, reason) { return true; }
1513
1514
    /*
1515
     *   Receive notification that our actor is ending a stateful
1516
     *   conversation.  This is called before the normal
1517
     *   InConversationState disengagement operations.  'reason' is one of
1518
     *   the endConvXxx enums, indicating why the conversation is ending.
1519
     *   
1520
     *   Instances can override this for special behavior on terminating a
1521
     *   conversation.  For example, an actor who just asked a question
1522
     *   could say something to indicate that the other actor is being
1523
     *   rude.  By default, we do nothing.
1524
     *   
1525
     *   Note that there's no way to block the ending of the conversation
1526
     *   here.  If you want to prevent the conversation from ending, use
1527
     *   canEndConversation() instead.  
1528
     */
1529
    endConversation(actor, reason) { }
1530
1531
    /*
1532
     *   Process a special command.  Check the given command line string
1533
     *   against all of our topics, and see if we have a match to any topic
1534
     *   that takes a special command syntax.  If we find a matching
1535
     *   special topic, we'll note the match, and turn the command into our
1536
     *   secret internal pseudo-command "XSPCLTOPIC".  That command will
1537
     *   then go through the parser, which will recognize it and process it
1538
     *   using the normal conversational mechanisms, which will find the
1539
     *   SpecialTopic we noted earlier (in this method) and display its
1540
     *   response.
1541
     *   
1542
     *   'str' is the original input string, exactly as entered by the
1543
     *   player, and 'procStr' is the "processed" version of the input
1544
     *   string.  The nature of the processing varies by language, but
1545
     *   generally this involves things like removing punctuation marks and
1546
     *   any "noise words" that don't usually change the meaning of the
1547
     *   input, at least for the purposes of matching a special topic.  
1548
     */
1549
    processSpecialCmd(str, procStr)
1550
    {
1551
        local match;
1552
        local cnt;
1553
1554
        /* we don't have an active special topic yet */
1555
        activeSpecialTopic = nil;
1556
1557
        /* 
1558
         *   if we have no special topics, there's definitely no special
1559
         *   processing we need to do 
1560
         */
1561
        if (specialTopics == nil)
1562
            return str;
1563
        
1564
        /* scan our special topics for a match */
1565
        cnt = 0;
1566
        foreach (local cur in specialTopics)
1567
        {
1568
            /* if this one is active, and it matches the string, note it */
1569
            if (cur.checkIsActive() && cur.matchPreParse(str, procStr))
1570
            {
1571
                /* remember it as the last match */
1572
                match = cur;
1573
1574
                /* count the match */
1575
                ++cnt;
1576
            }
1577
        }
1578
1579
        /*
1580
         *   If we found exactly one match, then activate it.  If we found
1581
         *   zero or more than one, ignore any special topics and proceed
1582
         *   on the assumption that this is a normal command.  (We ignore
1583
         *   ambiguous matches because this probably means that the entire
1584
         *   command is some very common word that happens to be acceptable
1585
         *   as a keyword in one or more of our matches.  In these cases,
1586
         *   the common word was probably meant as an ordinary command,
1587
         *   since the player would likely have been more specific if a
1588
         *   special topic were really desired.)  
1589
         */
1590
        if (cnt == 1)
1591
        {
1592
            /* 
1593
             *   remember the active SpecialTopic - we'll use this memory
1594
             *   to find it again when we get through the full command
1595
             *   processing 
1596
             */
1597
            activeSpecialTopic = match;
1598
1599
            /* 
1600
             *   Change the command to our special internal pseudo-command
1601
             *   that triggers the active special topic.  Include the
1602
             *   original string as a literal phrase, enclosed in double
1603
             *   quotes and specially coded to ensure that the tokenizer
1604
             *   doesn't become confused by any embedded quotes.  
1605
             */
1606
            return 'xspcltopic "' + SpecialTopicAction.encodeOrig(str) + '"';
1607
        }
1608
        else
1609
        {
1610
            /* proceed, treating the original input as an ordinary command */
1611
            return str;
1612
        }
1613
    }
1614
1615
    patWhitespace = static new RexPattern('<space>+')
1616
    patDelim = static new RexPattern('<punct|space>')
1617
1618
    /*
1619
     *   Handle an XSPCLTOPIC command from the given actor.  This is part
1620
     *   two of the two-phase processing of SpecialTopic matches.  Our
1621
     *   pre-parser checks each SpecialTopic's custom syntax for a match
1622
     *   to the player's text input, and if it finds a match, it sets our
1623
     *   activeSpecialTopic property to the matching SpecialTopic, and
1624
     *   changes the user's command to XSPCLTOPIC for processing by the
1625
     *   regular parser.  The regular parser sees the XSPCLTOPIC command,
1626
     *   which is a valid verb that calls the issuing actor's
1627
     *   saySpecialTopic() routine, which in turn forwards the request to
1628
     *   the issuing actor's interlocutor's current conversation node -
1629
     *   which is to say, 'self'.  We complete the two-step procedure by
1630
     *   going back to the active special topic object that we previously
1631
     *   noted and showing its response.  
1632
     */
1633
    saySpecialTopic(fromActor)
1634
    {
1635
        /* make sure we have an active special topic object */
1636
        if (activeSpecialTopic != nil)
1637
        {
1638
            local actor = getTopicOwner();
1639
            
1640
            /* tell the conversation manager we're starting a response */
1641
            conversationManager.beginResponse(actor);
1642
1643
            /* let the SpecialTopic handle the response */
1644
            activeSpecialTopic.handleTopic(fromActor, nil);
1645
1646
            /* 
1647
             *   Tell the conversation manager we're done.  By default, we
1648
             *   want to leave the conversation tree entirely, so set the
1649
             *   new default node to 'nil'.  
1650
             */
1651
            conversationManager.finishResponse(actor, nil);
1652
1653
            /* that wraps things up for the active special topic */
1654
            activeSpecialTopic = nil;
1655
        }
1656
        else
1657
        {
1658
            /* 
1659
             *   There is no active special topic, so the player must have
1660
             *   typed in the XSPCLTOPIC command explicitly - if we got
1661
             *   here through the normal two-step procedure then this
1662
             *   property would not be nil.  Politely decline the command,
1663
             *   since it's not for the player's direct use.  
1664
             */
1665
            gLibMessages.commandNotPresent;
1666
        }
1667
    }
1668
1669
    /*
1670
     *   The active special topic.  This is the SpecialTopic object that
1671
     *   we matched during pre-parsing, so it's the one whose response we
1672
     *   wish to show while processing the command we pre-parsed.  
1673
     */
1674
    activeSpecialTopic = nil
1675
1676
    /*
1677
     *   Note that we're becoming active, with a reason code.  Our actor
1678
     *   will call this method when we're becoming active, as long as we
1679
     *   weren't already active.
1680
     *   
1681
     *   'reason' is a string giving a reason code for why we're being
1682
     *   called.  For calls from the library, this will be one of these
1683
     *   codes:
1684
     *   
1685
     *    'convnode' - processing a <.convnode> tag
1686
     *   
1687
     *    'convend' - processing a <.convend> tag
1688
     *   
1689
     *    'initiateConversation' - a call to Actor.initiateConversation()
1690
     *   
1691
     *    'endConversation' - a call to Actor.endConversation()
1692
     *   
1693
     *   The reason code is provided so that the node can adapt its action
1694
     *   for different trigger conditions, if desired.  By default, we
1695
     *   ignore the reason code and just call the basic noteActive()
1696
     *   method.  
1697
     */
1698
    noteActiveReason(reason)
1699
    {
1700
        noteActive();
1701
    }
1702
    
1703
    /*
1704
     *   Note that we're becoming active, with a reason code.  Our actor
1705
     *   will call this method when we're becoming active, as long as we
1706
     *   weren't already active.
1707
     *   
1708
     *   Note that if you want to adapt the method's behavior according to
1709
     *   why the node was activated, you can override noteActiveReason()
1710
     *   instead of this method.  
1711
     */
1712
    noteActive()
1713
    {
1714
        /* if desired, schedule a topic inventory whenever we're activated */
1715
        if (autoShowTopics())
1716
            conversationManager.scheduleTopicInventory();
1717
    }
1718
1719
    /*
1720
     *   Note that we're leaving this conversation node.  This doesn't do
1721
     *   anything by default, but individual instances might find the
1722
     *   notification useful for triggering side effects.  
1723
     */
1724
    noteLeaving() { }
1725
;
1726
1727
1728
/* ------------------------------------------------------------------------ */
1729
/*
1730
 *   Pre-parser for special ConvNode-specific commands.  When the player
1731
 *   character is talking to another character, and the NPC's current
1732
 *   ConvNode includes topics with their own commands, we'll check the
1733
 *   player's input to see if it matches any of these topics.  
1734
 */
1735
specialTopicPreParser: StringPreParser
1736
    doParsing(str, which)
1737
    {
1738
        local actor;
1739
        local node;
1740
        
1741
        /* 
1742
         *   don't handle this on requests for missing literals - these
1743
         *   responses are always interpreted as literal text, so there's
1744
         *   no way this could be a special ConvNode command 
1745
         */
1746
        if (which == rmcAskLiteral)
1747
            return str;
1748
1749
        /* 
1750
         *   if the player character isn't currently in conversation, or
1751
         *   the actor with whom the player character is conversing doesn't
1752
         *   have a current conversation node, there's nothing to do 
1753
         */
1754
        if ((actor = gPlayerChar.getCurrentInterlocutor()) == nil
1755
            || (node = actor.curConvNode) == nil)
1756
            return str;
1757
1758
        /* ask the conversation node to process the string */
1759
        return node.processSpecialCmd(str, processInputStr(str));
1760
    }
1761
    
1762
    /* 
1763
     *   Process the input string, as desired, for special-topic parsing.
1764
     *   This method is for the language module's use; by default, we do
1765
     *   nothing.
1766
     *   
1767
     *   Language modules should override this to remove punctuation marks
1768
     *   and to do any other language-dependent processing to make the
1769
     *   string parsable.  
1770
     */
1771
    processInputStr(str) { return str; }
1772
;
1773
1774
/* ------------------------------------------------------------------------ */
1775
/*
1776
 *   A conversational action type descriptor.  This descriptor is used in
1777
 *   handleConversation() in Actor and ActorState to describe the type of
1778
 *   conversational action we're performing.  The type descriptor object
1779
 *   encapsulates a set of information that tells us how to handle the
1780
 *   action.  
1781
 */
1782
class ConvType: object
1783
    /* 
1784
     *   The unknown interlocutor message property.  This is used when we
1785
     *   try this conversational action without knowing whom we're talking
1786
     *   to.  For example, if we just say HELLO, and there's no one around
1787
     *   to talk to, we'll use this as the default response.  This can be a
1788
     *   library message property, or simply a single-quoted string to
1789
     *   display.  
1790
     */
1791
    unknownMsg = nil
1792
1793
    /*
1794
     *   The TopicDatabase topic-list property.  This is the property of
1795
     *   the TopicDatabase object that we evaluate to get this list of
1796
     *   topic entries to search for a match to the topic.  
1797
     */
1798
    topicListProp = nil
1799
1800
    /* the default response property for this action */
1801
    defaultResponseProp = nil
1802
1803
    /* 
1804
     *   Call the default response property on the given topic database.
1805
     *   This invokes the property given by defaultResponseProp().  We have
1806
     *   both the property and the method to call the property because this
1807
     *   allows us to test for the existence of the property and to call it
1808
     *   with the appropriate argument list. 
1809
     */
1810
    defaultResponse(db, otherActor, topic) { }
1811
1812
    /*
1813
     *   Perform any special follow-up action for this type of
1814
     *   conversational action. 
1815
     */
1816
    afterResponse(actor, otherActor) { }
1817
;
1818
1819
helloConvType: ConvType
1820
    unknownMsg = &sayHelloMsg
1821
    topicListProp = &miscTopics
1822
    defaultResponseProp = &defaultGreetingResponse
1823
    defaultResponse(db, other, topic)
1824
        { db.defaultGreetingResponse(other); }
1825
1826
    /* after an explicit HELLO, show any suggested topics */
1827
    afterResponse(actor, otherActor)
1828
    {
1829
        /* show or schedule a topic inventory, as appropriate */
1830
        conversationManager.showOrScheduleTopicInventory(actor, otherActor);
1831
    }
1832
;
1833
1834
byeConvType: ConvType
1835
    unknownMsg = &sayGoodbyeMsg
1836
    topicListProp = &miscTopics
1837
    defaultResponseProp = &defaultGoodbyeResponse
1838
    defaultResponse(db, other, topic)
1839
        { db.defaultGoodbyeResponse(other); }
1840
;
1841
1842
yesConvType: ConvType
1843
    unknownMsg = &sayYesMsg
1844
    topicListProp = &miscTopics
1845
    defaultResponseProp = &defaultYesResponse
1846
    defaultResponse(db, other, topic)
1847
        { db.defaultYesResponse(other); }
1848
;
1849
1850
noConvType: ConvType
1851
    unknownMsg = &sayNoMsg
1852
    topicListProp = &miscTopics
1853
    defaultResponseProp = &defaultNoResponse
1854
    defaultResponse(db, other, topic)
1855
        { db.defaultNoResponse(other); }
1856
;
1857
1858
askAboutConvType: ConvType
1859
    topicListProp = &askTopics
1860
    defaultResponseProp = &defaultAskResponse
1861
    defaultResponse(db, other, topic)
1862
        { db.defaultAskResponse(other, topic); }
1863
;
1864
1865
askForConvType: ConvType
1866
    topicListProp = &askForTopics
1867
    defaultResponseProp = &defaultAskForResponse
1868
    defaultResponse(db, other, topic)
1869
        { db.defaultAskForResponse(other, topic); }
1870
;
1871
1872
tellAboutConvType: ConvType
1873
    topicListProp = &tellTopics
1874
    defaultResponseProp = &defaultTellResponse
1875
    defaultResponse(db, other, topic)
1876
        { db.defaultTellResponse(other, topic); }
1877
;
1878
1879
giveConvType: ConvType
1880
    topicListProp = &giveTopics
1881
    defaultResponseProp = &defaultGiveResponse
1882
    defaultResponse(db, other, topic)
1883
        { db.defaultGiveResponse(other, topic); }
1884
;
1885
1886
showConvType: ConvType
1887
    topicListProp = &showTopics
1888
    defaultResponseProp = &defaultShowResponse
1889
    defaultResponse(db, other, topic)
1890
        { db.defaultShowResponse(other, topic); }
1891
;
1892
1893
commandConvType: ConvType
1894
    topicListProp = &commandTopics
1895
    defaultResponseProp = &defaultCommandResponse
1896
    defaultResponse(db, other, topic)
1897
        { db.defaultCommandResponse(other, topic); }
1898
;
1899
1900
/* 
1901
 *   This type is for NPC-initiated conversations.  It's not a normal
1902
 *   conversational action, since it doesn't involve handling a player
1903
 *   command, but is usually instead triggered by an agenda item,
1904
 *   takeTurn(), or other background activity.  
1905
 */
1906
initiateConvType: ConvType
1907
    topicListProp = &initiateTopics
1908
;
1909
1910
/* 
1911
 *   CONSULT ABOUT isn't a true conversational action, since it's applied
1912
 *   to inanimate objects (such as books); but it's handled through the
1913
 *   conversation system, so it needs a conversation type object 
1914
 */
1915
consultConvType: ConvType
1916
    topicListProp = &consultTopics
1917
;
1918
1919
/* ------------------------------------------------------------------------ */
1920
/*
1921
 *   A topic database entry.  Actors and actor state objects store topic
1922
 *   databases; a topic database is essentially a set of these entries.
1923
 *   
1924
 *   A TopicEntry can go directly inside an Actor, in which case it's part
1925
 *   of the actor's global set of topics; or, it can go inside an
1926
 *   ActorState, in which case it's part of the state's database and is
1927
 *   only active when the state is active; or, it can go inside a
1928
 *   TopicGroup, which is a set of topics with a common controlling
1929
 *   condition; or, it can go inside a ConvNode, in which case it's in
1930
 *   effect only when the conversation node is active.
1931
 *   
1932
 *   Each entry is a relationship between a topic, which is something that
1933
 *   can come up in an ASK or TELL action, and a handling for the topic.
1934
 *   In addition, each entry determines what kind or kinds of actions it
1935
 *   responds to.
1936
 *   
1937
 *   Note that TopicEntry objects are *not* simulation objects.  Rather,
1938
 *   these are abstract objects; they can be associated with simulation
1939
 *   objects via the matching mechanism, but these are separate from the
1940
 *   actual simulation objects.  The reason for this separation is that a
1941
 *   given simulation object might have many different response - the
1942
 *   response could vary according to who's being asked the question, who's
1943
 *   asking, and what else is happening in the game.
1944
 *   
1945
 *   An entry decides for itself if it matches a topic.  By default, an
1946
 *   entry can match based on either a simulation object, which we'll match
1947
 *   to anything in the topic's "in scope" or "likely" match lists, or
1948
 *   based on a regular expression string, which we'll match to the actual
1949
 *   topic text entered in the player's command.
1950
 *   
1951
 *   An entry can decide how strongly it matches a topic.  The database
1952
 *   will choose the strongest match when multiple entries match the same
1953
 *   topic.  The strength of the match is given by a numeric score; the
1954
 *   higher the score, the stronger the match.  The match strength makes it
1955
 *   easy to specify a hierarchy of topics from specific to general, so
1956
 *   that we provide general responses to general topic areas, but can
1957
 *   still respond to particular topics areas more specifically.  For
1958
 *   example, we might want to provide a specific match to the FROBNOZ
1959
 *   SPELL object, talking about that particular magic spell, but provide a
1960
 *   generic '.* spell' pattern to response to questions about any old
1961
 *   spell.  We'd give the generic pattern a lower score, so that the
1962
 *   specific FROBNOZ SPELL response would win when it matches, but we'd
1963
 *   fall back on the generic pattern in other cases.  
1964
 */
1965
class TopicEntry: object
1966
    /*
1967
     *   My matching simulation object or objects.  This can be either a
1968
     *   single object or a list of objects. 
1969
     */
1970
    matchObj = nil
1971
1972
    /*
1973
     *   Is this topic active?  This can be used to control how an actor
1974
     *   can respond without have to worry about adding and removing topics
1975
     *   manually at key events, or storing the topics in state objects.
1976
     *   Sometimes, it's easier to just put a topic entry in the actor's
1977
     *   database from the start, and test some condition dynamically when
1978
     *   the topic is actually queried.  To do this, override this method
1979
     *   to test the condition that determines when the topic entry should
1980
     *   become active.  We'll never show the topic's response when
1981
     *   isActive returns nil.  By default, we simply return true to
1982
     *   indicate that the topic entry is active.  
1983
     */
1984
    isActive = true
1985
1986
    /*
1987
     *   Flag: we are a "conversational" topic.  This is true by default.
1988
     *   When this is set to nil, a ConversationReadyState will NOT show
1989
     *   its greeting and will not enter its InConversationState to show
1990
     *   this topic entry's response.
1991
     *   
1992
     *   This should be set to nil when the topic entry's response is
1993
     *   non-conversational, in which case a greeting would be
1994
     *   undesirable.  This is appropriate for responses like "You don't
1995
     *   think he'd want to talk about that", where the response indicates
1996
     *   that the player character didn't even ask a question (or
1997
     *   whatever).  
1998
     */
1999
    isConversational = true
2000
2001
    /*
2002
     *   Do we imply a greeting?  By default, all conversational topics
2003
     *   imply a greeting.  We separate this out so that the implied
2004
     *   greeting can be controlled independently of whether or not we're
2005
     *   actually conversational, if desired.  
2006
     */
2007
    impliesGreeting = (isConversational)
2008
2009
    /*
2010
     *   Get the actor associated with the topic, if any.  By default,
2011
     *   we'll return our enclosing database's topic owner, if it's an
2012
     *   actor - in almost all cases, if there's any actor associated with
2013
     *   a topic, it's simply the owner of the database containing the
2014
     *   topic.  
2015
     */
2016
    getActor()
2017
    {
2018
        local owner;
2019
2020
        /* 
2021
         *   if we have an owner, and it's an actor, then it's our
2022
         *   associated actor; otherwise, we don't have any associated
2023
         *   actor 
2024
         */
2025
        if ((owner = location.getTopicOwner()) != nil && owner.ofKind(Actor))
2026
            return owner;
2027
        else
2028
            return nil;
2029
    }
2030
2031
    /*
2032
     *   Determine if this topic is active.  This checks the isActive
2033
     *   property, and also takes into account our relationship to
2034
     *   alternative entries for the topic.  Generally, you should *define*
2035
     *   (override) isActive, and *call* this method.  
2036
     */
2037
    checkIsActive()
2038
    {
2039
        /* 
2040
         *   if our isActive property indicates we're not active, we're
2041
         *   definitely not active, so there's no need to check for an
2042
         *   overriding alternative 
2043
         */
2044
        if (!isActive)
2045
            return nil;
2046
2047
        /* if we have an active nested alternative, it overrides us */
2048
        if (altTopicList.indexWhich({x: x.isActive}) != nil)
2049
            return nil;
2050
2051
        /* ask our container if its topics are active */
2052
        return location.topicGroupActive();
2053
    }
2054
2055
    /*
2056
     *   Check to see if any alternative in the alternative group is
2057
     *   active.  This returns true if we're active or if any of our nested
2058
     *   AltTopics is active.  
2059
     */
2060
    anyAltIsActive()
2061
    {
2062
        /* 
2063
         *   if all topics within our container are inactive, then there's
2064
         *   definitely no active alternative 
2065
         */
2066
        if (!location.topicGroupActive())
2067
            return nil;
2068
2069
        /* 
2070
         *   if we're active, or any of our nested AltTopics is active, our
2071
         *   alternative group is active 
2072
         */
2073
        if (isActive || altTopicList.indexWhich({x: x.isActive}) != nil)
2074
            return true;
2075
2076
        /* we didn't find any active alternatives in the entire group */
2077
        return nil;
2078
    }
2079
2080
    /*
2081
     *   Adjust my score value for any hierarchical adjustments.  We'll add
2082
     *   the score adjustment for each enclosing object.  
2083
     */
2084
    adjustScore(score)
2085
    {
2086
        /* the score is nil, it means there's no match, so don't adjust it */
2087
        if (score == nil)
2088
            return score;
2089
2090
        /* add in the cumulative adjustment from my containers */
2091
        return score + location.topicGroupScoreAdjustment;
2092
    }
2093
2094
    /*
2095
     *   Check to see if we want to defer to the given topic from an
2096
     *   inferior topic database.  By default, we never defer to a topic
2097
     *   from an inferior database: we choose a matching topic from the top
2098
     *   database in the hierarchy where we find a match.
2099
     *   
2100
     *   The database hierarchy, for most purposes, starts with the
2101
     *   ConvNode at the highest level, then the ActorState, then the
2102
     *   Actor.  We search those databases, in that order, and we take the
2103
     *   first match we find.  By default, if there's another match in a
2104
     *   lower-level database, it doesn't matter what its matchScore is: we
2105
     *   always pick the one from the highest-level database where we find
2106
     *   a match.  You can override this method to change this behavior.
2107
     *   
2108
     *   We don't actually define this method here, because the presence of
2109
     *   the method is significant.  If the method isn't defined at all, we
2110
     *   won't bother looking for a possible deferral, saving the trouble
2111
     *   of searching the other databases in the hierarchy.  
2112
     */
2113
    // deferToEntry(other) { return nil; }
2114
2115
    /* 
2116
     *   Our match strength score.  By default, we'll use a score of 100,
2117
     *   which is just an arbitrary base score.  
2118
     */
2119
    matchScore = 100
2120
2121
    /*
2122
     *   The set of database lists we're part of.  This is a list of
2123
     *   property pointers, giving the TopicDatabase properties of the
2124
     *   lists we participate in. 
2125
     */
2126
    includeInList = []
2127
2128
    /*
2129
     *   Our response.  This is displayed when we're the topic entry
2130
     *   selected to handle an ASK or TELL.  Each topic entry must override
2131
     *   this to show our response text (or, alternatively, an entry can
2132
     *   override handleTopic so that it doesn't call this property).  
2133
     */
2134
    topicResponse = ""
2135
2136
    /*
2137
     *   The number of times this topic has invoked by the player.  Each
2138
     *   time the player asks/tells/etc about this topic, we'll increment
2139
     *   this count.  
2140
     */
2141
    talkCount = 0
2142
2143
    /*
2144
     *   The number of times this topic or any nested AltTopic has been
2145
     *   invoked by the player.  Each time the player asks/tells/etc about
2146
     *   this topic OR any of its AltTopic children, we'll increment this
2147
     *   count.  
2148
     */
2149
    altTalkCount = 0
2150
2151
    /* 
2152
     *   the owner of any AltTopic nested within me is the same as my own
2153
     *   topic owner, which we take from our location 
2154
     */
2155
    getTopicOwner()
2156
    {
2157
        if (location != nil)
2158
            return location.getTopicOwner();
2159
        else
2160
            return nil;
2161
    }
2162
2163
    /*
2164
     *   Initialize.  If we have a location property, we'll assume that the
2165
     *   location is a topic database object, and we'll add ourselves to
2166
     *   that database.  
2167
     */
2168
    initializeTopicEntry()
2169
    {
2170
        /* if we have a location, add ourselves to its topic database */
2171
        if (location != nil)
2172
            location.addTopic(self);
2173
2174
        /* sort our list of AltTopic children */
2175
        altTopicList = altTopicList.sort(
2176
            SortAsc, {a, b: a.altTopicOrder - b.altTopicOrder});
2177
    }
2178
2179
    /* add a topic nested within us */
2180
    addTopic(entry)
2181
    {
2182
        /* if we have a location, add the entry to its topic database */
2183
        if (location != nil)
2184
            location.addTopic(entry);
2185
    }
2186
2187
    /*
2188
     *   Add an AltTopic entry.  This is called by our AltTopic children
2189
     *   during initialization; we'll simply add the entry to our list of
2190
     *   AltTopic children.  
2191
     */
2192
    addAltTopic(entry)
2193
    {
2194
        /* add the entry to our list of alternatives */
2195
        altTopicList += entry;
2196
    }
2197
2198
    /* get the topic group score adjustment (for AltTopics nested within) */
2199
    topicGroupScoreAdjustment = (location.topicGroupScoreAdjustment)
2200
2201
    /* check the group isActive status (for AltTopics nested within) */
2202
    topicGroupActive = (location.topicGroupActive)
2203
2204
    /* our list of AltTopic children */
2205
    altTopicList = []
2206
2207
    /* 
2208
     *   Match the topic.  This is abstract in this base class; it must be
2209
     *   defined by each concrete subclass.  This returns nil if there's
2210
     *   no match, or an integer value if there's a match.  The higher the
2211
     *   number's value, the stronger the match.
2212
     *   
2213
     *   This is abstract in the base class because the meaning of 'topic'
2214
     *   varies by subclass, according to which type of command it's used
2215
     *   with.  For example, in ASK and TELL commands, 'topic' is a
2216
     *   ResolvedTopic describing the topic in the player's command; for
2217
     *   GIVE and SHOW commands, it's the resolved simulation object.
2218
     */
2219
    // matchTopic(fromActor, topic) { return nil; }
2220
2221
    /*
2222
     *   Check to see if a match to this topic entry is *possible* right
2223
     *   now for the given actor.  For most subclasses, this is inherently
2224
     *   imprecise, because the 'match' function simply isn't reversible in
2225
     *   general: to know if we can be matched, we'd have to determine if
2226
     *   there's a non-empty set of possible inputs that can match us.
2227
     *   This method is complementary to matchTopic(), so subclasses must
2228
     *   override with a corresponding implementation.
2229
     *   
2230
     *   'actor' is the actor to whom we're making the suggestion.
2231
     *   'scopeList' is the list of objects that are in scope for the
2232
     *   actor.
2233
     *   
2234
     *   The library only uses this to determine if a suggestion should be
2235
     *   offered.  So, specialized topic instances with non-standard match
2236
     *   rules don't have to worry about this unless they're used as
2237
     *   suggestions, or unless the game itself needs this information for
2238
     *   some other reason.  
2239
     */
2240
    // isMatchPossible(actor, scopeList) { return true; }
2241
2242
    /*
2243
     *   Set pronouns for the topic, if possible.  If the topic corresponds
2244
     *   to a game-world object, then we should set the pronoun antecedent
2245
     *   to the game object.  This must be handled per subclass because of
2246
     *   the range of possible meanings of 'topic'.  
2247
     */
2248
    setTopicPronouns(fromActor, topic) { }
2249
2250
    /*
2251
     *   Handle the topic.  This is called when we find that this is the
2252
     *   best topic entry for the current topic.
2253
     *   
2254
     *   By default, we'll do one of two things:
2255
     *   
2256
     *   - If 'self' inherits from Script, then we'll simply invoke our
2257
     *   doScript() method.  This makes it especially easy to set up a
2258
     *   topic entry that shows a series of responses: just add EventList
2259
     *   or one of its subclasses to the base class list when defining the
2260
     *   topic, and define the eventList property as a list of string
2261
     *   responses.  For example:
2262
     *   
2263
     *.     + TopicEntry, StopEventList @blackBook
2264
     *.        ['<q>What makes you think I know anything about it?</q>
2265
     *.         he says, his voice shaking. ',
2266
     *.         '<q>No! You can\'t make me tell you!</q> he wails. ',
2267
     *.         '<q>All right, fine! I\'ll tell you, but I warn you,
2268
     *.         this is knowledge mortal men were never meant to know.</q> ',
2269
     *.         // and so on
2270
     *.        ]
2271
     *.     ;
2272
     *   
2273
     *   - Otherwise, we'll call our topicResponse property, which should
2274
     *   simply be a double-quoted string to display.  This is the simplest
2275
     *   way to define a topic with just one response.
2276
     *   
2277
     *   Note that 'topic' will vary by subclass, depending on the type of
2278
     *   command used with the topic type.  For example, for ASK and TELL
2279
     *   commands, 'topic' is a ResolvedTopic object; for GIVE and SHOW,
2280
     *   it's a simulation object (i.e., generally a Thing subclass).  
2281
     */
2282
    handleTopic(fromActor, topic)
2283
    {
2284
        /* note the invocation */
2285
        noteInvocation(fromActor);
2286
2287
        /* set pronoun antecedents if possible */
2288
        setTopicPronouns(fromActor, topic);
2289
        
2290
        /* check to see if we're a Script */
2291
        if (ofKind(Script))
2292
        {
2293
            /* we're a Script - invoke our script */
2294
            doScript();
2295
        }
2296
        else
2297
        {
2298
            /* show our simple response string */
2299
            topicResponse;
2300
        }
2301
    }
2302
2303
    /* note that we've been invoked */
2304
    noteInvocation(fromActor)
2305
    {
2306
        /* 
2307
         *   we count as one of the alternatives in our alternative group,
2308
         *   so note the invocation of the group
2309
         */
2310
        noteAltInvocation(fromActor, self);
2311
2312
        /* count the invocation */
2313
        ++talkCount;
2314
    }
2315
2316
    /* 
2317
     *   Note that something in our entire alternative group has been
2318
     *   invoked.  We count as a member of our own group, so this is
2319
     *   invoked when we're invoked; this is also invoked when any AltTopic
2320
     *   child of ours is invoked.  
2321
     */
2322
    noteAltInvocation(fromActor, alt)
2323
    {
2324
        local owner;
2325
        
2326
        /* notify our owner of the topic invocation */
2327
        if ((owner = location.getTopicOwner()) != nil)
2328
            owner.notifyTopicResponse(fromActor, alt);
2329
        
2330
        /* count the alternative invocation */
2331
        ++altTalkCount;
2332
    }
2333
2334
    /*
2335
     *   Add a suggested topic.  A suggested topic can be nested within a
2336
     *   topic entry; doing this associates the suggested topic with the
2337
     *   topic entry, and automatically associates the suggested topic
2338
     *   with the entry's actor or actor state.  
2339
     */
2340
    addSuggestedTopic(t)
2341
    {
2342
        /* 
2343
         *   If the SuggestedTopic is *directly* within us, we're the
2344
         *   SuggestedTopic object's associated TopicEntry.  The nesting
2345
         *   could be deeper, if we have alternative topics nested within
2346
         *   us; in these cases, we're not directly associated with the
2347
         *   suggested topic.  
2348
         */
2349
        if (t.location == self)
2350
            t.associatedTopic = self;
2351
2352
        /* add the suggestion to our location's topic database */
2353
        if (location != nil)
2354
            location.addSuggestedTopic(t);
2355
    }
2356
;
2357
2358
/*
2359
 *   A TopicGroup is an abstract container for a set of TopicEntry objects.
2360
 *   The purpose of the group object is to apply a common "is active"
2361
 *   condition to all of the topics within the group.
2362
 *   
2363
 *   The isActive condition of the TopicGroup is effectively AND'ed with
2364
 *   any other conditions on the nested TopicEntry's.  In other words, a
2365
 *   TopicEntry within the TopicGroup is active if the TopicEntry would
2366
 *   otherwise be acive AND the TopicGroup is active.
2367
 *   
2368
 *   TopicEntry objects are associated with the group via the 'location'
2369
 *   property - set the location of the TopicEntry to point to the
2370
 *   containing TopicGroup.
2371
 *   
2372
 *   You can put a TopicGroup anywhere a TopicEntry could go - directly
2373
 *   inside an Actor, inside an ActorState, or within another TopicGroup.
2374
 *   The topic entries within a topic group act as though they were
2375
 *   directly in the topic group's container.  
2376
 */
2377
class TopicGroup: object
2378
    /* 
2379
     *   The group "active" condition - each instance should override this
2380
     *   to specify the condition that applies to all of the TopicEntry
2381
     *   objects within the group.  
2382
     */
2383
    isActive = true
2384
2385
    /*
2386
     *   The *adjustment* to the match score for topic entries contained
2387
     *   within this group.  This is usually a positive number, so that it
2388
     *   boosts the match strength of the child topics.  
2389
     */
2390
    matchScoreAdjustment = 0
2391
2392
    /* 
2393
     *   the topic owner for any topic entries within the group is the
2394
     *   topic owner taken from the group's own location 
2395
     */
2396
    getTopicOwner() { return location.getTopicOwner(); }
2397
2398
    /* are TopicEntry objects within the group active? */
2399
    topicGroupActive()
2400
    {
2401
        /* 
2402
         *   our TopicEntry objects are active if the group condition is
2403
         *   true and our container's contents are active
2404
         */
2405
        return isActive && location.topicGroupActive();
2406
    }
2407
2408
    /* 
2409
     *   Get my score adjustment.  We'll return our own basic score
2410
     *   adjustment plus the cumulative adjustment for our containers.  
2411
     */
2412
    topicGroupScoreAdjustment = (matchScoreAdjustment
2413
                                 + location.topicGroupScoreAdjustment)
2414
2415
    /* add a topic - we'll simply add the topic directly to our container */
2416
    addTopic(topic) { location.addTopic(topic); }
2417
2418
    /* add a suggested topic - we'll pass this up to our container */
2419
    addSuggestedTopic(topic) { location.addSuggestedTopic(topic); }
2420
;
2421
2422
/*
2423
 *   An alternative topic entry.  This makes it easy to define different
2424
 *   responses to a topic according to the game state; for example, we
2425
 *   might want to provide a different response for a topic after some
2426
 *   event has occurred, so that we can reflect knowledge of the event in
2427
 *   the response.
2428
 *   
2429
 *   A set of alternative topics is sort of like an inverted if-then-else.
2430
 *   You start by defining a normal TopicEntry (an AskTopic, or an
2431
 *   AskTellTopic, or whatever) for the basic response.  Then, you add a
2432
 *   nested AltTopic located within the base topic; you can add another
2433
 *   AltTopic nested within the base topic, and another after that, and so
2434
 *   on.  When we need to choose one of the topics, we'll choose the last
2435
 *   one that indicates it's active.  So, the order of appearance is
2436
 *   essentially an override order: the first AltTopic overrides its parent
2437
 *   TopicEntry, and each subsequent AltTopic overrides its previous
2438
 *   AltTopic.
2439
 *   
2440
 *   + AskTellTopic @lighthouse "It's very tall.";
2441
 *.  ++ AltTopic "Not really..." isActive=(...);
2442
 *.  ++ AltTopic "Well, maybe..." isActive=(...);
2443
 *.  ++ AltTopic "One more thing..." isActive=(...);
2444
 *   
2445
 *   In this example, the response we'll show for ASK ABOUT LIGHTHOUSE will
2446
 *   always be the LAST entry of the group that's active.  For example, if
2447
 *   all of the responses are active except for the very last one, then
2448
 *   we'll show the "Well, maybe" response, because it's the last active
2449
 *   response.  If the main AskTellTopic is active, but none of the
2450
 *   AltTopics are active, we'll show the "It's very tall" main response,
2451
 *   because it's the last element of the group that's active.
2452
 *   
2453
 *   Note that an AltTopic takes its matching information from its parent,
2454
 *   so you don't need to specify a matchObj or any other matching
2455
 *   information in an AltTopic.  You merely need to provide the response
2456
 *   text and the isActive test.  
2457
 */
2458
class AltTopic: TopicEntry
2459
    /* we match if our parent matches, and with the same score */
2460
    matchTopic(fromActor, topic)
2461
        { return location.matchTopic(fromActor, topic); }
2462
2463
    /* we can match if our parent can match */
2464
    isMatchPossible(actor, scopeList)
2465
        { return location.isMatchPossible(actor, scopeList); }
2466
2467
    /* we can match a pre-parse string if our parent can */
2468
    matchPreParse(str, pstr) { return location.matchPreParse(str, pstr); }
2469
2470
    /* set pronouns for the topic */
2471
    setTopicPronouns(fromActor, topic)
2472
        { location.setTopicPronouns(fromActor, topic); }
2473
2474
    /* include in the same lists as our parent */
2475
    includeInList = (location.includeInList)
2476
2477
    /* AltTopic initialization */
2478
    initializeAltTopic()
2479
    {
2480
        /* add myself to our parent's child list */
2481
        if (location != nil)
2482
            location.addAltTopic(self);
2483
    }
2484
2485
    /*
2486
     *   Determine if this topic is active.  An AltTopic is active if its
2487
     *   own isActive indicates true, AND none of its subsequent siblings
2488
     *   are active.  
2489
     */
2490
    checkIsActive()
2491
    {
2492
        /* we can't be active if our own isActive says we're not */
2493
        if (!isActive)
2494
            return nil;
2495
2496
        /* 
2497
         *   Check for any active element after us in the parent's list.
2498
         *   To do this, scan from the end of the parent list backwards,
2499
         *   and look for an element that's active.  If we reach our own
2500
         *   entry, then we'll know that there are no active entries
2501
         *   following us in the list.  Note that we already know we're
2502
         *   active, or we wouldn't have gotten this far, so we can simply
2503
         *   look for the rightmost active element in the list.  
2504
         */
2505
        if (location != nil
2506
            && location.altTopicList.lastValWhich({x: x.isActive}) != self)
2507
        {
2508
            /* 
2509
             *   we found an active element after ourself, so it overrides
2510
             *   us - we're therefore not active 
2511
             */
2512
            return nil;
2513
        }
2514
2515
        /* ask our container if its topics are active */
2516
        return location.topicGroupActive();
2517
    }
2518
2519
    /* take our implied-greeting status from our parent */
2520
    impliesGreeting = (location.impliesGreeting)
2521
2522
    /* take our conversational status from our parent */
2523
    isConversational = (location.isConversational)
2524
2525
    /* 
2526
     *   Our relative order within our parent's list of alternatives.  By
2527
     *   default, we simply return the source file ordering, which ensures
2528
     *   that static AltTopic objects (i.e., those defined directly in
2529
     *   source files, not dynamically created with 'new') will be ordered
2530
     *   just as they're laid out in the source file.  
2531
     */
2532
    altTopicOrder = (sourceTextOrder)
2533
2534
    /* note invocation */
2535
    noteInvocation(fromActor)
2536
    {
2537
        /* count our own invocation */
2538
        ++talkCount;
2539
2540
        /* let our container know its AltTopic child is being invoked */
2541
        if (location != nil)
2542
            location.noteAltInvocation(fromActor, self);
2543
    }
2544
2545
    /* our AltTopic counter is the AltTopic counter for the enclosing topic */
2546
    altTalkCount = (location != nil ? location.altTalkCount : talkCount)
2547
;
2548
2549
/*
2550
 *   A "topic match" topic entry.  This is a topic entry that matches topic
2551
 *   phrases in the grammar.
2552
 *   
2553
 *   Handling topic phrases is a bit tricky, because they can't be resolved
2554
 *   to definitive game-world objects the way ordinary noun phrases can.
2555
 *   Topic phrases can refer to things that aren't physically present, but
2556
 *   which are known to the actor performing the command; they can refer to
2557
 *   abstract Topic objects, that have no physical existence in the game
2558
 *   world at all; and they can ever be arbitrary text that doesn't match
2559
 *   any vocabulary defined by the game.
2560
 *   
2561
 *   Our strategy in matching topics is to first narrow the list down to
2562
 *   the physical and abstract game objects that both match the vocabulary
2563
 *   used in the command and are part of the memory of the actor performing
2564
 *   the command.  That much is handled by the normal topic phrase
2565
 *   resolution rules, and gives us a list of possible matches.  Then,
2566
 *   given this narrowed list of possibilities, we look through the list of
2567
 *   objects that we're associated with; we effectively intersect the two
2568
 *   lists, and if the result is non-empty, we consider it a match.
2569
 *   Finally, we also consider any regular expression that we're associated
2570
 *   with; if we have one, and the topic phrase text in the command matches
2571
 *   the input, we'll consider it a match.  
2572
 */
2573
class TopicMatchTopic: TopicEntry
2574
    /*  
2575
     *   A regular expression pattern that we'll match to the actual topic
2576
     *   text as entered in the command.  If 'matchExactCase' is true,
2577
     *   we'll match the exact text in its original upper/lower case
2578
     *   rendering; otherwise, we'll convert the player input to lower-case
2579
     *   before matching it against the pattern.  In most cases, we'll want
2580
     *   to match the input no matter what combination of upper and lower
2581
     *   case the player entered, so matchExactCase is nil by default.
2582
     *   
2583
     *   Note that both the object (or object list) and the regular
2584
     *   expression pattern can be included for a single topic entry
2585
     *   object.  This allows a topic entry to match several different ways
2586
     *   of entering the topic name, or to match several different topics
2587
     *   with the same response.  
2588
     */
2589
    matchPattern = nil
2590
    matchExactCase = nil
2591
2592
    /* 
2593
     *   Match the topic.  By default, we'll match to either the simulation
2594
     *   object or objects in matchObj, or the pattern in matchPattern.
2595
     *   Note that we always try both ways of matching, so a single
2596
     *   AskTellTopic can define both a pattern and an object list.
2597
     *   
2598
     *   Subclasses can override this as desired to use other ways of
2599
     *   matching.  
2600
     */
2601
    matchTopic(fromActor, topic)
2602
    {
2603
        /* 
2604
         *   if we have one or more match objects, try matching to the
2605
         *   topic's best simulation object match 
2606
         */
2607
        if (matchObj != nil)
2608
        {
2609
            /* 
2610
             *   we have a match object or match object list - if it's a
2611
             *   collection, check each element, otherwise just match the
2612
             *   single object 
2613
             */
2614
            if (matchObj.ofKind(Collection))
2615
            {
2616
                /* try matching each object in the list */
2617
                if (matchObj.indexWhich({x: findMatchObj(x, topic)}) != nil)
2618
                    return matchScore;
2619
            }
2620
            else
2621
            {
2622
                /* match the single object */
2623
                if (findMatchObj(matchObj, topic))
2624
                    return matchScore;
2625
            }
2626
        }
2627
2628
        /* 
2629
         *   check for a match to the regular expression pattern, if we
2630
         *   have a pattern AND the resolved topic allows literal matches 
2631
         */
2632
        if (matchPattern != nil && topic.canMatchLiterally())
2633
        {
2634
            local txt;
2635
2636
            /* 
2637
             *   There's no match object; try matching our regular
2638
             *   expression to the actual topic text.  Get the actual text.
2639
             */
2640
            txt = topic.getTopicText();
2641
2642
            /* 
2643
             *   if they don't want an exact case match, convert the
2644
             *   original topic text to lower case 
2645
             */
2646
            if (!matchExactCase)
2647
                txt = txt.toLower();
2648
2649
            /* if the regular expression matches, we match */
2650
            if (rexMatch(matchPattern, txt) != nil)
2651
                return matchScore;
2652
        }
2653
2654
        /* we didn't find a match - indicate this with a nil score */
2655
        return nil;
2656
    }
2657
2658
    /*
2659
     *   Match an individual item from our match list to the given
2660
     *   ResolvedTopic object.  We'll check each object in the resolved
2661
     *   topic's "in scope" and "likely" lists.  
2662
     */
2663
    findMatchObj(obj, rt)
2664
    {
2665
        /* check the "in scope" list */
2666
        if (rt.inScopeList.indexOf(obj) != nil)
2667
            return true;
2668
2669
        /* check the "likely" list */
2670
        return (rt.likelyList.indexOf(obj) != nil);
2671
    }
2672
2673
    /*
2674
     *   It's possible for us to match if any of our matchObj objects are
2675
     *   known to the actor.  If we have no matchObj objects, we must be
2676
     *   matching on a regular expression or on a custom condition, so we
2677
     *   can't speculate on matchability; we'll simply return true in those
2678
     *   cases.  
2679
     */
2680
    isMatchPossible(actor, scopeList)
2681
    {
2682
        /* check what we have in our matchObj */
2683
        if (matchObj == nil)
2684
        {
2685
            /* 
2686
             *   we have no match object, so we must match on a regular
2687
             *   expression or a custom condition; we can't speculate on
2688
             *   our matchability, so just return true as a default 
2689
             */
2690
            return true;
2691
        }
2692
        else if (matchObj.ofKind(Collection))
2693
        {
2694
            /* 
2695
             *   we have a list of match objects - return true if any of
2696
             *   them are known or are currently in scope 
2697
             */
2698
            return (matchObj.indexWhich(
2699
                {x: actor.knowsAbout(x) || scopeList.indexOf(x)}) != nil);
2700
        }
2701
        else
2702
        {
2703
            /* 
2704
             *   we have a single match object - return true if it's known
2705
             *   or it's in scope 
2706
             */
2707
            return (actor.knowsAbout(matchObj)
2708
                    || scopeList.indexOf(matchObj) != nil);
2709
        }
2710
    }
2711
2712
    /* set the topic pronouns */
2713
    setTopicPronouns(fromActor, topic)
2714
    {
2715
        /* check to see what kind of match object we have */
2716
        if (matchObj == nil)
2717
        {
2718
            /* 
2719
             *   no match object, so we must match a regular expression
2720
             *   pattern; this gives us no clue what game object we might
2721
             *   match, so there's nothing we can do here 
2722
             */
2723
        }
2724
        else if (matchObj.ofKind(Collection))
2725
        {
2726
            local lst;
2727
            
2728
            /*
2729
             *   We match a list of objects.  Get the subset of the
2730
             *   in-scope list from the topic that we match.  Consider only
2731
             *   the in-scope items for now, and consider only game-world
2732
             *   objects (Things).  
2733
             */
2734
            lst = matchObj.subset(
2735
                {x: x.ofKind(Thing) && topic.inScopeList.indexOf(x) != nil});
2736
2737
            /* if that didn't turn up anything, consider the likelies, too */
2738
            if (lst.length() == 0)
2739
                lst = matchObj.subset(
2740
                    {x: (x.ofKind(Thing)
2741
                         && topic.likelyList.indexOf(x) != nil)});
2742
2743
            /* 
2744
             *   if that narrows it down to one match, make it the pronoun
2745
             *   antecedent 
2746
             */
2747
            if (lst.length() == 1)
2748
                fromActor.setPronounObj(lst[1]);
2749
        }
2750
        else
2751
        {
2752
            /* 
2753
             *   we match a single object; if it's a game-world object (a
2754
             *   Thing), use it as the pronoun antecedent 
2755
             */
2756
            if (matchObj.ofKind(Thing))
2757
                fromActor.setPronounObj(matchObj);
2758
        }
2759
    }
2760
;
2761
2762
/*
2763
 *   A dual ASK/TELL topic database entry.  This type of topic is included
2764
 *   in both the ASK ABOUT and TELL ABOUT lists.
2765
 *   
2766
 *   Many authors have chosen to treat ASK and TELL as equivalent, or at
2767
 *   least, equivalent for most topics.  Since these verbs only very weakly
2768
 *   suggest what the player character is actually saying, it's frequently
2769
 *   the case that a given topic response makes just as much sense coming
2770
 *   from TELL as from ASK, or vice versa.  In these cases, it's best to
2771
 *   enter the topic under both ASK and TELL; which one the player tries
2772
 *   might simply depend on the player's frame of mind, and they might feel
2773
 *   cheated if one works and the other doesn't in cases where both are
2774
 *   equally valid.
2775
 */
2776
class AskTellTopic: TopicMatchTopic
2777
    /* include me in both the ASK and TELL lists */
2778
    includeInList = [&askTopics, &tellTopics]
2779
;
2780
2781
/*
2782
 *   An ASK ABOUT topic database entry.  This type of topic is included in
2783
 *   the ASK ABOUT list only.  
2784
 */
2785
class AskTopic: AskTellTopic
2786
    includeInList = [&askTopics]
2787
;
2788
2789
/*
2790
 *   A TELL ABOUT topic database entry.  This type of topic entry is
2791
 *   included in the TELL ABOUT list only.  
2792
 */
2793
class TellTopic: AskTellTopic
2794
    includeInList = [&tellTopics]
2795
;
2796
2797
/*
2798
 *   An ASK FOR topic database entry.  This type of topic entry is
2799
 *   included in the ASK FOR list only. 
2800
 */
2801
class AskForTopic: AskTellTopic
2802
    includeInList = [&askForTopics]
2803
;
2804
2805
/*
2806
 *   A combination ASK ABOUT and ASK FOR topic. 
2807
 */
2808
class AskAboutForTopic: AskTellTopic
2809
    includeInList = [&askTopics, &askForTopics]
2810
;
2811
2812
/* 
2813
 *   A combination ASK ABOUT, TELL ABOUT, and ASK FOR topic.  
2814
 */
2815
class AskTellAboutForTopic: AskTellTopic
2816
    includeInList = [&askTopics, &tellTopics, &askForTopics]
2817
;
2818
2819
2820
/*
2821
 *   A base class for topic entries that match simple simulation objects.  
2822
 */
2823
class ThingMatchTopic: TopicEntry
2824
    /*
2825
     *   Match the topic.  We'll match the simulation object in 'obj' to
2826
     *   our matchObj object or list.  
2827
     */
2828
    matchTopic(fromActor, obj)
2829
    {
2830
        /* 
2831
         *   if matchObj is a collection, check each element, otherwise
2832
         *   just match the single object 
2833
         */
2834
        if (matchObj.ofKind(Collection))
2835
        {
2836
            /* try matching each object in the list */
2837
            if (matchObj.indexOf(obj) != nil)
2838
                return matchScore;
2839
        }
2840
        else
2841
        {
2842
            /* match the single object */
2843
            if (matchObj == obj)
2844
                return matchScore;
2845
        }
2846
2847
        /* didn't find a match - indicate this by returning a nil score */
2848
        return nil;
2849
    }
2850
2851
    /*
2852
     *   It's possible for us to match if any of our matchObj objects are
2853
     *   in scope.
2854
     */
2855
    isMatchPossible(actor, scopeList)
2856
    {
2857
        /* check to see what kind of match object we have */
2858
        if (matchObj.ofKind(Collection))
2859
        {
2860
            /* we can match if any of our match objects are in scope */
2861
            return (matchObj.indexWhich({x: scopeList.indexOf(x)}) != nil);
2862
        }
2863
        else
2864
        {
2865
            /* we can match if our single match object is in scope */
2866
            return scopeList.indexOf(matchObj);
2867
        }
2868
    }
2869
2870
    /* set the topic pronouns */
2871
    setTopicPronouns(fromActor, topic)
2872
    {
2873
        /* 
2874
         *   the 'topic' is just an ordinary game object; as long as it's a
2875
         *   Thing, set it as the antecedent 
2876
         */
2877
        if (topic.ofKind(Thing))
2878
            fromActor.setPronounObj(topic);
2879
    }
2880
;
2881
2882
/*
2883
 *   A GIVE/SHOW topic database entry.
2884
 *   
2885
 *   Note that this base class is usable for any command that refers to a
2886
 *   simulation object.  It's NOT suitable for ASK/TELL lists, or for other
2887
 *   commands that refer to topics, since we expect our 'topic' to be a
2888
 *   resolved simulation object.  
2889
 */
2890
class GiveShowTopic: ThingMatchTopic
2891
    /* include me in both the GIVE and SHOW lists */
2892
    includeInList = [&giveTopics, &showTopics]
2893
;
2894
2895
/*
2896
 *   A GIVE TO topic database entry.  This type of topic entry is included
2897
 *   in the GIVE TO list only.  
2898
 */
2899
class GiveTopic: GiveShowTopic
2900
    includeInList = [&giveTopics]
2901
;
2902
2903
/*
2904
 *   A SHOW TO topic database entry.  This type of topic entry is included
2905
 *   in the SHOW TO list only.  
2906
 */
2907
class ShowTopic: GiveShowTopic
2908
    includeInList = [&showTopics]
2909
;
2910
2911
/*
2912
 *   A TopicEntry that can match a Thing or a Topic.  This can be used to
2913
 *   combine ASK/TELL-type responses and GIVE/SHOW-type responses in a
2914
 *   single topic entry. 
2915
 *   
2916
 *   When this kind of topic is used as a suggested topic, note that you
2917
 *   should name the suggestion according to the least restrictive verb.
2918
 *   This is important because the suggestion will be active if any of the
2919
 *   verbs would allow it; to ensure that we suggest a verb that will
2920
 *   actually work, we should thus use the least restrictive verb.  In
2921
 *   practice, this means you should use ASK or TELL as the suggestion
2922
 *   name, because an object merely has to be known to be used as a topic;
2923
 *   it might be possible to ASK/TELL about an object but not GIVE/SHOW the
2924
 *   object, because the object is known but not currently in scope.  
2925
 */
2926
class TopicOrThingMatchTopic: ThingMatchTopic, TopicMatchTopic
2927
    matchTopic(fromActor, obj)
2928
    {
2929
        /* 
2930
         *   if we're being asked to match a ResolvedTopic, use the
2931
         *   inherited TopicMatchTopic handling; otherwise, use the
2932
         *   inherited ThingMatchTopic handling 
2933
         */
2934
        if (obj.ofKind(ResolvedTopic))
2935
            return inherited TopicMatchTopic(fromActor, obj);
2936
        else
2937
            return inherited ThingMatchTopic(fromActor, obj);
2938
    }
2939
2940
    isMatchPossible(actor, scopeList)
2941
    {
2942
        /* if a match is possible from either subclass, allow it */
2943
        return (inherited TopicMatchTopic(actor, scopeList)
2944
                || inherited ThingMatchTopic(actor, scopeList));
2945
    }
2946
2947
    setTopicPronouns(fromActor, obj)
2948
    {
2949
        /* 
2950
         *   if the object is a ResolvedTopic, use the inherited
2951
         *   TopicMatchTopic handling, otherwise use the ThingMatchTopic
2952
         *   handling 
2953
         */
2954
        if (obj.ofKind(ResolvedTopic))
2955
            return inherited TopicMatchTopic(fromActor, obj);
2956
        else
2957
            return inherited ThingMatchTopic(fromActor, obj);
2958
    }
2959
;
2960
2961
/*
2962
 *   A combined ASK/TELL/SHOW topic.  Players will sometimes want to point
2963
 *   something out when it's visible, rather than asking about it; this
2964
 *   allows SHOW TO to be used as a synonym for ASK ABOUT for these cases. 
2965
 */
2966
class AskTellShowTopic: TopicOrThingMatchTopic
2967
    includeInList = [&askTopics, &tellTopics, &showTopics]
2968
;
2969
2970
/*
2971
 *   A combined ASK/TELL/GIVE/SHOW topic.
2972
 */
2973
class AskTellGiveShowTopic: TopicOrThingMatchTopic
2974
    includeInList = [&askTopics, &tellTopics, &giveTopics, &showTopics]
2975
;
2976
2977
/*
2978
 *   A command topic.  This is used to respond to orders given to an NPC,
2979
 *   as in "BOB, GO EAST."  The match object for this kind of topic entry
2980
 *   is an Action class; for example, to create a response to "BOB, LOOK",
2981
 *   we'd create a CommandTopic that matches LookAction.
2982
 *   
2983
 *   If you're designing a CommandTopic for a command can be accepted from
2984
 *   a remote location, such as by telephone, you should be aware that the
2985
 *   command will be running in the NPC's visual sense context.  This means
2986
 *   that if the player character can't see the NPC, the topic result
2987
 *   message will be hidden - the NPC's visual sense context hides all
2988
 *   messages generated while it's in effect if the PC can't see the NPC.
2989
 *   This is usually desirable, since most messages relay visual
2990
 *   information that wouldn't be visible to the player character if the PC
2991
 *   can't see the subject of the message.  However, if you've specifically
2992
 *   designed your CommandTopic to work remotely, this isn't at all what
2993
 *   you want, since you've already taken the remoteness into account in
2994
 *   the message and thus want the message to be displayed after all.  The
2995
 *   way to handle this is to wrap the message in a callWithSenseContext()
2996
 *   with a nil sense context.  For example:
2997
 *   
2998
 *   topicResponse()
2999
 *.     { callWithSenseContext(nil, nil, {: "Here's my message!" }); }
3000
 */
3001
class CommandTopic: TopicEntry
3002
    /* we go in the command topics list */
3003
    includeInList = [&commandTopics]
3004
3005
    /* match the topic */
3006
    matchTopic(fromActor, obj)
3007
    {
3008
        /* 
3009
         *   Check the collection or the single object, as needed.  Note
3010
         *   that our match object is an Action base class, so we must
3011
         *   match if 'obj' is of the match object class. 
3012
         */
3013
        if (matchObj.ofKind(Collection))
3014
        {
3015
            /* check each entry for a match */
3016
            if (matchObj.indexWhich({x: obj.ofKind(x)}) != nil)
3017
                return matchScore;
3018
        }
3019
        else
3020
        {
3021
            /* check our single object */
3022
            if (obj.ofKind(matchObj))
3023
                return matchScore;
3024
        }
3025
3026
        /* didn't find a match */
3027
        return nil;
3028
    }
3029
3030
    /* 
3031
     *   we can always match, since the player can always type in any
3032
     *   possible action 
3033
     */
3034
    isMatchPossible(actor, scopeList) { return true; }
3035
3036
    /* we have no pronouns to set */
3037
    setTopicPronouns(fromActor, topic) { }
3038
;
3039
3040
/*
3041
 *   A base class for simple miscellaneous topics.  These handle things
3042
 *   like YES, NO, HELLO, and GOODBYE, where the topic is entirely
3043
 *   contained in the verb, and there's no separate noun phrase needed to
3044
 *   indicate the topic.  
3045
 */
3046
class MiscTopic: TopicEntry
3047
    matchTopic(fromActor, obj)
3048
    {
3049
        /* 
3050
         *   if it's one of our matching topics, return our match score,
3051
         *   otherwise return a nil score to indicate failure 
3052
         */
3053
        return (matchList.indexOf(obj) != nil) ? matchScore : nil;
3054
    }
3055
3056
    /* 
3057
     *   a match is always possible for simple verb topics (since the
3058
     *   player could always type the verb) 
3059
     */
3060
    isMatchPossible(actor, scopeList) { return true; }
3061
;
3062
3063
/*
3064
 *   A greeting topic - this handles a HELLO or TALK TO command, as well
3065
 *   as implied greetings (the kind of greeting generated when we jump
3066
 *   directly into a conversation with an actor that uses stateful
3067
 *   conversations, by typing a command like ASK ABOUT or TELL ABOUT
3068
 *   without first saying HELLO explicitly).
3069
 */
3070
class HelloTopic: MiscTopic
3071
    includeInList = [&miscTopics]
3072
    matchList = [helloTopicObj, impHelloTopicObj]
3073
3074
    /* 
3075
     *   this is an explicit greeting, so it obviously shouldn't trigger
3076
     *   an implied greeting, regardless of how conversational we are
3077
     */
3078
    impliesGreeting = nil
3079
3080
    /* 
3081
     *   if we use this as a greeting upon entering a ConvNode, we'll want
3082
     *   to stay in the node afterward
3083
     */
3084
    noteInvocation(fromActor)
3085
    {
3086
        inherited(fromActor);
3087
        "<.convstay>";
3088
    }
3089
;
3090
3091
/*
3092
 *   An implied greeting topic.  This handles ONLY implied greetings.
3093
 *   
3094
 *   Note that we have a higher-than-normal score by default.  This makes
3095
 *   it easy to program two common cases for conversational states.
3096
 *   First, the more common case, where you want a single message for both
3097
 *   implied and explicit greetings: just create a HelloTopic, since that
3098
 *   responds to both kinds.  Second, the less common case, where we want
3099
 *   to differentiate, writing separate responses for implied and explicit
3100
 *   greetings: create a HelloTopic for the explicit kind, and ALSO create
3101
 *   an ImpHelloTopic for the implied kind.  Since the ImpHelloTopic has a
3102
 *   higher score, it'll overshadow the HelloTopic object when it matches
3103
 *   an implied greeting; but since ImpHelloTopic doesn't match an
3104
 *   explicit greeting, we'll fall back on the HelloTopic for that.  
3105
 */
3106
class ImpHelloTopic: MiscTopic
3107
    includeInList = [&miscTopics]
3108
    matchList = [impHelloTopicObj]
3109
    matchScore = 200
3110
3111
    /* 
3112
     *   this is itself a greeting, so we obviously don't want to trigger
3113
     *   another greeting to greet the greeting
3114
     */
3115
    impliesGreeting = nil
3116
3117
    /* 
3118
     *   if we use this as a greeting upon entering a ConvNode, we'll want
3119
     *   to stay in the node afterward
3120
     */
3121
    noteInvocation(fromActor)
3122
    {
3123
        inherited(fromActor);
3124
        "<.convstay>";
3125
    }
3126
;
3127
3128
/*
3129
 *   Actor Hello topic - this handles greetings when an NPC initiates the
3130
 *   conversation. 
3131
 */
3132
class ActorHelloTopic: MiscTopic
3133
    includeInList = [&miscTopics]
3134
    matchList = [actorHelloTopicObj]
3135
    matchScore = 200
3136
3137
    /* this is a greeting, so we don't want to trigger another greeting */
3138
    impliesGreeting = nil
3139
3140
    /* 
3141
     *   if we use this as a greeting upon entering a ConvNode, we'll want
3142
     *   to stay in the node afterward
3143
     */
3144
    noteInvocation(fromActor)
3145
    {
3146
        inherited(fromActor);
3147
        "<.convstay>";
3148
    }
3149
;
3150
3151
/*
3152
 *   A goodbye topic - this handles both explicit GOODBYE commands and
3153
 *   implied goodbyes.  Implied goodbyes happen when a conversation ends
3154
 *   without an explicit GOODBYE command, such as when the player character
3155
 *   walks away from the NPC, or the NPC gets bored and wanders off, or the
3156
 *   NPC terminates the conversation of its own volition.  
3157
 */
3158
class ByeTopic: MiscTopic
3159
    includeInList = [&miscTopics]
3160
    matchList = [byeTopicObj,
3161
                 leaveByeTopicObj, boredByeTopicObj, actorByeTopicObj]
3162
3163
    /* 
3164
     *   If we're not already in a conversation when we say GOODBYE, don't
3165
     *   bother saying HELLO implicitly - if the player is saying GOODBYE
3166
     *   explicitly, she probably has the impression that there's some kind
3167
     *   of interaction already going on with the NPC.  If we didn't
3168
     *   override this, you'd get an automatic HELLO followed by the
3169
     *   explicit GOODBYE when not already in conversation, which is a
3170
     *   little weird. 
3171
     */
3172
    impliesGreeting = nil
3173
;
3174
3175
/* 
3176
 *   An implied goodbye topic.  This handles ONLY automatic (implied)
3177
 *   conversation endings, which happen when we walk away from an actor
3178
 *   we're talking to, or the other actor ends the conversation after being
3179
 *   ignored for too long, or the other actor ends the conversation of its
3180
 *   own volition via npc.endConversation().
3181
 *   
3182
 *   We use a higher-than-default matchScore so that any time we have both
3183
 *   a ByeTopic and an ImpByeTopic that are both active, we'll choose the
3184
 *   more specific ImpByeTopic.  
3185
 */
3186
class ImpByeTopic: MiscTopic
3187
    includeInList = [&miscTopics]
3188
    matchList = [leaveByeTopicObj, boredByeTopicObj, actorByeTopicObj]
3189
    matchScore = 200
3190
;
3191
3192
/*
3193
 *   A "bored" goodbye topic.  This handles ONLY goodbyes that happen when
3194
 *   the actor we're talking terminates the conversation out of boredom
3195
 *   (i.e., after a period of inactivity in the conversation).
3196
 *   
3197
 *   Note that this is a subset of ImpByeTopic - ImpByeTopic handles
3198
 *   "bored" and "leaving" goodbyes, while this one handles only the
3199
 *   "bored" goodbyes.  You can use this kind of topic if you want to
3200
 *   differentiate the responses to "bored" and "leaving" conversation
3201
 *   endings.  
3202
 */
3203
class BoredByeTopic: MiscTopic
3204
    includeInList = [&miscTopics]
3205
    matchList = [boredByeTopicObj]
3206
    matchScore = 300
3207
;
3208
3209
/*
3210
 *   A "leaving" goodbye topic.  This handles ONLY goodbyes that happen
3211
 *   when the PC walks away from the actor they're talking to.
3212
 *   
3213
 *   Note that this is a subset of ImpByeTopic - ImpByeTopic handles
3214
 *   "bored" and "leaving" goodbyes, while this one handles only the
3215
 *   "leaving" goodbyes.  You can use this kind of topic if you want to
3216
 *   differentiate the responses to "bored" and "leaving" conversation
3217
 *   endings.  
3218
 */
3219
class LeaveByeTopic: MiscTopic
3220
    includeInList = [&miscTopics]
3221
    matchList = [leaveByeTopicObj]
3222
    matchScore = 300
3223
;
3224
3225
/*
3226
 *   An "actor" goodbye topic.  This handles ONLY goodbyes that happen when
3227
 *   the NPC terminates the conversation of its own volition via
3228
 *   npc.endConversation(). 
3229
 */
3230
class ActorByeTopic: MiscTopic
3231
    includeInList = [&miscTopics]
3232
    matchList = [actorByeTopicObj]
3233
    matchScore = 300
3234
;
3235
3236
/* a topic for both HELLO and GOODBYE */
3237
class HelloGoodbyeTopic: MiscTopic
3238
    includeInList = [&miscTopics]
3239
    matchList = [helloTopicObj, impHelloTopicObj,
3240
                 byeTopicObj, boredByeTopicObj, leaveByeTopicObj,
3241
                 actorByeTopicObj]
3242
3243
    /* 
3244
     *   since we handle greetings, we don't want to trigger a separate
3245
     *   implied greeting 
3246
     */
3247
    impliesGreeting = nil
3248
;
3249
3250
/* 
3251
 *   Topic singletons representing HELLO and GOODBYE topics.  These are
3252
 *   used as the parameter to matchTopic() when we're looking for the
3253
 *   response to the corresponding verbs. 
3254
 */
3255
helloTopicObj: object;
3256
byeTopicObj: object;
3257
3258
/* 
3259
 *   a topic singleton for implied greetings (the kind of greeting that
3260
 *   happens when we jump right into a conversation with a command like
3261
 *   ASK ABOUT or TELL ABOUT, rather than explicitly saying HELLO first) 
3262
 */
3263
impHelloTopicObj: object;
3264
3265
/*
3266
 *   a topic singleton for an NPC-initiated hello (this is the kind of
3267
 *   greeting that happens when the NPC is the one who initiates the
3268
 *   conversation, via actor.initiateConversation()) 
3269
 */
3270
actorHelloTopicObj: object;
3271
3272
3273
/* 
3274
 *   topic singletons for the two kinds of automatic goodbyes (the kind of
3275
 *   conversation ending that happens when we simply walk away from an
3276
 *   actor we're in conversation with, or when we ignore the other actor
3277
 *   for enough turns that the actor gets bored and ends the conversation
3278
 *   of its own volition) 
3279
 */
3280
boredByeTopicObj: object;
3281
leaveByeTopicObj: object;
3282
3283
/*
3284
 *   a topic singleton for an NPC-initiated goodbye (this is the kind of
3285
 *   goodbye that happens when the NPC is the one who breaks off the
3286
 *   conversation, via npc.endConversation()) 
3287
 */
3288
actorByeTopicObj: object;
3289
3290
/*
3291
 *   A YES/NO topic.  These handle YES and/or NO, which are normally used
3292
 *   as responses to questions posed by the NPC.  YesNoTopic is the base
3293
 *   class, and can be used to create a single response for both YES and
3294
 *   NO; YesTopic provides a response just for YES; and NoTopic provides a
3295
 *   response just for NO.  The only thing an instance of these classes
3296
 *   should normally need to specify is the response text (or a list of
3297
 *   response strings, by multiply inheriting from an EventList subclass as
3298
 *   usual).  
3299
 */
3300
class YesNoTopic: MiscTopic
3301
    includeInList = [&miscTopics]
3302
3303
    /* 
3304
     *   our list of matching topic objects - we'll only ever be asked to
3305
     *   match 'yesTopicObj' (for YES inputs) or 'noTopicObj' (for NO
3306
     *   inputs) 
3307
     */
3308
    matchList = [yesTopicObj, noTopicObj]
3309
;
3310
3311
class YesTopic: YesNoTopic
3312
    matchList = [yesTopicObj]
3313
;
3314
3315
class NoTopic: YesNoTopic
3316
    matchList = [noTopicObj]
3317
;
3318
3319
/*
3320
 *   Topic singletons representing the "topic" of YES and NO commands.  We
3321
 *   use these as the parameter to matchTopic() in the TopicEntry objects
3322
 *   when we're looking for a response to a YES or NO command.  
3323
 */
3324
yesTopicObj: object;
3325
noTopicObj: object;
3326
3327
3328
/*
3329
 *   A default topic entry.  This is an easy way to create an entry that
3330
 *   will be used as a last resort, if no other entry is found.  This kind
3331
 *   of entry will match *any* topic, but with the lowest possible score,
3332
 *   so it will only be used if there's no other match for the topic.
3333
 *   
3334
 *   It's a good idea to provide some variety in a character's default
3335
 *   responses, because it seems that in every real game session, the
3336
 *   player will at some point spend a while peppering an NPC with
3337
 *   questions on every topic that comes to mind.  Usually, the player will
3338
 *   think of many things that the author didn't anticipate.  The more
3339
 *   things the author covers, the better, but it's unrealistic to think
3340
 *   that an author can reasonably anticipate every topic, or even most
3341
 *   topics, that players will think of.  So, we'll have a whole bunch of
3342
 *   ASK, ASK, ASK commands all at once, and much of the time we'll get a
3343
 *   bunch of default responses in a row.  It gets tedious in these cases
3344
 *   when the NPC repeats the same default response over and over.
3345
 *   
3346
 *   A simple but effective trick is to provide three or four random
3347
 *   variations on "I don't know that," customized for the character.  This
3348
 *   makes the NPC seem less like a totally predictable robot, and it can
3349
 *   also be a convenient place to flesh out the character a bit.  An easy
3350
 *   way to do this is to add ShuffledEventList to the superclass list of
3351
 *   the default topic entry, and provide a eventList list with the various
3352
 *   random responses.  For example:
3353
 *   
3354
 *   + DefaultAskTellTopic, ShuffledEventList
3355
 *.    ['Bob mutters something unintelligible and keeps fiddling with
3356
 *.     the radio. ',
3357
 *.     'Bob looks up from the radio for a second, but then goes back
3358
 *.     to adjusting the knobs. ',
3359
 *.     'Bob just keeps adjusting the radio, completely ignoring you. ']
3360
 *.  ;
3361
 *   
3362
 *   It's important to be rather generic in default responses; in
3363
 *   particular, it's a bad idea to suggest that the NPC doesn't know about
3364
 *   the topic.  From the author's perspective, it's easy to make the
3365
 *   mistake of thinking "this is a default response, so it'll only be used
3366
 *   for topics that are completely off in left field."  Wrong!  Sometimes
3367
 *   the player will indeed ask about completely random stuff, but in
3368
 *   *most* cases, the player is only asking because they think it's a
3369
 *   reasonable thing to ask about.  Defaults that say things like "I don't
3370
 *   know anything about that" or "What a crazy thing to ask about" or "You
3371
 *   must be stupid if you think I know about that!" can make a game look
3372
 *   poorly implemented, because these will inevitably be shown in response
3373
 *   to questions that the NPC really ought to know about:
3374
 *   
3375
 *.  >ask bob about his mother
3376
 *.  "I don't know anything about that!"
3377
 *.  
3378
 *.  >ask bob about his father
3379
 *.  "You'd have to be a moron to think I'd know about that!"
3380
 *   
3381
 *   It's better to use responses that suggest that the NPC is
3382
 *   uninterested, or is hostile, or is preoccupied with something else, or
3383
 *   doesn't understand the question, or something else appropriate to the
3384
 *   character.  If you can manage to make the response about the
3385
 *   *character*, rather than the topic, it'll reduce the chances that the
3386
 *   response is jarringly illogical.  
3387
 */
3388
class DefaultTopic: TopicEntry
3389
    /*
3390
     *   A list of objects to exclude from the default match.  This can be
3391
     *   used to create a default topic that matches everything EXCEPT a
3392
     *   few specific topics that are handled in enclosing topic databases.
3393
     *   For example, if you want to create a catch-all in a ConvNode's
3394
     *   list of topics, but you want a particular topic to escape the
3395
     *   catch-all and be sent instead to the Actor's topic database, you
3396
     *   can put that topic in the exclude list for the catch-all, making
3397
     *   it a catch-almost-all.  
3398
     */
3399
    excludeMatch = []
3400
3401
    /* match anything except topics in our exclude list */
3402
    matchTopic(fromActor, topic)
3403
    {
3404
        /* 
3405
         *   If the topic matches anything in the exclusion list, do NOT
3406
         *   match the topic.  If 'topic' is a ResolvedTopic, search its
3407
         *   in-scope and 'likely' lists; otherwise search for 'topic'
3408
         *   directly in the exclusion list.  
3409
         */
3410
        if (topic.ofKind(ResolvedTopic))
3411
        {
3412
            /* it's a resolved topic, so search the in-scope/likely lists */
3413
            if (topic.inScopeList.intersect(excludeMatch).length() != 0
3414
                || topic.likelyList.intersect(excludeMatch).length() != 0)
3415
                return nil;
3416
        }
3417
        else if (excludeMatch.indexOf(topic) != nil)
3418
            return nil;
3419
3420
        /* match anything else with our score */
3421
        return matchScore;
3422
    }
3423
3424
    /* use a low default matching score */
3425
    matchScore = 1
3426
3427
    /* a match is always possible for a default topic */
3428
    isMatchPossible(actor, scopeList) { return true; }
3429
3430
    /* set the topic pronoun */
3431
    setTopicPronouns(fromActor, topic)
3432
    {
3433
        /*
3434
         *   We're not matching anything, so we can get no guidance from
3435
         *   the match object.  Instead, look at the topic itself.  If it's
3436
         *   a Thing, set the Thing as the antecedent.  If it's a
3437
         *   ResolvedTopic, and there's only one Thing match in scope, or
3438
         *   only one Thing match in the likely list, set that.  Otherwise,
3439
         *   we have no grounds for guessing.  
3440
         */
3441
        if (topic != nil)
3442
        {
3443
            if (topic.ofKind(Thing))
3444
            {
3445
                /* we have a Thing - use it as the antecedent */
3446
                fromActor.setPronounObj(topic);
3447
            }
3448
            else if (topic.ofKind(ResolvedTopic))
3449
            {
3450
                local lst;
3451
                
3452
                /* 
3453
                 *   if there's only one Thing in scope, or only one Thing
3454
                 *   in the 'likely' list, use it 
3455
                 */
3456
                lst = topic.inScopeList.subset({x: x.ofKind(Thing)});
3457
                if (lst.length() == 0)
3458
                    lst = topic.likelyList.subset({x: x.ofKind(Thing)});
3459
3460
                /* if we got exactly one object, it's the antecedent */
3461
                if (lst.length() == 1)
3462
                    fromActor.setPronounObj(lst[1]);
3463
            }
3464
        }
3465
    }
3466
;
3467
3468
/* 
3469
 *   Default topic entries for different uses.  We'll use a hierarchy of
3470
 *   low match scores, in descending order of specificity: 3 for
3471
 *   single-type defaults (ASK only, for example), 2 for multi-type
3472
 *   defaults (ASK/TELL), and 1 for the ANY default.  
3473
 */
3474
class DefaultCommandTopic: DefaultTopic
3475
    includeInList = [&commandTopics]
3476
    matchScore = 3
3477
;
3478
class DefaultAskTopic: DefaultTopic
3479
    includeInList = [&askTopics]
3480
    matchScore = 3
3481
;
3482
class DefaultTellTopic: DefaultTopic
3483
    includeInList = [&tellTopics]
3484
    matchScore = 3
3485
;
3486
class DefaultAskTellTopic: DefaultTopic
3487
    includeInList = [&askTopics, &tellTopics]
3488
    matchScore = 2
3489
;
3490
class DefaultGiveTopic: DefaultTopic
3491
    includeInList = [&giveTopics]
3492
    matchScore = 3
3493
;
3494
class DefaultShowTopic: DefaultTopic
3495
    includeInList = [&showTopics]
3496
    matchScore = 3
3497
;
3498
class DefaultGiveShowTopic: DefaultTopic
3499
    includeInList = [&giveTopics, &showTopics]
3500
    matchScore = 2
3501
;
3502
class DefaultAskForTopic: DefaultTopic
3503
    includeInList = [&askForTopics]
3504
    matchScore = 3
3505
;
3506
class DefaultAnyTopic: DefaultTopic
3507
    includeInList = [&askTopics, &tellTopics, &showTopics, &giveTopics,
3508
                     &askForTopics, &miscTopics, &commandTopics]
3509
3510
    /* 
3511
     *   exclude these from actor-initiated hellos & goodbyes - those
3512
     *   should only match topics explicitly 
3513
     */
3514
    excludeMatch = [actorHelloTopicObj, actorByeTopicObj]
3515
    matchScore = 1
3516
;
3517
3518
3519
/*
3520
 *   A "special" topic.  This is a topic that responds to its own unique,
3521
 *   custom command input.  In other words, rather than responding to a
3522
 *   normal command like ASK ABOUT or SHOW TO, we'll respond to a command
3523
 *   for which we define our own syntax.  Our special syntax doesn't have
3524
 *   to follow any of the ordinary parsing conventions, because whenever
3525
 *   our ConvNode is active, we get a shot at parsing player input before
3526
 *   the regular parser gets to see it.
3527
 *   
3528
 *   A special topic MUST be part of a ConvNode, because these are
3529
 *   inherently meaningful only in context.  A special topic is active
3530
 *   only when its conversation node is active.
3531
 *   
3532
 *   Special topics are automatically Suggested Topics as well as Topic
3533
 *   Entries.  Because special topics use their own custom grammar, it's
3534
 *   unreasonable to expect a player to guess at the custom grammar, so we
3535
 *   should always provide a topic inventory suggestion for every special
3536
 *   topic.  
3537
 */
3538
class SpecialTopic: TopicEntry, SuggestedTopicTree
3539
    /*
3540
     *   Our keyword list.  Each special topic instance must define a list
3541
     *   of strings giving the keywords we match.  The special topic will
3542
     *   match user input if the user input consists exclusively of words
3543
     *   from this keyword list.  The user input doesn't have to include
3544
     *   all of the words defined here, but all of the words in the user's
3545
     *   input have to appear here to match.
3546
     *   
3547
     *   Alternatively, an instance can specifically define its own custom
3548
     *   regular expression pattern instead of using the keyword list; the
3549
     *   regular expression allows the instance to include punctuation in
3550
     *   the syntax, or apply more restrictive criteria than simply
3551
     *   matching the keywords.  
3552
     */
3553
    keywordList = []
3554
3555
    /*
3556
     *   Initialize the special topic.  This runs during
3557
     *   pre-initialization, to give us a chance to do pre-game set-up.
3558
     *   
3559
     *   This routine adds the topic's keywords to the global dictionary,
3560
     *   under the 'special' token type.  Since a special topic's keywords
3561
     *   are accepted when the special topic is active, it would be wrong
3562
     *   for the parser to claim that the words are unknown when the
3563
     *   special topic isn't active.  By adding the keywords to the
3564
     *   dictionary, we let the parser know that they're valid words, so
3565
     *   that it won't claim that they're unknown.  
3566
     */
3567
    initializeSpecialTopic()
3568
    {
3569
        /* add each keyword */
3570
        foreach (local cur in keywordList)
3571
        {
3572
            /* 
3573
             *   Add the keyword.  Since we don't actually need the
3574
             *   word-to-object association that the dictionary stores,
3575
             *   simply associate the word with the SpecialTopic class
3576
             *   rather than with this particular special topic instance.
3577
             *   The dictionary only stores a given word-obj-prop
3578
             *   association once, even if it's entered repeatedly, so
3579
             *   tying all of the special topic keywords to the
3580
             *   SpecialTopic class ensures that we won't store redundant
3581
             *   entries if the same keyword is used in multiple special
3582
             *   topics.  
3583
             */
3584
            cmdDict.addWord(SpecialTopic, cur, &specialTopicWord);
3585
        }
3586
    }
3587
3588
    /* 
3589
     *   our regular expression pattern - we'll build this automatically
3590
     *   from the keyword list if this isn't otherwise defined 
3591
     */
3592
    matchPat = nil
3593
    
3594
    /* our suggestion (topic inventory) base name */
3595
    name = ''
3596
3597
    /* 
3598
     *   our suggestion (topic inventory) full name is usually the same as
3599
     *   the base name; special topics usually aren't grouped in topic
3600
     *   suggestion listings, since each topic usually has its own unique,
3601
     *   custom syntax 
3602
     */
3603
    fullName = (name)
3604
3605
    /* on being suggested, update the special topic history */
3606
    noteSuggestion() { specialTopicHistory.noteListing(self); }
3607
3608
    /* include in the specialTopics list of our parent topic database */
3609
    includeInList = [&specialTopics]
3610
3611
    /* 
3612
     *   By default, don't limit the number of times we'll suggest this
3613
     *   topic.  Since a special topic is valid only in a particular
3614
     *   ConvNode context, we normally want all of the topics in that
3615
     *   context to be available, even if they've been used before. 
3616
     */
3617
    timesToSuggest = nil
3618
3619
    /* check for a match */
3620
    matchTopic(fromActor, topic)
3621
    {
3622
        /* 
3623
         *   We match if and only if we're the current active topic for
3624
         *   our conversation node, as designated during our pre-parsing.
3625
         *   Because we're activated exclusively by our special syntax,
3626
         *   the only way we can ever match is by matching our special
3627
         *   syntax in pre-parsing; when that happens, the pre-parser
3628
         *   notes the matching SpecialTopic and sends a pseudo-command to
3629
         *   the parser to let it know to invoke the special topic's
3630
         *   response.  We take this circuitous route to showing the
3631
         *   response because we do our actual matching in the pre-parse
3632
         *   step, but we want to do the actual command processing
3633
         *   normally; we can only accomplish both needs using this
3634
         *   two-step process, with the two steps tied together via our
3635
         *   memory of the topic selected in pre-parse.  
3636
         */
3637
        if (getConvNode().activeSpecialTopic == self)
3638
            return matchScore;
3639
        else
3640
            return nil;
3641
    }
3642
3643
    /* 
3644
     *   a special topic is always matchable, since we match on literal
3645
     *   text 
3646
     */
3647
    isMatchPossible(actor, scopeList) { return true; }
3648
3649
    /*
3650
     *   Match a string during pre-parsing.  By default, we'll match the
3651
     *   string if all of its words (as defined by the regular expression
3652
     *   parser) match our keywords.  
3653
     */
3654
    matchPreParse(str, procStr)
3655
    {
3656
        /* build the regular expression pattern if there isn't one */
3657
        if (matchPat == nil)
3658
        {
3659
            local pat;
3660
3661
            /* start with the base pattern string */
3662
            pat = '<nocase><space>*(%<';
3663
3664
            /* add the keywords */
3665
            for (local i = 1, local len = keywordList.length() ;
3666
                 i <= len ; ++i)
3667
            {
3668
                /* add this keyword to the pattern */
3669
                pat += keywordList[i];
3670
3671
                /* add the separator or terminator, as appropriate */
3672
                if (i == len)
3673
                    pat += '%><space>*)+';
3674
                else
3675
                    pat += '%><space>*|%<';
3676
            }
3677
3678
            /* create the pattern object */
3679
            matchPat = new RexPattern(pat);
3680
        }
3681
3682
        /* we have a match if the pattern matches the processed input */
3683
        return rexMatch(matchPat, procStr) == procStr.length();
3684
    }
3685
3686
    /* find our enclosing ConvNode object */
3687
    getConvNode()
3688
    {
3689
        /* scan up the containment tree for a ConvNode */
3690
        for (local loc = location ; loc != nil ; loc = loc.location)
3691
        {
3692
            /* if this is a ConvNode, it's what we're looking for */
3693
            if (loc.ofKind(ConvNode))
3694
                return loc;
3695
        }
3696
3697
        /* not found */
3698
        return nil;
3699
    }
3700
;
3701
3702
/*
3703
 *   A history of special topics listed in topic inventories.  This keeps
3704
 *   track of special topics that we've recently offered, so that we can
3705
 *   provide better feedback if the player tries to use a recently-listed
3706
 *   special topic after it's gone out of context.
3707
 *   
3708
 *   When the player types a command that the parser doesn't recognize, the
3709
 *   parser will check the special topic history to see if the command
3710
 *   matches a special topic that was suggested recently.  If so, we'll
3711
 *   explain that the command isn't usable right now, rather than claiming
3712
 *   that the command is completely invalid.  A player might justifiably
3713
 *   find it confusing to have the game suggest a command one minute, and
3714
 *   then claim that the very same command is invalid a minute later.
3715
 *   
3716
 *   Ideally, we'd search *every* special topic for a match each time the
3717
 *   player enters an invalid command, but that could take a long time in a
3718
 *   conversation-heavy game with a large number of special topics.  As a
3719
 *   compromise, we keep track of the last few special commands that were
3720
 *   actually suggested, so that we can scan those.  The reasoning is that
3721
 *   a player is more likely to try a recently-offered special command; the
3722
 *   player will probably eventually forget older suggestions, and in any
3723
 *   case it's much more jarring to see a "command not understood" response
3724
 *   to a suggestion that's still fresh in the player's memory.
3725
 *   
3726
 *   This is a transient object because we're interested in the special
3727
 *   topics that have been offered in the current session, irrespective of
3728
 *   things like 'undo' and 'restore'.  From the player's perspective, the
3729
 *   recency of a special topic suggestion is a function of the transcript,
3730
 *   not of the internal story timeline.  For example, if the game suggests
3731
 *   a special topic, then the player types UNDO, the player might still
3732
 *   think to try the special topic on the next turn simply because it's
3733
 *   right there on the screen a few lines up.  
3734
 */
3735
transient specialTopicHistory: object
3736
    /* 
3737
     *   Maximum number of topics to keep in our inventory.  When the
3738
     *   history exceeds this number, we'll throw away the oldest entry
3739
     *   each time we need to add a new entry - thus, we'll always have the
3740
     *   N most recent suggestions.
3741
     *   
3742
     *   This can be configured as desired.  The default setting tries to
3743
     *   strike a balance between speed and good feedback - we try to keep
3744
     *   track of enough entries that most players wouldn't think to try
3745
     *   anything that's aged out of the list, but not so many that it
3746
     *   takes a long time to scan them all.
3747
     *   
3748
     *   If you set this to nil, we won't keep a history at all, but
3749
     *   instead simply scan every special topic in the entire game when we
3750
     *   need to look for a match to an entered command - in a game with a
3751
     *   small number of special topics (on the order of, say, 30 or 40),
3752
     *   there should be no problem using this approach.  Note that this
3753
     *   changes the behavior in one important way: when there's no history
3754
     *   limit, we can topics that *haven't even been offered yet*.  In
3755
     *   some ways this is more desirable than only scanning past
3756
     *   suggestions, since it avoids weird situations where the game
3757
     *   claims that a command is unrecognized at one point, but later
3758
     *   suggests and then accepts the exact same command.  It's
3759
     *   conceivably less desirable in that it could accidentally give away
3760
     *   information to the player, by letting them know that a randomly
3761
     *   typed command will be meaningful at some point in the game - but
3762
     *   the odds of this even happening seem minuscule, and the
3763
     *   possibility that it would give away meaningful information even if
3764
     *   it did happen seems very remote.  
3765
     */
3766
    maxEntries = 20
3767
3768
    /* note that a special topic 't' is being listed in a topic inventory */
3769
    noteListing(t)
3770
    {
3771
        /* 
3772
         *   If t's already in the list, delete it from its current
3773
         *   position, so that we can add it back at the end of the list,
3774
         *   reflecting its status as the most recent entry.  
3775
         */
3776
        historyList.removeElement(t);
3777
3778
        /* 
3779
         *   if the list is already at capacity, remove the oldest entry,
3780
         *   which is the first entry in the list 
3781
         */
3782
        if (maxEntries != nil && historyList.length() >= maxEntries)
3783
            historyList.removeElementAt(1);
3784
3785
        /* add the new entry at the end of the list */
3786
        historyList.append(t);
3787
    }
3788
3789
    /*
3790
     *   Scan the history list (or, if there's no limit to the history,
3791
     *   scan all of the special topics in the entire game) for a match to
3792
     *   an unrecognized command.  Returns true if we find a match, nil if
3793
     *   not.  
3794
     */
3795
    checkHistory(toks)
3796
    {
3797
        local str, procStr;
3798
        
3799
        /* get the original and processed version of the input string */
3800
        str = cmdTokenizer.buildOrigText(toks);
3801
        procStr = specialTopicPreParser.processInputStr(str);
3802
        
3803
        /* 
3804
         *   scan each special topic in the history - or, if the history is
3805
         *   unlimited, scan every special topic 
3806
         */
3807
        if (maxEntries != nil)
3808
        {
3809
            /* scan each entry in our history list */
3810
            for (local l = historyList, local i = 1, local len = l.length() ;
3811
                 i <= len ; ++i)
3812
            {
3813
                /* check this entry */
3814
                if (l[i].matchPreParse(str, procStr))
3815
                    return true;
3816
            }
3817
        }
3818
        else
3819
        {
3820
            /* no history limit - scan every special topic in the game */
3821
            for (local o = firstObj(SpecialTopic) ; o != nil ;
3822
                 o = nextObj(o, SpecialTopic))
3823
            {
3824
                /* check this entry */
3825
                if (o.matchPreParse(str, procStr))
3826
                    return true;
3827
            }
3828
        }
3829
3830
        /* we didn't find a match */
3831
        return nil;
3832
    }
3833
3834
    /* 
3835
     *   The list of entries.  Create it when we first need it, which
3836
     *   perInstance does for us.  
3837
     */
3838
    historyList = perInstance(new transient Vector(maxEntries))
3839
;
3840
3841
/*
3842
 *   An "initiate" topic entry.  This is a rather different kind of topic
3843
 *   entry from the ones we've defined so far; an initiate topic is for
3844
 *   cases where the NPC itself wants to initiate a conversation in
3845
 *   response to something in the environment.
3846
 *   
3847
 *   One way to use initiate topics is to use the current location as the
3848
 *   topic key.  This lets the NPC say something appropriate to the current
3849
 *   room, and can be coded simply as
3850
 *   
3851
 *.     actor.initiateTopic(location);
3852
 */
3853
class InitiateTopic: ThingMatchTopic
3854
    /* include in the initiateTopics list */
3855
    includeInList = [&initiateTopics]
3856
3857
    /* 
3858
     *   since this kind of topic is triggered by internal calculations in
3859
     *   the game, and not on anything the player is doing, there's no
3860
     *   reason that our match object should be a pronoun antecedent 
3861
     */
3862
    setTopicPronouns(fromActor, topic) { }
3863
;
3864
3865
/* a catch-all default initiate topic */
3866
class DefaultInitiateTopic: DefaultTopic
3867
    includeInList = [&initiateTopics]
3868
;
3869
3870
3871
/* ------------------------------------------------------------------------ */
3872
/*
3873
 *   An ActorState represents the current state of an Actor.
3874
 *   
3875
 *   The main thing that makes actors special is that they're supposed to
3876
 *   be living, breathing people or creatures.  That substantially
3877
 *   complicates the programming of one of these objects, because in order
3878
 *   to create the appearance of animation, many things about an actor have
3879
 *   to change over time.
3880
 *   
3881
 *   The ActorState is designed to make it easier to program this
3882
 *   variability that's needed to make an actor seem life-like.  The idea
3883
 *   is to separate the parts of an actor that tend to change according to
3884
 *   what the actor is doing, moving all of those out of the Actor object
3885
 *   and into an ActorState object instead.  Each ActorState object
3886
 *   represents one state of an actor (i.e., one thing the actor can be
3887
 *   doing).  The Actor object becomes easier to program, because we've
3888
 *   reduced the Actor object to the character's constant, unchanging
3889
 *   features.  The stateful part is also easier to program, because we
3890
 *   don't have to make it conditional on anything; we simply define all of
3891
 *   the stateful parts in an ActorState, and we define separate ActorState
3892
 *   objects for the different states.
3893
 *   
3894
 *   For example, suppose we want a shopkeeper actor, whose activities
3895
 *   include waiting behind the counter, sweeping the floor, and stacking
3896
 *   cans.  We'd define one ActorState object for each of these activities.
3897
 *   When the shopkeeper switches from standing behind the counter to
3898
 *   sweeping, for example, we simply set the "curState" property in the
3899
 *   shopkeeper object so that it points to the "sweeping" state object.
3900
 *   When it's time to stack cans, we change "curState" to it points to the
3901
 *   "stacking cans" state object.  
3902
 */
3903
class ActorState: TravelMessageHandler, ActorTopicDatabase
3904
    construct(actor) { location = actor; }
3905
3906
    /*
3907
     *   Activate the state - this is called when we're about to become
3908
     *   the active state for an actor.  We do nothing by default.
3909
     */
3910
    activateState(actor, oldState) { }
3911
3912
    /* 
3913
     *   Deactivate the state - this is called when we're the active state
3914
     *   for an actor, and the actor is about to switch to a new state.
3915
     *   We do nothing by default.  
3916
     */
3917
    deactivateState(actor, newState) { }
3918
3919
    /* 
3920
     *   Is this the actor's initial state?  If so, we'll automatically
3921
     *   set the actor's curState to point to 'self' during
3922
     *   pre-initialization.  For obvious reasons, this should be set to
3923
     *   true for only one state for each actor; if multiple states are
3924
     *   all flagged as initial for the same actor, we'll pick on
3925
     *   arbitrarily as the actual initial state.  
3926
     */
3927
    isInitState = nil
3928
3929
    /*
3930
     *   Should we automatically suggest topics when the player greets our
3931
     *   actor?  By default, we show our "topic inventory" (the list of
3932
     *   currently active topics marked as "suggested").  This can be set
3933
     *   to nil to suppress this automatic suggestion list.
3934
     *   
3935
     *   Some authors might not like the idea of automatically suggesting
3936
     *   topics every time we greet a character, but nonetheless wish to
3937
     *   keep the TOPICS command as a sort of hint mechanism.  This flag
3938
     *   can be used for this purpose.  Authors who don't like suggested
3939
     *   topics at all can simply skip defining any SuggestedTopic entries,
3940
     *   in which case there will never be anything to suggest, rendering
3941
     *   this flag moot.  
3942
     */
3943
    autoSuggest = true
3944
3945
    /*
3946
     *   The 'location' is the actor that we're associated with.
3947
     *   
3948
     *   ActorState objects aren't actual simulation objects, so the
3949
     *   'location' property isn't used for containment.  For convenience,
3950
     *   though, use it to indicate which actor we're associated with; this
3951
     *   lets us use the '+' notation to define the state objects
3952
     *   associated with an actor.  
3953
     */
3954
    location = nil
3955
3956
    /* 
3957
     *   Get the actor associated with the state - this is simply the
3958
     *   'location' property.  If we're nested inside another ActorState,
3959
     *   then our actor is our enclosing ActorState's actor.  
3960
     */
3961
    getActor()
3962
    {
3963
        if (location.ofKind(ActorState))
3964
            return location.getActor();
3965
        else
3966
            return location;
3967
    }
3968
3969
    /* the owner of any topic entries within the state is just my actor */
3970
    getTopicOwner() { return getActor(); }
3971
3972
    /* initialize the actor state */
3973
    initializeActorState()
3974
    {
3975
        /* 
3976
         *   if we're the initial state for our actor, set the actor's
3977
         *   current state property to point to me 
3978
         */
3979
        if (isInitState)
3980
            getActor().setCurState(self);
3981
    }
3982
3983
    /*
3984
     *   Show the special description for the actor when the actor is
3985
     *   associated with this state.  By default, we use the actor's
3986
     *   actorHereDesc message, which usually shows a generic message
3987
     *   (something like "Bob is here" or "Bob is sitting on the chair") to
3988
     *   indicate that the actor is present.
3989
     *   
3990
     *   States representing scripted activities should override these to
3991
     *   indicate what the actor is doing: "Bob is sweeping the floor," for
3992
     *   example.
3993
     */
3994
    specialDesc() { getActor().actorHereDesc; }
3995
3996
    /* show the special description for the actor at a distance */
3997
    distantSpecialDesc() { getActor().actorThereDesc; }
3998
3999
    /* show the special description for the actor in a remote location */
4000
    remoteSpecialDesc(actor) { getActor().actorThereDesc; }
4001
4002
    /*
4003
     *   The list group(s) for the special description.  By default, if
4004
     *   our specialDesc isn't overridden, we'll keep this in sync with
4005
     *   the specialDesc by returning our actor's actorListWith.  And if
4006
     *   specialDesc *is* overridden, we'll just return an empty list to
4007
     *   indicate that we're not part of any list group.  If you want to
4008
     *   provide your own listing group special to the state, simply
4009
     *   override this and speicfy the custom list group.  
4010
     */
4011
    specialDescListWith()
4012
    {
4013
        /* 
4014
         *   if specialDesc is inherited from ActorState, then use the
4015
         *   default handling from the actor; otherwise, use no grouping at
4016
         *   all by default 
4017
         */
4018
        if (!overrides(self, ActorState, &specialDesc))
4019
            return getActor().actorListWith;
4020
        else
4021
            return [];
4022
    }
4023
4024
    /* show the special description when we appear in a contents listing */
4025
    showSpecialDescInContents(actor, cont)
4026
    {
4027
        /* by default, just show our posture in our container */
4028
        getActor().listActorPosture(actor);
4029
    }
4030
4031
    /* 
4032
     *   Our "state" description.  This shows information on what the actor
4033
     *   is *currently* doing; we display this after the static part of the
4034
     *   actor's description on EXAMINE <ACTOR>.  By default, we add
4035
     *   nothing here, but state objects that represent scripted activies
4036
     *   should override this to describe their scripted activities.
4037
     */
4038
    stateDesc = ""
4039
4040
    /*
4041
     *   Should we obey an action?  If so, returns true; if not, displays
4042
     *   an appropriate response and returns nil.  This will only be
4043
     *   called when the issuing actor is different from our actor, since
4044
     *   a command to oneself is implicitly always obeyed.
4045
     */
4046
    obeyCommand(issuingActor, action)
4047
    {
4048
        /* 
4049
         *   By default, we ignore all orders.  We do need to generate a
4050
         *   response, though, so for this purpose, treat the order as a
4051
         *   conversational action, with the 'action' object as the topic.
4052
         */
4053
        handleConversation(issuingActor, action, commandConvType);
4054
4055
        /* indicate that the order is refused */
4056
        return nil;
4057
    }
4058
4059
    /*
4060
     *   Suggest topics for the given actor to talk to us about.  This is
4061
     *   called when the given actor enters a TOPICS command (in which
4062
     *   case 'explicit' will be true) or enters a conversation with us
4063
     *   via TALK TO or the like (in which case 'explicit' will be nil).
4064
     */
4065
    suggestTopicsFor(actor, explicit)
4066
    {
4067
        /* 
4068
         *   if this is not an explicit TOPICS request, and we're not in
4069
         *   "auto suggest" mode, don't show anything - we don't want any
4070
         *   automatic suggestions in this mode  
4071
         */
4072
        if (!explicit && !autoSuggest)
4073
            return;
4074
4075
        /* 
4076
         *   show a paragraph break, in case we're being tacked on to
4077
         *   another report; but make it cosmetic, so that this by itself
4078
         *   doesn't suppress a default report, in case we don't end up
4079
         *   displaying any topics 
4080
         */
4081
        cosmeticSpacingReport('<.p>');
4082
4083
        /* show our suggestion list */
4084
        showSuggestedTopicList(getSuggestedTopicList(),
4085
                               actor, getActor(), explicit);
4086
    }
4087
4088
    /*
4089
     *   Get our suggested topic list.  The suggested topic list consists
4090
     *   of the union of the current ConvNode's suggestion list, the
4091
     *   ActorState list, and the Actor's suggestion list.  In each case,
4092
     *   the suggestion list is the list of all SuggestedTopic objects at
4093
     *   each database level.
4094
     *   
4095
     *   The suggestions are arranged in a hierarchy, and each hierarchy
4096
     *   level can prevent suggestions from a lower level from being
4097
     *   included.  The top level of the hierarchy is the ConvNode; the
4098
     *   next level is the ActorState; and the last level is the Actor.
4099
     *   Suggestions are limited at each level with the 'limitSuggestions'
4100
     *   property: if true, suggestions from lower levels are not included.
4101
     */
4102
    getSuggestedTopicList()
4103
    {
4104
        local v = new Vector(16);
4105
        local node;
4106
        local lst;
4107
4108
        /* add the actor's current conversation node topics */
4109
        if ((node = getActor().curConvNode) != nil)
4110
        {
4111
            /* if there are any suggested topics in the node, include them */
4112
            if ((lst = node.suggestedTopics) != nil)
4113
                v.appendAll(lst);
4114
4115
            /* 
4116
             *   if this ConvNode is marked as limiting suggestions to
4117
             *   those defined within the node, return what we have
4118
             *   without adding anything from the broader context 
4119
             */
4120
            if (node.limitSuggestions)
4121
                return v;
4122
        }
4123
4124
        /* add our own topics */
4125
        if ((lst = stateSuggestedTopics) != nil)
4126
            v.appendAll(lst);
4127
4128
        /* 
4129
         *   if the ActorState is limiting suggestions, don't include any
4130
         *   suggestions from the broader context (i.e., from the Actor
4131
         *   itself) 
4132
         */
4133
        if (limitSuggestions)
4134
            return v;
4135
4136
        /* if our actor has its own list, add those as well */
4137
        if ((lst = getActor().suggestedTopics) != nil)
4138
            v.appendAll(lst);
4139
4140
        /* return the combined list */
4141
        return v;
4142
    }
4143
4144
    /* 
4145
     *   get the topic suggestions for this state - by default, we just
4146
     *   return our own suggestedTopics list 
4147
     */
4148
    stateSuggestedTopics = (suggestedTopics)
4149
4150
    /*
4151
     *   Get my implied in-conversation state.  This is used when our actor
4152
     *   initiates a conversation without specifying a particular
4153
     *   conversation state to enter (i.e., actor.initiateConversation() is
4154
     *   called with 'state' set to nil).  By default, we don't have an
4155
     *   implied conversation state, so we just return 'self' to indicate
4156
     *   that we want to stay in the current state.  States that are
4157
     *   coupled with separate in-conversation states, such as
4158
     *   ConversationReadyState, should return their associated
4159
     *   conversation states here.  
4160
     */
4161
    getImpliedConvState = (self)
4162
4163
    /*
4164
     *   General conversation handler.  This can be used to process most
4165
     *   conversational commands - ASK, TELL, GIVE, SHOW, etc.  The
4166
     *   standard sequence of processing is as follows:
4167
     *   
4168
     *   - If our actor has a non-nil current conversation node (ConvNode)
4169
     *   object, and the ConvNode wants to handle the event, let the
4170
     *   ConvNode handle it.
4171
     *   
4172
     *   - Otherwise, check our own topic database to see if we can find a
4173
     *   TopicEntry that matches the topic; if we can find one, let the
4174
     *   TopicEntry handle it.
4175
     *   
4176
     *   - Otherwise, let the actor handle it.
4177
     *   
4178
     *   'otherActor' is the actor who originated the conversation command
4179
     *   (usually the player character). 'topic' is the subject being
4180
     *   discussed (the indirect object of ASK ABOUT, for example).
4181
     *   convType' is a ConvType describing the type of conversational
4182
     *   action we're performing.  
4183
     */
4184
    handleConversation(otherActor, topic, convType)
4185
    {
4186
        local actor = getActor();
4187
        local hasDefault;
4188
        local node;
4189
        local path;
4190
4191
        /* determine if I have a default response handler */
4192
        hasDefault = propDefined(convType.defaultResponseProp);
4193
4194
        /*
4195
         *   Figure the database search path for looking up the topics.
4196
         *   We'll start in the ConvNode database, then continue to the
4197
         *   ActorState database, then finally to the Actor database.
4198
         *   However, we won't reach the Actor database if there's a
4199
         *   default response handler in the state, because if we fail to
4200
         *   find it at the state, we'll take the default.
4201
         *   
4202
         *   Since the path we need to provide at each point is the
4203
         *   *remaining* path, don't bother including the ConvNode, since
4204
         *   we'd just have to take it right back out to get the remaining
4205
         *   path after the ConvNode.  
4206
         */
4207
        path = [self];
4208
        if (!hasDefault)
4209
            path += actor;
4210
4211
        /* 
4212
         *   If our actor has a current conversation node, check to see if
4213
         *   the conversation node wants to handle it.  If not, check our
4214
         *   own topic database, then the actor's.  
4215
         */
4216
        if ((node = actor.curConvNode) == nil
4217
            || !node.handleConversation(otherActor, topic, convType, path))
4218
        {
4219
            /* get the remaining database search path */
4220
            path = path.sublist(2);
4221
            
4222
            /* 
4223
             *   Either we don't have a ConvNode, or the ConvNode isn't
4224
             *   interested in handling the operation.  Check to see if we
4225
             *   can handle it through our own topic database.  
4226
             */
4227
            if (!handleTopic(otherActor, topic, convType, path))
4228
            {
4229
                /*
4230
                 *   We couldn't find anything in our topic database that's
4231
                 *   interested in handling it.  Check to see if the state
4232
                 *   object defines the default response handler method,
4233
                 *   and use that as the response if so. 
4234
                 */
4235
                if (hasDefault)
4236
                {
4237
                    /* 
4238
                     *   the state object (i.e., self) does define the
4239
                     *   default response method, so invoke that 
4240
                     */
4241
                    convType.defaultResponse(self, otherActor, topic);
4242
                }
4243
                else
4244
                {
4245
                    /* 
4246
                     *   We don't have a topic database entry and we don't
4247
                     *   have our own definition of the default response
4248
                     *   handler.  All that remains is to let our actor
4249
                     *   handle it.  
4250
                     */
4251
                    actor.handleConversation(otherActor, topic, convType);
4252
                }
4253
            }
4254
        }
4255
4256
        /* whatever happened, run the appropriate after-response handling */
4257
        convType.afterResponse(actor, otherActor);
4258
    }
4259
4260
    /*
4261
     *   Receive notification that a TopicEntry is being used (via its
4262
     *   handleTopic method) to respond to a command.  The TopicEntry will
4263
     *   call this before it shows its message or takes any other action.
4264
     *   By default, we do nothing.  
4265
     */
4266
    notifyTopicResponse(fromActor, entry) { }
4267
4268
    /* 
4269
     *   Handle a before-action notification for our actor.  By default,
4270
     *   we do nothing.  
4271
     */
4272
    beforeAction()
4273
    {
4274
        /* do nothing by default */
4275
    }
4276
4277
    /* handle an after-action notification for our actor */
4278
    afterAction()
4279
    {
4280
    }
4281
4282
    /* handle a before-travel notification */
4283
    beforeTravel(traveler, connector)
4284
    {
4285
        local other = getActor().getCurrentInterlocutor();
4286
        
4287
        /* 
4288
         *   if our conversational partner is departing, break off the
4289
         *   conversation
4290
         */
4291
        if (connector != nil
4292
            && other != nil
4293
            && traveler.isActorTraveling(other))
4294
        {
4295
            /* end the conversation */
4296
            if (!endConversation(gActor, endConvTravel))
4297
            {
4298
                /* 
4299
                 *   they don't want to allow the conversation to end, so
4300
                 *   abort the travel action 
4301
                 */
4302
                exit;
4303
            }
4304
        }
4305
    }
4306
4307
    /* handle an after-travel notification */
4308
    afterTravel(traveler, connector)
4309
    {
4310
    }
4311
4312
    /*
4313
     *   End the current conversation.  'reason' indicates why we're
4314
     *   leaving the conversation - this is one of the endConvXxx enums
4315
     *   defined in adv3.h.  beforeTravel() calls this automatically when
4316
     *   the other party is trying to depart, and they're talking to us.
4317
     *   
4318
     *   This returns true if we wish to allow the conversation to end,
4319
     *   nil if not.  
4320
     */
4321
    endConversation(actor, reason)
4322
    {
4323
        local ourActor = getActor();
4324
        local node;
4325
4326
        /* tell the current ConvNode about it */
4327
        if ((node = ourActor.curConvNode) != nil)
4328
        {
4329
            local ret;
4330
4331
            /* the can-end call might show a response, so set our actor */
4332
            conversationManager.beginResponse(ourActor);
4333
4334
            /* ask the node if it's okay to end the conversation */
4335
            ret = node.canEndConversation(actor, reason);
4336
4337
            /* 
4338
             *   If the result is blockEndConv, it means that the actor
4339
             *   said something to force the conversation to keep going.
4340
             *   Make a note that the other actor already said something on
4341
             *   this turn so that we don't generate another scripted
4342
             *   message later, and flag this as preventing the
4343
             *   conversation ending. 
4344
             */
4345
            if (ret == blockEndConv)
4346
            {
4347
                /* flag that the other actor said something this turn */
4348
                ourActor.noteConvAction(actor);
4349
4350
                /* we're unable to end the conversation now */
4351
                ret = nil;
4352
            }
4353
4354
            /* end the response, leaving the node unchanged by default */
4355
            conversationManager.finishResponse(
4356
                ourActor, ourActor.curConvNode);
4357
4358
            /* 
4359
             *   if the node said no, tell the caller we can't end the
4360
             *   conversation right now 
4361
             */
4362
            if (!ret)
4363
                return nil;
4364
            
4365
            /* tell the node we are indeed ending the conversation */
4366
            node.endConversation(actor, reason);
4367
        }
4368
4369
        /* forget any conversation tree position */
4370
        ourActor.setConvNodeReason(nil, 'endConversation');
4371
4372
        /* indicate that we are allowing the conversation to end */
4373
        return true;
4374
    }
4375
4376
    /*
4377
     *   Take a turn.  This is called when it's the actor's turn and
4378
     *   there's not something else the actor needs to be doing (such as
4379
     *   following another actor, or carrying out a command in the actor's
4380
     *   pending command queue).
4381
     *   
4382
     *   By default, we perform several steps automatically.
4383
     *   
4384
     *   First, we check to see if the actor is in a ConvNode.  If so, the
4385
     *   ConvNode takes precedence.  If we haven't been addressed already
4386
     *   in conversation on this turn, we'll let the ConvNode perform its
4387
     *   "continuation," which lets the NPC advance the conversation of its
4388
     *   own volition.  In any case, if we have a current ConvNode, we're
4389
     *   done with the turn, since we assume the actor will want to proceed
4390
     *   with the conversation before pursuing its agenda or performing a
4391
     *   background action.
4392
     *   
4393
     *   Second, assuming there's no active ConvNode, we check for an
4394
     *   "agenda" item that's ready to execute.  If we find one, we execute
4395
     *   it, and we're done.  The agenda item takes precedence over any
4396
     *   other scripting we might have.
4397
     *   
4398
     *   Finally, if we also inherit from Script, and we didn't find an
4399
     *   active ConvNode or an agenda item that was ready to execute, we
4400
     *   invoke our doScript() method.  This makes it especially easy to
4401
     *   define random background messages for the actor - just add an
4402
     *   EventList class (ShuffledEventList is usually the right one) to
4403
     *   the state's superclass list, and define a list of background
4404
     *   message strings.  
4405
     */
4406
    takeTurn()
4407
    {
4408
        local actor = getActor();
4409
4410
        /* 
4411
         *   Check to see if we want to continue a conversation.  If so,
4412
         *   and we haven't already conversed this turn, try the
4413
         *   continuing conversation.  If that displays anything, consider
4414
         *   the turn done.
4415
         *   
4416
         *   Otherwise, try executing an agenda item.  If we do, consider
4417
         *   the turn done.
4418
         *   
4419
         *   Otherwise, if we're of class Script, execute our scripted
4420
         *   action.  
4421
         */
4422
        if (actor.curConvNode != nil
4423
            && !actor.conversedThisTurn()
4424
            && actor.curConvNode.npcContinueConversation())
4425
        {
4426
            /* 
4427
             *   we displayed an NPC-motivated conversation continuation,
4428
             *   so we're done with this turn 
4429
             */
4430
        }
4431
        else if (actor.executeAgenda())
4432
        {
4433
            /* we executed an agenda item, so we need do nothing more */
4434
        }
4435
        else if (ofKind(Script))
4436
        {
4437
            /* we're a Script, so invoke our scripted action */
4438
            doScript();
4439
        }
4440
    }
4441
4442
    /*
4443
     *   Receive notification that we just followed another actor as part
4444
     *   of our programmed following behavior (in other words, due to our
4445
     *   'followingActor' property, not due to an explicit FOLLOW command
4446
     *   directed to us).  'success' is true if we ended up in the actor's
4447
     *   location, nil if not.
4448
     *   
4449
     *   This can be used to update the actor's state after a 'follow'
4450
     *   operation occurs; for example, if the actor's state depends on
4451
     *   the actor's location, this can update the state accordingly.  We
4452
     *   don't do anything by default.  
4453
     */
4454
    justFollowed(success)
4455
    {
4456
        /* do nothing by default */
4457
    }
4458
4459
    /*
4460
     *   Our group-travel arrival description.  By default, when we perform
4461
     *   an accompanying travel with another actor as the lead actor, the
4462
     *   accompanying travel state will display this message instead of our
4463
     *   specialDesc when the lead actor first arrives in the new location.
4464
     *   We'll just display our own specialDesc by default, but this should
4465
     *   usually be overridden to say something specific to the group
4466
     *   travel arrival.  The actual message is entirely dependent on the
4467
     *   nature of the group travel, which is why we don't provide a
4468
     *   special message by default.
4469
     *   
4470
     *   For scripted behavior, it's sometimes better to use arrivingTurn()
4471
     *   rather than this method to describe the behavior.
4472
     *   arrivingWithDesc() is called as part of the room description, so
4473
     *   it's best for any message shown here to fit well into the usual
4474
     *   room description format.  For more complex transitions into the
4475
     *   new room state, arrivingTurn() is sometimes more appropriate,
4476
     *   since it runs like a daemon, after the arrival (and thus the new
4477
     *   room description) is completed.  
4478
     */
4479
    arrivingWithDesc() { specialDesc(); }
4480
4481
    /*
4482
     *   Perform any special action on a group-travel arrival.  When group
4483
     *   travel is performed using the AccompanyingInTravelState class,
4484
     *   this is essentially called in lieu of the regular takeTurn()
4485
     *   method on the state that is coming into effect after the group
4486
     *   travel.  (Not really, but effectively: the accompanying travel
4487
     *   state will still be in effect, so its takeTurn() method is what's
4488
     *   really called, but that method will call this method explicitly.)
4489
     *   By default, we do nothing.  Since this runs on our turn, it's a
4490
     *   good place to put any scripted behavior we perform on arriving at
4491
     *   our new destination after the group travel.  
4492
     */
4493
    arrivingTurn() { }
4494
4495
    /* 
4496
     *   For our TravelMessageHandler implementation, the nominal traveler
4497
     *   is our actor.  Note that this is all we need to implement for
4498
     *   travel message handling, since we simply inherit the default
4499
     *   handling for all of the arrival/departure messages.  
4500
     */
4501
    getNominalTraveler() { return getActor(); }
4502
;
4503
4504
/*
4505
 *   A "ready for conversation" state.  This can be used as the base class
4506
 *   for actor states when the actor is receptive to conversation, and we
4507
 *   want to have the sense of a conversational context.  The key feature
4508
 *   that this class provides is the ability to provide messages when
4509
 *   engaging and disengaging the conversation.
4510
 *   
4511
 *   Note that this state is NOT required for conversation, since the basic
4512
 *   ActorState object accepts conversational commands like ASK, TELL,
4513
 *   GIVE, and TAKE.  The special feature of the "conversation ready" state
4514
 *   is that we explicitly move the actor to a separate state when
4515
 *   conversation begins.  This is especially appropriate for states in
4516
 *   which the NPC is actively carrying on some other activity; the
4517
 *   conversation should interrupt those states, so that the actor stops
4518
 *   the other activity and gives us its full attention.
4519
 *   
4520
 *   This type of state can be associated with its in-conversation state
4521
 *   object in one of two ways.  First, the inConvState property can be
4522
 *   explicitly set to point to the in-conversation state object.  Second,
4523
 *   this object can be nested inside its in-conversation state object via
4524
 *   the 'location' property (so you can use the '+' syntax to put this
4525
 *   object inside its in-conversation state object).  The 'ready' object
4526
 *   goes inside the 'conversing' object because a single 'conversing'
4527
 *   object can frequently be shared among several 'ready' states.  
4528
 */
4529
class ConversationReadyState: ActorState
4530
    /*
4531
     *   The associated in-conversation state.  This should be set to an
4532
     *   InConversationState object that controls the actor's behavior
4533
     *   while carrying on a conversation.  Note that the library will
4534
     *   automatically set this if the instance is nested (via its
4535
     *   'location' property) inside an InConversationState object.  
4536
     */
4537
    inConvState = nil
4538
4539
    /* my implied conversational state is my in-conversation state */
4540
    getImpliedConvState = (inConvState)
4541
4542
    /*
4543
     *   Show our greeting message.  If 'explicit' is true, it means that
4544
     *   the player character is greeting us through an explicit greeting
4545
     *   command, such as HELLO or TALK TO.  Otherwise, the greeting is
4546
     *   implied by some other conversational action, such a ASK ABOUT or
4547
     *   SHOW TO.  We do nothing by default; this should be overridden in
4548
     *   most cases to show some sort of exchange of pleasantries -
4549
     *   something like this:
4550
     *   
4551
     *.  >bob, hello
4552
     *.  "Hi, there," you say.
4553
     *   
4554
     *   Bob looks up over his newspaper.  "Oh, hello," he says, putting
4555
     *   down the paper.  "What can I do for you?"
4556
     *   
4557
     *   Note that games shouldn't usually override this method.  Instead,
4558
     *   you should simply create a HelloTopic entry and put it inside the
4559
     *   state object; we'll find the HelloTopic and show its message as
4560
     *   our greeting.
4561
     *   
4562
     *   If you want to distinguish between explicit and implicit
4563
     *   greetings, you can create an ImpHelloTopic entry for implied
4564
     *   greetings (i.e., the kind of greeting that occurs automatically
4565
     *   when the player jumps right into a conversation with our actor
4566
     *   using ASK ABOUT or the like, without explicitly saying HELLO
4567
     *   first).  The regular HelloTopic will handle explicit greetings,
4568
     *   and the ImpHelloTopic will handle the implied kind.  
4569
     */
4570
    showGreetingMsg(actor, explicit)
4571
    {
4572
        /* look for a HelloTopic in our topic database */
4573
        if (handleTopic(actor, explicit ? helloTopicObj : impHelloTopicObj,
4574
                        helloConvType, nil))
4575
            "<.p>";
4576
    }
4577
4578
    /*
4579
     *   Enter this state from a conversation.  This should show any
4580
     *   message we want to display when we're ending a conversation and
4581
     *   switching from the conversation to this state.  'reason' is the
4582
     *   endConvXxx enum indicating what triggered the termination of the
4583
     *   conversation.  'oldNode' is the ConvNode we were in just before we
4584
     *   initiated the termination - we need this information because we
4585
     *   want to look in the ConvNode for a Bye topic message to display,
4586
     *   but we can't just look in the actor for the node because it will
4587
     *   already have been cleared out by the time we get here.
4588
     *   
4589
     *   Games shouldn't normally override this method.  Instead, simply
4590
     *   create a ByeTopic entry and put it inside the state object; we'll
4591
     *   find the ByeTopic and show its message for the goodbye.
4592
     *   
4593
     *   If you want to distinguish between different types of goodbyes,
4594
     *   you can create an ImpByeTopic for any implied goodbye (i.e., the
4595
     *   kind where the other actor just walks away, or where we get bored
4596
     *   of the other actor ignoring us).  You can also further
4597
     *   differentiate by creating BoredByeTopic and/or LeaveByeTopic
4598
     *   objects to handle just those cases.  The regular ByeTopic will
4599
     *   handle explicit GOODBYE commands, and the others (ImpByeTopic,
4600
     *   BoredByeTopic, LeaveByeTopic) will handle the implied kinds.  
4601
     */
4602
    enterFromConversation(actor, reason, oldNode)
4603
    {
4604
        local topic;
4605
        local reasonMap = [endConvBye, byeTopicObj,
4606
                           endConvTravel, leaveByeTopicObj,
4607
                           endConvBoredom, boredByeTopicObj,
4608
                           endConvActor, actorByeTopicObj];
4609
        
4610
        /* figure out which topic object we need, based on the reason code */
4611
        topic = reasonMap[reasonMap.indexOf(reason) + 1];
4612
        
4613
        /* 
4614
         *   Look for a ByeTopic in the ConvNode; failing that, try our own
4615
         *   database. 
4616
         */
4617
        if (oldNode == nil
4618
            || !oldNode.handleConversation(actor, topic, byeConvType, nil))
4619
        {
4620
            /* there's no node handler; try our own database */
4621
            handleTopic(actor, topic, byeConvType, nil);
4622
        }
4623
    }
4624
4625
    /* handle a conversational action directed to our actor */
4626
    handleConversation(otherActor, topic, convType)
4627
    {
4628
        /* 
4629
         *   If this is a greeting, handle it ourselves.  Otherwise, pass
4630
         *   it along to our associated in-conversation state.  
4631
         */
4632
        if (convType == helloConvType)
4633
        {
4634
            /* 
4635
             *   Switch to our associated in-conversation state and show a
4636
             *   greeting.  Since we're explicitly entering the
4637
             *   conversation, we have no topic entry.  
4638
             */
4639
            enterConversation(otherActor, nil);
4640
4641
            /* show or schedule a topic inventory, as appropriate */
4642
            conversationManager.showOrScheduleTopicInventory(
4643
                getActor(), otherActor);
4644
        }
4645
        else
4646
        {
4647
            /* 
4648
             *   it's not a greeting, so pass it to our in-conversation
4649
             *   state for handling
4650
             */
4651
            inConvState.handleConversation(otherActor, topic, convType);
4652
        }
4653
    }
4654
4655
    /*
4656
     *   Initiate conversation based on the given simulation object.  This
4657
     *   is an internal method that isn't usually called directly from game
4658
     *   code; game code usually calls the Actor's initiateTopic(), which
4659
     *   calls this routine to check for a topic that's part of the state
4660
     *   object. 
4661
     */
4662
    initiateTopic(obj)
4663
    {
4664
        /* defer to our in-conversation state */
4665
        return inConvState.initiateTopic(obj);
4666
    }
4667
4668
    /*
4669
     *   Receive notification that a TopicEntry is being used (via its
4670
     *   handleTopic method) to respond to a command.  If the TopicEntry is
4671
     *   conversational, automatically enter our in-conversation state.  
4672
     */
4673
    notifyTopicResponse(fromActor, entry)
4674
    {
4675
        if (entry.isConversational)
4676
            enterConversation(fromActor, entry);
4677
    }
4678
4679
    /* 
4680
     *   Enter a conversation with the given actor, either explicitly (via
4681
     *   HELLO or TALK TO) or implicitly (by directly asking a question,
4682
     *   etc).  'entry' gives the TopicEntry that's triggering the implicit
4683
     *   conversation entry; if this is nil, it means that we're being
4684
     *   triggered explicitly.  
4685
     */
4686
    enterConversation(actor, entry)
4687
    {
4688
        local myActor = getActor();
4689
        local explicit = (entry == nil);
4690
        
4691
        /* if the actor can't talk to us, we can't enter the conversation */
4692
        if (!actor.canTalkTo(myActor))
4693
        {
4694
            /* tell them we can't talk now */
4695
            reportFailure(&objCannotHearActorMsg, myActor);
4696
            
4697
            /* terminate the command */
4698
            exit;
4699
        }
4700
4701
        /* 
4702
         *   Show our greeting, if desired.  We show a greeting if we're
4703
         *   being invoked explicitly (that is, there's no TopicEntry), or
4704
         *   if we're being invoked explicitly and the TopicEntry implies a
4705
         *   greeting.  
4706
         */
4707
        if (explicit || entry.impliesGreeting)
4708
            showGreetingMsg(actor, explicit);
4709
4710
        /* activate the in-conversation state */
4711
        myActor.setCurState(inConvState);
4712
    }
4713
4714
    /*
4715
     *   Get this state's suggested topic list.  ConversationReady states
4716
     *   shouldn't normally have topic entries of their own, since a
4717
     *   ConvversationReady state usually forwards conversation handling
4718
     *   to its corresponding in-conversation state.  So, simply return
4719
     *   the suggestion list from our in-conversation state object.  
4720
     */
4721
    stateSuggestedTopics = (inConvState.suggestedTopics)
4722
4723
    /* initialize the actor state object */
4724
    initializeActorState()
4725
    {
4726
        /* inherit the default handling */
4727
        inherited();
4728
4729
        /* 
4730
         *   if we're nested inside an in-conversation state object, the
4731
         *   containing in-conversation state is the one we'll use for
4732
         *   conversations 
4733
         */
4734
        if (location.ofKind(InConversationState))
4735
            inConvState = location;
4736
    }
4737
;
4738
4739
/*
4740
 *   The "in-conversation" state.  This works with ConversationReadyState
4741
 *   to handle transitions in and out of conversations.  In this state, we
4742
 *   are actively engaged in a conversation.
4743
 *   
4744
 *   Throughout this implementation, we assume that we only care about
4745
 *   conversations with a single character, specifically the player
4746
 *   character.  There's generally no good reason to fully model
4747
 *   conversations between NPC's, since that kind of NPC activity is in
4748
 *   most cases purely pre-scripted and thus requires no special state
4749
 *   tracking.  Since we generally only need to worry about tracking a
4750
 *   conversation with the player character, we don't bother with the
4751
 *   possibility that we're simultaneously in conversation with more than
4752
 *   one other character.  
4753
 */
4754
class InConversationState: ActorState
4755
    /*
4756
     *   Our attention span, in turns.  This is the number of turns that
4757
     *   we'll be willing to stay in the conversation while the other
4758
     *   character is ignoring us.  After the conversation has been idle
4759
     *   this long, we'll assume the other actor is no longer talking to
4760
     *   us, so we'll terminate the conversation ourselves.
4761
     *   
4762
     *   If the NPC's doesn't have a limited attention span, set this
4763
     *   property to nil.  This will prevent the NPC from ever disengaging
4764
     *   of its own volition.    
4765
     */
4766
    attentionSpan = 4
4767
4768
    /*
4769
     *   The state to switch to when the conversation ends.  Instances can
4770
     *   override this to select the next state.  By default, we'll return
4771
     *   to the state that we were in immediately before the conversation
4772
     *   started.  
4773
     */
4774
    nextState = (previousState)
4775
4776
    /*
4777
     *   End the current conversation.  'reason' indicates why we're
4778
     *   leaving the conversation - this is one of the endConvXxx enums
4779
     *   defined in adv3.h.
4780
     *   
4781
     *   This method is a convenience only; you aren't required to call
4782
     *   this method to end the conversation, since you can simply switch
4783
     *   to another actor state directly if you prefer.  This method's
4784
     *   main purpose is to display an appropriate message terminating the
4785
     *   conversation while switching to the new state.  If you want to
4786
     *   display your own message directly from the code that's changing
4787
     *   the state, there's no reason to call this.
4788
     *   
4789
     *   This returns true if we wish to allow the conversation to end,
4790
     *   nil if not.  
4791
     */
4792
    endConversation(actor, reason)
4793
    {
4794
        local nxt;
4795
        local myActor = getActor();
4796
4797
        /* 
4798
         *   note the current ConvNode for our actor - when we check with
4799
         *   the ConvNode to see about ending the conversation, this will
4800
         *   automatically exit the ConvNode, so we need to save this first
4801
         *   so that we can refer to it later to check for a Bye topic
4802
         */
4803
        local oldNode = myActor.curConvNode;
4804
4805
        /* 
4806
         *   Inherit the base behavior first - if it disallows the action,
4807
         *   return failure.  The inherited version will check with the
4808
         *   current ConvNode to see if has any objection.  
4809
         */
4810
        if (!inherited(actor, reason))
4811
            return nil;
4812
4813
        /* get the next state */
4814
        nxt = nextState;
4815
4816
        /* if there isn't one, stay in the actor's current state */
4817
        if (nxt == nil)
4818
            nxt = myActor.curState;
4819
        
4820
        /* 
4821
         *   If the next state is a 'conversation ready' state, tell it
4822
         *   we're entering from a conversation.  We're ending the
4823
         *   conversation explicitly only if 'reason' is endConvBye.  Pass
4824
         *   along the ConvNode we just exited (if any), so that we can
4825
         *   look for a response in the node.  
4826
         */
4827
        if (nxt.ofKind(ConversationReadyState))
4828
            nxt.enterFromConversation(actor, reason, oldNode);
4829
4830
        /* switch our actor to the next state */
4831
        myActor.setCurState(nxt);
4832
4833
        /* indicate that we are allowing the conversation to end */
4834
        return true;
4835
    }
4836
4837
    /*  handle a conversational command */
4838
    handleConversation(otherActor, topic, convType)
4839
    {
4840
        /* handle goodbyes specially */
4841
        if (convType == byeConvType)
4842
        {
4843
            /*
4844
             *   If this is an implicit goodbye, run the normal
4845
             *   conversation handling in order to display any implied
4846
             *   ByeTopic message - but capture the output in case we
4847
             *   decide not to end the conversation after all.  Only do
4848
             *   this in the case of an implicit goodbye, though - for an
4849
             *   explicit goodbye, there's no need for this as the explicit
4850
             *   BYE will do the same thing on its own.  
4851
             */
4852
            local txt = nil;
4853
            if (topic != byeTopicObj)
4854
            {
4855
                txt = mainOutputStream.captureOutput(
4856
                    {: inherited(otherActor, topic, convType) });
4857
            }
4858
4859
            /* 
4860
             *   try to end the conversation; if we won't allow it,
4861
             *   terminate the action here 
4862
             */
4863
            if (!endConversation(otherActor, endConvBye))
4864
                exit;
4865
4866
            /* show the captured ByeTopic output */
4867
            if (txt != nil)
4868
                say(txt);
4869
        }
4870
        else
4871
        {
4872
            /* use the inherited handling */
4873
            inherited(otherActor, topic, convType);
4874
        }
4875
    }
4876
4877
    /* 
4878
     *   provide a default HELLO response, if we don't have a special
4879
     *   TopicEntry for it 
4880
     */
4881
    defaultGreetingResponse(actor)
4882
    {
4883
        /* 
4884
         *   As our default response, point out that we're already at the
4885
         *   actor's service.  (This isn't an error, because the other
4886
         *   actor might not have been talking to us, even though we
4887
         *   thought we were talking to them.)  
4888
         */
4889
        gLibMessages.alreadyTalkingTo(getActor(), actor);
4890
    }
4891
4892
    takeTurn()
4893
    {
4894
        local actor = getActor();
4895
        
4896
        /* if we didn't interact this turn, increment our boredom counter */
4897
        if (!actor.conversedThisTurn())
4898
            actor.boredomCount++;
4899
4900
        /* run the inherited handling */
4901
        inherited();
4902
    }
4903
4904
    /* activate this state */
4905
    activateState(actor, oldState)
4906
    {
4907
        /*
4908
         *   If the previous state was a ConversationReadyState, or we
4909
         *   have no other state remembered, remember the previous state -
4910
         *   this is the default we'll return to at the end of the
4911
         *   conversation, if the instance doesn't specify another state.
4912
         *   
4913
         *   We don't remember prior states that aren't conv-ready states
4914
         *   to make it easier to temporarily interrupt a conversation
4915
         *   with some other state, and later return to the conversation.
4916
         *   If we remembered every prior state, then we'd return to the
4917
         *   interrupting state when the conversation ended, which is
4918
         *   usually not what's wanted.  Usually, we want to return to the
4919
         *   last conv-ready state when a conversation ends, ignoring any
4920
         *   other intermediate states that have been active since the
4921
         *   conv-ready state was last in effect.  
4922
         */
4923
        if (previousState == nil || oldState.ofKind(ConversationReadyState))
4924
            previousState = oldState;
4925
4926
        /* 
4927
         *   reset the actor's boredom counter, since we're just starting a
4928
         *   new conversation, and add our boredom agenda item to the
4929
         *   active list to monitor our boredom level 
4930
         */
4931
        actor.boredomCount = 0;
4932
        actor.addToAgenda(actor.boredomAgendaItem);
4933
4934
        /* remember the time of the last conversation command */
4935
        actor.lastConvTime = Schedulable.gameClockTime;
4936
    }
4937
4938
    /* deactivate this state */
4939
    deactivateState(actor, newState)
4940
    {
4941
        /* 
4942
         *   we're leaving the conversation state, so there's no need to
4943
         *   monitor our boredom level any longer 
4944
         */
4945
        actor.removeFromAgenda(actor.boredomAgendaItem);
4946
4947
        /* do the normal work */
4948
        inherited(actor, newState);
4949
    }
4950
4951
    /* 
4952
     *   The previous state - this is the state we were in before the
4953
     *   conversation began, and the one we'll return to by default when
4954
     *   the conversation ends.  We'll set this automatically on
4955
     *   activation.  
4956
     */
4957
    previousState = nil
4958
;
4959
4960
/*
4961
 *   A special kind of agenda item for monitoring "boredom" during a
4962
 *   conversation.  We check to see if our actor is in a conversation, and
4963
 *   the PC has been ignoring the conversation for too long; if so, our
4964
 *   actor initiates the end of the conversation, since the PC apparently
4965
 *   isn't paying any attention to us. 
4966
 */
4967
class BoredomAgendaItem: AgendaItem
4968
    /* we construct these dynamically during actor initialization */
4969
    construct(actor)
4970
    {
4971
        /* remember our actor as our location */
4972
        location = actor;
4973
    }
4974
4975
    /* 
4976
     *   we're ready to run if our actor is in an InConversationState and
4977
     *   its boredom count has reached the limit for the state 
4978
     */
4979
    isReady()
4980
    {
4981
        local actor = getActor();
4982
        local state = actor.curState;
4983
4984
        return (inherited()
4985
                && state.ofKind(InConversationState)
4986
                && state.attentionSpan != nil
4987
                && actor.boredomCount >= state.attentionSpan);
4988
    }
4989
4990
    /* on invocation, end the conversation */
4991
    invokeItem()
4992
    {
4993
        local actor = getActor();
4994
        local state = actor.curState;
4995
4996
        /* tell the state to end the conversation */
4997
        state.endConversation(actor.getCurrentInterlocutor(), endConvBoredom);
4998
    }
4999
5000
    /* 
5001
     *   by default, handle boredom before other agenda items - we do this
5002
     *   because an ongoing conversation will be the first thing on the
5003
     *   NPC's mind 
5004
     */
5005
    agendaOrder = 50
5006
;
5007
5008
5009
/*
5010
 *   A "hermit" actor state is a state where the actor is unresponsive to
5011
 *   conversational overtures (ASK ABOUT, TELL ABOUT, HELLO, GOODBYE, YES,
5012
 *   NO, SHOW TO, GIVE TO, and any orders directed to the actor).  Any
5013
 *   attempt at conversation will be met with the 'noResponse' message.  
5014
 */
5015
class HermitActorState: ActorState
5016
    /* 
5017
     *   Show our response to any conversational command.  We'll simply
5018
     *   show the standard "there's no response" message by default, but
5019
     *   subclasses can (and usually should) override this to explain
5020
     *   what's really going on.  Note that this routine will be invoked
5021
     *   for any sort of conversation command, so any override needs to be
5022
     *   generic enough that it's equally good for ASK, TELL, and
5023
     *   everything else.
5024
     *   
5025
     *   Note that it's fairly easy to create a shuffled list of random
5026
     *   messages, if you want to add some variety to the actor's
5027
     *   responses.  To do this, use an embedded ShuffledEventList:
5028
     *   
5029
     *   myState: HermitActorState
5030
     *.    noResponse() { myList.doScript(); }
5031
     *.    myList: ShuffledEventList {
5032
     *.      ['message1', 'message2', 'message3'] }
5033
     *.  ;
5034
     */
5035
    noResponse() { mainReport(&noResponseFromMsg, getActor()); }
5036
5037
    /* all conversation actions get the same default response */
5038
    handleConversation(otherActor, topic, convType)
5039
    {
5040
        /* just show our standard default response */
5041
        noResponse();
5042
    }
5043
5044
    /* 
5045
     *   Since the hermit state blocks topics from outside the state, don't
5046
     *   offer suggestions for other topics while in this state.
5047
     *   
5048
     *   Note that you might sometimes want to override this to allow the
5049
     *   usual topic suggestions (by setting this to nil).  In particular:
5050
     *   
5051
     *   - If it's not outwardly obvious that the actor is unresponsive,
5052
     *   you'll probably want to allow suggestions.  Remember, TOPICS
5053
     *   suggests topics that the *PC* wants to talk about, not things the
5054
     *   NPC is interested in.  If the PC doesn't necessarily know that the
5055
     *   NPC won't respond, the PC would still want to ask about those
5056
     *   topics.
5057
     *   
5058
     *   - If the hermit state is to be short-lived, you might want to show
5059
     *   the topic suggestions even in the hermit state, so that the player
5060
     *   is aware that there are still useful topics to explore with the
5061
     *   NPC.  The player might otherwise assume that the NPC is out of
5062
     *   useful topics, and not bother trying again later when the NPC
5063
     *   becomes more responsive.  
5064
     */
5065
    limitSuggestions = true
5066
;
5067
5068
/*
5069
 *   The basic "accompanying" state.  In this state, whenever the actor
5070
 *   we're accompanying travels to a location we want to follow, we'll
5071
 *   travel at the same time with the other actor.  
5072
 */
5073
class AccompanyingState: ActorState
5074
    /*
5075
     *   Check to see if we are to accompany the given traveler on the
5076
     *   given travel.  'traveler' is the Traveler performing the travel,
5077
     *   and 'conn' is the connector that the traveler is about to take.
5078
     *   
5079
     *   Note that 'traveler' is a Traveler object.  This will simply be an
5080
     *   Actor (which is a kind of Traveler) when the actor is performing
5081
     *   the travel directly, but it could also be another kind of
5082
     *   Traveler, such as a Vehicle.  This routine must determine whether
5083
     *   to accompany other kinds of actors.
5084
     *   
5085
     *   By default, we'll return true to indicate that we want to
5086
     *   accompany any traveler anywhere they go.  This should almost
5087
     *   always be overridden in practice to be more specific.  
5088
     */
5089
    accompanyTravel(traveler, conn) { return true; }
5090
5091
    /* 
5092
     *   Get our accompanying state object.  We'll create a basic
5093
     *   accompanying in-travel state object, returning to the current
5094
     *   state when we're done.  'traveler' is the Traveler object that's
5095
     *   performing the travel; this might be an Actor, but could also be a
5096
     *   Vehicle or other Traveler subclass.  
5097
     */
5098
    getAccompanyingTravelState(traveler, connector)
5099
    {
5100
        /* 
5101
         *   Create the default intermediate state for the travel.  Note
5102
         *   that the lead actor is the actor performing the command - this
5103
         *   won't necessarily be the traveler, since the actor could be
5104
         *   steering a vehicle.  
5105
         */
5106
        return new AccompanyingInTravelState(
5107
            getActor(), gActor, getActor().curState);
5108
    }
5109
5110
    /*
5111
     *   handle a before-travel notification for my actor 
5112
     */
5113
    beforeTravel(traveler, connector)
5114
    {
5115
        /*
5116
         *   If we want to accompany the given traveler on this travel, add
5117
         *   ourselves to the initiating actor's list of accompanying
5118
         *   actors.  Never set an actor to accompany itself, since doing
5119
         *   so would lead to infinite recursion.  
5120
         */
5121
        if (accompanyTravel(traveler, connector) && getActor() != gActor)
5122
        {
5123
            /* 
5124
             *   Add me to the list of actors accompanying the actor
5125
             *   initiating the travel - that actor will run a nested
5126
             *   travel action on us before doing its own travel.  Note
5127
             *   that the initiating actor is gActor, since that's the
5128
             *   actor performing the action that led to the travel.  
5129
             */
5130
            gActor.addAccompanyingActor(getActor());
5131
            
5132
            /* put my actor into the appropriate new group travel state */
5133
            getActor().setCurState(
5134
                getAccompanyingTravelState(traveler, connector));
5135
        }
5136
5137
        /* inherit the default handling */
5138
        inherited(traveler, connector);
5139
    }
5140
;
5141
5142
/*
5143
 *   "Accompanying in-travel" state - this is an actor state used when an
5144
 *   actor is taking part in a group travel operation.  This state lasts
5145
 *   only as long as the single turn - which belongs to the lead actor -
5146
 *   that it takes to carry out the group travel.  Once our turn comes
5147
 *   around, we'll restore the actor to the previous state - or, we can set
5148
 *   the actor to a different state, if desired.  Setting the actor to a
5149
 *   different state is useful when the group travel triggers a new
5150
 *   scripted activity in the new room.  
5151
 */
5152
class AccompanyingInTravelState: ActorState
5153
    construct(actor, lead, next)
5154
    {
5155
        /* do the normal initialization */
5156
        inherited(actor);
5157
5158
        /* remember the lead actor and the next state */
5159
        leadActor = lead;
5160
        nextState = next;
5161
    }
5162
5163
    /* the lead actor of the group travel */
5164
    leadActor = nil
5165
5166
    /* 
5167
     *   the next state - we'll switch our actor to this state after the
5168
     *   travel has been completed 
5169
     */
5170
    nextState = nil
5171
5172
    /*
5173
     *   Show our "I am here" description.  By default, we'll use the
5174
     *   arrivingWithDesc of the *next* state object.  
5175
     */
5176
    specialDesc() { nextState.arrivingWithDesc; }
5177
5178
    /* take our turn */
5179
    takeTurn()
5180
    {
5181
        /* 
5182
         *   The group travel only takes the single turn in which the
5183
         *   travel is initiated, so by the time our turn comes around, the
5184
         *   group travel is done.  Clear out the lead actor's linkage to
5185
         *   us as an accompanying actor.  
5186
         */
5187
        leadActor.accompanyingActors.removeElement(getActor());
5188
5189
        /* switch our actor to the next state */
5190
        getActor().setCurState(nextState);
5191
5192
        /* 
5193
         *   call our next state's on-arrival turn-taking method, so that
5194
         *   it can carry out any desired scripted behavior for our arrival
5195
         */
5196
        nextState.arrivingTurn();
5197
    }
5198
5199
    /* initiate a topic - defer to the next state */
5200
    initiateTopic(obj) { return nextState.initiateTopic(obj); }
5201
5202
    /* 
5203
     *   Override our departure messages.  When we're accompanying another
5204
     *   actor on a group travel, the lead actor will, as part of its turn,
5205
     *   send each accompanying actor (including us) on ahead.  This means
5206
     *   that the lead actor will see us departing from the starting
5207
     *   location, because we'll leave before the lead actor has itself
5208
     *   departed.  Rather than using the normal "Bob leaves to the west"
5209
     *   departure report, customize the departure reports to indicate
5210
     *   specifically that we're going with the lead actor.  (Note that we
5211
     *   only have to handle the departing messages, since group travel
5212
     *   always sends accompanying actors on ahead of the main actor, hence
5213
     *   the accompanying actors will always be seen departing, not
5214
     *   arriving.)
5215
     *   
5216
     *   Note that all of these call our generic sayDeparting() method by
5217
     *   default, so a subclass can catch all of the departure types at
5218
     *   once just by overriding sayDeparting().  Overriding the individual
5219
     *   methods is still desirable, of course, if you want separate
5220
     *   messages for the different departure types.  
5221
     */
5222
    sayDeparting(conn)
5223
        { gLibMessages.sayDepartingWith(getActor(), leadActor); }
5224
    sayDepartingDir(dir, conn) { sayDeparting(conn); }
5225
    sayDepartingThroughPassage(conn) { sayDeparting(conn); }
5226
    sayDepartingViaPath(conn) { sayDeparting(conn); }
5227
    sayDepartingUpStairs(conn) { sayDeparting(conn); }
5228
    sayDepartingDownStairs(conn) { sayDeparting(conn); }
5229
5230
    /*
5231
     *   Describe local travel using our standard departure message as
5232
     *   well.  This is used to describe our travel when our origin and
5233
     *   destination locations are both visible to the PC; in these cases,
5234
     *   we don't describe the departure separately because the whole
5235
     *   process of travel from departure to arrival is visible to the PC
5236
     *   and thus is best handled with a single message, which we generate
5237
     *   here.  In our case, since the "accompanying" state describes even
5238
     *   normal travel as though it were visible all along, we can use our
5239
     *   standard "departing" message to describe local travel as well.  
5240
     */
5241
    sayArrivingLocally(dest, conn) { sayDeparting(conn); }
5242
    sayDepartingLocally(dest, conn) { sayDeparting(conn); }
5243
;
5244
5245
/* ------------------------------------------------------------------------ */
5246
/*
5247
 *   A pending conversation information object.  An Actor keeps a list of
5248
 *   these for pending conversations.  
5249
 */
5250
class PendingConvInfo: object
5251
    construct(state, node, turns)
5252
    {
5253
        /* remember how to start the conversation */
5254
        state_ = state;
5255
        node_ = node;
5256
5257
        /* compute the game clock time when we can start the conversation */
5258
        time_ = Schedulable.gameClockTime + turns;
5259
    }
5260
5261
    /* 
5262
     *   our ActorState and ConvNode (or ConvNode name string), describing
5263
     *   how we're to start the conversation 
5264
     */
5265
    state_ = nil
5266
    node_ = nil
5267
5268
    /* the minimum game clock time at which we can start the conversation */
5269
    time_ = nil
5270
;
5271
5272
/* ------------------------------------------------------------------------ */
5273
/*
5274
 *   An "agenda item."  Each actor can have its own "agenda," which is a
5275
 *   list of these items.  Each item represents an action that the actor
5276
 *   wants to perform - this is usually a goal the actor wants to achieve,
5277
 *   or a conversational topic the actor wants to pursue.
5278
 *   
5279
 *   On any given turn, an actor can carry out only one agenda item.
5280
 *   
5281
 *   Agenda items are a convenient way of controlling complex behavior.
5282
 *   Each agenda item defines its own condition for when the actor can
5283
 *   pursue the item, and each item defines what the actor does when
5284
 *   pursuing the item.  Agenda items can improve the code structure for an
5285
 *   NPC's behavior, since they nicely isolate a single background action
5286
 *   and group it with the conditions that trigger it.  But the main
5287
 *   benefit of agenda items is the one-per-turn pacing - by executing at
5288
 *   most one agenda item per turn, we ensure that the NPC will carry out
5289
 *   its self-initiated actions at a measured pace, rather than as a jumble
5290
 *   of random actions on a single turn.
5291
 *   
5292
 *   Note that NPC-initiated conversation messages override agendas.  If an
5293
 *   actor has an active ConvNode, AND the ConvNode displays a
5294
 *   "continuation message" on a given turn, then the actor will not pursue
5295
 *   its agenda on that turn.  In this way, ConvNode continuation messages
5296
 *   act rather like high-priority agenda items.  
5297
 */
5298
class AgendaItem: object
5299
    /* 
5300
     *   My actor - agenda items should be nested within the actor using
5301
     *   '+' so that we can find our actor.  Note that this doesn't add the
5302
     *   item to the actor's agenda - that has to be done explicitly with
5303
     *   actor.addToAgenda().  
5304
     */
5305
    getActor() { return location; }
5306
5307
    /*
5308
     *   Is this item active at the start of the game?  Override this to
5309
     *   true to make the item initially active; we'll add it to the
5310
     *   actor's agenda during the game's initialization.  
5311
     */
5312
    initiallyActive = nil
5313
5314
    /* 
5315
     *   Is this item ready to execute?  The actor will only execute an
5316
     *   agenda item when this condition is met.  By default, we're ready
5317
     *   to execute.  Items can override this to provide a declarative
5318
     *   condition of readiness if desired.  
5319
     */
5320
    isReady = true
5321
5322
    /*
5323
     *   Is this item done?  On each turn, we'll remove any items marked as
5324
     *   done from the actor's agenda list.  We remove items marked as done
5325
     *   before executing any items, so done-ness overrides readiness; in
5326
     *   other words, if an item is both 'done' and 'ready', it'll simply
5327
     *   be removed from the list and will not be executed.
5328
     *   
5329
     *   By default, we simply return nil.  Items can override this to
5330
     *   provide a declarative condition of done-ness, or they can simply
5331
     *   set the property to true when they finish their work.  For
5332
     *   example, an item that only needs to execute once can simply set
5333
     *   isDone to true in its invokeItem() method; an item that's to be
5334
     *   repeated until some success condition obtains can override isDone
5335
     *   to return the success condition.  
5336
     */
5337
    isDone = nil
5338
5339
    /*
5340
     *   The ordering of the item relative to other agenda items.  When we
5341
     *   choose an agenda item to execute, we always choose the lowest
5342
     *   numbered item that's ready to run.  You can leave this with the
5343
     *   default value if you don't care about the order.  
5344
     */
5345
    agendaOrder = 100
5346
5347
    /*
5348
     *   Execute this item.  This is invoked during the actor's turn when
5349
     *   the item is the first item that's ready to execute in the actor's
5350
     *   agenda list.  We do nothing by default.
5351
     */
5352
    invokeItem() { }
5353
5354
    /*
5355
     *   Reset the item.  This is invoked whenever the item is added to an
5356
     *   actor's agenda.  By default, we'll set isDone to nil as long as
5357
     *   isDone isn't a method; this makes it easier to reuse agenda
5358
     *   items, since we don't have to worry about clearing out the isDone
5359
     *   flag when reusing an item. 
5360
     */
5361
    resetItem()
5362
    {
5363
        /* if isDone isn't a method, reset it to nil */
5364
        if (propType(&isDone) != TypeCode)
5365
            isDone = nil;
5366
    }
5367
;
5368
5369
/* 
5370
 *   An AgendaItem initializer.  For each agenda item that's initially
5371
 *   active, we'll add the item to its actor's agenda.  
5372
 */
5373
PreinitObject
5374
    execute()
5375
    {
5376
        forEachInstance(AgendaItem, new function(item) {
5377
            /* 
5378
             *   If this item is initially active, add the item to its
5379
             *   actor's agenda. 
5380
             */
5381
            if (item.initiallyActive)
5382
                item.getActor().addToAgenda(item);
5383
        });
5384
    }
5385
;
5386
5387
/*
5388
 *   A "conversational" agenda item.  This type of item is ready to execute
5389
 *   only when the actor hasn't engaged in conversation during the same
5390
 *   turn.  This type of item is ideal for situations where we want the
5391
 *   actor to pursue a conversational topic, because we won't initiate the
5392
 *   action until we get a turn where the player didn't directly talk to
5393
 *   us.  
5394
 */
5395
class ConvAgendaItem: AgendaItem
5396
    isReady = (!getActor().conversedThisTurn()
5397
               && getActor().canTalkTo(otherActor)
5398
               && inherited())
5399
5400
    /* 
5401
     *   The actor we're planning to address - by default, this is the PC.
5402
     *   If the conversational overture will be directed to another NPC,
5403
     *   you can specify that other actor here. 
5404
     */
5405
    otherActor = (gPlayerChar)
5406
;
5407
5408
/*
5409
 *   A delayed agenda item.  This type of item becomes ready to execute
5410
 *   when the game clock reaches a given turn counter.  
5411
 */
5412
class DelayedAgendaItem: AgendaItem
5413
    /* we're ready if the game clock time has reached our ready time */
5414
    isReady = (Schedulable.gameClockTime >= readyTime && inherited())
5415
5416
    /* the turn counter on the game clock when we become ready */
5417
    readyTime = 0
5418
5419
    /*
5420
     *   Set our ready time based on a delay from the current time.  We'll
5421
     *   become ready after the given number of turns elapses.  For
5422
     *   convenience, we return 'self', so a delayed agenda item can be
5423
     *   initialized and added to an actor's agenda in one simple
5424
     *   operation, like so:
5425
     *   
5426
     *   actor.addToAgenda(item.setDelay(1)); 
5427
     */
5428
    setDelay(turns)
5429
    {
5430
        /* 
5431
         *   initialize our ready time as the given number of turns in the
5432
         *   future from the current game clock time 
5433
         */
5434
        readyTime = Schedulable.gameClockTime + turns;
5435
5436
        /* return 'self' for the caller's convenience */
5437
        return self;
5438
    }
5439
;
5440
5441
5442
/* ------------------------------------------------------------------------ */
5443
/*
5444
 *   An Actor is a living person, animal, or other entity with a will of
5445
 *   its own.  Actors can usually be addressed with targeted commands
5446
 *   ("bob, go north"), and with commands like ASK ABOUT, TELL ABOUT, GIVE
5447
 *   TO, and SHOW TO.
5448
 *   
5449
 *   Note that, by default, an Actor can be picked up and moved with
5450
 *   commands like TAKE, PUT IN, and so on.  This is suitable for some
5451
 *   kinds of actors but not for others: it might make sense with a cat or
5452
 *   a small dog, but not with a bank guard or an orc.  For an actor that
5453
 *   can't be taken, use the UntakeableActor or one of its subclasses.
5454
 *   
5455
 *   An actor's contents are the things the actor is carrying or wearing.  
5456
 */
5457
class Actor: Thing, Schedulable, Traveler, ActorTopicDatabase
5458
    /* flag: we're an actor */
5459
    isActor = true
5460
5461
    /*
5462
     *   Our current state.  This is an ActorState object representing what
5463
     *   we're currently doing.  Whenever the actor changes to a new state
5464
     *   (for example, because of a scripted activity), this can be changed
5465
     *   to reflect the actor's new state.  The state object groups the
5466
     *   parts of the actor's description and other methods that tend to
5467
     *   vary according to what the actor's doing; it's easier to keep
5468
     *   everything related to scripted activities together in a state
5469
     *   object than it is to handle all of the variability with switch()
5470
     *   statements of the like in methods directly in the actor.
5471
     *   
5472
     *   It's not necessary to initialize this if the actor doesn't take
5473
     *   advantage of the ActorState mechanism.  If this isn't initialized
5474
     *   for a particular actor, we'll automatically create a default
5475
     *   ActorState object during pre-initialization.  
5476
     */
5477
    curState = nil
5478
5479
    /* set the current state */
5480
    setCurState(state)
5481
    {
5482
        /* if this isn't a change of state, there's nothing to do */
5483
        if (state == curState)
5484
            return;
5485
        
5486
        /* if we have a previous state, tell it it's becoming inactive */
5487
        if (curState != nil)
5488
            curState.deactivateState(self, state);
5489
5490
        /* notify the new state it's becoming active */
5491
        if (state != nil)
5492
            state.activateState(self, curState);
5493
5494
        /* remember the new state */
5495
        curState = state;
5496
    }
5497
5498
    /*
5499
     *   Our current conversation node.  This is a ConvNode object that
5500
     *   keeps track of the flow of the conversation.  
5501
     */
5502
    curConvNode = nil
5503
5504
    /* 
5505
     *   Our table of conversation nodes.  At initialization, the
5506
     *   conversation manager scans all ConvNode instances and adds each
5507
     *   one to its actor's table.  This table is keyed by the name of
5508
     *   node, and the value for each entry is the ConvNode object - this
5509
     *   lets us look up the ConvNode object by name.  Because each actor
5510
     *   has its own lookup table, ConvNode names only have to be unique
5511
     *   within the actor's set of ConvNodes.  
5512
     */
5513
    convNodeTab = perInstance(new LookupTable(32, 32))
5514
5515
    /* set the current conversation node */
5516
    setConvNode(node) { setConvNodeReason(node, nil); }
5517
5518
    /* set the current conversation node, with a reason code */
5519
    setConvNodeReason(node, reason)
5520
    {
5521
        /* remember the old node */
5522
        local oldNode = curConvNode;
5523
        
5524
        /* if the node was specified by name, look up the object */
5525
        if (dataType(node) == TypeSString)
5526
            node = convNodeTab[node];
5527
5528
        /* remember the new node */
5529
        curConvNode = node;
5530
5531
        /* 
5532
         *   If we're changing to a new node, notify the new and old
5533
         *   nodes.  Note that these notifications occur after the new
5534
         *   node has been set, which ensures that any further node change
5535
         *   triggered by the node change won't redundantly issue the same
5536
         *   notifications: since the old node is no longer active, it
5537
         *   can't receive another departure notification, and since the
5538
         *   new node is already active, it can't receive another
5539
         *   activation. 
5540
         */
5541
        if (node != oldNode)
5542
        {
5543
            /* if there's an old node, note that we're leaving it */
5544
            if (oldNode != nil)
5545
                oldNode.noteLeaving();
5546
5547
            /* let the node know that it's becoming active */
5548
            if (node != nil)
5549
                node.noteActiveReason(reason);
5550
        }
5551
5552
        /* 
5553
         *   note that we've explicitly set a ConvNode (even if it's not
5554
         *   actually changing), in case the conversation manager is
5555
         *   tracking what's happening during a response 
5556
         */
5557
        responseSetConvNode = true;
5558
    }
5559
5560
    /* 
5561
     *   conversation manager ID - this is assigned by the conversation
5562
     *   manager to map to and from output stream references to the actor;
5563
     *   this is only for internal use by the conversation manager
5564
     */
5565
    convMgrID = nil
5566
5567
    /* 
5568
     *   Flag indicating whether or not we've set a ConvNode in the course
5569
     *   of the current response.  This is for use by the converstaion
5570
     *   manager. 
5571
     */
5572
    responseSetConvNode = nil
5573
5574
    /*
5575
     *   Initiate a conversation with the player character.  This lets the
5576
     *   NPC initiate a conversation, in response to something the player
5577
     *   character does, or as part of the NPC's scripted activity.  This
5578
     *   is only be used for situations where the NPC initiates the
5579
     *   conversation - if the player character initiates conversation with
5580
     *   TALK TO, ASK, TELL, etc., we handle the conversation through our
5581
     *   normal handlers for those commands.
5582
     *   
5583
     *   'state' is the ActorState to switch to for the conversation.  This
5584
     *   will normally be an InConversationState object, but doesn't have
5585
     *   to be.
5586
     *   
5587
     *   You can pass nil for 'state' to use the current state's implied
5588
     *   conversational state.  The implied conversational state of a
5589
     *   ConversationReadyState is the associated InConversationState; the
5590
     *   implied conversation state of any other state is simply the same
5591
     *   state.
5592
     *   
5593
     *   'node' is a ConvNode object, or a string naming a ConvNode object.
5594
     *   We'll make this our current conversation node.  A valid
5595
     *   conversation node is required because we use this to generate the
5596
     *   initial NPC greeting of the conversation.  In most cases, when the
5597
     *   NPC initiates a conversation, it's because the NPC wants to ask a
5598
     *   question or otherwise say something specific, so there should
5599
     *   always be a conversational context implied, thus the need for a
5600
     *   ConvNode.  If there's no need for a conversational context, the
5601
     *   NPC script code might just as well display the conversational
5602
     *   exchange as a plain old message, and not bother going to all this
5603
     *   trouble.  
5604
     */
5605
    initiateConversation(state, node)
5606
    {
5607
        /* 
5608
         *   if there's no state provided, use the current state's implied
5609
         *   conversation state 
5610
         */
5611
        if (state == nil)
5612
            state = curState.getImpliedConvState;
5613
5614
        /* 
5615
         *   if there's an ActorHelloTopic for the old state, invoke it to
5616
         *   show the greeting 
5617
         */
5618
        curState.handleTopic(self, actorHelloTopicObj, helloConvType, nil);
5619
5620
        /* switch to the new state, if it's not the current state */
5621
        if (state != nil && state != curState)
5622
            setCurState(state);
5623
5624
        /* we're now talking to the player character */
5625
        noteConversation(gPlayerChar);
5626
5627
        /* switch to the conversation node */
5628
        setConvNodeReason(node, 'initiateConversation');
5629
5630
        /* tell the conversation node that the NPC is initiating it */
5631
        if (node != nil)
5632
            curConvNode.npcInitiateConversation();
5633
    }
5634
5635
    /*
5636
     *   Initiate a conversation based on the given simulation object.
5637
     *   We'll look for an InitiateTopic matching the given object, and if
5638
     *   we can find one, we'll show its topic response.  
5639
     */
5640
    initiateTopic(obj)
5641
    {
5642
        /* try our current state first */
5643
        if (curState.initiateTopic(obj))
5644
            return true;
5645
5646
        /* we didn't find a state object; use the default handling */
5647
        return inherited(obj);
5648
    }
5649
5650
    /*
5651
     *   Schedule initiation of conversation.  This allows the caller to
5652
     *   set up a conversation to start on a future turn.  The
5653
     *   conversation will start after (1) the given number of turns has
5654
     *   elapsed, and (2) the player didn't target this actor with a
5655
     *   conversational command on the same turn.  This allows us to set
5656
     *   the NPC so that it *wants* to start a conversation, and will do
5657
     *   so as soon as it has a chance to get a word in.
5658
     *   
5659
     *   If 'turns' is zero, the conversation can start the next time the
5660
     *   actor takes a turn; so, if this is called during the PC's action
5661
     *   processing, the conversation can start on the same turn.  Note
5662
     *   that if this is called during the actor's takeTurn() processing,
5663
     *   it won't actually start the conversation until the next turn,
5664
     *   because that's the next time we'll check the queue.  If 'turns'
5665
     *   is 1, then the player will get at least one more command before
5666
     *   the conversation will begin, and so on with higher numbers.  
5667
     */
5668
    scheduleInitiateConversation(state, node, turns)
5669
    {
5670
        /* add a new pending conversation to our list */
5671
        pendingConv.append(new PendingConvInfo(state, node, turns));
5672
    }
5673
5674
    /*
5675
     *   Break off our current conversation, of the NPC's own volition.
5676
     *   This is the opposite number of initiateConversation: this causes
5677
     *   the NPC to effectively say BYE on its own, rather than waiting
5678
     *   for the PC to decide to end the conversation.
5679
     *   
5680
     *   This call is mostly useful when the actor's current state is an
5681
     *   InConversationState, since the main function of this routine is
5682
     *   to switch to an out-of-conversation state.  
5683
     */
5684
    endConversation()
5685
    {
5686
        /* 
5687
         *   tell the current state to end the conversation of the NPC's
5688
         *   own volition 
5689
         */
5690
        curState.endConversation(self, endConvActor);
5691
    }
5692
5693
    /* 
5694
     *   Our list of pending conversation initiators.  In our takeTurn()
5695
     *   processing, we'll check this list for conversations that we can
5696
     *   initiate. 
5697
     */
5698
    pendingConv = nil
5699
5700
    /* 
5701
     *   Hide actors from 'all' by default.  The kinds of actions that
5702
     *   normally apply to 'all' and the kinds that normally apply to
5703
     *   actors have pretty low overlap.
5704
     *   
5705
     *   If a particular actor looks a lot like an inanimate object, it
5706
     *   might want to override this to participate in 'all' for most or
5707
     *   all actions.  
5708
     */
5709
    hideFromAll(action) { return true; }
5710
5711
    /* 
5712
     *   don't hide actors from defaulting, though - it's frequently
5713
     *   convenient and appropriate to assume an actor by default,
5714
     *   especially for commands like GIVE TO and SHOW TO 
5715
     */
5716
    hideFromDefault(action) { return nil; }
5717
5718
    /* 
5719
     *   We meet the objHeld precondition for ourself - that is, for any
5720
     *   verb that requires holding an object, we can be considered to be
5721
     *   holding ourself. 
5722
     */
5723
    meetsObjHeld(actor) { return actor == self || inherited(actor); }
5724
5725
    /* 
5726
     *   Actors are not listed with the ordinary objects in a room's
5727
     *   description.  However, an actor is listed as part of an inventory
5728
     *   description.
5729
     */
5730
    isListed = nil
5731
    isListedInContents = nil
5732
    isListedInInventory = true
5733
5734
    /* the contents of an actor aren't listed in a room's description */
5735
    contentsListed = nil
5736
5737
    /*
5738
     *   Full description.  By default, we'll show either the pcDesc or
5739
     *   npcDesc, depending on whether we're the current player character
5740
     *   or a non-player character. 
5741
     *   
5742
     *   Generally, individual actors should NOT override this method.
5743
     *   Instead, customize pcDesc and/or npcDesc to describe the permanent
5744
     *   features of the actor.  
5745
     */
5746
    desc
5747
    {
5748
        /* 
5749
         *   show the appropriate messages, depending on whether we're the
5750
         *   player character or a non-player character 
5751
         */
5752
        if (isPlayerChar())
5753
        {
5754
            /* show our as-player-character description */
5755
            pcDesc;
5756
        }
5757
        else
5758
        {
5759
            /* show our as-non-player-character description */
5760
            npcDesc;
5761
        }
5762
    }
5763
5764
    /* show our status */
5765
    examineStatus()
5766
    {
5767
        /* 
5768
         *   If I'm an NPC, show where I'm sitting/standing/etc.  (If I'm
5769
         *   the PC, we don't usually want to show this explicitly to avoid
5770
         *   redundancy.  The player is usually sufficiently aware of the
5771
         *   PC's posture by virtue of being in control of the actor, and
5772
         *   the information also tends to show up often enough in other
5773
         *   places, such as on the status line and in the room
5774
         *   description.)  
5775
         */
5776
        if (!isPlayerChar())
5777
            postureDesc;
5778
5779
        /* show the status from our state object */
5780
        curState.stateDesc;
5781
5782
        /* inherit the default handling to show our contents */
5783
        inherited();
5784
    }
5785
5786
    /* 
5787
     *   Show my posture, as part of the full EXAMINE description of this
5788
     *   actor.  We'll let our nominal actor container handle it.  
5789
     */
5790
    postureDesc() { descViaActorContainer(&roomActorPostureDesc, nil); }
5791
    
5792
    /* 
5793
     *   The default description when we examine this actor and the actor
5794
     *   is serving as the player character.  This should generally not
5795
     *   include any temporary status information; just show constant,
5796
     *   fixed features.  
5797
     */
5798
    pcDesc { gLibMessages.pcDesc(self); }
5799
5800
    /* 
5801
     *   Show the description of this actor when this actor is a non-player
5802
     *   character.
5803
     *   
5804
     *   This description should include only the constant, fixed
5805
     *   description of the character.  Do not include information on what
5806
     *   the actor is doing right now, because that belongs in the
5807
     *   ActorState object instead.  When we display the actor's
5808
     *   description, we'll show this text, and then we'll show the
5809
     *   ActorState description as well; this combination approach makes it
5810
     *   easier to keep the description synchronized with any scripted
5811
     *   activities the actor is performing.
5812
     *   
5813
     *   By default, we'll show this as a "default descriptive report,"
5814
     *   since it simply says that there's nothing special to say.
5815
     *   However, whenever this is overridden with an actual description,
5816
     *   you shouldn't bother to use defaultDescReport - simply display the
5817
     *   descriptive message directly:
5818
     *   
5819
     *   npcDesc = "He's wearing a gorilla costume. " 
5820
     */
5821
    npcDesc { defaultDescReport(&npcDescMsg, self); }
5822
5823
    /* examine my contents specially */
5824
    examineListContents()
5825
    {
5826
        /* if I'm not the player character, show my inventory */
5827
        if (!isPlayerChar())
5828
            holdingDesc;
5829
    }
5830
    
5831
    /*
5832
     *   Always list actors specially, rather than as ordinary items in
5833
     *   contents listings.  We'll send this to our current state object
5834
     *   for processing, since our "I am here" description tends to vary by
5835
     *   state.
5836
     */
5837
    specialDesc() { curState.specialDesc(); }
5838
    distantSpecialDesc() { curState.distantSpecialDesc(); }
5839
    remoteSpecialDesc(actor) { curState.remoteSpecialDesc(actor); }
5840
    specialDescListWith() { return curState.specialDescListWith(); }
5841
5842
    /*
5843
     *   By default, show the special description for an actor in the group
5844
     *   of special descriptions that come *after* the room's portable
5845
     *   contents listing.  An actor's presence is usually a dynamic
5846
     *   feature of a room, and so we don't want to suggest that the actor
5847
     *   is a permanent feature of the room by describing the actor
5848
     *   directly with the room's main description.  
5849
     */
5850
    specialDescBeforeContents = nil
5851
5852
    /*
5853
     *   When we're asked to show a special description as part of the
5854
     *   description of a containing object (which will usually be a nested
5855
     *   room of some kind), just show our posture in our container, rather
5856
     *   than showing our full "I am here" description. 
5857
     */
5858
    showSpecialDescInContents(actor, cont)
5859
    {
5860
        /* show our posture to indicate our container */
5861
        listActorPosture(actor);
5862
    }
5863
5864
    /* 
5865
     *   By default, put all of the actor special descriptions after the
5866
     *   special descriptions of ordinary objects, by giving actors a
5867
     *   higher listing order value. 
5868
     */
5869
    specialDescOrder = 200
5870
5871
    /*
5872
     *   Get my listing group for my special description as part of a room
5873
     *   description.  By default, we'll let our immediate location decide
5874
     *   how we're grouped.  
5875
     */
5876
    actorListWith()
5877
    {
5878
        local group;
5879
5880
        /* 
5881
         *   if our special desc is overridden, don't use any grouping by
5882
         *   default - this make a special description defined in the
5883
         *   actor override any grouping we'd otherwise do 
5884
         */
5885
        if (overrides(self, Actor, &specialDesc))
5886
            return [];
5887
5888
        /* get the group for the posture */
5889
        group = location.listWithActorIn(posture);
5890
5891
        /* 
5892
         *   if we have a group, return a list containing the group;
5893
         *   otherwise return an empty list 
5894
         */
5895
        return (group == nil ? [] : [group]);
5896
    }
5897
5898
    /*
5899
     *   Actor "I am here" description.  This is displayed as part of the
5900
     *   description of a room - it describes the actor as being present in
5901
     *   the room.  By default, we let the "nominal actor container"
5902
     *   provide the description.  
5903
     */
5904
    actorHereDesc { descViaActorContainer(&roomActorHereDesc, nil); }
5905
5906
    /*
5907
     *   Actor's "I am over there" description.  This is displayed in the
5908
     *   room description when the actor is visible, but is either in a
5909
     *   separate top-level room or is at a distance.  By default, we let
5910
     *   the "nominal actor container" provide the description.  
5911
     */
5912
    actorThereDesc { descViaActorContainer(&roomActorThereDesc, nil); }
5913
5914
    /*
5915
     *   Show our status, as an addendum to the given room's name (this is
5916
     *   the room title, shown at the start of a room description and on
5917
     *   the status line).  By default, we'll let our nominal actor
5918
     *   container provide the status, to indicate when we're
5919
     *   standing/sitting/lying in a nested room.
5920
     *   
5921
     *   In concrete terms, this generally adds a message such as "(sitting
5922
     *   on the chair)" to the name of a room if we're in a nested room
5923
     *   within the room.  When we're standing in the main room, this
5924
     *   generally adds nothing.
5925
     *   
5926
     *   Note that we pass the room we're describing as the "container to
5927
     *   ignore" parameter, because we don't want to say something like
5928
     *   "Phone Booth (standing in the phone booth)" - that is, we don't
5929
     *   want to mention the nominal container again if the nominal
5930
     *   container is what we're naming in the first place.  
5931
     */
5932
    actorRoomNameStatus(room)
5933
        { descViaActorContainer(&roomActorStatus, room); }
5934
5935
    /*
5936
     *   Describe the actor via the "nominal actor container."  The nominal
5937
     *   container is determined by our direct location.
5938
     *   
5939
     *   'contToIgnore' is a container to ignore.  If our nominal container
5940
     *   is the same as this object, we'll generate a description without a
5941
     *   mention of a container at all.
5942
     *   
5943
     *   The reason we have the 'contToIgnore' parameter is that the caller
5944
     *   might already have reported our general location, and now merely
5945
     *   wants to add that we're standing or standing or whatever.  In
5946
     *   these cases, if we were to say that we're sitting on or standing
5947
     *   on that same object, it would be redundant information: "Bob is in
5948
     *   the garden, sitting in the garden."  The 'contToIgnore' parameter
5949
     *   tells us the object that the caller has already mentioned as our
5950
     *   general location so that we don't re-report the same thing.  We
5951
     *   need to know the actual object, rather than just the fact that the
5952
     *   caller mentioned a general location, because our general location
5953
     *   and the specific place we're standing or sitting or whatever might
5954
     *   not be the same: "Bob is in the garden, sitting in the lawn
5955
     *   chair."
5956
     *   
5957
     */
5958
    descViaActorContainer(prop, contToIgnore)
5959
    {
5960
        local pov;
5961
        local cont;
5962
        
5963
        /* get our nominal container for our current posture */
5964
        cont = location.getNominalActorContainer(posture);
5965
5966
        /* get the point of view, using the player character by default */
5967
        if ((pov = getPOV()) == nil)
5968
            pov = gPlayerChar;
5969
        
5970
        /* 
5971
         *   if we have a nominal container, and it's not the one to
5972
         *   ignore, and the player character can see it, generate the
5973
         *   description via the container; otherwise, use a generic
5974
         *   library message that doesn't mention the container 
5975
         */
5976
        if (cont not in (nil, contToIgnore) && pov.canSee(cont))
5977
        {
5978
            /* describe via the container */
5979
            cont.(prop)(self);
5980
        }
5981
        else
5982
        {
5983
            /* use the generic library message */
5984
            gLibMessages.(prop)(self);
5985
        }
5986
    }
5987
    
5988
    /* 
5989
     *   Describe my inventory as part of my description - this is only
5990
     *   called when we examine an NPC.  If an NPC doesn't wish to have
5991
     *   its inventory listed as part of its description, it can simply
5992
     *   override this to do nothing.  
5993
     */
5994
    holdingDesc
5995
    {
5996
        /* 
5997
         *   show our contents as for a normal "examine", but using the
5998
         *   special contents lister for what an actor is holding 
5999
         */
6000
        examineListContentsWith(holdingDescInventoryLister);
6001
    }
6002
6003
    /*
6004
     *   refer to the player character with my player character referral
6005
     *   person, and refer to all other characters in the third person 
6006
     */
6007
    referralPerson { return isPlayerChar() ? pcReferralPerson : ThirdPerson; }
6008
6009
    /* by default, refer to the player character in the second person */
6010
    pcReferralPerson = SecondPerson
6011
6012
    /*
6013
     *   The referral person of the current command targeting the actor.
6014
     *   This is meaningful only when a command is being directed to this
6015
     *   actor, and this actor is an NPC.
6016
     *   
6017
     *   The referral person depends on the specifics of the language.  In
6018
     *   English, a command like "bob, go north" is a second-person
6019
     *   command, while "tell bob to go north" is a third-person command.
6020
     *   The only reason this is important is in interpreting what "you"
6021
     *   means if it's used as an object in the command.  "tell bob to hit
6022
     *   you" probably means that Bob should hit the player character,
6023
     *   while "bob, hit you" probably means that Bob should hit himself.  
6024
     */
6025
    commandReferralPerson = nil
6026
6027
    /* determine if I'm the player character */
6028
    isPlayerChar() { return libGlobal.playerChar == self; }
6029
6030
    /*
6031
     *   Implicit command handling style for this actor.  There are two
6032
     *   styles for handling implied commands: "player" and "NPC",
6033
     *   indicated by the enum codes ModePlayer and ModeNPC, respectively.
6034
     *   
6035
     *   In "player" mode, each implied command is announced with a
6036
     *   description of the command to be performed; DEFAULT responses are
6037
     *   suppressed; and failures are shown.  Furthermore, interactive
6038
     *   requests for more information from the parser are allowed.
6039
     *   Transcripts like this result:
6040
     *   
6041
     *   >open door
6042
     *.  (first opening the door)
6043
     *.  (first unlocking the door)
6044
     *.  What do you want to unlock it with?
6045
     *   
6046
     *   In "NPC" mode, implied commands are treated as complete and
6047
     *   separate commands.  They are not announced; default responses are
6048
     *   shown; failures are NOT shown; and interactive requests for more
6049
     *   information are not allowed.  When an implied command fails in NPC
6050
     *   mode, the parser acts as though the command had never been
6051
     *   attempted.
6052
     *   
6053
     *   By default, we return ModePlayer if we're the player character,
6054
     *   ModeNPC if not (thus the respective names of the modes).  Some
6055
     *   authors might prefer to use "player mode" for NPC's as well as for
6056
     *   the player character, which is why the various parts of the parser
6057
     *   that care about this mode consult this method rather than simply
6058
     *   testing the PC/NPC status of the actor.  
6059
     */
6060
    impliedCommandMode() { return isPlayerChar() ? ModePlayer : ModeNPC; }
6061
6062
    /*
6063
     *   Try moving the given object into this object.  For an actor, this
6064
     *   will do one of two things.  If 'self' is the actor performing the
6065
     *   action that's triggering this implied command, then we can achieve
6066
     *   the goal simply by taking the object.  Otherwise, the way to get
6067
     *   an object into my possession is to have the actor performing the
6068
     *   command give me the object.  
6069
     */
6070
    tryMovingObjInto(obj)
6071
    {
6072
        if (gActor == self)
6073
        {
6074
            /* 
6075
             *   I'm performing the triggering action, so I merely need to
6076
             *   pick up the object 
6077
             */
6078
            return tryImplicitAction(Take, obj);
6079
        }
6080
        else
6081
        {
6082
            /* 
6083
             *   another actor is performing the action; since that actor
6084
             *   is the one who must perform the implied action, the way to
6085
             *   get an object into my inventory is for that actor to give
6086
             *   it to me 
6087
             */
6088
            return tryImplicitAction(GiveTo, obj, self);
6089
        }
6090
    }
6091
6092
    /* desribe our containment of an object as carrying the object */
6093
    mustMoveObjInto(obj) { reportFailure(&mustBeCarryingMsg, obj, self); }
6094
6095
    /*
6096
     *   You can limit the cumulative amount of bulk an actor can hold, and
6097
     *   the maximum bulk of any one object the actor can hold, using
6098
     *   bulkCapacity and maxSingleBulk.  These properties are analogous to
6099
     *   the same ones in Container.
6100
     *   
6101
     *   A word of caution on these is in order.  Many authors worry that
6102
     *   it's unrealistic if the player character can carry too much at one
6103
     *   time, so they'll fiddle with these properties to impose a carrying
6104
     *   limit that seems realistic.  Be advised that authors love this
6105
     *   sort of "realism" a whole lot more than players do.  Players
6106
     *   almost universally don't care about it, and in fact tend to hate
6107
     *   the inventory juggling it inevitably leads to.  Juggling inventory
6108
     *   isn't any fun for the player.  Don't fool yourself about this -
6109
     *   the thoughts in the mind of a player who's tediously carting
6110
     *   objects back and forth three at a time will not include admiration
6111
     *   of your prowess at simulational realism.  In contrast, if you set
6112
     *   the carrying limit to infinity, it's a rare player who will even
6113
     *   notice, and a much rarer player who'll complain about it.
6114
     *   
6115
     *   If you really must insist on inventory limits, refer to the
6116
     *   BagOfHolding class for a solution that can salvage most of the
6117
     *   "realism" that the accountancy-inclined author craves, without
6118
     *   creating undue inconvenience for the player.  BagOfHolding makes
6119
     *   inventory limits palatable for the player by essentially
6120
     *   automating the required inventory juggling.  In fact, for most
6121
     *   players, an inventory limit in conjunction with a bag of holding
6122
     *   is actually better than an unlimited inventory, since it improves
6123
     *   readability by keeping the direct inventory list to a manageable
6124
     *   size.  
6125
     */
6126
    bulkCapacity = 10000
6127
    maxSingleBulk = 10
6128
6129
    /*
6130
     *   An actor can limit the cumulative amount of weight being held,
6131
     *   using weightCapacity.  By default we make this so large that
6132
     *   there is effectively no limit to how much weight an actor can
6133
     *   carry.  
6134
     */
6135
    weightCapacity = 10000
6136
6137
    /*
6138
     *   Can I own the given object?  By default, an actor can own
6139
     *   anything.  
6140
     */
6141
    canOwn(obj) { return true; }
6142
6143
    /*
6144
     *   Get the preconditions for travel.  By default, we'll add the
6145
     *   standard preconditions that the connector requires for actors.
6146
     *   
6147
     *   Note that these preconditions apply only when the actor is the
6148
     *   traveler.  If the actor is in a vehicle, so that the vehicle is
6149
     *   the traveler in a given travel operation, the vehicle's
6150
     *   travelerPreCond conditions are used instead of ours.  
6151
     */
6152
    travelerPreCond(conn) { return conn.actorTravelPreCond(self); }
6153
6154
    /* by default, actors are listed when they arrive aboard a vehicle */
6155
    isListedAboardVehicle = true
6156
6157
    /*
6158
     *   Get the object that's actually going to move when this actor
6159
     *   travels via the given connector.  In most cases this is simply the
6160
     *   actor; but when the actor is in a vehicle, travel commands move
6161
     *   the vehicle, not the actor: the actor stays in the vehicle while
6162
     *   the vehicle moves to a new location.  We determine this by asking
6163
     *   our immediate location what it thinks about the situation.
6164
     *   
6165
     *   If we have a special traveler explicitly set, it overrides the
6166
     *   traveler indicated by the location.  
6167
     */
6168
    getTraveler(conn)
6169
    {
6170
        /* 
6171
         *   Return our special traveler if we have one; otherwise, if we
6172
         *   have a location, return the traveler indicated by our
6173
         *   location; otherwise, we're the traveler. 
6174
         */
6175
        if (specialTraveler != nil)
6176
            return specialTraveler;
6177
        else if (location != nil)
6178
            return location.getLocTraveler(self, conn);
6179
        else
6180
            return self;
6181
    }
6182
6183
    /*
6184
     *   Get the "push traveler" for the actor.  This is the nominal
6185
     *   traveler that we want to use when the actor enters a command like
6186
     *   PUSH BOX NORTH.  'obj' is the object we're trying to push.  
6187
     */
6188
    getPushTraveler(obj)
6189
    {
6190
        /* 
6191
         *   If we already have a special traveler, just use the special
6192
         *   traveler.  Otherwise, if we have a location, ask the location
6193
         *   what it thinks.  Otherwise, we're the traveler. 
6194
         */
6195
        if (specialTraveler != nil)
6196
            return specialTraveler;
6197
        else if (location != nil)
6198
            return location.getLocPushTraveler(self, obj);
6199
        else
6200
            return self;
6201
    }
6202
6203
    /* is an actor traveling with us? */
6204
    isActorTraveling(actor)
6205
    {
6206
        /* we're the only actor traveling when we're the traveler */
6207
        return (actor == self);
6208
    }
6209
6210
    /* invoke a callback on each actor traveling with the traveler */
6211
    forEachTravelingActor(func)
6212
    {
6213
        /* we're the only actor, so simply invoke the callback on myself */
6214
        (func)(self);
6215
    }
6216
6217
    /* 
6218
     *   Get the actors involved in travel, when we're acting in our role
6219
     *   as a Traveler.  When the Traveler is simply the Actor, the only
6220
     *   actor involved in the travel is 'self'. 
6221
     */
6222
    getTravelerActors = [self]
6223
6224
    /* we're the self-motive actor doing the travel */
6225
    getTravelerMotiveActors = [self]
6226
6227
    /*
6228
     *   Set the "special traveler."  When this is set, we explicitly
6229
     *   perform travel through this object rather than through the
6230
     *   traveler indicated by our location.  Returns the old value, so
6231
     *   that the old value can be restored when the caller has finished
6232
     *   its need for the special traveler.  
6233
     */
6234
    setSpecialTraveler(traveler)
6235
    {
6236
        local oldVal;
6237
6238
        /* remember the old value so that we can return it */
6239
        oldVal = specialTraveler;
6240
6241
        /* remember the new value */
6242
        specialTraveler = traveler;
6243
6244
        /* return the old value */
6245
        return oldVal;
6246
    }
6247
6248
    /* our special traveler */
6249
    specialTraveler = nil
6250
6251
    /*
6252
     *   Try moving the actor into the given room in preparation for
6253
     *   travel, using pre-condition rules. 
6254
     */
6255
    checkMovingTravelerInto(room, allowImplicit)
6256
    {
6257
        /* try moving the actor into the room */
6258
        return room.checkMovingActorInto(allowImplicit);
6259
    }
6260
6261
    /*
6262
     *   Check to ensure the actor is ready to enter the given nested
6263
     *   room, using pre-condition rules.  By default, we'll ask the given
6264
     *   nested room to handle it.  
6265
     */
6266
    checkReadyToEnterNestedRoom(dest, allowImplicit)
6267
    {
6268
        /* ask the destination to do the work */
6269
        return dest.checkActorReadyToEnterNestedRoom(allowImplicit);
6270
    }
6271
6272
    /*
6273
     *   Travel within a location, as from a room to a contained nested
6274
     *   room.  This should generally be used in lieu of travelTo when
6275
     *   traveling between locations that are related directly by
6276
     *   containment rather than with TravelConnector objects.
6277
     *   
6278
     *   Travel within a location is not restricted by darkness; we assume
6279
     *   that if the nested objects are in scope at all, travel among them
6280
     *   is allowed.
6281
     *   
6282
     *   This type of travel does not trigger calls to travelerLeaving()
6283
     *   or travelerArriving().  To mitigate this loss of notification, we
6284
     *   call actorTravelingWithin() on the source and destination
6285
     *   objects.  
6286
     */
6287
    travelWithin(dest)
6288
    {
6289
        /* if I'm not going anywhere, ignore the operation */
6290
        if (dest == location)
6291
            return;
6292
6293
        /* 
6294
         *   Notify the traveler.  Note that since this is local travel
6295
         *   within a single top-level location, there's no connector. 
6296
         */
6297
        getTraveler(nil).travelerTravelWithin(self, dest);
6298
    }
6299
6300
    /*
6301
     *   Traveler interface: perform local travel, between nested rooms
6302
     *   within a single top-level location. 
6303
     */
6304
    travelerTravelWithin(actor, dest)
6305
    {
6306
        local origin;
6307
6308
        /* remember my origin */
6309
        origin = location;
6310
        
6311
        /* notify the source that we're traveling within a room */
6312
        if (origin != nil)
6313
            origin.actorTravelingWithin(origin, dest);
6314
6315
        /* 
6316
         *   if our origin and destination have different effective follow
6317
         *   locations, track the follow 
6318
         */
6319
        if (origin != nil
6320
            && dest != nil
6321
            && origin.effectiveFollowLocation != dest.effectiveFollowLocation)
6322
        {
6323
            /* 
6324
             *   notify observing objects of the travel; we're not moving
6325
             *   along a connector, so there is no connector associated
6326
             *   with the tracking information 
6327
             */
6328
            connectionTable().forEachAssoc(
6329
                {obj, val: obj.beforeTravel(self, nil)});
6330
        }
6331
6332
        /* move me to the destination */
6333
        moveInto(dest);
6334
6335
        /* 
6336
         *   recalculate the global sense context for message generation
6337
         *   purposes, since we've moved to a new location 
6338
         */
6339
        if (gAction != nil)
6340
            gAction.recalcSenseContext();
6341
6342
        /* notify the destination of the interior travel */
6343
        if (dest != nil)
6344
            dest.actorTravelingWithin(origin, dest);
6345
    }
6346
6347
    /*
6348
     *   Check for travel in the dark.  If we're in a dark room, and our
6349
     *   destination is a dark room, ask the connector for guidance.
6350
     *   
6351
     *   Travel connectors normally call this before invoking our
6352
     *   travelTo() method to carry out the travel.  The darkness check
6353
     *   usually must be made before any barrier checks.  
6354
     */
6355
    checkDarkTravel(dest, connector)
6356
    {
6357
        local origin;
6358
        
6359
        /* 
6360
         *   If we're not in the dark in the current location, there's no
6361
         *   need to check for dark-to-dark travel; light-to-dark travel
6362
         *   is always allowed. 
6363
         */
6364
        if (isLocationLit())
6365
            return;
6366
6367
        /* get the origin - this is the traveler's location */
6368
        origin = getTraveler(connector).location;
6369
6370
        /*
6371
         *   Check to see if the connector itself is visible in the dark.
6372
         *   If it is, then allow the travel without restriction.  
6373
         */
6374
        if (connector.isConnectorVisibleInDark(origin, self))
6375
            return;
6376
6377
        /*
6378
         *   We are attempting dark-to-dark travel.  We allow or disallow
6379
         *   this type of travel on a per-connector basis, so ask the
6380
         *   connector to handle it.  If the connector wishes to disallow
6381
         *   the travel, it will display an appropriate failure report and
6382
         *   terminate the command with 'exit'.  
6383
         */
6384
        connector.darkTravel(self, dest);
6385
    }
6386
6387
    /*
6388
     *   Travel to a new location. 
6389
     */
6390
    travelTo(dest, connector, backConnector)
6391
    {
6392
        /* send the request to the traveler */
6393
        getTraveler(connector)
6394
            .travelerTravelTo(dest, connector, backConnector);
6395
    }
6396
6397
    /*
6398
     *   Perform scripted travel to the given adjacent location.  This
6399
     *   looks for a directional connector in our current location whose
6400
     *   destination is the given location, and for a corresponding
6401
     *   back-connector in the destination location.  If we can find the
6402
     *   connectors, we'll perform the travel using travelTo().
6403
     *   
6404
     *   The purpose of this routine is to simplify scripted travel for
6405
     *   simple cases where directional connectors are available for the
6406
     *   desired travel.  This routine is NOT suitable for intelligent
6407
     *   goal-seeking NPC's who automatically try to find their own routes,
6408
     *   for two reasons.  First, this routine only lets an NPC move to an
6409
     *   *adjacent* location; it won't try to find a path between arbitrary
6410
     *   locations.  Second, this routine is "omniscient": it doesn't take
6411
     *   into account what the NPC knows about the connections between
6412
     *   locations, but simply finds a connector that actually provides the
6413
     *   desired travel.
6414
     *   
6415
     *   What this routine *is* suitable for are cases where we have a
6416
     *   pre-scripted series of NPC travel actions, where we have a list of
6417
     *   rooms we want the NPC to visit in order.  This routine simplifies
6418
     *   this type of scripting by automatically finding the connectors;
6419
     *   the script only has to specify the next location for the NPC to
6420
     *   visit.  
6421
     */
6422
    scriptedTravelTo(dest)
6423
    {
6424
        local conn;
6425
6426
        /* find a connector from the current location to the new location */
6427
        conn = location.getConnectorTo(self, dest);
6428
6429
        /* if we found the connector, perform the travel */
6430
        if (conn != nil)
6431
            nestedActorAction(self, TravelVia, conn);
6432
    }
6433
6434
    /*
6435
     *   Remember the last door I traveled through.  We use this
6436
     *   information for disambiguation, to boost the likelihood that an
6437
     *   actor that just traveled through a door is referring to the same
6438
     *   door in a subsequent "close" command.  
6439
     */
6440
    rememberLastDoor(obj) { lastDoorTraversed = obj; }
6441
6442
    /*
6443
     *   Remember our most recent travel.  If we know the back connector
6444
     *   (i.e., the connector that reverses the travel we're performing),
6445
     *   then we'll be able to accept a GO BACK command to attempt to
6446
     *   return to the previous location.  
6447
     */
6448
    rememberTravel(origin, dest, backConnector)
6449
    {
6450
        /* remember the destination of the travel, and the connector back */
6451
        lastTravelDest = dest;
6452
        lastTravelBack = backConnector;
6453
    }
6454
6455
    /*
6456
     *   Reverse the most recent travel.  If we're still within the same
6457
     *   destination we reached in the last travel, and we know the
6458
     *   connector we arrived through (i.e., the "back connector" for the
6459
     *   last travel, which reverses the connector we took to get here),
6460
     *   then try traveling via the connector.  
6461
     */
6462
    reverseLastTravel()
6463
    {
6464
        /* 
6465
         *   If we don't know the connector back to our previous location,
6466
         *   we obviously can't reverse the travel.  If we're not still in
6467
         *   the same location as the previous travel's destination, then
6468
         *   we can't reverse the travel either, because the back
6469
         *   connector isn't applicable to our current location.  (This
6470
         *   latter condition could only happen if we've been moved
6471
         *   somewhere without ordinary travel occurring, but this is a
6472
         *   possibility.) 
6473
         */
6474
        if (lastTravelBack == nil
6475
            || lastTravelDest == nil
6476
            || !isIn(lastTravelDest))
6477
        {
6478
            reportFailure(&cannotGoBackMsg);
6479
            exit;
6480
        }
6481
6482
        /* attempt travel via our back connector */
6483
        nestedAction(TravelVia, lastTravelBack);
6484
    }
6485
6486
    /* the last door I traversed */
6487
    lastDoorTraversed = nil
6488
6489
    /* the destination and back connector for our last travel */
6490
    lastTravelDest = nil
6491
    lastTravelBack = nil
6492
6493
    /* 
6494
     *   use a custom message for cases where we're holding a destination
6495
     *   object for BOARD, ENTER, etc 
6496
     */
6497
    checkStagingLocation(dest)
6498
    {
6499
        /* 
6500
         *   if the destination is within us, explain specifically that
6501
         *   this is the problem 
6502
         */
6503
        if (dest.isIn(self))
6504
            reportFailure(&invalidStagingContainerActorMsg, self, dest);
6505
        else
6506
            inherited(dest);
6507
6508
        /* terminate the command */
6509
        exit;
6510
    }
6511
6512
    /* 
6513
     *   Travel arrival/departure messages.  Defer to the current state
6514
     *   object on all of these.  
6515
     */
6516
    sayArriving(conn)
6517
        { curState.sayArriving(conn); }
6518
    sayDeparting(conn)
6519
        { curState.sayDeparting(conn); }
6520
    sayArrivingLocally(dest, conn)
6521
        { curState.sayArrivingLocally(dest, conn); }
6522
    sayDepartingLocally(dest, conn)
6523
        { curState.sayDepartingLocally(dest, conn); }
6524
    sayTravelingRemotely(dest, conn)
6525
        { curState.sayTravelingRemotely(dest, conn); }
6526
    sayArrivingDir(dir, conn)
6527
        { curState.sayArrivingDir(dir, conn); }
6528
    sayDepartingDir(dir, conn)
6529
        { curState.sayDepartingDir(dir, conn); }
6530
    sayArrivingThroughPassage(conn)
6531
        { curState.sayArrivingThroughPassage(conn); }
6532
    sayDepartingThroughPassage(conn)
6533
        { curState.sayDepartingThroughPassage(conn); }
6534
    sayArrivingViaPath(conn)
6535
        { curState.sayArrivingViaPath(conn); }
6536
    sayDepartingViaPath(conn)
6537
        { curState.sayDepartingViaPath(conn); }
6538
    sayArrivingUpStairs(conn)
6539
        { curState.sayArrivingUpStairs(conn); }
6540
    sayArrivingDownStairs(conn)
6541
        { curState.sayArrivingDownStairs(conn); }
6542
    sayDepartingUpStairs(conn)
6543
        { curState.sayDepartingUpStairs(conn); }
6544
    sayDepartingDownStairs(conn)
6545
        { curState.sayDepartingDownStairs(conn); }
6546
6547
    /*
6548
     *   Get the current interlocutor.  By default, we'll address new
6549
     *   conversational commands (ASK ABOUT, TELL ABOUT, SHOW TO) to the
6550
     *   last conversational partner, if that actor is still within range.
6551
     */
6552
    getCurrentInterlocutor()
6553
    {
6554
        /* 
6555
         *   if we've talked to someone before, and we can still talk to
6556
         *   them now, return that actor; otherwise we have no default 
6557
         */
6558
        if (lastInterlocutor != nil && canTalkTo(lastInterlocutor))
6559
            return lastInterlocutor;
6560
        else
6561
            return nil;
6562
    }
6563
6564
    /*
6565
     *   Get the default interlocutor.  If there's a current interlocutor,
6566
     *   and we can still talk to that actor, then that's the default
6567
     *   interlocutor.  If not, we'll return whatever actor is the default
6568
     *   for a TALK TO command.  Note that TALK TO won't necessarily have a
6569
     *   default actor; if it doesn't, we'll simply return nil.  
6570
     */
6571
    getDefaultInterlocutor()
6572
    {
6573
        local actor;
6574
        
6575
        /* check for a current interlocutor */
6576
        actor = getCurrentInterlocutor();
6577
6578
        /* 
6579
         *   if we're not talking to anyone, or if the person we were
6580
         *   talking to can no longer hear us, look for a default object
6581
         *   for a TALK TO command and use it instead as the default 
6582
         */
6583
        if (actor == nil || !canTalkTo(actor))
6584
        {
6585
            local tt;
6586
            local res;
6587
            
6588
            /* set up a TALK TO command and a resolver */
6589
            tt = new TalkToAction();
6590
            res = new Resolver(tt, gIssuingActor, gActor);
6591
            
6592
            /* get the default direct object */
6593
            actor = tt.getDefaultDobj(new EmptyNounPhraseProd(), res);
6594
            
6595
            /* if that worked, get the object from the resolve info */
6596
            if (actor != nil)
6597
                actor = actor[1].obj_;
6598
        }
6599
6600
        /* return what we found */
6601
        return actor;
6602
    }
6603
6604
    /* 
6605
     *   The most recent actor that we've interacted with through a
6606
     *   conversational command (ASK, TELL, GIVE, SHOW, etc).
6607
     */
6608
    lastInterlocutor = nil
6609
6610
    /* 
6611
     *   Our conversational "boredom" counter.  While we're in a
6612
     *   conversation, this tracks the number of turns since the last
6613
     *   conversational command from the actor we're talking to.
6614
     *   
6615
     *   Note that this state is part of the actor, even though it's
6616
     *   usually managed by the InConversationState object.  The state is
6617
     *   stored with the actor rather than with the state object because
6618
     *   it really describes the condition of the actor, not of the state
6619
     *   object.  
6620
     */
6621
    boredomCount = 0
6622
6623
    /* 
6624
     *   game-clock time (Schedulable.gameClockTime) of the last
6625
     *   conversational command addressed to us by the player character 
6626
     */
6627
    lastConvTime = -1
6628
6629
    /* 
6630
     *   Did we engage in any conversation on the current turn?  This can
6631
     *   be used as a quick check in background activity scripts when we
6632
     *   want to run a step only in the absence of any conversation on the
6633
     *   same turn. 
6634
     */
6635
    conversedThisTurn() { return lastConvTime == Schedulable.gameClockTime; }
6636
6637
    /* 
6638
     *   Note that we're performing a conversational command targeting the
6639
     *   given actor.  We'll make the actors point at each other with their
6640
     *   'lastInterlocutor' properties.  This is called on the character
6641
     *   performing the conversation command: if the player types ASK BOB
6642
     *   ABOUT BOOK, this will be called on the player character actor,
6643
     *   with 'other' set to Bob.  
6644
     */
6645
    noteConversation(other)
6646
    {
6647
        /* note that we're part of a conversational action */
6648
        noteConvAction(other);
6649
6650
        /* let the other actor know we're conversing with them */
6651
        other.noteConversationFrom(self);
6652
    }
6653
6654
    /*
6655
     *   Note that another actor is issuing a conversational command
6656
     *   targeting us.  For example, if the player types ASK BOB ABOUT
6657
     *   BOOK, then this will be called on Bob, with the player character
6658
     *   actor as 'other'. 
6659
     */
6660
    noteConversationFrom(other)
6661
    {
6662
        /* note that we're part of a conversational action */
6663
        noteConvAction(other);
6664
    }
6665
6666
    /* 
6667
     *   Note that we're taking part in a conversational action with
6668
     *   another character.  This is symmetrical - it could mean we're the
6669
     *   initiator of the conversation action or the target.  We'll
6670
     *   remember the person we're talking to, and reset our conversation
6671
     *   time counters so we know we've conversed on this turn.  
6672
     */
6673
    noteConvAction(other)
6674
    {
6675
        /* note our last conversational partner */
6676
        lastInterlocutor = other;
6677
6678
        /* set the actor to be the pronoun antecedent */
6679
        setPronounObj(other);
6680
6681
        /* 
6682
         *   reset our boredom counter, as the other actor has just spoken
6683
         *   to us 
6684
         */
6685
        boredomCount = 0;
6686
6687
        /* remember the time of our last conversation from the PC */
6688
        lastConvTime = Schedulable.gameClockTime;
6689
    }
6690
6691
    /* note that we're consulting an item */
6692
    noteConsultation(obj) { lastConsulted = obj; }
6693
6694
    /*
6695
     *   Receive notification that a TopicEntry response in our database is
6696
     *   being invoked.  We'll just pass this along to our current state.  
6697
     */
6698
    notifyTopicResponse(fromActor, entry)
6699
    {
6700
        /* let our current state handle it */
6701
        curState.notifyTopicResponse(fromActor, entry);
6702
    }
6703
6704
    /* the object we most recently consulted */
6705
    lastConsulted = nil
6706
6707
    /*
6708
     *   The actor's "agenda."  This is a list of AgendaItem objects that
6709
     *   describe things the actor wants to do of its own volition on its
6710
     *   own turn. 
6711
     */
6712
    agendaList = nil
6713
6714
    /* 
6715
     *   our special "boredom" agenda item - this makes us initiate an end
6716
     *   to an active conversation when the PC has ignored us for a given
6717
     *   number of consecutive turns 
6718
     */
6719
    boredomAgendaItem = perInstance(new BoredomAgendaItem(self))
6720
6721
    /* add an agenda item */
6722
    addToAgenda(item)
6723
    {
6724
        /* if we don't have an agenda list yet, create one */
6725
        if (agendaList == nil)
6726
            agendaList = new Vector(10);
6727
6728
        /* add the item */
6729
        agendaList.append(item);
6730
6731
        /* 
6732
         *   keep the list in ascending order of agendaOrder values - this
6733
         *   will ensure that we'll always choose the earliest item that's
6734
         *   ready to run 
6735
         */
6736
        agendaList.sort(SortAsc, {a, b: a.agendaOrder - b.agendaOrder});
6737
6738
        /* reset the agenda item */
6739
        item.resetItem();
6740
    }
6741
6742
    /* remove an agenda item */
6743
    removeFromAgenda(item)
6744
    {
6745
        /* if we have an agenda list, remove the item */
6746
        if (agendaList != nil)
6747
            agendaList.removeElement(item);
6748
    }
6749
6750
    /*
6751
     *   Execute the next item in our agenda, if there are any items in the
6752
     *   agenda that are ready to execute.  We'll return true if we found
6753
     *   an item to execute, nil if not.  
6754
     */
6755
    executeAgenda()
6756
    {
6757
        local item;
6758
6759
        /* if we don't have an agenda, there are obviously no items */
6760
        if (agendaList == nil)
6761
            return nil;
6762
        
6763
        /* remove any items that are marked as done */
6764
        while ((item = agendaList.lastValWhich({x: x.isDone})) != nil)
6765
            agendaList.removeElement(item);
6766
6767
        /* 
6768
         *   Scan for an item that's ready to execute.  Since we keep the
6769
         *   list sorted in ascending order of agendaOrder values, we can
6770
         *   just pick the earliest item in the list that's ready to run,
6771
         *   since that will be the ready-to-run item with the lowest
6772
         *   agendaOrder number. 
6773
         */
6774
        item = agendaList.valWhich({x: x.isReady});
6775
6776
        /* if we found an item, execute it */
6777
        if (item != nil)
6778
        {
6779
            try
6780
            {
6781
                /* execute the item */
6782
                item.invokeItem();
6783
            }
6784
            catch (RuntimeError err)
6785
            {
6786
                /* 
6787
                 *   If an error occurs while executing the item, mark the
6788
                 *   item as done.  This will ensure that we won't get
6789
                 *   stuck in a loop trying to execute the same item over
6790
                 *   and over, which will probably just run into the same
6791
                 *   error on each attempt.  
6792
                 */
6793
                item.isDone = true;
6794
6795
                /* re-throw the exception */
6796
                throw err;
6797
            }
6798
6799
            /* tell the caller we found an item to execute */
6800
            return true;
6801
        }
6802
        else
6803
        {
6804
            /* tell the caller we found no agenda item */
6805
            return nil;
6806
        }
6807
    }
6808
6809
    /*
6810
     *   Calculate the amount of bulk I'm holding directly.  By default,
6811
     *   we'll simply add up the "actor-encumbering bulk" of each of our
6812
     *   direct contents.
6813
     *   
6814
     *   Note that we don't differentiate here based on whether or not an
6815
     *   item is being worn, or anything else - we deliberately leave such
6816
     *   distinctions up to the getEncumberingBulk routine, so that only
6817
     *   the objects are in the business of deciding how bulky they are
6818
     *   under different circumstances.  
6819
     */
6820
    getBulkHeld()
6821
    {
6822
        local total;
6823
6824
        /* start with nothing */
6825
        total = 0;
6826
6827
        /* add the bulks of directly-contained items */
6828
        foreach (local cur in contents)
6829
            total += cur.getEncumberingBulk(self);
6830
6831
        /* return the total */
6832
        return total;
6833
    }
6834
6835
    /*
6836
     *   Calculate the total weight I'm holding.  By default, we'll add up
6837
     *   the "actor-encumbering weight" of each of our direct contents.
6838
     *   
6839
     *   Note that we deliberately only consider our direct contents.  If
6840
     *   any of the items we are directly holding contain further items,
6841
     *   getEncumberingWeight will take their weights into account; this
6842
     *   frees us from needing any special knowledge of the internal
6843
     *   structure of any items we're holding, and puts that knowledge in
6844
     *   the individual items where it belongs.  
6845
     */
6846
    getWeightHeld()
6847
    {
6848
        local total;
6849
6850
        /* start with nothing */
6851
        total = 0;
6852
6853
        /* add the weights of directly-contained items */
6854
        foreach (local cur in contents)
6855
            total += cur.getEncumberingWeight(self);
6856
6857
        /* return the total */
6858
        return total;
6859
    }
6860
6861
    /*
6862
     *   Try making room to hold the given object.  This is called when
6863
     *   checking the "room to hold object" pre-condition, such as for the
6864
     *   "take" verb.  
6865
     *   
6866
     *   If holding the new object would exceed the our maximum holding
6867
     *   capacity, we'll go through our inventory looking for objects that
6868
     *   can reduce our held bulk with implicit commands.  Objects with
6869
     *   holding affinities - "bags of holding", keyrings, and the like -
6870
     *   can implicitly shuffle the actor's possessions in a manner that
6871
     *   is neutral as far as the actor is concerned, thereby reducing our
6872
     *   active holding load.
6873
     *   
6874
     *   Returns true if an implicit command was attempted, nil if not.  
6875
     */
6876
    tryMakingRoomToHold(obj, allowImplicit)
6877
    {
6878
        local objWeight;
6879
        local objBulk;
6880
        local aff;
6881
        
6882
        /* get the amount of weight this will add if taken */
6883
        objWeight = obj.getEncumberingWeight(self);
6884
6885
        /* 
6886
         *   If this object alone is too heavy for us, give up.  We
6887
         *   distinguish this case from the case where the total (of
6888
         *   everything held plus the new item) is too heavy: in the
6889
         *   latter case we can tell the actor that they can pick this up
6890
         *   by dropping something else first, whereas if this item alone
6891
         *   is too heavy, no such advice is warranted. 
6892
         */
6893
        if (objWeight > weightCapacity)
6894
        {
6895
            reportFailure(&tooHeavyForActorMsg, obj);
6896
            exit;
6897
        }
6898
6899
        /* 
6900
         *   if taking the object would push our total carried weight over
6901
         *   our total carrying weight limit, give up 
6902
         */
6903
        if (obj.whatIfHeldBy({: getWeightHeld()}, self) > weightCapacity)
6904
        {
6905
            reportFailure(&totalTooHeavyForMsg, obj);
6906
            exit;
6907
        }
6908
6909
        /* get the amount of bulk the object will add */
6910
        objBulk = obj.getEncumberingBulk(self);
6911
        
6912
        /* 
6913
         *   if the object is simply too big to start with, we can't make
6914
         *   room no matter what we do 
6915
         */
6916
        if (objBulk > maxSingleBulk || objBulk > bulkCapacity)
6917
        {
6918
            reportFailure(&tooLargeForActorMsg, obj);
6919
            exit;
6920
        }
6921
6922
        /*
6923
         *   Test what would happen to our bulk if we were to move the
6924
         *   object into our directly held inventory.  Do this by running
6925
         *   a "what if" scenario to test moving the object into our
6926
         *   inventory, and check what effect it has on our held bulk.  If
6927
         *   it fits, we can let the caller proceed without further work.  
6928
         */
6929
        if (obj.whatIfHeldBy({: getBulkHeld()}, self) <= bulkCapacity)
6930
            return nil;
6931
6932
        /* 
6933
         *   if we're not allowed to run implicit commands, we won't be
6934
         *   able to accomplish anything, so give up 
6935
         */
6936
        if (!allowImplicit)
6937
        {
6938
            reportFailure(&handsTooFullForMsg, obj);
6939
            exit;
6940
        }
6941
6942
        /* 
6943
         *   Get "bag of holding" affinity information for my immediate
6944
         *   contents.  Consider only objects with encumbering bulk, since
6945
         *   it will do us no good to move objects without any encumbering
6946
         *   bulk.  Also ignore objects that aren't being held (some direct
6947
         *   contents aren't considered to be held, such as clothing being
6948
         *   worn).  
6949
         */
6950
        aff = getBagAffinities(contents.subset(
6951
            {x: x.getEncumberingBulk(self) != 0 && x.isHeldBy(self)}));
6952
6953
        /* if there are no bag affinities, we can't move anything around */
6954
        if (aff.length() == 0)
6955
        {
6956
            reportFailure(&handsTooFullForMsg, obj);
6957
            exit;
6958
        }
6959
6960
        /*
6961
         *   If we have at least four items, find the two that were picked
6962
         *   up most recently (according to the "holding index" value) and
6963
         *   move them to the end of the list.  In most cases, we'll only
6964
         *   have to dispose of one or two items to free up enough space
6965
         *   in our hands, so we'll probably never get to the last couple
6966
         *   of items in our list, so we're effectively ruling out moving
6967
         *   these two most recent items; but they'll be in the list if we
6968
         *   do find we need to move them after all.
6969
         *   
6970
         *   The point of this rearrangement is to avoid annoying cases of
6971
         *   moving something we just picked up, especially if we just
6972
         *   picked it up in order to carry out the command that's making
6973
         *   us free up more space now.  This looks especially stupid when
6974
         *   we perform some command that requires picking up two items
6975
         *   automatically: we pick up the first, then we put it away in
6976
         *   order to pick up the second, but then we find that we need
6977
         *   the first again.  
6978
         */
6979
        if (aff.length() >= 4)
6980
        {
6981
            local a, b;
6982
            
6983
            /* remove the two most recent items from the vector */
6984
            a = BagAffinityInfo.removeMostRecent(aff);
6985
            b = BagAffinityInfo.removeMostRecent(aff);
6986
6987
            /* re-insert them at the end of the vector */
6988
            aff.append(b);
6989
            aff.append(a);
6990
        }
6991
        
6992
        /*
6993
         *   Move each object in the list until we have reduced the bulk
6994
         *   sufficiently. 
6995
         */
6996
        foreach (local cur in aff)
6997
        {
6998
            /* 
6999
             *   Try moving this object to its bag.  If the bag is itself
7000
             *   inside this object, don't even try, since that would be an
7001
             *   attempt at circular containment.
7002
             *   
7003
             *   If the object we're trying to hold is inside this object,
7004
             *   don't move the object.  That might put the object we're
7005
             *   trying to hold out of reach, since moving an object into a
7006
             *   bag could involve closing the object or making its
7007
             *   contents not directly accessible.  
7008
             */
7009
            if (!cur.bag_.isIn(cur.obj_)
7010
                && !obj.isIn(cur.obj_)
7011
                && cur.bag_.tryPuttingObjInBag(cur.obj_))
7012
            {
7013
                /* 
7014
                 *   this routine tried tried to move the object into the
7015
                 *   bag - check our held bulk to see if we're in good
7016
                 *   enough shape yet
7017
                 */
7018
                if (obj.whatIfHeldBy({: getBulkHeld()}, self) <= bulkCapacity)
7019
                {
7020
                    /* 
7021
                     *   We've met our condition - there's no need to look
7022
                     *   any further.  Return, telling the caller we've
7023
                     *   performed an implicit command.  
7024
                     */
7025
                    return true;
7026
                }
7027
            }
7028
        }
7029
        
7030
        /*
7031
         *   If we get this far, it means that we tried every child object
7032
         *   but failed to find anything that could help.  Explain the
7033
         *   problem and abort the command.  
7034
         */
7035
        reportFailure(&handsTooFullForMsg, obj);
7036
        exit;
7037
    }
7038
7039
    /*
7040
     *   Check a bulk change of one of my direct contents. 
7041
     */
7042
    checkBulkChangeWithin(obj)
7043
    {
7044
        local objBulk;
7045
        
7046
        /* get the object's new bulk */
7047
        objBulk = obj.getEncumberingBulk(self);
7048
        
7049
        /* 
7050
         *   if this change would cause the object to exceed our
7051
         *   single-item bulk limit, don't allow it 
7052
         */
7053
        if (objBulk > maxSingleBulk || objBulk > bulkCapacity)
7054
        {
7055
            reportFailure(&becomingTooLargeForActorMsg, obj);
7056
            exit;
7057
        }
7058
7059
        /* 
7060
         *   If our total carrying capacity is exceeded with this change,
7061
         *   don't allow it.  Note that 'obj' is already among our
7062
         *   contents when this routine is called, so we can simply check
7063
         *   our current total bulk within.  
7064
         */
7065
        if (getBulkHeld() > bulkCapacity)
7066
        {
7067
            reportFailure(&handsBecomingTooFullForMsg, obj);
7068
            exit;
7069
        }
7070
    }
7071
7072
    /* 
7073
     *   Next available "holding index" value.  Each time we pick up an
7074
     *   item, we'll assign it our current holding index value and then
7075
     *   increment our value.  This gives us a simple way to keep track of
7076
     *   the order in which we picked up items we're carrying.
7077
     *   
7078
     *   Note that we make the simplifying assumption that an object can
7079
     *   be held by only one actor at a time (multi-location items are
7080
     *   generally not portable), which means that we can use a simple
7081
     *   property in each object being held to store its holding index.  
7082
     */
7083
    nextHoldingIndex = 1
7084
7085
    /* add an object to my contents */
7086
    addToContents(obj)
7087
    {
7088
        /* assign the new object our next holding index */
7089
        obj.holdingIndex = nextHoldingIndex++;
7090
7091
        /* inherit default handling */
7092
        inherited(obj);
7093
    }
7094
7095
    /*
7096
     *   Go to sleep.  This is used by the 'Sleep' action to carry out the
7097
     *   command.  By default, we simply say that we're not sleepy; actors
7098
     *   can override this to cause other actions.  
7099
     */
7100
    goToSleep()
7101
    {
7102
        /* simply report that we can't sleep now */
7103
        mainReport(&cannotSleepMsg);
7104
    }
7105
7106
    /*
7107
     *   My current "posture," which specifies how we're positioned with
7108
     *   respect to our container; this is one of the standard library
7109
     *   posture enum values (Standing, etc.) or another posture added by
7110
     *   the game.  
7111
     */
7112
    posture = standing
7113
7114
    /*
7115
     *   Get a default acknowledgment of a change to our posture.  This
7116
     *   should acknowledge the posture so that it tells us the current
7117
     *   posture.  This is used for a command such as "stand up" from a
7118
     *   chair, so that we can report the appropriate posture status in
7119
     *   our acknowledgment; we might end up being inside another nested
7120
     *   container after standing up from the chair, so we might not
7121
     *   simply be standing when we're done.   
7122
     */
7123
    okayPostureChange()
7124
    {
7125
        /* get our nominal container for our current posture */
7126
        local cont = location.getNominalActorContainer(posture);
7127
7128
        /* if the container is visible, let it handle it */
7129
        if (cont != nil && gPlayerChar.canSee(cont))
7130
        {
7131
            /* describe via the container */
7132
            cont.roomOkayPostureChange(self);
7133
        }
7134
        else
7135
        {
7136
            /* use the generic library message */
7137
            defaultReport(&okayPostureChangeMsg, posture);
7138
        }
7139
    }
7140
7141
    /*
7142
     *   Describe the actor as part of the EXAMINE description of a nested
7143
     *   room containing the actor.  'povActor' is the actor doing the
7144
     *   looking.  
7145
     */
7146
    listActorPosture(povActor)
7147
    {
7148
        /* get our nominal container for our current posture */
7149
        local cont = location.getNominalActorContainer(posture);
7150
        
7151
        /* if the container is visible, let it handle it */
7152
        if (cont != nil && povActor.canSee(cont))
7153
            cont.roomListActorPosture(self);
7154
    }
7155
7156
    /*
7157
     *   Stand up.  This is used by the 'Stand' action to carry out the
7158
     *   command. 
7159
     */
7160
    standUp()
7161
    {
7162
        /* if we're already standing, say so */
7163
        if (posture == standing)
7164
        {
7165
            reportFailure(&alreadyStandingMsg);
7166
            return;
7167
        }
7168
7169
        /* ask the location to make us stand up */
7170
        location.makeStandingUp();
7171
    }
7172
7173
    /*
7174
     *   Disembark.  This is used by the 'Get out' action to carry out the
7175
     *   command.  By default, we'll let the room handle it.  
7176
     */
7177
    disembark()
7178
    {
7179
        /* let the room handle it */
7180
        location.disembarkRoom();
7181
    }
7182
7183
    /* 
7184
     *   Set our posture to the given status.  By default, we'll simply
7185
     *   set our posture property to the new status, but actors can
7186
     *   override this to handle side effects of the change.
7187
     */
7188
    makePosture(newPosture)
7189
    {
7190
        /* remember our new posture */
7191
        posture = newPosture;
7192
    }
7193
7194
    /* 
7195
     *   Display a description of the actor's location from the actor's
7196
     *   point of view.
7197
     *   
7198
     *   If 'verbose' is true, then we'll show the full description in all
7199
     *   cases.  Otherwise, we'll show the full description if the actor
7200
     *   hasn't seen the location before, or the terse description if the
7201
     *   actor has previously seen the location.  
7202
     */
7203
    lookAround(verbose)
7204
    {
7205
        /* turn on the sense cache while we're looking */
7206
        libGlobal.enableSenseCache();
7207
        
7208
        /* show a description of my immediate location, if I have one */
7209
        if (location != nil)
7210
            location.lookAroundPov(self, self, verbose);
7211
7212
        /* turn off the sense cache now that we're done */
7213
        libGlobal.disableSenseCache();
7214
    }
7215
7216
    /*
7217
     *   Adjust a table of visible objects for 'look around'.  By default,
7218
     *   we remove any explicitly excluded objects.  
7219
     */
7220
    adjustLookAroundTable(tab, pov, actor)
7221
    {
7222
        /* remove any explicitly excluded objects */
7223
        foreach (local cur in excludeFromLookAroundList)
7224
            tab.removeElement(cur);
7225
7226
        /* inherit the base handling */
7227
        inherited(tab, pov, actor);
7228
    }
7229
7230
    /*
7231
     *   Add an object to the 'look around' exclusion list.  Returns true
7232
     *   if the object was already in the list, nil if not.  
7233
     */
7234
    excludeFromLookAround(obj)
7235
    {
7236
        /* 
7237
         *   if the object is already in the list, don't add it again -
7238
         *   just tell the caller it's already there
7239
         */
7240
        if (excludeFromLookAroundList.indexOf(obj) != nil)
7241
            return true;
7242
7243
        /* add it to the list and tell the caller it wasn't already there */
7244
        excludeFromLookAroundList.append(obj);
7245
        return nil;
7246
    }
7247
7248
    /* remove an object from the 'look around' exclusion list */
7249
    unexcludeFromLookAround(obj)
7250
    {
7251
        excludeFromLookAroundList.removeElement(obj);
7252
    }
7253
7254
    /*
7255
     *   Our list of objects explicitly excluded from 'look around'.  These
7256
     *   objects will be suppressed from any sort of listing (including in
7257
     *   the room's contents list and in special descriptions) in 'look
7258
     *   around' when this actor is doing the looking. 
7259
     */
7260
    excludeFromLookAroundList = perInstance(new Vector(5))
7261
7262
    /*
7263
     *   Get the location into which objects should be moved when the
7264
     *   actor drops them with an explicit 'drop' command.  By default, we
7265
     *   return the drop destination of our current container.  
7266
     */
7267
    getDropDestination(objToDrop, path)
7268
    {
7269
        return (location != nil
7270
                ? location.getDropDestination(objToDrop, path)
7271
                : nil);
7272
    }
7273
7274
    /*
7275
     *   The senses that determine scope for this actor.  An actor might
7276
     *   possess only a subset of the defined sense.
7277
     *   
7278
     *   By default, we give each actor all of the human senses that we
7279
     *   define, except touch.  In general, merely being able to touch an
7280
     *   object doesn't put the object in scope, because if an object
7281
     *   isn't noticed through some other sense, touch would only make an
7282
     *   object accessible if it's within arm's reach, which for our
7283
     *   purposes means that the object is being held directly by the
7284
     *   actor.  Imagine an actor in a dark room: lots of things might be
7285
     *   touchable in the sense that there's no physical barrier to
7286
     *   touching them, but without some other sense to locate the
7287
     *   objects, the actor wouldn't have any way of knowing where to
7288
     *   reach to touch things, so they're not in scope.  So, touch isn't
7289
     *   a scope sense.  
7290
     */
7291
    scopeSenses = [sight, sound, smell]
7292
7293
    /*
7294
     *   "Sight-like" senses: these are the senses that operate like sight
7295
     *   for the actor, and which the actor can use to determine the names
7296
     *   of objects and the spatial relationships between objects.  These
7297
     *   senses should operate passively, in the sense that they should
7298
     *   tend to collect sensory input continuously and without explicit
7299
     *   action by the actor, the way sight does and the way touch, for
7300
     *   example, does not.  These senses should also operate instantly,
7301
     *   in the sense that the sense can reasonably take in most or all of
7302
     *   a location at one time.
7303
     *   
7304
     *   These senses are used to determine what objects should be listed
7305
     *   in room descriptions, for example.
7306
     *   
7307
     *   By default, the only sight-like sense is sight, since other human
7308
     *   senses don't normally provide a clear picture of the spatial
7309
     *   relationships among objects.  (Touch could with some degree of
7310
     *   effort, but it can't operate passively or instantly, since
7311
     *   deliberate and time-consuming action would be necessary.)
7312
     *   
7313
     *   An actor can have more than one sight-like sense, in which case
7314
     *   the senses will act effectively as one sense that can reach the
7315
     *   union of objects reachable through the individual senses.  
7316
     */
7317
    sightlikeSenses = [sight]
7318
7319
    /* 
7320
     *   Hearing-like senses.  These are senses that the actor can use to
7321
     *   hear objects. 
7322
     */
7323
    hearinglikeSenses = [sound]
7324
7325
    /*
7326
     *   Smell-like senses.  These are senses that the actor can use to
7327
     *   smell objects. 
7328
     */
7329
    smelllikeSenses = [smell]
7330
7331
    /*
7332
     *   Communication senses: these are the senses through which the
7333
     *   actor can communicate directly with other actors through commands
7334
     *   and messages.
7335
     *   
7336
     *   Conceptually, these senses are intended to be only those senses
7337
     *   that the actors would *naturally* use to communicate, because
7338
     *   senses in this list allow direct communications via the most
7339
     *   ordinary game commands, such as "bob, go east".
7340
     *   
7341
     *   If some form of indirect communication is possible via a sense,
7342
     *   but that form is not something the actor would think of as the
7343
     *   most natural, default form of communication, it should *not* be
7344
     *   in this list.  For example, two sighted persons who can see one
7345
     *   another but cannot hear one another could still communicate by
7346
     *   writing messages on pieces of paper, but they would ordinarily
7347
     *   communicate by talking.  In such a case, sound should be in the
7348
     *   list but sight should not be, because sight is not a natural,
7349
     *   default form of communications for the actors.  
7350
     */
7351
    communicationSenses = [sound]
7352
    
7353
    /*
7354
     *   Determine if I can communicate with the given character via a
7355
     *   natural, default form of communication that we share with the
7356
     *   other character.  This determines if I can talk to the other
7357
     *   character.  We'll return true if I can talk to the other actor,
7358
     *   nil if not.
7359
     *   
7360
     *   In order for the player character to issue a command to a
7361
     *   non-player character (as in "bob, go east"), the NPC must be able
7362
     *   to sense the PC via at least one communication sense that the two
7363
     *   actors have in common.
7364
     *   
7365
     *   Likewise, in order for a non-player character to say something to
7366
     *   the player, the player must be able to sense the NPC via at least
7367
     *   one communication sense that the two actors have in common.  
7368
     */
7369
    canTalkTo(actor)
7370
    {
7371
        local common;
7372
        
7373
        /* 
7374
         *   first, get a list of the communications senses that we have
7375
         *   in common with the other actor - we must have a sense channel
7376
         *   via this sense 
7377
         */
7378
        common = communicationSenses.intersect(actor.communicationSenses);
7379
7380
        /* 
7381
         *   if there are no common senses, we can't communicate,
7382
         *   regardless of our physical proximity 
7383
         */
7384
        if (common == [])
7385
            return nil;
7386
7387
        /* 
7388
         *   Determine how well the other actor can sense me in these
7389
         *   senses.  Note that all that matters it that the actor can
7390
         *   hear me, because we're determine if I can talk to the other
7391
         *   actor - it doesn't matter if I can hear the other actor.  
7392
         */
7393
        foreach (local curSense in common)
7394
        {
7395
            local result;
7396
7397
            /* 
7398
             *   determine how well the other actor can sense me in this
7399
             *   sense 
7400
             */
7401
            result = actor.senseObj(curSense, self);
7402
7403
            /* check whether or not this is good enough */
7404
            if (actor.canBeTalkedTo(self, curSense, result))
7405
                return true;
7406
        }
7407
7408
        /* 
7409
         *   if we get this far, we didn't find any senses with a clear
7410
         *   enough communications channel - we can't talk to the other
7411
         *   actor 
7412
         */
7413
        return nil;
7414
    }
7415
7416
    /*
7417
     *   Determine whether or not I can understand an attempt by another
7418
     *   actor to talk to me.  'talker' is the actor doing the talking.
7419
     *   'sense' is the sense we're testing; this will always be a sense
7420
     *   in our communicationSenses list, and will always be a
7421
     *   communications sense we have in common with the other actor.
7422
     *   'info' is a SenseInfo object giving information on the clarity of
7423
     *   the sense path to the other actor.
7424
     *   
7425
     *   We return true if we can understand the communication, nil if
7426
     *   not.  There is no middle ground where we can partially
7427
     *   understand; we can either understand or not.
7428
     *   
7429
     *   Note that this routine is concerned only with our ability to
7430
     *   sense the communication.  The result here should NOT pay any
7431
     *   attention to whether or not we can actually communicate given a
7432
     *   clear sense path - for example, this routine should not reflect
7433
     *   whether or not we have a spoken language in common with the other
7434
     *   actor.
7435
     *   
7436
     *   This is a service method for canTalkTo.  This is broken out as a
7437
     *   separate method so that individual actors can override the
7438
     *   necessary conditions for communications in particular senses.  
7439
     */
7440
    canBeTalkedTo(talker, sense, info)
7441
    {
7442
        /*   
7443
         *   By default, we allow communication if the sense path is
7444
         *   transparent or distant.  We don't care what the sense is,
7445
         *   since we know we'll never be asked about a sense that's not
7446
         *   in our communicationSenses list.  
7447
         */
7448
        return info.trans is in (transparent, distant);
7449
    }
7450
7451
    /*
7452
     *   Flag: we wait for commands issued to other actors to complete
7453
     *   before we get another turn.  If this is true, then whenever we
7454
     *   issue a command to another actor ("bob, go north"), we will not
7455
     *   get another turn until the other actor has finished executing the
7456
     *   full set of commands we issued.
7457
     *   
7458
     *   By default, this is true, which means that we wait for other
7459
     *   actors to finish all of the commands we issue before we take
7460
     *   another turn.  
7461
     *   
7462
     *   If this is set to nil, we'll continue to take turns while the
7463
     *   other actor carries out our commands.  In this case, the only
7464
     *   time cost to us of issuing a command is given by orderingTime(),
7465
     *   which normally takes one turn for issuing a command, regardless
7466
     *   of the command's complexity.  Some games might wish to use this
7467
     *   mode for interesting effects with NPC's carrying out commands in
7468
     *   parallel with the player, but it's an unconventional style that
7469
     *   some players might find confusing, so we don't use this mode by
7470
     *   default.  
7471
     */
7472
    issueCommandsSynchronously = true
7473
7474
    /*
7475
     *   Flag: the "target actor" of the command line automatically reverts
7476
     *   to this actor at the end of a sentence, when this actor is the
7477
     *   issuer of a command.  If this flag is nil, an explicit target
7478
     *   actor stays in effect until the next explicit target actor (or the
7479
     *   end of the entire command line, if no other explicit target actors
7480
     *   are named); if this flag is true, a target actor is in effect only
7481
     *   until the end of a sentence.
7482
     *   
7483
     *   Consider this command line:
7484
     *   
7485
     *   >Bob, go north and get fuel cell. Get log tape.
7486
     *   
7487
     *   If this flag is nil, then the second sentence ("get log tape") is
7488
     *   interpreted as a command to Bob, because Bob is explicitly
7489
     *   designated as the target of the command, and this remains in
7490
     *   effect until the end of the entire command line.
7491
     *   
7492
     *   If this flag is true, on the other hand, then the second sentence
7493
     *   is interpreted as a command to the player character, because the
7494
     *   target actor designation ("Bob,") lasts only until the end of the
7495
     *   sentence.  Once a new sentence begins, we revert to the issuing
7496
     *   actor (the player character, since the command came from the
7497
     *   player via the keyboard).
7498
     */
7499
    revertTargetActorAtEndOfSentence = nil
7500
7501
    /*
7502
     *   The amount of time, in game clock units, it takes me to issue an
7503
     *   order to another actor.  By default, it takes one unit (which is
7504
     *   usually equal to one turn) to issue a command to another actor.
7505
     *   However, if we are configured to wait for our issued commands to
7506
     *   complete in full, the ordering time is zero; we don't need any
7507
     *   extra wait time in this case because we'll wait the full length
7508
     *   of the issued command to begin with.  
7509
     */
7510
    orderingTime(targetActor)
7511
    {
7512
        return issueCommandsSynchronously ? 0 : 1;
7513
    }
7514
7515
    /*
7516
     *   Wait for completion of a command that we issued to another actor.
7517
     *   The parser calls this routine after each time we issue a command
7518
     *   to another actor.
7519
     *   
7520
     *   If we're configured to wait for completion of orders given to
7521
     *   other actors before we get another turn, we'll set ourselves up
7522
     *   in waiting mode.  Otherwise, we'll do nothing.  
7523
     */
7524
    waitForIssuedCommand(targetActor)
7525
    {
7526
        /* if we can issue commands asynchronously, there's nothing to do */
7527
        if (!issueCommandsSynchronously)
7528
            return;
7529
7530
        /* 
7531
         *   Add an empty pending command at the end of the target actor's
7532
         *   queue.  This command won't do anything when executed; its
7533
         *   purpose is to let us track whether or not the target is still
7534
         *   working on commands we have issued up to this point, which we
7535
         *   can tell by looking to see whether our empty command is still
7536
         *   in the actor's queue.
7537
         *   
7538
         *   Note that we can't simply wait until the actor's queue is
7539
         *   empty, because the actor could acquire new commands while
7540
         *   it's working on our pending commands, and we wouldn't want to
7541
         *   wait for those to finish.  Adding a dummy pending command is
7542
         *   a reliable way of tracking the actor's queue, because any
7543
         *   changes to the target actor's command queue will leave our
7544
         *   dummy command in its proper place until the target actor gets
7545
         *   around to executing it, at which point it will be removed.
7546
         *   
7547
         *   Remember the dummy pending command in a property of self, so
7548
         *   that we can check later to determine when the command has
7549
         *   finished.  
7550
         */
7551
        waitingForActor = targetActor;
7552
        waitingForInfo = new PendingCommandMarker(self);
7553
        targetActor.pendingCommand.append(waitingForInfo);
7554
    }
7555
7556
    /* 
7557
     *   Synchronous command processing: the target actor and dummy
7558
     *   pending command we're waiting for.  When these are non-nil, we
7559
     *   won't take another turn until the given PendingCommandInfo has
7560
     *   been removed from the given target actor's command queue. 
7561
     */
7562
    waitingForActor = nil
7563
    waitingForInfo = nil
7564
7565
    /*
7566
     *   Add the given actor to the list of actors accompanying my travel
7567
     *   on the current turn.  This does NOT set an actor in "follow mode"
7568
     *   or "accompany mode" or anything like that - don't use this to make
7569
     *   an actor follow me around.  Instead, this makes the given actor go
7570
     *   with us for the CURRENT travel only - the travel we're already in
7571
     *   the process of performing to process the current TravelVia action.
7572
     */
7573
    addAccompanyingActor(actor)
7574
    {
7575
        /* if we don't have the accompanying actor vector yet, create it */
7576
        if (accompanyingActors == nil)
7577
            accompanyingActors = new Vector(8);
7578
7579
        /* add the actor to my list */
7580
        accompanyingActors.append(actor);
7581
    }
7582
7583
    /* 
7584
     *   My vector of actors who are accompanying me. 
7585
     *   
7586
     *   This is for internal bookkeeping only, and it applies to the
7587
     *   current travel only.  This is NOT a general "follow mode" setting,
7588
     *   and it shouldn't be used to get me to follow another actor or
7589
     *   another actor to follow me.  To make me accompany another actor,
7590
     *   simply override accompanyTravel() so that it returns a suitable
7591
     *   ActorState object.  
7592
     */
7593
    accompanyingActors = nil
7594
7595
    /*
7596
     *   Get the list of objects I can follow.  This is a list of all of
7597
     *   the objects which I have seen departing a location - these are
7598
     *   all in scope for 'follow' commands.  
7599
     */
7600
    getFollowables()
7601
    {
7602
        /* return the list of the objects we know about */
7603
        return followables_.mapAll({x: x.obj});
7604
    }
7605
7606
    /* 
7607
     *   Do I track departing objects for following the given object?
7608
     *   
7609
     *   By default, the player character tracks everyone, and NPC's track
7610
     *   only the actor they're presently tasked to follow.  Most NPC's
7611
     *   will never accept 'follow' commands, so there's no need to track
7612
     *   everyone all the time; for efficiency, we take advantage of this
7613
     *   assumption so that we can avoid storing a bunch of tracking
7614
     *   information that will never be used.
7615
     */
7616
    wantsFollowInfo(obj)
7617
    {
7618
        /* 
7619
         *   by default, the player character tracks everyone, and NPC's
7620
         *   track only the object (if any) they're currently tasked to
7621
         *   follow 
7622
         */
7623
        return isPlayerChar() || followingActor == obj;
7624
    }
7625
7626
    /*
7627
     *   Receive notification that an object is leaving its current
7628
     *   location as a result of the action we're currently processing.
7629
     *   Actors (and possibly other objects) will broadcast this
7630
     *   notification to all Actor objects connected in any way by
7631
     *   containment when they move under their own power (such as with
7632
     *   Actor.travelTo) to a new location.  We'll keep tracking
7633
     *   information if we are configured to keep tracking information for
7634
     *   the given object and we can see the given object.  Note that this
7635
     *   is called when the object is still at the source end of the travel
7636
     *   - the important thing is that we see the object departing.
7637
     *   
7638
     *   'obj' is the object that is seen to be leaving, and 'conn' is the
7639
     *   TravelConnector it is taking.
7640
     *   
7641
     *   'conn' is the connector being traversed.  If we're simply being
7642
     *   observed in this location (as in a call to setHasSeen), rather
7643
     *   than being observed to leave the location, the connector will be
7644
     *   nil.
7645
     *   
7646
     *   'from' is the effective starting location of the travel.  This
7647
     *   isn't necessarily the departing object's location, since the
7648
     *   departing object could be inside a vehicle or some other kind of
7649
     *   traveler object.
7650
     *   
7651
     *   Note that this notification is sent only to actors with some sort
7652
     *   of containment connection to the object that's moving, because a
7653
     *   containment connection is necessary for there to be a sense
7654
     *   connection.  
7655
     */
7656
    trackFollowInfo(obj, conn, from)
7657
    {
7658
        local info;
7659
        
7660
        /* 
7661
         *   If we're not tracking the given object, or we can't see the
7662
         *   given object, ignore the notification.  In addition, we
7663
         *   obviously have no need to track ourselves.x  
7664
         */
7665
        if (obj == self || !wantsFollowInfo(obj) || !canSee(obj))
7666
            return;
7667
7668
        /* 
7669
         *   If we already have a FollowInfo for the given object, re-use
7670
         *   the existing one; otherwise, create a new one and add it to
7671
         *   our tracking list. 
7672
         */
7673
        info = followables_.valWhich({x: x.obj == obj});
7674
        if (info == nil)
7675
        {
7676
            /* we don't have an existing one - create a new one */
7677
            info = new FollowInfo();
7678
            info.obj = obj;
7679
7680
            /* add it to our list */
7681
            followables_ += info;
7682
        }
7683
7684
        /* remember information about the travel */
7685
        info.connector = conn;
7686
        info.sourceLocation = from;
7687
    }
7688
7689
    /*
7690
     *   Get information on what to do to make this actor follow the given
7691
     *   object.  This returns a FollowInfo object that reports our last
7692
     *   knowledge of the given object's location and departure, or nil if
7693
     *   we don't know anything about how to follow the actor.  
7694
     */
7695
    getFollowInfo(obj)
7696
    {
7697
        return followables_.valWhich({x: x.obj == obj});
7698
    }
7699
7700
    /*
7701
     *   By default, all actors are followable.
7702
     */
7703
    verifyFollowable()
7704
    {
7705
        return true;
7706
    }
7707
7708
    /*
7709
     *   Verify a "follow" command being performed by this actor.  
7710
     */
7711
    actorVerifyFollow(obj)
7712
    {
7713
        /* 
7714
         *   check to see if we're in the same effective follow location
7715
         *   as the target; if we are, it makes no sense to follow the
7716
         *   target, since we're already effectively at the same place 
7717
         */
7718
        if (obj.location != nil
7719
            && (location.effectiveFollowLocation
7720
                == obj.location.effectiveFollowLocation))
7721
        {
7722
            /*
7723
             *   We're in the same location as the target.  If we're the
7724
             *   player character, this makes no sense, because the player
7725
             *   character can't go into follow mode (as that would take
7726
             *   away the player's ability to control the player
7727
             *   character).  If we're an NPC, though, this simply tells
7728
             *   us to go into follow mode for the target, so there's
7729
             *   nothing wrong with it.  
7730
             */
7731
            if (isPlayerChar)
7732
            {
7733
                /* 
7734
                 *   The target is right here, but we're the player
7735
                 *   character, so it makes no sense for us to go into
7736
                 *   follow mode.  If we can see the target, complain that
7737
                 *   it's already here; if not, we can only assume it's
7738
                 *   here, but we can't know for sure.  
7739
                 */
7740
                if (canSee(obj))
7741
                    illogicalNow(&followAlreadyHereMsg);
7742
                else
7743
                    illogicalNow(&followAlreadyHereInDarkMsg);
7744
            }
7745
        }
7746
        else if (!canSee(obj))
7747
        {
7748
            /* 
7749
             *   The target isn't here, and we can't see it from here, so
7750
             *   we must want to follow it to its current location.  Get
7751
             *   information on how we will follow the target.  If there's
7752
             *   no such information, we obviously can't do any following
7753
             *   because we never saw the target go anywhere in the first
7754
             *   place.  
7755
             */
7756
            if (getFollowInfo(obj) == nil)
7757
            {
7758
                /* we've never heard of the target */
7759
                illogicalNow(&followUnknownMsg);
7760
            }
7761
        }
7762
    }
7763
7764
    /*
7765
     *   Carry out a "follow" command being performed by this actor.  
7766
     */
7767
    actorActionFollow(obj)
7768
    {
7769
        local canSeeObj;
7770
7771
        /* note whether or not we can see the target */
7772
        canSeeObj = canSee(obj);
7773
        
7774
        /* 
7775
         *   If we're not the PC, check to see if this is a follow-mode
7776
         *   request; otherwise, try to go to the location of the target.
7777
         */
7778
        if (!isPlayerChar && canSeeObj)
7779
        {
7780
            /*
7781
             *   If we're not already following this actor, acknowledge the
7782
             *   request and go into 'follow' mode.  If we're already
7783
             *   following this actor, and we didn't issue the command to
7784
             *   ourself, let them know we're already in the requested
7785
             *   mode.  Otherwise, ignore it silently - if we issued the
7786
             *   command to ourself, it's because we're just executing our
7787
             *   own 'follow' mode imperative. 
7788
             */
7789
            if (followingActor != obj)
7790
            {
7791
                /* let them know we're going to follow the actor now */
7792
                reportAfter(&okayFollowModeMsg);
7793
7794
                /* go into follow mode */
7795
                followingActor = obj;
7796
            }
7797
            else if (gIssuingActor != self)
7798
            {
7799
                /* let them know we're already in follow mode */
7800
                reportAfter(&alreadyFollowModeMsg);
7801
            }
7802
7803
            /* 
7804
             *   if we're already in the target's effective follow
7805
             *   location, that's all we need to do 
7806
             */
7807
            if (location.effectiveFollowLocation
7808
                == obj.location.effectiveFollowLocation)
7809
                return;
7810
        }
7811
7812
        /* 
7813
         *   If we can see the target, AND we're in the same top-level
7814
         *   location as the target, then simply use a "local travel"
7815
         *   operation to move into the same location.  This only works
7816
         *   with targets that are within the same top-level location,
7817
         *   since that's the whole point of the local-travel routines.
7818
         *   For non-local travel, we need to perform a full-fledged travel
7819
         *   command instead.  
7820
         */
7821
        if (canSeeObj && isIn(obj.getOutermostRoom()))
7822
        {
7823
            /* 
7824
             *   We have no information, so we will only have made it past
7825
             *   verification if we can see the other actor from our
7826
             *   current location.  Try moving to the other actor's
7827
             *   effective follow location.  
7828
             */
7829
            obj.location.effectiveFollowLocation.checkMovingActorInto(true);
7830
7831
            /*
7832
             *   Since checkMovingActorInto will do its work through
7833
             *   implicit actions, if we're the player character, then the
7834
             *   entire action will have been performed implicitly, so we
7835
             *   won't have a real report for the series of generated
7836
             *   actions, just implied action announcements.  If we're an
7837
             *   NPC, on the other hand, we'll generate the full reports,
7838
             *   since NPC implied actions simply show what the actor is
7839
             *   doing.  So, if we're the PC, generate an additional
7840
             *   default acknowledgment of the 'follow' action. 
7841
             */
7842
            if (isPlayerChar)
7843
                defaultReport(&okayFollowInSightMsg,
7844
                              location.effectiveFollowLocation);
7845
        }
7846
        else
7847
        {
7848
            local info;
7849
            local srcLoc;
7850
            
7851
            /* get the information on how to follow the target */
7852
            info = getFollowInfo(obj);
7853
7854
            /* get the effective follow location we have to be in */
7855
            srcLoc = info.sourceLocation.effectiveFollowLocation;
7856
7857
            /* if there's no connector, we can't go anywhere */
7858
            if (info.connector == nil)
7859
            {
7860
                /* 
7861
                 *   We have no departure information, so we can't follow
7862
                 *   the actor.  If we're currently within sight of the
7863
                 *   location where we last saw the actor, it means that we
7864
                 *   saw the actor here, then went somewhere else, then
7865
                 *   came back, and in our absence the actor itself
7866
                 *   departed.  In this case, report that we don't know
7867
                 *   where the actor went.
7868
                 *   
7869
                 *   If we're not in sight of the location where we last
7870
                 *   saw the actor, then instead remind the player of where
7871
                 *   that was.  
7872
                 */
7873
                if (canSee(srcLoc))
7874
                {
7875
                    /* 
7876
                     *   we're where we last saw the actor, but the actor
7877
                     *   must have departed while we were away - so we
7878
                     *   simply don't know where the actor went 
7879
                     */
7880
                    reportFailure(&followUnknownMsg);
7881
                }
7882
                else
7883
                {
7884
                    /* 
7885
                     *   we've gone somewhere else since we last saw the
7886
                     *   actor, so remind the player of where it was that
7887
                     *   we saw the actor 
7888
                     */
7889
                    reportFailure(&cannotFollowFromHereMsg, srcLoc);
7890
                }
7891
7892
                /* in any case, that's all we can do now */
7893
                return;
7894
            }
7895
7896
            /*
7897
             *   Before we can follow the target, we must be in the same
7898
             *   effective location that the target was in when we
7899
             *   observed the target leaving.
7900
             */
7901
            if (location.effectiveFollowLocation != srcLoc)
7902
            {
7903
                /* 
7904
                 *   If we can't even see the effective follow location, we
7905
                 *   must have last observed the other actor traveling from
7906
                 *   an unrelated location.  In this case, simply say that
7907
                 *   we don't know where the followee went. 
7908
                 */
7909
                if (!canSee(srcLoc))
7910
                {
7911
                    reportFailure(&cannotFollowFromHereMsg, srcLoc);
7912
                    return;
7913
                }
7914
7915
                /* 
7916
                 *   Try moving into the same location, by invoking the
7917
                 *   pre-condition handler for moving me into the
7918
                 *   effective follow location from our memory of the
7919
                 *   actor's travel.  We *could* run this as an actual
7920
                 *   precondition, but it's easier to run it here now that
7921
                 *   we've sorted out exactly what we want to do.  
7922
                 */
7923
                srcLoc.checkMovingActorInto(true);
7924
            }
7925
        
7926
            /* perform a TravelVia action on the connector */
7927
            nestedAction(TravelVia, info.connector);
7928
        }
7929
    }
7930
7931
    /*
7932
     *   Our list of followable information.  Each entry in this list is a
7933
     *   FollowInfo object that tracks a particular followable.  
7934
     */
7935
    followables_ = []
7936
7937
    /* determine if I've ever seen the given object */
7938
    hasSeen(obj) { return obj.(seenProp); }
7939
7940
    /* mark the object to remember that I've seen it */
7941
    setHasSeen(obj) { obj.noteSeenBy(self, seenProp); }
7942
7943
    /* receive notification that another actor is observing us */
7944
    noteSeenBy(actor, prop)
7945
    {
7946
        /* do the standard work to remember that we've been seen */
7947
        inherited(actor, prop);
7948
7949
        /* 
7950
         *   Update the follow tracking information with the latest
7951
         *   observed location.  We're merely observing the fact that the
7952
         *   actor is here, not that the actor is departing, so the
7953
         *   connector is nil.
7954
         *   
7955
         *   The point of noting the actor's presence in the "follow info"
7956
         *   is that we want to replace any previous memory we have of the
7957
         *   actor departing from another location.  Now that we know where
7958
         *   the actor is, any old memory of the actor having left another
7959
         *   location is now irrelevant.  We only keep track of one "follow
7960
         *   info" record per actor, so this new record will replace any
7961
         *   older record.  
7962
         */
7963
        actor.trackFollowInfo(self, nil, location);
7964
    }
7965
7966
    /* 
7967
     *   Determine if I know about the given object.  I know about an
7968
     *   object if it's specifically marked as known to me; I also know
7969
     *   about the object if I can see it now, or if I've ever seen it in
7970
     *   the past.  
7971
     */
7972
    knowsAbout(obj) { return canSee(obj) || hasSeen(obj) || obj.(knownProp); }
7973
7974
    /* mark the object as known to me */
7975
    setKnowsAbout(obj) { obj.(knownProp) = true; }
7976
7977
    /*
7978
     *   My 'seen' property.  By default, this is simply 'seen', which
7979
     *   means that we don't distinguish who's seen what - in other words,
7980
     *   there's a single, global 'seen' flag per object, and if anyone's
7981
     *   ever seen something, then we consider that to mean everyone has
7982
     *   seen it.
7983
     *   
7984
     *   Some games might want to track each NPC's sight memory
7985
     *   separately, or at least they might want to track it individually
7986
     *   for a few specific NPC's.  You can do this by making up a new
7987
     *   property name for each NPC whose sight memory you want to keep
7988
     *   separate, and simply setting 'seenProp' to that property name for
7989
     *   each such NPC.  For example, for Bob, you could make the property
7990
     *   bobHasSeen, so in Bob you'd define 'sightProp = &bobHasSeen'.  
7991
     */
7992
    seenProp = &seen
7993
7994
    /*
7995
     *   My 'known' property.  By default, this is simply 'known', which
7996
     *   means that we don't distinguish who knows what.
7997
     *   
7998
     *   As with 'seenProp' above, if you want to keep track of each NPC's
7999
     *   knowledge separately, you must override this property for each
8000
     *   NPC who's to have its own knowledge base to use a separate
8001
     *   property name.  For example, if you want to keep track of what
8002
     *   Bob knows individually, you could define 'knownProp = &bobKnows'
8003
     *   in Bob.  
8004
     */
8005
    knownProp = &isKnown
8006
8007
    /*
8008
     *   Determine if the actor recognizes the given object as a "topic,"
8009
     *   which is an object that represents some knowledge the actor can
8010
     *   use in conversations, consultations, and the like.
8011
     *   
8012
     *   By default, we'll recognize any Topic object marked as known, and
8013
     *   we'll recognize any game object for which our knowsAbout(obj)
8014
     *   returns true.  Games might wish to override this in some cases to
8015
     *   limit or expand an actor's knowledge according to what the actor
8016
     *   has experienced of the setting or story.  Note that it's often
8017
     *   easier to control actor knowledge using the lower-level
8018
     *   knowsAbout() and setKnowsAbout() methods, though.  
8019
     */
8020
    knowsTopic(obj)
8021
    {
8022
        /* we know the object as a topic if we know about it at all */
8023
        return knowsAbout(obj);
8024
    }
8025
8026
    /*
8027
     *   Determine if the given object is a likely topic for a
8028
     *   conversational action performed by this actor.  By default, we'll
8029
     *   return true if the topic is known, nil if not.  
8030
     */
8031
    isLikelyTopic(obj)
8032
    {
8033
        /* if the object is known, it's a possible topic */
8034
        return knowsTopic(obj);
8035
    }
8036
8037
    /* we are the owner of any TopicEntry objects contained within us */
8038
    getTopicOwner() { return self; }
8039
8040
    /*
8041
     *   Suggest topics of conversation.  This is called by the TOPICS
8042
     *   command (in which case 'explicit' is true), and whenever we first
8043
     *   engage a character in a stateful conversation (in which case
8044
     *   'explicit' is nil).
8045
     *   
8046
     *   We'll show the list of suggested topics associated with our
8047
     *   current conversational partner.  If there are no topics, we'll say
8048
     *   nothing unless 'explicit' is true, in which case we'll simply say
8049
     *   that there are no topics that the player character is thinking
8050
     *   about.
8051
     *   
8052
     *   The purpose of this method is to let the game author keep an
8053
     *   "inventory" of topics with this actor for a given conversational
8054
     *   partner.  This inventory is meant to represent the topics that on
8055
     *   the player character's mind - things the player character wants to
8056
     *   talk about with the other actor.  Note that we're talking about
8057
     *   what the player *character* is thinking about - obviously we don't
8058
     *   know what's on the player's mind.
8059
     *   
8060
     *   When we enter conversation, or when the player asks for advice,
8061
     *   we'll show this inventory.  The idea is to help guide the player
8062
     *   through a conversation without the more heavy-handed device of a
8063
     *   formal conversation menu system, so that conversations have a more
8064
     *   free-form feel without leaving the player hunting in the dark for
8065
     *   the magic ASK ABOUT topic.
8066
     *   
8067
     *   The TOPICS system is entirely optional.  If a game doesn't specify
8068
     *   any SuggestedTopic objects, then this routine will simply never be
8069
     *   called, and the TOPICS command won't be allowed.  Some authors
8070
     *   think it gives away too much to provide a list of topic
8071
     *   suggestions like this, and others don't like anything that smacks
8072
     *   of a menu system because they think it destroys the illusion
8073
     *   created by the text-input command line that the game is boundless.
8074
     *   Authors who feel this way can just ignore the TOPICS system.  But
8075
     *   be aware that the illusion of boundlessness isn't always a good
8076
     *   thing for players; hunting around for ASK ABOUT topics can make
8077
     *   the game's limits just as obvious, if not more so, by exposing the
8078
     *   vast number of inputs for which the actor doesn't have a good
8079
     *   response.  Players aren't stupid - a string of variations on "I
8080
     *   don't know about that" is just as obviously mechanistic as a
8081
     *   numbered list of menu choices.  Using the TOPICS system might be a
8082
     *   good compromise for many authors, since the topic list can help
8083
     *   guide the player to the right questions without making the player
8084
     *   feel straitjacketed by a menu list.  
8085
     */
8086
    suggestTopics(explicit)
8087
    {
8088
        local actor;
8089
        
8090
        /* 
8091
         *   if we're talking to someone, look up their suggested topics;
8092
         *   otherwise, we have nothing to suggest 
8093
         */
8094
        if ((actor = getCurrentInterlocutor()) != nil)
8095
        {
8096
            /* 
8097
             *   we're talking to someone - suggest topics appropriate to
8098
             *   the person we're talking to 
8099
             */
8100
            actor.suggestTopicsFor(self, explicit);
8101
        }
8102
        else if (explicit)
8103
        {
8104
            /* we're not talking to anyone, so there's nothing to suggest */
8105
            gLibMessages.noTopicsNotTalking;
8106
        }
8107
    }
8108
8109
    /*
8110
     *   Suggest topics that the given actor might want to talk to us
8111
     *   about.  The given actor is almost always the player character,
8112
     *   since generally NPC's don't talk to one another using
8113
     *   conversation commands (there'd be no point; they're simple
8114
     *   programmed automata, not full-blown AI's).  
8115
     */
8116
    suggestTopicsFor(actor, explicit)
8117
    {
8118
        /* by default, let our state suggest topics */
8119
        curState.suggestTopicsFor(actor, explicit);
8120
    }
8121
8122
    /*
8123
     *   Receive notification that a command is being carried out in our
8124
     *   presence. 
8125
     */
8126
    beforeAction()
8127
    {
8128
        /*
8129
         *   If another actor is trying to take something in my inventory,
8130
         *   by default, do not allow it. 
8131
         */
8132
        if (gActor != self
8133
            && (gActionIs(Take) || gActionIs(TakeFrom))
8134
            && gDobj.isIn(self))
8135
        {
8136
            /* check to see if we want to allow this action */
8137
            checkTakeFromInventory(gActor, gDobj);
8138
        }
8139
8140
        /* let our state object take a look at the action */
8141
        curState.beforeAction();
8142
    }
8143
8144
    /* 
8145
     *   Perform any actor-specific processing for an action.  The main
8146
     *   command processor invokes this on gActor after notifying nearby
8147
     *   objects via beforeAction(), but before carrying out the main
8148
     *   action of the command.  
8149
     */
8150
    actorAction()
8151
    {
8152
        /* do nothing by default */
8153
    }
8154
8155
    /*
8156
     *   Receive notification that a command has just been carried out in
8157
     *   our presence.  
8158
     */
8159
    afterAction()
8160
    {
8161
        /* let the state object handle it */
8162
        curState.afterAction();
8163
    }
8164
8165
    /* receive a notification that someone is about to travel */
8166
    beforeTravel(traveler, connector)
8167
    {
8168
        /* let the state object handle it */
8169
        curState.beforeTravel(traveler, connector);
8170
8171
        /* 
8172
         *   If desired, track the departure so that we can follow the
8173
         *   traveler later.  First, track the departure of each actor
8174
         *   traveling with the traveler.  
8175
         */
8176
        traveler.forEachTravelingActor(
8177
            {actor: trackFollowInfo(actor, connector, traveler.location)});
8178
8179
        /* 
8180
         *   if the traveler is distinct from the actors traveling, track
8181
         *   it as well 
8182
         */
8183
        if (!traveler.isActorTraveling(traveler))
8184
            trackFollowInfo(traveler, connector, traveler.location);
8185
    }
8186
8187
    /* receive a notification that someone has just traveled here */
8188
    afterTravel(traveler, connector)
8189
    {
8190
        /* let the state object handle it */
8191
        curState.afterTravel(traveler, connector);
8192
    }
8193
8194
    /*
8195
     *   Receive notification that I'm initiating travel.  This is called
8196
     *   on the actor performing the travel action before the travel is
8197
     *   actually carried out.  
8198
     */
8199
    actorTravel(traveler, connector)
8200
    {
8201
        /*
8202
         *   If other actors are accompanying me on this travel, run the
8203
         *   same travel action on the accompanying actors, using nested
8204
         *   actions.  
8205
         */
8206
        if (accompanyingActors != nil
8207
            && accompanyingActors.length() != 0)
8208
        {
8209
            /* 
8210
             *   Run the same travel action as a nested action on each
8211
             *   accompanying actor.  Skip this for any accompanying actor
8212
             *   we're carrying, as they'll naturally go with us as a
8213
             *   result of being carried.  
8214
             */
8215
            foreach (local cur in accompanyingActors)
8216
            {
8217
                /* if the actor's not being carried, run the same action */
8218
                if (!cur.isIn(self))
8219
                    nestedActorAction(cur, TravelVia, gDobj);
8220
            }
8221
8222
            /* 
8223
             *   The accompanying actor list applies for this single group
8224
             *   travel command, so now that we've moved everyone, we have
8225
             *   no further need for the list.  Clear it out. 
8226
             */
8227
            accompanyingActors.removeRange(1, accompanyingActors.length());
8228
        }
8229
    }
8230
8231
    /*
8232
     *   Check to see if we want to allow another actor to take something
8233
     *   from my inventory.  By default, we won't allow it - we'll always
8234
     *   fail the command.  
8235
     */
8236
    checkTakeFromInventory(actor, obj)
8237
    {
8238
        /* don't allow it - show an error and terminate the command */
8239
        mainReport(&willNotLetGoMsg, self, obj);
8240
        exit;
8241
    }
8242
8243
    /*
8244
     *   Build a list of the objects that are explicitly registered to
8245
     *   receive notification when I'm the actor in a command.
8246
     */
8247
    getActorNotifyList()
8248
    {
8249
        return actorNotifyList;
8250
    }
8251
8252
    /*
8253
     *   Add an item to our registered notification items.  These items
8254
     *   are to receive notifications when we're the actor performing a
8255
     *   command.
8256
     *   
8257
     *   Items can be added here if they must be notified of actions
8258
     *   performed by the actor even when the items aren't connected by
8259
     *   containment with the actor at the time of the action.  All items
8260
     *   connected to the actor by containment are automatically notified
8261
     *   of each action; only items that must receive notification even
8262
     *   when not in scope need to be registered here.  
8263
     */
8264
    addActorNotifyItem(obj)
8265
    {
8266
        actorNotifyList += obj;
8267
    }
8268
8269
    /* remove an item from the registered notification list */
8270
    removeActorNotifyItem(obj)
8271
    {
8272
        actorNotifyList -= obj;
8273
    }
8274
8275
    /* our list of registered actor notification items */
8276
    actorNotifyList = []
8277
8278
    /*
8279
     *   Get the ambient light level in the visual senses at this actor.
8280
     *   This is the ambient level at the actor.  
8281
     */
8282
    getVisualAmbient()
8283
    {
8284
        local ret;
8285
        local cache;
8286
8287
        /* check for a cached value */
8288
        if ((cache = libGlobal.actorVisualAmbientCache) != nil
8289
            && (ret = cache[self]) != nil)
8290
        {
8291
            /* found a cached entry - use it */
8292
            return ret;
8293
        }
8294
8295
        /* get the maximum ambient level at self for my sight-like senses */
8296
        ret = senseAmbientMax(sightlikeSenses);
8297
8298
        /* if caching is active, cache our result for next time */
8299
        if (cache != nil)
8300
            cache[self] = ret;
8301
8302
        /* return the result */
8303
        return ret;
8304
    }
8305
8306
    /*
8307
     *   Determine if my location is lit for my sight-like senses.
8308
     */
8309
    isLocationLit()
8310
    {
8311
        /* 
8312
         *   Check for a simple, common case before doing the full
8313
         *   sense-path calculation: if our location is providing its own
8314
         *   light to its interior, then the location is lit.  Most simple
8315
         *   rooms are always lit.  
8316
         */
8317
        if (sightlikeSenses.indexOf(sight) != nil
8318
            && location != nil
8319
            && location.brightness > 1
8320
            && location.transSensingOut(sight) == transparent)
8321
            return true;
8322
8323
        /* 
8324
         *   We don't have the simple case of light directly from our
8325
         *   location, so run the full sense path check and get our
8326
         *   maximum visual ambience level.  If it's above the "self-lit"
8327
         *   level of 1, then we can see. 
8328
         */
8329
        return (getVisualAmbient() > 1);
8330
    }
8331
8332
    /*
8333
     *   Get the best (most transparent) sense information for one of our
8334
     *   visual senses to the given object.  
8335
     */
8336
    bestVisualInfo(obj)
8337
    {
8338
        local best;
8339
8340
        /* we don't have a best value yet */
8341
        best = nil;
8342
8343
        /* check each sight-like sense */
8344
        foreach (local sense in sightlikeSenses)
8345
        {
8346
            /* 
8347
             *   get the information for the object in this sense, and keep
8348
             *   the best (most transparent) info we've seen so far 
8349
             */
8350
            best = SenseInfo.selectMoreTrans(best, senseObj(sense, obj));
8351
        }
8352
8353
        /* return the best one we found */
8354
        return best;
8355
    }
8356
8357
    /*
8358
     *   Build a list of all of the objects of which an actor is aware.
8359
     *   
8360
     *   An actor is aware of an object if the object is within reach of
8361
     *   the actor's senses, and has some sort of presence in that sense.
8362
     *   Note that both of these conditions must be true for at least one
8363
     *   sense possessed by the actor; an object that is within earshot,
8364
     *   but not within reach of any other sense, is in scope only if the
8365
     *   object is making some kind of noise.
8366
     *   
8367
     *   In addition, objects that the actor is holding (i.e., those
8368
     *   contained by the actor directly) are always in scope, regardless
8369
     *   of their reachability through any sense.  
8370
     */
8371
    scopeList()
8372
    {
8373
        local lst;
8374
8375
        /* we have nothing in our master list yet */
8376
        lst = new Vector(32);
8377
8378
        /* oneself is always in one's own scope list */
8379
        lst.append(self);
8380
8381
        /* iterate over each sense */
8382
        foreach (local sense in scopeSenses)
8383
        {
8384
            /* 
8385
             *   get the list of objects with a presence in this sense
8386
             *   that can be sensed from our point of view, and and append
8387
             *   it to our master list 
8388
             */
8389
            lst.appendUnique(sensePresenceList(sense));
8390
        }
8391
8392
        /* add all of the items we are directly holding */
8393
        lst.appendUnique(contents);
8394
8395
        /* 
8396
         *   ask each of our direct contents to add any contents of their
8397
         *   own that are in scope by virtue of their containers being in
8398
         *   scope 
8399
         */
8400
        foreach (local cur in contents)
8401
            cur.appendHeldContents(lst);
8402
8403
        /* add any items that are specially in scope in the location */
8404
        if (location != nil)
8405
        {
8406
            /* get the extra scope items */
8407
            local extra = location.getExtraScopeItems(self);
8408
8409
            /* if this is a non-nil list, add it to our list */
8410
            if (extra.length() != 0)
8411
                lst.appendUnique(extra);
8412
        }
8413
8414
        /* 
8415
         *   Finally, add anything extra each item already in scope wants
8416
         *   to add.  Note that we keep going until we've visited each
8417
         *   element of the vector at its current length on each iteration,
8418
         *   so if we add any new items, we'll check them to see if they
8419
         *   want to add any new items, and so on.  
8420
         */
8421
        for (local i = 1 ; i <= lst.length() ; ++i)
8422
        {
8423
            local extra;
8424
8425
            /* get the extra scope items for this item */
8426
            extra = lst[i].getExtraScopeItems(self);
8427
8428
            /* if the extra item list is non-nil, add it to our list */
8429
            if (extra.length() != 0)
8430
                lst.appendUnique(extra);
8431
        }
8432
8433
        /* return the result */
8434
        return lst.toList();
8435
    }
8436
8437
    /*
8438
     *   Determine if I can see the given object.  This returns true if
8439
     *   the object can be sensed at all in one of my sight-like senses,
8440
     *   nil if not.  
8441
     */
8442
    canSee(obj)
8443
    {
8444
        /* try each sight-like sense */
8445
        foreach (local sense in sightlikeSenses)
8446
        {
8447
            /* 
8448
             *   if I can sense the object in this sense, I can sense the
8449
             *   object 
8450
             */
8451
            if (senseObj(sense, obj).trans != opaque)
8452
                return true;
8453
        }
8454
8455
        /* we didn't find any sight-like sense where we can see the object */
8456
        return nil;
8457
    }
8458
8459
    /*
8460
     *   Determine if I can hear the given object. 
8461
     */
8462
    canHear(obj)
8463
    {
8464
        /* try each hearling-like sense */
8465
        foreach (local sense in hearinglikeSenses)
8466
        {
8467
            /* 
8468
             *   if I can sense the object in this sense, I can sense the
8469
             *   object 
8470
             */
8471
            if (senseObj(sense, obj).trans != opaque)
8472
                return true;
8473
        }
8474
        
8475
        /* we found no hearing-like sense that lets us hear the object */
8476
        return nil;
8477
    }
8478
8479
    /*
8480
     *   Determine if I can smell the given object. 
8481
     */
8482
    canSmell(obj)
8483
    {
8484
        /* try each hearling-like sense */
8485
        foreach (local sense in smelllikeSenses)
8486
        {
8487
            /* 
8488
             *   if I can sense the object in this sense, I can sense the
8489
             *   object 
8490
             */
8491
            if (senseObj(sense, obj).trans != opaque)
8492
                return true;
8493
        }
8494
        
8495
        /* we found no smell-like sense that lets us hear the object */
8496
        return nil;
8497
    }
8498
8499
    /*
8500
     *   Find the object that prevents us from seeing the given object. 
8501
     */
8502
    findVisualObstructor(obj)
8503
    {
8504
        /* try to find an opaque obstructor in one of our visual senses */
8505
        foreach (local sense in sightlikeSenses)
8506
        {
8507
            local obs;
8508
8509
            /* cache path information for this sense */
8510
            cacheSenseInfo(connectionTable(), sense);
8511
            
8512
            /* if we find an obstructor in this sense, return it */
8513
            if ((obs = findOpaqueObstructor(sense, obj)) != nil)
8514
                return obs;
8515
        }
8516
8517
        /* we didn't find any obstructor */
8518
        return nil;
8519
    }
8520
8521
    /*
8522
     *   Build a table of full sensory information for all of the objects
8523
     *   visible to the actor through the actor's sight-like senses.
8524
     *   Returns a lookup table with the same set of information as
8525
     *   senseInfoTable().  
8526
     */
8527
    visibleInfoTable()
8528
    {
8529
        /* return objects visible from my own point of view */
8530
        return visibleInfoTableFromPov(self);
8531
    }
8532
8533
    /*
8534
     *   Build a table of full sensory information for all of the objects
8535
     *   visible to me from a particular point of view through my
8536
     *   sight-like senses.  
8537
     */
8538
    visibleInfoTableFromPov(pov)
8539
    {
8540
        local tab;
8541
8542
        /* we have no master table yet */
8543
        tab = nil;
8544
8545
        /* iterate over each sense */
8546
        foreach (local sense in sightlikeSenses)
8547
        {
8548
            local cur;
8549
            
8550
            /* get information for all objects for the current sense */
8551
            cur = pov.senseInfoTable(sense);
8552
8553
            /* merge the table so far with the new table */
8554
            tab = mergeSenseInfoTable(cur, tab);
8555
        }
8556
8557
        /* return the result */
8558
        return tab;
8559
    }
8560
8561
    /*
8562
     *   Build a lookup table of the objects that can be sensed for the
8563
     *   purposes of taking inventory.  We'll include everything in the
8564
     *   normal visual sense table, plus everything directly held.  
8565
     */
8566
    inventorySenseInfoTable()
8567
    {
8568
        local visInfo;
8569
        local cont;
8570
        local ambient;
8571
        local info;
8572
        
8573
        /*   
8574
         *   Start with the objects visible to the actor through the
8575
         *   actor's sight-like senses.  
8576
         */
8577
        visInfo = visibleInfoTable();
8578
8579
        /* get the ambient light level at the actor */
8580
        if ((info = visInfo[self]) != nil)
8581
            ambient = info.ambient;
8582
        else
8583
            ambient = 0;
8584
        
8585
        /*   
8586
         *   We'll assume that, for each item that the actor is directly
8587
         *   holding AND knows about, the actor can still identify the item
8588
         *   by touch, even if it's not visible.  This way, when we're in a
8589
         *   dark room, we'll still be able to refer to the objects we're
8590
         *   directly holding, as long as we already know about them.
8591
         *   
8592
         *   Likewise, add items within our direct contents that are
8593
         *   considered equally held.  
8594
         */
8595
        cont = new Vector(32);
8596
        foreach (local cur in contents)
8597
        {
8598
            /* add this item from our contents */
8599
            cont.append(cur);
8600
8601
            /* add its contents that are themselves equally as held */
8602
            cur.appendHeldContents(cont);
8603
        }
8604
8605
        /* 
8606
         *   Make a fully-sensible entry for each of our held items.  We
8607
         *   can simply replace any existing entry in the table that we got
8608
         *   from the visual senses, since a fully transparent entry will
8609
         *   be at least as good as anything we got from the normal visual
8610
         *   list.  Only include items that the actor knows about; we'll
8611
         *   assume that we can identify by touch anything we're holding if
8612
         *   we already know what it is, but not otherwise.  
8613
         */
8614
        foreach (local cur in cont)
8615
        {
8616
            /* if we know about the object, make it effectively visible */
8617
            if (knowsAbout(cur))
8618
                visInfo[cur] = new SenseInfo(cur, transparent, nil, ambient);
8619
        }
8620
8621
        /* return the table */
8622
        return visInfo;
8623
    }
8624
8625
    /*
8626
     *   Show what the actor is carrying.
8627
     */
8628
    showInventory(tall)
8629
    {
8630
        /* 
8631
         *   show our inventory with our default listers as given by our
8632
         *   inventory/wearing lister properties 
8633
         */
8634
        showInventoryWith(tall, inventoryLister);
8635
    }
8636
8637
    /*
8638
     *   Show what the actor is carrying, using the given listers.
8639
     *   
8640
     *   Note that this method must be overridden if the actor does not
8641
     *   use a conventional 'contents' list property to store its full set
8642
     *   of contents.  
8643
     */
8644
    showInventoryWith(tall, inventoryLister)
8645
    {
8646
        local infoTab;
8647
8648
        /* get the table of objects sensible for inventory */
8649
        infoTab = inventorySenseInfoTable();
8650
8651
        /* list in the appropriate mode ("wide" or "tall") */
8652
        inventoryLister.showList(self, self, contents,
8653
                                 ListRecurse | (tall ? ListTall : 0),
8654
                                 0, infoTab, nil);
8655
8656
        /* mention sounds coming from inventory items */
8657
        inventorySense(sound, inventoryListenLister);
8658
8659
        /* mention odors coming from inventory items */
8660
        inventorySense(smell, inventorySmellLister);
8661
    }
8662
8663
    /*
8664
     *   Add to an inventory description a list of things we notice
8665
     *   through a specific sense.
8666
     */
8667
    inventorySense(sense, lister)
8668
    {
8669
        local infoTab;
8670
        local presenceList;
8671
        
8672
        /* get the information table for the desired sense */
8673
        infoTab = senseInfoTable(sense);
8674
        
8675
        /* 
8676
         *   get the list of everything with a presence in this sense that
8677
         *   I'm carrying 
8678
         */
8679
        presenceList = senseInfoTableSubset(infoTab,
8680
            {obj, info: obj.isIn(self) && obj.(sense.presenceProp)});
8681
8682
        /* add a paragraph break */
8683
        cosmeticSpacingReport('<.p>');
8684
        
8685
        /* list the items */
8686
        lister.showList(self, nil, presenceList, 0, 0, infoTab, nil);
8687
    }
8688
8689
    /*
8690
     *   The Lister object that we use for inventory listings.  By
8691
     *   default, we use actorInventoryLister, but this can be overridden
8692
     *   if desired to use a different listing style.  
8693
     */
8694
    inventoryLister = actorInventoryLister
8695
8696
    /*
8697
     *   The Lister for inventory listings, for use in a full description
8698
     *   of the actor.  By default, we use the "long form" inventory
8699
     *   lister, on the assumption that most actors have relatively lengthy
8700
     *   descriptive text.  This can be overridden to use other formats;
8701
     *   the short-form lister, for example, is useful for actors with only
8702
     *   brief descriptions.  
8703
     */
8704
    holdingDescInventoryLister = actorHoldingDescInventoryListerLong
8705
8706
    /*
8707
     *   Perform library pre-initialization on the actor 
8708
     */
8709
    initializeActor()
8710
    {
8711
        /* set up an empty pending command list */
8712
        pendingCommand = new Vector(5);
8713
8714
        /* create a default inventory lister if we don't have one already */
8715
        if (inventoryLister == nil)
8716
            inventoryLister = actorInventoryLister;
8717
8718
        /* create our antecedent tables */
8719
        antecedentTable = new LookupTable(8, 8);
8720
        possAnaphorTable = new LookupTable(8, 8);
8721
8722
        /* if we don't have a state object, create a default */
8723
        if (curState == nil)
8724
            setCurState(new ActorState(self));
8725
8726
        /* create our pending-conversation list */
8727
        pendingConv = new Vector(5);
8728
    }
8729
8730
    /*
8731
     *   Note conditions before an action or other event.  By default, we
8732
     *   note our location and light/dark status, so that we comment on
8733
     *   any change in the light/dark status after the event if we're
8734
     *   still in the same location.  
8735
     */
8736
    noteConditionsBefore()
8737
    {
8738
        /* note our original location and light/dark status */
8739
        locationBefore = location;
8740
        locationLitBefore = isLocationLit();
8741
    }
8742
8743
    /*
8744
     *   Note conditions after an action or other event.  By default, if
8745
     *   we are still in the same location we were in when
8746
     *   noteConditionsBefore() was last called, and the light/dark status
8747
     *   has changed, we'll mention the change in light/dark status. 
8748
     */
8749
    noteConditionsAfter()
8750
    {
8751
        /* 
8752
         *   If our location hasn't changed but our light/dark status has,
8753
         *   note the new status.  We don't make any announcement if the
8754
         *   location has changed, since the travel routine will
8755
         *   presumably have shown us the new location's light/dark status
8756
         *   implicitly as part of the description of the new location
8757
         *   after travel. 
8758
         */
8759
        if (location == locationBefore
8760
            && isLocationLit() != locationLitBefore)
8761
        {
8762
            /* consider this the start of a new turn */
8763
            "<.commandsep>";
8764
8765
            /* note the change with a new 'NoteDarkness' action */
8766
            newActorAction(self, NoteDarkness);
8767
8768
            /* 
8769
             *   start another turn, in case this occurred during an
8770
             *   implicit action or the like 
8771
             */
8772
            "<.commandsep>";
8773
        }
8774
    }
8775
8776
    /* conditions we noted in noteConditionsBefore() */
8777
    locationBefore = nil
8778
    locationLitBefore = nil
8779
8780
    /* let the actor have a turn as soon as the game starts */
8781
    nextRunTime = 0
8782
8783
    /* 
8784
     *   Scheduling order - this determines the order of execution when
8785
     *   several items are schedulable at the same game clock time.
8786
     *   
8787
     *   We choose a scheduling order that schedules actors in this
8788
     *   relative order:
8789
     *   
8790
     *   100 player character, ready to execute
8791
     *.  200 NPC, ready to execute
8792
     *.  300 player character, idle
8793
     *.  400 NPC, idle
8794
     *   
8795
     *   An "idle" actor is one that is waiting for another character to
8796
     *   complete a command, or an NPC with no pending commands to
8797
     *   perform.  (For the player character, it doesn't matter whether or
8798
     *   not there's a pending command, because if the PC has no pending
8799
     *   command, we ask the player for one.)
8800
     *   
8801
     *   This ordering ensures that each actor gets a chance to run each
8802
     *   turn, but that actors with work to do go first, and other things
8803
     *   being equal, the player character goes ahead of NPC's.  
8804
     */
8805
    scheduleOrder = 100
8806
8807
    /* calculate the scheduling order */
8808
    calcScheduleOrder()
8809
    {
8810
        /* determine if we're ready to run */
8811
        if (readyForTurn())
8812
            scheduleOrder = isPlayerChar() ? 100 : 200;
8813
        else
8814
            scheduleOrder = isPlayerChar() ? 300 : 400;
8815
8816
        /* return the scheduling order */
8817
        return scheduleOrder;
8818
    }
8819
8820
    /*
8821
     *   Determine if we're ready to do something on our turn.  We're
8822
     *   ready to do something if we're not waiting for another actor to
8823
     *   finish doing something and either we're the player character or
8824
     *   we already have a pending command in our command queue.  
8825
     */
8826
    readyForTurn()
8827
    {
8828
        /* 
8829
         *   if we're waiting for another actor, we're not ready to do
8830
         *   anything 
8831
         */
8832
        if (checkWaitingForActor())
8833
            return nil;
8834
8835
        /* 
8836
         *   if we're the player character, we're always ready to take a
8837
         *   turn as long as we're not waiting for another actor (which we
8838
         *   now know we're not), because we can either execute one of our
8839
         *   previously queued commands, or we can ask for a new command
8840
         *   to perform 
8841
         */
8842
        if (isPlayerChar())
8843
            return true;
8844
8845
        /* 
8846
         *   if we have something other than placeholders in our command
8847
         *   queue, we're ready to take a turn, because we can execute the
8848
         *   next command in our queue 
8849
         */
8850
        if (pendingCommand.indexWhich({x: x.hasCommand}) != nil)
8851
            return true;
8852
8853
        /* 
8854
         *   we have no specific work to do, so we're not ready for our
8855
         *   next turn 
8856
         */
8857
        return nil;
8858
    }
8859
8860
    /*
8861
     *   Check to see if we're waiting for another actor to do something.
8862
     *   Return true if so, nil if not.  If we've been waiting for another
8863
     *   actor, and the actor has finished the task we've been waiting for
8864
     *   since the last time we checked, we'll clean up our internal state
8865
     *   relating to the wait and return nil.  
8866
     */
8867
    checkWaitingForActor()
8868
    {
8869
        local cmdIdx;
8870
        local idx;
8871
8872
        /* if we're not waiting for an actor, simply return nil */
8873
        if (waitingForActor == nil)
8874
            return nil;
8875
8876
        /* 
8877
         *   We're waiting for an actor to complete a command.  Check to
8878
         *   see if the completion marker is still in the actor's queue; if
8879
         *   it's not, then the other actor has already completed our task.
8880
         *   If the completion marker is in the other actor's queue, but
8881
         *   there are no command entries before it, then we're also done
8882
         *   waiting, because we're not actually waiting for the completion
8883
         *   marker but instead for the tasks that were ahead of it in the
8884
         *   main game execution loop.
8885
         *   
8886
         *   So, find the index of our marker in the queue, and find the
8887
         *   index of the first real command in the queue.  If our marker
8888
         *   is still in the queue, and there's a command in the queue
8889
         *   before our marker, the actor we're waiting for still has
8890
         *   things to do before we're ready, so we're still waiting.  
8891
         */
8892
        idx = waitingForActor.pendingCommand.indexOf(waitingForInfo);
8893
        cmdIdx = waitingForActor.pendingCommand.indexWhich({x: x.hasCommand});
8894
        if (idx != nil && cmdIdx != nil && idx > cmdIdx)
8895
        {
8896
            /* 
8897
             *   The marker is still in the queue, and there's at least
8898
             *   one other command ahead of it, so the other actor hasn't
8899
             *   finished the task we've been waiting for.  Tell the
8900
             *   caller that we are indeed still waiting for someone.  
8901
             */
8902
            return true;
8903
        }
8904
8905
        /*
8906
         *   The other actor has disposed of our end-marker (or is about
8907
         *   to, because it's the next thing left in the actor's queue),
8908
         *   so it has finished with all of the commands we have been
8909
         *   waiting for.  However, if I haven't caught up in game clock
8910
         *   time with the actor I've been waiting for, I'm still waiting.
8911
         */
8912
        if (waitingForActor.nextRunTime > nextRunTime)
8913
            return true;
8914
8915
        /* we're done waiting - forget our wait status information */
8916
        waitingForActor = nil;
8917
        waitingForInfo = nil;
8918
8919
        /* tell the caller we're no longer waiting for anyone */
8920
        return nil;
8921
    }
8922
8923
    /* the action the actor performed most recently */
8924
    mostRecentAction = nil
8925
8926
    /*
8927
     *   Add busy time.  An action calls this when we are the actor
8928
     *   performing the action, and the action consumes game time.  This
8929
     *   marks us as busy for the given time units.  
8930
     */
8931
    addBusyTime(action, units)
8932
    {
8933
        /* note the action being performed */
8934
        mostRecentAction = action;
8935
8936
        /* adjust the next run time by the busy time */
8937
        nextRunTime += units;
8938
    }
8939
8940
    /*
8941
     *   When it's our turn and we don't have any command to perform,
8942
     *   we'll call this routine, which can perform a scripted operation
8943
     *   if desired.  
8944
     */
8945
    idleTurn()
8946
    {
8947
        local tCur = Schedulable.gameClockTime;
8948
        local origNextRunTime = nextRunTime;
8949
        
8950
        /* 
8951
         *   if we haven't been targeted for conversation on this turn,
8952
         *   see if we have a conversation we want to start 
8953
         */
8954
        if (lastConvTime < tCur)
8955
        {
8956
            /* check for a conversation that's ready to go */
8957
            local info = pendingConv.valWhich({x: tCur >= x.time_});
8958
8959
            /* if we found one, kick it off */
8960
            if (info != nil)
8961
            {
8962
                /* remove it from the list */
8963
                pendingConv.removeElement(info);
8964
8965
                /* start the conversation */
8966
                initiateConversation(info.state_, info.node_);
8967
            }
8968
        }
8969
8970
        /* notify our state object that we're taking a turn */
8971
        curState.takeTurn();
8972
8973
        /* 
8974
         *   If we haven't already adjusted our next run time, consume a
8975
         *   turn, so we're not ready to run again until the next game time
8976
         *   increment.  In some cases, we'll already have made this
8977
         *   adjustment; for example, we might have run a nested command
8978
         *   within our state object's takeTurn() method.  
8979
         */
8980
        if (nextRunTime == origNextRunTime)
8981
            ++nextRunTime;
8982
    }
8983
8984
    /*
8985
     *   Receive notification that this is a non-idle turn.  This is
8986
     *   called whenever a command in our pending command queue is about
8987
     *   to be executed.
8988
     *   
8989
     *   This method need not do anything at all, since the caller will
8990
     *   take care of running the pending command.  The purpose of this
8991
     *   method is to take care of any changes an actor wants to make when
8992
     *   it receives an explicit command, as opposed to running its own
8993
     *   autonomous activity.
8994
     *   
8995
     *   By default, we cancel follow mode if it's in effect.  It usually
8996
     *   makes sense for an explicit command to interrupt follow mode;
8997
     *   follow mode is usually started by an explicit command in the
8998
     *   first place, so it is usually sensible for a new command to
8999
     *   replace the one that started follow mode.
9000
     */
9001
    nonIdleTurn()
9002
    {
9003
        /* by default, cancel follow mode */
9004
        followingActor = nil;
9005
    }
9006
9007
    /*
9008
     *   If we're following an actor, this keeps track of the actor we're
9009
     *   following.  NPC's can use this to follow around another actor
9010
     *   whenever possible.  
9011
     */
9012
    followingActor = nil
9013
9014
    /*
9015
     *   Handle a situation where we're trying to follow an actor but
9016
     *   can't.  By default, this simply cancels our follow mode.
9017
     *   
9018
     *   Actors might want to override this to be more tolerant.  For
9019
     *   example, an actor might want to wait until five turns elapse to
9020
     *   give up on following, in case the target actor returns after a
9021
     *   brief digression; or an actor could stay in follow mode until it
9022
     *   received other instructions, or found something better to do.  
9023
     */
9024
    cannotFollow()
9025
    {
9026
        /* 
9027
         *   by default, simply cancel follow mode by forgetting about the
9028
         *   actor we're following
9029
         */
9030
        followingActor = nil;
9031
    }
9032
9033
    /*
9034
     *   Execute one "turn" - this is a unit of time passing.  The player
9035
     *   character generally is allowed to execute one command in the
9036
     *   course of a turn; a non-player character with a programmed task
9037
     *   can perform an increment of the task.
9038
     *   
9039
     *   We set up an ActorTurnAction environment and invoke our
9040
     *   executeActorTurn() method.  In most cases, subclasses should
9041
     *   override executeActorTurn() rather than this method, since
9042
     *   overriding executeTurn() directly will lose the action
9043
     *   environment.  
9044
     */
9045
    executeTurn()
9046
    {
9047
        /* start a new command visually when a new actor is taking over */
9048
        "<.commandsep>";
9049
        
9050
        /* 
9051
         *   Execute the turn in a daemon action context, and in the sight
9052
         *   context of the actor.  The sense context will ensure that we
9053
         *   report the results of the action only if the actor is visible
9054
         *   to the player character; in most cases, the actor's
9055
         *   visibility is equivalent to the visibility of the effects, so
9056
         *   this provides a simple way of ensuring that the results of
9057
         *   the action are reported if and only if they're visible to the
9058
         *   player character.
9059
         *   
9060
         *   Note that if we are the player character, don't use the sense
9061
         *   context filtering -- we normally want full reports for
9062
         *   everything the player character does.  
9063
         */
9064
        return withActionEnv(EventAction, self,
9065
            {: callWithSenseContext(isPlayerChar() ? nil : self, sight,
9066
                                    {: executeActorTurn() }) });
9067
    }
9068
9069
    /* 
9070
     *   The main processing for an actor's turn.  In most cases,
9071
     *   subclasses should override this method (rather than executeTurn)
9072
     *   to specialize an actor's turn processing. 
9073
     */
9074
    executeActorTurn()
9075
    {
9076
        /*
9077
         *   If we have a pending response, and we're in a position to
9078
         *   deliver it, our next work is to deliver the pending response.
9079
         */
9080
        if (pendingResponse != nil && canTalkTo(pendingResponse.issuer_))
9081
        {
9082
            /* 
9083
             *   We have a pending response, and the command issuer from
9084
             *   the pending response can hear us now, so we can finally
9085
             *   deliver the response.
9086
             *   
9087
             *   If the issuer is the player character, send to the player
9088
             *   using our deferred message generator; otherwise, call the
9089
             *   issuer's notification routine, since it's an NPC-to-NPC
9090
             *   notification.  
9091
             */
9092
            if (pendingResponse.issuer_.isPlayerChar())
9093
            {
9094
                /* 
9095
                 *   we're notifying the player - use the deferred message
9096
                 *   generator 
9097
                 */
9098
                getParserDeferredMessageObj().(pendingResponse.prop_)(
9099
                    self, pendingResponse.args_...);
9100
            }
9101
            else
9102
            {
9103
                /* it's an NPC-to-NPC notification - notify the issuer */
9104
                pendingResponse.issuer_.notifyIssuerParseFailure(
9105
                    self, pendingResponse.prop_, pendingResponse.args_);
9106
            }
9107
9108
            /* 
9109
             *   in either case, we've gotten this out of our system now,
9110
             *   so we can forget about the pending response 
9111
             */
9112
            pendingResponse = nil;
9113
        }
9114
            
9115
        /* check to see if we're waiting for another actor */
9116
        if (checkWaitingForActor())
9117
        {
9118
            /* 
9119
             *   we're still waiting, so there's nothing for us to do; take
9120
             *   an idle turn and return 
9121
             */
9122
            idleTurn();
9123
            return true;
9124
        }
9125
            
9126
        /* 
9127
         *   if we're the player character, and we have no pending commands
9128
         *   to execute, our next task will be to read and execute a
9129
         *   command 
9130
         */
9131
        if (pendingCommand.length() == 0 && isPlayerChar())
9132
        {
9133
            local toks;
9134
            
9135
            /* read a command line and get the resulting token list */
9136
            toks = readMainCommandTokens(rmcCommand);
9137
            
9138
            /* 
9139
             *   re-activate the main transcript - reading the command
9140
             *   line will have deactivated the transcript, but we want it
9141
             *   active again now that we're about to start executing the
9142
             *   command 
9143
             */
9144
            gTranscript.activate();
9145
            
9146
            /* 
9147
             *   If it came back nil, it means that the input was fully
9148
             *   processed in pre-parsing; this means that we don't have
9149
             *   any more work to do on this turn, so we can simply end our
9150
             *   turn now.  
9151
             */
9152
            if (toks == nil)
9153
                return true;
9154
            
9155
            /* retrieve the token list from the command line */
9156
            toks = toks[2];
9157
            
9158
            /* 
9159
             *   Add it to our pending command queue.  Since we read the
9160
             *   command from the player, and we're the player character,
9161
             *   we treat the command as coming from myself.
9162
             *   
9163
             *   Since this is a newly-read command line, we're starting a
9164
             *   new sentence.  
9165
             */
9166
            addPendingCommand(true, self, toks);
9167
        }
9168
9169
        /*
9170
         *   Check to see if we have any pending command to execute.  If
9171
         *   so, our next task is to execute the pending command.  
9172
         */
9173
        if (pendingCommand.length() != 0)
9174
        {
9175
            local cmd;
9176
            
9177
            /* remove the first pending command from our queue */
9178
            cmd = pendingCommand[1];
9179
            pendingCommand.removeElementAt(1);
9180
            
9181
            /* if this is a real command, note the non-idle turn */
9182
            if (cmd.hasCommand)
9183
                nonIdleTurn();
9184
            
9185
            /* execute the first pending command */
9186
            cmd.executePending(self);
9187
            
9188
            /* 
9189
             *   We're done with this turn.  If we no longer have any
9190
             *   pending commands, tell the scheduler to refigure the
9191
             *   execution order, since another object might now be ready
9192
             *   to run ahead of our idle activity.  
9193
             */
9194
            if (pendingCommand.indexWhich({x: x.hasCommand}) == nil)
9195
                return nil;
9196
            else
9197
                return true;
9198
        }
9199
        
9200
        /*
9201
         *   If we're following an actor, and the actor isn't in sight, see
9202
         *   if we can catch up.  
9203
         */
9204
        if (followingActor != nil
9205
            && location != nil
9206
            && (followingActor.location.effectiveFollowLocation
9207
                != location.effectiveFollowLocation))
9208
        {
9209
            local info;
9210
            
9211
            /* see if we have enough information to follow */
9212
            info = getFollowInfo(followingActor);
9213
                
9214
            /* 
9215
             *   Check to see if we have enough information to follow the
9216
             *   actor.  We can only follow if we saw the actor depart at
9217
             *   some point, and we're in the same location where we last
9218
             *   saw the actor depart.  (We have to be in the same
9219
             *   location, because we follow by performing the same command
9220
             *   we saw the actor perform when we last saw the actor
9221
             *   depart.  Repeating the command will obviously be
9222
             *   ineffective unless we're in the same location as the actor
9223
             *   was.)  
9224
             */
9225
            if (info != nil)
9226
            {
9227
                local success;
9228
                
9229
                /* 
9230
                 *   we know how to follow the actor, so simply perform
9231
                 *   the same command we saw the actor perform.  
9232
                 */
9233
                newActorAction(self, Follow, followingActor);
9234
                
9235
                /* note whether or not we succeeded */
9236
                success = (location.effectiveFollowLocation ==
9237
                           followingActor.location.effectiveFollowLocation);
9238
                    
9239
                /* notify the state object of our attempt */
9240
                curState.justFollowed(success);
9241
                
9242
                /* 
9243
                 *   if we failed to track the actor, note that we are
9244
                 *   unable to follow the actor 
9245
                 */
9246
                if (!success)
9247
                {
9248
                    /* note that we failed to follow the actor */
9249
                    cannotFollow();
9250
                }
9251
                
9252
                /* we're done with this turn */
9253
                return true;
9254
            }
9255
            else
9256
            {
9257
                /* 
9258
                 *   we don't know how to follow this actor - call our
9259
                 *   cannot-follow handler 
9260
                 */
9261
                cannotFollow();
9262
            }
9263
        }
9264
9265
        /* we have no pending work to perform, so take an idle turn */
9266
        idleTurn();
9267
        
9268
        /* no change in scheduling priority */
9269
        return true;
9270
    }
9271
9272
    /*
9273
     *   By default, all actors are likely command targets.  This should
9274
     *   be overridden for actors who are obviously not likely to accept
9275
     *   commands of any kind.
9276
     *   
9277
     *   This is used to disambiguate target actors in commands, so this
9278
     *   should provide an indication of what should be obvious to a
9279
     *   player, because the purpose of this information is to guess what
9280
     *   the player is likely to take for granted in specifying a target
9281
     *   actor.
9282
     */
9283
    isLikelyCommandTarget = true
9284
9285
    /*
9286
     *   Determine if we should accept a command.  'issuingActor' is the
9287
     *   actor who issued the command: if the player typed the command on
9288
     *   the command line, this will be the player character actor.
9289
     *   
9290
     *   This routine performs only the simplest check, since it doesn't
9291
     *   have access to the specific action being performed.  This is
9292
     *   intended as a first check, to allow us to bypass noun resolution
9293
     *   if the actor simply won't accept any command from the issuer.
9294
     *   
9295
     *   Returns true to accept a command, nil to reject it.  If this
9296
     *   routine returns nil, and the command came from the player
9297
     *   character, a suitable message should be displayed.
9298
     *   
9299
     *   Note that most actors should not override this routine simply to
9300
     *   express the will of the actor to accept a command, since this
9301
     *   routine performs a number of checks for the physical ability of
9302
     *   the actor to execute a command from the issuer.  To determine
9303
     *   whether or not the actor should obey physically valid commands
9304
     *   from the issuer, override obeyCommand().  
9305
     */
9306
    acceptCommand(issuingActor)
9307
    {
9308
        /* if we're the current player character, accept any command */
9309
        if (isPlayerChar())
9310
            return true;
9311
9312
        /* if we can't hear the issuer, we can't talk to it */
9313
        if (issuingActor != self && !issuingActor.canTalkTo(self))
9314
        {
9315
            /* report that the target actor can't hear the issuer */
9316
            reportFailure(&objCannotHearActorMsg, self);
9317
9318
            /* tell the caller that the command cannot proceed */
9319
            return nil;
9320
        }
9321
9322
        /* if I'm busy doing something else, say so */
9323
        if (nextRunTime > Schedulable.gameClockTime)
9324
        {
9325
            /* tell the issuing actor I'm busy */
9326
            notifyParseFailure(issuingActor, &refuseCommandBusy,
9327
                               [issuingActor]);
9328
9329
            /* tell the caller to abandon the command */
9330
            return nil;
9331
        }
9332
9333
        /* check to see if I have other work to perform first */
9334
        if (!acceptCommandBusy(issuingActor))
9335
            return nil;
9336
9337
        /* we didn't find any reason to object, so allow the command */
9338
        return true;
9339
    }
9340
9341
    /*
9342
     *   Check to see if I'm busy with pending commands, and if so,
9343
     *   whether or not I should accept a new command.  Returns true if we
9344
     *   should accept a command, nil if not.  If we return nil, we must
9345
     *   notify the issuer of the rejection.
9346
     *   
9347
     *   By default, we won't accept a command if we have any work
9348
     *   pending.  
9349
     */
9350
    acceptCommandBusy(issuingActor)
9351
    {
9352
        /* if we have any pending commands, don't accept a new command */
9353
        if (pendingCommand.length() != 0)
9354
        {
9355
            /* 
9356
             *   if we have only commands from the same issuer pending,
9357
             *   cancel all of the pending commands and accept the new
9358
             *   command instead 
9359
             */
9360
            foreach (local info in pendingCommand)
9361
            {
9362
                /* 
9363
                 *   if this is from a different issuer, don't accept a
9364
                 *   new command 
9365
                 */
9366
                if (info.issuer_ != issuingActor)
9367
                {
9368
                    /* tell the other actor that we're busy */
9369
                    notifyParseFailure(issuingActor, &refuseCommandBusy,
9370
                                       [issuingActor]);
9371
9372
                    /* tell the caller to abandon the command */
9373
                    return nil;
9374
                }
9375
            }
9376
9377
            /* 
9378
             *   all of the pending commands were from the same issuer, so
9379
             *   presumably the issuer wants to override those commands;
9380
             *   remove the old ones from our pending queue
9381
             */
9382
            pendingCommand.removeRange(1, pendingCommand.length());
9383
        }
9384
9385
        /* we didn't find any problems */
9386
        return true;
9387
    }
9388
9389
    /*
9390
     *   Determine whether or not we want to obey a command from the given
9391
     *   actor to perform the given action.  We only get this far when we
9392
     *   determine that it's possible for us to accept a command, given
9393
     *   the sense connections between us and the issuing actor, and given
9394
     *   our pending command queue.
9395
     *   
9396
     *   When this routine is called, the action has been determined, and
9397
     *   the noun phrases have been resolved.  However, we haven't
9398
     *   actually started processing the action yet, so the globals for
9399
     *   the noun slots (gDobj, gIobj, etc) are NOT available.  If the
9400
     *   routine needs to know which objects are involved, it must obtain
9401
     *   the full list of resolved objects from the action (using, for
9402
     *   example, getResolvedDobjList()).
9403
     *   
9404
     *   When there's a list of objects to be processed (as in GET ALL),
9405
     *   we haven't started working on any one of them yet - this check is
9406
     *   made once for the entire command, and applies to the entire list
9407
     *   of objects.  If the actor wants to respond specially to
9408
     *   individual objects, you can do that by overriding actorAction()
9409
     *   instead of this routine.
9410
     *   
9411
     *   This routine should display an appropriate message and return nil
9412
     *   if the command is not to be accepted, and should simply return
9413
     *   true to accept the command.
9414
     *   
9415
     *   By default, we'll let our state object handle this.
9416
     *   
9417
     *   Note that actors that override this might also need to override
9418
     *   wantsFollowInfo(), since an actor that accepts "follow" commands
9419
     *   will need to keep track of the movements of other actors if it is
9420
     *   to carry out any following.  
9421
     */
9422
    obeyCommand(issuingActor, action)
9423
    {
9424
        /* note that the issuing actor is targeting me in conversation */
9425
        issuingActor.noteConversation(self);
9426
9427
        /* let the state object handle it */
9428
        return curState.obeyCommand(issuingActor, action);
9429
    }
9430
    
9431
    /* 
9432
     *   Say hello/goodbye/yes/no to the given actor.  We'll greet the
9433
     *   target actor is the target actor was specified (i.e., actor !=
9434
     *   self); otherwise, we'll greet our current default conversational
9435
     *   partner, if we have one.  
9436
     */
9437
    sayHello(actor) { sayToActor(actor, helloTopicObj, helloConvType); }
9438
    sayGoodbye(actor) { sayToActor(actor, byeTopicObj, byeConvType); }
9439
    sayYes(actor) { sayToActor(actor, yesTopicObj, yesConvType); }
9440
    sayNo(actor) { sayToActor(actor, noTopicObj, noConvType); }
9441
9442
    /* handle one of the conversational addresses */
9443
    sayToActor(actor, topic, convType)
9444
    {
9445
        /*
9446
         *   If the target actor is the same as the issuing actor, then no
9447
         *   target actor was specified in the command, so direct the
9448
         *   address to our current conversational partner, if we have
9449
         *   one. 
9450
         */
9451
        if (actor == self)
9452
            actor = getDefaultInterlocutor();
9453
9454
        /* 
9455
         *   if we found an actor, send the address to the actor's state
9456
         *   object; otherwise, handle it with the given default message 
9457
         */
9458
        if (actor != nil)
9459
        {
9460
            /* make sure we can talk to the other actor */
9461
            if (!canTalkTo(actor))
9462
            {
9463
                /* can't talk to them - say so and give up */
9464
                reportFailure(&objCannotHearActorMsg, actor);
9465
                exit;
9466
            }
9467
9468
            /* remember our current conversational partner */
9469
            noteConversation(actor);
9470
            
9471
            /* handle it as a topic */
9472
            actor.curState.handleConversation(self, topic, convType);
9473
        }
9474
        else
9475
        {
9476
            /* 
9477
             *   we don't know whom we're addressing; just show the default
9478
             *   message for an unknown interlocutor
9479
             */
9480
            mainReport(convType.unknownMsg);
9481
        }
9482
    }
9483
9484
    /* 
9485
     *   Handle the XSPCLTOPIC pseudo-command.  This command is generated
9486
     *   by the SpecialTopic pre-parser when it recognizes the player's
9487
     *   input as matching an active SpecialTopic's custom syntax.  Our
9488
     *   job is to route this back to our current interlocutor's active
9489
     *   ConvNode, so that it can find the SpecialTopic that it matched in
9490
     *   pre-parsing and show its response. 
9491
     */
9492
    saySpecialTopic()
9493
    {
9494
        local actor;
9495
        
9496
        /* send it to our interlocutor */
9497
        if ((actor = getCurrentInterlocutor()) == nil
9498
            || actor.curConvNode == nil)
9499
        {
9500
            /*
9501
             *   We don't seem to have a current interlocutor, or the
9502
             *   interlocutor doesn't have a current conversation node.
9503
             *   This is inconsistent; there's no way we could have
9504
             *   generated XSPCLTOPIC from our pre-parser under these
9505
             *   conditions.  The most likely thing is that the player
9506
             *   tried typing in XSPCLTOPIC manually.  Politely ignore it. 
9507
             */
9508
            gLibMessages.commandNotPresent;
9509
        }
9510
        else
9511
        {
9512
            /* note the conversation directed to the other actor */
9513
            noteConversation(actor);
9514
9515
            /* send the request to the ConvNode for processing */
9516
            actor.curConvNode.saySpecialTopic(self);
9517
        }
9518
    }
9519
9520
    /* 
9521
     *   Add a command to our pending command list.  The new command is
9522
     *   specified as a list of tokens to be parsed, and it is added after
9523
     *   any commands already in our pending list.  
9524
     */
9525
    addPendingCommand(startOfSentence, issuer, toks)
9526
    {
9527
        /* add a descriptor to the pending command list */
9528
        pendingCommand.append(
9529
            new PendingCommandToks(startOfSentence, issuer, toks));
9530
    }
9531
9532
    /* 
9533
     *   Insert a command at the head of our pending command list.  The
9534
     *   new command is specified as a list of tokens to parse, and it is
9535
     *   inserted into our pending command list before any commands
9536
     *   already in the list.  
9537
     */
9538
    addFirstPendingCommand(startOfSentence, issuer, toks)
9539
    {
9540
        /* add a descriptor to the start of our list */
9541
        pendingCommand.insertAt(
9542
            1, new PendingCommandToks(startOfSentence, issuer, toks));
9543
    }
9544
9545
    /*
9546
     *   Add a resolved action to our pending command list.  The new
9547
     *   command is specified as a resolved Action object; it is added
9548
     *   after any commands already in our list. 
9549
     */
9550
    addPendingAction(startOfSentence, issuer, action, [objs])
9551
    {
9552
        /* add a descriptor to the pending command list */
9553
        pendingCommand.append(new PendingCommandAction(
9554
            startOfSentence, issuer, action, objs...));
9555
    }
9556
9557
    /*
9558
     *   Insert a resolved action at the start of our pending command
9559
     *   list.  The new command is specified as a resolved Action object;
9560
     *   it is added before any commands already in our list.  
9561
     */
9562
    addFirstPendingAction(startOfSentence, issuer, action, [objs])
9563
    {
9564
        /* add a descriptor to the pending command list */
9565
        pendingCommand.insertAt(1, new PendingCommandAction(
9566
            startOfSentence, issuer, action, objs...));
9567
    }
9568
9569
9570
    /* pending commands - this is a list of PendingCommandInfo objects */
9571
    pendingCommand = nil
9572
9573
    /* 
9574
     *   pending response - this is a single PendingResponseInfo object,
9575
     *   which we'll deliver as soon as the issuing actor is in a position
9576
     *   to hear us 
9577
     */
9578
    pendingResponse = nil
9579
9580
    /* 
9581
     *   get the library message object for a parser message addressed to
9582
     *   the player character 
9583
     */
9584
    getParserMessageObj()
9585
    {
9586
        /* 
9587
         *   If I'm the player character, use the player character message
9588
         *   object; otherwise, use the default non-player character
9589
         *   message object.
9590
         *   
9591
         *   To customize parser messages from a particular actor, create
9592
         *   an object based on npcMessages, and override this routine in
9593
         *   the actor so that it returns the custom object rather than
9594
         *   the standard npcMessages object.  To customize messages for
9595
         *   ALL of the NPC's in a game, simply modify npcMessages itself,
9596
         *   since it's the default for all non-player characters.  
9597
         */
9598
        return isPlayerChar() ? playerMessages : npcMessages;
9599
    }
9600
9601
    /*
9602
     *   Get the deferred library message object for a parser message
9603
     *   addressed to the player character.  We only use this to generate
9604
     *   messages deferred from non-player characters.  
9605
     */
9606
    getParserDeferredMessageObj() { return npcDeferredMessages; }
9607
9608
    /*
9609
     *   Get the library message object for action responses.  This is
9610
     *   used to generate library responses to verbs.  
9611
     */
9612
    getActionMessageObj()
9613
    {
9614
        /* 
9615
         *   return the default player character or NPC message object,
9616
         *   depending on whether I'm the player or not; individual actors
9617
         *   can override this to supply actor-specific messages for
9618
         *   library action responses 
9619
         */
9620
        return isPlayerChar() ? playerActionMessages: npcActionMessages;
9621
    }
9622
9623
    /* 
9624
     *   Notify an issuer that a command sent to us resulted in a parsing
9625
     *   failure.  We are meant to reply to the issuer to let the issuer
9626
     *   know about the problem.  messageProp is the libGlobal message
9627
     *   property describing the error, and args is a list with the
9628
     *   (varargs) arguments to the message property.  
9629
     */
9630
    notifyParseFailure(issuingActor, messageProp, args)
9631
    {
9632
        /* 
9633
         *   In case the actor is in a remote location but in scope for the
9634
         *   purposes of the conversation only (such as over a phone or
9635
         *   radio), run this in a neutral sense context.  Since we're
9636
         *   reporting a parser failure, we want the message to be
9637
         *   displayed no matter what the scope situation is. 
9638
         */
9639
        callWithSenseContext(nil, nil, new function()
9640
        {
9641
            /* check who's talking to whom */
9642
            if (issuingActor.isPlayerChar())
9643
            {
9644
                /*
9645
                 *   The player issued the command.  If the command was
9646
                 *   directed to an NPC (i.e., we're not the player), check
9647
                 *   to see if the player character is in scope from our
9648
                 *   perspective.  
9649
                 */
9650
                if (issuingActor != self && !canTalkTo(issuingActor))
9651
                {
9652
                    /* 
9653
                     *   The player issued the command to an NPC, but the
9654
                     *   player is not capable of hearing the NPC's
9655
                     *   response.  
9656
                     */
9657
                    cannotRespondToCommand(issuingActor, messageProp, args);
9658
                }
9659
                else
9660
                {
9661
                    /* 
9662
                     *   generate a message using the appropriate message
9663
                     *   generator object 
9664
                     */
9665
                    getParserMessageObj().(messageProp)(self, args...);
9666
                }
9667
            }
9668
            else
9669
            {
9670
                /*
9671
                 *   the command was issued from one NPC to another -
9672
                 *   notify the issuer of the problem, but don't display
9673
                 *   any messages, since this interaction is purely among
9674
                 *   the NPC's 
9675
                 */
9676
                issuingActor.
9677
                    notifyIssuerParseFailure(self, messageProp, args);
9678
            }
9679
        });
9680
    }
9681
9682
    /*
9683
     *   We have a parser error to report to the player, but we cannot
9684
     *   respond at the moment because the player is not capable of
9685
     *   hearing us (there is no sense path for our communications senses
9686
     *   from us to the player actor).  Defer reporting the message until
9687
     *   later.
9688
     */
9689
    cannotRespondToCommand(issuingActor, messageProp, args)
9690
    {
9691
        /* 
9692
         *   Remember the problem for later deliver.  If we already have a
9693
         *   deferred response, forget it - just report the latest
9694
         *   problem.  
9695
         */
9696
        pendingResponse =
9697
            new PendingResponseInfo(issuingActor, messageProp, args);
9698
9699
        /*
9700
         *   Some actors might want to override this to start searching
9701
         *   for the player character.  We don't have any generic
9702
         *   mechanism to conduct such a search, but a game that
9703
         *   implements one might want to make use of it here.  
9704
         */
9705
    }
9706
9707
    /*
9708
     *   Receive notification that a command we sent to another NPC
9709
     *   failed.  This is only called when one NPC sends a command to
9710
     *   another NPC; this is called on the issuer to let the issuer know
9711
     *   that the target can't perform the command because of the given
9712
     *   resolution failure.
9713
     *   
9714
     *   By default, we don't do anything here, because we don't have any
9715
     *   default code to send a command from one NPC to another.  Any
9716
     *   custom NPC actor that sends a command to another NPC actor might
9717
     *   want to use this to deal with problems in processing those
9718
     *   commands.  
9719
     */
9720
    notifyIssuerParseFailure(targetActor, messageProp, args)
9721
    {
9722
        /* by default, we do nothing */
9723
    }
9724
9725
    /*
9726
     *   Antecedent lookup table.  Each actor keeps its own table of
9727
     *   antecedents indexed by pronoun type, so that we can
9728
     *   simultaneously have different antecedents for different pronouns.
9729
     */
9730
    antecedentTable = nil
9731
9732
    /* 
9733
     *   Possessive anaphor lookup table.  In almost all cases, the
9734
     *   possessive anaphor for a given pronoun will be the same as the
9735
     *   corresponding regular pronoun: HIS indicates possession by HIM,
9736
     *   for example.  In a few cases, though, the anaphoric quality of
9737
     *   possessives takes precedence, and these will differ.  For
9738
     *   example, in TELL BOB TO DROP HIS BOOK, "his" refers back to Bob,
9739
     *   while in TELL BOB TO HIT HIM, "him" refers to whatever it
9740
     *   referred to before the command.  
9741
     */
9742
    possAnaphorTable = nil
9743
9744
    /* 
9745
     *   set the antecedent for the neuter singular pronoun ("it" in
9746
     *   English) 
9747
     */
9748
    setIt(obj)
9749
    {
9750
        setPronounAntecedent(PronounIt, obj);
9751
    }
9752
    
9753
    /* set the antecedent for the masculine singular ("him") */
9754
    setHim(obj)
9755
    {
9756
        setPronounAntecedent(PronounHim, obj);
9757
    }
9758
    
9759
    /* set the antecedent for the feminine singular ("her") */
9760
    setHer(obj)
9761
    {
9762
        setPronounAntecedent(PronounHer, obj);
9763
    }
9764
9765
    /* set the antecedent list for the ungendered plural pronoun ("them") */
9766
    setThem(lst)
9767
    {
9768
        setPronounAntecedent(PronounThem, lst);
9769
    }
9770
9771
    /* look up a pronoun's value */
9772
    getPronounAntecedent(typ)
9773
    {
9774
        /* get the stored antecedent for this pronoun */
9775
        return antecedentTable[typ];
9776
    }
9777
9778
    /* set a pronoun's antecedent value */
9779
    setPronounAntecedent(typ, val)
9780
    {
9781
        /* remember the value in the antecedent table */
9782
        antecedentTable[typ] = val;
9783
9784
        /* set the same value for the possessive anaphor */
9785
        possAnaphorTable[typ] = val;
9786
    }
9787
9788
    /* set a possessive anaphor value */
9789
    setPossAnaphor(typ, val)
9790
    {
9791
        /* set the value in the possessive anaphor table only */
9792
        possAnaphorTable[typ] = val;
9793
    }
9794
9795
    /* get a possessive anaphor value */
9796
    getPossAnaphor(typ) { return possAnaphorTable[typ]; }
9797
9798
    /* forget the possessive anaphors */
9799
    forgetPossAnaphors()
9800
    {
9801
        /* copy all of the antecedents to the possessive anaphor table */
9802
        antecedentTable.forEachAssoc(
9803
            {key, val: possAnaphorTable[key] = val});
9804
    }
9805
9806
    /*
9807
     *   Copy pronoun antecedents from the given actor.  This should be
9808
     *   called whenever an actor issues a command to us, so that pronouns
9809
     *   in the command are properly resolved relative to the issuer.  
9810
     */
9811
    copyPronounAntecedentsFrom(issuer)
9812
    {
9813
        /* copy every element from the issuer's table */
9814
        issuer.antecedentTable.forEachAssoc(
9815
            {key, val: setPronounAntecedent(key, val)});
9816
    }
9817
9818
    /* -------------------------------------------------------------------- */
9819
    /*
9820
     *   Verb processing 
9821
     */
9822
9823
    /* show a "take from" message as indicating I don't have the dobj */
9824
    takeFromNotInMessage = &takeFromNotInActorMsg
9825
9826
    /* verify() handler to check against applying an action to 'self' */
9827
    verifyNotSelf(msg)
9828
    {
9829
        /* check to make sure we're not trying to do this to myself */
9830
        if (self == gActor)
9831
            illogicalSelf(msg);
9832
    }
9833
9834
    /* macro to verify we're not self, and inherit the default behavior */
9835
#define verifyNotSelfInherit(msg) \
9836
    verify() \
9837
    { \
9838
        verifyNotSelf(msg); \
9839
        inherited(); \
9840
    }
9841
    
9842
    /* 
9843
     *   For the basic physical manipulation verbs (TAKE, DROP, PUT ON,
9844
     *   etc), it's illogical to operate on myself, so check for this in
9845
     *   verify().  Otherwise, handle these as we would ordinary objects,
9846
     *   since we might be able to manipulate other actors in the normal
9847
     *   manner, especially actors small enough that we can pick them up. 
9848
     */
9849
    dobjFor(Take) { verifyNotSelfInherit(&takingSelfMsg) }
9850
    dobjFor(Drop) { verifyNotSelfInherit(&droppingSelfMsg) }
9851
    dobjFor(PutOn) { verifyNotSelfInherit(&puttingSelfMsg) }
9852
    dobjFor(PutUnder) { verifyNotSelfInherit(&puttingSelfMsg) }
9853
    dobjFor(Throw) { verifyNotSelfInherit(&throwingSelfMsg) }
9854
    dobjFor(ThrowAt) { verifyNotSelfInherit(&throwingSelfMsg) }
9855
    dobjFor(ThrowDir) { verifyNotSelfInherit(&throwingSelfMsg) }
9856
    dobjFor(ThrowTo) { verifyNotSelfInherit(&throwingSelfMsg) }
9857
9858
    /* customize the message for THROW TO <actor> */
9859
    iobjFor(ThrowTo)
9860
    {
9861
        verify()
9862
        {
9863
            /* by default, we don't want to catch anything */
9864
            illogical(&willNotCatchMsg, self);
9865
        }
9866
    }
9867
9868
    /* treat PUT SELF IN FOO as GET IN FOO */
9869
    dobjFor(PutIn)
9870
    {
9871
        verify()
9872
        {
9873
            /* the target actor is always unsuitable as a default */
9874
            if (gActor == self)
9875
                nonObvious;
9876
        }
9877
9878
        check()
9879
        {
9880
            /* if I'm putting myself somewhere, treat it as GET IN */
9881
            if (gActor == self)
9882
                replaceAction(Enter, gIobj);
9883
9884
            /* do the normal work */
9885
            inherited();
9886
        }
9887
    }
9888
9889
    dobjFor(Kiss)
9890
    {
9891
        preCond = [touchObj]
9892
        verify()
9893
        {
9894
            /* cannot kiss oneself */
9895
            verifyNotSelf(&cannotKissSelfMsg);
9896
        }
9897
        action() { mainReport(&cannotKissActorMsg); }
9898
    }
9899
9900
    dobjFor(AskFor)
9901
    {
9902
        preCond = [canTalkToObj]
9903
        verify()
9904
        {
9905
            /* it makes no sense to ask myself for something */
9906
            verifyNotSelf(&cannotAskSelfForMsg);
9907
        }
9908
        action()
9909
        {
9910
            /* note that the issuer is targeting us with conversation */
9911
            gActor.noteConversation(self);
9912
9913
            /* let the state object handle it */
9914
            curState.handleConversation(gActor, gTopic, askForConvType);
9915
        }
9916
    }
9917
9918
    dobjFor(TalkTo)
9919
    {
9920
        preCond = [canTalkToObj]
9921
        verify()
9922
        {
9923
            /* it's generally illogical to talk to oneself */
9924
            verifyNotSelf(&cannotTalkToSelfMsg);
9925
        }
9926
        action()
9927
        {
9928
            /* note that the issuer is targeting us in conversation */
9929
            gActor.noteConversation(self);
9930
9931
            /* handle it as a 'hello' topic */
9932
            curState.handleConversation(gActor, helloTopicObj, helloConvType);
9933
        }
9934
    }
9935
9936
    iobjFor(GiveTo)
9937
    {
9938
        verify()
9939
        {
9940
            /* it makes no sense to give something to myself */
9941
            verifyNotSelf(&cannotGiveToSelfMsg);
9942
9943
            /* it also makes no sense to give something to itself */
9944
            if (gDobj == gIobj)
9945
                illogicalSelf(&cannotGiveToItselfMsg);
9946
        }
9947
        action()
9948
        {
9949
            /* take note that I've seen the direct object */
9950
            noteObjectShown(gDobj);
9951
9952
            /* note that the issuer is targeting us with conversation */
9953
            gActor.noteConversation(self);
9954
9955
            /* let the state object handle it */
9956
            curState.handleConversation(gActor, gDobj, giveConvType);
9957
        }
9958
    }
9959
9960
    iobjFor(ShowTo)
9961
    {
9962
        verify()
9963
        {
9964
            /* it makes no sense to show something to myself */
9965
            verifyNotSelf(&cannotShowToSelfMsg);
9966
9967
            /* it also makes no sense to show something to itself */
9968
            if (gDobj == gIobj)
9969
                illogicalSelf(&cannotShowToItselfMsg);
9970
        }
9971
        action()
9972
        {
9973
            /* take note that I've seen the direct object */
9974
            noteObjectShown(gDobj);
9975
9976
            /* note that the issuer is targeting us with conversation */
9977
            gActor.noteConversation(self);
9978
9979
            /* let the actor state object handle it */
9980
            curState.handleConversation(gActor, gDobj, showConvType);
9981
        }
9982
    }
9983
9984
    /*
9985
     *   Note that the given object has been explicitly shown to me.  By
9986
     *   default, we'll mark the object and its visible contents as having
9987
     *   been seen by me.  This is called whenever we're the target of a
9988
     *   SHOW TO or GIVE TO, since presumably such an explicit act of
9989
     *   calling our attention to an object would make us consider the
9990
     *   object as having been seen in the future.  
9991
     */
9992
    noteObjectShown(obj)
9993
    {
9994
        local info;
9995
9996
        /* get the table of things we can see */
9997
        info = visibleInfoTable();
9998
9999
        /* if the object is in the table, mark it as seen */
10000
        if (info[obj] != nil)
10001
            setHasSeen(obj);
10002
10003
        /* also mark the visible contents of the object as having been seen */
10004
        obj.setContentsSeenBy(info, self);
10005
    }
10006
10007
    dobjFor(AskAbout)
10008
    {
10009
        preCond = [canTalkToObj]
10010
        verify()
10011
        {
10012
            /* it makes no sense to ask oneself about something */
10013
            verifyNotSelf(&cannotAskSelfMsg);
10014
        }
10015
        action()
10016
        {
10017
            /* note that the issuer is targeting us with conversation */
10018
            gActor.noteConversation(self);
10019
10020
            /* let our state object handle it */
10021
            curState.handleConversation(gActor, gTopic, askAboutConvType);
10022
        }
10023
    }
10024
10025
    dobjFor(TellAbout)
10026
    {
10027
        preCond = [canTalkToObj]
10028
        verify()
10029
        {
10030
            /* it makes no sense to tell oneself about something */
10031
            verifyNotSelf(&cannotTellSelfMsg);
10032
        }
10033
        check()
10034
        {
10035
            /* 
10036
             *   If the direct object is the issuing actor, rephrase this
10037
             *   as "issuer, ask actor about iobj".
10038
             *   
10039
             *   Note that we do this in 'check' rather than 'action',
10040
             *   because this will ensure that we'll rephrase the command
10041
             *   properly even if the subclass overrides with its own
10042
             *   check, AS LONG AS the overriding method inherits this base
10043
             *   definition first.  If we did the rephrasing in the
10044
             *   'action', then an overriding 'check' might incorrectly
10045
             *   disqualify the operation on the assumption that it's an
10046
             *   ordinary TELL ABOUT rather than what it really is, which
10047
             *   is a rephrased ASK ABOUT.  
10048
             */
10049
            if (gDobj == gIssuingActor)
10050
                replaceActorAction(gIssuingActor, AskAbout, gActor, gTopic);
10051
10052
        }
10053
        action()
10054
        {
10055
            /* note that the issuer is targeting us with conversation */
10056
            gActor.noteConversation(self);
10057
10058
            /* let the state object handle it */
10059
            curState.handleConversation(gActor, gTopic, tellAboutConvType);
10060
        }
10061
    }
10062
10063
    /*
10064
     *   Handle a conversational command.  All of the conversational
10065
     *   actions (HELLO, GOODBYE, YES, NO, ASK ABOUT, ASK FOR, TELL ABOUT,
10066
     *   SHOW TO, GIVE TO) are routed here when we're the target of the
10067
     *   action (for example, we're BOB in ASK BOB ABOUT TOPIC) AND the
10068
     *   ActorState doesn't want to handle the action. 
10069
     */
10070
    handleConversation(actor, topic, convType)
10071
    {
10072
        /* try handling the topic from our topic database */
10073
        if (!handleTopic(actor, topic, convType, nil))
10074
        {
10075
            /* the topic database didn't handle it; use a default response */
10076
            defaultConvResponse(actor, topic, convType);
10077
        }
10078
    }
10079
10080
    /*
10081
     *   Show a default response to a conversational action.  By default,
10082
     *   we'll show the default response for our conversation type.  
10083
     */
10084
    defaultConvResponse(actor, topic, convType)
10085
    {
10086
        /* call the appropriate default response for the ConvType */
10087
        convType.defaultResponse(self, actor, topic);
10088
    }
10089
10090
    /* 
10091
     *   Show our default greeting message - this is used when the given
10092
     *   another actor greets us with HELLO or TALK TO, and we don't
10093
     *   otherwise handle it (such as via a topic database entry).
10094
     *   
10095
     *   By default, we'll just show "there's no response" as a default
10096
     *   message.  We'll show this in default mode, so that if the caller
10097
     *   is going to show a list of suggested conversation topics (which
10098
     *   the 'hello' and 'talk to' commands will normally try to do), the
10099
     *   topic list will override the "there's no response" default.  In
10100
     *   other words, we'll have one of these two types of exchanges:
10101
     *   
10102
     *.  >talk to bob
10103
     *.  There's no response
10104
     *   
10105
     *.  >talk to bill
10106
     *.  You could ask him about the candle, the book, or the bell, or
10107
     *.  tell him about the crypt.
10108
     */
10109
    defaultGreetingResponse(actor)
10110
        { defaultReport(&noResponseFromMsg, self); }
10111
10112
    /* show our default goodbye message */
10113
    defaultGoodbyeResponse(actor)
10114
        { mainReport(&noResponseFromMsg, self); }
10115
10116
    /*
10117
     *   Show the default answer to a question - this is called when we're
10118
     *   the actor in ASK <actor> ABOUT <topic>, and we can't find a more
10119
     *   specific response for the given topic.
10120
     *   
10121
     *   By default, we'll show the basic "there's no response" message.
10122
     *   This isn't a very good message in most cases, because it makes an
10123
     *   actor pretty frustratingly un-interactive, which gives the actor
10124
     *   the appearance of a cardboard cut-out.  But there's not much
10125
     *   better that the library can do; the potential range of actors
10126
     *   makes a more specific default response impossible.  If the default
10127
     *   response were "I don't know about that," it wouldn't work very
10128
     *   well if the actor is someone who only speaks Italian.  So, the
10129
     *   best we can do is this generally rather poor default.  But that
10130
     *   doesn't mean that authors should resign themselves to a poor
10131
     *   default answer; instead, it means that actors should take care to
10132
     *   override this when defining an actor, because it's usually
10133
     *   possible to find a much better default for a *specific* actor.
10134
     *   
10135
     *   The *usual* way of providing a default response is to define a
10136
     *   DefaultAskTopic (or a DefaultAskTellTopic) and put it in the
10137
     *   actor's topic database.  
10138
     */
10139
    defaultAskResponse(fromActor, topic)
10140
        { mainReport(&noResponseFromMsg, self); }
10141
10142
    /*
10143
     *   Show the default response to being told of a topic - this is
10144
     *   called when we're the actor in TELL <actor> ABOUT <topic>, and we
10145
     *   can't find a more specific response for the topic.
10146
     *   
10147
     *   As with defaultAskResponse, this should almost always be
10148
     *   overridden by each actor, since the default response ("there's no
10149
     *   response") doesn't make the actor seem very dynamic.
10150
     *   
10151
     *   The usual way of providing a default response is to define a
10152
     *   DefaultTellTopic (or a DefaultAskTellTopic) and put it in the
10153
     *   actor's topic database.  
10154
     */
10155
    defaultTellResponse(fromActor, topic)
10156
        { mainReport(&noResponseFromMsg, self); }
10157
10158
    /* the default response for SHOW TO */
10159
    defaultShowResponse(byActor, topic)
10160
        { mainReport(&notInterestedMsg, self); }
10161
10162
    /* the default response for GIVE TO */
10163
    defaultGiveResponse(byActor, topic)
10164
        { mainReport(&notInterestedMsg, self); }
10165
10166
    /* the default response for ASK FOR */
10167
    defaultAskForResponse(byActor, obj)
10168
        { mainReport(&noResponseFromMsg, self); }
10169
10170
    /* default response to being told YES */
10171
    defaultYesResponse(fromActor)
10172
        { mainReport(&noResponseFromMsg, self); }
10173
10174
    /* default response to being told NO */
10175
    defaultNoResponse(fromActor)
10176
        { mainReport(&noResponseFromMsg, self); }
10177
10178
    /* default refusal of a command */
10179
    defaultCommandResponse(fromActor, topic)
10180
        { mainReport(&refuseCommand, self, fromActor); }
10181
;
10182
10183
/* ------------------------------------------------------------------------ */
10184
/*
10185
 *   An UntakeableActor is one that can't be picked up and moved. 
10186
 */
10187
class UntakeableActor: Actor, Immovable
10188
    /* use customized messages for some 'Immovable' methods */
10189
    cannotTakeMsg = &cannotTakeActorMsg
10190
    cannotMoveMsg = &cannotMoveActorMsg
10191
    cannotPutMsg = &cannotPutActorMsg
10192
10193
    /* TASTE tends to be a bit rude */
10194
    dobjFor(Taste)
10195
    {
10196
        action() { mainReport(&cannotTasteActorMsg); }
10197
    }
10198
10199
    /* 
10200
     *   even though we act like an Immovable, we don't count as an
10201
     *   Immovable for listing purposes 
10202
     */
10203
    contentsInFixedIn(loc) { return nil; }
10204
;
10205
10206
10207
/*
10208
 *   A Person is an actor that represents a human character.  This is just
10209
 *   an UntakeableActor with some custom versions of the messages for
10210
 *   taking and moving the actor.  
10211
 */
10212
class Person: UntakeableActor
10213
    /* customize the messages for trying to take or move me */
10214
    cannotTakeMsg = &cannotTakePersonMsg
10215
    cannotMoveMsg = &cannotMovePersonMsg
10216
    cannotPutMsg = &cannotPutPersonMsg
10217
    cannotTasteActorMsg = &cannotTastePersonMsg
10218
10219
    /* 
10220
     *   use a fairly large default bulk, since people are usually fairly
10221
     *   large compared with the sorts of items that one carries around 
10222
     */
10223
    bulk = 10
10224
;
10225
10226
/* ------------------------------------------------------------------------ */
10227
/*
10228
 *   Pending response information structure 
10229
 */
10230
class PendingResponseInfo: object
10231
    construct(issuer, prop, args)
10232
    {
10233
        issuer_ = issuer;
10234
        prop_ = prop;
10235
        args_ = args;
10236
    }
10237
10238
    /* the issuer of the command (and target of the response) */
10239
    issuer_ = nil
10240
10241
    /* the message property and argument list for the message */
10242
    prop_ = nil
10243
    args_ = []
10244
;
10245
10246
/*
10247
 *   Pending Command Information structure.  This is an abstract base class
10248
 *   that we subclass for particular ways of representing the command to be
10249
 *   executed.  
10250
 */
10251
class PendingCommandInfo: object
10252
    construct(issuer) { issuer_ = issuer; }
10253
    
10254
    /*
10255
     *   Check to see if this pending command item has a command to
10256
     *   perform.  This returns true if we have a command, nil if we're
10257
     *   just a queue placeholder without any actual command to execute.  
10258
     */
10259
    hasCommand = true
10260
10261
    /* execute the command */
10262
    executePending(targetActor) { }
10263
10264
    /* the issuer of the command */
10265
    issuer_ = nil
10266
10267
    /* we're at the start of a "sentence" */
10268
    startOfSentence_ = nil
10269
;
10270
10271
/* a pending command based on a list of tokens from an input string */
10272
class PendingCommandToks: PendingCommandInfo
10273
    construct(startOfSentence, issuer, toks)
10274
    {
10275
        inherited(issuer);
10276
        
10277
        startOfSentence_ = startOfSentence;
10278
        tokens_ = toks;
10279
    }
10280
10281
    /* 
10282
     *   Execute the command.  We'll parse our tokens and execute the
10283
     *   parsed results.
10284
     */
10285
    executePending(targetActor)
10286
    {
10287
        /* parse and execute the tokens */
10288
        executeCommand(targetActor, issuer_, tokens_, startOfSentence_);
10289
    }
10290
    
10291
    /* the token list for the command */
10292
    tokens_ = nil
10293
;
10294
10295
/* a pending command based on a pre-resolved Action and its objects */
10296
class PendingCommandAction: PendingCommandInfo
10297
    construct(startOfSentence, issuer, action, [objs])
10298
    {
10299
        inherited(issuer);
10300
        
10301
        startOfSentence_ = startOfSentence;
10302
        action_ = action;
10303
        objs_ = objs;
10304
    }
10305
10306
    /* execute the pending command */
10307
    executePending(targetActor)
10308
    {
10309
        /* invoke the action's main execution method */
10310
        try
10311
        {
10312
            /* run the action */
10313
            newActionObj(CommandTranscript, issuer_,
10314
                         targetActor, action_, objs_...);
10315
        }
10316
        catch (TerminateCommandException tcExc)
10317
        {
10318
            /* 
10319
             *   the command cannot proceed; simply abandon the command
10320
             *   action here 
10321
             */
10322
        }
10323
    }
10324
    
10325
    /* the resolved Action to perform */
10326
    action_ = nil
10327
10328
    /* the resolved objects for the action */
10329
    objs_ = nil
10330
;
10331
10332
/*
10333
 *   A pending command marker.  This is not an actual pending command;
10334
 *   rather, it's just a queue marker.  We sometimes want to synchronize
10335
 *   some other activity with an actor's progress through its command
10336
 *   queue; for example, we might want one actor to wait until another
10337
 *   actor has executed a particular pending action.  These markers can be
10338
 *   used for this kind of synchronization; they move through the queue
10339
 *   like ordinary pending commands, so we can tell if an actor has reached
10340
 *   a particular command by observing the marker's progress through the
10341
 *   queue.  
10342
 */
10343
class PendingCommandMarker: PendingCommandInfo
10344
    /* I have no command to execute */
10345
    hasCommand = nil
10346
;
10347
10348
/* ------------------------------------------------------------------------ */
10349
/*
10350
 *   Set the current player character 
10351
 */
10352
setPlayer(actor)
10353
{
10354
    /* remember the new player character */
10355
    libGlobal.playerChar = actor;
10356
10357
    /* set the root global point of view to this actor */
10358
    setRootPOV(actor, actor);
10359
}
10360