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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved.
5
 *   
6
 *   TADS 3 Library: Actions.
7
 *   
8
 *   This module defines the Action classes.  An Action is an abstract
9
 *   object representing a command to be performed.  
10
 */
11
12
#include "adv3.h"
13
#include "tok.h"
14
15
/* ------------------------------------------------------------------------ */
16
/*
17
 *   Object associations lists.  We use this object to store some lookup
18
 *   tables that we build during preinitialization to relate object usages
19
 *   (DirectObject, IndirectObject) to certain properties. 
20
 */
21
objectRelations: PreinitObject
22
    /* preinitialization - build the lookup tables */
23
    execute()
24
    {
25
        /* build the default pre-condition properties table */
26
        preCondDefaultProps[DirectObject] = &preCondDobjDefault;
27
        preCondDefaultProps[IndirectObject] = &preCondIobjDefault;
28
29
        /* build the catch-all pre-conditions properties table */
30
        preCondAllProps[DirectObject] = &preCondDobjAll;
31
        preCondAllProps[IndirectObject] = &preCondIobjAll;
32
33
        /* build the default verification properties table */
34
        verifyDefaultProps[DirectObject] = &verifyDobjDefault;
35
        verifyDefaultProps[IndirectObject] = &verifyIobjDefault;
36
37
        /* build the catch-all verification properties table */
38
        verifyAllProps[DirectObject] = &verifyDobjAll;
39
        verifyAllProps[IndirectObject] = &verifyIobjAll;
40
41
        /* build the default check properties table */
42
        checkDefaultProps[DirectObject] = &checkDobjDefault;
43
        checkDefaultProps[IndirectObject] = &checkIobjDefault;
44
45
        /* build the catch-all check properties table */
46
        checkAllProps[DirectObject] = &checkDobjAll;
47
        checkAllProps[IndirectObject] = &checkIobjAll;
48
49
        /* build the default action properties table */
50
        actionDefaultProps[DirectObject] = &actionDobjDefault;
51
        actionDefaultProps[IndirectObject] = &actionIobjDefault;
52
53
        /* build the catch-all check properties table */
54
        actionAllProps[DirectObject] = &actionDobjAll;
55
        actionAllProps[IndirectObject] = &actionIobjAll;
56
    }
57
58
    /* lookup table for default precondition properties */
59
    preCondDefaultProps = static new LookupTable()
60
61
    /* lookup table for catch-all precondition properties */
62
    preCondAllProps = static new LookupTable()
63
64
    /* lookup table for default verification properties */
65
    verifyDefaultProps = static new LookupTable()
66
67
    /* lookup table for catch-all verification properties */
68
    verifyAllProps = static new LookupTable()
69
70
    /* lookup table for default check properties */
71
    checkDefaultProps = static new LookupTable()
72
73
    /* lookup table for catch-all check properties */
74
    checkAllProps = static new LookupTable()
75
76
    /* lookup table for default action properties */
77
    actionDefaultProps = static new LookupTable()
78
79
    /* lookup table for catch-all action properties */
80
    actionAllProps = static new LookupTable()
81
;
82
83
84
/* ------------------------------------------------------------------------ */
85
/*
86
 *   Invoke the given function with the given values for the parser global
87
 *   variables gActor and gAction.  
88
 */
89
withParserGlobals(issuer, actor, action, func)
90
{
91
    local oldIssuer;
92
    local oldActor;
93
    local oldAction;
94
    
95
    /* remember the old action and actor, so we can restore them later */
96
    oldIssuer = gIssuingActor;
97
    oldActor = gActor;
98
    oldAction = gAction;
99
    
100
    /* establish our actor and action as the global settings */
101
    gIssuingActor = issuer;
102
    gActor = actor;
103
    gAction = action;
104
    
105
    /* make sure we restore globals on the way out */
106
    try
107
    {
108
        /* invoke the callback and return the result */
109
        return (func)();
110
    }
111
    finally
112
    {
113
        /* restore the globals we changed */
114
        gActor = oldActor;
115
        gIssuingActor = oldIssuer;
116
        gAction = oldAction;
117
    }
118
}
119
120
121
/* ------------------------------------------------------------------------ */
122
/*
123
 *   Pre-condition Descriptor.  This object encapsulates a precondition
124
 *   object and the argument we want to pass to its condition check method.
125
 */
126
class PreCondDesc: object
127
    construct(cond, arg)
128
    {
129
        /* remember the condition and the check argument */
130
        cond_ = cond;
131
        arg_ = arg;
132
    }
133
134
    /* check the precondition */
135
    checkPreCondition(allowImplicit)
136
    {
137
        /* call the precondition's check method with the argument we stored */
138
        return cond_.checkPreCondition(arg_, allowImplicit);
139
    }
140
141
    /* the precondition object */
142
    cond_ = nil
143
144
    /* the check argument */
145
    arg_ = nil
146
147
    /* our list sorting index */
148
    index_ = 0
149
;
150
151
152
/* ------------------------------------------------------------------------ */
153
/*
154
 *   Basic Action class.  An Action is the language-independent definition
155
 *   of the abstract action of a command.  
156
 */
157
class Action: BasicProd
158
    /*
159
     *   Are we the given kind of action?  By default, this simply returns
160
     *   true if we're of the given action class. 
161
     */
162
    actionOfKind(cls) { return ofKind(cls); }
163
164
    /*
165
     *   Reset the action in preparation for re-execution.  This should
166
     *   discard any scoped context from any past execution of the
167
     *   command, such as cached scope information.  
168
     */
169
    resetAction()
170
    {
171
        /* forget any past successful verification passes */
172
        verifiedOkay = [];
173
    }
174
175
    /* 
176
     *   Repeat the action, for an AGAIN command.
177
     */
178
    repeatAction(lastTargetActor, lastTargetActorPhrase,
179
                 lastIssuingActor, countsAsIssuerTurn)
180
    {
181
        /* execute the command */
182
        executeAction(lastTargetActor, lastTargetActorPhrase,
183
                      lastIssuingActor, countsAsIssuerTurn, self);
184
    }
185
186
    /*
187
     *   Cancel iteration of the action.  This can be called during the
188
     *   'check' or 'action' phases of executing this action.  It tells
189
     *   the action that we want to stop executing the action when we're
190
     *   finished with the current object.
191
     *   
192
     *   Note that this doesn't cause a jump out of the current code, so
193
     *   it's not like 'exit' or the other termination signals.  Instead,
194
     *   this simply tells the action to proceed normally for the
195
     *   remainder of the processing for the current object, and then act
196
     *   as though there were no more objects to iterate over, ending the
197
     *   command normally.  If you want to cut off the remainder of the
198
     *   execution cycle for the current object, you can use 'exit' (for
199
     *   example) immediately after calling this method.  
200
     */
201
    cancelIteration() { iterationCanceled = true; }
202
203
    /* internal flag: object iteration has been canceled */
204
    iterationCanceled = nil
205
206
    /*
207
     *   Create an instance of this action, for use by a recursive or
208
     *   programmatically-generated command.
209
     *   
210
     *   The generic actions defined in the library are always subclassed
211
     *   by language-specific library modules, because the language
212
     *   modules have to define the grammar rules for the verbs - we can't
213
     *   define the grammar rules generically because the verbs wouldn't
214
     *   be reusable for non-English translations if we did.  As a result,
215
     *   library code referring to one of the library verbs by name, say
216
     *   TakeAction, doesn't get a language-specific subclass of the verb,
217
     *   but just gets the language-independent base class.
218
     *   
219
     *   However, to make full use of an Action object in a recursive
220
     *   command, we do need a final language-specific subclass - without
221
     *   this, we won't be able to generate text describing the command,
222
     *   for example.  This method bridges this gap by finding a suitable
223
     *   language-specific subclass of the given action, then creating an
224
     *   instance of that subclass rather than an instance of the base
225
     *   class.
226
     *   
227
     *   By default, we'll take any subclass of this action that is itself
228
     *   a class.  However, if any subclass has the property
229
     *   defaultForRecursion set to true, we'll use that class
230
     *   specifically - this lets the language module designate a
231
     *   particular subclass to use as the default for recursive commands,
232
     *   which might be desirable in cases where the language module
233
     *   defines more than one subclass of an action.  
234
     */
235
    createActionInstance()
236
    {
237
        local found;
238
239
        /* 
240
         *   Iterate over our subclasses.  Initialize 'found' to this base
241
         *   class, so that if we fail to find any subclasses, we'll at
242
         *   least be able to create an instance of the generic base
243
         *   class.  
244
         */
245
        for (local cur = firstObj(self, ObjClasses), found = self ;
246
             cur != nil ; cur = nextObj(cur, self, ObjClasses))
247
        {
248
            /* 
249
             *   if this one is marked as a default for recursion, and the
250
             *   last one we found isn't, choose this one over the last
251
             *   one we found 
252
             */
253
            if (cur.defaultForRecursion && !found.defaultForRecursion)
254
                found = cur;
255
            
256
            /* 
257
             *   If this one is a subclass of the last one we found, pick
258
             *   it instead of the last one.  We always want a final
259
             *   subclass here, never an intermediate class, so if we find
260
             *   a subclass of the one we've tentatively picked, we know
261
             *   that the tentative selection isn't final after all.
262
             */
263
            if (cur.ofKind(found))
264
                found = cur;
265
        }
266
267
        /* return a new instance of what we found */
268
        return found.createInstance();
269
    }
270
271
    /*
272
     *   Create an instance of this action based on another action.  We'll
273
     *   copy the basic properties of the original action. 
274
     */
275
    createActionFrom(orig)
276
    {
277
        local action;
278
        
279
        /* create a new instance of this action */
280
        action = createActionInstance();
281
282
        /* copy the token list information to the new action */
283
        action.tokenList = orig.tokenList;
284
        action.firstTokenIndex = orig.firstTokenIndex;
285
        action.lastTokenIndex = orig.lastTokenIndex;
286
287
        /* the new action is implicit if the original was */
288
        if (orig.isImplicit)
289
            action.setImplicit(orig.implicitMsg);
290
291
        /* return the new action */
292
        return action;
293
    }
294
295
    /*
296
     *   Mark the command as implicit.  'msgProp' is the property (of
297
     *   gLibMessages) to use to announce the implicit command.  
298
     */
299
    setImplicit(msgProp)
300
    {
301
        /* mark ourselves as implicit */
302
        isImplicit = true;
303
304
        /* do not show default reports for implicit commands */
305
        showDefaultReports = nil;
306
307
        /* all implicit commands are nested */
308
        setNested();
309
310
        /* 
311
         *   do not include implicit commands in undo - since an implicit
312
         *   command is only a subpart of an explicit command, an implicit
313
         *   command is not undone individually but only as part of the
314
         *   enclosing explicit command 
315
         */
316
        includeInUndo = nil;
317
318
        /* remember the implicit command announcement message property */
319
        implicitMsg = msgProp;
320
    }
321
322
    /*
323
     *   Mark the command as nested, noting the parent action (which we
324
     *   take as the global current action). 
325
     */
326
    setNested()
327
    {
328
        /* remember the parent action */
329
        parentAction = gAction;
330
    }
331
332
    /* 
333
     *   Determine if I'm nested within the given action.  Returns true if
334
     *   the given action is my parent action, or if my parent action is
335
     *   nested within the given action. 
336
     */
337
    isNestedIn(action)
338
    {
339
        /* if my parent action is the given action, I'm nested in it */
340
        if (parentAction == action)
341
            return true;
342
343
        /* 
344
         *   if I have a parent action, and it's nested in the given
345
         *   action, then I'm nested in the given action because I'm
346
         *   nested in anything my parent is nested in 
347
         */
348
        if (parentAction != nil && parentAction.isNestedIn(action))
349
            return true;
350
351
        /* we're not nested in the given action */
352
        return nil;
353
    }
354
355
    /*
356
     *   Set the "original" action.  An action with an original action is
357
     *   effectively part of the original action for the purposes of its
358
     *   reported results.
359
     *   
360
     *   An action has an original action if it's a nested or replacement
361
     *   action for an action.  
362
     */
363
    setOriginalAction(action)
364
    {
365
        /* remember my original action */
366
        originalAction = action;
367
    }
368
369
    /*
370
     *   Get the "original" action.  If I'm a replacement or nested action,
371
     *   this returns the original main action of which I'm a part, for
372
     *   reporting pruposes.
373
     *   
374
     *   It's important to note that an implied action does NOT count as a
375
     *   nested or replacement action for the purposes of this method.
376
     *   That is, if a command A triggers an implied action B, which
377
     *   triggers a nested action C, and then after that command A itself
378
     *   triggers a nested action D, then
379
     *   
380
     *   A.getOriginalAction -> A
381
     *.  B.getOriginalAction -> B (B is implied, not nested)
382
     *.  C.getOriginalAction -> C (C is nested within B)
383
     *.  D.getOriginalAction -> A (D is nested within A)
384
     *   
385
     *   The purpose of the original action is to tell us, mainly for
386
     *   reporting purposes, what we're really trying to do with the
387
     *   action.  This allows reports to hide the internal details of how
388
     *   the action is carried out, and instead say what the action was
389
     *   meant to do from the player's perspective.  
390
     */
391
    getOriginalAction()
392
    {
393
        /* if I have no other original action, I'm the original action */
394
        if (originalAction == nil)
395
            return self;
396
397
        /* return my original action's original action */
398
        return originalAction.getOriginalAction();
399
    }
400
401
    /*
402
     *   Determine if this action is "part of" the given action.  I'm part
403
     *   of the given action if I am the given action, or the given action
404
     *   is my "original" action, or my original action is part of the
405
     *   given action.
406
     */
407
    isPartOf(action)
408
    {
409
        /* if I'm the same as the given action, I'm obviously part of it */
410
        if (action == self)
411
            return true;
412
413
        /* if my original action is part of the action, I'm part of it */
414
        if (originalAction != nil && originalAction.isPartOf(action))
415
            return true;
416
417
        /* I'm not part of the given action */
418
        return nil;
419
    }
420
421
    /*
422
     *   Mark the action as "remapped."  This indicates that the action
423
     *   was explicitly remapped to a different action during the remap()
424
     *   phase.  
425
     */
426
    setRemapped(orig) { remappedFrom = gAction; }
427
428
    /* determine if I'm remapped, and get the original action if so */
429
    isRemapped() { return remappedFrom != nil; }
430
    getRemappedFrom() { return remappedFrom; }
431
432
    /*
433
     *   Get the "simple synonym" remapping for one of our objects, if
434
     *   any.  'obj' is the resolved object to remap, and 'role' is the
435
     *   object role identifier (DirectObject, IndirectObject, etc).
436
     *   'remapProp' is the remapping property for the role; this is
437
     *   simply the result of our getRemapPropForRole(role), but we ask
438
     *   the caller to pass this in so that it can be pre-computed in
439
     *   cases where we'll called in a loop.
440
     *   
441
     *   A simple synonym remapping is a remapTo that applies the same
442
     *   verb to a new object in the same role.  For example, if we remap
443
     *   OPEN DESK to OPEN DRAWER, then the drawer is the simple synonym
444
     *   remapping for the desk in an OPEN command.  A remapping is
445
     *   considered a simple synonym remapping only if we're remapping to
446
     *   the same action, AND the new object is in the same action role as
447
     *   the original object was.
448
     *   
449
     *   If there's no simple synonym remapping, we'll return nil.  
450
     */
451
    getSimpleSynonymRemap(obj, role, remapProp)
452
    {
453
        local mapping;
454
        local remapIdx;
455
        
456
        /* if the object isn't remapped at all, there's no remapping */
457
        if ((mapping = obj.(remapProp)) == nil)
458
            return nil;
459
460
        /* if the mapping isn't to the same action, it's not a synonym */
461
        if (!ofKind(mapping[1]))
462
            return nil;
463
464
465
        /* 
466
         *   Find the specific (non-role) object in the remap vector -
467
         *   this is the entry that's actually an object rather than a
468
         *   role identifier.  (The remapping vector is required to have
469
         *   exactly one such entry.  Look from the right end of the list,
470
         *   since the first entry is always the new action, which is
471
         *   itself an object, but not the object we're looking for.)  
472
         */
473
        remapIdx = mapping.lastIndexWhich({x: dataType(x) == TypeObject});
474
        if (remapIdx == nil)
475
            return nil;
476
477
        /* 
478
         *   Determine if the object plays the same role in the new action
479
         *   as the original object did in the original action.  It does
480
         *   if it's at the index in the mapping vector of our object role
481
         *   in the action.  Note that the mapping vector has slot 1
482
         *   filled with the action, so its objects are actually one slot
483
         *   higher than they are in the action itself.
484
         *   
485
         *   If the new object isn't in the same role, then this isn't a
486
         *   simple synonym remapping.  
487
         */
488
        if (getRoleFromIndex(remapIdx - 1) != role)
489
            return nil;
490
491
        /* 
492
         *   We have the same action applied to a new object in the same
493
         *   role as the original object, so this is a simple synonym
494
         *   remapping.  Return the new object we're mapping to. 
495
         */
496
        return mapping[remapIdx];
497
    }
498
499
    /* 
500
     *   the defaultForRecursion flag must be explicitly set in subclasses
501
     *   when desired - by default we'll use any language-specific
502
     *   subclass of an Action for recursive commands 
503
     */
504
    defaultForRecursion = nil
505
506
    /* 
507
     *   Flag: the command is implicit.  An implicit command is one that
508
     *   is performed as an implied enabling step of another command - for
509
     *   example, if an actor wants to throw something, the actor must be
510
     *   holding the object, so will implicitly try to take the object.
511
     */
512
    isImplicit = nil
513
514
    /*
515
     *   The parent action.  If the command is performed programmatically
516
     *   in the course of executing another command, this is set to the
517
     *   enclosing action.
518
     *   
519
     *   Note that while all implicit commands are nested, not all nested
520
     *   commands are implicit.  A nested command may simply be a
521
     *   component of another command, or another command may be handled
522
     *   entirely by running a different command as a nested command.  In
523
     *   any case, a nested but non-implicit command does not appear to
524
     *   the player as a separate command; it is simply part of the
525
     *   original command.  
526
     */
527
    parentAction = nil
528
529
    /*
530
     *   The original action we were remapped from.  This is valid when
531
     *   the action was explicitly remapped during the remap() phase to a
532
     *   different action. 
533
     */
534
    remappedFrom = nil
535
536
    /* 
537
     *   the original action - we are effectively part of the original
538
     *   action for reporting purposes 
539
     */
540
    originalAction = nil
541
542
    /* the libMessage property, if any, to announce the implicit command */
543
    implicitMsg = nil
544
545
    /* 
546
     *   Flag: we are to show default reports for this action.  In most
547
     *   cases we will do so, but for some types of commands (such as
548
     *   implicit commands), we suppress default reports. 
549
     */
550
    showDefaultReports = true
551
552
    /*
553
     *   Get a message parameter object for the action.  Each action
554
     *   subclass defines this to return its objects according to its own
555
     *   classifications.  The default action has no objects, but
556
     *   recognizes 'actor' as the current command's actor.  
557
     */
558
    getMessageParam(objName)
559
    {
560
        switch(objName)
561
        {
562
        case 'pc':
563
            /* return the player character */
564
            return gPlayerChar;
565
            
566
        case 'actor':
567
            /* return the current actor */
568
            return gActor;
569
570
        default:
571
            /* 
572
             *   if we have an extra message parameters table, look up the
573
             *   parameter name in the table 
574
             */
575
            if (extraMessageParams != nil)
576
                return extraMessageParams[objName];
577
578
            /* we don't recognize other names */
579
            return nil;
580
        }
581
    }
582
583
    /*
584
     *   Define an extra message-specific parameter.  Message processors
585
     *   can use this to add their own special parameters, so that they
586
     *   can refer to parameters that aren't involved directly in the
587
     *   command.  For example, a message for "take <dobj>" might want to
588
     *   refer to the object containing the direct object.
589
     */
590
    setMessageParam(objName, obj)
591
    {
592
        /* 
593
         *   if we don't yet have an extra message parameters table,
594
         *   create a small lookup table for it 
595
         */
596
        if (extraMessageParams == nil)
597
            extraMessageParams = new LookupTable(8, 8);
598
599
        /* add the parameter to the table, indexing by the parameter name */
600
        extraMessageParams[objName.toLower()] = obj;
601
    }
602
603
    /*
604
     *   For convenience, this method allows setting any number of
605
     *   name/value pairs for message parameters. 
606
     */
607
    setMessageParams([lst])
608
    {
609
        /* set each pair from the argument list */
610
        for (local i = 1, local len = lst.length() ; i+1 <= len ; i += 2)
611
            setMessageParam(lst[i], lst[i+1]);
612
    }
613
614
    /*
615
     *   Synthesize a global message parameter name for the given object.
616
     *   We'll store the association and return the synthesized name. 
617
     */
618
    synthMessageParam(obj)
619
    {
620
        local nm;
621
        
622
        /* synthesize a name */
623
        nm = 'synth' + toString(synthParamID++);
624
625
        /* store the association */
626
        setMessageParam(nm, obj);
627
628
        /* return the synthesized name */
629
        return nm;
630
    }
631
632
    /* synthesized message object parameter serial number */
633
    synthParamID = 1
634
635
    /*
636
     *   Extra message parameters.  If a message processor wants to add
637
     *   special message parameters of its own, we'll create a lookup
638
     *   table for the extra parameters.  Message processors might want to
639
     *   add their own special parameters to allow referring to objects
640
     *   other than the main objects of the command.  
641
     */
642
    extraMessageParams = nil
643
644
    /*
645
     *   Flag: this command is repeatable with 'again'.  Before executing
646
     *   a command, we'll save it for use by the 'again' command if this
647
     *   flag is true.
648
     */
649
    isRepeatable = true
650
651
    /*
652
     *   Flag: this command should be included in the undo records.  This
653
     *   is true for almost every command, but a few special commands
654
     *   (undo, save) are not subject to undo.  
655
     */
656
    includeInUndo = true
657
658
    /*
659
     *   Flag: this is a "conversational" command, as opposed to an order.
660
     *   When this type of command is addressed to another character, it
661
     *   doesn't ask the other character to do anything, but rather engages
662
     *   the other character in conversation.  Examples:
663
     *   
664
     *.  Bob, hello
665
     *.  Bob, goodbye
666
     *.  Bob, tell me about the planet
667
     *.  Bob, yes
668
     *.  Bob, no
669
     *   
670
     *   ("Tell me about..." is a little different from the others.  We
671
     *   treat it as conversational because it means the same thing as "ask
672
     *   Bob about...", which we consider conversational because it would
673
     *   be rendered in real life as a question.  In other words, the
674
     *   action involves the issuing actor stating the question, which
675
     *   means that issuing actor is the one doing the physical work of the
676
     *   action.  "Tell me about..." could be seen as an order, but it
677
     *   seems more appropriate to view it as simply an alternative way of
678
     *   phrasing a question.)
679
     *   
680
     *   The issuing actor is passed as a parameter because some actions
681
     *   are conversational only in some cases; "tell me about the planet"
682
     *   is conversational, but "tell Bill about the planet" isn't, since
683
     *   the latter doesn't ask Bob a question but orders Bob to talk to
684
     *   Bill.
685
     *   
686
     *   When the issuing actor and target actor are the same, this is
687
     *   irrelevant.  The purpose of this is to distinguish orders given to
688
     *   another character from conversational overtures directed to the
689
     *   other character, so if the command is coming from the player and
690
     *   bound for the player character, there's obviously no conversation
691
     *   going on.
692
     *   
693
     *   Note also that, contrary to what one might think at first glance,
694
     *   a command like ASK ABOUT is NOT conversational; it's a command to
695
     *   ask someone about something, and isn't itself a conversational
696
     *   overture.  The act of asking is itself a conversational overture,
697
     *   but the asking is the *result* of the command, not the command
698
     *   itself.  An action is only conversational if the action itself is
699
     *   a conversational overture.  So, "BOB, HELLO" is conversational;
700
     *   "BOB, ASK BILL ABOUT COMPUTER" is not, because it orders Bob to do
701
     *   something.  
702
     */
703
    isConversational(issuingActor) { return nil; }
704
705
    /*
706
     *   Get the actual verb phrase the player typed in to generate this
707
     *   Action, expressed in a canonical format.  The canonical format
708
     *   consists of the lower-case version of all of the actual text the
709
     *   player typed, but with each noun phrase replaced by a standard
710
     *   placeholder token describing the slot.  The placeholder tokens are
711
     *   '(dobj)' for the direct object, '(iobj)' for the indirect object,
712
     *   '(literal)' for a literal text phrase, and '(topic)' for a topic
713
     *   phrase.
714
     *   
715
     *   For example, if the player typed PUT BOOK AND PENCIL IN BOX, the
716
     *   canonical phrasing we return would be "put (dobj) in (iobj)".
717
     */
718
    getEnteredVerbPhrase()
719
    {
720
        local orig;
721
        local txt;
722
        
723
        /* 
724
         *   If there's an original action, let the original action do the
725
         *   work.  If we've been remapped, or if this is an implied
726
         *   action, we won't necessarily have been constructed from the
727
         *   actual player input, so we need to go back to the original
728
         *   action for this information. 
729
         */
730
        if (getOriginalAction() != self)
731
            return getOriginalAction.getEnteredVerbPhrase();
732
733
        /* start with the original token list for the predicate */
734
        orig = getPredicate().getOrigTokenList();
735
736
        /* add each token to the result text */
737
        for (txt = '', local i = 1, local len = orig.length() ;
738
             i <= len ; ++i)
739
        {
740
            local foundNp;
741
            
742
            /* add a space if this isn't the first element */
743
            if (i > 1)
744
                txt += ' ';
745
746
            /*
747
             *   Check to see if we're entering one of the noun-phrase
748
             *   slots.  We are if we've reached the first token of one of
749
             *   the predicate noun phrases. 
750
             */
751
            foundNp = nil;
752
            foreach (local npProp in predicateNounPhrases)
753
            {
754
                local match;
755
                
756
                /* check to see if we're at this noun phrase */
757
                if ((match = self.(npProp)) != nil
758
                    && i == match.firstTokenIndex)
759
                {
760
                    /* 
761
                     *   we're entering this noun phrase - add the generic
762
                     *   placeholder token for the noun phrase 
763
                     */
764
                    txt += (npProp == &dobjMatch ? '(dobj)' :
765
                            npProp == &iobjMatch ? '(iobj)' :
766
                            npProp == &topicMatch ? '(topic)' :
767
                            npProp == &literalMatch ? '(literal)' :
768
                            '(other)');
769
770
                    /* skip the entire run of tokens for the noun phrase */
771
                    i = match.lastTokenIndex;
772
773
                    /* note that we found a noun phrase */
774
                    foundNp = true;
775
776
                    /* stop looking for a noun phrase */
777
                    break;
778
                }
779
            }
780
781
            /* 
782
             *   if we didn't find a noun phrase, this token is a literal
783
             *   part of the predicate grammar, so add it as-is 
784
             */
785
            if (!foundNp)
786
                txt += getTokVal(orig[i]);
787
        }
788
789
        /* return the phrase, with everything converted to lower-case */
790
        return txt.toLower();
791
    }
792
793
    /*
794
     *   Get the grammar match tree object for the predicate that was used
795
     *   to enter this command.  By default, if we have an original action,
796
     *   we return the original action; otherwise we just return 'self'.
797
     *   
798
     *   Language libraries must override this to return the original match
799
     *   tree object if Actions are separate from predicate match trees.
800
     *   
801
     *   (The 'predicate' is simply the grammar match tree object for the
802
     *   entire verb phrase from the player's actual command entry text
803
     *   that matched this Action.  For example, in "BOB, TAKE BOX", the
804
     *   predicate is the match tree for the "TAKE BOX" part.  In "BOB,
805
     *   TAKE BOX AND GO NORTH", the predicate for the Take action is still
806
     *   the "TAKE BOX" part.  For "BOB, TAKE BOX AND BOOK AND GO NORTH",
807
     *   the predicate for the Take action is "TAKE BOX AND BOOK" - the
808
     *   full verb phrase includes any multiple-object lists in the
809
     *   original command.)  
810
     */
811
    getPredicate()
812
    {
813
        /* 
814
         *   By default, we just return the original Action - we assume
815
         *   that the language library defines Action subclasses as the
816
         *   actual match tree objects for predicate grammars.  Language
817
         *   modules must override this if they use separate object types
818
         *   for the predicate match tree objects.  
819
         */
820
        return getOriginalAction();
821
    }
822
823
    /*
824
     *   Get the noun-phrase information for my predicate grammar.  This
825
     *   returns a list of the match-tree properties for the noun-phrase
826
     *   sub-productions in our predicate grammar.  The properties
827
     *   generally include &dobjMatch, &iobjMatch, &literalMatch, and
828
     *   &topicMatch.  The order of the slots isn't important; they simply
829
     *   tell us which ones we should find in our predicate grammar match.
830
     *   
831
     *   The base Action is intransitive, so it doesn't have any
832
     *   noun-phrase slots, hence this is an empty list.  
833
     */
834
    predicateNounPhrases = []
835
836
    /*
837
     *   Get the object "role" identifier for the given index.  This
838
     *   returns the identifier (DirectObject, IndirectObject, etc.) for
839
     *   the object at the given slot index, as used in
840
     *   setResolvedObjects().  The first object is at index 1.  
841
     */
842
    getRoleFromIndex(idx) { return nil; }
843
844
    /* 
845
     *   Get the "other object" role - this is the complement of the given
846
     *   object role for a two-object action, and is used to determine the
847
     *   real role of the special OtherObject placeholder in a remapTo().
848
     *   This is only meaningful with actions of exactly two objects.  
849
     */
