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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library: command execution
7
 *   
8
 *   This module defines functions that perform command execution.  
9
 */
10
11
#include "adv3.h"
12
13
14
/* ------------------------------------------------------------------------ */
15
/*
16
 *   Execute a command line, as issued by the given actor and as given as a
17
 *   list of tokens.
18
 *   
19
 *   If 'firstInSentence' is true, we're at the start of a "sentence."  The
20
 *   meaning and effect of this may vary by language.  In English, a
21
 *   sentence ends with certain punctuation marks (a period, semicolon,
22
 *   exclamation mark, or question mark), so anything after one of these
23
 *   punctuation marks is the start of a new sentence.  Also in English, we
24
 *   can address a command to an explicit target actor using the "actor,"
25
 *   prefix syntax, which we can't use except at the start of a sentence.
26
 *   
27
 *   If the command line consists of multiple commands, we will only
28
 *   actually execute the first command before returning.  We'll schedule
29
 *   any additional commands for later execution by putting them into the
30
 *   target actor's pending command queue before we return, but we won't
31
 *   actually execute them.  
32
 */
33
executeCommand(targetActor, issuingActor, toks, firstInSentence)
34
{
35
    local actorPhrase;
36
    local actorSpecified;
37
38
    /*
39
     *   Turn on sense caching while we're working, until execution
40
     *   begins.  The parsing and resolution phases of command processing
41
     *   don't involve any changes to game state, so we can safely cache
42
     *   sense information; caching sense information during these phases
43
     *   is desirable because these steps (noun resolution in particular)
44
     *   involve repeated inspection of the current sensory environment,
45
     *   which can require expensive calculations.  
46
     */
47
    libGlobal.enableSenseCache();
48
49
    /* we don't have an explicit actor phrase yet */
50
    actorPhrase = nil;
51
    
52
    /* presume an actor will not be specified */
53
    actorSpecified = nil;
54
    
55
    /*
56
     *   If this is the start of a new sentence, and the issuing actor
57
     *   wants to cancel any target actor designation at the end of each
58
     *   sentence, change the target actor back to the issuing actor.  
59
     */
60
    if (firstInSentence
61
        && issuingActor != targetActor
62
        && issuingActor.revertTargetActorAtEndOfSentence)
63
    {
64
        /* switch to the target actor */
65
        targetActor = issuingActor;
66
67
        /* switch to the issuer's sense context */
68
        senseContext.setSenseContext(targetActor, sight);
69
    }
70
71
    /*
72
     *   Keep going until we've processed the command.  This might take
73
     *   several iterations, because we might have replacement commands to
74
     *   execute.  
75
     */
76
parseTokenLoop:
77
    for (;;)
78
    {
79
        local lst;
80
        local action;
81
        local match;
82
        local nextIdx;
83
        local nextCommandTokens;
84
        local extraIdx;
85
        local extraTokens;
86
        
87
        /*
88
         *   Catch any errors that occur while executing the command,
89
         *   since some of them are signals to us that we should reparse
90
         *   some new text read or generated deep down inside the command
91
         *   processing.  
92
         */
93
        try
94
        {
95
            local rankings;
96
97
            /* we have no extra tokens yet */
98
            extraTokens = [];
99
100
            /* 
101
             *   Parse the token list.  If this is the first command on
102
             *   the command line, allow an actor prefix.  Otherwise, just
103
             *   look for a command.  
104
             */
105
            lst = (firstInSentence ? firstCommandPhrase : commandPhrase)
106
                  .parseTokens(toks, cmdDict);
107
108
            /*
109
             *   As a first cut at reducing the list of possible matches
110
             *   to those that make sense, eliminate from this list any
111
             *   matches which do not have valid actions.  In grammars for
112
             *   "scrambling" languages (i.e., languages with flexible
113
             *   word ordering), it is possible to construct commands that
114
             *   fit the grammatical rules of sentence construction but
115
             *   which make no sense because of the specific constraints
116
             *   of the verbs involved; we can filter out such nonsense
117
             *   interpretations immediately by keeping only those
118
             *   structural interpretations that can resolve to valid
119
             *   actions.  
120
             */
121
            lst = lst.subset(
122
                {x: x.resolveFirstAction(issuingActor, targetActor) != nil});
123
124
            /* if we have no matches, the command isn't understood */
125
            if (lst.length() == 0)
126
            {
127
                /* 
128
                 *   If this is a first-in-sentence phrase, try looking for
129
                 *   a target actor phrase.  If we can find one, we can
130
                 *   direct the 'oops' to that actor, to allow the game to
131
                 *   customize messages more specifically.  
132
                 */
133
                if (firstInSentence)
134
                {
135
                    local i;
136
                    
137
                    /* try parsing an "actor, <unknown>" phrase */
138
                    lst = actorBadCommandPhrase.parseTokens(toks, cmdDict);
139
140
                    /* if we got any matches, try to resolve actors */
141
                    lst = lst.mapAll({x: x.resolveNouns(
142
                        issuingActor, issuingActor,
143
                        new ActorResolveResults())});
144
145
                    /* drop any that didn't yield any results */
146
                    lst = lst.subset({x: x != nil && x.length() != 0});
147
148
                    /*
149
                     *
150
                     *   if anything's left, and one of the entries 
151
                     *   resolves to an actor, arbitrarily pick the 
152
                     *   first such entry use the resolved actor as the 
153
                     *   new target actor 
154
                     */
155
                    if (lst.length() != 0
156
                        && (i = lst.indexWhich(
157
                            {x: x[1].obj_.ofKind(Actor)})) != nil)
158
                        targetActor = lst[i][1].obj_;
159
                }
160
161
                /* 
162
                 *   We don't understand the command.  Check for unknown
163
                 *   words - if we have any, give them a chance to use
164
                 *   OOPS to correct a typo.  
165
                 */
166
                tryOops(toks, issuingActor, targetActor,
167
                        1, toks, rmcCommand);
168
169
                /* 
170
                 *   try running it by the SpecialTopic history to see if
171
                 *   they're trying to use a special topic in the wrong
172
                 *   context - if so, explain that they can't use the
173
                 *   command right now, rather than claiming that the
174
                 *   command is completely invalid 
175
                 */
176
                if (specialTopicHistory.checkHistory(toks))
177
                {
178
                    /* the special command is not currently available */
179
                    targetActor.notifyParseFailure(
180
                        issuingActor, &specialTopicInactive, []);
181
                }
182
                else
183
                {
184
                    /* tell the issuer we didn't understand the command */
185
                    targetActor.notifyParseFailure(
186
                        issuingActor, &commandNotUnderstood, []);
187
                }
188
                
189
                /* 
190
                 *   we're done with this command, and we want to abort
191
                 *   any subsequent commands on the command line 
192
                 */
193
                return;
194
            }
195
196
            /* show the matches if we're in debug mode */
197
            dbgShowGrammarList(lst);
198
        
199
            /* 
200
             *   Perform a tentative resolution on each alternative
201
             *   structural interpretation of the command, and rank the
202
             *   interpretations in order of "goodness" as determined by
203
             *   our ranking criteria encoded in CommandRanking.
204
             *   
205
             *   Note that we perform the tentative resolution and ranking
206
             *   even if we have only one interpretation, because the
207
             *   tentative resolution is often useful for the final
208
             *   resolution pass.  
209
             */
210
            rankings = CommandRanking
211
                       .sortByRanking(lst, issuingActor, targetActor);
212
                
213
            /* 
214
             *   Take the interpretation that came up best in the rankings
215
             *   (or, if we have multiple at the same ranking, arbitrarily
216
             *   pick the one that happened to come up first in the list)
217
             *   - they're ranked in descending order of goodness, so take
218
             *   the first one.
219
             */
220
            match = rankings[1].match;
221
222
            /* if we're in debug mode, show the winner */
223
            dbgShowGrammarWithCaption('Winner', match);
224
            
225
            /*
226
             *   Get the token list for the rest of the command after what
227
             *   we've parsed so far.
228
             *   
229
             *   Note that we'll start over and parse these tokens anew,
230
             *   even though we might have parsed them already into a
231
             *   subcommand on the previous iteration.  Even if we already
232
             *   parsed these tokens, we want to parse them again, because
233
             *   we did not have a suitable context for evaluating the
234
             *   semantic strengths of the possible structural
235
             *   interpretations this command on the previous iteration;
236
             *   for example, it was not possible to resolve noun phrases
237
             *   in this command because we could not guess what the scope
238
             *   would be when we got to this point.  So, we'll simply
239
             *   discard the previous match tree, and start over, treating
240
             *   the new tokens as a brand new command.  
241
             */
242
            nextIdx = match.getNextCommandIndex();
243
            nextCommandTokens = toks.sublist(nextIdx);
244
245
            /* if the pending command list is empty, make it nil */
246
            if (nextCommandTokens.length() == 0)
247
                nextCommandTokens = nil;
248
249
            /*
250
             *   Get the part of the token list that the match doesn't use
251
             *   at all.  These are the tokens following the tokens we
252
             *   matched. 
253
             */
254
            extraIdx = match.tokenList.length() + 1;
255
            extraTokens = toks.sublist(extraIdx);
256
257
            /*
258
             *   We now have the best match for the command tree.
259
             *   
260
             *   If the command has an actor clause, resolve the actor, so
261
             *   that we can direct the command to that actor.  If the
262
             *   command has no actor clause, the command is to the actor
263
             *   who issued the command.
264
             *   
265
             *   Do NOT process the actor clause if we've already done so.
266
             *   If we edit the token list and retry the command after
267
             *   this point, there is no need to resolve the actor part
268
             *   again.  Doing so could be bad - if resolving the actor
269
             *   required player interaction, such as asking for help with
270
             *   an ambiguous noun phrase, we do not want to go through
271
             *   the same interaction again just because we have to edit
272
             *   and retry a later part of the command.  
273
             */
274
            if (match.hasTargetActor())
275
            {
276
                local actorResults;
277
278
                /*
279
                 *   If we haven't yet explicitly specified a target
280
                 *   actor, and the default target actor is different from
281
                 *   the issuing actor, then this is really a new command
282
                 *   from the issuing actor.
283
                 *   
284
                 *   First, an actor change mid-command is allowed only if
285
                 *   the issuing actor waits for NPC commands to be
286
                 *   carried out; if not, then we can't change the actor
287
                 *   mid-command.
288
                 *   
289
                 *   Second, since the command comes from the issuing
290
                 *   actor, not from the default target actor, we want to
291
                 *   issue the orders on the issuing actor's turn, so put
292
                 *   the command into the queue for the issuer, and let
293
                 *   the issuer re-parse the command on the issuer's next
294
                 *   turn.  
295
                 */
296
                if (!actorSpecified && issuingActor != targetActor)
297
                {
298
                    /* 
299
                     *   don't allow the command if the issuing actor
300
                     *   doesn't wait for orders to be completed 
301
                     */
302
                    if (!issuingActor.issueCommandsSynchronously)
303
                    {
304
                        /* turn off any sense capturing */
305
                        senseContext.setSenseContext(nil, sight);
306
307
                        /* show the error */
308
                        issuingActor.getParserMessageObj()
309
                            .cannotChangeActor();
310
311
                        /* done */
312
                        return;
313
                    }
314
315
                    /* put the command into the issuer's queue */
316
                    issuingActor.addFirstPendingCommand(
317
                        firstInSentence, issuingActor, toks);
318
319
                    /* done */
320
                    return;
321
                }
322
323
                /* create an actor-specialized results object */
324
                actorResults = new ActorResolveResults();
325
                
326
                /* set up the actors in the results object */
327
                actorResults.setActors(targetActor, issuingActor);
328
                
329
                /* resolve the actor object */
330
                match.resolveNouns(issuingActor, targetActor, actorResults);
331
332
                /* get the target actor from the command */
333
                targetActor = match.getTargetActor();
334
335
                /* pull out the phrase specifying the actor */
336
                actorPhrase = match.getActorPhrase();
337
338
                /*
339
                 *   Copy antecedents from the issuing actor to the target
340
                 *   actor.  Since the issuer is giving us a new command
341
                 *   here, pronouns will be given from the issuer's
342
                 *   perspective.  
343
                 */
344
                targetActor.copyPronounAntecedentsFrom(issuingActor);
345
346
                /* let the actor phrase know we're actually using it */
347
                match.execActorPhrase(issuingActor);
348
                
349
                /* 
350
                 *   Ask the target actor if it's interested in the
351
                 *   command at all.  This only applies when the actor was
352
                 *   actually specified - if an actor wasn't specified,
353
                 *   the command is either directed to the issuer itself,
354
                 *   in which case the command will always be accepted; or
355
                 *   the command is a continuation of a command line
356
                 *   previously accepted.  
357
                 */
358
                if (!targetActor.acceptCommand(issuingActor))
359
                {
360
                    /* 
361
                     *   the command was immediately rejected - abandon
362
                     *   the command and any subsequent commands on the
363
                     *   same line 
364
                     */
365
                    return;
366
                }
367
368
                /* note that an actor was specified */
369
                actorSpecified = true;
370
371
                /*
372
                 *   Pull out the rest of the command (other than the
373
                 *   target actor specification) and start over with a
374
                 *   fresh parse of the whole thing.  We must do this
375
                 *   because our tentative resolution pass that we used to
376
                 *   pick the best structural interpretation of the
377
                 *   command couldn't go far enough - since we didn't know
378
                 *   the actor involved, we weren't able to resolve nouns
379
                 *   in the rest of the command.  Now that we know the
380
                 *   actor, we can start over and resolve everything in
381
                 *   the rest of the command, and thus choose the right
382
                 *   structural match for the command.  
383
                 */
384
                toks = match.getCommandTokens();
385
386
                /* what follows obviously isn't first in the sentence */
387
                firstInSentence = nil;
388
389
                /* go back to parse the rest */
390
                continue parseTokenLoop;
391
            }
392
393
            /* pull out the first action from the command */        
394
            action = match.resolveFirstAction(issuingActor, targetActor);
395
396
            /*
397
             *   If the winning interpretation had any unknown words, run
398
             *   a resolution pass to resolve those interactively, if
399
             *   possible.  We want to do this before doing any other
400
             *   interactive resolution because OOPS has the unique
401
             *   property of forcing us to reparse the command; if we
402
             *   allowed any other interactive resolution to happen before
403
             *   processing an OOPS, we'd likely have to repeat the other
404
             *   resolution on the reparse, which would confuse and
405
             *   irritate users by asking the same question more than once
406
             *   for what is apparently the same command.  
407
             */
408
            if (rankings[1].unknownWordCount != 0)
409
            {
410
                /* 
411
                 *   resolve using the OOPS results gatherer - this runs
412
                 *   essentially the same preliminary resolution process
413
                 *   as the ranking results gatherer, but does perform
414
                 *   interactive resolution of unknown words via OOPS 
415
                 */
416
                match.resolveNouns(
417
                    issuingActor, targetActor,
418
                    new OopsResults(issuingActor, targetActor));
419
            }
420
421
            /* 
422
             *   If the command is directed to a different actor than the
423
             *   issuer, change to the target actor's sense context.  
424
             */
425
            if (actorSpecified && targetActor != issuingActor)
426
                senseContext.setSenseContext(targetActor, sight);
427
428
            /* set up a transcript to receive the command results */
429
            withCommandTranscript(CommandTranscript, new function()
430
            {
431
                /* 
432
                 *   Execute the action.
433
                 *   
434
                 *   If a target actor was specified, and it's not the same
435
                 *   as the issuing actor, this counts as a turn for the
436
                 *   issuing actor.  
437
                 */
438
                executeAction(targetActor, actorPhrase, issuingActor,
439
                              actorSpecified && issuingActor != targetActor,
440
                              action);
441
            });
442
443
            /*
444
             *   If we have anything remaining on the command line, insert
445
             *   the remaining commands at the start of the target actor's
446
             *   command queue, since we want the target actor to continue
447
             *   with the rest of this command line as its next operation.
448
             *   
449
             *   Since the remainder of the line represents part of the
450
             *   work the actor pulled out of the queue in order to call
451
             *   us, put the remainder back in at the START of the actor's
452
             *   queue - it was before anything else that might be in the
453
             *   actor's queue before, so it should stay ahead of anything
454
             *   else now.  
455
             */
456
            if (nextCommandTokens != nil)
457
            {
458
                /* 
459
                 *   Prepend the remaining commands to the actor's queue.
460
                 *   The next command is the start of a new sentence if
461
                 *   the command we just executed ends the sentence. 
462
                 */
463
                targetActor.addFirstPendingCommand(
464
                    match.isEndOfSentence(), issuingActor, nextCommandTokens);
465
            }
466
467
            /*
468
             *   If the command was directed from the issuer to a
469
             *   different target actor, and the issuer wants to wait for
470
             *   the full set of issued commands to complete before
471
             *   getting another turn, tell the issuer to begin waiting.  
472
             */
473
            if (actorSpecified && issuingActor != targetActor)
474
                issuingActor.waitForIssuedCommand(targetActor);
475
476
            /* we're done */
477
            return;
478
        }
479
        catch (ParseFailureException rfExc)
480
        {
481
            /*
482
             *   Parsing failed in such a way that we cannot proceed.
483
             *   Tell the target actor to notify the issuing actor.  
484
             */
485
            rfExc.notifyActor(targetActor, issuingActor);
486
487
            /* 
488
             *   the command cannot proceed, so abandon any remaining
489
             *   tokens on the command line 
490
             */
491
            return;
492
        }
493
        catch (CancelCommandLineException cclExc)
494
        {
495
            /* 
496
             *   if there are any tokens remaining, the game might want to
497
             *   show an explanation 
498
             */
499
            if (nextCommandTokens != nil)
500
                targetActor.getParserMessageObj().explainCancelCommandLine();
501
502
            /* stop now, abandoning the rest of the command line */	
503
            return;
504
        }
505
        catch (TerminateCommandException tcExc)
506
        {
507
            /* 
508
             *   the command cannot proceed - we can't do any more
509
             *   processing of this command, so simply return, abandoning
510
             *   any additional tokens we have 
511
             */
512
            return;
513
        }
514
        catch (RetryCommandTokensException rctExc)
515
        {
516
            /* 
517
             *   We want to replace the current command's token list with
518
             *   the new token list - get the new list, and then go back
519
             *   and start over processing it.
520
             *   
521
             *   Note that we must retain any tokens beyond those that the
522
             *   match tree used.  The exception only edits the current
523
             *   match tree's matched tokens, since it doesn't have access
524
             *   to any of the original tokens beyond those, so we must
525
             *   now add back in any tokens beyond the originals.  
526
             */
527
            toks = rctExc.newTokens_ + extraTokens;
528
529
            /* go back and process the command again with the new tokens */
530
            continue parseTokenLoop;
531
        }
532
        catch (ReplacementCommandStringException rcsExc)
533
        {
534
            local str;
535
            
536
            /* retrieve the new command string from the exception */
537
            str = rcsExc.newCommand_;
538
539
            /* 
540
             *   if the command string is nil, it means that the command
541
             *   has been fully handled already, so we simply return
542
             *   without any further work 
543
             */
544
            if (str == nil)
545
                return;
546
547
            /* 
548
             *   Replace the entire command string with the one from the
549
             *   exception - this cancels any previous command that we
550
             *   had.
551
             */
552
            toks = cmdTokenizer.tokenize(str);
553
554
            /* 
555
             *   we have a brand new command line, so we're starting a
556
             *   brand new sentence 
557
             */
558
            firstInSentence = true;
559
560
            /* set the issuing and target actor according to the exception */
561
            issuingActor = rcsExc.issuingActor_;
562
            targetActor = rcsExc.targetActor_;
563
564
            /* 
565
             *   Put this work into the target actor's work queue, so that
566
             *   the issuer will carry out the command at the next
567
             *   opportunity.  This is a brand new command line, so it
568
             *   starts a new sentence.  
569
             */
570
            targetActor.addPendingCommand(true, issuingActor, toks);
571
572
            /* we're done processing this command */
573
            return;
574
        }
575
    }
576
}
577
578
/* ------------------------------------------------------------------------ */
579
/*
580
 *   GlobalRemapping makes it possible to transform one action into another
581
 *   globally - as opposed to the remapTo mechanism, which lets an object
582
 *   involved in the command perform a remapping.  The key difference
583
 *   between global remappings and remapTo is that the latter can't happen
584
 *   until after the objects are resolved (for fairly obvious reasons: each
585
 *   remapTo mapping is associated with an object, so you can't know which
586
 *   mapping to apply until you know which object is involved).  In
587
 *   contrast, global remappings are performed *before* object resolution -
588
 *   this is possible because the mappings don't depend on the objects
589
 *   involved in the action.
590
 *   
591
 *   Whenever an action is about to be executed, the parser runs through
592
 *   all of the defined global remappings, and gives each one a chance to
593
 *   remap the command.  If any remapping succeeds, we replace the original
594
 *   command with the remapped version, then repeat the scan of the global
595
 *   remapping list from the start - we do another complete scan of the
596
 *   list in case there's another global mapping that applies to the
597
 *   remapped version of the command.  We repeat this process until we make
598
 *   it through the whole list without finding a remapping.
599
 *   
600
 *   GlobalRemapping instances are added to the master list of mappings
601
 *   automatically at pre-init time, and any time you construct one
602
 *   dynamically with 'new'.
603
 */
