exec.t

documentation
 #charset "us-ascii"
 
 /* 
  *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
  *   
  *   TADS 3 Library: command execution
  *   
  *   This module defines functions that perform command execution.  
  */
 
 #include "adv3.h"
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Execute a command line, as issued by the given actor and as given as a
  *   list of tokens.
  *   
  *   If 'firstInSentence' is true, we're at the start of a "sentence."  The
  *   meaning and effect of this may vary by language.  In English, a
  *   sentence ends with certain punctuation marks (a period, semicolon,
  *   exclamation mark, or question mark), so anything after one of these
  *   punctuation marks is the start of a new sentence.  Also in English, we
  *   can address a command to an explicit target actor using the "actor,"
  *   prefix syntax, which we can't use except at the start of a sentence.
  *   
  *   If the command line consists of multiple commands, we will only
  *   actually execute the first command before returning.  We'll schedule
  *   any additional commands for later execution by putting them into the
  *   target actor's pending command queue before we return, but we won't
  *   actually execute them.  
  */
 executeCommand(targetActor, issuingActor, toks, firstInSentence)
 {
     local actorPhrase;
     local actorSpecified;
 
     /*
      *   Turn on sense caching while we're working, until execution
      *   begins.  The parsing and resolution phases of command processing
      *   don't involve any changes to game state, so we can safely cache
      *   sense information; caching sense information during these phases
      *   is desirable because these steps (noun resolution in particular)
      *   involve repeated inspection of the current sensory environment,
      *   which can require expensive calculations.  
      */
     libGlobal.enableSenseCache();
 
     /* we don't have an explicit actor phrase yet */
     actorPhrase = nil;
     
     /* presume an actor will not be specified */
     actorSpecified = nil;
     
     /*
      *   If this is the start of a new sentence, and the issuing actor
      *   wants to cancel any target actor designation at the end of each
      *   sentence, change the target actor back to the issuing actor.  
      */
     if (firstInSentence
         && issuingActor != targetActor
         && issuingActor.revertTargetActorAtEndOfSentence)
     {
         /* switch to the target actor */
         targetActor = issuingActor;
 
         /* switch to the issuer's sense context */
         senseContext.setSenseContext(targetActor, sight);
     }
 
     /*
      *   Keep going until we've processed the command.  This might take
      *   several iterations, because we might have replacement commands to
      *   execute.  
      */
 parseTokenLoop:
     for (;;)
     {
         local lst;
         local action;
         local match;
         local nextIdx;
         local nextCommandTokens;
         local extraIdx;
         local extraTokens;
         
         /*
          *   Catch any errors that occur while executing the command,
          *   since some of them are signals to us that we should reparse
          *   some new text read or generated deep down inside the command
          *   processing.  
          */
         try
         {
             local rankings;
 
             /* we have no extra tokens yet */
             extraTokens = [];
 
             /* 
              *   Parse the token list.  If this is the first command on
              *   the command line, allow an actor prefix.  Otherwise, just
              *   look for a command.  
              */
             lst = (firstInSentence ? firstCommandPhrase : commandPhrase)
                   .parseTokens(toks, cmdDict);
 
             /*
              *   As a first cut at reducing the list of possible matches
              *   to those that make sense, eliminate from this list any
              *   matches which do not have valid actions.  In grammars for
              *   "scrambling" languages (i.e., languages with flexible
              *   word ordering), it is possible to construct commands that
              *   fit the grammatical rules of sentence construction but
              *   which make no sense because of the specific constraints
              *   of the verbs involved; we can filter out such nonsense
              *   interpretations immediately by keeping only those
              *   structural interpretations that can resolve to valid
              *   actions.  
              */
             lst = lst.subset(
                 {x: x.resolveFirstAction(issuingActor, targetActor) != nil});
 
             /* if we have no matches, the command isn't understood */
             if (lst.length() == 0)
             {
                 /* 
                  *   If this is a first-in-sentence phrase, try looking for
                  *   a target actor phrase.  If we can find one, we can
                  *   direct the 'oops' to that actor, to allow the game to
                  *   customize messages more specifically.  
                  */
                 if (firstInSentence)
                 {
                     /* try parsing an "actor, <unknown>" phrase */
                     lst = actorBadCommandPhrase.parseTokens(toks, cmdDict);
 
                     /* if we got any matches, try to resolve actors */
                     lst = lst.mapAll({x: x.resolveNouns(
                         issuingActor, issuingActor,
                         new ActorResolveResults())});
 
                     /* drop any that didn't yield any results */
                     lst = lst.subset({x: x != nil && x.length() != 0});
 
                     /* 
                      *   if anything's left, arbitrarily pick the first one
                      *   and use that as the new target actor 
                      */
                     if (lst.length() != 0)
                         targetActor = lst[1][1].obj_;
                 }
 
                 /* 
                  *   We don't understand the command.  Check for unknown
                  *   words - if we have any, give them a chance to use
                  *   OOPS to correct a typo.  
                  */
                 tryOops(toks, issuingActor, targetActor,
                         1, toks, rmcCommand);
 
                 /* 
                  *   try running it by the SpecialTopic history to see if
                  *   they're trying to use a special topic in the wrong
                  *   context - if so, explain that they can't use the
                  *   command right now, rather than claiming that the
                  *   command is completely invalid 
                  */
                 if (specialTopicHistory.checkHistory(toks))
                 {
                     /* the special command is not currently available */
                     targetActor.notifyParseFailure(
                         issuingActor, &specialTopicInactive, []);
                 }
                 else
                 {
                     /* tell the issuer we didn't understand the command */
                     targetActor.notifyParseFailure(
                         issuingActor, &commandNotUnderstood, []);
                 }
                 
                 /* 
                  *   we're done with this command, and we want to abort
                  *   any subsequent commands on the command line 
                  */
                 return;
             }
 
             /* show the matches if we're in debug mode */
             dbgShowGrammarList(lst);
         
             /* 
              *   Perform a tentative resolution on each alternative
              *   structural interpretation of the command, and rank the
              *   interpretations in order of "goodness" as determined by
              *   our ranking criteria encoded in CommandRanking.
              *   
              *   Note that we perform the tentative resolution and ranking
              *   even if we have only one interpretation, because the
              *   tentative resolution is often useful for the final
              *   resolution pass.  
              */
             rankings = CommandRanking
                        .sortByRanking(lst, issuingActor, targetActor);
                 
             /* 
              *   Take the interpretation that came up best in the rankings
              *   (or, if we have multiple at the same ranking, arbitrarily
              *   pick the one that happened to come up first in the list)
              *   - they're ranked in descending order of goodness, so take
              *   the first one.
              */
             match = rankings[1].match;
 
             /* if we're in debug mode, show the winner */
             dbgShowGrammarWithCaption('Winner', match);
             
             /*
              *   Get the token list for the rest of the command after what
              *   we've parsed so far.
              *   
              *   Note that we'll start over and parse these tokens anew,
              *   even though we might have parsed them already into a
              *   subcommand on the previous iteration.  Even if we already
              *   parsed these tokens, we want to parse them again, because
              *   we did not have a suitable context for evaluating the
              *   semantic strengths of the possible structural
              *   interpretations this command on the previous iteration;
              *   for example, it was not possible to resolve noun phrases
              *   in this command because we could not guess what the scope
              *   would be when we got to this point.  So, we'll simply
              *   discard the previous match tree, and start over, treating
              *   the new tokens as a brand new command.  
              */
             nextIdx = match.getNextCommandIndex();
             nextCommandTokens = toks.sublist(nextIdx);
 
             /* if the pending command list is empty, make it nil */
             if (nextCommandTokens.length() == 0)
                 nextCommandTokens = nil;
 
             /*
              *   Get the part of the token list that the match doesn't use
              *   at all.  These are the tokens following the tokens we
              *   matched. 
              */
             extraIdx = match.tokenList.length() + 1;
             extraTokens = toks.sublist(extraIdx);
 
             /*
              *   We now have the best match for the command tree.
              *   
              *   If the command has an actor clause, resolve the actor, so
              *   that we can direct the command to that actor.  If the
              *   command has no actor clause, the command is to the actor
              *   who issued the command.
              *   
              *   Do NOT process the actor clause if we've already done so.
              *   If we edit the token list and retry the command after
              *   this point, there is no need to resolve the actor part
              *   again.  Doing so could be bad - if resolving the actor
              *   required player interaction, such as asking for help with
              *   an ambiguous noun phrase, we do not want to go through
              *   the same interaction again just because we have to edit
              *   and retry a later part of the command.  
              */
             if (match.hasTargetActor())
             {
                 local actorResults;
 
                 /*
                  *   If we haven't yet explicitly specified a target
                  *   actor, and the default target actor is different from
                  *   the issuing actor, then this is really a new command
                  *   from the issuing actor.
                  *   
                  *   First, an actor change mid-command is allowed only if
                  *   the issuing actor waits for NPC commands to be
                  *   carried out; if not, then we can't change the actor
                  *   mid-command.
                  *   
                  *   Second, since the command comes from the issuing
                  *   actor, not from the default target actor, we want to
                  *   issue the orders on the issuing actor's turn, so put
                  *   the command into the queue for the issuer, and let
                  *   the issuer re-parse the command on the issuer's next
                  *   turn.  
                  */
                 if (!actorSpecified && issuingActor != targetActor)
                 {
                     /* 
                      *   don't allow the command if the issuing actor
                      *   doesn't wait for orders to be completed 
                      */
                     if (!issuingActor.issueCommandsSynchronously)
                     {
                         /* turn off any sense capturing */
                         senseContext.setSenseContext(nil, sight);
 
                         /* show the error */
                         issuingActor.getParserMessageObj()
                             .cannotChangeActor();
 
                         /* done */
                         return;
                     }
 
                     /* put the command into the issuer's queue */
                     issuingActor.addFirstPendingCommand(
                         firstInSentence, issuingActor, toks);
 
                     /* done */
                     return;
                 }
 
                 /* create an actor-specialized results object */
                 actorResults = new ActorResolveResults();
                 
                 /* set up the actors in the results object */
                 actorResults.setActors(targetActor, issuingActor);
                 
                 /* resolve the actor object */
                 match.resolveNouns(issuingActor, targetActor, actorResults);
 
                 /* get the target actor from the command */
                 targetActor = match.getTargetActor();
 
                 /* pull out the phrase specifying the actor */
                 actorPhrase = match.getActorPhrase();
 
                 /*
                  *   Copy antecedents from the issuing actor to the target
                  *   actor.  Since the issuer is giving us a new command
                  *   here, pronouns will be given from the issuer's
                  *   perspective.  
                  */
                 targetActor.copyPronounAntecedentsFrom(issuingActor);
 
                 /* let the actor phrase know we're actually using it */
                 match.execActorPhrase(issuingActor);
                 
                 /* 
                  *   Ask the target actor if it's interested in the
                  *   command at all.  This only applies when the actor was
                  *   actually specified - if an actor wasn't specified,
                  *   the command is either directed to the issuer itself,
                  *   in which case the command will always be accepted; or
                  *   the command is a continuation of a command line
                  *   previously accepted.  
                  */
                 if (!targetActor.acceptCommand(issuingActor))
                 {
                     /* 
                      *   the command was immediately rejected - abandon
                      *   the command and any subsequent commands on the
                      *   same line 
                      */
                     return;
                 }
 
                 /* note that an actor was specified */
                 actorSpecified = true;
 
                 /*
                  *   Pull out the rest of the command (other than the
                  *   target actor specification) and start over with a
                  *   fresh parse of the whole thing.  We must do this
                  *   because our tentative resolution pass that we used to
                  *   pick the best structural interpretation of the
                  *   command couldn't go far enough - since we didn't know
                  *   the actor involved, we weren't able to resolve nouns
                  *   in the rest of the command.  Now that we know the
                  *   actor, we can start over and resolve everything in
                  *   the rest of the command, and thus choose the right
                  *   structural match for the command.  
                  */
                 toks = match.getCommandTokens();
 
                 /* what follows obviously isn't first in the sentence */
                 firstInSentence = nil;
 
                 /* go back to parse the rest */
                 continue parseTokenLoop;
             }
 
             /* pull out the first action from the command */        
             action = match.resolveFirstAction(issuingActor, targetActor);
 
             /*
              *   If the winning interpretation had any unknown words, run
              *   a resolution pass to resolve those interactively, if
              *   possible.  We want to do this before doing any other
              *   interactive resolution because OOPS has the unique
              *   property of forcing us to reparse the command; if we
              *   allowed any other interactive resolution to happen before
              *   processing an OOPS, we'd likely have to repeat the other
              *   resolution on the reparse, which would confuse and
              *   irritate users by asking the same question more than once
              *   for what is apparently the same command.  
              */
             if (rankings[1].unknownWordCount != 0)
             {
                 /* 
                  *   resolve using the OOPS results gatherer - this runs
                  *   essentially the same preliminary resolution process
                  *   as the ranking results gatherer, but does perform
                  *   interactive resolution of unknown words via OOPS 
                  */
                 match.resolveNouns(
                     issuingActor, targetActor,
                     new OopsResults(issuingActor, targetActor));
             }
 
             /* 
              *   If the command is directed to a different actor than the
              *   issuer, change to the target actor's sense context.  
              */
             if (actorSpecified && targetActor != issuingActor)
                 senseContext.setSenseContext(targetActor, sight);
 
             /* set up a transcript to receive the command results */
             withCommandTranscript(CommandTranscript, new function()
             {
                 /* 
                  *   Execute the action.
                  *   
                  *   If a target actor was specified, and it's not the same
                  *   as the issuing actor, this counts as a turn for the
                  *   issuing actor.  
                  */
                 executeAction(targetActor, actorPhrase, issuingActor,
                               actorSpecified && issuingActor != targetActor,
                               action);
             });
 
             /*
              *   If we have anything remaining on the command line, insert
              *   the remaining commands at the start of the target actor's
              *   command queue, since we want the target actor to continue
              *   with the rest of this command line as its next operation.
              *   
              *   Since the remainder of the line represents part of the
              *   work the actor pulled out of the queue in order to call
              *   us, put the remainder back in at the START of the actor's
              *   queue - it was before anything else that might be in the
              *   actor's queue before, so it should stay ahead of anything
              *   else now.  
              */
             if (nextCommandTokens != nil)
             {
                 /* 
                  *   Prepend the remaining commands to the actor's queue.
                  *   The next command is the start of a new sentence if
                  *   the command we just executed ends the sentence. 
                  */
                 targetActor.addFirstPendingCommand(
                     match.isEndOfSentence(), issuingActor, nextCommandTokens);
             }
 
             /*
              *   If the command was directed from the issuer to a
              *   different target actor, and the issuer wants to wait for
              *   the full set of issued commands to complete before
              *   getting another turn, tell the issuer to begin waiting.  
              */
             if (actorSpecified && issuingActor != targetActor)
                 issuingActor.waitForIssuedCommand(targetActor);
 
             /* we're done */
             return;
         }
         catch (ParseFailureException rfExc)
         {
             /*
              *   Parsing failed in such a way that we cannot proceed.
              *   Tell the target actor to notify the issuing actor.  
              */
             rfExc.notifyActor(targetActor, issuingActor);
 
             /* 
              *   the command cannot proceed, so abandon any remaining
              *   tokens on the command line 
              */
             return;
         }
         catch (CancelCommandLineException cclExc)
         {
             /* 
              *   if there are any tokens remaining, the game might want to
              *   show an explanation 
              */
             if (nextCommandTokens != nil)
                 targetActor.getParserMessageObj().explainCancelCommandLine();
 
             /* stop now, abandoning the rest of the command line */	
             return;
         }
         catch (TerminateCommandException tcExc)
         {
             /* 
              *   the command cannot proceed - we can't do any more
              *   processing of this command, so simply return, abandoning
              *   any additional tokens we have 
              */
             return;
         }
         catch (RetryCommandTokensException rctExc)
         {
             /* 
              *   We want to replace the current command's token list with
              *   the new token list - get the new list, and then go back
              *   and start over processing it.
              *   
              *   Note that we must retain any tokens beyond those that the
              *   match tree used.  The exception only edits the current
              *   match tree's matched tokens, since it doesn't have access
              *   to any of the original tokens beyond those, so we must
              *   now add back in any tokens beyond the originals.  
              */
             toks = rctExc.newTokens_ + extraTokens;
 
             /* go back and process the command again with the new tokens */
             continue parseTokenLoop;
         }
         catch (ReplacementCommandStringException rcsExc)
         {
             local str;
             
             /* retrieve the new command string from the exception */
             str = rcsExc.newCommand_;
 
             /* 
              *   if the command string is nil, it means that the command
              *   has been fully handled already, so we simply return
              *   without any further work 
              */
             if (str == nil)
                 return;
 
             /* 
              *   Replace the entire command string with the one from the
              *   exception - this cancels any previous command that we
              *   had.
              */
             toks = cmdTokenizer.tokenize(str);
 
             /* 
              *   we have a brand new command line, so we're starting a
              *   brand new sentence 
              */
             firstInSentence = true;
 
             /* set the issuing and target actor according to the exception */
             issuingActor = rcsExc.issuingActor_;
             targetActor = rcsExc.targetActor_;
 
             /* 
              *   Put this work into the target actor's work queue, so that
              *   the issuer will carry out the command at the next
              *   opportunity.  This is a brand new command line, so it
              *   starts a new sentence.  
              */
             targetActor.addPendingCommand(true, issuingActor, toks);
 
             /* we're done processing this command */
             return;
         }
     }
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Execute an action, as specified by an Action object.  We'll resolve
  *   the nouns in the action, then perform the action.
  */
 executeAction(targetActor, targetActorPhrase,
               issuingActor, countsAsIssuerTurn, action)
 {
     local results;
 
 startOver:
     /* create a basic results object to handle the resolution */
     results = new BasicResolveResults();
         
     /* set up the actors in the results object */
     results.setActors(targetActor, issuingActor);
 
     /* catch any "remap" signals while resolving noun phrases */
     try
     {
         /* resolve noun phrases */
         action.resolveNouns(issuingActor, targetActor, results);
     }
     catch (RemapActionSignal sig)
     {
         /* mark the new action as remapped */
         sig.action_.setRemapped(action);
         
         /* get the new action from the signal */
         action = sig.action_;
 
         /* start over with the new action */
         goto startOver;
     }
 
     /*
      *   Check to see if we should create an undo savepoint for the
      *   command.  If the action is not marked for inclusion in the undo
      *   log, there is no need to log a savepoint for it.
      *   
      *   Don't save undo for nested commands.  A nested command is part of
      *   a main command, and we only want to save undo for the main
      *   command, not for its individual sub-commands.  
      */
     if (action.includeInUndo
         && action.parentAction == nil
         && (targetActor.isPlayerChar()
             || (issuingActor.isPlayerChar() && countsAsIssuerTurn)))
     {
         /* 
          *   Remember the command we're about to perform, so that if we
          *   undo to here we'll be able to report what we undid.  Note that
          *   we do this *before* setting the savepoint, because we want
          *   after the undo to know the command we were about to issue at
          *   the savepoint.  
          */
         libGlobal.lastCommandForUndo = action.getOrigText();
         libGlobal.lastActorForUndo =
             (targetActorPhrase == nil
              ? nil
              : targetActorPhrase.getOrigText());
             
         /* 
          *   set a savepoint here, so that we on 'undo' we'll restore
          *   conditions to what they were just before we executed this
          *   command 
          */
         savepoint();
     }
     
     /* 
      *   If this counts as a turn for the issuer, adjust the issuer's busy
      *   time.
      *   
      *   However, this doesn't apply if the command is conversational (that
      *   is, it's something like "BOB, HELLO").  A conversational command
      *   is conceptually carried out by the issuer, not the target actor,
      *   since the action consists of the issuer actually saying something
      *   to the target actor.  The normal turn accounting in Action will
      *   count a conversational command this way, so we don't have to do
      *   the extra bookkeeping for such a command here.
      */
     if (countsAsIssuerTurn && !action.isConversational(issuingActor))
     {
         /* 
          *   note in the issuer that the target is the most recent
          *   conversational partner 
          */
         issuingActor.lastInterlocutor = targetActor;
         
         /* make the issuer busy for the order-giving interval */
         issuingActor.addBusyTime(nil,
                                  issuingActor.orderingTime(targetActor));
         
         /* notify the target that this will be a non-idle turn */
         targetActor.nonIdleTurn();
     }
             
     /*
      *   If the issuer is directing the command to a different actor, and
      *   it's not a conversational command, check with the target actor to
      *   see if it wants to accept the command.  Don't check
      *   conversational commands, since these aren't of the nature of
      *   orders to be obeyed.  
      */
     if (issuingActor != targetActor
         && !action.isConversational(issuingActor)
         && !targetActor.obeyCommand(issuingActor, action))
     {
         /* 
          *   if the issuing actor's "ordering time" is zero, make this take
          *   up a turn anyway, just for the refusal 
          */
         if (issuingActor.orderingTime(targetActor) == 0)
             issuingActor.addBusyTime(nil, 1);
 
         /* 
          *   This command was rejected, so don't process it any further,
          *   and give up on processing any remaining commands on the same
          *   command line. 
          */
         throw new TerminateCommandException();
     }
     
     /* execute the action */
     action.doAction(issuingActor, targetActor, targetActorPhrase,
                     countsAsIssuerTurn);
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Try an implicit action.
  *   
  *   Returns true if the action was attempted, whether or not it
  *   succeeded, nil if the command was not even attempted.  We will not
  *   attempt an implied command that verifies as "dangerous," since this
  *   means that it should be obvious to the player character that such a
  *   command should not be performed lightly.  
  */
 _tryImplicitAction(issuingActor, targetActor, msgProp, actionClass, [objs])
 {
     local action;
     
     /* create an instance of the desired action class */
     action = actionClass.createActionInstance();
 
     /* mark the action as implicit */
     action.setImplicit(msgProp);
 
     /* install the resolved objects in the action */
     action.setResolvedObjects(objs...);
 
     /* 
      *   For an implicit action, we must check the objects involved to make
      *   sure they're in scope.  If any of the objects aren't in scope,
      *   there is no way the actor would know to perform the command, so
      *   the command would not be implied in the first place.  Simply fail
      *   without trying the command.  
      */
     if (!action.resolvedObjectsInScope())
         return nil;
 
     /* 
      *   catch the abort-implicit signal, so we can turn it into a result
      *   code for our caller instead of an exception 
      */
     try
     {
         /* execute the action */
         action.doAction(issuingActor, targetActor, nil, nil);
         
         /* 
          *   if the actor is in "NPC" mode for implied commands, do some
          *   extra work 
          */
         if (targetActor.impliedCommandMode() == ModeNPC)
         {
             /*   
              *   we're in NPC mode, so if the implied action failed, then
              *   act as though the command had never been attempted 
              */
             if (gTranscript.isFailure)
             {
                 /* the implied command failed - act like we didn't even try */
                 return nil;
             }
 
             /* 
              *   In "NPC" mode, we display the results from implied
              *   commands as though they had been explicitly entered as
              *   separate actions.  So, add visual separation after the
              *   results from the implied command. 
              */
             gTranscript.addCommandSep();
         }
 
         /* tell the caller we at least tried to execute the command */
         return true;
     }
     catch (AbortImplicitSignal sig)
     {
         /* tell the caller we didn't execute the command at all */
         return nil;
     }
     catch (ParseFailureException exc)
     {
         /*
          *   Parse failure.  If the actor is in NPC mode, we can't have
          *   asked for a new command, so this must be some failure in
          *   processing the implied command itself; most likely, we tried
          *   to resolve a missing object or the like and found that we
          *   couldn't perform interactive resolution (because of the NPC
          *   mode).  In this case, simply treat this as a failure of the
          *   implied command itself, and act as though we didn't even try
          *   the implied command.
          *   
          *   If the actor is in player mode, then we *can* perform
          *   interactive resolution, so we won't have thrown a parser
          *   failure before trying to solve the problem interactively.  The
          *   failure must therefore be in an interactive response.  In this
          *   case, simply re-throw the failure so that it reaches the main
          *   parser. 
          */
         if (targetActor.impliedCommandMode() == ModeNPC)
         {
             /* NPC mode - the implied command itself failed */
             return nil;
         }
         else
         {
             /* player mode - interactive resolution failed */
             throw exc;
         }
     }
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Run a replacement action. 
  */
 _replaceAction(actor, actionClass, [objs])
 {
     /* run the replacement action as a nested action */
     _nestedAction(true, actor, actionClass, objs...);
 
     /* the invoking command is done */
     exit;
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Resolve and execute a replacement action.  This differs from the
  *   normal replacement action execution in that the action we execute
  *   requires resolution before execution.  
  */
 resolveAndReplaceAction(newAction)
 {
     /* prepare the replacement action */
     prepareNestedAction(true, nil, newAction);
     
     /* 
      *   resolve and execute the new action, using the same target and
      *   issuing actors as the original action 
      */
     executeAction(gActor, nil, gIssuingActor, nil, newAction);
     
     /* the invoking command has been replaced, so it's done */
     exit;
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Run an action as a new turn.  Returns the CommandTranscript describing
  *   the action's results.
  */
 _newAction(transcriptClass, issuingActor, targetActor, actionClass, [objs])
 {
     local action;
 
     /* create an instance of the desired action class */
     action = actionClass.createActionInstance();
 
     /* execute the command with the action instance */
     return newActionObj(transcriptClass, issuingActor, targetActor,
                         action, objs...);
 }
 
 /*
  *   Run an action as a new turn.  This is almost the same as _newAction,
  *   but should be used when the caller has already explicitly created an
  *   instance of the Action to be performed.
  *   
  *   If issuingActor is nil, we'll use the current global issuing actor; if
  *   that's also nil, we'll use the target actor.
  *   
  *   Returns a CommandTranscript object describing the result of the
  *   action.  
  */
 newActionObj(transcriptClass, issuingActor, targetActor, actionObj, [objs])
 {
     /* create the results object and install it as the global transcript */
     return withCommandTranscript(transcriptClass, new function()
     {
         /* install the resolved objects in the action */
         actionObj.setResolvedObjects(objs...);
 
         /* 
          *   if the issuing actor isn't specified, use the current global
          *   issuing actor; if that's also not set, use the target actor 
          */
         if (issuingActor == nil)
             issuingActor = gIssuingActor;
         if (issuingActor == nil)
             issuingActor = targetActor;
         
         /* 
          *   Execute the given action.  Because this is a new action,
          *   execute the action in a new sense context for the given actor.
          */
         callWithSenseContext(targetActor.isPlayerChar()
                              ? nil : targetActor, sight,
             {: actionObj.doAction(issuingActor, targetActor, nil, nil)});
 
         /* return the current global transcript object */
         return gTranscript;
     });
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Run a nested action.  'isReplacement' has the same meaning as in
  *   execNestedAction(). 
  */
 _nestedAction(isReplacement, actor, actionClass, [objs])
 {
     local action;
 
     /* create an instance of the desired action class */
     action = actionClass.createActionInstance();
 
     /* install the resolved objects in the action */
     action.setResolvedObjects(objs...);
 
     /* execute the new action */
     execNestedAction(isReplacement, nil, actor, action);
 }
 
 /*
  *   Execute a fully-constructed nested action.
  *   
  *   'isReplacement' indicates whether the action is a full replacement or
  *   an ordinary nested action.  If it's a replacement, then we use the
  *   game time taken by the replacement, and set the enclosing action
  *   (i.e., the current gAction) to take zero time.  If it's an ordinary
  *   nested action, then we consider the nested action to take zero time,
  *   using the current action's time as the overall command time.  
  *   
  *   'isRemapping' indicates whether or not this is a remapped action.  If
  *   we're remapping from one action to another, this will be true; for
  *   any other kind of nested or replacement action, this should be nil.  
  */
 execNestedAction(isReplacement, isRemapping, actor, action)
 {
     /* prepare the nested action */
     prepareNestedAction(isReplacement, isRemapping, action);
 
     /* execute the new action in the actor's sense context */
     callWithSenseContext(
         actor.isPlayerChar() ? nil : actor, sight,
         {: action.doAction(gIssuingActor, actor, nil, nil) });
 }
 
 /*
  *   Prepare a nested or replacement action for execution. 
  */
 prepareNestedAction(isReplacement, isRemapping, action)
 {
     /* 
      *   if the original action is an implicit command, make the new
      *   command implicit as well 
      */
     if (gAction.isImplicit)
     {
         /* 
          *   make the new action implicit, but don't describe it as a
          *   separate implicit command - it's effectively part of the
          *   original implicit command 
          */
         action.setImplicit(nil);
     }
 
     /* mark the new action as nested */
     action.setNested();
 
     /* a nested action is part of the enclosing action */
     action.setOriginalAction(gAction);
 
     /* if this is a remapping, mark it as such */
     if (isRemapping)
         action.setRemapped(gAction);
 
     /* 
      *   Set either the nested action's time or the enclosing (current)
      *   action's time to zero - we want to count only the time of one
      *   command or the other.
      *   
      *   If we're running an ordinary nested command, set the nested
      *   command's time to zero, since we want to consider it just a part
      *   of the enclosing command and thus to take no time of its own.
      *   
      *   If we're running a full replacement command, and we're replacing
      *   something other than an implied command, don't consider the
      *   enclosing command to take any time, since the enclosing command is
      *   carrying out its entire function via the replacement and thus
      *   requires no time of its own.  If we're replacing an implied
      *   command, this doesn't apply, since the implied command defers to
      *   its enclosing command for timing.  If we're replacing a command
      *   that already has zero action time, this also doesn't apply, since
      *   we're presumably replacing a command that's itself nested.  
      */
     if (isReplacement && !gAction.isImplicit && gAction.actionTime != 0)
         gAction.zeroActionTime();
     else
         action.actionTime = 0;
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Run a previously-executed command as a nested action, re-resolving
  *   all of its objects to ensure they are still valid.
  */
 nestedActionAgain(action)
 {
     /* reset the any cached information for the new command context */
     action.resetAction();
 
     /* mark the action as nested */
     action.setNested();
     action.setOriginalAction(gAction);
 
     /* 
      *   do not count any time for the nested action, since it's merely
      *   part of the main turn and doesn't count as a separate turn of its
      *   own 
      */
     action.actionTime = 0;
 
     /* execute the command */
     executeAction(gActor, nil, gIssuingActor, nil, action);
 }
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Run some code in a simulated Action environment.  We'll create a dummy
  *   instance of the given Action class, and set up a command transcript,
  *   then invoke the function.  This is useful for writing daemon code that
  *   needs to invoke other code that's set up to expect a normal action
  *   processing environment.  
  */
 withActionEnv(actionClass, actor, func)
 {
     local oldAction, oldActor;
 
     /* remember the old globals */
     oldAction = gAction;
     oldActor = gActor;
 
     try
     {
         /* set up a dummy action */
         gAction = actionClass.createInstance();
 
         /* use the player character as the actor */
         gActor = actor;
 
         /* 
          *   execute the function with a command transcript active; obtain
          *   and return the return value of the function 
          */
         return withCommandTranscript(CommandTranscript, func);
     }
     finally
     {
         /* restore globals on the way out */
         gAction = oldAction;
         gActor = oldActor;
     }
 }
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Exit signal.  This signal indicates that we're finished with the
  *   entire command execution sequence for an action; the remainder of the
  *   command execution sequence is to be skipped for the action.  Throw
  *   this from within the command execution sequence in order to skip
  *   directly to the end-of-turn processing.  This skips everything
  *   remaining in the action, including after-action notification and the
  *   like.  This signal skips directly past the 'afterAction' phase of the
  *   command.
  *   
  *   Note that this doesn't prevent further processing of the same command
  *   if there are multiple objects involved, and it doesn't affect
  *   processing of additional commands on the same command line.  If you
  *   want to cancel further iteration of the same command for additional
  *   objects, call gAction.cancelIteration().  
  */
 class ExitSignal: Exception
 ;
 
 /*
  *   Exit Action signal.  This signal indicates that we're finished with
  *   the execAction portion of processing the command, but we still want
  *   to proceed with the rest of the command as normal.  This can be used
  *   when a step in the action processing wants to preempt any of the more
  *   default processing that would normally follow.  This skips directly
  *   to the 'afterAction' phase of the command.
  *   
  *   Note that this doesn't prevent further processing of the same command
  *   if there are multiple objects involved, and it doesn't affect
  *   processing of additional commands on the same command line.  If you
  *   want to cancel further iteration of the same command for additional
  *   objects, call gAction.cancelIteration().  
  */
 class ExitActionSignal: Exception
 ;
 
 /*
  *   Abort implicit command signal.  This exception indicates that we are
  *   aborting an implicit command without having tried to execute the
  *   command at all.  This is thrown when an implied command is to be
  *   aborted before it's even attempted, such as when verification shows
  *   the command is obviously dangerous and thus should never be attempted
  *   without the player having explicitly requesting it.  
  */
 class AbortImplicitSignal: Exception
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Action Remap signal.  This signal can be thrown only during the noun
  *   phrase resolution phase of execution, and indicates that we want to
  *   remap the action to a different action, specified in the signal.
  *   
  *   This is useful when an object is always used in a special way, so
  *   that a generic verb used with the object must be mapped to a more
  *   specific verb on the object.  For example, a game with a generic USE
  *   verb might convert USE PAINTBRUSH ON WALL to PAINT WALL WITH
  *   PAINTBRUSH by remapping the UseWith action to a PaintWith action
  *   instead.  
  */
 class RemapActionSignal: Exception
     construct(action)
     {
         /* remember the new action */
         action_ = action;
     }
 
     /* the new action that should replace the original action */
     action_ = nil
 ;
 
 /*
  *   Remap a 'verify' method for a remapped action.  This is normally
  *   invoked through the remapTo() macro.  
  */
 remapVerify(oldRole, resultSoFar, remapInfo)
 {
     local newAction;
     local objs;
     local idx;
     local newRole;
 
     /* extract new action's object list from the remapping info list */
     objs = remapInfo.sublist(2);
 
     /* 
      *   Create a new action object.  We only perform verification
      *   remapping during the resolution phase of the command processing,
      *   because once we've finished resolving, we'll actually replace the
      *   action with the remapped action and thus won't have to remap
      *   verification (or anything else) at that point.  So, pass true for
      *   the in-resolve flag to the action creation routine. 
      */
     newAction = remapActionCreate(true, oldRole, remapInfo);
 
     /* 
      *   Find the object that's given as a resolved object, rather than as
      *   a DirectObject (etc) identifier - the one given as a specific
      *   object is the one that corresponds to the original object.  
      */
     idx = objs.indexWhich({x: dataType(x) == TypeObject});
 
     /* get the role identifier (DirectObject, etc) for the slot position */
     newRole = newAction.getRoleFromIndex(idx);
 
     /* if we don't yet have a result list object, create one */
     if (resultSoFar == nil)
         resultSoFar = new VerifyResultList();
 
     /* if we found a remapping, verify it */
     if (idx != nil)
     {
         /* 
          *   Remember the remapped object in the result list.  Note that we
          *   do this first, before calling the remapped verification
          *   property, so that our call to the remapped verification
          *   property will overwrite this setting if it does further
          *   remapping.  We want the ultimate target object represented
          *   here, after all remappings are finished.  
          */
         resultSoFar.remapAction_ = newAction;
         resultSoFar.remapTarget_ = objs[idx];
         resultSoFar.remapRole_ = newRole;
 
         /* install the new action as the current action while verifying */
         local oldAction = gAction;
         gAction = newAction;
 
         try
         {
             /* call verification on the new object in the new role */
             return newAction.callVerifyProp(
                 objs[idx], 
                 newAction.getVerifyPropForRole(newRole),
                 newAction.getPreCondPropForRole(newRole),
                 newAction.getRemapPropForRole(newRole),
                 resultSoFar, newRole);
         }
         finally
         {
             /* restore the old gAction on the way out */
             gAction = oldAction;
         }
     }
     else
     {
         /* there's no remapping, so there's nothing to verify */
         return resultSoFar;
     }
 }
 
 /*
  *   Perform a remapping to a new action.  This is normally invoked
  *   through the remapTo() macro.  
  */
 remapAction(inResolve, oldRole, remapInfo)
 {
     local newAction;
 
     /* get the new action */
     newAction = remapActionCreate(inResolve, oldRole, remapInfo);
 
     /* 
      *   replace the current action, using the appropriate mechanism
      *   depending on the current processing phase 
      */
     if (inResolve)
     {
         /* 
          *   we're still resolving the objects, so we must use a signal to
          *   start the resolution process over for the new action 
          */
         throw new RemapActionSignal(newAction);
     }
     else
     {
         /* 
          *   We've finished resolving everything, so we can simply use the
          *   new action as a replacement action.
          */
         execNestedAction(true, true, gActor, newAction);
 
         /* 
          *   the remapped action replaces the original action, so
          *   terminate the original action 
          */
         exit;
     }
 }
 
 /*
  *   Create a new action object for the given remapped action. 
  */
 remapActionCreate(inResolve, oldRole, remapInfo)
 {
     local newAction;
     local newObjs;
     local newActionClass;
     local objs;
 
     /* get the new action class and object list from the remap info */
     newActionClass = remapInfo[1];
     objs = remapInfo.sublist(2);
     
     /* 
      *   create a new instance of the replacement action, carrying forward
      *   the properties of the original (current) action 
      */
     newAction = newActionClass.createActionFrom(gAction);
 
     /* remember the original action we're remapping */
     newAction.setOriginalAction(gAction);
 
     /* set up an empty vector for the match trees for the new action */
     newObjs = new Vector(objs.length());
 
     /* remap according to the phase of the execution */
     if (inResolve)
     {
         /* translate the object mappings */
         foreach (local cur in objs)
         {
             /* check what we have to translate */
             if (dataType(cur) == TypeEnum)
             {
                 /* 
                  *   it's an object role - if it's the special OtherObject
                  *   designator, get the other role of a two-object
                  *   command 
                  */
                 if (cur == OtherObject)
                     cur = gAction.getOtherObjectRole(oldRole);
                 
                 /* 
                  *   get the match tree for this role from the old action
                  *   and add it to our list 
                  */
                 newObjs.append(gAction.getMatchForRole(cur));
             }
             else
             {
                 /* append the new ResolveInfo to the new object list */
                 newObjs.append(gAction.getResolveInfo(cur, oldRole));
             }
         }
 
         /* set the object matches in the new action */
         newAction.setObjectMatches(newObjs.toList()...);
     }
     else
     {
         /* translate the object mappings */
         foreach (local cur in objs)
         {
             /* check what we have to translate */
             if (dataType(cur) != TypeEnum)
             {
                 /* it's an explicit object - use it directly */
                 newObjs.append(cur);
             }
             else
             {
                 /* it's a role - translate OtherObject if needed */
                 if (cur == OtherObject)
                     cur = gAction.getOtherObjectRole(oldRole);
                
                 /* get the resolved object for this role */
                 newObjs.append(gAction.getObjectForRole(cur));
             }
         }
 
         /* set the resolved objects in the new action */
         newAction.setResolvedObjects(newObjs.toList()...);
     }
 
     /* return the new action */
     return newAction;
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Result message object.  This is used for verification results and
  *   main command reports, which must keep track of messages to display.  
  */
 class MessageResult: object
     /* 
      *   Construct given literal message text, or alternatively a property
      *   of the current actor's verb messages object.  In either case,
      *   we'll expand the message immediately to allow the message to be
      *   displayed later with any parameters fixed at the time the message
      *   is constructed.  
      */
     construct(msg, [params])
     {
         /* if we're based on an existing object, copy its characteristics */
         if (dataType(msg) == TypeObject && msg.ofKind(MessageResult))
         {
             /* base it on the existing object */
             messageText_ = msg.messageText_;
             messageProp_ = msg.messageProp_;
             return;
         }
 
         /* 
          *   if the message was given as a property, remember the property
          *   for identification purposes 
          */
         if (dataType(msg) == TypeProp)
             messageProp_ = msg;
 
         /* 
          *   Resolve the message and store the text.  Use the action's
          *   objects (the direct object, indirect object, etc) as the
          *   sources for message overrides - this makes it easy to override
          *   messages on a per-object basis without having to rewrite the
          *   whole verify/check/action routines. 
          */
         messageText_ = resolveMessageText(gAction.getCurrentObjects(),
                                           msg, params);
     }
 
     /*
      *   Static method: resolve a message.  If the message is given as a
      *   property, we'll look up the message in the given source objects
      *   and in the actor's "action messages" object.  We'll return the
      *   resolved message string.  
      */
     resolveMessageText(sources, msg, params)
     {
         /*
          *   The message can be given either as a string or as a property
          *   of the actor's verb message object.  If it's the latter, look
          *   up the text of the property from the appropriate object.  
          */
     findTextSource:
         if (dataType(msg) == TypeProp)
         {
             local msgObj;
             
             /* 
              *   Presume that we'll read the message from the current
              *   actor's "action message object."  This is typically
              *   playerActionMessages or npcActionMessages, but it's up to
              *   the actor to specify which object we get our messages
              *   from.  
              */
             msgObj = gActor.getActionMessageObj();
             
             /*
              *   First, look up the message property in the action's
              *   objects (the direct object, indirect object, etc).  This
              *   makes it easy to override messages on a per-object basis
              *   without having to rewrite the whole verify/check/action
              *   routine.  
              */
             foreach (local cur in sources)
             {
                 /* check to see if this object defines the message property */
                 if (cur != nil && cur.propDefined(msg))
                 {
                     local res;
                     
                     /*   
                      *   This object defines the property, so check what
                      *   we have. 
                      */
                     switch (cur.propType(msg))
                     {
                     case TypeProp:
                         /* 
                          *   It's another property, so we're being
                          *   directed back to the player action message
                          *   object.  The object does override the
                          *   message, but the override points to another
                          *   message property in the action object message
                          *   set.  Simply redirect 'msg' to point to the
                          *   new property, and use the same action message
                          *   object we already assumed we'd use.  
                          */
                         msg = cur.(msg);
                         break;
 
                     case TypeSString:
                         /* it's a simple string - retrieve it */
                         msg = cur.(msg);
 
                         /* 
                          *   since it's just a string, we're done finding
                          *   the message text - there's no need to do any
                          *   further property lookup, since we've obviously
                          *   reached the end of that particular line 
                          */
                         break findTextSource;
 
                     case TypeCode:
                         /*
                          *   Check the parameter count - we'll allow this
                          *   method to take the full set of parameters, or
                          *   no parameters at all.  We allow the no-param
                          *   case for convenience in cases where the method
                          *   simply wants to return a string or property ID
                          *   from a short method that doesn't need to know
                          *   the parameters; in these cases, it's
                          *   syntactically a lot nicer looking to write it
                          *   as a "prop = (expresion)" than to write the
                          *   full method-with-params syntax. 
                          */
                         if (cur.getPropParams(msg) == [0, 0, nil])
                             res = cur.(msg);
                         else
                             res = cur.(msg)(params...);
 
                         /*
                          *   If that returned nil, ignore it entirely and
                          *   keep scanning the remaining source objects.
                          *   The object must have decided it didn't want to
                          *   provide the message override in this case
                          *   after all. 
                          */
                         if (res == nil)
                             continue;
 
                         /* we didn't get nil, so use the result */
                         msg = res;
 
                         /* 
                          *   if we got a string, we've fully resolved the
                          *   message text, so we can stop searching for it 
                          */
                         if (dataType(msg) == TypeSString)
                             break findTextSource;
 
                         /*
                          *   It's not nil and it's not a string, so it must
                          *   be a property ID.  In this case, the property
                          *   ID is a property to evaluate in the normal
                          *   action message object.  Simply proceed to
                          *   evaluate the new message property as normal.  
                          */
                         break;
 
                     case TypeNil:
                         /* 
                          *   it's explicitly nil, which simply means to
                          *   ignore this definition; keep scanning other
                          *   source objects 
                          */
                         continue;
 
                     default:
                         /* 
                          *   In any other case, this must simply be the
                          *   message we're to use.  For this case, the
                          *   source of the message is this object, so
                          *   forget about the normal action message object
                          *   and instead use the current object.  Then
                          *   proceed to evaluate the message property as
                          *   normal, which will fetch it from the current
                          *   object.  
                          */
                         msgObj = cur;
                         break;
                     }
 
                     /* 
                      *   we found a definition, so we don't need to look
                      *   at any of the other objects involved in the
                      *   action - we just use the first override we find 
                      */
                     break;
                 }
             }
 
             /* look up the message in the actor's message generator */
             msg = msgObj.(msg)(params...);
         }
 
         /* 
          *   format the string and remember the result - do the formatting
          *   immediately, because we want to make sure we expand any
          *   substitution parameters in the context of the current
          *   command, since the parameters might change (and thus alter
          *   the meaning of the message) by the time it's displayed 
          */
         msg = langMessageBuilder.generateMessage(msg);
 
         /* 
          *   "quote" the message text - it's fully expanded now, so
          *   there's no need to further expand anything that might by
          *   coincidence look like substitution parameters in its text 
          */
         msg = langMessageBuilder.quoteMessage(msg);
 
         /* return the resolved message string */
         return msg;
     }
 
     /* 
      *   set a new message, given the same type of information as we'd use
      *   to construct the object 
      */
     setMessage(msg, [params])
     {
         /* simply invoke the constructor to re-fill the message data */
         construct(msg, params...);
     }
 
     /*
      *   Display a message describing why the command isn't allowed. 
      */
     showMessage()
     {
         /* show our message string */
         say(messageText_);
     }
 
     /* the text of our result message */
     messageText_ = nil
 
     /* the message property, if we have one */
     messageProp_ = nil
 ;
 
TADS 3 Library Manual
Generated on 9/8/2006 from TADS version 3.0.11