850
    getOtherObjectRole(role) { return nil; }
851
852
    /* get the resolved object in a given role */
853
    getObjectForRole(role) { return nil; }
854
855
    /* get the match tree for the noun phrase in the given role */
856
    getMatchForRole(role) { return nil; }
857
858
    /* get the 'verify' property for a given object role */
859
    getVerifyPropForRole(role)
860
    {
861
        return nil;
862
    }
863
864
    /* get the 'preCond' property for a given object role */
865
    getPreCondPropForRole(role)
866
    {
867
        return nil;
868
    }
869
870
    /* get the 'remap' property for a given object role */
871
    getRemapPropForRole(role)
872
    {
873
        return nil;
874
    }
875
876
    /* 
877
     *   Get the ResolveInfo for the given object in the current command.
878
     *   Since we don't have any objects at all, we'll simply return a new
879
     *   ResolveInfo wrapping the given object.  'cur' is the object we're
880
     *   looking for, and 'oldRole' is the role the object previously
881
     *   played in the action.  
882
     */
883
    getResolveInfo(obj, oldRole) { return new ResolveInfo(obj, 0); }
884
885
    /*
886
     *   Explicitly set the resolved objects.  This should be overridden
887
     *   in each subclass for the number of objects specific to the action
888
     *   (a simple transitive action takes one argument, an action with
889
     *   both a direct and indirect object takes two arguments, and so
890
     *   on).  The base action doesn't have any objects at all, so this
891
     *   takes no arguments.
892
     *   
893
     *   This method is used to set up an action to be performed
894
     *   programmatically, rather than based on parsed input.  Since
895
     *   there's no parsed input in such cases, the objects are specified
896
     *   directly by the programmatic caller.  
897
     */
898
    setResolvedObjects() { }
899
900
    /*
901
     *   Explicitly set the object match trees.  This sets the pre-resolved
902
     *   production match trees.  The arguments are given in the order of
903
     *   their roles in the action, using the same order that
904
     *   setResolvedObjects() uses.
905
     *   
906
     *   The arguments to this routine can either be match tree objects,
907
     *   which we'll plug into our grammar tree in the respective roles
908
     *   exactly as given; or they can be ResolveInfo objects giving the
909
     *   desired resolutions, in which case we'll build the appropriate
910
     *   kind of PreResolvedProd for each one.  The types can be freely
911
     *   mixed.  
912
     */
913
    setObjectMatches() { }
914
915
    /*
916
     *   Check that the resolved objects are all in scope.  Returns true if
917
     *   so, nil if not.
918
     *   
919
     *   This routine is meant for use only for actions built
920
     *   programmatically using setResolvedObjects().  In particular, we
921
     *   assume that we have only one object in each slot.  For normal
922
     *   parser-built actions, this check isn't necessary, because the
923
     *   parser only resolves objects that are in scope in the first place.
924
     */
925
    resolvedObjectsInScope()
926
    {
927
        /* we have no objects at all, so there is nothing out of scope */
928
        return true;
929
    }
930
931
    /*
932
     *   Get the list of bindings for an anaphoric pronoun - this is a
933
     *   pronoun that refers back to an earlier noun phrase in the same
934
     *   sentence, such as HIMSELF in ASK BOB ABOUT HIMSELF.  These
935
     *   obviously make no sense for verbs that take one (or zero)
936
     *   objects, since there's no earlier phrase to refer back to; these
937
     *   should return nil to indicate that an anaphoric pronoun is simply
938
     *   nonsensical in such a context.  Actions of two or more objects
939
     *   should return a list of objects for the preceding noun phrase.
940
     *   
941
     *   Actions of two or more objects should set this if possible to the
942
     *   resolved object list for the previous noun phrase, as a
943
     *   ResolveInfo list.
944
     *   
945
     *   The tricky part is that some actions will resolve noun phrases in
946
     *   a different order than they appear in the actual command grammar;
947
     *   similarly, it's also possible that some non-English languages use
948
     *   cataphoric pronouns (i.e., pronouns that refer to noun phrases
949
     *   that appear later in the sentence).  To allow for these cases,
950
     *   return an empty list here if a binding is grammatically possible
951
     *   but not yet available because of the resolution order, and note
952
     *   internally that the parser asked us for an anaphoric binding.
953
     *   Afterwards, the action's resolver method must go back and perform
954
     *   *another* resolve pass on the noun phrase production that
955
     *   requested the anaphor binding.
956
     *   
957
     *   'typ' is the PronounType specifier for the corresponding ordinary
958
     *   pronoun.  For 'himself', for example, typ will be PronounHim.  
959
     */
960
    getAnaphoricBinding(typ) { return nil; }
961
962
    /*
963
     *   Set a special pronoun override.  This creates a temporary pronoun
964
     *   definition, which lasts as long as this action (and any nested
965
     *   actions).  The temporary definition overrides the normal meaning
966
     *   of the pronoun.
967
     *   
968
     *   One example of where this is useful is in global action remapping
969
     *   cases where the target actor changes in the course of the
970
     *   remapping.  For example, if we remap BOB, GIVE ME YOUR BOOK to ASK
971
     *   BOB FOR YOUR BOOK, the YOUR qualifier should still refer to Bob
972
     *   even though the command is no longer addressing Bob directly.
973
     *   This routine can be used in this case to override the meaning of
974
     *   'you' so that it refers to Bob.  
975
     */
976
    setPronounOverride(typ, val)
977
    {
978
        /* if we don't have an override table yet, create one */
979
        if (pronounOverride == nil)
980
            pronounOverride = new LookupTable(5, 10);
981
982
        /* add it to the table */
983
        pronounOverride[typ] = val;
984
    }
985
986
    /* 
987
     *   Get any special pronoun override in effect for the action, as set
988
     *   via setPronounOverride().  This looks in our own override table
989
     *   for a definition; then, if we have no override of our own, we ask
990
     *   our parent action if we have one, then our original action.  
991
     */
992
    getPronounOverride(typ)
993
    {
994
        local pro;
995
996
        /* check our own table */
997
        if (pronounOverride != nil
998
            && (pro = pronounOverride[typ]) != nil)
999
            return pro;
1000
1001
        /* we don't have anything in our own table; check our parent */
1002
        if (parentAction != nil
1003
            && (pro = parentAction.getPronounOverride(typ)) != nil)
1004
            return pro;
1005
1006
        /* if still nothing, check with the original action */
1007
        if (originalAction != nil
1008
            && originalAction != parentAction
1009
            && (pro = originalAction.getPronounOverride(typ)) != nil)
1010
            return pro;
1011
1012
        /* we didn't find an override */
1013
        return nil;
1014
    }
1015
1016
    /* 
1017
     *   the pronoun override table - this is nil by default, which means
1018
     *   that no overrides have been defined yet; we create a LookupTable
1019
     *   upon adding the first entry to the table 
1020
     */
1021
    pronounOverride = nil
1022
1023
    /* wrap an object with a ResolveInfo */
1024
    makeResolveInfo(val)
1025
    {
1026
        /* if it's already a ResolveInfo object, return it as-is */
1027
        if (dataType(val) == TypeObject && val.ofKind(ResolveInfo))
1028
            return val;
1029
        else
1030
            return new ResolveInfo(val, 0);
1031
    }
1032
1033
    /*
1034
     *   Convert an object or list of objects to a ResolveInfo list 
1035
     */
1036
    makeResolveInfoList(val)
1037
    {
1038
        /* if we have a non-list collection, make it a list */
1039
        if (dataType(val) == TypeObject && val.ofKind(Collection))
1040
            val = val.toList();
1041
1042
        /* if it's nil or an empty list, return an empty list */
1043
        if (val == nil || val == [])
1044
            return [];
1045
1046
        /* see what we have */
1047
        if (dataType(val) == TypeList)
1048
        {
1049
            /* it's a list - make a ResolveInfo for each item */
1050
            return val.mapAll({x: makeResolveInfo(x)});
1051
        }
1052
        else 
1053
        {
1054
            /* it's not a list - return a one-element ResolveInfo list */
1055
            return [makeResolveInfo(val)];
1056
        }
1057
    }
1058
1059
    /* 
1060
     *   If the command is repeatable, save it for use by 'again'.
1061
     */ 
1062
    saveActionForAgain(issuingActor, countsAsIssuerTurn,
1063
                       targetActor, targetActorPhrase)
1064
    {
1065
        /*
1066
         *   Check to see if the command is repeatable.  It's repeatable if
1067
         *   the base action is marked as repeatable, AND it's not nested,
1068
         *   AND it's issued by the player character, AND it's either a PC
1069
         *   command or it counts as an issuer turn.
1070
         *   
1071
         *   Nested commands are never repeatable with 'again', since no
1072
         *   one ever typed them in.
1073
         *   
1074
         *   "Again" is strictly for the player's use, so it's repeatable
1075
         *   only if this is the player's turn, as opposed to a scripted
1076
         *   action by an NPC.  This is the player's turn only if the
1077
         *   command was issued by the player character (which means it
1078
         *   came from the player), and either it's directed to the player
1079
         *   character OR it counts as a turn for the player character.  
1080
         */
1081
        if (isRepeatable
1082
            && parentAction == nil
1083
            && (issuingActor.isPlayerChar()
1084
                && (targetActor.isPlayerChar() || countsAsIssuerTurn)))
1085
            AgainAction.saveForAgain(issuingActor, targetActor,
1086
                                     targetActorPhrase, self);
1087
    }
1088
1089
    /*
1090
     *   Perform this action.  Throughout execution of the action, the
1091
     *   global parser variables that specify the current actor and action
1092
     *   are valid.  
1093
     */
1094
    doAction(issuingActor, targetActor, targetActorPhrase,
1095
             countsAsIssuerTurn)
1096
    {
1097
        local oldActor;
1098
        local oldIssuer;
1099
        local oldAction;
1100
        local oldResults;
1101
1102
        /* 
1103
         *   save the current parser globals, for restoration when we
1104
         *   finish this command - if this command is nested within
1105
         *   another, this will let us ensure that everything is restored
1106
         *   properly when we finish with this command 
1107
         */
1108
        oldActor = gActor;
1109
        oldIssuer = gIssuingActor;
1110
        oldAction = gAction;
1111
        oldResults = gVerifyResults;
1112
1113
        /* 
1114
         *   set the new globals (note that there are no verification
1115
         *   results or command reports objects yet - these are valid only
1116
         *   while we're running the corresponding command phases) 
1117
         */
1118
        gActor = targetActor;
1119
        gIssuingActor = issuingActor;
1120
        gAction = self;
1121
        gVerifyResults = nil;
1122
1123
        /* make sure we restore globals on our way out */
1124
        try
1125
        {
1126
            local pc;
1127
            
1128
            /* if applicable, save the command for AGAIN */
1129
            saveActionForAgain(issuingActor, countsAsIssuerTurn,
1130
                               targetActor, targetActorPhrase);
1131
1132
            /* start a new command visually if this isn't a nested action */
1133
            if (parentAction == nil)
1134
                gTranscript.addCommandSep();
1135
1136
            /* have the player character note initial conditions */
1137
            pc = gPlayerChar;
1138
            pc.noteConditionsBefore();
1139
1140
            /* run the before routine for the entire action */
1141
            beforeActionMain();
1142
1143
            /* run the subclass-specific processing */
1144
            doActionMain();
1145
1146
            /* run the after routine for the entire action */
1147
            afterActionMain();
1148
1149
            /* 
1150
             *   If this is a top-level action, show the command results.
1151
             *   Don't show results for a nested action, since we want to
1152
             *   wait and let the top-level action show the results after
1153
             *   it has the full set of results.  
1154
             */
1155
            if (parentAction == nil)
1156
            {
1157
                /* 
1158
                 *   If the player character didn't change, ask the player
1159
                 *   character to note any condition changes.  If the
1160
                 *   player character did change to a new actor,
1161
                 *   presumably the command will have displayed a specific
1162
                 *   message, since this would be an unusual development
1163
                 *   for which we can generate no generic message.  
1164
                 */
1165
                if (gPlayerChar == pc)
1166
                    pc.noteConditionsAfter();
1167
            }
1168
        }
1169
        finally
1170
        {
1171
            /* restore the parser globals to how we found them */
1172
            gActor = oldActor;
1173
            gIssuingActor = oldIssuer;
1174
            gAction = oldAction;
1175
            gVerifyResults = oldResults;
1176
        }
1177
    }
1178
1179
    /*
1180
     *   Perform processing before running the action.  This is called
1181
     *   just once per action, even if the action will be iterated for a
1182
     *   list of objects. 
1183
     */
1184
    beforeActionMain()
1185
    {
1186
    }
1187
1188
    /*
1189
     *   Perform processing after running the entire action.  This is
1190
     *   called just once per action, even if the action was iterated for
1191
     *   a list of objects. 
1192
     */
1193
    afterActionMain()
1194
    {
1195
        /* call each registered after-action handler */
1196
        if (afterActionMainList != nil)
1197
        {
1198
            foreach (local cur in afterActionMainList)
1199
                cur.afterActionMain();
1200
        }
1201
1202
        /* 
1203
         *   Mark ourselves as busy for the amount of time this action
1204
         *   takes.  Don't count the time taken for implied actions,
1205
         *   though, since these are meant to be zero-time sub-actions
1206
         *   performed as part of the main action and thus don't have a
1207
         *   separate time cost. 
1208
         *   
1209
         *   Note that we add our busy time in the main after-action
1210
         *   processing because we only want to count our time cost once
1211
         *   for the whole command, even if we're performing the command on
1212
         *   multiple objects.  
1213
         */
1214
        if (!isImplicit)
1215
        {
1216
            local actor; 
1217
            
1218
            /* 
1219
             *   If the command is conversational, the turn counts as an
1220
             *   issuer turn; otherwise, it counts as a turn for the target
1221
             *   actor.  Conversational commands are effectively carried
1222
             *   out by the issuer, even though in form they're directed to
1223
             *   another actor (as in "BOB, HELLO"), so we need to count
1224
             *   the time they take as the issuer's time.  
1225
             */
1226
            actor = (isConversational(gIssuingActor)
1227
                     ? gIssuingActor : gActor);
1228
1229
            /* add the busy time to the actor */ 
1230
            actor.addBusyTime(self, actionTime);
1231
        }
1232
1233
        /*
1234
         *   If the command failed, and this is a top-level (not nested)
1235
         *   action, check to see if the game wants to cancel remaining
1236
         *   commands on the line.  
1237
         */
1238
        if (gTranscript.isFailure
1239
            && parentAction == nil
1240
            && gameMain.cancelCmdLineOnFailure)
1241
        {
1242
            /* 
1243
             *   the command failed, and they want to cancel remaining
1244
             *   commands on failure - throw a 'cancel command line'
1245
             *   exception to cancel any remaining tokens 
1246
             */
1247
            throw new CancelCommandLineException();
1248
        }
1249
    }
1250
1251
    /*
1252
     *   Register an object for afterActionMain invocation.  After we've
1253
     *   finished with the entire action - including all iterated objects
1254
     *   involved in the action - we'll invoke each registered object's
1255
     *   afterActionMain() method.  This registration is only meaningful
1256
     *   for the current action instance, and can only be set up before
1257
     *   the action has been finished (i.e., before the current gAction
1258
     *   invokes its own afterActionMain() method).
1259
     *   
1260
     *   Each object is only registered once.  If a caller attempts to
1261
     *   register the same object repeatedly, we'll simply ignore the
1262
     *   repeated requests.
1263
     *   
1264
     *   This is a convenient way to implement a collective follow-up to
1265
     *   the parts of an iterated action.  Since repeated registrations
1266
     *   are ignored, each handler for an iterated object (such as a
1267
     *   "dobjFor" action() handler) can register its follow-up handler
1268
     *   without worrying about redundant registration.  Then, when the
1269
     *   overall action is completed for each iterated object involved,
1270
     *   the follow-up handler will be invoked, and it can do any final
1271
     *   work for the overall action.  For example, the follow-up handler
1272
     *   could display a message summarizing the iterated parts of the
1273
     *   action; or, it could even scan the transcript for particular
1274
     *   messages and replace them with a summary.  
1275
     */
1276
    callAfterActionMain(obj)
1277
    {
1278
        /* if we don't have an after-action list yet, create one */
1279
        if (afterActionMainList == nil)
1280
            afterActionMainList = new Vector(8);
1281
1282
        /* if this item isn't already in the list, add it */
1283
        if (afterActionMainList.indexOf(obj) == nil)
1284
            afterActionMainList.append(obj);
1285
    }
1286
1287
    /* list of methods to invoke after we've completed the action */
1288
    afterActionMainList = nil
1289
1290
    /* 
1291
     *   the amount of time on the game clock that the action consumes -
1292
     *   by default, each action consumes one unit, but actions can
1293
     *   override this to consume more or less game time 
1294
     */
1295
    actionTime = 1
1296
1297
    /*
1298
     *   Zero the action time in this action and any parent actions.  This
1299
     *   should be used when a nested replacement action is to completely
1300
     *   take over the time-keeping responsibility for the entire turn; all
1301
     *   containing actions in this case are to take zero time, since the
1302
     *   nested action is the only part of the turn that will count for
1303
     *   timing.  
1304
     */
1305
    zeroActionTime()
1306
    {
1307
        /* clear my action time */
1308
        actionTime = 0;
1309
1310
        /* if we have a parent action, zero it and its parents */
1311
        if (parentAction != nil)
1312
            parentAction.zeroActionTime();
1313
    }
1314
1315
    /*
1316
     *   Execute the action for a single set of objects.  This runs the
1317
     *   full execution sequence for the current set of objects.
1318
     *   
1319
     *   Subclasses generally won't override this method, but will instead
1320
     *   override the methods that implement the individual steps in the
1321
     *   execution sequence.
1322
     */
1323
    doActionOnce()
1324
    {
1325
        /*
1326
         *   Perform the sequence of operations to execute the action.  If
1327
         *   an ExitSignal is thrown during the sequence, skip to the
1328
         *   end-of-turn processing.  
1329
         */
1330
        try
1331
        {
1332
            local result;
1333
            local impReport;
1334
1335
            /*
1336
             *   Before doing any actual execution, check the command for
1337
             *   remapping.  If we end up doing any remapping, the
1338
             *   remapping routine will simply replace the current command,
1339
             *   so we the remapping call will terminate the current action
1340
             *   with 'exit' and thus never return here.  
1341
             */
1342
            checkRemapping();
1343
1344
            /*
1345
             *   If this is an implicit action, check for danger: we never
1346
             *   try a command implicitly when the command is obviously
1347
             *   dangerous.
1348
             */
1349
            if (isImplicit)
1350
            {
1351
                /* 
1352
                 *   verify the action for an implicit command, checking
1353
                 *   for actions that aren't allowe implicitly - we never
1354
                 *   try a command implicitly when the command is (or
1355
                 *   should be) obviously dangerous or is simply
1356
                 *   non-obvious 
1357
                 */
1358
                result = verifyAction();
1359
1360
                /* 
1361
                 *   If the action can be performed, but can't be performed
1362
                 *   implicitly, abort.  Note that we only silently ignore
1363
                 *   the action if it is allowed normally but not
1364
                 *   implicitly: if it's not even allowed normally, we'll
1365
                 *   simply fail later with the normal failure message,
1366
                 *   since there's no harm in trying.  
1367
                 */
1368
                if (result != nil
1369
                    && result.allowAction
1370
                    && !result.allowImplicit)
1371
                    abortImplicit;
1372
            }
1373
1374
            /*
1375
             *   If this is an implicit command, display a message
1376
             *   indicating that we're performing the command.
1377
             */
1378
            impReport = maybeAnnounceImplicit();
1379
1380
            /*
1381
             *   Make one or two passes through verifications and
1382
             *   preconditions.  If any precondition performs an implicit
1383
             *   command, we must run everything a second time to ensure
1384
             *   that the implicit command or commands did not invalidate
1385
             *   any earlier precondition or a verification.
1386
             *   
1387
             *   Run verifications before preconditions, because there
1388
             *   would be no point in applying implicit commands from
1389
             *   preconditions if the command verifies as illogical in the
1390
             *   first place.  
1391
             */
1392
            for (local iter = 1 ; iter <= 2 ; ++iter)
1393
            {
1394
                /* verify the action */
1395
                result = verifyAction();
1396
                
1397
                /* 
1398
                 *   if verification doesn't allow the command to proceed,
1399
                 *   show the reason and end the command 
1400
                 */
1401
                if (result != nil && !result.allowAction)
1402
                {
1403
                    /* show the result message */
1404
                    result.showMessage();
1405
                    
1406
                    /* mark the command as a failure */
1407
                    gTranscript.noteFailure();
1408
1409
                    /* 
1410
                     *   if we have an implicit report, mark it as a mere
1411
                     *   attempt, since the action can't be completed 
1412
                     */
1413
                    if (impReport != nil)
1414
                        impReport.noteJustTrying();
1415
                    
1416
                    /* terminate the command */
1417
                    exit;
1418
                }
1419
1420
                /* 
1421
                 *   Check preconditions of the action.  If we don't invoke
1422
                 *   any implicit commands, we can stop here: nothing in
1423
                 *   the game state will have changed, so there is no need
1424
                 *   to re-verify or re-check preconditions.
1425
                 *   
1426
                 *   Only allow implicit actions on the first pass.  Do not
1427
                 *   perform implicit actions on the second pass, because
1428
                 *   if we did so we could get into an infinite loop of
1429
                 *   conflicting preconditions, where each pass would
1430
                 *   reverse the state from the last pass.  
1431
                 */
1432
                if (!checkPreConditions(iter == 1))
1433
                    break;
1434
            }
1435
1436
            /* 
1437
             *   Disable sense caching once we start the action phase -
1438
             *   once we start making changes to game state, it's too much
1439
             *   work to figure out when to invalidate the cache, so simply
1440
             *   turn off caching entirely.
1441
             *   
1442
             *   Note that the sense cache will already be disabled if we
1443
             *   executed any implied commands, because the first implied
1444
             *   command will have disabled the cache as soon as it reached
1445
             *   its execution phase, and no one will have turned caching
1446
             *   back on.  It does no harm to disable it again here.  
1447
             */
1448
            libGlobal.disableSenseCache();
1449
1450
            /* if desired, run the "before" notifications before "check" */
1451
            if (gameMain.beforeRunsBeforeCheck)
1452
                runBeforeNotifiers();
1453
                
1454
            /* 
1455
             *   Invoke the action's execution method.  Catch any "exit
1456
             *   action" exceptions - these indicate that the action is
1457
             *   finished but that the rest of the command processing is to
1458
             *   proceed as normal.  
1459
             */
1460
            try
1461
            {
1462
                /* notify the actor of what we're about to do */
1463
                gActor.actorAction();
1464
1465
                /* check the action */
1466
                checkAction();
1467
1468
                /* if desired, run the "before" notifications after "check" */
1469
                if (!gameMain.beforeRunsBeforeCheck)
1470
                    runBeforeNotifiers();
1471
                
1472
                /* execute the action */
1473
                execAction();
1474
            }
1475
            catch (ExitActionSignal eaSig)
1476
            {
1477
                /* 
1478
                 *   an exit action signal was thrown - since we've now
1479
                 *   skipped past any remaining action processing, simply
1480
                 *   continue with the rest of the command processing as
1481
                 *   normal 
1482
                 */
1483
            }
1484
            
1485
            /* call afterAction for each object in the notify list */
1486
            notifyBeforeAfter(&afterAction);
1487
            
1488
            /* notify the actor's containers of the completed action */
1489
            gActor.forEachContainer(callRoomAfterAction);
1490
            
1491
            /* run the after-action processing */
1492
            afterAction();
1493
        }
1494
        catch (ExitSignal exc)
1495
        {
1496
            /* the execution sequence is finished - simply stop here */
1497
        }
1498
    }
1499
1500
    /*
1501
     *   Run the "before" notifiers: this calls beforeAction on everything
1502
     *   in scope, and calls roomBeforeAction on the actor's containers.  
1503
     */
1504
    runBeforeNotifiers()
1505
    {
1506
        /* run the before-action processing */
1507
        beforeAction();
1508
1509
        /* 
1510
         *   notify the actor's containers that an action is about
1511
         *   to take place within them 
1512
         */
1513
        gActor.forEachContainer(callRoomBeforeAction);
1514
1515
        /* call beforeAction for each object in the notify list */
1516
        notifyBeforeAfter(&beforeAction);
1517
    }
1518
1519
    /*
1520
     *   Reset the message generation context for a sense change.  This
1521
     *   can be called when something substantial happens in the midst of
1522
     *   a command, and we might need different message generation rules
1523
     *   before and after the change.  For example, this is used when a
1524
     *   non-player character moves from one location to another, because
1525
     *   the NPC might want to generate leaving and arriving messages
1526
     *   differently in the two locations.
1527
     */
1528
    recalcSenseContext()
1529
    {
1530
        /* tell the sense context capturer to recalculate the context */
1531
        senseContext.recalcSenseContext();
1532
    }
1533
1534
    /*
1535
     *   Maybe announce the action as an implied action. 
1536
     */
1537
    maybeAnnounceImplicit()
1538
    {
1539
        /* 
1540
         *   if we're a remapped action, we'll actually want to announce
1541
         *   the original, not the remapping 
1542
         */
1543
        if (remappedFrom != nil)
1544
            return remappedFrom.maybeAnnounceImplicit();
1545
            
1546
        /* 
1547
         *   if we're implicit, and we have an implicit announcement
1548
         *   message, announce the implicit action 
1549
         */
1550
        if (isImplicit && implicitMsg != nil)
1551
            return gTranscript.announceImplicit(self, implicitMsg);
1552
1553
        /* we don't need to announce the implied action */
1554
        return nil;
1555
    }
1556
1557
    /*
1558
     *   Announce the object of an action.  This should be used for each
1559
     *   iteration of a command that takes objects to announce the objects
1560
     *   on this iteration.
1561
     *   
1562
     *   We announce an object under several circumstances:
1563
     *   
1564
     *   - If we are iterating through multiple objects, we'll show the
1565
     *   current object to show the player the individual step in the
1566
     *   command being performed.
1567
     *   
1568
     *   - If 'all' was used to specify the object, we'll announce it even
1569
     *   if only one object is involved, to make it clear to the player
1570
     *   exactly what we chose as a match.
1571
     *   
1572
     *   - If we are executing the command on a single object, and the
1573
     *   object was chosen through disambiguation of a set of ambiguous
1574
     *   choices, and some of the discarded possibilities were logical but
1575
     *   less so than the chosen object, we'll show the assumption we
1576
     *   made.  In such cases, our assumption is not necessarily correct,
1577
     *   so we'll tell the user about our choice explicitly by way of
1578
     *   confirmation - this gives the user a better chance of noticing
1579
     *   quickly if our assumption was incorrect.
1580
     *   
1581
     *   - If we supplied a default for a missing noun phrase in the
1582
     *   player's command, we'll show what we chose.  Since the player
1583
     *   didn't say what they meant, we'll make it plain that we're
1584
     *   providing an assumption about what we thought they must have
1585
     *   meant.
1586
     *   
1587
     *   'info' is the ResolveInfo object describing this resolved object,
1588
     *   and 'numberInList' is the total number of objects we're iterating
1589
     *   over for this object function (direct object, indirect object,
1590
     *   etc).  'whichObj' is one of the object function constants
1591
     *   (DirectObject, IndirectObject, etc) describing which object we're
1592
     *   mentioning; the language-specific message generator might use
1593
     *   this in conjunction with the action to include a preposition with
1594
     *   the displayed phrase, for example, or choose an appropriate
1595
     *   inflection.
1596
     */
1597
    announceActionObject(info, numberInList, whichObj)
1598
    {
1599
        /* 
1600
         *   Show prefix announcements only if the player character is
1601
         *   performing the action.  For NPC's, we don't use the prefix
1602
         *   format, because it doesn't work as well for NPC result
1603
         *   reports; instead, the NPC versions of the library messages
1604
         *   tend to use sufficiently detailed reports that the prefix
1605
         *   isn't required (for example, "Bob takes the iron key" rather
1606
         *   than just "Taken").  
1607
         */
1608
        if (gActor.isPlayerChar)
1609
        {
1610
            /* check for the various announcements */
1611
            if (maybeAnnounceMultiObject(info, numberInList, whichObj))
1612
            {
1613
                /* we've done a multi announcement, so we're now done */
1614
            }
1615
            else if ((info.flags_ & UnclearDisambig) != 0
1616
                     || ((info.flags_ & ClearDisambig) != 0
1617
                         && gameMain.ambigAnnounceMode == AnnounceClear))
1618
            {
1619
                /* show the object, since we're not certain it's right */
1620
                gTranscript.announceAmbigActionObject(info.obj_, whichObj);
1621
            }
1622
            else if ((info.flags_ & DefaultObject) != 0
1623
                     && !(info.flags_ & AnnouncedDefaultObject))
1624
            {
1625
                /*   
1626
                 *   Show the object, since we supplied it as a default.
1627
                 *   At this stage in the command, we have resolved
1628
                 *   everything.  
1629
                 */
1630
                gTranscript.announceDefaultObject(
1631
                    info.obj_, whichObj, self, true);
1632
1633
                /* note that we've announced this object */
1634
                info.flags_ |= AnnouncedDefaultObject;
1635
            }
1636
        }
1637
    }
1638
1639
    /* announce a multi-action object, if appropriate */
1640
    maybeAnnounceMultiObject(info, numberInList, whichObj)