604
class GlobalRemapping: PreinitObject
605
    /*
606
     *   Check for and apply a remapping.  This method must be implemented
607
     *   in each GlobalRemapping instance to perform the actual remapping
608
     *   work.
609
     *   
610
     *   This routine should first check to see if the command is relevant
611
     *   to this remapping.  In most cases, this means checking that the
612
     *   command matches some template, such as having a particular action
613
     *   (verb) and combination of potential objects.  Note that the
614
     *   objects aren't fully resolved during global remapping - the whole
615
     *   point of global remapping is to catch certain phrasings before we
616
     *   get to the noun resolution phase - but the *phrases* involved will
617
     *   be known, so the range of potential matches is knowable.
618
     *   
619
     *   If the routine decides that the action isn't relevant to this
620
     *   remapping, it should simply return nil.
621
     *   
622
     *   If the action decides to remap the action, it must create a new
623
     *   Action object representing the replacement version of the command.
624
     *   Then, return a list, [targetActor, action], giving the new target
625
     *   actor and the new action.  You don't have to change the target
626
     *   actor, of course, but it's included in the result so that you can
627
     *   change it if you want to.  For example, you could use this to
628
     *   remap a command of the form "X, GIVE ME Y" to "ME, ASK X FOR Y" -
629
     *   note that the target actor changes from X to ME.  
630
     */