1641
    {
1642
        /* 
1643
         *   announce if we have more than one object, or we're set to
1644
         *   announce this object in any case 
1645
         */
1646
        if (numberInList > 1 || (info.flags_ & AlwaysAnnounce) != 0)
1647
        {
1648
            /* show the current object of a multi-object action */
1649
            gTranscript.announceMultiActionObject(info.obj_, whichObj);
1650
1651
            /* tell the caller we made an announcement */
1652
            return true;
1653
        }
1654
1655
        /* tell the caller we didn't make an announcement */
1656
        return nil;
1657
    }
1658
    
1659
1660
    /*
1661
     *   Announce a defaulted object list, if appropriate.  We'll announce
1662
     *   the object if we have a single object in the given resolution
1663
     *   list, it was defaulted, and it hasn't yet been announced.
1664
     */
1665
    maybeAnnounceDefaultObject(lst, which, allResolved)
1666
    {
1667
        /* 
1668
         *   if the list has exactly one element, and it's marked as a
1669
         *   defaulted object, and it hasn't yet been announced, announce
1670
         *   it 
1671
         */
1672
        if (lst != nil
1673
            && lst.length() == 1
1674
            && (lst[1].flags_ & DefaultObject) != 0
1675
            && !(lst[1].flags_ & AnnouncedDefaultObject))
1676
        {
1677
            /* announce the object */
1678
            gTranscript.announceDefaultObject(
1679
                lst[1].obj_, which, self, allResolved);
1680
1681
            /* 
1682
             *   we've now announced the object; mark it as announced so
1683
             *   we don't show the same announcement again 
1684
             */
1685
            lst[1].flags_ |= AnnouncedDefaultObject;
1686
        }
1687
    }
1688
1689
    /*
1690
     *   "Pre-announce" a common object for a command that might involve
1691
     *   iteration over other objects.  For example, in "put all in box",
1692
     *   the box is common to all iterations of the command, so we would
1693
     *   want to preannounce it, if it needs to be announced at all,
1694
     *   before the iterations of the command.
1695
     *   
1696
     *   We'll announce the object only if it's marked as defaulted or
1697
     *   unclearly disambiguated, and then only if the other list will be
1698
     *   announcing its objects as multi-action objects.  However, we do
1699
     *   not pre-announce anything for a remapped action, because we'll
1700
     *   show the full action description for each individually announced
1701
     *   object, so we don't need or want a separate announcement for the
1702
     *   group.
1703
     *   
1704
     *   Returns true if we did any pre-announcing, nil otherwise.  If we
1705
     *   return true, the caller should not re-announce this object during
1706
     *   the iteration, since our pre-announcement is common to all
1707
     *   iterations.  
1708
     */
1709
    preAnnounceActionObject(info, mainList, whichObj)
1710
    {
1711
        /* do not pre-announce anything for a remapped action */
1712
        if (isRemapped())
1713
            return nil;
1714
1715
        /* 
1716
         *   determine if the main list will be announcing its objects -
1717
         *   it will if it has more than one object, or if its one object
1718
         *   is marked as "always announce" 
1719
         */
1720
        if (mainList.length() > 1
1721
            || (mainList[1].flags_ & AlwaysAnnounce) != 0)
1722
        {
1723
            /* 
1724
             *   we will be announcing the main list object or objects, so
1725
             *   definitely pre-announce this object if appropriate 
1726
             */
1727
            announceActionObject(info, 1, whichObj);
1728
1729
            /* tell the caller we pre-announced the object */
1730
            return true;
1731
        }
1732
1733
        /* we didn't pre-announce the object */
1734
        return nil;
1735
    }
1736
1737
    /*
1738
     *   Run our action-specific pre-processing.  By default, we do
1739
     *   nothing here.  
1740
     */
1741
    beforeAction()
1742
    {
1743
    }
1744
1745
    /*
1746
     *   Check the action.  This runs the 'check' phase, and must be
1747
     *   overridden for each subclass.
1748
     *   
1749
     *   Intransitive actions don't generally have to do anything in the
1750
     *   'check' phase, since they can simply do any necessary checks in
1751
     *   the 'execute' phase that runs immediately after 'check'.  This
1752
     *   phase is separated out from 'execute' mostly for the benefit of
1753
     *   transitive actions, where the 'check' phase gives the involved
1754
     *   objects a chance to object to the action.  
1755
     */
1756
    checkAction() { }
1757
1758
    /*
1759
     *   Execute the action.  This must be overridden by each subclass.
1760
     *   
1761
     *   Intransitive actions must do all of their work in this routine.
1762
     *   In most cases, transitive actions will delegate processing to one
1763
     *   or more of the objects involved in the command - for example,
1764
     *   most single-object commands will call a method in the direct
1765
     *   object to carry out the command.  
1766
     */
1767
    execAction()
1768
    {
1769
        /* by default, just show the 'no can do' message */
1770
        mainReport(&cannotDoThatMsg);
1771
    }
1772
1773
    /*
1774
     *   Run our action-specific post-processing.  By default, we do
1775
     *   nothing here. 
1776
     */
1777
    afterAction()
1778
    {
1779
    }
1780
1781
    /*
1782
     *   Verify the action.  Action subclasses with one or more objects
1783
     *   should call object verification routines here.  Returns a
1784
     *   VerifyResultList with the results, or nil if there are no
1785
     *   verification results at all.  A nil return should be taken as
1786
     *   success, not failure, because it means that we found no objection
1787
     *   to the command.  
1788
     */
1789
    verifyAction()
1790
    {
1791
        /* 
1792
         *   there are no objects in the default action, but we might have
1793
         *   pre-condition verifiers 
1794
         */
1795
        return callVerifyPreCond(nil);
1796
    }
1797
1798
    /*
1799
     *   Initialize tentative resolutions for other noun phrases besides
1800
     *   the one indicated. 
1801
     */
1802
    initTentative(issuingActor, targetActor, whichObj)
1803
    {
1804
        /* by default, we have no noun phrases to tentatively resolve */
1805
    }
1806
1807
    /*
1808
     *   Check for remapping the action.  This should check with each
1809
     *   resolved object involved in the command to see if the object wants
1810
     *   to remap the action to a new action; if it does, the object must
1811
     *   replace the current action (using replaceAction or equivalent).
1812
     *   Note that replacing the action must use 'exit' to terminate the
1813
     *   original action, so this will never return if remapping actually
1814
     *   does occur.  
1815
     */
1816
    checkRemapping()
1817
    {
1818
        /* by default, we have no objects, so we do nothing here */
1819
    }
1820
1821
    /*
1822
     *   Invoke a callback with a verify results list in gVerifyResults,
1823
     *   using the existing results list or creating a new one if there is
1824
     *   no existing one.  Returns the results list used. 
1825
     */
1826
    withVerifyResults(resultsSoFar, obj, func)
1827
    {
1828
        local oldResults;
1829
1830
        /* if we don't already have a result list, create one */
1831
        if (resultsSoFar == nil)
1832
            resultsSoFar = new VerifyResultList();
1833
1834
        /* remember the old global results list */
1835
        oldResults = gVerifyResults;
1836
1837
        /* install the new one */
1838
        gVerifyResults = resultsSoFar;
1839
1840
        /* make sure we restore the old result list on the way out */
1841
        try
1842
        {
1843
            /* invoke the callback */
1844
            (func)();
1845
        }
1846
        finally
1847
        {
1848
            /* restore the old result list */
1849
            gVerifyResults = oldResults;
1850
        }
1851
1852
        /* return the result list */
1853
        return resultsSoFar;
1854
    }
1855
1856
    /*
1857
     *   Verify the non-object-related pre-conditions.  This runs
1858
     *   verification on each of the pre-condition objects defined for the
1859
     *   action.  
1860
     */
1861
    callVerifyPreCond(resultSoFar)
1862
    {
1863
        /* look at each of our preconditions */
1864
        foreach (local cond in preCond)
1865
        {
1866
            /* 
1867
             *   If this precondition defines a verifier, call it.  Check
1868
             *   to see if we have a verifier defined first so that we can
1869
             *   avoid creating a result list if we won't have any use for
1870
             *   it. 
1871
             */
1872
            if (cond.propDefined(&verifyPreCondition))
1873
            {
1874
                /* 
1875
                 *   invoke the pre-condition verifier - this is an
1876
                 *   action-level verifier, so there's no object involved 
1877
                 */
1878
                resultSoFar = withVerifyResults(resultSoFar, nil,
1879
                    {: cond.verifyPreCondition(nil) });
1880
            }
1881
        }
1882
1883
        /* return the latest result list */
1884
        return resultSoFar;
1885
    }
1886
1887
    /*
1888
     *   Call a catch-all property on the given object.
1889
     *   
1890
     *   actionProp is the custom per-object/per-action property that we
1891
     *   normally invoke to process the action.  For example, if we're
1892
     *   processing verification for the direct object of Take, this would
1893
     *   be &verifyDobjTake.
1894
     *   
1895
     *   defProp is the default property that corresponds to actionProp.
1896
     *   This is the per-object/default-action property that we invoke
1897
     *   when the object doesn't provide a "more specialized" version of
1898
     *   actionProp - that is, if the object doesn't define or inherit
1899
     *   actionProp at a point in its class hierarchy that is more
1900
     *   specialized than the point at which it defines defProp, we'll
1901
     *   call defProp.  If there is a more specialized definition of
1902
     *   actionProp for the object, it effectively overrides the default
1903
     *   handler, so we do not invoke the default handler.
1904
     *   
1905
     *   allProp is the catch-all property corresponding to actionProp.
1906
     *   We invoke this property in all cases.
1907
     *   
1908
     *   Returns true if there is indeed a Default property that overrides
1909
     *   the action, nil if not.  
1910
     */
1911
    callCatchAllProp(obj, actionProp, defProp, allProp)
1912
    {
1913
        /* always invoke the catch-all property first */
1914
        obj.(allProp)();
1915
1916
        /* 
1917
         *   invoke the default property only if the object doesn't have a
1918
         *   "more specialized" version of actionProp 
1919
         */
1920
        if (!obj.propHidesProp(actionProp, defProp))
1921
        {
1922
            /* the Default overrides the action-specific method */
1923
            obj.(defProp)();
1924
1925
            /* tell the caller the Default routine handled it */
1926
            return true;
1927
        }
1928
        else
1929
        {
1930
            /* tell the caller to call the action-specific property */
1931
            return nil;
1932
        }
1933
    }
1934
1935
    /*
1936
     *   Call a verification routine.  This creates a results object and
1937
     *   makes it active, then invokes the given verification routine on
1938
     *   the given object.
1939
     *   
1940
     *   We call verification directly on the object, and we also call
1941
     *   verification on the object's preconditions.
1942
     *   
1943
     *   If resultSoFar is non-nil, it is a VerifyResultList that has the
1944
     *   results so far - this can be used for multi-object verifications
1945
     *   to gather all of the verification results for all of the objects
1946
     *   into a single result list.  If resultSoFar is nil, we'll create a
1947
     *   new result list.  
1948
     */
1949
    callVerifyProp(obj, verProp, preCondProp, remapProp,
1950
                   resultSoFar, whichObj)
1951
    {
1952
        local remapInfo;
1953
1954
        /* check for remapping */
1955
        if ((remapInfo = obj.(remapProp)()) != nil)
1956
        {
1957
            /* call the remapped verify */
1958
            resultSoFar = remapVerify(whichObj, resultSoFar, remapInfo);
1959
1960
            /*
1961
             *   If the original object has a verify that effectively
1962
             *   "overrides" the remap - i.e., defined by an object that
1963
             *   inherits from the object where the remap is defined - then
1964
             *   run it by the overriding verify as well. 
1965
             */
1966
            local remapSrc = obj.propDefined(remapProp, PropDefGetClass);
1967
            if (obj.propDefined(verProp)
1968
                && overrides(obj, remapSrc, verProp))
1969
            {
1970
                resultSoFar = withVerifyResults(
1971
                    resultSoFar, obj, new function() { obj.(verProp)(); });
1972
            }
1973
1974
            /* return the results */
1975
            return resultSoFar;
1976
        }
1977
1978
        /* initialize tentative resolutions for other noun phrases */
1979
        initTentative(gIssuingActor, gActor, whichObj);
1980
1981
        /* 
1982
         *   run the verifiers in the presence of a results list, and
1983
         *   return the result list 
1984
         */
1985
        return withVerifyResults(resultSoFar, obj, new function()
1986
        {
1987
            local lst;
1988
            
1989
            /* 
1990
             *   check the object for a default or catch-all verifier, and
1991
             *   call it if present; if there isn't a default that
1992
             *   overrides the action-specific verifier, continue to the
1993
             *   action-specific verifer 
1994
             */
1995
            if (!callCatchAllProp(
1996
                obj, verProp, objectRelations.verifyDefaultProps[whichObj],
1997
                objectRelations.verifyAllProps[whichObj]))
1998
            {
1999
                /* 
2000
                 *   invoke the action-specific verifier method - this
2001
                 *   will update the global verification results object
2002
                 *   with the appropriate status for this action being
2003
                 *   performed on this object 
2004
                 */
2005
                obj.(verProp)();
2006
            }
2007
2008
            /*
2009
             *   Check the pre-conditions defined for this action on this
2010
             *   object.  For each one that has a verifier, invoke the
2011
             *   verifier.  
2012
             */
2013
            lst = getObjPreConditions(obj, preCondProp, whichObj);
2014
            if (lst != nil)
2015
                foreach (local cur in lst)
2016
                    cur.verifyPreCondition(obj);
2017
        });
2018
    }
2019
2020
    /*
2021
     *   Get the precondition list for an object.  whichObj is the object
2022
     *   role of the object whose preconditions we're retrieving; this is
2023
     *   nil if we're looking for action-level preconditions.  
2024
     */
2025
    getObjPreConditions(obj, preCondProp, whichObj)
2026
    {
2027
        local allPreProp;
2028
        local defPreProp;
2029
        local pre;
2030
2031
        /* 
2032
         *   if we're looking for action preconditions, there are no
2033
         *   default or catch-all properties, so simply get the
2034
         *   preconditions from the action
2035
         */
2036
        if (whichObj == nil)
2037
            return obj.(preCondProp);
2038
2039
        /* get the default-action and catch-all precondition properties */
2040
        defPreProp = objectRelations.preCondDefaultProps[whichObj];
2041
        allPreProp = objectRelations.preCondAllProps[whichObj];
2042
        
2043
        /*
2044
         *   Check for an "overriding" default-action handler.  If we have
2045
         *   a default-action handler that hides the specific handler for
2046
         *   this action, use the default handler's precondition list
2047
         *   instead.  Otherwise, use the specific action preconditions.  
2048
         */
2049
        if (obj.propHidesProp(defPreProp, preCondProp))
2050
            pre = obj.(defPreProp);
2051
        else
2052
            pre = obj.(preCondProp);
2053
2054
        /* if we have catch-all preconditions, add them to the list */
2055
        if (obj.propDefined(allPreProp))
2056
        {
2057
            /* get the catch-all preconditions */
2058
            local allPre = obj.(allPreProp);
2059
2060
            /* add them to the list so far (if any) */
2061
            pre = (pre == nil ? allPre : pre + allPre);
2062
        }
2063
2064
        /* return the precondition list */
2065
        return pre;
2066
    }
2067
2068
    /*
2069
     *   Verify that some sort of handling for this action is defined on
2070
     *   at least one of the given objects.  If we have no handlers at all
2071
     *   definfed, we'll add an "illogical" status to the verification
2072
     *   results to indicate that the action is not defined on this
2073
     *   object.  This check provides a last resort for verbs with no
2074
     *   handling at all defined on the involved objects, to ensure that
2075
     *   the verb won't go completely unprocessed.
2076
     *   
2077
     *   Each entry in objList is an object involved in the action.  For
2078
     *   each entry in objList, there must be *THREE* entries in propList:
2079
     *   a verify property, a check property, and an action property.  If
2080
     *   any of these three properties is defined on the corresponding
2081
     *   object, we'll allow the command to proceed.  If we can find none
2082
     *   of the given handler properties on any of our objects, we'll add
2083
     *   an "illogical" verify result.  
2084
     */
2085
    verifyHandlersExist(objList, propList, result)
2086
    {
2087
        /* 
2088
         *   check each object to see if any define their corresponding
2089
         *   action property 
2090
         */
2091
        for (local i = 1, local j = 1, local len = objList.length() ;
2092
             i <= len ; ++i, j += 3)
2093
        {
2094
            /* check each of the associated handler properties */
2095
            for (local k = 0 ; k < 3 ; ++k)
2096
            {
2097
                /* check this handler property */
2098
                if (objList[i].propDefined(propList[j + k]))
2099
                {
2100
                    /* we've found a handler, so we can proceed */
2101
                    return result;
2102
                }
2103
            }
2104
        }
2105
        
2106
        /* 
2107
         *   None of the objects defines an appropriate action method, so
2108
         *   this verifies as illogical.  If there's no result list so
2109
         *   far, create one.  
2110
         */
2111
        if (result == nil)
2112
            result = new VerifyResultList();
2113
2114
        /* add an "illogical" status to the results */
2115
        result.addResult(new IllogicalVerifyResult(&cannotDoThatMsg));
2116
2117
        /* return the result */
2118
        return result;
2119
    }
2120
    
2121
    /*
2122
     *   Call the beforeAction or afterAction method for each object in
2123
     *   the notification list. 
2124
     */
2125
    notifyBeforeAfter(prop)
2126
    {
2127
        local lst;
2128
2129
        /* get a table of potential notification receivers */
2130
        lst = getNotifyTable();
2131
2132
        /* go through the table and notify each object */
2133
        lst.forEachAssoc({obj, val: obj.(prop)()});
2134
    }
2135
2136
    /*
2137
     *   Get the list of objects to notify before or after the action has
2138
     *   been performed.  
2139
     */
2140
    getNotifyTable()
2141
    {
2142
        local tab;
2143
        local curObjs;
2144
        local actor;
2145
2146
        /* stash the current actor in a local for faster reference */
2147
        actor = gActor;
2148
        
2149
        /* start with everything connected by containment to the actor */
2150
        tab = actor.connectionTable();
2151
2152
        /* add the actor's explicitly registered notification list */
2153
        foreach (local cur in actor.getActorNotifyList())
2154
            tab[cur] = true;
2155
2156
        /* add the items explicitly registered in the actor's location(s) */
2157
        actor.forEachContainer(
2158
            {loc: loc.getRoomNotifyList().forEach(
2159
                {obj: tab[obj] = true})
2160
            });
2161
2162
        /* get the list of objects directly involved in the command */
2163
        curObjs = getCurrentObjects();
2164
2165
        /* 
2166
         *   add any objects explicitly registered with the objects
2167
         *   directly involved in the command 
2168
         */
2169
        foreach (local cur in curObjs)
2170
        {
2171
            /* add each item in the object's notify list */
2172
            foreach (local ncur in cur.getObjectNotifyList())
2173
                tab[ncur] = true;
2174
        }
2175
2176
        /*
2177
         *   Remove from the list all of the actor's containers.  These
2178
         *   will be notified via the more specific room notification
2179
         *   methods, so we don't want to send them generic notifiers as
2180
         *   well. 
2181
         */
2182
        tab.forEachAssoc(new function(obj, val)
2183
        {
2184
            if (actor.isIn(obj))
2185
                tab.removeElement(obj);
2186
        });
2187
2188
        /* if we have any explicitly registered objects, add them */
2189
        if (beforeAfterObjs != nil)
2190
        {
2191
            /* add each object in the list of explicitly registered objects */
2192
            foreach (local cur in beforeAfterObjs)
2193
                tab[cur] = true;
2194
        }
2195
2196
        /* return the final table */
2197
        return tab;
2198
    }
2199
2200
    /*
2201
     *   Register an object for explicit inclusion in beforeAction and
2202
     *   afterAction notifications.  This can be used to register objects
2203
     *   that might not be connected by containment or otherwise
2204
     *   notifiable by normal means.  If this is called after the
2205
     *   beforeAction notification loop has already started, then the
2206
     *   object will only be sent an afterAction notification.  
2207
     */
2208
    addBeforeAfterObj(obj)
2209
    {
2210
        /* if we don't yet have a before/after list, create one */
2211
        if (beforeAfterObjs == nil)
2212
            beforeAfterObjs = new Vector(16);
2213
2214
        /* add the object to the list */
2215
        beforeAfterObjs.append(obj);
2216
    }
2217
2218
    /* vector of objects requiring explicit before/after notification */
2219
    beforeAfterObjs = nil
2220
2221
    /*
2222
     *   Get the list of all of the objects (direct object, indirect
2223
     *   object, and any additional objects for actions with three or more
2224
     *   object roles) involved in the current execution.  This is valid
2225
     *   only during a call to doActionOnce(), since that's the only time
2226
     *   a particular set of objects are selected for the action.
2227
     *   
2228
     *   By default, an action has no objects roles at all, so we'll just
2229
     *   return an empty list.  
2230
     */
2231
    getCurrentObjects()
2232
    {
2233
        return [];
2234
    }
2235
2236
    /*
2237
     *   Set the current objects.  This takes a list of the same form
2238
     *   returned by getCurrentObjects(). 
2239
     */
2240
    setCurrentObjects(lst) { }
2241
2242
    /*
2243
     *   Check any pre-conditions for the action.
2244
     *   
2245
     *   This should check all of the conditions that must be met for the
2246
     *   action to proceed.  If any pre-condition can be met by running an
2247
     *   implicit command first, that implicit command should be executed
2248
     *   here.  If any pre-condition cannot be met, this routine should
2249
     *   notify the actor and throw an ExitSignal.
2250
     *   
2251
     *   Returns true if any implicit commands are executed, nil if not.
2252
     *   Implicit commands can only be attempted if allowImplicit is true;
2253
     *   if this is nil, a precondition must simply fail (by displaying an
2254
     *   appropriate failure report and using 'exit') without attempting
2255
     *   an implicit command if its assertion does not hold.  
2256
     */
2257
    checkPreConditions(allowImplicit)
2258
    {
2259
        /* check each condition in our action preconditions */
2260
        return callPreConditions(getPreCondDescList(), allowImplicit);
2261
    }
2262
2263
    /*
2264
     *   Get the precondition descriptor list for the action.  For the base
2265
     *   intransitive action type, this simply returns the list of
2266
     *   conditions for the action itself.  
2267
     */
2268
    getPreCondDescList()
2269
    {
2270
        /* get the action-level preconditions */
2271
        return getObjPreCondDescList(self, &preCond, nil, nil);
2272
    }
2273
2274
    /*
2275
     *   Get a precondition descriptor list for the given object.  This
2276
     *   returns a list of PreCondDesc objects that wrap the preconditions
2277
     *   for the given object in the given role for this action.  
2278
     */
2279
    getObjPreCondDescList(obj, preCondProp, checkArg, whichObj)
2280
    {
2281
        local lst;
2282
        
2283
        /* get the list of preconditions for the given object in its role */
2284
        lst = getObjPreConditions(obj, preCondProp, whichObj);
2285
2286
        /* if there are no preconditions, return an empty list */
2287
        if (lst == nil)
2288
            return [];
2289
2290
        /* 
2291
         *   wrap the precondition objects in descriptors, so that we can
2292
         *   keep track of the check argument that goes with these
2293
         *   preconditions 
2294
         */
2295
        return lst.mapAll({x: new PreCondDesc(x, checkArg)});
2296
    }
2297
2298
    /*
2299
     *   Call a method on all of the precondition objects in the
2300
     *   precondition list obtained from the given property of the given
2301
     *   object. 
2302
     */
2303
    callPreConditions(lst, allowImplicit)
2304
    {
2305
        local ret;
2306
        local i;
2307
        
2308
        /* presume we won't call any implicit commands */
2309
        ret = nil;
2310
2311
        /* 
2312
         *   note the original descriptor list order, so that we can retain
2313
         *   the current ordering when the execution order doesn't require
2314
         *   changes 
2315
         */
2316
        i = 0;
2317
        lst.forEach({x: x.index_ = i++});
2318
2319
        /* sort the precondition list by execution order */
2320
        lst = lst.sort(SortAsc, new function(a, b) {
2321
            local delta;
2322
            
2323
            /* if the execution orders differ, sort by execution order */
2324
            delta = a.cond_.preCondOrder - b.cond_.preCondOrder;
2325
            if (delta != 0)
2326
                return delta;
2327
2328
            /* otherwise, retain the original list order */
2329
            return a.index_ - b.index_;
2330
        });
2331
2332
        /* catch any 'exit' signals within the preconditions */
2333
        try
2334
        {
2335
            /* invoke the check method for each condition in the list */
2336
            foreach (local cur in lst)
2337
            {
2338
                /* 
2339
                 *   call this precondition; if it runs an implicit
2340
                 *   command, note that we have run an implicit command 
2341
                 */
2342
                if (cur.checkPreCondition(allowImplicit))
2343
                    ret = true;
2344
            }
2345
        }
2346
        catch (ExitSignal es)
2347
        {
2348
            /* 
2349
             *   any 'exit' that occurs within a precondition is a failure
2350
             *   for the enclosing action 
2351
             */
2352
            gTranscript.noteFailure();
2353
            
2354
            /* re-throw the 'exit' to cancel the enclosing action */
2355
            throw es;
2356
        }
2357
2358
        /* return true if any implicit commands were executed */
2359
        return ret;
2360
    }
2361
2362
    /* 
2363
     *   Our list of action-level pre-condition objects.  These are the
2364
     *   conditions that apply to the overall action, not to the
2365
     *   individual objects involved.  (Object-level pre-conditions are
2366
     *   attached to the objects, not to the action.)  
2367
     */
2368
    preCond = []
2369
2370
    /*
2371
     *   Finish the result list for a resolved noun phrase.  This is used
2372
     *   just before disambiguation.  We'll give each object in the list a
2373
     *   chance to filter the list with filterResolveList, and we'll note
2374
     *   the noun phrase we matched in each resolved object.  
2375
     */
2376
    finishResolveList(lst, whichObj, np, requiredNum)
2377
    {
2378
        /* give each object a chance to filter the list */
2379
        foreach (local cur in lst)
2380
            lst = cur.obj_.filterResolveList(
2381
                lst, self, whichObj, np, requiredNum);
2382
2383
        /* stash the noun phrase in each object in the list */
2384
        foreach (local cur in lst)
2385
            cur.np_ = np;
2386
2387
        /* return the list */
2388
        return lst;
2389
    }
2390
2391
    /*
2392
     *   Get a list of verification results for the given ResolveInfo
2393
     *   objects, sorted from best to worst.  Each entry in the returned
2394
     *   list will be a VerifyResultList object whose obj_ property is set
2395
     *   to the ResolveInfo object for which it was generated.  
2396
     */
2397
    getSortedVerifyResults(lst, verProp, preCondProp, remapProp,
2398
                           whichObj, np, requiredNum)
2399
    {
2400
        local results;
2401
        local idx;
2402
2403
        /* if there's nothing in the list, we're done */
2404
        if (lst == [])
2405
            return lst;
2406
2407
        /*
2408
         *   Before we run verification, give each object in the list a
2409
         *   chance to do its own filtering on the entire list.  This form
2410
         *   of filtering allows an object to act globally on the list, so
2411
         *   that it can take special action according to the presence or
2412
         *   absence of other objects, and can affect the presence of other
2413
         *   objects.  
2414
         */
2415
        lst = finishResolveList(lst, whichObj, np, requiredNum);
2416
2417
        /* create a vector to hold the verification results */        
2418
        results = new Vector(lst.length());
2419
2420
        /*
2421
         *   Call the given verifier method on each object, noting each
2422
         *   result.
2423
         */
2424
        idx = 0;
2425
        foreach (local cur in lst)
2426
        {
2427
            local curResult;
2428
2429
            /* call the verifier method and note the current result */
2430
            curResult = callVerifyProp(cur.obj_, verProp, preCondProp,
2431
                                       remapProp, nil, whichObj);
2432
2433
            /* 
2434
             *   save the ResolveInfo in the verify result list object, so
2435
             *   that we can figure out later (after sorting the results)
2436
             *   which original ResolveInfo this verification result
2437
             *   applies to 
2438
             */
2439
            curResult.obj_ = cur;
2440
2441
            /* remember the original list order */
2442
            curResult.origOrder = idx++;
2443
2444
            /* add this verify result to our result vector */
2445
            results.append(curResult);
2446
        }
2447
2448
        /* 
2449
         *   Sort the results in descending order of logicalness, and
2450
         *   return the sorted list.  When results are equivalently
2451
         *   logical, keep the results in their existing order.  
2452
         */
2453
        return results.toList().sort(SortDesc, new function(x, y)
2454
        {
2455
            /* compare the logicalness */
2456
            local c = x.compareTo(y);
2457
2458
            /* 
2459
             *   if it's the same, keep in ascending pluralOrder - note
2460
             *   that we must reverse the sense of this comparison, since
2461
             *   we're sorting the overall list in descending order
2462
             */
2463
            if (c == 0)
2464
                c = (y.obj_.obj_.pluralOrder - x.obj_.obj_.pluralOrder);
2465
2466
            /* if they're otherwise the same, preserve the original order */
2467
            if (c == 0)
2468
                c = (y.origOrder - x.origOrder);
2469
2470
            /* return the result */
2471
            return c;
2472
        });
2473
    }
2474
2475
    /*
2476
     *   Combine any remapped verify results in the given verify result
2477
     *   list.  We will remove any result that was remapped to a different
2478
     *   object if and only if the target of the remapping appears in the
2479
     *   list and has the same results as the remapped original.  When
2480
     *   objects are remapped in this fashion, they become effectively
2481
     *   equivalent for the purposes of this command, so we don't have to
2482
     *   distinguish between them for disambiguation or defaulting
2483
     *   purposes.  
2484
     */