631
    getRemapping(issuingActor, targetActor, action)
632
    {
633
        /* 
634
         *   this must be overridden to perform the actual remapping; by
635
         *   default, simply return nil to indicate that we don't want to
636
         *   remap this action 
637
         */
638
        return nil;
639
    }
640
641
    /*
642
     *   Remapping order - the parser applies global remappings in
643
     *   ascending order of this value.  In most cases, the order shouldn't
644
     *   matter, since most remappings should be narrow enough that a given
645
     *   command will only be subject to one remapping rule.  However, in
646
     *   some cases you might need to define rules that overlap, so the
647
     *   ordering lets you specify which one goes first.  In most cases
648
     *   you'll want to apply the more specific rule first. 
649
     */
650
    remappingOrder = 100
651
652
    /* 
653
     *   Static class method: look for a remapping.  This runs through the
654
     *   master list of mappings, looking for a mapping that applies to the
655
     *   given command.  If we find one, we'll replace the command with the
656
     *   remapped version, then start over with a fresh scan of the entire
657
     *   list to see if there's a remapping for the *new* version of the
658
     *   command.  We repeat this until we get through the whole list
659
     *   without finding a remapping.
660
     *   
661
     *   The return value is a list, [targetActor, action], giving the
662
     *   resulting target actor and new action object.  If we don't find
663
     *   any remapping, this will simply be the original values passed in
664
     *   as our arguments; if we do find a remapping, this will be the new
665
     *   version of the command.  
666
     */
667
    findGlobalRemapping(issuingActor, targetActor, action)
668
    {
669
        /* get the global remapping list */
670
        local v = GlobalRemapping.allGlobalRemappings;
671
        local cnt = v.length();
672
673
        /* if necessary, sort the list */
674
        if (GlobalRemapping.listNeedsSorting)
675
        {
676
            /* sort it by ascending remappingOrder value */
677
            v.sort(SortAsc, {a, b: a.remappingOrder - b.remappingOrder});
678
679
            /* note that it's now sorted */
680
            GlobalRemapping.listNeedsSorting = nil;
681
        }
682
        
683
        /* 
684
         *   iterate through the list repeatedly, until we make it all the
685
         *   way through without finding a mapping 
686
         */
687
        for (local done = nil ; !done ; )
688
        {
689
            /* presume we won't find a remapping on this iteration */
690
            done = true;
691
692
            /* run through the list, looking for a remapping */
693
            for (local i = 1 ; i <= cnt ; ++i)
694
            {
695
                local rm;
696
                
697
                /* check for a remapping */
698
                rm = v[i].getRemapping(issuingActor, targetActor, action);
699
                if (rm != nil)
700
                {
701
                    /* found a remapping - apply it */
702
                    targetActor = rm[1];
703
                    action = rm[2];
704
705
                    /* 
706
                     *   we found a remapping, so we have to repeat the
707
                     *   scan of the whole list - note that we're not done,
708
                     *   and break out of the current scan so that we start
709
                     *   over with a fresh scan 
710
                     */
711
                    done = nil;
712
                    break;
713
                }
714
            }
715
        }
716
717
        /* return the final version of the command */
718
        return [targetActor, action];
719
    }
720
721
    /* pre-initialization: add each instance to the master list */
722
    execute()
723
    {
724
        /* add me to the master list */
725
        registerGlobalRemapping();
726
    }
727
728
    /* construction: add myself to the master list */
729
    construct()
730
    {
731
        /* add me to the master list */
732
        registerGlobalRemapping();
733
    }        
734
735
    /* register myself with the global list, making this an active mapping */
736
    registerGlobalRemapping()
737
    {
738
        /* add myself to the global list */
739
        GlobalRemapping.allGlobalRemappings.append(self);
740
741
        /* note that a sort is required the next time we run */
742
        GlobalRemapping.listNeedsSorting = true;
743
    }
744
745
    /* 
746
     *   unregister - this removes me from the global list, making this
747
     *   mapping inactive: after being unregistered, the parser won't apply
748
     *   this mapping to new commands 
749
     */
750
    unregisterGlobalRemapping()
751
    {
752
        GlobalRemapping.allGlobalRemappings.removeElement(self);
753
    }
754
755
    /* 
756
     *   Static class property: the master list of remappings.  We build
757
     *   this automatically at preinit time, and manipulate it via our
758
     *   constructor.
759
     */
760
    allGlobalRemappings = static new Vector(10)
761
762
    /* 
763
     *   static class property: the master list needs to be sorted; this is
764
     *   set to true each time we update the list, so that the list scanner
765
     *   knows to sort it before doing its scan 
766
     */
767
    listNeedsSorting = nil
768
;
769
770
771
772
/* ------------------------------------------------------------------------ */
773
/*
774
 *   Execute an action, as specified by an Action object.  We'll resolve
775
 *   the nouns in the action, then perform the action.
776
 */
777
executeAction(targetActor, targetActorPhrase,
778
              issuingActor, countsAsIssuerTurn, action)
779
{
780
    local rm, results;
781
782
startOver:
783
    /* check for a global remapping */
784
    rm = GlobalRemapping.findGlobalRemapping(
785
        issuingActor, targetActor, action);
786
    targetActor = rm[1];
787
    action = rm[2];
788
789
    /* create a basic results object to handle the resolution */
790
    results = new BasicResolveResults();
791
        
792
    /* set up the actors in the results object */
793
    results.setActors(targetActor, issuingActor);
794
795
    /* catch any "remap" signals while resolving noun phrases */
796
    try
797
    {
798
        /* resolve noun phrases */
799
        action.resolveNouns(issuingActor, targetActor, results);
800
    }
801
    catch (RemapActionSignal sig)
802
    {
803
        /* mark the new action as remapped */
804
        sig.action_.setRemapped(action);
805
        
806
        /* get the new action from the signal */
807
        action = sig.action_;
808
809
        /* start over with the new action */
810
        goto startOver;
811
    }
812
813
    /*
814
     *   Check to see if we should create an undo savepoint for the
815
     *   command.  If the action is not marked for inclusion in the undo
816
     *   log, there is no need to log a savepoint for it.
817
     *   
818
     *   Don't save undo for nested commands.  A nested command is part of
819
     *   a main command, and we only want to save undo for the main
820
     *   command, not for its individual sub-commands.  
821
     */
822
    if (action.includeInUndo
823
        && action.parentAction == nil
824
        && (targetActor.isPlayerChar()
825
            || (issuingActor.isPlayerChar() && countsAsIssuerTurn)))
826
    {
827
        /* 
828
         *   Remember the command we're about to perform, so that if we
829
         *   undo to here we'll be able to report what we undid.  Note that
830
         *   we do this *before* setting the savepoint, because we want
831
         *   after the undo to know the command we were about to issue at
832
         *   the savepoint.  
833
         */
834
        libGlobal.lastCommandForUndo = action.getOrigText();
835
        libGlobal.lastActorForUndo =
836
            (targetActorPhrase == nil
837
             ? nil
838
             : targetActorPhrase.getOrigText());
839
            
840
        /* 
841
         *   set a savepoint here, so that we on 'undo' we'll restore
842
         *   conditions to what they were just before we executed this
843
         *   command 
844
         */
845
        savepoint();
846
    }
847
    
848
    /* 
849
     *   If this counts as a turn for the issuer, adjust the issuer's busy
850
     *   time.
851
     *   
852
     *   However, this doesn't apply if the command is conversational (that
853
     *   is, it's something like "BOB, HELLO").  A conversational command
854
     *   is conceptually carried out by the issuer, not the target actor,
855
     *   since the action consists of the issuer actually saying something
856
     *   to the target actor.  The normal turn accounting in Action will
857
     *   count a conversational command this way, so we don't have to do
858
     *   the extra bookkeeping for such a command here.
859
     */
860
    if (countsAsIssuerTurn && !action.isConversational(issuingActor))
861
    {
862
        /* 
863
         *   note in the issuer that the target is the most recent
864
         *   conversational partner 
865
         */
866
        issuingActor.lastInterlocutor = targetActor;
867
        
868
        /* make the issuer busy for the order-giving interval */
869
        issuingActor.addBusyTime(nil,
870
                                 issuingActor.orderingTime(targetActor));
871
        
872
        /* notify the target that this will be a non-idle turn */
873
        targetActor.nonIdleTurn();
874
    }
875
876
    /*
877
     *   If the issuer is directing the command to a different actor, and
878
     *   it's not a conversational command, check with the target actor to
879
     *   see if it wants to accept the command.  Don't check
880
     *   conversational commands, since these aren't of the nature of
881
     *   orders to be obeyed.  
882
     */
883
    if (issuingActor != targetActor
884
        && !action.isConversational(issuingActor)
885
        && !targetActor.obeyCommand(issuingActor, action))
886
    {
887
        /* 
888
         *   if the issuing actor's "ordering time" is zero, make this take
889
         *   up a turn anyway, just for the refusal 
890
         */
891
        if (issuingActor.orderingTime(targetActor) == 0)
892
            issuingActor.addBusyTime(nil, 1);
893
894
        /*
895
         *   Since we're aborting the command, we won't get into the normal
896
         *   execution for it.  However, we might still want to save it for
897
         *   an attempted re-issue with AGAIN, so do so explicitly now. 
898
         */
899
        action.saveActionForAgain(issuingActor, countsAsIssuerTurn,
900
                                  targetActor, targetActorPhrase);
901
902
        /* 
903
         *   This command was rejected, so don't process it any further,
904
         *   and give up on processing any remaining commands on the same
905
         *   command line. 
906
         */
907
        throw new TerminateCommandException();
908
    }
909
    
910
    /* execute the action */
911
    action.doAction(issuingActor, targetActor, targetActorPhrase,
912
                    countsAsIssuerTurn);
913
}
914
915
/* ------------------------------------------------------------------------ */
916
/*
917
 *   Try an implicit action.
918
 *   
919
 *   Returns true if the action was attempted, whether or not it
920
 *   succeeded, nil if the command was not even attempted.  We will not
921
 *   attempt an implied command that verifies as "dangerous," since this
922
 *   means that it should be obvious to the player character that such a
923
 *   command should not be performed lightly.  
924
 */