2485
    combineRemappedVerifyResults(lst, role)
2486
    {
2487
        /* scan each element in the list */
2488
        for (local i = 1, local len = lst.length() ; i <= len ; ++i)
2489
        {
2490
            local cur = lst[i];
2491
            
2492
            /* if this element has been remapped, consider it further */
2493
            if (cur.remapTarget_ != nil)
2494
            {
2495
                local other;
2496
                
2497
                /* 
2498
                 *   scan for another entry in the list that matches us
2499
                 *   for remapping purposes 
2500
                 */
2501
                other = lst.indexWhich(
2502
                    {x: x != cur
2503
                        && cur.matchForCombineRemapped(x, self, role)});
2504
2505
                /* 
2506
                 *   if we found another entry, delete the other entry,
2507
                 *   since it is indistinguishable from this entry
2508
                 */
2509
                if (other != nil)
2510
                {
2511
                    /* remove this element from the list */
2512
                    lst -= cur;
2513
2514
                    /* adjust our list counters for the deletion */
2515
                    --i;
2516
                    --len;
2517
                }
2518
            }
2519
        }
2520
2521
        /* return the updated list */
2522
        return lst;
2523
    }
2524
2525
    /*
2526
     *   Filter an ambiguous object list using the given verification
2527
     *   method.  We call the given verification method on each object,
2528
     *   noting the result, then find the best (most logical) result in
2529
     *   the list.  We reduce the set to the objects that all have the
2530
     *   same best value - everything else in the list is less logical, so
2531
     *   we discard it.  This gives us a set of objects that are all of
2532
     *   equivalent likelihood and all of the best likelihood of all the
2533
     *   objects.
2534
     *   
2535
     *   This is the typical way that we disambiguate a list of objects,
2536
     *   but this is merely a service routine, so individual actions can
2537
     *   choose to use this or other mechanisms as appropriate.  
2538
     */
2539
    filterAmbiguousWithVerify(lst, requiredNum, verProp,
2540
                              preCondProp, remapProp, whichObj, np)
2541
    {
2542
        local results;
2543
        local discards;
2544
        local keepers;
2545
        local bestResult;
2546
        local uniqueKeepers;
2547
2548
        /* if there's nothing in the list, there's nothing to do */
2549
        if (lst == [])
2550
            return lst;
2551
2552
        /* first, filter out redundant facets */
2553
        lst = filterFacets(lst);
2554
        
2555
        /* 
2556
         *   Call the verifier method on each object, and sort the results
2557
         *   from best to worst.  
2558
         */
2559
        results = getSortedVerifyResults(lst, verProp, preCondProp,
2560
                                         remapProp, whichObj,
2561
                                         np, requiredNum);
2562
2563
        /* note the best result value */
2564
        bestResult = results[1];
2565
2566
        /* 
2567
         *   ask the noun phrase to filter this list down to the ones it
2568
         *   wants to keep 
2569
         */
2570
        keepers = np.getVerifyKeepers(results);
2571
2572
        /*
2573
         *   Count the number of unique keepers - this is the number of
2574
         *   items in the keepers list that don't have any equivalents in
2575
         *   the keepers list.
2576
         *   
2577
         *   To calculate this number, start with the total number of
2578
         *   items in the list, and reduce it by one for each item with an
2579
         *   earlier equivalent in the list.  Note that we only ignore
2580
         *   items with unique equivalents *earlier* in the list so that
2581
         *   we keep exactly one of each equivalent - if we ignored every
2582
         *   element with a unique equivalent elsewhere in the list, we'd
2583
         *   ignore every equivalent item, so we'd only count the items
2584
         *   with no equivalents at all.  
2585
         */
2586
        uniqueKeepers = keepers.length();
2587
        for (local i = 1, local cnt = keepers.length() ; i <= cnt ; ++i)
2588
        {
2589
            local eqIdx;
2590
            
2591
            /* check for a unique equivalent earlier in the list */
2592
            eqIdx = keepers.indexWhich(
2593
                {x: x.obj_.obj_.isVocabEquivalent(keepers[i].obj_.obj_)});
2594
            if (eqIdx != nil && eqIdx < i)
2595
            {
2596
                /* 
2597
                 *   this one has an earlier equivalent, so don't include
2598
                 *   it in the unique item count 
2599
                 */
2600
                --uniqueKeepers;
2601
            }
2602
        }
2603
2604
        /*
2605
         *   If we found more items to keep than were required by the
2606
         *   caller, we were not able to reduce the set to an unambiguous
2607
         *   subset.  In this case, keep *all* of the items that are
2608
         *   logical. 
2609
         */
2610
        if (uniqueKeepers > requiredNum)
2611
        {
2612
            local allAllowed;
2613
            
2614
            /* filter so that we keep all of the logical results */
2615
            allAllowed = results.subset({x: x.allowAction});
2616
2617
            /* if that list is non-empty, use it as the keepers */
2618
            if (allAllowed.length() != 0)
2619
                keepers = allAllowed;
2620
        }
2621
2622
        /* 
2623
         *   Get the list of discards - this is the balance of the
2624
         *   original result list after removing the best ones.  
2625
         */
2626
        discards = results - keepers;
2627
2628
        /* 
2629
         *   We now have the set of objects we want, but the entries in
2630
         *   the list are all VerifyResultList instances, and we just want
2631
         *   the objects - pull out a list of just the ResolveInfo, and
2632
         *   return that.  
2633
         */
2634
        keepers = keepers.mapAll({x: x.obj_});
2635
2636
        /*
2637
         *   Check to see if we should set flags in the results.  If we
2638
         *   eliminated any objects, flag the remaining objects as having
2639
         *   been selected through disambiguation.  If the best of the
2640
         *   discarded objects were logical, flag the survivors as
2641
         *   "unclear," because we only selected them as better than the
2642
         *   discards, and not because they were the only possible
2643
         *   choices.  
2644
         */
2645
        if (discards != [])
2646
        {
2647
            local addedFlags;
2648
            
2649
            /* 
2650
             *   We have reduced the set.  If the best of the discards was
2651
             *   ranked as highly as the survivors at the coarsest level,
2652
             *   flag the survivors as having been "unclearly"
2653
             *   disambiguated; otherwise, mark them as "clearly"
2654
             *   disambiguated.  
2655
             */
2656
            if (keepers.indexOf(bestResult.obj_) != nil
2657
                && (discards[1].getEffectiveResult().resultRank ==
2658
                    bestResult.getEffectiveResult().resultRank))
2659
            {
2660
                /* 
2661
                 *   we had to make a choice that discarded possibilities
2662
                 *   that were valid, though not as good as the one we
2663
                 *   chose - mark the objects as being not perfectly clear 
2664
                 */
2665
                addedFlags = UnclearDisambig;
2666
2667
                /* 
2668
                 *   if the keepers and the rejects are all basic
2669
                 *   equivalents, don't bother flagging this as unclear,
2670
                 *   since there's no point in mentioning that we chose
2671
                 *   one basic equivalent over another, as they all have
2672
                 *   the same name 
2673
                 */
2674
                if (BasicResolveResults.filterWithDistinguisher(
2675
                    keepers + discards.mapAll({x: x.obj_}),
2676
                    basicDistinguisher).length() == 1)
2677
                {
2678
                    /* they're all basic equivalents - mark as clear */
2679
                    addedFlags = ClearDisambig;
2680
                }
2681
            }
2682
            else
2683
            {
2684
                /* the choice is clear */
2685
                addedFlags = ClearDisambig;
2686
            }
2687
2688
            /* add the flags to each survivor */
2689
            foreach (local cur in keepers)
2690
                cur.flags_ |= addedFlags;
2691
        }
2692
2693
        /* return the results */
2694
        return keepers;
2695
    }
2696
2697
    /*
2698
     *   Filter a plural list with a verification method.  We'll reduce
2699
     *   the list to the subset of objects that verify as logical, if
2700
     *   there are any.  If there are no logical objects in the list,
2701
     *   we'll simply return the entire original list.  
2702
     */
2703
    filterPluralWithVerify(lst, verProp, preCondProp, remapProp, whichObj, np)
2704
    {
2705
        local results;
2706
2707
        /* if there's nothing in the list, there's nothing to do */
2708
        if (lst == [])
2709
            return lst;
2710
2711
        /* first, filter out redundant facets */
2712
        lst = filterFacets(lst);
2713
        
2714
        /* 
2715
         *   Call the verifier method on each object, and sort the results
2716
         *   from best to worst.  
2717
         */
2718
        results = getSortedVerifyResults(lst, verProp, preCondProp,
2719
                                         remapProp, whichObj, np, nil);
2720
2721
        /*
2722
         *   If the best (and thus first) result allows the action, filter
2723
         *   the list to keep only the "keepers," as determined by the noun
2724
         *   phrase.  Otherwise, there are no allowed results, so return
2725
         *   the original list.  
2726
         */
2727
        if (results[1].allowAction)
2728
            results = np.getVerifyKeepers(results);
2729
2730
        /* return the resolve results objects from the list */
2731
        return results.mapAll({x: x.obj_});
2732
    }
2733
2734
    /*
2735
     *   Filter out redundant facets of the same object.  The various
2736
     *   facets of an object are equivalent to the parser.  An object that
2737
     *   has multiple facets is meant to appear to be one game world
2738
     *   object from the perspective of a character - the multiple facet
2739
     *   objects are an internal implementation detail. 
2740
     */
2741
    filterFacets(lst)
2742
    {
2743
        local result;
2744
        local actor = gActor;
2745
2746
        /* 
2747
         *   create a vector for the result list, presuming we'll keep all
2748
         *   of the original list elements 
2749
         */
2750
        result = new Vector(lst.length(), lst);
2751
2752
        /* check for facets */
2753
        foreach (local cur in lst)
2754
        {
2755
            local allFacets;
2756
2757
            /* if this item has any facets, check for them in our results */
2758
            allFacets = cur.obj_.getFacets() + cur.obj_;
2759
            if (allFacets.length() != 0)
2760
            {
2761
                local inScopeFacets;
2762
                local best;
2763
                
2764
                /* make a new list of the facets that appear in the results */
2765
                inScopeFacets = allFacets.subset(
2766
                    {f: result.indexWhich({r: r.obj_ == f}) != nil});
2767
2768
                /* pick the best of those facets */
2769
                best = findBestFacet(actor, inScopeFacets);
2770
                
2771
                /* 
2772
                 *   remove all of the facets besides the best one from the
2773
                 *   result list - this will ensure that we have only the
2774
                 *   single best facet in the final results 
2775
                 */
2776
                foreach (local r in result)
2777
                {
2778
                    /* 
2779
                     *   if this result list item is in the facet list, and
2780
                     *   it's not the best facet, delete it from the result
2781
                     *   list 
2782
                     */
2783
                    if (r.obj_ != best
2784
                        && inScopeFacets.indexOf(r.obj_) != nil)
2785
                        result.removeElement(r);
2786
                }
2787
2788
            }
2789
        }
2790
2791
        /* return the result list */
2792
        return result.toList();
2793
    }
2794
2795
    /*
2796
     *   Get a default object using the given verification method.  We'll
2797
     *   start with the 'all' list, then use the verification method to
2798
     *   reduce the list to the most likely candidates.  If we find a
2799
     *   unique most likely candidate, we'll return a ResolveInfo list
2800
     *   with that result; otherwise, we'll return nothing, since there is
2801
     *   no suitable default.  
2802
     */
2803
    getDefaultWithVerify(resolver, verProp, preCondProp, remapProp,
2804
                         whichObj, np)
2805
    {
2806
        local lst;
2807
        local results;
2808
        local bestResult;
2809
        
2810
        /* 
2811
         *   Start with the 'all' list for this noun phrase.  This is the
2812
         *   set of every object that we consider obviously suitable for
2813
         *   the command, so it's a good starting point to guess at a
2814
         *   default object.
2815
         */
2816
        lst = resolver.getAllDefaults();
2817
2818
        /* eliminate objects that can't be defaults for this action */
2819
        lst = lst.subset({x: !x.obj_.hideFromDefault(self)});
2820
2821
        /* 
2822
         *   reduce equivalent items to a single instance of each
2823
         *   equivalent - if we have several equivalent items that are
2824
         *   equally good as defaults, we can pick just one 
2825
         */
2826
        lst = resolver.filterAmbiguousEquivalents(lst, np);
2827
2828
        /* if we have no entries in the list, there is no default */
2829
        if (lst == [])
2830
            return nil;
2831
2832
        /* 
2833
         *   get the verification results for these objects, sorted from
2834
         *   best to worst 
2835
         */
2836
        results = getSortedVerifyResults(lst, verProp, preCondProp,
2837
                                         remapProp, whichObj, np, nil);
2838
2839
        /* eliminate redundant remapped objects */
2840
        results = combineRemappedVerifyResults(results, whichObj);
2841
2842
        /* note the best result */
2843
        bestResult = results[1];
2844
2845
        /* 
2846
         *   if the best item is not allowed as an implied object, there
2847
         *   is no default 
2848
         */
2849
        if (!bestResult.allowImplicit)
2850
            return nil;
2851
2852
        /* 
2853
         *   The best item must be uniquely logical in order to be a
2854
         *   default - if more than one item is equally good, it makes no
2855
         *   sense to assume anything about what the user meant.  So, if
2856
         *   we have more than one item, and the second item is equally as
2857
         *   logical as the first item, we cannot supply a default.  (The
2858
         *   second item cannot be better than the first item, because of
2859
         *   the sorting - at most, it can be equally good.)  
2860
         */
2861
        if (results.length() != 1 && bestResult.compareTo(results[2]) == 0)
2862
            return nil;
2863
2864
        /* 
2865
         *   We have a uniquely logical item, so we can assume the user
2866
         *   must have been referring to this item.  Return the
2867
         *   ResolveInfo for this item (which the verify result sorter
2868
         *   stashed in the obj_ property of the verify result object).
2869
         *   
2870
         *   Before returning the list, clear the 'all' flag in the result
2871
         *   (getAll() set that flag), and replace it with the 'default'
2872
         *   flag, since the object is an implied default.  
2873
         */
2874
        bestResult.obj_.flags_ &= ~MatchedAll;
2875
        bestResult.obj_.flags_ |= DefaultObject;
2876
2877
        /* return the result list */
2878
        return [bestResult.obj_];
2879
    }
2880
2881
    /*
2882
     *   A synthesized Action (one that's generated by something other than
2883
     *   parsing a command line, such as an event action or nested action)
2884
     *   won't have a parser token list attached to it.  If we're asked to
2885
     *   get the token list, we need to check for this possibility.  If we
2886
     *   don't have a token list, but we do have a parent action, we'll
2887
     *   defer to the parent action.  Otherwise, we'll simply return nil.
2888
     */
2889
    getOrigTokenList()
2890
    {
2891
        /* if we don't have a token list, look elsewhere */
2892
        if (tokenList == nil)
2893
        {
2894
            /* if we have a parent action, defer to it */
2895
            if (parentAction != nil)
2896
                return parentAction.getOrigTokenList();
2897
2898
            /* we have nowhere else to look, so return an empty list */
2899
            return [];
2900
        }
2901
2902
        /* inherit the standard handling from BasicProd */
2903
        return inherited();
2904
    }
2905
    
2906
2907
    /* 
2908
     *   List of objects that verified okay on a prior pass.  This is a
2909
     *   scratch-pad for use by verifier routines, to keep track of work
2910
     *   they've already done.  A few verifiers use this as a way to
2911
     *   detect when an implicit action actually finished the entire job,
2912
     *   which would in many cases result in a verify failure if not
2913
     *   checked (because a command that effects conditions that already
2914
     *   hold is normally illogical); by tracking that the verification
2915
     *   previously succeeded, the verifier can know that the action
2916
     *   should be allowed to proceed and do nothing.  
2917
     */
2918
    verifiedOkay = []
2919
;
2920
2921
/*
2922
 *   Call the roomBeforeAction method on a given room's containing rooms,
2923
 *   then on the room itself.  
2924
 */
2925
callRoomBeforeAction(room)
2926
{
2927
    /* first, call roomBeforeAction on the room's containers */
2928
    room.forEachContainer(callRoomBeforeAction);
2929
2930
    /* call roomBeforeAction on this room */
2931
    room.roomBeforeAction();
2932
}
2933
2934
/*
2935
 *   Call the roomAfterAction method on a given room, then on the room's
2936
 *   containing rooms.  
2937
 */
2938
callRoomAfterAction(room)
2939
{
2940
    /* first, call roomAfterAction on this room */
2941
    room.roomAfterAction();
2942
2943
    /* next, call roomAfterAction on the room's containers */
2944
    room.forEachContainer(callRoomAfterAction);
2945
}
2946
2947
/* ------------------------------------------------------------------------ */
2948
/*
2949
 *   Intransitive Action class - this is an action that takes no objects.
2950
 *   In general, each subclass should implement its action handling in its
2951
 *   execAction() method.  
2952
 */
2953
class IAction: Action
2954
    /* 
2955
     *   resolve my noun phrases to objects 
2956
     */
2957
    resolveNouns(issuingActor, targetActor, results)
2958
    {
2959
        /* 
2960
         *   We have no objects to resolve.  The only thing we have to do
2961
         *   is note in the results our number of structural noun slots
2962
         *   for the verb, which is zero, since we have no objects at all. 
2963
         */
2964
        results.noteNounSlots(0);
2965
    }
2966
2967
    /*
2968
     *   Execute the action.  
2969
     */
2970
    doActionMain()
2971
    {
2972
        /* 
2973
         *   we have no objects to iterate, so simply run through the
2974
         *   execution sequence once 
2975
         */
2976
        doActionOnce();
2977
    }
2978
;
2979
2980
/* ------------------------------------------------------------------------ */
2981
/*
2982
 *   Transitive Action class - this is an action that takes a direct
2983
 *   object.
2984
 *   
2985
 *   For simplicity, this object is its own object resolver - we really
2986
 *   don't need a separate resolver object because we have only one object
2987
 *   list for this verb.  (In contrast, an action with both a direct and
2988
 *   indirect object might need separate resolution rules for the two
2989
 *   objects, and hence would need separate resolver objects for the two.)
2990
 *   
2991
 *   The advantage of implementing the Resolver behavior in this object,
2992
 *   rather than using a separate object, is that it's less trouble to
2993
 *   override object resolution rules - simply override the resolver
2994
 *   methods in the subclass where you define the grammar rule for the
2995
 *   action.  
2996
 */
2997
class TAction: Action, Resolver
2998
    construct()
2999
    {
3000
        /* inherit only the Action constructor */
3001
        inherited Action.construct();
3002
    }
3003
3004
    resetAction()
3005
    {
3006
        /* inherit default handling */
3007
        inherited();
3008
        
3009
        /* discard our cached resolver */
3010
        dobjResolver_ = nil;
3011
    }
3012
    
3013
    /*
3014
     *   Create an action for retrying an original action with changes. 
3015
     */
3016
    createForRetry(orig)
3017
    {
3018
        local action;
3019
        
3020
        /* create the new action based on the original action */
3021
        action = createActionFrom(orig);
3022
3023
        /* 
3024
         *   do not include the new command in undo, since it's merely
3025
         *   part of the enclosing explicit command 
3026
         */
3027
        action.includeInUndo = nil;
3028
3029
        /* mark the action as nested */
3030
        action.setNested();
3031
3032
        /*
3033
         *   Even if the original action is implicit, don't announce this
3034
         *   action as implicit, because it's good enough to have
3035
         *   announced the original implicit action.  Now, this new
3036
         *   command actually still is implicit if the original was - we
3037
         *   simply don't want to announce it as such.  To suppress the
3038
         *   extra announcement while still retaining the rest of the
3039
         *   desired implicitness, simply set the implicitMsg property of
3040
         *   the new action to nil; when there's no implicitMsg, there's
3041
         *   no announcement. 
3042
         */
3043
        action.implicitMsg = nil;
3044
3045
        /* return the new action */
3046
        return action;
3047
    }
3048
3049
    /*
3050
     *   Retry an intransitive action as a single-object action.  We'll
3051
     *   obtain a indirect object using the normal means (first looking
3052
     *   for a default, then prompting the player if we can't find a
3053
     *   suitable default).  'orig' is the original zero-object action.
3054
     *   
3055
     *   This routine terminates with 'exit' if it doesn't throw some
3056
     *   other error.  
3057
     */
3058
    retryWithMissingDobj(orig, asker)
3059
    {
3060
        /* resolve and execute the replacement action */
3061
        resolveAndReplaceAction(createForMissingDobj(orig, asker));
3062
    }
3063
3064
    /*
3065
     *   Retry an action as a single-object action with an ambiguous
3066
     *   direct object.  We'll ask which of the given possible objects is
3067
     *   intended.  
3068
     */
3069
    retryWithAmbiguousDobj(orig, objs, asker, objPhrase)
3070
    {
3071
        local action;
3072
        local resolver;
3073
        
3074
        /* create a missing-direct-object replacement action */
3075
        action = createForMissingDobj(orig, asker);
3076
3077
        /* reduce the object list to the objects in scope */
3078
        resolver = action.getDobjResolver(gIssuingActor, gActor, true);
3079
        objs = objs.subset({x: resolver.objInScope(x)});
3080
3081
        /* plug in the ambiguous direct object list */
3082
        action.dobjMatch = new PreResolvedAmbigProd(objs, asker, objPhrase);
3083
        
3084
        /* resolve and execute the replacement action */
3085
        resolveAndReplaceAction(action);
3086
    }
3087
3088
    /*
3089
     *   Test to see if askForDobj() would find a default direct object.
3090
     *   Returns true if there's a default, nil if not.  If this returns
3091
     *   true, then askForDobj() will simply take the default and proceed;
3092
     *   otherwise, it will have to actually ask the user for the missing
3093
     *   information.  
3094
     */
3095
    testRetryDefaultDobj(orig)
3096
    {
3097
        local action;
3098
        local def;
3099
        
3100
        /* create the new action for checking for a direct object */
3101
        action = createForMissingDobj(orig, ResolveAsker);
3102
3103
        /* get the default dobj */
3104
        def = action.getDefaultDobj(
3105
            action.dobjMatch,
3106
            action.getDobjResolver(gIssuingActor, gActor, nil));
3107
3108
        /* if there's exactly one result, then we have a default */
3109
        return (def != nil && def.length() == 1);
3110
    }
3111
3112
    /*
3113
     *   Create an instance of this action for retrying a given
3114
     *   single-object action with a missing direct object.  
3115
     */
3116
    createForMissingDobj(orig, asker)
3117
    {
3118
        local action;
3119
3120
        /* create the action for a retry */
3121
        action = createForRetry(orig);
3122
3123
        /* use an empty noun phrase for the new action's direct object */
3124
        action.dobjMatch = new EmptyNounPhraseProd();
3125
3126
        /* set our custom response production and ResolveAsker */
3127
        action.dobjMatch.setPrompt(action.askDobjResponseProd, asker);
3128
3129
        /* initialize the new action with any pre-resolved parts */
3130
        action.initForMissingDobj(orig);
3131
3132
        /* return the new action */
3133
        return action;
3134
    }
3135
3136
    /*
3137
     *   Initialize this action in preparation for retrying with a missing
3138
     *   direct object.  This routine must copy any phrases from the
3139
     *   original action that have already been resolved.  This base
3140
     *   TAction implementation obviously can't have anything pre-resolved
3141
     *   in the original, since the original must simply be an IAction.
3142
     *   Subclasses must override as appropriate for the kinds of base
3143
     *   actions from which they can be retried.  
3144
     */
3145
    initForMissingDobj(orig) { }
3146
3147
    /*
3148
     *   The root production to use to parse missing direct object
3149
     *   responses.  By default, this is nounList, but individual actions
3150
     *   can override this as appropriate.
3151
     *   
3152
     *   Note that language modules might want to override this to allow
3153
     *   for special responses.  For example, in English, some verbs might
3154
     *   want to override this with a specialized production that allows
3155
     *   the appropriate preposition in the response.  
3156
     */
3157
    askDobjResponseProd = nounList
3158
3159
    /*
3160
     *   Can the direct object potentially resolve to the given simulation
3161
     *   object?  This only determines if the object is a *syntactic*
3162
     *   match, meaning that it can match at a vocabulary and grammar
3163
     *   level.  This doesn't test it for logicalness or check that it's an
3164
     *   otherwise valid resolution.  
3165
     */
3166
    canDobjResolveTo(obj)
3167
    {
3168
        /* check our dobj match tree to see if it can resolve to 'obj' */
3169
        return dobjMatch.canResolveTo(
3170
            obj, self, issuer_, actor_, DirectObject);
3171
    }
3172
3173
    /*
3174
     *   Resolve objects.  This is called at the start of command
3175
     *   execution to resolve noun phrases in the command to specific
3176
     *   objects.  
3177
     */
3178
    resolveNouns(issuingActor, targetActor, results)
3179
    {
3180
        /* note that we have a single noun slot (the direct object) */
3181
        results.noteNounSlots(1);
3182
3183
        /* 
3184
         *   Ask the direct object noun phrase list to resolve itself, and
3185
         *   store the resulting object list.  Since we're starting a
3186
         *   resolution pass through our objects, reset the resolver if
3187
         *   we're reusing it.  
3188
         */
3189
        dobjList_ = dobjMatch.resolveNouns(
3190
            getDobjResolver(issuingActor, targetActor, true), results);
3191
    }
3192
3193
    /* a transitive action has one noun phrase: the direct object */
3194
    predicateNounPhrases = [&dobjMatch]
3195
3196
    /* get the role of an object */
3197
    getRoleFromIndex(idx)
3198
    {
3199
        /* we only take a single object role - the direct object */
3200
        return (idx == 1 ? DirectObject : inherited(idx));
3201
    }
3202
3203
    /* get the resolved object in a given role */
3204
    getObjectForRole(role)
3205
    {
3206
        /* return the direct object if requested */
3207
        return (role == DirectObject ? getDobj() : inherited(role));
3208
    }
3209
3210
    /* get the match tree for the noun phrase in the given role */
3211
    getMatchForRole(role)
3212
    {
3213
        /* return the direct object match tree if requested */
3214
        return (role == DirectObject ? dobjMatch : inherited(role));
3215
    }
3216
3217
    /* get the 'verify' property for a given object role */
3218
    getVerifyPropForRole(role)
3219
    {
3220
        return (role == DirectObject ? verDobjProp : inherited(role));
3221
    }
3222
3223
    /* get the 'preCond' property for a given object role */
3224
    getPreCondPropForRole(role)
3225
    {
3226
        return (role == DirectObject ? preCondDobjProp : inherited(role));
3227
    }
3228
3229
    /* get the 'remap' property for a given object role */
3230
    getRemapPropForRole(role)
3231
    {
3232
        return (role == DirectObject ? remapDobjProp : inherited(role));
3233
    }
3234
3235
    /* get the ResolveInfo for the given object */
3236
    getResolveInfo(obj, oldRole)
3237
    {
3238
        local info;
3239
        
3240
        /* scan our resolved direct object list for the given object */
3241
        if (dobjList_ != nil)
3242
            info = dobjList_.valWhich({x: x.obj_ == obj});
3243
3244
        /* if we didn't find one, create one from scratch */
3245
        if (info == nil)
3246
        {
3247
            /* get the flags for the first object in the old role */
3248
            local flags = (dobjList_.length() > 0 ? dobjList_[1].flags_ : 0);
3249
3250
            /* create a ResolveInfo to represent the object */
3251
            info = new ResolveInfo(obj, flags);
3252
        }
3253
3254
        /* return what we found (or created) */
3255
        return info;
3256
    }
3257
3258
    /* get the list of resolved direct objects */
3259
    getResolvedDobjList()
3260
    {
3261
        /* 
3262
         *   if we have a direct object list, return the objects from it;
3263
         *   otherwise, return an empty list 
3264
         */
3265
        return (dobjList_ == nil ? nil : dobjList_.mapAll({x: x.obj_}));
3266
    }
3267
3268
    /* manually set the resolved objects - we'll set the direct object */
3269
    setResolvedObjects(dobj)
3270
    {
3271
        /* set the resolved direct object */
3272
        setResolvedDobj(dobj);
3273
    }
3274
3275
    /* set the resolved direct object */
3276
    setResolvedDobj(dobj)
3277
    {
3278
        /* 
3279
         *   set the direct object tree to a fake grammar tree that
3280
         *   resolves to our single direct object, in case we're asked to
3281
         *   resolve explicitly 
3282
         */
3283
        dobjMatch = new PreResolvedProd(dobj);
3284
3285
        /* 
3286
         *   Set the resolved direct object list to the single object or
3287
         *   to the list of objects, depending on what we received.
3288
         */
3289
        dobjList_ = makeResolveInfoList(dobj);
3290
3291
        /* set the current object as well */
3292
        dobjCur_ = (dobjList_.length() > 0 ? dobjList_[1].obj_ : nil);
3293
        dobjInfoCur_ = (dobjList_.length() > 0 ? dobjList_[1] : nil);
3294
    }
3295
3296
    /* manually set the unresolved object noun phrase match trees */
3297
    setObjectMatches(dobj)
3298
    {
3299
        /* 
3300
         *   if it's a ResolveInfo, handle it as a resolved object;
3301
         *   otherwise handle it as a match tree to be resolved 
3302
         */
3303
        if (dobj.ofKind(ResolveInfo))
3304
            setResolvedDobj(dobj);
3305
        else
3306
            dobjMatch = dobj;
3307
    }
3308
3309
    /* check that the resolved objects are in scope */
3310
    resolvedObjectsInScope()
3311
    {
3312
        /* check the direct object */
3313
        return getDobjResolver(gIssuingActor, gActor, true)
3314
            .objInScope(dobjList_[1].obj_);
3315
    }
3316
3317
    /*
3318
     *   Get a message parameter object for the action.  We define 'dobj'
3319
     *   as the direct object, in addition to any inherited targets.  
3320
     */
3321
    getMessageParam(objName)
3322
    {
3323
        switch(objName)
3324
        {
3325
        case 'dobj':
3326
            /* return the current direct object */
3327
            return dobjCur_;
3328
3329
        default:
3330
            /* inherit default handling */
3331
            return inherited(objName);
3332
        }
3333
    }
3334
3335
    /*
3336
     *   Execute the action.  We'll run through the execution sequence
3337
     *   once for each resolved direct object.  
3338
     */
3339
    doActionMain()
3340
    {
3341
        /* 
3342
         *   Set the direct object list as the antecedent, using the
3343
         *   language-specific pronoun setter.  Don't set pronouns for a
3344
         *   nested command, because the player didn't necessarily refer to
3345
         *   the objects in a nested command.  
3346
         */
3347
        if (parentAction == nil)
3348
            gActor.setPronoun(dobjList_);
3349
3350
        /* we haven't yet canceled the iteration */
3351
        iterationCanceled = nil;
3352
3353
        /* run through the sequence once for each direct object */
3354
        for (local i = 1, local len = dobjList_.length() ;
3355
             i <= len && !iterationCanceled ; ++i)
3356
        {
3357
            /* make this object our current direct object */
3358
            dobjCur_ = dobjList_[i].obj_;
3359
            dobjInfoCur_ = dobjList_[i];
3360
3361
            /* announce the object if appropriate */
3362
            announceActionObject(dobjList_[i], len, whichMessageObject);
3363
3364
            /* run the execution sequence for the current direct object */
3365
            doActionOnce();
3366
3367
            /* if we're top-level, count the iteration in the transcript */
3368
            if (parentAction == nil)
3369
                gTranscript.newIter();
3370
        }
3371
    }
3372
3373
    /* get the precondition descriptor list for the action */
3374
    getPreCondDescList()
3375
    {
3376
        /* 
3377
         *   return the inherited preconditions plus the conditions that
3378
         *   apply to the direct object 
3379
         */
3380
        return inherited()
3381
            + getObjPreCondDescList(dobjCur_, preCondDobjProp, dobjCur_,
3382
                                    DirectObject);
3383
    }
3384
        
3385
    /*
3386
     *   Get the list of active objects.  We have only a direct object, so
3387
     *   we'll return a list with the current direct object. 
3388
     */
3389
    getCurrentObjects()
3390
    {
3391
        return [dobjCur_];
3392
    }
3393
3394
    /* set the current objects */
3395
    setCurrentObjects(lst)
3396
    {
3397
        dobjCur_ = lst[1];
3398
        dobjInfoCur_ = nil;
3399
    }
3400
3401
    /*
3402
     *   Verify the action.
3403
     */
3404
    verifyAction()
3405
    {
3406
        local result;
3407
        
3408
        /* invoke any general (non-object) pre-condition verifiers */
3409
        result = callVerifyPreCond(nil);
3410
3411
        /* 
3412
         *   invoke the verifier routine ("verXxx") on the current direct
3413
         *   object and return the result
3414
         */
3415
        result = callVerifyProp(dobjCur_, verDobjProp, preCondDobjProp,
3416
                                remapDobjProp, result, DirectObject);
3417
3418
        /* verify that the action routine ("doXxx") exists on the object */
3419
        return verifyHandlersExist(
3420
            [dobjCur_], [verDobjProp, actionDobjProp, checkDobjProp],
3421
            result);
3422
    }
3423
3424
    /* initialize tentative resolutions for other noun phrases */
3425
    initTentative(issuingActor, targetActor, whichObj)
3426
    {
3427
        /* 
3428
         *   we have only one noun phrase, so there's nothing else to
3429
         *   tentatively resolve 
3430
         */
3431
    }
3432
3433
    /*
3434
     *   Check for remapping 
3435
     */
3436
    checkRemapping()
3437
    {
3438
        local remapInfo;
3439
        
3440
        /* check for remapping in the direct object */
3441
        if ((remapInfo = dobjCur_.(remapDobjProp)) != nil)
3442
        {
3443
            /* 
3444
             *   we have a remapping, so apply it - note that this won't
3445
             *   return, since this will completely replace the command
3446
             *   and thus terminate the old command with 'exit' 
3447
             */
3448
            remapAction(nil, DirectObject, remapInfo);
3449
        }
3450
    }
3451
3452
    /*
3453
     *   Check the command.
3454
     *   
3455
     *   For a single-object transitive action, this runs the catch-all
3456
     *   'check' properties (the dobjFor(Default) and dobjFor(All) 'check'
3457
     *   methods) on the direct object, then calls the individual 'check'
3458
     *   routine for this specific action.  
3459
     */
3460
    checkAction()
3461
    {
3462
        try
3463
        {
3464
            /* call the catch-all 'check' properties */
3465
            if (!callCatchAllProp(dobjCur_, checkDobjProp,
3466
                                  &checkDobjDefault, &checkDobjAll))
3467
            {
3468
                /* 
3469
                 *   the action-specific check routine overrides any
3470
                 *   Default catch-all, so call the direct object's check
3471
                 *   routine (its "checkXxx") method 
3472
                 */
3473
                dobjCur_.(checkDobjProp)();
3474
            }
3475
        }
3476
        catch (ExitSignal es)
3477
        {
3478
            /* mark the action as a failure in the transcript */
3479
            gTranscript.noteFailure();
3480
3481
            /* re-throw the signal */
3482
            throw es;
3483
        }
3484
    }
3485
3486
    /*
3487
     *   Execute the command. 
3488
     */
3489
    execAction()
3490
    {
3491
        /* call the catch-all 'action' properties */
3492
        if (!callCatchAllProp(dobjCur_, actionDobjProp,
3493
                              &actionDobjDefault, &actionDobjAll))
3494
        {
3495
            /* 
3496
             *   the verb-specific 'action' handler overrides any Default
3497
             *   action handler, so call the verb-specific 'action'
3498
             *   routine in the direct object (the "doXxx" method) 
3499
             */
3500
            dobjCur_.(actionDobjProp)();
3501
        }
3502
    }
3503
3504
    /*
3505
     *   The direct object preconditions, verifier, remapper, check, and
3506
     *   action methods for this action.  Each concrete action must define
3507
     *   these appropriately.  By convention, the methods are named like
3508
     *   so:
3509
     *   
3510
     *.  preconditions: preCondDobjAction
3511
     *.  verifier: verDobjAction
3512
     *.  remap: remapDobjAction
3513
     *.  check: checkDobjAction
3514
     *.  action: actionDobjAction
3515
     *   
3516
     *   where the 'Action' suffix is replaced by the name of the action.
3517
     *   The DefineTAction macro applies this convention, so in most cases
3518
     *   library and game authors will never have to create all of those
3519
     *   property names manually.  
3520
     */
3521
    verDobjProp = nil
3522
    preCondDobjProp = nil
3523
    remapDobjProp = nil
3524
    checkDobjProp = nil
3525
    actionDobjProp = nil
3526
3527
    /*
3528
     *   Get my direct object resolver.  If I don't already have one,
3529
     *   create one and cache it; if I've already cached one, return it.
3530
     *   Note that we cache the resolver because it can sometimes take a
3531
     *   bit of work to set one up (the scope list can in some cases be
3532
     *   complicated to calculate).  We use the resolver only during the
3533
     *   object resolution phase; since game state can't change during
3534
     *   this phase, it's safe to keep a cached copy.  
3535
     */
3536
    getDobjResolver(issuingActor, targetActor, reset)
3537
    {
3538
        /* create a new resolver if we don't already have one cached */
3539
        if (dobjResolver_ == nil)
3540
            dobjResolver_ = createDobjResolver(issuingActor, targetActor);
3541
3542
        /* reset the resolver if desired */
3543
        if (reset)
3544
            dobjResolver_.resetResolver();
3545
3546
        /* return it */
3547
        return dobjResolver_;
3548
    }
3549
3550
    /*
3551
     *   Create a resolver for the direct object.  By default, we are our
3552
     *   own resolver.  Some actions might want to override this to create
3553
     *   and return a specialized resolver instance if special resolution
3554
     *   rules are needed.  
3555
     */
3556
    createDobjResolver(issuingActor, targetActor)
3557
    {
3558
        /* initialize myself as a resolver */
3559
        initResolver(issuingActor, targetActor);
3560
3561
        /* return myself */
3562
        return self;
3563
    }
3564
3565
    /*
3566
     *   Does this action allow "all" to be used in noun phrases?  By
3567
     *   default, we allow it or not according to a gameMain property.
3568
     *   
3569
     *   Note that the inventory management verbs (TAKE, TAKE FROM, DROP,
3570
     *   PUT IN, PUT ON) override this to allow "all" to be used, so
3571
     *   disallowing "all" here (or via gameMain) won't disable "all" for
3572
     *   those verbs.  
3573
     */
3574
    actionAllowsAll = (gameMain.allVerbsAllowAll)
3575
3576
    /*
3577
     *   Resolve 'all' for the direct object, given a list of everything
3578
     *   in scope.  By default, we'll simply return everything in scope;
3579
     *   some actions might want to override this to return a more
3580
     *   specific list of objects suitable for 'all'.  
3581
     */
3582
    getAllDobj(actor, scopeList)
3583
    {
3584
        return scopeList;
3585
    }
3586
3587
    /* filter an ambiguous direct object noun phrase */
3588
    filterAmbiguousDobj(lst, requiredNum, np)
3589
    {
3590
        /* filter using the direct object verifier method */
3591
        return filterAmbiguousWithVerify(lst, requiredNum, verDobjProp,
3592
                                         preCondDobjProp, remapDobjProp,
3593
                                         DirectObject, np);
3594
    }
3595
3596
    /* filter a plural phrase */
3597
    filterPluralDobj(lst, np)
3598
    {
3599
        /* filter using the direct object verifier method */
3600
        return filterPluralWithVerify(lst, verDobjProp, preCondDobjProp,
3601
                                      remapDobjProp, DirectObject, np);
3602
    }
3603
3604
    /* get the default direct object */
3605
    getDefaultDobj(np, resolver)
3606
    {
3607
        /* get a default direct object using the verify method */
3608
        return getDefaultWithVerify(resolver, verDobjProp, preCondDobjProp,
3609
                                    remapDobjProp, DirectObject, np);
3610
    }
3611
3612
    /* get the current direct object of the command */
3613
    getDobj() { return dobjCur_; }
3614
3615
    /* get the full ResolveInfo associated with the current direct object */
3616
    getDobjInfo() { return dobjInfoCur_; }
3617
3618
    /* get the object resolution flags for the direct object */
3619
    getDobjFlags() { return dobjInfoCur_ != nil ? dobjInfoCur_.flags_ : 0; }
3620
3621
    /* get the number of direct objects */
3622
    getDobjCount() { return dobjList_ != nil ? dobjList_.length() : 0; }
3623
3624
    /* the predicate must assign the direct object production tree here */
3625
    dobjMatch = nil
3626
3627
    /* my resolved list of direct objects */
3628
    dobjList_ = []
3629
3630
    /* 
3631
     *   The resolved direct object on which we're currently executing the
3632
     *   command.  To execute the command, we iterate through the direct
3633
     *   object list, calling the execution sequence for each object in
3634
     *   the list.  We set this to the current object in each iteration.  
3635
     */
3636
    dobjCur_ = nil
3637
3638
    /* the full ResolveInfo associated with dobjCur_ */
3639
    dobjInfoCur_ = nil
3640
3641
    /* my cached direct object resolver */
3642
    dobjResolver_ = nil
3643
3644
    /* -------------------------------------------------------------------- */
3645
    /*
3646
     *   Resolver interface implementation - for the moment, we don't need
3647
     *   any special definitions here, since the basic Resolver
3648
     *   implementation (which we inherit) is suitable for a single-object
3649
     *   action.  
3650
     */
3651
3652
    /* -------------------------------------------------------------------- */
3653
    /*
3654
     *   private Resolver implementation details 
3655
     */
3656
3657
    /*
3658
     *   Initialize me as a resolver.  
3659
     */
3660
    initResolver(issuingActor, targetActor)
3661
    {
3662
        /* remember the actors */
3663
        issuer_ = issuingActor;
3664
        actor_ = targetActor;
3665
3666
        /* I'm the action as well as the resolver */
3667
        action_ = self;
3668
3669
        /* cache the actor's default scope list */
3670
        cacheScopeList();
3671
    }
3672
3673
    /* issuing actor */
3674
    issuer_ = nil
3675
3676
    /* target actor */
3677
    actor_ = nil
3678
3679
    /*
3680
     *   By default, our direct object plays the direct object role in
3681
     *   generated messages.  Subclasses can override this if the resolved
3682
     *   object is to play a different role.  Note that this only affects
3683
     *   generated messages; for parsing purposes, our object is always in
3684
     *   the DirectObject role.  
3685
     */
3686
    whichMessageObject = DirectObject
3687
;
3688
3689
3690
/* ------------------------------------------------------------------------ */
3691
/*
3692
 *   "Tentative" noun resolver results gather.  This type of results
3693
 *   gatherer is used to perform a tentative pre-resolution of an object
3694
 *   of a multi-object action.
3695
 *   
3696
 *   Consider what happens when we resolve a two-object action, such as
3697
 *   "put <dobj> in <iobj>".  Since we have two objects, we obviously must
3698
 *   resolve one object or the other first; but this means that we must
3699
 *   resolve one object with no knowledge of the resolution of the other
3700
 *   object.  This often makes it very difficult to resolve that first
3701
 *   object intelligently, because we'd really like to know something
3702
 *   about the other object.  For example, if we first resolve the iobj of
3703
 *   "put <dobj> in <iobj>", it would be nice to know which dobj we're
3704
 *   talking about, since we could reduce the likelihood that the iobj is
3705
 *   the dobj's present container.
3706
 *   
3707
 *   Tentative resolution addresses this need by giving us some
3708
 *   information about a later-resolved object while resolving an
3709
 *   earlier-resolved object, even though we obviously can't have fully
3710
 *   resolved the later-resolved object.  In tentative resolution, we
3711
 *   perform the resolution of the later-resolved object, completely in
3712
 *   the dark about the earlier-resolved object(s), and come up with as
3713
 *   much information as we can.  The important thing about this stage of
3714
 *   resolution is that we don't ask any interactive questions and we
3715
 *   don't count anything for ranking purposes - we simply do the best we
3716
 *   can and note the results, leaving any ranking or interaction for the
3717
 *   true resolution phase that we'll perform later.  
3718
 */
3719
class TentativeResolveResults: ResolveResults
3720
    construct(target, issuer) { setActors(target, issuer); }
3721
3722
    /* 
3723
     *   ignore most resolution problems, since this is only a tentative
3724
     *   resolution pass 
3725
     */
3726
    noMatch(action, txt) { }
3727
    noVocabMatch(action, txt) { }
3728
    noMatchForAll() { }
3729
    noteEmptyBut() { }
3730
    noMatchForAllBut() { }
3731
    noMatchForListBut() { }
3732
    noMatchForPronoun(typ, txt) { }
3733
    reflexiveNotAllowed(typ, txt) { }
3734
    wrongReflexive(typ, txt) { }
3735
    noMatchForPossessive(owner, txt) { }
3736
    noMatchForLocation(loc, txt) { }
3737
    noteBadPrep() { }
3738
    nothingInLocation(loc) { }
3739
    unknownNounPhrase(match, resolver) { return []; }
3740
    noteLiteral(txt) { }
3741
    emptyNounPhrase(resolver) { return []; }
3742
    zeroQuantity(txt) { }
3743
    insufficientQuantity(txt, matchList, requiredNum) { }
3744
    uniqueObjectRequired(txt, matchList) { }
3745
    noteAdjEnding() { }
3746
    noteIndefinite() { }
3747
    noteMiscWordList(txt) { }
3748
    notePronoun() { }
3749
    noteMatches(matchList) { }
3750
    incCommandCount() { }
3751
    noteActorSpecified() { }
3752
    noteNounSlots(cnt) { }
3753
    noteWeakPhrasing(level) { }
3754
    allowActionRemapping = nil
3755
3756
    /* 
3757
     *   during the tentative phase, keep all equivalents - we don't want
3758
     *   to make any arbitrary choices among equivalents during this
3759
     *   phase, because doing so could improperly force a choice among
3760
     *   otherwise ambiguous resolutions to the other phrase 
3761
     */
3762
    allowEquivalentFiltering = nil
3763
3764
    /* 
3765
     *   for ambiguous results, don't attempt to narrow things down - just
3766
     *   keep the entire list 
3767
     */
3768
    ambiguousNounPhrase(keeper, asker, txt,
3769
                        matchList, fullMatchList, scopeList,
3770
                        requiredNum, resolver)
3771
    {
3772
        return matchList;
3773
    }
3774
3775
    /* 
3776
     *   no interaction is allowed, so return nothing if we need to ask
3777
     *   for a missing object 
3778
     */
3779
    askMissingObject(asker, resolver, responseProd)
3780
    {
3781
        /* note that we have a missing noun phrase */
3782
        npMissing = true;
3783
3784
        /* return nothing */
3785
        return nil;
3786
    }
3787
3788
    /* 
3789
     *   no interaction is allowed, so return no tokens if we need to ask
3790
     *   for a literal 
3791
     */
3792
    askMissingLiteral(action, which) { return []; }
3793
3794
    /* no interaction is allowed during tentative resolution */
3795
    canResolveInteractively() { return nil; }
3796
3797
    /* 
3798
     *   flag: the noun phrase we're resolving is a missing noun phrase,
3799
     *   which means that we'll ask for it to be filled in when we get
3800
     *   around to resolving it for real 
3801
     */
3802
    npMissing = nil
3803
;
3804
3805
/*
3806
 *   A dummy object that we use for the *tentative* resolution of a noun
3807
 *   phrase when the noun phrase doesn't match anything.  This lets us
3808
 *   distinguish cases where we have a noun phrase that has an error from a
3809
 *   noun phrase that's simply missing.  
3810
 */
3811
dummyTentativeObject: object
3812
;
3813
3814
dummyTentativeInfo: ResolveInfo
3815
    obj_ = dummyTentativeObject
3816
    flags_ = 0
3817
;
3818
3819
/* ------------------------------------------------------------------------ */
3820
/* 
3821
 *   Transitive-with-indirect Action class - this is an action that takes
3822
 *   both a direct and indirect object.  We subclass the basic one-object
3823
 *   action to add the indirect object handling.  
3824
 */
3825
class TIAction: TAction
3826
    resetAction()
3827
    {
3828
        /* inherit defaulting handling */
3829
        inherited();
3830
3831
        /* discard our cached iobj resolver */
3832
        iobjResolver_ = nil;
3833
    }
3834
    
3835
    /*
3836
     *   Retry a single-object action as a two-object action.  We'll treat
3837
     *   the original action's direct object list as our direct object
3838
     *   list, and obtain an indirect object using the normal means (first
3839
     *   looking for a default, then prompting the player if we can't find
3840
     *   a suitable default).  'orig' is the original single-object action.
3841
     *   
3842
     *   This routine terminates with 'exit' if it doesn't throw some
3843
     *   other error.  
3844
     */
3845
    retryWithMissingIobj(orig, asker)
3846
    {
3847
        /* resolve and execute the replacement action */
3848
        resolveAndReplaceAction(createForMissingIobj(orig, asker));
3849
    }
3850
3851
    /*
3852
     *   Retry an action as a two-object action with an ambiguous indirect
3853
     *   object.  We'll ask which of the given possible objects is
3854
     *   intended.  
3855
     */
3856
    retryWithAmbiguousIobj(orig, objs, asker, objPhrase)
3857
    {
3858
        local action;
3859
        local resolver;
3860
        
3861
        /* create a missing-indirect-object replacement action */
3862
        action = createForMissingIobj(orig, asker);
3863
3864
        /* reduce the object list to the objects in scope */
3865
        resolver = action.getIobjResolver(gIssuingActor, gActor, true);
3866
        objs = objs.subset({x: resolver.objInScope(x)});
3867
3868
        /* plug in the ambiguous indirect object list */
3869
        action.iobjMatch = new PreResolvedAmbigProd(objs, asker, objPhrase);
3870
        
3871
        /* resolve and execute the replacement action */
3872
        resolveAndReplaceAction(action);
3873
    }
3874
3875
    /*
3876
     *   Test to see if askForIobj() would find a default indirect object.
3877
     *   Returns true if there's a default, nil if not.  If this returns
3878
     *   true, then askForIobj() will simply take the default and proceed;
3879
     *   otherwise, it will have to actually ask the user for the missing
3880
     *   information.  
3881
     */
3882
    testRetryDefaultIobj(orig)
3883
    {
3884
        local action;
3885
        local def;
3886
        
3887
        /* create the new action for checking for an indirect object */
3888
        action = createForMissingIobj(orig, ResolveAsker);
3889
3890
        /* get the default iobj */
3891
        def = action.getDefaultIobj(
3892
            action.iobjMatch,
3893
            action.getIobjResolver(gIssuingActor, gActor, nil));
3894
3895
        /* if there's exactly one result, then we have a default */
3896
        return (def != nil && def.length() == 1);
3897
    }
3898
3899
    /*
3900
     *   Create an instance of this action for retrying a given
3901
     *   single-object action with a missing indirect object.  
3902
     */
3903
    createForMissingIobj(orig, asker)
3904
    {
3905
        local action;
3906
        
3907
        /* create the new action based on the original action */
3908
        action = createForRetry(orig);
3909
3910
        /* use an empty noun phrase for the new action's indirect object */
3911
        action.iobjMatch = new EmptyNounPhraseProd();
3912
3913
        /* set our custom response production and ResolveAsker */
3914
        action.iobjMatch.setPrompt(action.askIobjResponseProd, asker);
3915
3916
        /* copy what we've resolved so far */
3917
        action.initForMissingIobj(orig);
3918
3919
        /* return the action */
3920
        return action;
3921
    }
3922
3923
    /*
3924
     *   Initialize the action for retrying with a missing direct object.
3925
     *   
3926
     *   If we're trying a TIAction, we can only be coming from a TAction
3927
     *   (since that's the only kind of original action that can turn into
3928
     *   a two-object, at least in the base library).  That means the
3929
     *   original action already has a direct object.  Now, since we're
3930
     *   asking for a MISSING direct object, the only possibility is that
3931
     *   the original action's direct object is our INDIRECT object.  For
3932
     *   example: SWEEP WITH BROOM is turning into SWEEP <what> WITH
3933
     *   BROOM. 
3934
     */
3935
    initForMissingDobj(orig)
3936
    {
3937
        local origDobj = orig.getDobj();
3938
        
3939
        /* 
3940
         *   Set the indirect object in the new action to the direct object
3941
         *   from the original action.  If there's no individual direct
3942
         *   object yet, we must be retrying the overall command, before we
3943
         *   started iterating through the individual dobjs, so copy the
3944
         *   entire dobj list from the original.  
3945
         */
3946
        iobjMatch = new PreResolvedProd(origDobj != nil
3947
                                        ? origDobj : orig.dobjList_ );
3948
    }
3949
3950
    /*
3951
     *   Initialize the action for retrying with a missing indirect object.
3952
     *   
3953
     *   We can only be coming from a TAction, so the TAction will have a
3954
     *   direct object already.  Simply copy that as our own direct
3955
     *   object.  For example: UNLOCK DOOR is turning into UNLOCK DOOR
3956
     *   WITH <what>.  
3957
     */
3958
    initForMissingIobj(orig)
3959
    {
3960
        local origDobj = orig.getDobj();
3961
        
3962
        /* 
3963
         *   Copy the direct object from the original.  If there's no
3964
         *   individual direct object yet, we must be retrying the overall
3965
         *   command, before we started iterating through the individual
3966
         *   dobjs, so copy the entire dobj list.  
3967
         */
3968
        dobjMatch = new PreResolvedProd(origDobj != nil
3969
                                        ? origDobj : orig.dobjList_);
3970
    }
3971
3972
    /*
3973
     *   The root production to use to parse missing indirect object
3974
     *   responses.  By default, this is singleNoun, but individual
3975
     *   actions can override this as appropriate.
3976
     *   
3977
     *   Note that language modules might want to override this to allow
3978
     *   for special responses.  For example, in English, most verbs will
3979
     *   want to override this with a specialized production that allows
3980
     *   the appropriate preposition in the response.  
3981
     */
3982
    askIobjResponseProd = singleNoun
3983
3984
    /*
3985
     *   Resolution order - returns DirectObject or IndirectObject to
3986
     *   indicate which noun phrase to resolve first in resolveNouns().
3987
     *   By default, we'll resolve the indirect object first, but
3988
     *   individual actions can override this to resolve in a non-default
3989
     *   order.  
3990
     */
3991
    resolveFirst = IndirectObject
3992
3993
    /*
3994
     *   Empty phrase resolution order.  This is similar to the standard
3995
     *   resolution order (resolveFirst), but is used only when both the
3996
     *   direct and indirect objects are empty phrases.
3997
     *   
3998
     *   When both phrases are empty, we will either use a default or
3999
     *   prompt interactively for the missing phrase.  In most cases, it
4000
     *   is desirable to prompt interactively for a missing direct object
4001
     *   first, regardless of the usual resolution order.  
4002
     */
4003
    resolveFirstEmpty = DirectObject
4004
    
4005
    /*
4006
     *   Determine which object to call first for action processing.  By
4007
     *   default, we execute in the same order as we resolve, but this can
4008
     *   be overridden if necessary.  
4009
     */
4010
    execFirst = (resolveFirst)
4011
4012
    /*
4013
     *   Can the indirect object potentially resolve to the given
4014
     *   simulation object?  This only determines if the object is a
4015
     *   *syntactic* match, meaning that it can match at a vocabulary and
4016
     *   grammar level.  This doesn't test it for logicalness or check that
4017
     *   it's an otherwise valid resolution.  
4018
     */
4019
    canIobjResolveTo(obj)
4020
    {
4021
        /* check our iobj match tree to see if it can resolve to 'obj' */
4022
        return iobjMatch.canResolveTo(
4023
            obj, self, issuer_, actor_, IndirectObject);
4024
    }
4025
4026
    /*
4027
     *   resolve our noun phrases to objects 
4028
     */
4029
    resolveNouns(issuingActor, targetActor, results)