925
_tryImplicitAction(issuingActor, targetActor, msgProp, actionClass, [objs])
926
{
927
    local action;
928
    
929
    /* create an instance of the desired action class */
930
    action = actionClass.createActionInstance();
931
932
    /* mark the action as implicit */
933
    action.setImplicit(msgProp);
934
935
    /* install the resolved objects in the action */
936
    action.setResolvedObjects(objs...);
937
938
    /* 
939
     *   For an implicit action, we must check the objects involved to make
940
     *   sure they're in scope.  If any of the objects aren't in scope,
941
     *   there is no way the actor would know to perform the command, so
942
     *   the command would not be implied in the first place.  Simply fail
943
     *   without trying the command.  
944
     */
945
    if (!action.resolvedObjectsInScope())
946
        return nil;
947
948
    /* 
949
     *   catch the abort-implicit signal, so we can turn it into a result
950
     *   code for our caller instead of an exception 
951
     */
952
    try
953
    {
954
        /* in NPC mode, add a command separator before each implied action */
955
        if (targetActor.impliedCommandMode() == ModeNPC)
956
            gTranscript.addCommandSep();
957
958
        /* execute the action */
959
        action.doAction(issuingActor, targetActor, nil, nil);
960
        
961
        /* 
962
         *   if the actor is in "NPC" mode for implied commands, do some
963
         *   extra work 
964
         */
965
        if (targetActor.impliedCommandMode() == ModeNPC)
966
        {
967
            /*   
968
             *   we're in NPC mode, so if the implied action failed, then
969
             *   act as though the command had never been attempted 
970
             */
971
            if (gTranscript.actionFailed(action))
972
            {
973
                /* the implied command failed - act like we didn't even try */
974
                return nil;
975
            }
976
977
            /* 
978
             *   In "NPC" mode, we display the results from implied
979
             *   commands as though they had been explicitly entered as
980
             *   separate actions.  So, add visual separation after the
981
             *   results from the implied command. 
982
             */
983
            gTranscript.addCommandSep();
984
        }
985
986
        /* tell the caller we at least tried to execute the command */
987
        return true;
988
    }
989
    catch (AbortImplicitSignal sig)
990
    {
991
        /* tell the caller we didn't execute the command at all */
992
        return nil;
993
    }
994
    catch (ParseFailureException exc)
995
    {
996
        /*
997
         *   Parse failure.  If the actor is in NPC mode, we can't have
998
         *   asked for a new command, so this must be some failure in
999
         *   processing the implied command itself; most likely, we tried
1000
         *   to resolve a missing object or the like and found that we
1001
         *   couldn't perform interactive resolution (because of the NPC
1002
         *   mode).  In this case, simply treat this as a failure of the
1003
         *   implied command itself, and act as though we didn't even try
1004
         *   the implied command.
1005
         *   
1006
         *   If the actor is in player mode, then we *can* perform
1007
         *   interactive resolution, so we won't have thrown a parser
1008
         *   failure before trying to solve the problem interactively.  The
1009
         *   failure must therefore be in an interactive response.  In this
1010
         *   case, simply re-throw the failure so that it reaches the main
1011
         *   parser. 
1012
         */
1013
        if (targetActor.impliedCommandMode() == ModeNPC)
1014
        {
1015
            /* NPC mode - the implied command itself failed */
1016
            return nil;
1017
        }
1018
        else
1019
        {
1020
            /* player mode - interactive resolution failed */
1021
            throw exc;
1022
        }
1023
    }
1024
}
1025
1026
/* ------------------------------------------------------------------------ */
1027
/*
1028
 *   Run a replacement action. 
1029
 */
1030
_replaceAction(actor, actionClass, [objs])
1031
{
1032
    /* run the replacement action as a nested action */
1033
    _nestedAction(true, actor, actionClass, objs...);
1034
1035
    /* the invoking command is done */
1036
    exit;
1037
}
1038
1039
/* ------------------------------------------------------------------------ */
1040
/*
1041
 *   Resolve and execute a replacement action.  This differs from the
1042
 *   normal replacement action execution in that the action we execute
1043
 *   requires resolution before execution.  
1044
 */
1045
resolveAndReplaceAction(newAction)
1046
{
1047
    /* prepare the replacement action */
1048
    prepareNestedAction(true, nil, newAction);
1049
    
1050
    /* 
1051
     *   resolve and execute the new action, using the same target and
1052
     *   issuing actors as the original action 
1053
     */
1054
    executeAction(gActor, nil, gIssuingActor, nil, newAction);
1055
    
1056
    /* the invoking command has been replaced, so it's done */
1057
    exit;
1058
}
1059
1060
/* ------------------------------------------------------------------------ */
1061
/*
1062
 *   Run an action as a new turn.  Returns the CommandTranscript describing
1063
 *   the action's results.
1064
 */
1065
_newAction(transcriptClass, issuingActor, targetActor, actionClass, [objs])
1066
{
1067
    local action;
1068
1069
    /* create an instance of the desired action class */
1070
    action = actionClass.createActionInstance();
1071
1072
    /* execute the command with the action instance */
1073
    return newActionObj(transcriptClass, issuingActor, targetActor,
1074
                        action, objs...);
1075
}
1076
1077
/*
1078
 *   Run an action as a new turn.  This is almost the same as _newAction,
1079
 *   but should be used when the caller has already explicitly created an
1080
 *   instance of the Action to be performed.
1081
 *   
1082
 *   If issuingActor is nil, we'll use the current global issuing actor; if
1083
 *   that's also nil, we'll use the target actor.
1084
 *   
1085
 *   Returns a CommandTranscript object describing the result of the
1086
 *   action.  
1087
 */
1088
newActionObj(transcriptClass, issuingActor, targetActor, actionObj, [objs])
1089
{
1090
    /* create the results object and install it as the global transcript */
1091
    return withCommandTranscript(transcriptClass, new function()
1092
    {
1093
        /* install the resolved objects in the action */
1094
        actionObj.setResolvedObjects(objs...);
1095
1096
        /* 
1097
         *   if the issuing actor isn't specified, use the current global
1098
         *   issuing actor; if that's also not set, use the target actor 
1099
         */
1100
        if (issuingActor == nil)
1101
            issuingActor = gIssuingActor;
1102
        if (issuingActor == nil)
1103
            issuingActor = targetActor;
1104
        
1105
        /* 
1106
         *   Execute the given action.  Because this is a new action,
1107
         *   execute the action in a new sense context for the given actor.
1108
         */
1109
        callWithSenseContext(targetActor.isPlayerChar()
1110
                             ? nil : targetActor, sight,
1111
            {: actionObj.doAction(issuingActor, targetActor, nil, nil)});
1112
1113
        /* return the current global transcript object */
1114
        return gTranscript;
1115
    });
1116
}
1117
1118
/* ------------------------------------------------------------------------ */
1119
/*
1120
 *   Run a nested action.  'isReplacement' has the same meaning as in
1121
 *   execNestedAction(). 
1122
 */