4030
    {
4031
        local first;
4032
        local objMatch1, objMatch2;
4033
        local objList1, objList2;
4034
        local getResolver1, getResolver2;
4035
        local objCur1;
4036
        local remapProp;
4037
        local reResolveFirst;
4038
4039
        /* 
4040
         *   note in the results that we have two noun slots (the direct
4041
         *   and indirect objects) 
4042
         */
4043
        results.noteNounSlots(2);
4044
4045
        /* we have no current known direct or indirect object yet */
4046
        dobjCur_ = nil;
4047
        iobjCur_ = nil;
4048
4049
        /* 
4050
         *   presume we won't have to re-resolve the first noun phrase,
4051
         *   and clear out our record of anaphor bindings 
4052
         */
4053
        reResolveFirst = nil;
4054
        needAnaphoricBinding_ = nil;
4055
        lastObjList_ = nil;
4056
4057
        /* 
4058
         *   Determine which object we want to resolve first.  If both
4059
         *   phrases are empty, use the special all-empty ordering;
4060
         *   otherwise, use the standard ordering for this verb.  
4061
         */
4062
        if (dobjMatch.isEmptyPhrase && iobjMatch.isEmptyPhrase)
4063
        {
4064
            /* both phrases are empty - use the all-empty ordering */
4065
            first = resolveFirstEmpty;
4066
        }
4067
        else
4068
        {
4069
            /* 
4070
             *   we have at least one non-empty phrase, so use our
4071
             *   standard ordering 
4072
             */
4073
            first = resolveFirst;
4074
        }
4075
4076
        /*
4077
         *   The resolution process is symmetrical for the two possible
4078
         *   resolution orders (direct object first or indirect object
4079
         *   first); all we need to do is to set up the parameters we'll
4080
         *   need according to which order we're using.
4081
         *   
4082
         *   This parameterized approach makes the code further below a
4083
         *   little mind-boggling, because it's using so much indirection.
4084
         *   But the alternative is worse: the alternative is to
4085
         *   essentially make two copies of the code below (one for the
4086
         *   dobj-first case and one for the iobj-first case), which is
4087
         *   prone to maintenance problems because of the need to keep the
4088
         *   two copies synchronized when making any future changes.  
4089
         */
4090
        if (first == DirectObject)
4091
        {
4092
            objMatch1 = dobjMatch;
4093
            objMatch2 = iobjMatch;
4094
            objList1 = &dobjList_;
4095
            objList2 = &iobjList_;
4096
            getResolver1 = &getDobjResolver;
4097
            getResolver2 = &getIobjResolver;
4098
            objCur1 = &dobjCur_;
4099
            remapProp = remapDobjProp;
4100
        }
4101
        else
4102
        {
4103
            objMatch1 = iobjMatch;
4104
            objMatch2 = dobjMatch;
4105
            objList1 = &iobjList_;
4106
            objList2 = &dobjList_;
4107
            getResolver1 = &getIobjResolver;
4108
            getResolver2 = &getDobjResolver;
4109
            objCur1 = &iobjCur_;
4110
            remapProp = remapIobjProp;
4111
        }
4112
4113
        /* 
4114
         *   Get the unfiltered second-resolved object list - this will
4115
         *   give the first-resolved object resolver access to some
4116
         *   minimal information about the possible second-resolved object
4117
         *   or objects.
4118
         *   
4119
         *   Note that we're not *really* resolving the second object here
4120
         *   - it is the second-resolved object, after all.  What we're
4121
         *   doing is figuring out the *potential* set of objects that
4122
         *   could resolve to the second phrase, with minimal
4123
         *   disambiguation, so that the first object resolver is not
4124
         *   totally in the dark about the second object's potential
4125
         *   resolution.  
4126
         */
4127
        initTentative(issuingActor, targetActor, first);
4128
            
4129
        /* resolve the first-resolved noun phrase */
4130
        self.(objList1) = objMatch1.resolveNouns(
4131
            self.(getResolver1)(issuingActor, targetActor, true), results);
4132
4133
        /* 
4134
         *   If the first-resolved noun phrase asked for an anaphoric
4135
         *   binding, we'll need to go back and re-resolve that noun
4136
         *   phrase after we resolve our other noun phrase, since the
4137
         *   first-resolved phrase refers to the second-resolved phrase. 
4138
         */
4139
        reResolveFirst = needAnaphoricBinding_;
4140
4141
        /* 
4142
         *   if the second-resolved phrase uses an anaphoric pronoun, the
4143
         *   anaphor can be taken as referring to the first-resolved
4144
         *   phrase's results 
4145
         */
4146
        lastObjList_ = self.(objList1);
4147
4148
        /* 
4149
         *   if the first-resolved phrase resolves to just one object, we
4150
         *   can immediately set the current resolved object, so that we
4151
         *   can use it while resolving the second-resolved object list 
4152
         */
4153
        if (self.(objList1).length() == 1)
4154
        {
4155
            /* set the current first-resolved object */
4156
            self.(objCur1) = self.(objList1)[1].obj_;
4157
4158
            /* if remapping is allowed at this point, look for a remapping */
4159
            if (results.allowActionRemapping)
4160
            {
4161
                withParserGlobals(issuingActor, targetActor, self,
4162
                                  new function()
4163
                {
4164
                    local remapInfo;
4165
                    
4166
                    /* check for a remapping */
4167
                    if ((remapInfo = self.(objCur1).(remapProp)) != nil)
4168
                    {
4169
                        /* 
4170
                         *   we have a remapping, so apply it - note that
4171
                         *   we're still in the process of resolving noun
4172
                         *   phrases (since we've only resolved one of our
4173
                         *   two noun phrases so far), so pass 'true' for
4174
                         *   the inResolve parameter 
4175
                         */
4176
                        remapAction(true, first, remapInfo);
4177
                    }
4178
                });
4179
            }
4180
        }
4181
4182
        /* resolve the second-resolved object */
4183
        self.(objList2) = objMatch2.resolveNouns(
4184
            self.(getResolver2)(issuingActor, targetActor, true), results);
4185
4186
        /* 
4187
         *   if we have to re-resolve the first noun phrase due to an
4188
         *   anaphoric pronoun, go back and do that now 
4189
         */
4190
        if (reResolveFirst)
4191
        {
4192
            /* 
4193
             *   use the second-resolved noun phrase as the referent of
4194
             *   the anaphoric pronoun(s) in the first-resolved phrase 
4195
             */
4196
            lastObjList_ = self.(objList2);
4197
4198
            /* re-resolve the first object list */
4199
            self.(objList1) = objMatch1.resolveNouns(
4200
                self.(getResolver1)(issuingActor, targetActor, true),
4201
                results);
4202
        }
4203
    }
4204
4205
    /* we have a direct and indirect object */
4206
    predicateNounPhrases = [&dobjMatch, &iobjMatch]
4207
4208
    /* get an object role */
4209
    getRoleFromIndex(idx)
4210
    {
4211
        /* 
4212
         *   the second object is always our indirect object; for other
4213
         *   roles, defer to the inherited behavior 
4214
         */
4215
        return (idx == 2 ? IndirectObject : inherited(idx));
4216
    }
4217
4218
    /* get the resolved object in a given role */
4219
    getObjectForRole(role)
4220
    {
4221
        /* return the indirect object if requested; otherwise inherit */
4222
        return (role == IndirectObject ? getIobj() : inherited(role));
4223
    }
4224
4225
    /* get the ResolveInfo for the given resolved object */
4226
    getResolveInfo(obj, oldRole)
4227
    {
4228
        local info;
4229
        
4230
        /* scan our resolved direct object list for the given object */
4231
        if (dobjList_ != nil)
4232
            info = dobjList_.valWhich({x: x.obj_ == obj});
4233
4234
        /* if we didn't find it there, try the indirect object list */
4235
        if (info == nil && iobjList_ != nil)
4236
            info = iobjList_.valWhich({x: x.obj_ == obj});
4237
4238
        /* if we didn't find one, create one from scratch */
4239
        if (info == nil)
4240
        {
4241
            local lst;
4242
            local flags;
4243
4244
            /* get the list for the old role */
4245
            lst = (oldRole == DirectObject ? dobjList_ : iobjList_);
4246
4247
            /* get the flags from the first element of the old list */
4248
            flags = (lst.length() > 0 ? lst[1].flags_ : 0);
4249
4250
            /* create a ResolveInfo to wrap the object */
4251
            info = new ResolveInfo(obj, flags);
4252
        }
4253
4254
        /* return what we found (or created) */
4255
        return info;
4256
    }
4257
4258
    /* get the OtherObject role for the given role */
4259
    getOtherObjectRole(role)
4260
    {
4261
        /* the complementary roles are DirectObject and IndirectObject */
4262
        return (role == IndirectObject ? DirectObject : IndirectObject);
4263
    }
4264
4265
    /* get the match tree for the noun phrase in the given role */
4266
    getMatchForRole(role)
4267
    {
4268
        /* return the indirect object match tree if requested; else inherit */
4269
        return (role == IndirectObject ? iobjMatch : inherited(role));
4270
    }
4271
4272
    /* get the 'verify' property for a given object role */
4273
    getVerifyPropForRole(role)
4274
    {
4275
        return (role == IndirectObject ? verIobjProp : inherited(role));
4276
    }
4277
4278
    /* get the 'preCond' property for a given object role */
4279
    getPreCondPropForRole(role)
4280
    {
4281
        return (role == IndirectObject ? preCondIobjProp : inherited(role));
4282
    }
4283
4284
    /* get the 'remap' property for a given object role */
4285
    getRemapPropForRole(role)
4286
    {
4287
        return (role == IndirectObject ? remapIobjProp : inherited(role));
4288
    }
4289
4290
    /* get the list of resolved indirect objects */
4291
    getResolvedIobjList()
4292
    {
4293
        /* 
4294
         *   if we have an indirect object list, return the objects from
4295
         *   it; otherwise, return an empty list 
4296
         */
4297
        return (iobjList_ == nil ? nil : iobjList_.mapAll({x: x.obj_}));
4298
    }
4299
4300
    /*
4301
     *   Manually set the resolved objects.  We'll set our direct and
4302
     *   indirect objects.  
4303
     */
4304
    setResolvedObjects(dobj, iobj)
4305
    {
4306
        /* inherit the base class handling to set the direct object */
4307
        inherited(dobj);
4308
4309
        /* set the resolved iobj */
4310
        setResolvedIobj(iobj);
4311
    }
4312
4313
    /* set a resolved iobj */
4314
    setResolvedIobj(iobj)
4315
    {
4316
        /* build a pre-resolved production for the indirect object */
4317
        iobjMatch = new PreResolvedProd(iobj);
4318
4319
        /* set the resolved indirect object */
4320
        iobjList_ = makeResolveInfoList(iobj);
4321
4322
        /* set the current indirect object as well */
4323
        iobjCur_ = (iobjList_.length() > 0 ? iobjList_[1].obj_ : nil);
4324
        iobjInfoCur_ = (iobjList_.length() > 0 ? iobjList_[1] : nil);
4325
    }
4326
4327
    /* manually set the unresolved object noun phrase match trees */
4328
    setObjectMatches(dobj, iobj)
4329
    {
4330
        /* inherit default handling */
4331
        inherited(dobj);
4332
4333
        /* 
4334
         *   if the iobj is a ResolveInfo, set it as a resolved object;
4335
         *   otherwise set it as an unresolved match tree 
4336
         */
4337
        if (iobj.ofKind(ResolveInfo))
4338
            setResolvedIobj(iobj);
4339
        else
4340
            iobjMatch = iobj;
4341
    }
4342
4343
    /*
4344
     *   Get the anaphoric binding for the noun phrase we're currently
4345
     *   resolving. 
4346
     */
4347
    getAnaphoricBinding(typ)
4348
    {
4349
        /* if we've resolved a prior noun phrase, return it */
4350
        if (lastObjList_ != nil)
4351
            return lastObjList_;
4352
4353
        /* 
4354
         *   we don't have a prior noun phrase - make a note that we have
4355
         *   to come back and re-resolve the current noun phrase 
4356
         */
4357
        needAnaphoricBinding_ = true;
4358
4359
        /* 
4360
         *   return an empty list to indicate that the anaphor is
4361
         *   acceptable in this context but has no binding yet 
4362
         */
4363
        return [];
4364
    }
4365
4366
    /*
4367
     *   The last object list we resolved.  We keep track of this so that
4368
     *   we can provide it as the anaphoric binding, if an anaphor binding
4369
     *   is requested.  
4370
     */
4371
    lastObjList_ = nil
4372
4373
    /*
4374
     *   Flag: we have been asked for an anaphoric binding, but we don't
4375
     *   have a binding available.  We'll check this after resolving the
4376
     *   first-resolved noun phrase so that we can go back and re-resolve
4377
     *   it again after resolving the other noun phrase.  
4378
     */
4379
    needAnaphoricBinding_ = nil
4380
4381
    /* check that the resolved objects are in scope */
4382
    resolvedObjectsInScope()
4383
    {
4384
        /* 
4385
         *   check the indirect object, and inherit the base class handling
4386
         *   to check the direct object 
4387
         */
4388
        return (inherited()
4389
                && (getIobjResolver(gIssuingActor, gActor, true)
4390
                    .objInScope(iobjList_[1].obj_)));
4391
    }
4392
4393
    /* 
4394
     *   get our indirect object resolver, or create one if we haven't
4395
     *   already cached one 
4396
     */
4397
    getIobjResolver(issuingActor, targetActor, reset)
4398
    {
4399
        /* if we don't already have one cached, create a new one */
4400
        if (iobjResolver_ == nil)
4401
            iobjResolver_ = createIobjResolver(issuingActor, targetActor);
4402
4403
        /* reset the resolver if desired */
4404
        if (reset)
4405
            iobjResolver_.resetResolver();
4406
        
4407
        /* return the cached resolver */
4408
        return iobjResolver_;
4409
    }
4410
4411
    /*
4412
     *   Create our indirect object resolver.  By default, we'll use a
4413
     *   basic indirect object resolver.
4414
     */
4415
    createIobjResolver(issuingActor, targetActor)
4416
    {
4417
        /* create and return a new basic indirect object resolver */
4418
        return new IobjResolver(self, issuingActor, targetActor);
4419
    }
4420
    
4421
    /* 
4422
     *   Resolve 'all' for the indirect object.  By default, we'll return
4423
     *   everything in the scope list.  
4424
     */
4425
    getAllIobj(actor, scopeList)
4426
    {
4427
        return scopeList;
4428
    }
4429
4430
    /* filter an ambiguous indirect object noun phrase */
4431
    filterAmbiguousIobj(lst, requiredNum, np)
4432
    {
4433
        /* filter using the indirect object verifier method */
4434
        return filterAmbiguousWithVerify(lst, requiredNum, verIobjProp,
4435
                                         preCondIobjProp, remapIobjProp,
4436
                                         IndirectObject, np);
4437
    }
4438
4439
    /* filter a plural phrase */
4440
    filterPluralIobj(lst, np)
4441
    {
4442
        /* filter using the direct object verifier method */
4443
        return filterPluralWithVerify(lst, verIobjProp, preCondIobjProp,
4444
                                      remapIobjProp, IndirectObject, np);
4445
    }
4446
4447
    /* get the default indirect object */
4448
    getDefaultIobj(np, resolver)
4449
    {
4450
        /* get a default indirect object using the verify method */
4451
        return getDefaultWithVerify(resolver, verIobjProp, preCondIobjProp,
4452
                                    remapIobjProp, IndirectObject, np);
4453
    }
4454
4455
    /*
4456
     *   Execute the action.  We'll run through the execution sequence
4457
     *   once for each resolved object in our direct or indirect object
4458
     *   list, depending on which one is the list and which one is the
4459
     *   singleton.  
4460
     */
4461
    doActionMain()
4462
    {
4463
        local lst;
4464
        local preAnnouncedDobj;
4465
        local preAnnouncedIobj;
4466
        
4467
        /* 
4468
         *   Get the list of resolved objects for the multiple object.  If
4469
         *   neither has multiple objects, it doesn't matter which is
4470
         *   iterated, since we'll just do the command once anyway.  
4471
         */
4472
        lst = (iobjList_.length() > 1 ? iobjList_ : dobjList_);
4473
4474
        /* 
4475
         *   Set the pronoun antecedents, using the game-specific pronoun
4476
         *   setter.  Don't set an antecedent for a nested command.
4477
         */
4478
        if (parentAction == nil)
4479
        {
4480
           /* 
4481
            *   Set both direct and indirect objects as potential
4482
            *   antecedents.  Rather than trying to figure out right now
4483
            *   which one we might want to refer to in the future, remember
4484
            *   both - we'll decide which one is the logical antecedent
4485
            *   when we find a pronoun to resolve in a future command.  
4486
            */ 
4487
           gActor.setPronounMulti(dobjList_, iobjList_); 
4488
4489
            /*
4490
             *   If one or the other object phrase was specified in the
4491
             *   input as a pronoun, keep the meaning of that pronoun the
4492
             *   same, overriding whatever we just did.  Note that the
4493
             *   order we use here doesn't matter: if a given pronoun
4494
             *   appears in only one of the two lists, then the list where
4495
             *   it's not set has no effect on the pronoun, hence it
4496
             *   doesn't matter which comes first; if a pronoun appears in
4497
             *   both lists, it will have the same value in both lists, so
4498
             *   we'll just do the same thing twice, so, again, order
4499
             *   doesn't matter.  
4500
             */
4501
            setPronounByInput(dobjList_);
4502
            setPronounByInput(iobjList_);
4503
        }
4504
4505
        /* 
4506
         *   pre-announce the non-list object if appropriate - this will
4507
         *   provide a common pre-announcement if we iterate through
4508
         *   several announcements of the main list objects 
4509
         */
4510
        if (lst == dobjList_)
4511
        {
4512
            /* pre-announce the single indirect object if needed */
4513
            preAnnouncedIobj = preAnnounceActionObject(
4514
                iobjList_[1], dobjList_, IndirectObject);
4515
4516
            /* we haven't announced the direct object yet */
4517
            preAnnouncedDobj = nil;
4518
        }
4519
        else
4520
        {
4521
            /* pre-announce the single direct object if needed */
4522
            preAnnouncedDobj = preAnnounceActionObject(
4523
                dobjList_[1], iobjList_, DirectObject);
4524
4525
            /* we haven't announced the indirect object yet */
4526
            preAnnouncedIobj = nil;
4527
        }
4528
4529
        /* we haven't yet canceled the iteration */
4530
        iterationCanceled = nil;
4531
4532
        /* iterate over the resolved list for the multiple object */
4533
        for (local i = 1, local len = lst.length() ;
4534
             i <= len && !iterationCanceled ; ++i)
4535
        {
4536
            local dobjInfo;
4537
            local iobjInfo;
4538
4539
            /* 
4540
             *   make the current list item the direct or indirect object,
4541
             *   as appropriate 
4542
             */
4543
            if (lst == dobjList_)
4544
            {
4545
                /* the direct object is the multiple object */
4546
                dobjInfo = dobjInfoCur_ = lst[i];
4547
                iobjInfo = iobjInfoCur_ = iobjList_[1];
4548
            }
4549
            else
4550
            {
4551
                /* the indirect object is the multiple object */
4552
                dobjInfo = dobjInfoCur_ = dobjList_[1];
4553
                iobjInfo = iobjInfoCur_ = lst[i];
4554
            }
4555
4556
            /* get the current dobj and iobj from the resolve info */
4557
            dobjCur_ = dobjInfo.obj_;
4558
            iobjCur_ = iobjInfo.obj_;
4559
4560
            /* 
4561
             *   if the action was remapped, and we need to announce
4562
             *   anything, announce the entire action 
4563
             */
4564
            if (isRemapped())
4565
            {
4566
                /*
4567
                 *   We were remapped.  The entire phrasing of the new
4568
                 *   action might have changed from what the player typed,
4569
                 *   so it might be nonsensical to show the objects as we
4570
                 *   usually would, as sentence fragments that are meant
4571
                 *   to combine with what the player actually typed.  So,
4572
                 *   instead of showing the usual sentence fragments, show
4573
                 *   the entire phrasing of the command.
4574
                 *   
4575
                 *   Only show the announcement if we have a reason to: we
4576
                 *   have unclear disambiguation in one of the objects, or
4577
                 *   one of the objects is defaulted.
4578
                 *   
4579
                 *   If we don't want to announce the remapped action,
4580
                 *   still consider showing a multi-object announcement,
4581
                 *   if we would normally need to do so.  
4582
                 */
4583
                if (needRemappedAnnouncement(dobjInfo)
4584
                    || needRemappedAnnouncement(iobjInfo))
4585
                {
4586
                    /* show the remapped announcement */
4587
                    gTranscript.announceRemappedAction();
4588
                }
4589
                else
4590
                {
4591
                    /* announce the multiple dobj if necessary */
4592
                    if (!preAnnouncedDobj)
4593
                        maybeAnnounceMultiObject(
4594
                            dobjInfo, dobjList_.length(), DirectObject);
4595
4596
                    /* announce the multiple iobj if necessary */
4597
                    if (!preAnnouncedIobj)
4598
                        maybeAnnounceMultiObject(
4599
                            iobjInfo, iobjList_.length(), IndirectObject);
4600
                }
4601
            }
4602
            else
4603
            {
4604
                /* announce the direct object if appropriate */
4605
                if (!preAnnouncedDobj)
4606
                    announceActionObject(dobjInfo, dobjList_.length(),
4607
                                         DirectObject);
4608
4609
                /* announce the indirect object if appropriate */
4610
                if (!preAnnouncedIobj)
4611
                    announceActionObject(iobjInfo, iobjList_.length(),
4612
                                         IndirectObject);
4613
            }
4614
4615
            /* run the execution sequence for the current direct object */
4616
            doActionOnce();
4617
4618
            /* if we're top-level, count the iteration in the transcript */
4619
            if (parentAction == nil)
4620
                gTranscript.newIter();
4621
        }
4622
    }
4623
4624
    /*
4625
     *   Set the pronoun according to the pronoun type actually used in
4626
     *   the input.  For example, if we said PUT BOX ON IT, we want IT to
4627
     *   continue referring to whatever IT referred to before this command
4628
     *   - we specifically do NOT want IT to refer to the BOX in this
4629
     *   case. 
4630
     */
4631
    setPronounByInput(lst)
4632
    {
4633
        local objs;
4634
        
4635
        /* get the subset of the list that was specified by pronoun */
4636
        lst = lst.subset({x: x.pronounType_ != nil});
4637
4638
        /* 
4639
         *   Get a list of the unique objects in the list.  This will
4640
         *   ensure that we can distinguish THEM from IT AND IT AND IT: if
4641
         *   we have the same pronoun appearing with multiple objects,
4642
         *   we'll know that it's because the pronoun was actually plural
4643
         *   (THEM) rather than a singular pronoun that was repeated (IT
4644
         *   AND IT AND IT). 
4645
         */
4646
        objs = lst.mapAll({x: x.obj_}).getUnique();
4647
4648
        /* 
4649
         *   Now retain one 'lst' element for each 'objs' element.  This
4650
         *   is a bit tricky: we're mapping each element in 'objs' to pick
4651
         *   out one element of 'lst' where the 'lst' element points to
4652
         *   the 'objs' element.  
4653
         */
4654
        lst = objs.mapAll({o: lst.valWhich({l: l.obj_ == o})});
4655
4656
        /* 
4657
         *   Now we can set the pronouns.  Go through the list, and set
4658
         *   each different pronoun type that appears.  Set it to the
4659
         *   subset of the list with that matches that pronoun type, and
4660
         *   then remove that subset and keep iterating to pick up the
4661
         *   remaining pronoun types.  
4662
         */
4663
        while (lst.length() != 0)
4664
        {
4665
            local cur;
4666
            local typ;
4667
4668
            /* on this iteration, handle the first pronoun type in the list */
4669
            typ = lst[1].pronounType_;
4670
            
4671
            /* pick out the subset with the current pronoun type */
4672
            cur = lst.subset({x: x.pronounType_ == typ});
4673
4674
            /* set the current pronoun to the current list */
4675
            gActor.setPronounByType(typ, cur);
4676
4677
            /* remove the subset we just handled from the remaining list */
4678
            lst -= cur;
4679
        }
4680
    }
4681
4682
    /*
4683
     *   Determine if we need to announce this action when the action was
4684
     *   remapped, based on the resolution information for one of our
4685
     *   objects.  We need to announce a remapped action when either
4686
     *   object had unclear disambiguation or was defaulted. 
4687
     */
4688
    needRemappedAnnouncement(info)
4689
    {
4690
        /* 
4691
         *   if it's a defaulted object that hasn't been announced, or it
4692
         *   was unclearly disambiguated, we need an announcement 
4693
         */
4694
        return (((info.flags_ & DefaultObject) != 0
4695
                 && (info.flags_ & AnnouncedDefaultObject) == 0)
4696
                || (info.flags_ & UnclearDisambig) != 0);
4697
    }
4698
4699
    /*
4700
     *   Verify the action.
4701
     */
4702
    verifyAction()
4703
    {
4704
        local result;
4705
        
4706
        /* invoke any general (non-object) pre-condition verifiers */
4707
        result = callVerifyPreCond(nil);
4708
4709
        /* check the direct object */
4710
        result = callVerifyProp(dobjCur_, verDobjProp, preCondDobjProp,
4711
                                remapDobjProp, result, DirectObject);
4712
4713
        /* 
4714
         *   Check the indirect object, combining the results with the
4715
         *   direct object results.  We combine the results for the two
4716
         *   objects because we're simply looking for any reason that we
4717
         *   can't perform the command.  
4718
         */
4719
        result = callVerifyProp(iobjCur_, verIobjProp, preCondIobjProp,
4720
                                remapIobjProp, result, IndirectObject);
4721
4722
        /* 
4723
         *   check that the action method ("doXxx") is defined on at least
4724
         *   one of the objects 
4725
         */
4726
        return verifyHandlersExist(
4727
            [dobjCur_, iobjCur_],
4728
            [verDobjProp, checkDobjProp, actionDobjProp,
4729
            verIobjProp, checkIobjProp, actionIobjProp],
4730
            result);
4731
    }
4732
4733
    /* initialize tentative resolutions for other noun phrases */
4734
    initTentative(issuingActor, targetActor, whichObj)
4735
    {
4736
        local tRes;
4737
        local ti, td;
4738
4739
        /* 
4740
         *   remember the old tentative direct and indirect objects, then
4741
         *   set them to empty lists - this will ensure that we don't
4742
         *   recursively try to get a tentative resolution for the object
4743
         *   we're working on right now, which would cause infinite
4744
         *   recursion 
4745
         */
4746
        td = tentativeDobj_;
4747
        ti = tentativeIobj_;
4748
4749
        /* set them to empty lists to indicate they don't need resolving */
4750
        tentativeDobj_ = [];
4751
        tentativeIobj_ = [];
4752
4753
        /* make sure our built-in dobj resolver is initialized */
4754
        issuer_ = issuingActor;
4755
        actor_ = targetActor;
4756
4757
        /* make sure we set things back when we're done */
4758
        try
4759
        {
4760
            /* initialize the other noun phrase */
4761
            if (whichObj == DirectObject && ti == nil)
4762
            {
4763
                /* tentatively resolve the indirect object */
4764
                tRes = new TentativeResolveResults(targetActor, issuingActor);
4765
                ti = iobjMatch.resolveNouns(
4766
                    getIobjResolver(issuingActor, targetActor, true), tRes);
4767
4768
                /* 
4769
                 *   if the list is empty, and we didn't have a missing
4770
                 *   noun phrase, use a dummy object as the tentative
4771
                 *   resolution - this distinguishes erroneous noun phrases
4772
                 *   from those that are simply missing 
4773
                 */
4774
                if (ti == [] && !tRes.npMissing)
4775
                    ti = [dummyTentativeInfo];
4776
            }
4777
            else if (whichObj == IndirectObject && td == nil)
4778
            {
4779
                /* tentatively resolve the direct object */
4780
                tRes = new TentativeResolveResults(targetActor, issuingActor);
4781
                td = dobjMatch.resolveNouns(
4782
                    getDobjResolver(issuingActor, targetActor, true), tRes);
4783
4784
                /* use a dummy object if appropriate */
4785
                if (td == [] && !tRes.npMissing)
4786
                    td = [dummyTentativeInfo];
4787
            }
4788
        }
4789
        finally
4790
        {
4791
            /* set the original (or updated) tentative lists */
4792
            tentativeDobj_ = td;
4793
            tentativeIobj_ = ti;
4794
        }
4795
    }
4796
4797
    /*
4798
     *   Check for remapping 
4799
     */
4800
    checkRemapping()
4801
    {
4802
        local remapInfo;
4803
        local role;
4804
4805
        /* presume we'll find remapping for the first-resolved object */
4806
        role = resolveFirst;
4807
        
4808
        /* check remapping for each object, in the resolution order */
4809
        if (resolveFirst == DirectObject)
4810
        {
4811
            /* the direct object is resolved first, so try it first */
4812
            if ((remapInfo = dobjCur_.(remapDobjProp)) == nil)
4813
            {
4814
                remapInfo = iobjCur_.(remapIobjProp);
4815
                role = IndirectObject;
4816
            }
4817
        }
4818
        else
4819
        {
4820
            /* the indirect object is resolved first, so remap it first */
4821
            if ((remapInfo = iobjCur_.(remapIobjProp)) == nil)
4822
            {
4823
                remapInfo = dobjCur_.(remapDobjProp);
4824
                role = DirectObject;
4825
            }
4826
        }
4827
4828
        /* if we found a remapping, apply it */
4829
        if (remapInfo != nil)
4830
            remapAction(nil, role, remapInfo);
4831
    }
4832
4833
    /*
4834
     *   Check the command.
4835
     *   
4836
     *   For a two-object action, this first calls the catch-all 'check'
4837
     *   methods (the dobjFor(Default) and dobjFor(All) methods) on the two
4838
     *   objects (indirect object first), then calls the 'check' methods
4839
     *   for this specific action (direct object first).  
4840
     */
4841
    checkAction()
4842
    {
4843
        local defIo, defDo;
4844
                
4845
        try
4846
        {
4847
            /* invoke the catch-all 'check' methods on each object */
4848
            defIo = callCatchAllProp(iobjCur_, checkIobjProp,
4849
                                     &checkIobjDefault, &checkIobjAll);
4850
            defDo = callCatchAllProp(dobjCur_, checkDobjProp,
4851
                                     &checkDobjDefault, &checkDobjAll);
4852
4853
            /* 
4854
             *   invoke the 'check' method on each object, as long as it
4855
             *   overrides any corresponding Default 'check' handler 
4856
             */
4857
            if (!defDo)
4858
                dobjCur_.(checkDobjProp)();
4859
            if (!defIo)
4860
                iobjCur_.(checkIobjProp)();
4861
        }
4862
        catch (ExitSignal es)
4863
        {
4864
            /* mark the action as a failure in the transcript */
4865
            gTranscript.noteFailure();
4866
4867
            /* re-throw the signal */
4868
            throw es;
4869
        }
4870
    }
4871
4872
    /*
4873
     *   Execute the command. 
4874
     */
4875
    execAction()
4876
    {
4877
        local defIo, defDo;
4878
                
4879
        /* invoke the catch-all 'action' method on each object */
4880
        defIo = callCatchAllProp(iobjCur_, actionIobjProp,
4881
                                 &actionIobjDefault, &actionIobjAll);
4882
        defDo = callCatchAllProp(dobjCur_, actionDobjProp,
4883
                                 &actionDobjDefault, &actionDobjAll);
4884
4885
        /* 
4886
         *   Invoke the action method on each object, starting with the
4887
         *   non-list object.  Call these only if the corresponding
4888
         *   default 'action' methods didn't override the verb-specific
4889
         *   methods.  
4890
         */
4891
        if (execFirst == DirectObject)
4892
        {
4893
            if (!defDo)
4894
                dobjCur_.(actionDobjProp)();
4895
            if (!defIo)
4896
                iobjCur_.(actionIobjProp)();
4897
        }
4898
        else
4899
        {
4900
            if (!defIo)
4901
                iobjCur_.(actionIobjProp)();
4902
            if (!defDo)
4903
                dobjCur_.(actionDobjProp)();
4904
        }
4905
    }
4906
4907
    /* get the precondition descriptor list for the action */
4908
    getPreCondDescList()
4909
    {
4910
        /* 
4911
         *   return the inherited preconditions plus the conditions that
4912
         *   apply to the indirect object 
4913
         */
4914
        return inherited()
4915
            + getObjPreCondDescList(iobjCur_, preCondIobjProp, iobjCur_,
4916
                                    IndirectObject);
4917
    }
4918
4919
    /*
4920
     *   Get a message parameter object for the action.  We define 'dobj'
4921
     *   as the direct object and 'iobj' as the indirect object, in
4922
     *   addition to any inherited targets.  
4923
     */
4924
    getMessageParam(objName)
4925
    {
4926
        switch(objName)
4927
        {
4928
        case 'iobj':
4929
            /* return the current indirect object */
4930
            return iobjCur_;
4931
4932
        default:
4933
            /* inherit default handling */
4934
            return inherited(objName);
4935
        }
4936
    }
4937
4938
    /* get the current indirect object being executed */
4939
    getIobj() { return iobjCur_; }
4940
    
4941
    /* get the full ResolveInfo associated with the current indirect object */
4942
    getIobjInfo() { return iobjInfoCur_; }
4943
4944
    /* get the object resolution flags for the indirect object */
4945
    getIobjFlags() { return iobjInfoCur_ != nil ? iobjInfoCur_.flags_ : 0; }
4946
4947
    /* get the number of direct objects */
4948
    getIobjCount() { return iobjList_ != nil ? iobjList_.length() : 0; }
4949
4950
    /*
4951
     *   Get the list of active objects.  We have a direct and indirect
4952
     *   object.  
4953
     */
4954
    getCurrentObjects()
4955
    {
4956
        return [dobjCur_, iobjCur_];
4957
    }
4958
4959
    /* set the current objects */
4960
    setCurrentObjects(lst)
4961
    {
4962
        /* remember the current direct and indirect objects */
4963
        dobjCur_ = lst[1];
4964
        iobjCur_ = lst[2];
4965
4966
        /* we don't have ResolveInfo objects for this call */
4967
        dobjInfoCur_ = iobjInfoCur_ = nil;
4968
    }
4969
4970
    /*
4971
     *   Copy one tentative object list to the other.  This is useful when
4972
     *   an object's verifier for one TIAction wants to forward the call
4973
     *   to the other object verifier for a different TIAction.  
4974
     */
4975
    copyTentativeObjs()
4976
    {
4977
        /* copy whichever tentative list is populated to the other slot */
4978
        if (tentativeDobj_ != nil)
4979
            tentativeDobj_ = tentativeIobj_;
4980
        else
4981
            tentativeIobj_ = tentativeDobj_;
4982
    }
4983
4984
    /*
4985
     *   Get the tentative direct/indirect object resolution lists.  A
4986
     *   tentative list is available for the later-resolved object while
4987
     *   resolving the earlier-resolved object. 
4988
     */
4989
    getTentativeDobj() { return tentativeDobj_; }
4990
    getTentativeIobj() { return tentativeIobj_; }
4991
4992
    /* 
4993
     *   the predicate grammar must assign the indirect object production
4994
     *   tree to iobjMatch 
4995
     */
4996
    iobjMatch = nil
4997
4998
    /* the indirect object list */
4999
    iobjList_ = []
5000
5001
    /* current indirect object being executed */
5002
    iobjCur_ = nil
5003
5004
    /* the full ResolveInfo associated with iobjCur_ */
5005
    iobjInfoCur_ = nil
5006
5007
    /* my cached indirect object resolver */
5008
    iobjResolver_ = nil
5009
5010
    /*
5011
     *   The tentative direct and indirect object lists.  A tentative list
5012
     *   is available for the later-resolved object while resolving the
5013
     *   earlier-resolved object.  
5014
     */
5015
    tentativeDobj_ = nil
5016
    tentativeIobj_ = nil
5017
5018
    /*
5019
     *   Verification and action properties for the indirect object.  By
5020
     *   convention, the verification method for the indirect object of a
5021
     *   two-object action is verIobjXxx; the check method is
5022
     *   checkIobjXxx; and the action method is actionIobjXxx.  
5023
     */
5024
    verIobjProp = nil
5025
    preCondIobjProp = nil
5026
    checkIobjProp = nil
5027
    actionIobjProp = nil
5028
5029
    /*
5030
     *   Action-remap properties for the indirect object.  By convention,
5031
     *   the remapper properties are named remapDobjAction and
5032
     *   remapIobjAction, for the direct and indirect objects,
5033
     *   respectively, where Action is replaced by the root name of the
5034
     *   action.  
5035
     */
5036
    remapIobjProp = nil
5037
;
5038
5039
/* ------------------------------------------------------------------------ */
5040
/*
5041
 *   Common base class for actions involving literal phrases.  This is a
5042
 *   mix-in class that can be combined with Action subclasses to create
5043
 *   specific kinds of literal actions.  
5044
 */
5045
class LiteralActionBase: object
5046
    /* 
5047
     *   Get a message parameter.  We define 'literal' as the text of the
5048
     *   literal phrase, in addition to inherited targets.  
5049
     */
5050
    getMessageParam(objName)
5051
    {
5052
        switch(objName)
5053
        {
5054
        case 'literal':
5055
            /* return the text of the literal phrase */
5056
            return text_;
5057
5058
        default:
5059
            /* inherit default handling */
5060
            return inherited(objName);
5061
        }
5062
    }
5063
5064
    /* manually set the resolved objects */
5065
    setResolvedObjects(txt)
5066
    {
5067
        /* remember the literal text */
5068
        text_ = txt;
5069
    }
5070
5071
    /* manually set the pre-resolved match trees */
5072
    setObjectMatches(lit)
5073
    {
5074
        /* if it's not already a PreResolvedLiteralProd, wrap it */
5075
        if (!lit.ofKind(PreResolvedLiteralProd))
5076
        {
5077
            /* save the literal text */
5078
            text_ = lit;
5079
            
5080
            /* wrap it in a match tree */
5081
            lit = new PreResolvedLiteralProd(lit);
5082
        }
5083
        
5084
        /* note the new literal match tree */
5085
        literalMatch = lit;
5086
    }
5087
5088
    /* get the current literal text */
5089
    getLiteral() { return text_; }
5090
5091
    /* the text of the literal phrase */
5092
    text_ = nil
5093
;
5094
5095
/* ------------------------------------------------------------------------ */
5096
/*
5097
 *   An action with a literal phrase as its only object, such as "say <any
5098
 *   text>".  We'll accept anything as the literal phrase - a number, a
5099
 *   quoted string, or arbitrary words - and treat them all simply as text.
5100
 *   
5101
 *   The grammar rules that produce these actions must set literalMatch to
5102
 *   the literal phrase's match tree.
5103
 *   
5104
 *   Because we don't have any actual resolved objects, we're based on
5105
 *   IAction.  Subclasses that implement particular literal actions should
5106
 *   override execAction() to carry out the action; this method can call
5107
 *   the getLiteral() method of self to get a string giving the literal
5108
 *   text.  
5109
 */
5110
class LiteralAction: LiteralActionBase, IAction
5111
    /*
5112
     *   Resolve objects.  We don't actually have any objects to resolve,
5113
     *   but we do have to get the text for the literal phrase.  
5114
     */
5115
    resolveNouns(issuingActor, targetActor, results)
5116
    {
5117
        /* the literal phrase counts as one noun slot */
5118
        results.noteNounSlots(1);
5119
        
5120
        /* 
5121
         *   "Resolve" our literal phrase.  The literal phrase doesn't
5122
         *   resolve to an object list the way a regular noun phrase would,
5123
         *   but rather just resolves to a text string giving the original
5124
         *   literal contents of the phrase.  
5125
         */
5126
        literalMatch.resolveLiteral(results);
5127
5128
        /* retrieve the text of the phrase, exactly as the player typed it */
5129
        text_ = literalMatch.getLiteralText(results, self, DirectObject);
5130
    }
5131
5132
    /* we have a literal phrase as our only noun phrase */
5133
    predicateNounPhrases = [&literalMatch]
5134
;
5135
5136
/* ------------------------------------------------------------------------ */
5137
/*
5138
 *   An action with a direct object and a literal, such as "turn dial to
5139
 *   <setting>" or "type <string> on keypad".  We'll accept anything as the
5140
 *   literal phrase - a number, a quoted string, or arbitrary words - and
5141
 *   treat them all simply as text.
5142
 *   
5143
 *   The grammar rules that produce these actions must set dobjMatch to the
5144
 *   resolvable object of the command, and must set literalMatch to the
5145
 *   literal phrase's match tree.  Note that we use dobjMatch as the
5146
 *   resolvable object even if the object serves grammatically as the
5147
 *   indirect object - this is a simplification, and the true grammatical
5148
 *   purpose of the object isn't important since there's only one true
5149
 *   object in the command.
5150
 *   
5151
 *   When referring to objects by role (such as in remapTo), callers should
5152
 *   ALWAYS refer to the resolvable object as DirectObject, and the literal
5153
 *   phrase as IndirectObject.
5154
 *   
5155
 *   Each subclass must set the property whichMessageLiteral to the
5156
 *   grammatical role (DirectObject, IndirectObject) the literal phrase
5157
 *   plays for message generation purposes.  This only affects messages; it
5158
 *   doesn't affect anything else; in particular, regardless of the
5159
 *   whichMessageLiteral setting, callers should always refer to the
5160
 *   literal as IndirectObject when calling getObjectForRole() and the
5161
 *   like, and should always call getDobj() to get the resolved version of
5162
 *   the resolvable object phrase.  
5163
 */
5164
class LiteralTAction: LiteralActionBase, TAction
5165
    /*
5166
     *   Resolve objects.  
5167
     */
5168
    resolveNouns(issuingActor, targetActor, results)
5169
    {
5170
        /* 
5171
         *   the literal phrase counts as one noun slot, and the direct
5172
         *   object as another 
5173
         */
5174
        results.noteNounSlots(2);
5175
5176
        /* 
5177
         *   If the literal phrase serves as the direct object in generated
5178
         *   messages, ask for its literal text first, so that we ask for
5179
         *   it interactively first.  Otherwise, get the tentative literal
5180
         *   text, in case it's useful to have in resolving the other
5181
         *   object.  
5182
         */
5183
        if (whichMessageLiteral == DirectObject)
5184
            text_ = literalMatch.getLiteralText(results, self, DirectObject);
5185
        else
5186
            text_ = literalMatch.getTentativeLiteralText();
5187
5188
        /* resolve the direct object */
5189
        dobjList_ = dobjMatch.resolveNouns(
5190
            getDobjResolver(issuingActor, targetActor, true), results);
5191
5192
        /* 
5193
         *   "Resolve" the literal, ignoring the result - we call this so
5194
         *   that the literal can do any scoring it wants to do during
5195
         *   resolution; but since we're treating it literally, it
5196
         *   obviously has no actual resolved value.  
5197
         */
5198
        literalMatch.resolveLiteral(results);
5199
5200
        /* the literal phrase resolves to the text only */
5201
        text_ = literalMatch.getLiteralText(results, self,
5202
                                            whichMessageLiteral);
5203
    }
5204
5205
    /* we have a direct object and a literal phrase */
5206
    predicateNounPhrases = [&dobjMatch, &literalMatch]
5207
5208
    /* get an object role */
5209
    getRoleFromIndex(idx)
5210
    {
5211
        /* 
5212
         *   index 2 is the literal object, which is always in the indirect
5213
         *   object role; for others, inherit the default handling 
5214
         */
5215
        return (idx == 2 ? IndirectObject : inherited(idx));
5216
    }
5217
5218
    /* get the OtherObject role for the given role */
5219
    getOtherObjectRole(role)
5220
    {
5221
        /* the complementary roles are DirectObject and IndirectObject */
5222
        return (role == DirectObject ? IndirectObject : DirectObject);
5223
    }
5224
5225
    /* get the resolved object in a given role */
5226
    getObjectForRole(role)
5227
    {
5228
        /* 
5229
         *   the literal is in the IndirectObject role; inherit the default
5230
         *   for others 
5231
         */
5232
        return (role == IndirectObject ? text_ : inherited(role));
5233
    }
5234
5235
    /* get the match tree for the given role */
5236
    getMatchForRole(role)
5237
    {
5238
        /* 
5239
         *   the literal is in the IndirectObject role; inherit the default
5240
         *   for anything else 
5241
         */
5242
        return (role == IndirectObject ? literalMatch : inherited(role));
5243
    }
5244
5245
    /* manually set the resolved objects */
5246
    setResolvedObjects(dobj, txt)
5247
    {
5248
        /* inherit default TAction handling for the direct object */
5249
        inherited TAction(dobj);
5250
5251
        /* inherit the LiteralActionBase handling for the literal text */
5252
        inherited LiteralActionBase(txt);
5253
    }
5254
5255
    /* manually set the pre-resolved match trees */
5256
    setObjectMatches(dobj, lit)
5257
    {
5258
        /* inherit default TAction handling for the direct object */
5259
        inherited TAction(dobj);
5260
5261
        /* inherit the default LiteralActionBase handling for the literal */
5262
        inherited LiteralActionBase(lit);
5263
    }
5264
5265
    /* 
5266
     *   Get a list of the current objects.  We include only the direct
5267
     *   object here, since the literal text is not a resolved object but
5268
     *   simply literal text. 
5269
     */
5270
    getCurrentObjects() { return [dobjCur_]; }
5271
5272
    /* set the current objects */
5273
    setCurrentObjects(lst)
5274
    {
5275
        dobjCur_ = lst[1];
5276
        dobjInfoCur_ = nil;
5277
    }
5278
5279
    /*
5280
     *   Retry a single-object action as an action taking both an object
5281
     *   and a literal phrase.  We'll treat the original action's direct
5282
     *   object list as our direct object list, and obtain a literal
5283
     *   phrase interactively.
5284
     *   
5285
     *   This routine terminates with 'exit' if it doesn't throw some
5286
     *   other error.  
5287
     */
5288
    retryWithMissingLiteral(orig)
5289
    {
5290
        local action;
5291
        
5292
        /* create the new action based on the original action */
5293
        action = createForRetry(orig);
5294
5295
        /* use an empty literal phrase for the new action */
5296
        action.literalMatch = new EmptyLiteralPhraseProd();
5297
5298
        /* initialize for the missing literal phrase */
5299
        action.initForMissingLiteral(orig);
5300
5301
        /* resolve and execute the replacement action */
5302
        resolveAndReplaceAction(action);
5303
    }
5304
5305
    /* initialize with a missing direct object phrase */
5306
    initForMissingDobj(orig)
5307
    {
5308
        /*
5309
         *   Since we have a missing direct objet, we must be coming from
5310
         *   a LiteralAction.  The LiteralAction will already have a
5311
         *   literal phrase, so copy it.  For example: WRITE HELLO ->
5312
         *   WRITE HELLO ON <what>.  
5313
         */
5314
        literalMatch = new PreResolvedLiteralProd(orig.getLiteral());
5315
    }
5316
5317
    /* initialize for a missing literal phrase */
5318
    initForMissingLiteral(orig)
5319
    {
5320
        local origDobj = orig.getDobj();
5321
        
5322
        /*
5323
         *   Since we have a missing literal, we must be coming from a
5324
         *   TAction.  The TAction will already have a direct object,
5325
         *   which we'll want to keep as our own direct object.  For
5326
         *   example: WRITE ON SLATE -> WRITE <what> ON SLATE.  
5327
         */
5328
        dobjMatch = new PreResolvedProd(origDobj != nil
5329
                                        ? origDobj : orig.dobjList_);
5330
    }
5331
5332
    /* object role played by the literal phrase */
5333
    whichMessageLiteral = nil
5334
5335
5336
    /* -------------------------------------------------------------------- */
5337
    /*
5338
     *   Direct Object Resolver implementation.  We serve as our own
5339
     *   direct object resolver, so we define any special resolver
5340
     *   behavior here.  
5341
     */
5342
5343
    /* 
5344
     *   the true grammatical role of the resolved object is always the
5345
     *   direct object 
5346
     */
5347
    whichObject = DirectObject
5348
5349
    /* 
5350
     *   What we call our direct object might actually be playing the
5351
     *   grammatical role of the indirect object - in order to inherit
5352
     *   easily from TAction, we call our resolved object our direct
5353
     *   object, regardless of which grammatical role it actually plays.
5354
     *   For the most part it doesn't matter which is which; but for the
5355
     *   purposes of our resolver, we actually do care about its real role.
5356
     *   So, override the resolver method whichMessageObject so that it
5357
     *   returns whichever role is NOT served by the topic object.  
5358
     */
5359
    whichMessageObject =
5360
        (whichMessageLiteral == DirectObject ? IndirectObject : DirectObject)
5361
;
5362
5363
/* ------------------------------------------------------------------------ */
5364
/*
5365
 *   Base class for actions that include a "topic" phrase.  This is a
5366
 *   mix-in class that can be used in different types of topic actions.  In
5367
 *   all cases, the topic phrase must be assigned to the 'topicMatch'
5368
 *   property in grammar rules based on this class.  
5369
 */
5370
class TopicActionBase: object
5371
    /*
5372
     *   Resolve the topic phrase.  This resolves the match tree in out
5373
     *   'topicMatch' property, and stores the result in topicList_.  This
5374
     *   is for use in resolveNouns().  
5375
     */
5376
    resolveTopic(issuingActor, targetActor, results)
5377
    {
5378
        /* get the topic resolver */
5379
        local resolver = getTopicResolver(issuingActor, targetActor, true);
5380
        
5381
        /* resolve the topic match tree */
5382
        topicList_ = topicMatch.resolveNouns(resolver, results);
5383
5384
        /* make sure it's properly packaged as a ResolvedTopic */
5385
        topicList_ = resolver.packageTopicList(topicList_, topicMatch);
5386
    }
5387
5388
    /*
5389
     *   Set the resolved topic to the given object list.  This is for use
5390
     *   in setResolvedObjects().  
5391
     */
5392
    setResolvedTopic(topic)
5393
    {
5394
        /* if the topic isn't given as a ResolvedTopic, wrap it in one */
5395
        if (!topic.ofKind(ResolvedTopic))
5396
            topic = ResolvedTopic.wrapObject(topic);
5397
5398
        /* use the match object from the ResolvedTopic */
5399
        topicMatch = topic.topicProd;
5400
5401
        /* finally, make a ResolveInfo list out of the ResolvedTopic */
5402
        topicList_ = makeResolveInfoList(topic);
5403
    }
5404
5405
    /*
5406
     *   Set the topic match tree.  This is for use in setObjectMatches().
5407
     */
5408
    setTopicMatch(topic)
5409
    {
5410
        /* 
5411
         *   If it's given as a ResolveInfo, wrap it in a PreResolvedProd.
5412
         *   Otherwise, if it's not a topic match (a TopicProd object), and
5413
         *   it has tokens, re-parse it as a topic match to make sure we
5414
         *   get the grammar optimized for global scope.  
5415
         */
5416
        if (topic.ofKind(ResolveInfo))
5417
        {
5418
            /* it's a resolved object, so wrap it in a fake match tree */
5419
            topic = new PreResolvedProd(topic);
5420
        }
5421
        else if (!topic.ofKind(TopicProd))
5422
        {
5423
            /* 
5424
             *   Re-parse it as a topic phrase.  If our dobj resolver knows
5425
             *   the actors, use those; otherwise default to the player
5426
             *   character. 
5427
             */
5428
            local issuer = (issuer_ != nil ? issuer_ : gPlayerChar);
5429
            local target = (actor_ != nil ? actor_ : gPlayerChar);
5430
            topic = reparseMatchAsTopic(topic, issuer, target);
5431
        }
5432
5433
        /* save the topic match */
5434
        topicMatch = topic;
5435
    }
5436
5437
    /*
5438
     *   Re-parse a match tree as a topic phrase.  Returns a TopicProd
5439
     *   match tree, if possible.  
5440
     */
5441
    reparseMatchAsTopic(topic, issuingActor, targetActor)
5442
    {
5443
        local toks;
5444
        
5445
        /* we can only proceed if the match tree has a token list */
5446
        if ((toks = topic.getOrigTokenList()) != nil && toks.length() > 0)
5447
        {
5448
            /* parse it as a topic phrase */
5449
            local match = topicPhrase.parseTokens(toks, cmdDict);
5450
        
5451
            /* 
5452
             *   if we parsed it successfully, and we have more than one
5453
             *   match, get the best of the bunch 
5454
             */
5455
            if (match != nil && match.length() > 1)
5456
            {
5457
                /* set up a topic resolver for the match */
5458
                local resolver = getTopicResolver(
5459
                    issuingActor, targetActor, true);
5460
                
5461
                /* sort it and return the best match */
5462
                return CommandRanking.sortByRanking(match, resolver)[1].match;
5463
            }
5464
            else if (match != nil && match.length() > 0)
5465
            {
5466
                /* we found exactly one match, so use it */
5467
                return match[1];
5468
            }
5469
        }
5470
5471
        /* 
5472
         *   if we make it this far, we couldn't parse it as a topic, so
5473
         *   just return the original 
5474
         */
5475
        return topic;
5476
    }
5477
5478
    /*
5479
     *   get the topic resolver 
5480
     */
5481
    getTopicResolver(issuingActor, targetActor, reset)
5482
    {
5483
        /* create one if we don't already have one */
5484
        if (topicResolver_ == nil)
5485
            topicResolver_ = createTopicResolver(issuingActor, targetActor);
5486
5487
        /* reset the resolver if desired */
5488
        if (reset)
5489
            topicResolver_.resetResolver();
5490
5491
        /* return the cached resolver */
5492
        return topicResolver_;
5493
    }
5494
5495
    /*
5496
     *   Create the topic resolver.  
5497
     */
5498
    createTopicResolver(issuingActor, targetActor)
5499
    {
5500
        return new TopicResolver(
5501
            self, issuingActor, targetActor, topicMatch, whichMessageTopic,
5502
            getTopicQualifierResolver(issuingActor, targetActor, nil));
5503
    }
5504
5505
    /*
5506
     *   Get a message parameter by name.  We'll return the topic for the
5507
     *   keyword 'topic', and inherit the default handling for anything
5508
     *   else.  
5509
     */
5510
    getMessageParam(objName)
5511
    {
5512
        switch(objName)
5513
        {
5514
        case 'topic':
5515
            /* return the topic */
5516
            return getTopic();
5517
5518
        default:
5519
            /* inherit default handling */
5520
            return inherited(objName);
5521
        }
5522
    }
5523
5524
    /* get the current topic */
5525
    getTopic()
5526
    {
5527
        /* 
5528
         *   Because the topic list always contains one entry (a
5529
         *   ResolvedTopic object encapsulating the topic information),
5530
         *   our current topic is always simply the first and only element
5531
         *   of the topic list. 
5532
         */
5533
        return topicList_[1].obj_;
5534
    }
5535
5536
5537
    /*
5538
     *   Get the resolver for qualifiers we find in the topic phrase
5539
     *   (qualifiers that might need resolution include things like
5540
     *   possessive adjectives and locational phrases).  
5541
     */
5542
    getTopicQualifierResolver(issuingActor, targetActor, reset)
5543
    {
5544
        /* if we don't already have a qualifier resolver, create one */
5545
        if (topicQualResolver_ == nil)
5546
            topicQualResolver_ = createTopicQualifierResolver(
5547
                issuingActor, targetActor);
5548
5549
        /* reset it if desired */
5550
        if (reset)
5551
            topicQualResolver_.resetResolver();
5552
5553
        /* return it */
5554
        return topicQualResolver_;
5555
    }
5556
5557
    /* create a topic qualifier resolver */
5558
    createTopicQualifierResolver(issuingActor, targetActor)
5559
    {
5560
        /* create and return a topic qualifier object resolver */
5561
        return new TopicQualifierResolver(self, issuingActor, targetActor);
5562
    }
5563
5564
    /* the topic qualifier resolver */
5565
    topicQualResolver_ = nil
5566
    
5567
    /* the resolved topic object list */
5568
    topicList_ = nil
5569
5570
    /* my cached topic resolver */
5571
    topicResolver_ = nil
5572
;
5573
5574
/* ------------------------------------------------------------------------ */
5575
/*
5576
 *   An action with a topic phrase as its only object, such as "think about
5577
 *   <topic>".  
5578
 *   
5579
 *   The grammar rules that produce these actions must set topicMatch to
5580
 *   the topic match tree.
5581
 *   
5582
 *   Because we don't have any actual resolved objects, we're based on
5583
 *   IAction.  Subclasses that implement particular topic actions should
5584
 *   override execAction() to carry out the action; this method can call
5585
 *   the getTopic() method of self to get a string giving the topic.  
5586
 */
5587
class TopicAction: TopicActionBase, IAction
5588
    /*
5589
     *   Resolve objects.  We don't actually have any normal objects to
5590
     *   resolve, but we do have to get the resolved topic phrase.  
5591
     */
5592
    resolveNouns(issuingActor, targetActor, results)
5593
    {
5594
        /* the topic phrase counts as one noun slot */
5595
        results.noteNounSlots(1);
5596
5597
        /* resolve the topic */
5598
        resolveTopic(issuingActor, targetActor, results);
5599
    }
5600
5601
    /* manually set the resolved objects */
5602
    setResolvedObjects(topic)
5603
    {
5604
        /* set the resolved topic */
5605
        setResolvedTopic(topic);
5606
    }
5607
5608
    /* manually set the pre-resolved match trees */
5609
    setObjectMatches(topic)
5610
    {
5611
        /* note the new topic match tree */
5612
        setTopicMatch(topic);
5613
    }
5614
5615
    /* we have a topic noun phrase */
5616
    predicateNounPhrases = [&topicMatch]
5617
;
5618
5619
5620
/* ------------------------------------------------------------------------ */
5621
/*
5622
 *   An Action with a direct object and a topic, such as "ask <actor> about
5623
 *   <topic>".  Topics differ from ordinary noun phrases in scope: rather
5624
 *   than resolving to simulation objects based on location, we resolve
5625
 *   these based on the actor's knowledge.
5626
 *   
5627
 *   The grammar rules that produce these actions must set dobjMatch to the
5628
 *   resolvable object of the command (the <actor> in "ask <actor> about
5629
 *   <topic>"), and must set topicMatch to the topic match tree object,
5630
 *   which must be a TopicProd object.  Note that, in some cases, the
5631
 *   phrasing might make the dobjMatch the indirect object, grammatically
5632
 *   speaking: "type <topic> on <object>"; even in such cases, use
5633
 *   dobjMatch for the resolvable object.
5634
 *   
5635
 *   When we resolve the topic, we will always resolve it to a single
5636
 *   object of class ResolvedTopic.  This contains the literal tokens of
5637
 *   the original command plus a list of simulation objects matching the
5638
 *   topic name, ordered from best to worst.  This is different from the
5639
 *   way most commands work, since we do not resolve the topic to a simple
5640
 *   game world object.  We keep all of this extra information because we
5641
 *   don't want to perform disambiguation in the normal fashion, but
5642
 *   instead resolve as much as we can with what we're given, and then give
5643
 *   the specialized action code as much information as we can to let the
5644
 *   action code figure out how to respond to the topic.  
5645
 */
5646
class TopicTAction: TopicActionBase, TAction
5647
    /* 
5648
     *   reset the action 
5649
     */
5650
    resetAction()
5651
    {
5652
        /* reset the inherited state */
5653
        inherited();
5654
5655
        /* forget our topic resolver */
5656
        topicResolver_ = nil;
5657
    }
5658
    
5659
    /*
5660
     *   resolve our noun phrases to objects 
5661
     */
5662
    resolveNouns(issuingActor, targetActor, results)
5663
    {
5664
        local dobjRes;
5665
        
5666
        /* 
5667
         *   the topic phrase counts as one noun slot, and the direct
5668
         *   object as another 
5669
         */
5670
        results.noteNounSlots(2);
5671
5672
        /* resolve the direct object, if we have one */
5673
        if (dobjMatch != nil)
5674
        {
5675
            /* presume we won't find an unbound anaphor */
5676
            needAnaphoricBinding_ = nil;
5677
            
5678
            /* 
5679
             *   if the direct object phrase is an empty phrase, resolve
5680
             *   the topic first 
5681
             */
5682
            if (dobjMatch.isEmptyPhrase)
5683
                resolveTopic(issuingActor, targetActor, results);
5684
            
5685
            /* get the direct object resolver */
5686
            dobjRes = getDobjResolver(issuingActor, targetActor, true);
5687
            
5688
            /* resolve the direct object */
5689
            dobjList_ = dobjMatch.resolveNouns(dobjRes, results);
5690
5691
            /* 
5692
             *   if that turned up an anaphor (LOOK UP BOOK IN ITSELF),
5693
             *   resolve the topic phrase as though it were the direct
5694
             *   object 
5695
             */
5696
            if (needAnaphoricBinding_ && topicMatch != nil)
5697
                dobjList_ = topicMatch.resolveNouns(dobjRes, results);
5698
        }
5699
5700
        /* resolve the topic */
5701
        resolveTopic(issuingActor, targetActor, results);
5702
    }
5703
5704
    /*
5705
     *   Filter the resolved topic.  This is called by our
5706
     *   TActionTopicResolver, which refers the resolution back to us.  
5707
     */
5708
    filterTopic(lst, np, resolver)
5709
    {
5710
        /* by default, simply put everything in an undifferentiated list */
5711
        return new ResolvedTopic(lst, [], [], topicMatch);
5712
    }
5713
5714
    /*
5715
     *   Retry a single-object action as an action taking both an object
5716
     *   and a topic phrase.  We'll treat the original action's direct
5717
     *   object list as our direct object list, and we'll obtain a topic
5718
     *   phrase interactively.
5719
     *   
5720
     *   This routine terminates with 'exit' if it doesn't throw some other
5721
     *   error.  
5722
     */