1123
_nestedAction(isReplacement, actor, actionClass, [objs])
1124
{
1125
    local action;
1126
1127
    /* create an instance of the desired action class */
1128
    action = actionClass.createActionInstance();
1129
1130
    /* install the resolved objects in the action */
1131
    action.setResolvedObjects(objs...);
1132
1133
    /* execute the new action */
1134
    execNestedAction(isReplacement, nil, actor, action);
1135
}
1136
1137
/*
1138
 *   Execute a fully-constructed nested action.
1139
 *   
1140
 *   'isReplacement' indicates whether the action is a full replacement or
1141
 *   an ordinary nested action.  If it's a replacement, then we use the
1142
 *   game time taken by the replacement, and set the enclosing action
1143
 *   (i.e., the current gAction) to take zero time.  If it's an ordinary
1144
 *   nested action, then we consider the nested action to take zero time,
1145
 *   using the current action's time as the overall command time.  
1146
 *   
1147
 *   'isRemapping' indicates whether or not this is a remapped action.  If
1148
 *   we're remapping from one action to another, this will be true; for
1149
 *   any other kind of nested or replacement action, this should be nil.  
1150
 */
1151
execNestedAction(isReplacement, isRemapping, actor, action)
1152
{
1153
    /* prepare the nested action */
1154
    prepareNestedAction(isReplacement, isRemapping, action);
1155
1156
    /* execute the new action in the actor's sense context */
1157
    callWithSenseContext(
1158
        actor.isPlayerChar() ? nil : actor, sight,
1159
        {: action.doAction(gIssuingActor, actor, nil, nil) });
1160
}
1161
1162
/*
1163
 *   Prepare a nested or replacement action for execution. 
1164
 */
1165
prepareNestedAction(isReplacement, isRemapping, action)
1166
{
1167
    /* 
1168
     *   if the original action is an implicit command, make the new
1169
     *   command implicit as well 
1170
     */
1171
    if (gAction.isImplicit)
1172
    {
1173
        /* 
1174
         *   make the new action implicit, but don't describe it as a
1175
         *   separate implicit command - it's effectively part of the
1176
         *   original implicit command 
1177
         */
1178
        action.setImplicit(nil);
1179
    }
1180
1181
    /* mark the new action as nested */
1182
    action.setNested();
1183
1184
    /* a nested action is part of the enclosing action */
1185
    action.setOriginalAction(gAction);
1186
1187
    /* if this is a remapping, mark it as such */
1188
    if (isRemapping)
1189
        action.setRemapped(gAction);
1190
1191
    /* 
1192
     *   Set either the nested action's time or the enclosing (current)
1193
     *   action's time to zero - we want to count only the time of one
1194
     *   command or the other.
1195
     *   
1196
     *   If we're running an ordinary nested command, set the nested
1197
     *   command's time to zero, since we want to consider it just a part
1198
     *   of the enclosing command and thus to take no time of its own.
1199
     *   
1200
     *   If we're running a full replacement command, and we're replacing
1201
     *   something other than an implied command, don't consider the
1202
     *   enclosing command to take any time, since the enclosing command is
1203
     *   carrying out its entire function via the replacement and thus
1204
     *   requires no time of its own.  If we're replacing an implied
1205
     *   command, this doesn't apply, since the implied command defers to
1206
     *   its enclosing command for timing.  If we're replacing a command
1207
     *   that already has zero action time, this also doesn't apply, since
1208
     *   we're presumably replacing a command that's itself nested.  
1209
     */
1210
    if (isReplacement && !gAction.isImplicit && gAction.actionTime != 0)
1211
        gAction.zeroActionTime();
1212
    else
1213
        action.actionTime = 0;
1214
}
1215
1216
/* ------------------------------------------------------------------------ */
1217
/*
1218
 *   Run a previously-executed command as a nested action, re-resolving
1219
 *   all of its objects to ensure they are still valid.
1220
 */
1221
nestedActionAgain(action)
1222
{
1223
    /* reset the any cached information for the new command context */
1224
    action.resetAction();
1225
1226
    /* mark the action as nested */
1227
    action.setNested();
1228
    action.setOriginalAction(gAction);
1229
1230
    /* 
1231
     *   do not count any time for the nested action, since it's merely
1232
     *   part of the main turn and doesn't count as a separate turn of its
1233
     *   own 
1234
     */
1235
    action.actionTime = 0;
1236
1237
    /* execute the command */
1238
    executeAction(gActor, nil, gIssuingActor, nil, action);
1239
}
1240
1241
1242
/* ------------------------------------------------------------------------ */
1243
/*
1244
 *   Run some code in a simulated Action environment.  We'll create a dummy
1245
 *   instance of the given Action class, and set up a command transcript,
1246
 *   then invoke the function.  This is useful for writing daemon code that
1247
 *   needs to invoke other code that's set up to expect a normal action
1248
 *   processing environment.  
1249
 */
1250
withActionEnv(actionClass, actor, func)
1251
{
1252
    local oldAction, oldActor;
1253
1254
    /* remember the old globals */
1255
    oldAction = gAction;
1256
    oldActor = gActor;
1257
1258
    try
1259
    {
1260
        /* set up a dummy action */
1261
        gAction = actionClass.createInstance();
1262
1263
        /* use the player character as the actor */
1264
        gActor = actor;
1265
1266
        /* 
1267
         *   execute the function with a command transcript active; obtain
1268
         *   and return the return value of the function 
1269
         */
1270
        return withCommandTranscript(CommandTranscript, func);
1271
    }
1272
    finally
1273
    {
1274
        /* restore globals on the way out */
1275
        gAction = oldAction;
1276
        gActor = oldActor;
1277
    }
1278
}
1279
1280
1281
/* ------------------------------------------------------------------------ */
1282
/*
1283
 *   Exit signal.  This signal indicates that we're finished with the
1284
 *   entire command execution sequence for an action; the remainder of the
1285
 *   command execution sequence is to be skipped for the action.  Throw
1286
 *   this from within the command execution sequence in order to skip
1287
 *   directly to the end-of-turn processing.  This skips everything
1288
 *   remaining in the action, including after-action notification and the
1289
 *   like.  This signal skips directly past the 'afterAction' phase of the
1290
 *   command.
1291
 *   
1292
 *   Note that this doesn't prevent further processing of the same command
1293
 *   if there are multiple objects involved, and it doesn't affect
1294
 *   processing of additional commands on the same command line.  If you
1295
 *   want to cancel further iteration of the same command for additional
1296
 *   objects, call gAction.cancelIteration().  
1297
 */
1298
class ExitSignal: Exception
1299
;
1300
1301
/*
1302
 *   Exit Action signal.  This signal indicates that we're finished with
1303
 *   the execAction portion of processing the command, but we still want
1304
 *   to proceed with the rest of the command as normal.  This can be used
1305
 *   when a step in the action processing wants to preempt any of the more
1306
 *   default processing that would normally follow.  This skips directly
1307
 *   to the 'afterAction' phase of the command.
1308
 *   
1309
 *   Note that this doesn't prevent further processing of the same command
1310
 *   if there are multiple objects involved, and it doesn't affect
1311
 *   processing of additional commands on the same command line.  If you
1312
 *   want to cancel further iteration of the same command for additional
1313
 *   objects, call gAction.cancelIteration().  
1314
 */
1315
class ExitActionSignal: Exception
1316
;
1317
1318
/*
1319
 *   Abort implicit command signal.  This exception indicates that we are
1320
 *   aborting an implicit command without having tried to execute the
1321
 *   command at all.  This is thrown when an implied command is to be
1322
 *   aborted before it's even attempted, such as when verification shows
1323
 *   the command is obviously dangerous and thus should never be attempted
1324
 *   without the player having explicitly requesting it.  
1325
 */