5723
    retryWithMissingTopic(orig)
5724
    {
5725
        local action;
5726
        
5727
        /* create the new action based on the original action */
5728
        action = createForRetry(orig);
5729
5730
        /* use an empty topic phrase for the new action */
5731
        action.topicMatch = new EmptyTopicPhraseProd();
5732
5733
        /* initialize for the missing topic */
5734
        action.initForMissingTopic(orig);
5735
5736
        /* resolve and execute the replacement action */
5737
        resolveAndReplaceAction(action);
5738
    }
5739
5740
    /* initialize a new action we're retrying for a missing direct object */
5741
    initForMissingDobj(orig)
5742
    {
5743
        /*
5744
         *   The original action must have been a TopicAction, so we
5745
         *   already have a topic.  Simply copy the original topic to use
5746
         *   as our own topic.  For example: ASK ABOUT BOOK -> ASK <whom>
5747
         *   ABOUT BOOK.  
5748
         */
5749
        topicMatch = new PreResolvedProd(orig.getTopic());
5750
    }
5751
5752
    /* initialize for retrying with a missing topic phrase */
5753
    initForMissingTopic(orig)
5754
    {
5755
        local origDobj = orig.getDobj();
5756
        
5757
        /*
5758
         *   The original action must have been a TAction, so we already
5759
         *   have a direct object.  Simply copy the original direct object
5760
         *   for use as our own.  For example: ASK BOB -> ASK BOB ABOUT
5761
         *   <what>.  
5762
         */
5763
        dobjMatch = new PreResolvedProd(origDobj != nil
5764
                                        ? origDobj : orig.dobjList_);
5765
    }
5766
5767
    /* create our TAction topic resolver */
5768
    createTopicResolver(issuingActor, targetActor)
5769
    {
5770
        return new TActionTopicResolver(
5771
            self, issuingActor, targetActor, topicMatch, whichMessageTopic,
5772
            getTopicQualifierResolver(issuingActor, targetActor, nil));
5773
    }
5774
5775
    /* 
5776
     *   In the topic phrase, we can use an anaphoric pronoun to refer
5777
     *   back to the direct object.  Since we resolve the direct object
5778
     *   phrase first, we can simply return the direct object list as the
5779
     *   binding.  If the direct object isn't resolved yet, make a note to
5780
     *   come back and re-bind the anaphor.  
5781
     */
5782
    getAnaphoricBinding(typ)
5783
    {
5784
        /* if we've already resolved the direct object phrase, return it */
5785
        if (dobjList_ not in (nil, []))
5786
            return dobjList_;
5787
5788
        /* no dobj yet - make a note that we need to re-bind the anaphor */
5789
        needAnaphoricBinding_ = true;
5790
5791
        /* 
5792
         *   return an empty list for now to indicate that the anaphor is
5793
         *   acceptable but has no binding yet 
5794
         */
5795
        return [];
5796
    }
5797
5798
    /*
5799
     *   Flag: we have been asked for an anaphoric binding, but we don't
5800
     *   have a binding available.  We'll check this after resolving the
5801
     *   first-resolved noun phrase so that we can go back and re-resolve
5802
     *   it again after resolving the other noun phrase.  
5803
     */
5804
    needAnaphoricBinding_ = nil
5805
5806
    /* we have a direct object and a topic phrase */
5807
    predicateNounPhrases = [&dobjMatch, &topicMatch]
5808
5809
    /* get an object role */
5810
    getRoleFromIndex(idx)
5811
    {
5812
        /* 
5813
         *   index 2 is the topic object, which is always in the indirect
5814
         *   object role; for others, inherit the default handling 
5815
         */
5816
        return (idx == 2 ? IndirectObject : inherited(idx));
5817
    }
5818
5819
    /* get the OtherObject role for the given role */
5820
    getOtherObjectRole(role)
5821
    {
5822
        /* the complementary roles are DirectObject and IndirectObject */
5823
        return (role == DirectObject ? IndirectObject : DirectObject);
5824
    }
5825
5826
    /* get the resolved object in a given role */
5827
    getObjectForRole(role)
5828
    {
5829
        /* 
5830
         *   the topic is in the IndirectObject role; inherit the default
5831
         *   for others 
5832
         */
5833
        return (role == IndirectObject ? getTopic() : inherited(role));
5834
    }
5835
5836
    /* get the match tree for the given role */
5837
    getMatchForRole(role)
5838
    {
5839
        /* 
5840
         *   the topic is in the IndirectObject role; inherit the default
5841
         *   for anything else 
5842
         */
5843
        return (role == IndirectObject ? topicMatch : inherited(role));
5844
    }
5845
5846
    /*
5847
     *   Manually set the resolved objects.  We'll set our direct and
5848
     *   indirect objects.  
5849
     */
5850
    setResolvedObjects(dobj, topic)
5851
    {
5852
        /* inherit default handling for the direct object */
5853
        inherited(dobj);
5854
5855
        /* set the resolved topic */
5856
        setResolvedTopic(topic);
5857
    }
5858
5859
    /* manually set the pre-resolved match trees */
5860
    setObjectMatches(dobj, topic)
5861
    {
5862
        /* inherit default handling for the direct object */
5863
        inherited(dobj);
5864
        
5865
        /* note the new topic match tree */
5866
        setTopicMatch(topic);
5867
    }
5868
5869
    /*
5870
     *   Get the list of active objects.  We return only our direct
5871
     *   object, since our topic isn't actually a simulation object.  
5872
     */
5873
    getCurrentObjects()
5874
    {
5875
        return [dobjCur_];
5876
    }
5877
5878
    /* set the current objects */
5879
    setCurrentObjects(lst)
5880
    {
5881
        dobjCur_ = lst[1];
5882
        dobjInfoCur_ = nil;
5883
    }
5884
5885
    /* the resolved topic object list */
5886
    topicList_ = nil
5887
5888
    /* my cached topic resolver */
5889
    topicResolver_ = nil
5890
5891
    /* grammatical role played by topic phrase in generated messages */
5892
    whichMessageTopic = nil
5893
5894
    /* -------------------------------------------------------------------- */
5895
    /*
5896
     *   Direct Object Resolver implementation.  We serve as our own
5897
     *   direct object resolver, so we define any special resolver
5898
     *   behavior here.  
5899
     */
5900
5901
    /* the true role of the resolved object is always as the direct object */
5902
    whichObject = DirectObject
5903
5904
    /* 
5905
     *   What we call our direct object might actually be playing the
5906
     *   grammatical role of the indirect object - in order to inherit
5907
     *   easily from TAction, we call our resolved object our direct
5908
     *   object, regardless of which grammatical role it actually plays.
5909
     *   For the most part it doesn't matter which is which; but for the
5910
     *   purposes of our resolver, we actually do care about its real role.
5911
     *   So, override the resolver method whichMessageObject so that it
5912
     *   returns whichever role is NOT served by the topic object.  
5913
     */
5914
    whichMessageObject = (whichMessageTopic == DirectObject
5915
                          ? IndirectObject : DirectObject)
5916
;
5917
5918
/*
5919
 *   "Conversation" TopicTAction.  Many TopicTAction verbs involve
5920
 *   conversation with an actor, who's specified as the direct object: ASK
5921
 *   <actor> ABOUT <topic>, TELL <actor> ABOUT <topic>, ASK <actor> FOR
5922
 *   <topic>.  For these common cases, the most likely default direct
5923
 *   object is the last interlocutor of the actor performing the command -
5924
 *   that is, ASK ABOUT BOOK should by default be directed to whomever we
5925
 *   were speaking to last.
5926
 *   
5927
 *   This subclass is suitable for such verbs.  When asked for a default
5928
 *   direct object, we'll check for a current interlocutor, and use it as
5929
 *   the default if available.  If no interlocutor is available, we'll
5930
 *   inherit the standard default handling.  
5931
 */
5932
class ConvTopicTAction: TopicTAction
5933
    getDefaultDobj(np, resolver)
5934
    {
5935
        local obj;
5936
        
5937
        /* 
5938
         *   check to see if the actor has a default interlocutor; if so,
5939
         *   use it as the default actor to be addressed here, otherwise
5940
         *   use the default handling 
5941
         */
5942
        obj = resolver.getTargetActor().getCurrentInterlocutor();
5943
        if (obj != nil)
5944
            return [new ResolveInfo(obj, 0)];
5945
        else
5946
            return inherited(np, resolver);
5947
    }
5948
5949
    /* 
5950
     *   Create the topic resolver.  Use a conversational topic resolver
5951
     *   for this type of action. 
5952
     */
5953
    createTopicResolver(issuingActor, targetActor)
5954
    {
5955
        return new ConvTopicResolver(
5956
            self, issuingActor, targetActor, topicMatch, whichMessageTopic,
5957
            getTopicQualifierResolver(issuingActor, targetActor, nil));
5958
    }
5959
;
5960
5961
5962
/* ------------------------------------------------------------------------ */
5963
/*
5964
 *   A conversational intransitive action.  This class is for actions such
5965
 *   as Hello, Goodbye, Yes, No - conversational actions that don't involve
5966
 *   any topics or other objects, but whose expression is entirely
5967
 *   contained in the verb.  
5968
 */
5969
class ConvIAction: IAction
5970
    /* this action is conversational */
5971
    isConversational(issuer) { return true; }
5972
;
5973
5974
/* ------------------------------------------------------------------------ */
5975
/*
5976
 *   Resolved Topic object.  The topic of a TopicTAction always resolves to
5977
 *   one of these objects.  
5978
 */
5979
class ResolvedTopic: object
5980
    construct(inScope, likely, others, prod)
5981
    {
5982
        /*
5983
         *   Remember our lists of objects.  We keep three separate lists,
5984
         *   so that the action knows how we've classified the objects
5985
         *   matching our phrase.  We keep a list of objects that are in
5986
         *   scope; a list of objects that aren't in scope but which the
5987
         *   actor thinks are likely topics; and a list of all of the
5988
         *   other matches.
5989
         *   
5990
         *   We keep only the simulation objects from the lists - we don't
5991
         *   keep the full ResolveInfo data.  
5992
         */
5993
        inScopeList = inScope.mapAll({x: x.obj_});
5994
        likelyList = likely.mapAll({x: x.obj_});
5995
        otherList = others.mapAll({x: x.obj_});
5996
5997
        /* keep the production match tree */
5998
        topicProd = prod;
5999
    }
6000
6001
    /* 
6002
     *   Static method: create a ResolvedTopic to represent an object
6003
     *   that's already been resolved to a game object for the current
6004
     *   action.  'role' is the object role to wrap (DirectObject,
6005
     *   IndirectObject, etc).  
6006
     */
6007
    wrapActionObject(role)
6008
    {
6009
        local rt;
6010
        
6011
        /* create a ResolvedTopic with just the match tree */
6012
        rt = new ResolvedTopic([],  [], [], gAction.getMatchForRole(role));
6013
6014
        /* 
6015
         *   add the current resolved object in the role as the one
6016
         *   in-scope match 
6017
         */
6018
        rt.inScopeList += gAction.getObjectForRole(role);
6019
6020
        /* return the ResolvedTopic wrapper */
6021
        return rt;
6022
    }
6023
6024
    /*
6025
     *   Static method: create a ResolvedTopic to represent the given
6026
     *   object.
6027
     */
6028
    wrapObject(obj)
6029
    {
6030
        local rt;
6031
        
6032
        /* create a ResolvedTopic with a PreResolvedProd for the object */
6033
        rt = new ResolvedTopic([], [], [], new PreResolvedProd(obj));
6034
6035
        /* add the given object or objects as the in-scope match list */
6036
        rt.inScopeList += obj;
6037
6038
        /* return the ResolvedTopic wrapper */
6039
        return rt;
6040
    }
6041
6042
    /*
6043
     *   Get the best object match to the topic.  This is a default
6044
     *   implementation that can be changed by game authors or library
6045
     *   extensions to implement different topic-matching strategies.  This
6046
     *   implementation simply picks an object arbitrarily from the
6047
     *   "strongest" of the three lists we build: if there's anything in
6048
     *   the inScopeList, we choose an object from that list; otherwise, if
6049
     *   there's anything in the likelyList, we choose an object from that
6050
     *   list; otherwise we choose an object from the otherList.
6051
     */
6052
    getBestMatch()
6053
    {
6054
        /* if there's an in-scope match, pick one */
6055
        if (inScopeList.length() != 0)
6056
            return inScopeList[1];
6057
6058
        /* nothing's in scope, so try to pick a likely match */
6059
        if (likelyList.length() != 0)
6060
            return likelyList[1];
6061
6062
        /* 
6063
         *   there's not even anything likely, so pick an "other", if
6064
         *   there's anything in that list 
6065
         */
6066
        if (otherList.length() != 0)
6067
            return otherList[1];
6068
6069
        /* there are no matches at all - return nil */
6070
        return nil;
6071
    }
6072
6073
    /*
6074
     *   Is the given object among the possible matches for the topic? 
6075
     */
6076
    canMatchObject(obj)
6077
    {
6078
        return inScopeList.indexOf(obj) != nil
6079
            || likelyList.indexOf(obj) != nil
6080
            || otherList.indexOf(obj) != nil;
6081
    }
6082
6083
    /* get the original text of the topic phrase */
6084
    getTopicText() { return topicProd.getOrigText(); }
6085
6086
    /*
6087
     *   Are we allowed to match the topic text literally, for parsing
6088
     *   purposes?  If this is true, it means that we can match the literal
6089
     *   text the player entered against strings, regular expressions,
6090
     *   etc.; for example, we can match a TopicMatchTopic's matchPattern
6091
     *   regular expression.  If this is nil, it means that we can only
6092
     *   interpret the meaning of the resolved topic by looking at the
6093
     *   various topic match lists (inScopeList, likelyList, otherList).
6094
     *   
6095
     *   By default, we simply return true.  Note that the base library
6096
     *   never has any reason of its own to disallow literal matching of
6097
     *   topic text; this property is purely for the use of language
6098
     *   modules, to handle language-specific input that parses at a high
6099
     *   level as a topic phrase but which has some idiomatic or
6100
     *   grammatical function that makes it in appropriate to try to
6101
     *   extract the meaning of the resolved topic from the literal text of
6102
     *   the topic phrase in isolation.  This case doesn't seem to arise in
6103
     *   English, but does occur in other languages: Michel Nizette cites
6104
     *   "parlez-en a Bob" as an example in French, where "en" is
6105
     *   essentially a particle modifying the verb, not a full-fledged
6106
     *   phrase that we can interpret separately as a topic.  
6107
     */
6108
    canMatchLiterally = true
6109
6110
    /* 
6111
     *   get the original tokens of the topic phrase, in canonical
6112
     *   tokenizer format 
6113
     */
6114
    getTopicTokens() { return topicProd.getOrigTokenList(); }
6115
6116
    /*
6117
     *   get the original word strings of the topic phrase - this is
6118
     *   simply a list of the original word strings (in their original
6119
     *   case), without any of the extra information of the more
6120
     *   complicated canonical tokenizer format 
6121
     */
6122
    getTopicWords()
6123
    {
6124
        /* return just the original text strings from the token list */
6125
        return topicProd.getOrigTokenList().mapAll({x: getTokOrig(x)});
6126
    }
6127
6128
    /*
6129
     *   Our lists of resolved objects matching the topic phrase,
6130
     *   separated by classification.  
6131
     */
6132
    inScopeList = nil
6133
    likelyList = nil
6134
    otherList = nil
6135
6136
    /*
6137
     *   The production match tree object that matched the topic phrase in
6138
     *   the command.  This can be used to obtain the original tokens of
6139
     *   the command or the original text of the phrase.  
6140
     */
6141
    topicProd = nil
6142
;
6143
6144
/*
6145
 *   A special topic for "nothing."  It's occasionally useful to be able
6146
 *   to construct a TopicTAction with an empty topic phrase.  For the
6147
 *   topic phrase to be well-formed, we need a valid ResolvedTopic object,
6148
 *   even though it won't refer to anything. 
6149
 */
6150
resolvedTopicNothing: ResolvedTopic
6151
    inScopeList = []
6152
    likelyList = []
6153
    otherList = []
6154
6155
    getTopicText() { return ''; }
6156
    getTopicTokens() { return []; }
6157
    getTopicWords() { return []; }
6158
;
6159
6160
/*
6161
 *   Topic Resolver
6162
 */
6163
class TopicResolver: Resolver
6164
    construct(action, issuingActor, targetActor, prod, which,
6165
              qualifierResolver)
6166
    {
6167
        /* inherit the base class constructor */
6168
        inherited(action, issuingActor, targetActor);
6169
6170
        /* the topic phrase doesn't have a true resolved role */
6171
        whichObject = nil;
6172
6173
        /* remember the grammatical role of our object in the command */
6174
        whichMessageObject = which;
6175
6176
        /* remember our topic match tree */
6177
        topicProd = prod;
6178
6179
        /* remember the resolver for qualifier phrases */
6180
        qualifierResolver_ = qualifierResolver;
6181
    }
6182
6183
    resetResolver()
6184
    {
6185
        /* inherit the default handling */
6186
        inherited();
6187
6188
        /* reset our qualifier resolver as well */
6189
        qualifierResolver_.resetResolver();
6190
    }
6191
6192
    /* 
6193
     *   package a resolved topic list - if it's not already represented as
6194
     *   a ResolvedTopic object, we'll apply that wrapping
6195
     */
6196
    packageTopicList(lst, match)
6197
    {
6198
        /* 
6199
         *   if the topic is other than a single ResolvedTopic object, run
6200
         *   it through the topic resolver's ambiguous noun phrase filter
6201
         *   to get it into that canonical form 
6202
         */
6203
        if (lst != nil
6204
            && (lst.length() > 1
6205
                || (lst.length() == 1 && !lst[1].obj_.ofKind(ResolvedTopic))))
6206
        {
6207
            /* it's not in canonical form, so get it in canonical form now */
6208
            lst = filterAmbiguousNounPhrase(lst, 1, match);
6209
        }
6210
6211
        /* return the result */
6212
        return lst;
6213
    }
6214
6215
    /* our qualifier resolver */
6216
    qualifierResolver_ = nil
6217
6218
    /* get our qualifier resolver */
6219
    getQualifierResolver() { return qualifierResolver_; }
6220
    getPossessiveResolver() { return qualifierResolver_; }
6221
6222
    /*
6223
     *   Determine if the object is in scope.  We consider any vocabulary
6224
     *   match to be in scope for the purposes of a topic phrase, since the
6225
     *   subject matter of topics is mere references to things, not the
6226
     *   things themselves; we can, for example, ASK ABOUT things that
6227
     *   aren't physically present, or even about completely abstract
6228
     *   ideas.
6229
     */
6230
    objInScope(obj) { return true; }
6231
6232
    /* 
6233
     *   our scope is global, because we don't limit the scope to the
6234
     *   physical senses 
6235
     */
6236
    isGlobalScope = true
6237
6238
    /*
6239
     *   Determine if an object is in physical scope.  We'll accept
6240
     *   anything that's in physical scope, and we'll also accept any topic
6241
     *   object that the actor knows about.
6242
     *   
6243
     *   Note that this isn't part of the basic Resolver interface.  It's
6244
     *   instead provided as a service routine for our subclasses, so that
6245
     *   they can easily determine the physical scope of an object if
6246
     *   needed.  
6247
     */
6248
    objInPhysicalScope(obj)
6249
    {
6250
        /* 
6251
         *   it's in physical scope, or if the actor knows about it as a
6252
         *   topic, it's in scope 
6253
         */
6254
        return (scope_.indexOf(obj) != nil);
6255
    }
6256
6257
    /*
6258
     *   Filter an ambiguous noun phrase list using the strength of
6259
     *   possessive qualification, if any.  For a topic phrase, we want to
6260
     *   keep all of the possibilities.  
6261
     */
6262
    filterPossRank(lst, num)
6263
    {
6264
        return lst;
6265
    }
6266
6267
    /*
6268
     *   Filter an ambiguous noun list.
6269
     *   
6270
     *   It is almost always undesirable from a user interface perspective
6271
     *   to ask for help disambiguating a topic phrase.  In the first
6272
     *   place, since all topics tend to be in scope all the time, we
6273
     *   might reveal too much about the inner model of the story if we
6274
     *   were to enumerate all of the topic matches to a phrase.  In the
6275
     *   second place, topics are used in conversational contexts, so it
6276
     *   almost never make sense for the parser to ask for clarification -
6277
     *   the other member of the conversation might ask, but not the
6278
     *   parser.  So, we'll always filter the list to the required number,
6279
     *   even if it means we choose arbitrarily.
6280
     *   
6281
     *   As a first cut, we prefer objects that are physically in scope to
6282
     *   those not in scope: if the player is standing next to a control
6283
     *   panel and types "ask bob about control panel," it makes little
6284
     *   sense to consider any other control panels in the simulation.
6285
     *   
6286
     *   As a second cut, we'll ask the actor to filter the list.  Games
6287
     *   that keep track of the actor's knowledge can use this to filter
6288
     *   according to topics the actor is likely to know about.  
6289
     */
6290
    filterAmbiguousNounPhrase(lst, requiredNum, np)
6291
    {
6292
        local rt;
6293
            
6294
        /* ask the action to create the ResolvedTopic */
6295
        rt = resolveTopic(lst, requiredNum, np);
6296
6297
        /* wrap the ResolvedTopic in the usual ResolveInfo list */
6298
        return [new ResolveInfo(rt, 0)];
6299
    }
6300
        
6301
    /*
6302
     *   Resolve the topic phrase.  This returns a ResolvedTopic object
6303
     *   encapsulating the resolution of the phrase.
6304
     *   
6305
     *   This default base class implementation simply creates a resolved
6306
     *   topic list with the whole set of possible matches
6307
     *   undifferentiated.  Subclasses for specialized actions might want
6308
     *   to differentiate the items in the list, based on things like the
6309
     *   actor's knowledge so far or what's in physical scope.  
6310
     */
6311
    resolveTopic(lst, requiredNum, np)
6312
    {
6313
        /* return a ResolvedTopic that lumps everything in the main list */
6314
        return new ResolvedTopic(lst, [], [], topicProd);
6315
    }
6316
6317
    /*
6318
     *   Resolve an unknown phrase.  We allow unknown words to be used in
6319
     *   topics; we simply return a ResolvedTopic that doesn't refer to
6320
     *   any simulation objects at all.  
6321
     */
6322
    resolveUnknownNounPhrase(tokList)
6323
    {
6324
        local rt;
6325
        
6326
        /* 
6327
         *   Create our ResolvedTopic object for the results.  We have
6328
         *   words we don't know, so we're not referring to any objects,
6329
         *   so our underlying simulation object list is empty.  
6330
         */
6331
        rt = new ResolvedTopic([], [], [], topicProd);
6332
6333
        /* return a resolved topic object with the empty list */
6334
        return [new ResolveInfo(rt, 0)];
6335
    }
6336
6337
    /* filter a plural */
6338
    filterPluralPhrase(lst, np)
6339
    {
6340
        /*
6341
         *   Handle this the same way we handle an ambiguous noun phrase,
6342
         *   so that we yield only one object.  Topics are inherently
6343
         *   singular; we'll allow asking about a grammatically plural
6344
         *   term, but we'll turn it into a single topic result.  
6345
         */
6346
        return filterAmbiguousNounPhrase(lst, 1, np);
6347
    }
6348
6349
    /* get a default object */
6350
    getDefaultObject(np)
6351
    {
6352
        /* there is never a default for a topic */
6353
        return nil;
6354
    }
6355
6356
    /* it's fine not to match a topic phrase */
6357
    noVocabMatch(action, txt) { }
6358
    noMatch(action, txt) { }
6359
6360
    /* we don't allow ALL or provide defaults */
6361
    getAll(np) { return []; }
6362
    getAllDefaults() { return []; }
6363
6364
    /* the production match tree for the topic phrase we're resolving */
6365
    topicProd = nil
6366
;
6367
6368
/*
6369
 *   A topic resolver specialized for TopicTActions - actions involving a
6370
 *   topic and a physical object, such as CONSULT ABOUT.  For these topics,
6371
 *   we'll let the action handle the resolution.  
6372
 */
6373
class TActionTopicResolver: TopicResolver
6374
    resolveTopic(lst, requiredNum, np)
6375
    {
6376
        /* let the action handle it */
6377
        return action_.filterTopic(lst, np, self);
6378
    }
6379
;
6380
6381
/*
6382
 *   A topic resolver specialized for conversational actions (ASK ABOUT,
6383
 *   TELL ABOUT, etc).  When we resolve the topic, we'll differentiate the
6384
 *   resolution to differentiate based on the knowledge of the actor who's
6385
 *   performing the command.  
6386
 */
6387
class ConvTopicResolver: TopicResolver
6388
    /*
6389
     *   Resolve the topic phrase.  We'll break up the vocabulary matches
6390
     *   into three sublists: the objects that are either in physical scope
6391
     *   or known to the actor performing the command; objects that the
6392
     *   actor considers likely topics; and everything else.  
6393
     */
6394
    resolveTopic(lst, requiredNum, np)
6395
    {
6396
        local inScope;
6397
        local actorPrefs;
6398
6399
        /* 
6400
         *   First, get the subset of items that are in conversational
6401
         *   scope - we'll consider this the best set of matches.  
6402
         */
6403
        inScope = lst.subset({x: objInConvScope(x.obj_)});
6404
6405
        /* 
6406
         *   eliminate the in-scope items from the list, so we can
6407
         *   consider only what remains 
6408
         */
6409
        lst -= inScope;
6410
6411
        /* 
6412
         *   ask the actor to pick out the most likely set of topics from
6413
         *   the ones that remain 
6414
         */
6415
        actorPrefs = lst.subset({x: actor_.isLikelyTopic(x.obj_)});
6416
6417
        /* eliminate those items from the list */
6418
        lst -= actorPrefs;
6419
6420
        /* create our ResolvedTopic object and return it */
6421
        return new ResolvedTopic(inScope, actorPrefs, lst, topicProd);
6422
    }
6423
6424
    /*
6425
     *   Determine if an object is in "conversational" scope - this returns
6426
     *   true if the object is in physical scope or it's known to the actor
6427
     *   performing the command. 
6428
     */
6429
    objInConvScope(obj)
6430
    {
6431
        /* 
6432
         *   if it's in physical scope, or the actor knows about it, it's
6433
         *   in conversation scope 
6434
         */
6435
        return (objInPhysicalScope(obj) || actor_.knowsTopic(obj));
6436
    }
6437
;
6438
6439
/* ------------------------------------------------------------------------ */
6440
/*
6441
 *   System action.  These actions are for out-of-game meta-verbs (save,
6442
 *   restore, undo).  These verbs take no objects, must be performed by
6443
 *   the player (thus by the player character, not an NPC), and consume no
6444
 *   game clock time.  
6445
 */
6446
class SystemAction: IAction
6447
    /* execute the action */
6448
    execAction()
6449
    {
6450
        /* 
6451
         *   Conceptually, system actions are performed by the player
6452
         *   directly, not by any character in the game (not even the
6453
         *   player character).  However, we don't distinguish in the
6454
         *   command-entry user interface between a command the player is
6455
         *   performing and a command to the player character, hence we
6456
         *   can merely ensure that the command is not directed to a
6457
         *   non-player character. 
6458
         */
6459
        if (!gActor.isPlayerChar)
6460
        {
6461
            gLibMessages.systemActionToNPC();
6462
            exit;
6463
        }
6464
6465
        /* 
6466
         *   system actions sometimes need to prompt for interactive
6467
         *   responses, so deactivate the report list - this will allow
6468
         *   interactive prompts to be shown immediately, not treated as
6469
         *   reports to be deferred until the command is finished 
6470
         */
6471
        gTranscript.showReports(true);
6472
        gTranscript.clearReports();
6473
6474
        /* perform our specific action */
6475
        execSystemAction();
6476
6477
        /* re-activate the transcript for the next command */
6478
        gTranscript.activate();
6479
    }
6480
6481
    /* each subclass must override this to perform its actual action */
6482
    execSystemAction() { }
6483
6484
    /*
6485
     *   Ask for an input file.  Freezes the real-time event clock for the
6486
     *   duration of reading the event, and sets our property
6487
     *   origElapsedTime to the elapsed time when we started processing
6488
     *   the interaction.  
6489
     */
6490
    getInputFile(prompt, dialogType, fileType, flags)
6491
    {
6492
        local result;
6493
        
6494
        /* 
6495
         *   note the game elapsed time before we start - we want to
6496
         *   freeze the real-time clock while we're waiting for the user
6497
         *   to respond, since this system verb exists outside of the
6498
         *   usual time flow of the game 
6499
         */
6500
        origElapsedTime = realTimeManager.getElapsedTime();
6501
        
6502
        /* ask for a file */
6503
        result = inputFile(prompt, dialogType, fileType, flags);
6504
6505
        /* 
6506
         *   restore the game real-time counter to what it was before we
6507
         *   started the interactive response 
6508
         */
6509
        realTimeManager.setElapsedTime(origElapsedTime);
6510
6511
        /* return the result from inputFile */
6512
        return result;
6513
    }
6514
6515
    /* system actions consume no game time */
6516
    actionTime = 0
6517
6518
    /* real-time event clock at start of getInputFile() */
6519
    origElapsedTime = nil
6520
;
6521
6522
/* ------------------------------------------------------------------------ */
6523
/*
6524
 *   An exception class for remappings that we can't handle. 
6525
 */
6526
class ActionRemappingTooComplexError: Exception
6527
;
6528