1326
class AbortImplicitSignal: Exception
1327
;
1328
1329
/* ------------------------------------------------------------------------ */
1330
/*
1331
 *   Action Remap signal.  This signal can be thrown only during the noun
1332
 *   phrase resolution phase of execution, and indicates that we want to
1333
 *   remap the action to a different action, specified in the signal.
1334
 *   
1335
 *   This is useful when an object is always used in a special way, so
1336
 *   that a generic verb used with the object must be mapped to a more
1337
 *   specific verb on the object.  For example, a game with a generic USE
1338
 *   verb might convert USE PAINTBRUSH ON WALL to PAINT WALL WITH
1339
 *   PAINTBRUSH by remapping the UseWith action to a PaintWith action
1340
 *   instead.  
1341
 */
1342
class RemapActionSignal: Exception
1343
    construct(action)
1344
    {
1345
        /* remember the new action */
1346
        action_ = action;
1347
    }
1348
1349
    /* the new action that should replace the original action */
1350
    action_ = nil
1351
;
1352
1353
/*
1354
 *   Remap a 'verify' method for a remapped action.  This is normally
1355
 *   invoked through the remapTo() macro.  
1356
 */
1357
remapVerify(oldRole, resultSoFar, remapInfo)
1358
{
1359
    local newAction;
1360
    local objs;
1361
    local idx;
1362
    local newRole;
1363
1364
    /* extract new action's object list from the remapping info list */
1365
    objs = remapInfo.sublist(2);
1366
1367
    /* 
1368
     *   Create a new action object.  We only perform verification
1369
     *   remapping during the resolution phase of the command processing,
1370
     *   because once we've finished resolving, we'll actually replace the
1371
     *   action with the remapped action and thus won't have to remap
1372
     *   verification (or anything else) at that point.  So, pass true for
1373
     *   the in-resolve flag to the action creation routine. 
1374
     */
1375
    newAction = remapActionCreate(true, oldRole, remapInfo);
1376
1377
    /* 
1378
     *   Find the object that's given as a resolved object, rather than as
1379
     *   a DirectObject (etc) identifier - the one given as a specific
1380
     *   object is the one that corresponds to the original object.  
1381
     */
1382
    idx = objs.indexWhich({x: dataType(x) == TypeObject});
1383
1384
    /* get the role identifier (DirectObject, etc) for the slot position */
1385
    newRole = newAction.getRoleFromIndex(idx);
1386
1387
    /* if we don't yet have a result list object, create one */
1388
    if (resultSoFar == nil)
1389
        resultSoFar = new VerifyResultList();
1390
1391
    /* if we found a remapping, verify it */
1392
    if (idx != nil)
1393
    {
1394
        /* 
1395
         *   Remember the remapped object in the result list.  Note that we
1396
         *   do this first, before calling the remapped verification
1397
         *   property, so that our call to the remapped verification
1398
         *   property will overwrite this setting if it does further
1399
         *   remapping.  We want the ultimate target object represented
1400
         *   here, after all remappings are finished.  
1401
         */
1402
        resultSoFar.remapAction_ = newAction;
1403
        resultSoFar.remapTarget_ = objs[idx];
1404
        resultSoFar.remapRole_ = newRole;
1405
1406
        /* install the new action as the current action while verifying */
1407
        local oldAction = gAction;
1408
        gAction = newAction;
1409
1410
        try
1411
        {
1412
            /* call verification on the new object in the new role */
1413
            return newAction.callVerifyProp(
1414
                objs[idx], 
1415
                newAction.getVerifyPropForRole(newRole),
1416
                newAction.getPreCondPropForRole(newRole),
1417
                newAction.getRemapPropForRole(newRole),
1418
                resultSoFar, newRole);
1419
        }
1420
        finally
1421
        {
1422
            /* restore the old gAction on the way out */
1423
            gAction = oldAction;
1424
        }
1425
    }
1426
    else
1427
    {
1428
        /* there's no remapping, so there's nothing to verify */
1429
        return resultSoFar;
1430
    }
1431
}
1432
1433
/*
1434
 *   Perform a remapping to a new action.  This is normally invoked
1435
 *   through the remapTo() macro.  
1436
 */
1437
remapAction(inResolve, oldRole, remapInfo)
1438
{
1439
    local newAction;
1440
1441
    /* get the new action */
1442
    newAction = remapActionCreate(inResolve, oldRole, remapInfo);
1443
1444
    /* 
1445
     *   replace the current action, using the appropriate mechanism
1446
     *   depending on the current processing phase 
1447
     */
1448
    if (inResolve)
1449
    {
1450
        /* 
1451
         *   we're still resolving the objects, so we must use a signal to
1452
         *   start the resolution process over for the new action 
1453
         */
1454
        throw new RemapActionSignal(newAction);
1455
    }
1456
    else
1457
    {
1458
        /* 
1459
         *   We've finished resolving everything, so we can simply use the
1460
         *   new action as a replacement action.
1461
         */
1462
        execNestedAction(true, true, gActor, newAction);
1463
1464
        /* 
1465
         *   the remapped action replaces the original action, so
1466
         *   terminate the original action 
1467
         */
1468
        exit;
1469
    }
1470
}
1471
1472
/*
1473
 *   Create a new action object for the given remapped action. 
1474
 */
1475
remapActionCreate(inResolve, oldRole, remapInfo)
1476
{
1477
    local newAction;
1478
    local newObjs;
1479
    local newActionClass;
1480
    local objs;
1481
1482
    /* get the new action class and object list from the remap info */
1483
    newActionClass = remapInfo[1];
1484
    objs = remapInfo.sublist(2);
1485
    
1486
    /* 
1487
     *   create a new instance of the replacement action, carrying forward
1488
     *   the properties of the original (current) action 
1489
     */
1490
    newAction = newActionClass.createActionFrom(gAction);
1491
1492
    /* remember the original action we're remapping */
1493
    newAction.setOriginalAction(gAction);
1494
1495
    /* set up an empty vector for the match trees for the new action */
1496
    newObjs = new Vector(objs.length());
1497
1498
    /* remap according to the phase of the execution */
1499
    if (inResolve)
1500
    {
1501
        /* translate the object mappings */
1502
        foreach (local cur in objs)
1503
        {
1504
            /* check what we have to translate */
1505
            if (dataType(cur) == TypeEnum)
1506
            {
1507
                /* 
1508
                 *   it's an object role - if it's the special OtherObject
1509
                 *   designator, get the other role of a two-object
1510
                 *   command 
1511
                 */
1512
                if (cur == OtherObject)
1513
                    cur = gAction.getOtherObjectRole(oldRole);
1514
                
1515
                /* 
1516
                 *   get the match tree for this role from the old action
1517
                 *   and add it to our list 
1518
                 */
1519
                newObjs.append(gAction.getMatchForRole(cur));
1520
            }
1521
            else
1522
            {
1523
                /* append the new ResolveInfo to the new object list */
1524
                newObjs.append(gAction.getResolveInfo(cur, oldRole));
1525
            }
1526
        }
1527
1528
        /* set the object matches in the new action */
1529
        newAction.setObjectMatches(newObjs.toList()...);
1530
    }
1531
    else
1532
    {
1533
        /* translate the object mappings */
1534
        foreach (local cur in objs)
1535
        {
1536
            /* check what we have to translate */
1537
            if (dataType(cur) != TypeEnum)
1538
            {
1539
                /* it's an explicit object - use it directly */
1540
                newObjs.append(cur);
1541
            }
1542
            else
1543
            {
1544
                /* it's a role - translate OtherObject if needed */
1545
                if (cur == OtherObject)
1546
                    cur = gAction.getOtherObjectRole(oldRole);
1547
               
1548
                /* get the resolved object for this role */
1549
                newObjs.append(gAction.getObjectForRole(cur));
1550
            }
1551
        }
1552
1553
        /* set the resolved objects in the new action */
1554
        newAction.setResolvedObjects(newObjs.toList()...);
1555
    }
1556
1557
    /* return the new action */
1558
    return newAction;
1559
}
1560
1561
/* ------------------------------------------------------------------------ */
1562
/*
1563
 *   Result message object.  This is used for verification results and
1564
 *   main command reports, which must keep track of messages to display.  
1565
 */
1566
class MessageResult: object
1567
    /* 
1568
     *   Construct given literal message text, or alternatively a property
1569
     *   of the current actor's verb messages object.  In either case,
1570
     *   we'll expand the message immediately to allow the message to be
1571
     *   displayed later with any parameters fixed at the time the message
1572
     *   is constructed.  
1573
     */
1574
    construct(msg, [params])
1575
    {
1576
        /* if we're based on an existing object, copy its characteristics */
1577
        if (dataType(msg) == TypeObject && msg.ofKind(MessageResult))
1578
        {
1579
            /* base it on the existing object */
1580
            messageText_ = msg.messageText_;
1581
            messageProp_ = msg.messageProp_;
1582
            return;
1583
        }
1584
1585
        /* 
1586
         *   if the message was given as a property, remember the property
1587
         *   for identification purposes 
1588
         */
1589
        if (dataType(msg) == TypeProp)
1590
            messageProp_ = msg;
1591
1592
        /* 
1593
         *   Resolve the message and store the text.  Use the action's
1594
         *   objects (the direct object, indirect object, etc) as the
1595
         *   sources for message overrides - this makes it easy to override
1596
         *   messages on a per-object basis without having to rewrite the
1597
         *   whole verify/check/action routines. 
1598
         */
1599
        messageText_ = resolveMessageText(gAction.getCurrentObjects(),
1600
                                          msg, params);
1601
    }
1602
1603
    /*
1604
     *   Static method: resolve a message.  If the message is given as a
1605
     *   property, we'll look up the message in the given source objects
1606
     *   and in the actor's "action messages" object.  We'll return the
1607
     *   resolved message string.  
1608
     */
1609
    resolveMessageText(sources, msg, params)
1610
    {
1611
        /*
1612
         *   The message can be given either as a string or as a property
1613
         *   of the actor's verb message object.  If it's the latter, look
1614
         *   up the text of the property from the appropriate object.  
1615
         */
1616
    findTextSource:
1617
        if (dataType(msg) == TypeProp)
1618
        {
1619
            local msgObj;
1620
            
1621
            /* 
1622
             *   Presume that we'll read the message from the current
1623
             *   actor's "action message object."  This is typically
1624
             *   playerActionMessages or npcActionMessages, but it's up to
1625
             *   the actor to specify which object we get our messages
1626
             *   from.  
1627
             */
1628
            msgObj = gActor.getActionMessageObj();
1629
            
1630
            /*
1631
             *   First, look up the message property in the action's
1632
             *   objects (the direct object, indirect object, etc).  This
1633
             *   makes it easy to override messages on a per-object basis
1634
             *   without having to rewrite the whole verify/check/action
1635
             *   routine.  
1636
             */
1637
            foreach (local cur in sources)
1638
            {
1639
                /* check to see if this object defines the message property */
1640
                if (cur != nil && cur.propDefined(msg))
1641
                {
1642
                    local res;
1643
                    
1644
                    /*   
1645
                     *   This object defines the property, so check what
1646
                     *   we have. 
1647
                     */
1648
                    switch (cur.propType(msg))
1649
                    {
1650
                    case TypeProp:
1651
                        /* 
1652
                         *   It's another property, so we're being
1653
                         *   directed back to the player action message
1654
                         *   object.  The object does override the
1655
                         *   message, but the override points to another
1656
                         *   message property in the action object message
1657
                         *   set.  Simply redirect 'msg' to point to the
1658
                         *   new property, and use the same action message
1659
                         *   object we already assumed we'd use.  
1660
                         */
1661
                        msg = cur.(msg);
1662
                        break;
1663
1664
                    case TypeSString:
1665
                        /* it's a simple string - retrieve it */
1666
                        msg = cur.(msg);
1667
1668
                        /* 
1669
                         *   since it's just a string, we're done finding
1670
                         *   the message text - there's no need to do any
1671
                         *   further property lookup, since we've obviously
1672
                         *   reached the end of that particular line 
1673
                         */
1674
                        break findTextSource;
1675
1676
                    case TypeCode:
1677
                        /*
1678
                         *   Check the parameter count - we'll allow this
1679
                         *   method to take the full set of parameters, or
1680
                         *   no parameters at all.  We allow the no-param
1681
                         *   case for convenience in cases where the method
1682
                         *   simply wants to return a string or property ID
1683
                         *   from a short method that doesn't need to know
1684
                         *   the parameters; in these cases, it's
1685
                         *   syntactically a lot nicer looking to write it
1686
                         *   as a "prop = (expresion)" than to write the
1687
                         *   full method-with-params syntax. 
1688
                         */
1689
                        if (cur.getPropParams(msg) == [0, 0, nil])
1690
                            res = cur.(msg);
1691
                        else
1692
                            res = cur.(msg)(params...);
1693
1694
                        /*
1695
                         *   If that returned nil, ignore it entirely and
1696
                         *   keep scanning the remaining source objects.
1697
                         *   The object must have decided it didn't want to
1698
                         *   provide the message override in this case
1699
                         *   after all. 
1700
                         */
1701
                        if (res == nil)
1702
                            continue;
1703
1704
                        /* we didn't get nil, so use the result */
1705
                        msg = res;
1706
1707
                        /* 
1708
                         *   if we got a string, we've fully resolved the
1709
                         *   message text, so we can stop searching for it 
1710
                         */
1711
                        if (dataType(msg) == TypeSString)
1712
                            break findTextSource;
1713
1714
                        /*
1715
                         *   It's not nil and it's not a string, so it must
1716
                         *   be a property ID.  In this case, the property
1717
                         *   ID is a property to evaluate in the normal
1718
                         *   action message object.  Simply proceed to
1719
                         *   evaluate the new message property as normal.  
1720
                         */
1721
                        break;
1722
1723
                    case TypeNil:
1724
                        /* 
1725
                         *   it's explicitly nil, which simply means to
1726
                         *   ignore this definition; keep scanning other
1727
                         *   source objects 
1728
                         */
1729
                        continue;
1730
1731
                    default:
1732
                        /* 
1733
                         *   In any other case, this must simply be the
1734
                         *   message we're to use.  For this case, the
1735
                         *   source of the message is this object, so
1736
                         *   forget about the normal action message object
1737
                         *   and instead use the current object.  Then
1738
                         *   proceed to evaluate the message property as
1739
                         *   normal, which will fetch it from the current
1740
                         *   object.  
1741
                         */
1742
                        msgObj = cur;
1743
                        break;
1744
                    }
1745
1746
                    /* 
1747
                     *   we found a definition, so we don't need to look
1748
                     *   at any of the other objects involved in the
1749
                     *   action - we just use the first override we find 
1750
                     */
1751
                    break;
1752
                }
1753
            }
1754
1755
            /* look up the message in the actor's message generator */
1756
            msg = msgObj.(msg)(params...);
1757
        }
1758
1759
        /* 
1760
         *   format the string and remember the result - do the formatting
1761
         *   immediately, because we want to make sure we expand any
1762
         *   substitution parameters in the context of the current
1763
         *   command, since the parameters might change (and thus alter
1764
         *   the meaning of the message) by the time it's displayed 
1765
         */
1766
        msg = langMessageBuilder.generateMessage(msg);
1767
1768
        /* 
1769
         *   "quote" the message text - it's fully expanded now, so
1770
         *   there's no need to further expand anything that might by
1771
         *   coincidence look like substitution parameters in its text 
1772
         */
1773
        msg = langMessageBuilder.quoteMessage(msg);
1774
1775
        /* return the resolved message string */
1776
        return msg;
1777
    }
1778
1779
    /* 
1780
     *   set a new message, given the same type of information as we'd use
1781
     *   to construct the object 
1782
     */
1783
    setMessage(msg, [params])
1784
    {
1785
        /* simply invoke the constructor to re-fill the message data */
1786
        construct(msg, params...);
1787
    }
1788
1789
    /*
1790
     *   Display a message describing why the command isn't allowed. 
1791
     */
1792
    showMessage()
1793
    {
1794
        /* show our message string */
1795
        say(messageText_);
1796
    }
1797
1798
    /* the text of our result message */
1799
    messageText_ = nil
1800
1801
    /* the message property, if we have one */
1802
    messageProp_ = nil
1803
;
1804