# action.t | documentation

    #charset "us-ascii"
    #include "advlite.h"

    /*
     *   ****************************************************************************
     *    action.t
     *    This module forms part of the adv3Lite library
     *    (c) 2012-13 Eric Eve
     */

    /*
     *   The library doesn't yet provide any support for actions that take three
     *   objects (TIAActions, as they might hypothetically be called), but since the
     *   Mercury parser does we provide a number of hooks that other code could use.
     *   To this end we need to define the properties a TIAAction might use so the
     *   compiler recognizes them.
     */
    property curAobj, verAobjProp, preCondAobjProp, remapAobjProp;
    property verifyAobjDefault, preCondAobjDefault;

    class Action: ReplaceRedirector

        /*
         *   Flag; should this action be considered a failure? This should be reset
         *   to true at the start of the action processing cycle but can be tested
         *   later to prevent, e.g., inappropriate reporting.
         */
        actionFailed = nil

        /*
         *   The execGroup() method is called by the current Command object before
         *   it calls the action on individual objects, to allow processing of the
         *   group of objects as a whole. By default we do nothing here in the
         *   library.
         */
        execGroup(cmd) { }

        /*
         *   The checkAction() method calls the check routines on the objects
         *   involved in the command (where there are objects). Subclasses such as
         *   TAction and TIAction need to override this to carry out the appropriate
         *   handling.
         */
        checkAction() { }

        /*
         *   The main routine for handling an action. This is the method called by
         *   the command object; the cmd parameter gives the calling Command object.
         */
        exec(cmd)
        {

            /* Resest actionFailed to nil */
            actionFailed = nil;

            /* Resent the scope list */
            scopeList = [];

            /* Note the current actor */
            libGlobal.curActor = cmd.actor;

            /* Note the location of the current actor */
            oldRoom = gActor.getOutermostRoom;

            /*
             *   Note whether the current actor's location starts out illuminated;
             *   we need to know this so we can display a notification if the
             *   illumination changes.
             */

            wasIlluminated = oldRoom.isIlluminated();

            /* execute the action-processing cycle */
            execCycle(cmd);

        }

        /*
         *   The action-processing cycle carries out the before action
         *   notifications, then executes the action. This needs to be overridden on
         *   various subclasses since the beforeAction notifications can occur at
         *   different points in different kinds of action.
         */
        execCycle(cmd)
        {
            try
            {
                IfDebug(actions,
                        "[Executing <<actionTab.symbolToVal(baseActionClass)>>
                        << if cmd.dobj != nil>> : <i><<dqinfo>></i>
                        <<cmd.dobj.name>><<end>>
                        << if cmd.iobj != nil>> : <i><<iqinfo>></i>
                        <<cmd.iobj.name>><<end>> ]\n" );

                /* Carry out the before action notifications. */
                beforeAction();

                /* Execute the main action handling. */
                execAction(cmd);

                /*
                 *   If the action is repeatable, make a note of it in case the
                 *   player issues an AGAIN command.
                 */
                if(isRepeatable)
                    libGlobal.lastAction = self.createClone();

            }
            catch(ExitActionSignal ex)
            {

            }
            /* If an exit signal is issued we skip to here. */
            catch(ExitSignal ex)
            {
                /*
                 *   If the exit macro is used in the course of the command,
                 *   consider the command a failure.
                 */
                actionFailed = true;
            }

        }

        /* The main action handler. Subclasses must override. */
        execAction(cmd)
        {
        }

        /* The room the actor was in when the action started */
        oldRoom = nil

        /* Flag to indicate whether the actor's location started out illuminated */
        wasIlluminated = nil

        /*
         *   A list of any PreConditions that apply to this action as a whole, as
         *   opposed to any of its objects. This is most likely to be relevant to an
         *   IAction.
         */
        preCond = nil

        checkActionPreconditions()
        {
            local preCondList;
            local checkOkay = true;

            /*
             *   Construct a list or preCondition objects on the appropriate object
             *   property.
             */
            preCondList = valToList(preCond);

            /* Sort the list in preCondOrder */
            preCondList = preCondList.sort(nil,
                                           {a, b: a.preCondOrder - b.preCondOrder});

            try
            {
                /* Iterate through the list to see if all the checks are satisfied */
                foreach(local cur in preCondList)
                {
                    /*
                     *   If we fail the check method on any precondition object,
                     *   note the failure and stop the iteration.
                     */
                    if(cur.checkPreCondition(gActor, true) == nil)
                    {
                        checkOkay = nil;
                        break;
                    }
                }
            }
            /*
             *   Game authors aren't meant to use the exit macro in check methods,
             *   but in case they do we handle it here.
             */
            catch (ExitSignal ex)
            {
                checkOkay = nil;
            }

            /*
             *   If the check method failed on any of our precondition objects note
             *   that the action is a failure.
             */
            if(checkOkay == nil)
                actionFailed = true;

            /*
             *   Otherwise, if we're not an implicit action, display any pending
             *   implicit action announcements.
             */
            else if(!isImplicit)
                "<<buildImplicitActionAnnouncement(true, true)>>";

            /* Return our overall check result. */
            return checkOkay;

        }

        beforeAction()
        {

            /*
             *   Check any Preconditions relating to the action as a whole (as
             *   opposed to any of its objects.
             */
            if(!checkActionPreconditions())
                exit;

            /*
             *   Call the before action handling on the current actor (in its
             *   capacity as actor/
             */
            gActor.actorAction();

            /*
             *   If the sceneManager is present then send a before action
             *   notification to every currently active Scene.
             */
            if(defined(sceneManager))
                sceneManager.notifyBefore();

            /*
             *   Call roomBeforeAction() on the current actor's location, and
             *   regionBeforeAction() on all the regions it's in.
             */
            gActor.getOutermostRoom.notifyBefore();

            /*
             *   If we don't already have a scope list for the current action, build
             *   it now.
             */
            if(nilToList(scopeList).length == 0)
                buildScopeList;

            /* Call the beforeAction method of every action in scope. */
            foreach(local cur in scopeList)
            {
                cur.beforeAction();
            }
        }

        /*
         *   Carry out the post-action processing. This first checks to see if
         *   there's been a change in illumination. If there has we either show a
         *   room description (if the actor's location is now lit) or announce the
         *   onset of darkness. We then call the after action notifications first on
         *   the actor's current room and then on every object in scope.
         *
         *   Note that afterAction() is called from the current Command object.
         */
        afterAction()
        {
            /*
             *   If the current action is considered a failure, we don't carry out
             *   any after action handling, since in this case there's no action to
             *   react to.
             */
            if(actionFailed)
                return;

            /*
             *   If the actor is still in the same room s/he started out in, check
             *   whether the current illumination level has changed, and, if so,
             *   either show a room description or announce the onset of darkness,
             *   as appropriate.
             */
            if(oldRoom == gActor.getOutermostRoom)
            {
                if(oldRoom.isIlluminated)
                {
                    if(!wasIlluminated)
                    {
                        "<.p>";
                        oldRoom.lookAroundWithin();
                    }
                }
                else if(wasIlluminated)
                {
                    DMsg(onset of darkness, '\n{I} {am} plunged into darkness. ');
                }
            }
            "<.p>";

            /* Call the afterAction notifications on all currently active scenes. */
            if(defined(sceneManager))
               sceneManager.notifyAfter();

            /*
             *   Call the afterAction notification on the current room and its
             *   regions.
             */
            gActor.getOutermostRoom.notifyAfter();

            /*
             *   Call the afterAction notification on every object in scope. Note
             *   that we have to recalculate the scope list here in case the action
             *   has changed it.
             */
            foreach(local cur in Q.scopeList(gActor))
            {
                cur.afterAction();
            }

        }

        /*
         *   The turnSequence() method is called from the current Command object. It
         *   first executes any current daemons (apart from any PromptDaemons) and
         *   then advances the turn counter. We define this on the Action class
         *   principally to make it simple for certain kinds of Action such as
         *   SystemActions to do nothing here (since they don't count as actions
         *   within the game world).
         */

        turnSequence()
        {

            /* Execute the regionDaemon on every region in which the player character is located. */
            local lst = gPlayerChar.getOutermostRoom.allRegions();

            foreach(local reg in lst)
            {
                "<.p>";
                reg.regionDaemon();
            }

            "<.p>";
            /* Execute the player character's current location's roomDaemon. */
            gPlayerChar.getOutermostRoom.roomDaemon();

            /*
             *   If the events.t module is included, execute all current Daemons and
             *   Fuses/
             */
            if(defined(eventManager) && eventManager.executeTurn())
                ;

            /* Advance the turn counter */
            libGlobal.totalTurns += turnsTaken;
        }

        /*
         *   The number of turns this action is counted as taking. Normally, this
         *   will be 1.
         */
        turnsTaken = 1

        /* Flag: is this an implicit action? By default it isn't. */
        isImplicit = nil

        /* Can this action be Undone? By default most actions can. */
        includeInUndo = true

        /* Flag: is this a conversational action? */
        isConversational = nil

        /*
         *   Is this action repeatable (with an AGAIN command)? Most actions are so
         *   the default is true but subclasses can override to exclude actions
         *   (such as certain system actions) that it would make no sense to repeat.
         */
        isRepeatable = true

        /*
         *   If an AGAIN command is used with this command, should the command be
         *   reparsed from scratch (because it might involve a different object) or
         *   not (because it should act on the same objects). We generally set this
         *   to true for actions it wouldn't normally make sense to repeat on the
         *   same object straight away. Since this applies to the majority of
         *   actions, we make this the default.
         */
        againRepeatsParse = true

        /*
         *   Report on the action. This is only relevant where the action has more
         *   or one objects, so TAction must override. This is called from the
         *   current Command object once all the objects have been acted on (in a
         *   case where multiple direct objects have been specified, as in TAKE ALL
         *   or TAKE RED BALL AND GREEN PEN). This allows the report routine to
         *   summarize the action for all the objects acted upon instead of
         *   displaying an individual report for each one.
         */
        reportAction()  { }

        /*
         *   Do we have a parent action, and if so what is it? The parent action
         *   would be the action that's using us as an implicit action or nested
         *   action.
         */
        parentAction = nil

        /*
         *   Carry out the verification stage for this object in this role, and
         *   carry out any remapping needed. This needs to be defined on Action
         *   since there might be verification of the ActorRole.
         */
        verify(obj, role)
        {
            local remapResult;
            local verifyProp;
            local preCondProp;
            local remapProp;

            /* Clear out any previous verify results */
            verifyTab = nil;

            /*
             *   Note which properties to use according to which role we're
             *   verifying for. (No actions in the adv3Lite library currently use an
             *   Accessory object but the possibility is included here to ease
             *   subsequent extension).
             */
            switch(role)
            {
            case DirectObject:
                verifyProp = verDobjProp;
                preCondProp = preCondDobjProp;
                remapProp = remapDobjProp;
                break;

            case IndirectObject:
                verifyProp = verIobjProp;
                preCondProp = preCondIobjProp;
                remapProp = remapIobjProp;
                break;

            case AccessoryObject:
                verifyProp = verAobjProp;
                preCondProp = preCondAobjProp;
                remapProp = remapAobjProp;
                break;
            case ActorRole:
                verifyProp = &verifyActor;
                remapProp = &remapActor;
                preCondProp = &preCondActor;
                break;
            }

            /* first check if we need to remap this action. */
            remapResult = obj.(remapProp);

            /*
             *   the object's remap routine can return an object or a list (if it
             *   returns anything else we ignore it). If it returns an object use
             *   that object in place of the one we were about to verify for the
             *   remainder of this action. If it returns a list, the list should
             *   contain the details of an action that is to replace the current
             *   action, so run the remapped action instead.
             */
            switch(dataType(remapResult))
            {
            case TypeObject:
                obj = remapResult;
                break;
            case TypeList:
                /*
                 *   If the remap result is a list, then we'll need to remap the
                 *   action to another one, but we'll leave that until all the
                 *   objects have been resolved. For the purpose of verifying this
                 *   object we'll just return a standard logical verify result to
                 *   allow the remapping to proceed at a later stage.
                 */
                DMsg(remap error, '<b>ERROR!</b> The long form of remap is no longer
                    available; please use a Doer instead. ');

            default:
                break;
            }

            /*
             *   Note which object we're currently verifying in the Action's
             *   verifyObj property so other routines can find it (particularly a
             *   verify routine called from a preCondition).
             */
            verifyObj = obj;
            curObj = obj;

            switch(role)
            {

            case DirectObject:
                curDobj = obj;
                break;

            case IndirectObject:
                curIobj = obj;
                break;

            case AccessoryObject:
                curAobj = obj;
                break;
            }

            /*
             *   if the object is a decoration then we use the catchall Default
             *   prop, unless we're an action that bypasses it.
             */
            if(obj.isDecoration
               && obj.decorationActions.indexWhich({x: self.ofKind(x)}) == nil)
            {
                switch(role)
                {

                case DirectObject:
                    verifyProp =  &verifyDobjDefault;
                    preCondProp = &preCondDobjDefault;
                    break;

                case IndirectObject:
                    verifyProp = &verifyIobjDefault;
                    preCondProp = &preCondIobjDefault;
                    break;

                case AccessoryObject:
                    verifyProp = &verifyAobjDefault;
                    preCondProp = &preCondAobjDefault;
                    break;
                }
            }

            /* Execute the appropriate verify routine on the object */
            obj.(verifyProp);

            /* If we don't already have a verify table, create it */
            if(verifyTab == nil)
                verifyTab = new LookupTable;

            /*
             *   If executing this verify routine didn't create an entry for this
             *   object in the verify table, create one now with a default 'logical'
             *   verify result.
             */
            if(verifyTab.isKeyPresent(obj) == nil)
                verifyTab[obj] = new VerifyResult(100, '', true, obj);

            /*
             *   Next run through all the items in our precondition list and execute
             *   their verify methods.
             */
            foreach(local cur in valToList(obj.(preCondProp)))
                cur.verifyPreCondition(obj);

            /*
             *   Return the entry for this object in our verify table (which may
             *   have been altered by one of the preconditions above).
             */
            return verifyTab[obj];
        }

        /*
         *   Flag, do we want an action that fails at the verify stage to count as a turn (in other
         *   words, if an action fails at the verify stage, do we want to advance the turn
         *   counter,excecute daemons, and do all the other turn sequence stuff)? By default we do,
         *   since this has long been the standard behaviour, but game code can override this to nil
         *   either globally on the Action class on on individual actions to cause failure at the verify
         *   stage to abort the remainder of the turn sequence.
         */
        failedActionCountsAsTurn = true

        /*
         *   Run the verify routine on the current object in the current role to see
         *   whether it will allow the action. If it won't, display any pending
         *   implicit action announcements, then display the message explaining why
         *   the action is disallowed, and finally return nil to tell our caller to
         *   halt the action. If the verify stage does allow the action to go ahead,
         *   return true to tell our caller that this routine has no objection.
         */
        verifyObjRole(obj, role)
        {
            local verResult;
            local verMsg;

            /* Make sure we start with a clean new verify table */
            verifyTab = new LookupTable;

            verResult = verify(obj, role);

            /*
             *   If the verify result is one that disallows the action then display
             *   the failure message, along with any failed implicit action message.
             */
            if(!verResult.allowAction)
            {
                 /* Note our failure message */
                verMsg = verResult.errMsg;

                /*
                 *   If this is the direct object of the command and there's more
                 *   than one, and if the option to announce objects in verify
                 *   messages is true, then announce the name of this object to make
                 *   it clear which one is being referred to.
                 */
                if(announceMultiVerify && role == DirectObject &&
                   gCommand.dobjs.length > 1)
                    announceObject(obj);

                /*
                 *   If we're an implicit action add a failed implicit action report
                 *   ('trying to...').
                 */
                if(isImplicit)
                    "<<buildImplicitActionAnnouncement(nil)>>";

                /*
                 *   Display the failure message, unless it's identical to the
                 *   previous one.
                 */
                if(verMsg != lastVerifyMsg || announceMultiVerify)
                {
                    say(verMsg);
                    "\n";
                    lastVerifyMsg = verMsg;
                }

                /* Note that this action has failed. */
                actionFailed = true;

                if(!failedActionCountsAsTurn)
                    abort;

                /*
                 *   Stop the processing of the action here by telling our caller
                 *   we've failed.
                 */
                return nil;
            }

            /*
             *   If we're an implicit action and our best verify result doesn't
             *   allow implicit actions, abort the implicit action.
             */
            if(isImplicit && !verResult.allowImplicit)
                abortImplicit;

            /*
             *   Otherwise return true to tell our caller we're not objecting to the
             *   action.
             */
            return true;
        }

        /* The object currently being verified */
        verifyObj = nil

        /*
         *   Get a message parameter object for the action.  Each action
         *   subclass defines this to return its objects according to its own
         *   classifications.  The default action has no objects, but
         *   recognizes 'actor' as the current command's actor.
         */
        getMessageParam(objName)
        {
            switch(objName)
            {
            case 'pc':
                /* return the player character */
                return gPlayerChar;

            case 'actor':
                /* return the current actor */
                return gActor;

            case 'cobj':
                /* return the current object, if there is one */
                return curObj;

            default:
                /*
                 *   if we have an extra message parameters table, look up the
                 *   parameter name in the table
                 */
                if (extraMessageParams != nil)
                    return extraMessageParams[objName];

                /* we don't recognize other names */
                return nil;
            }
        }

        /*
         *   Define an extra message-specific parameter.  Message processors
         *   can use this to add their own special parameters, so that they
         *   can refer to parameters that aren't involved directly in the
         *   command.  For example, a message for "take <dobj>" might want to
         *   refer to the object containing the direct object.
         */
        setMessageParam(objName, obj)
        {
            /*
             *   if we don't yet have an extra message parameters table,
             *   create a small lookup table for it
             */
            if (extraMessageParams == nil)
                extraMessageParams = new LookupTable(8, 8);

            /* add the parameter to the table, indexing by the parameter name */
            extraMessageParams[objName.toLower()] = obj;
        }

        /*
         *   For convenience, this method allows setting any number of
         *   name/value pairs for message parameters.
         */
        setMessageParams([lst])
        {
            /* set each pair from the argument list */
            for (local i = 1, local len = lst.length() ; i+1 <= len ; i += 2)
                setMessageParam(lst[i], lst[i+1]);
        }

        /*
         *   Synthesize a global message parameter name for the given object.
         *   We'll store the association and return the synthesized name.
         */
        synthMessageParam(obj)
        {
            local nm;

            /* synthesize a name */
            nm = 'synth' + toString(synthParamID++);

            /* store the association */
            setMessageParam(nm, obj);

            /* return the synthesized name */
            return nm;
        }

        /* synthesized message object parameter serial number */
        synthParamID = 1

        /*
         *   Extra message parameters.  If a message processor wants to add
         *   special message parameters of its own, we'll create a lookup
         *   table for the extra parameters.  Message processors might want to
         *   add their own special parameters to allow referring to objects
         *   other than the main objects of the command.
         */
        extraMessageParams = nil

        /*
         *   Get a list of all the objects that this action should act on if the
         *   player typed ALL for role (DirectObject, IndirectObject, or perhaps in
         *   some future version of the library, AccessoryObject. This is the method
         *   that can be overridden on subclasses to give action-specific
         *   definitions of ALL.
         */
        getAll(cmd, role)
        {
            /* by default, return everything in scope */
            return World.scope.toList();
        }

        /*
         *   Get a list of all the objects this action will act on if the player
         *   types ALL for role (DirectObject or IndirectObject). This is the method
         *   actually called by the Parser. We first obtain the list of objects
         *   returned by getAll() and then filter out any objects for which
         *   hideFromAll(action) is true for this action. Subclasses should normally
         *   override getAll() rather than this method.
         */
        getAllUnhidden(cmd, role)
        {
            return getAll(cmd, role).subset({x: x.hideFromAll(self) == nil});
        }

         /*
         *   Score a set of objects in a given noun role in the action, in
         *   order to resolve an ambiguous command.  Our job, in brief, is to
         *   READ THE PLAYER'S MIND: we want to figure out which object or
         *   objects the player is actually referring to when their words are
         *   ambiguous.
         *
         *   'cmd' is the Command object describing the command.  The various
         *   object lists (dobjs, iobjs, accs) have been filled in with the
         *   in-scope objects that match the noun phrase, but these haven't
         *   been disambiguated yet, so there might be more objects listed than
         *   will actually be used in the final command.
         *
         *   'role' tells us the noun phrase role that we're scoring
         *   (DirectObject, IndirectObject, AccessoryObject, TopicRole,
         *   LiteralRole).
         *
         *   'lst' is the match list.  This is a Vector containing NPMatch
         *   objects.  There's one NPMatch for each object that we're
         *   considering as a match for the player's noun phrase.
         *
         *   For each item in the match list, we must set the NPMatch object's
         *   'score' property to a number indicating how likely we think it is
         *   that the player is referring to this object.  The higher the
         *   score, the more likely we think it is.  The score value is purely
         *   relative - the caller will pick the object or objects with the
         *   highest score.
         *
         *
         *
         *   We run through the verify routine for each object, which in turn
         *   runs through the preconditions of that object. We take the returned
         *   verify score to be the score for the object (or its replacement if
         *   remapping took place).
         *
         *   Next, we do any verb-specific adjustments via self.actionScore().
         *
         *   Finally, we call each object's scoreObject() routine to give the
         *   object a chance to make any adjustments for special affinities (or
         *   aversions).
         */
        scoreObjects(cmd, role, lst)
        {
            local bestScore = 0;
            local bestResult = nil;
            local verResult;

            gAction = cmd.action;
            gActor = cmd.actor;

            foreach (local i in lst)
            {
                /* get this object */
                local obj = i.obj;

                /*
                 *   Get the verify result by running the verify routine on the
                 *   current Command object's action for this object in this role.
                 */
                verResult = cmd.action.verify(obj, role);

                /*
                 *   Compute the score as being the verify result's result rank
                 *   times 100
                 */
                i.score = verResult.resultRank * 100;

                /*
                 *   If this score is greater than the best score we've found so
                 *   far, note the new best score and the new best verify result.
                 */
                if(i.score > bestScore)
                {
                    bestScore = i.score;
                    bestResult = verResult;
                }

                /*
                 *   The verify process could result in the remapping of the
                 *   original object to a new one.
                 */
                i.obj = verResult.myObj;
            }

            /*
             *   Make a note of which object came out best in case it's needed when
             *   we come to verify the other object.
             */
            if(role == DirectObject)
                curDobj = bestResult.myObj;

            if(role == IndirectObject)
                curIobj = bestResult.myObj;

            if(role == AccessoryObject)
                curAobj = bestResult.myObj;

            /* apply verb-specific adjustments */
            foreach (local i in lst)
                scoreObject(cmd, role, lst, i);

            /* apply object-specific adjustments */
            foreach (local i in lst)
                i.obj.scoreObject(cmd, role, lst, i);

        }

        /*
         *   Wraps a list of objects in NPMatch objects so they can be run through
         *   the scoreObjects method.
         */
        wrapObjectsNP(lst)
        {
            local nplist = [];

            foreach(local cur in lst)
            {
                nplist += new NPMatch(nil, cur, 0);
            }

            return nplist;
        }

        /* Build the scope list for this action. */
        buildScopeList(whichRole = DirectObject)
        {
            /* Start with the scope list supplied by the Query object */
            scopeList = Q.scopeList(gActor).toList();

            /* Add any additional items to scope as special cases if desired. */
            addExtraScopeItems(whichRole);
        }

        /*
         *   Add extra scope items if this action needs a wider definition of scope
         *   than normal. By default we simply allow the current actor's current
         *   location to add additional items to scope if it wishes to.
         */
        addExtraScopeItems(role?)
        {
            gActor.getOutermostRoom.addExtraScopeItems(self);
        }

        /* Our currently cached list of items in scope for this action. */
        scopeList = []

        /* Used by Mercury's spelling corrector code. */
        spellingPriority = 10

        /*
         *   Can ALL be used with this action? By default we take our value from
         *   gameMain.allVerbsAllowAll, though basic inventory-handling actions in
         *   the library will override this. This property is really only relevant
         *   on TAction and its descendents, but we define it here just to make sure
         *   no cases are missed.
         */
        allowAll = (gameMain.allVerbsAllowAll)

        /*
         *   If we've been redirected here from another action, store a reference to
         *   that action.
         */
        redirectParent = nil

        /* Does the command from which we've been redirected allow ALL? */
        parentAllowAll = (redirectParent ? redirectParent.allowAll : nil)

        /*
         *   The message to display if an action fails at the check stage (via an
         *   exit macro) without any other explanatory text being displayed.
         */
        failCheckMsg = BMsg(fail check, '{I} {cannot} do that (but the author of
            this game failed to specify why).')

        /* optional command is not supported in this game */
        commandNotPresent()
        {
           DMsg(command not present, '<.parser>That command isn&rsquo;t needed
               in this story.<./parser> ');
        }

        /* acknowledge a change in the score notification status */
        acknowledgeNotifyStatus(stat)
        {
            DMsg(acknowledge notify status, '<.parser>Score notifications are now
            <<stat ? 'on' : 'off'>>.<./parser> ');
        }

        /*
         *   Flag: is this an action that acts on an object even if it is hidden;
         *   normally this will only apply to debugging actions.
         */
        unhides = nil

        /*
         *   This does nothing in the main library but is provided as a hook for the
         *   objtime extension to use to add to the time taken by implicit actions.
         */
        addImplicitTime() { }

        /*
         *   Advance the game clock time. This does nothing in the main library but
         *   is provided as a hook for the objtime extension to use.
         */
        advanceTime() {}
    ;

    /*
     *   The SystemAction class is for actions not affecting the game world but
     *   rather acting on the game session, such as SAVE, RESTORE and QUIT.
     */
    class SystemAction: IAction
        /* A SystemAction is not normally undo-able */
        includeInUndo = nil

        /* A SystemAction is not normally repeatable */
        isRepeatable = nil

        /*
         *   Since a SystemAction isn't an action in the game world, we don't want
         *   it to trigger any after action notifications.
         */
        afterAction() { }

        /*
         *   Since a SystemAction isn't an action in the game world, we don't want
         *   it to count as a turn, so we don't run any Daemons or Fuses and we
         *   don't advance the turn count.
         */
        turnSequence() { }

        /*
         *   A SystemAction doesn't take any turns (this is a bit belt-and-braces
         *   since turnSequence does nothing in any case).
         */
        turnsTaken = 0

        /*
         *   Since this isn't an action within the game world we bypass all the
         *   normal pre-action handling and just execute a reduced cycle.
         */
        exec(cmd) { execCycle(cmd); }

        /*
         *   There's no before notifications for a SystemAction so we simply execute
         *   the action and, if we should define it as repeatable, make a note of it
         *   in case the player issues an AGAIN command on the next turn.
         */
        execCycle(cmd)
        {
            try
            {
                /* Display a message if we're debugging actions. */
                IfDebug(actions,
                        "[Executing <<actionTab.symbolToVal(baseActionClass)>> ]\n" );

                /* Execute the action. */
                execAction(cmd);

                /*
                 *   If we're a repeatable action, note that we were the last action
                 *   to be executed.
                 */
                if(isRepeatable)
                    libGlobal.lastAction = self.createClone();
            }
            catch(ExitActionSignal ex)
            {
            }

            catch(ExitSignal ex)
            {
                actionFailed = true;
            }

        }

         /*
          *   Ask for an input file.  We call the input manager, which displays the
          *   appropriate local file selector dialog. This is used for SystemActions
          *   that need a file to act on, such as SAVE, RESTORE and QUIT.
          */
        getInputFile(prompt, dialogType, fileType, flags)
        {
            return inputManager.getInputFile(prompt, dialogType, fileType, flags);
        }
    ;

    /*
     *   An IAction is an Action that doesn't directly act on any objects. At least
     *   in this version of the library it works just like the base Action class.
     */
    class IAction: Action
        /*
         *   There's usually no point in parsing an IAction again when it's repeated
         *   since there are no objects to have changed.
         */
        againRepeatsParse = nil

        /*
         *   For an IAction there's no point in trying to score anything but the
         *   Actor object; attempting to score objects via their verify properties
         *   will cause a run-time error, since IActions don't define verify
         *   properties and the like.
         */
        scoreObjects(cmd, role, lst)
        {
            if(role == ActorRole)
                inherited(cmd, role, lst);
            else
            {
                /* apply verb-specific adjustments */
                foreach (local i in lst)
                    scoreObject(cmd, role, lst, i);

                /* apply object-specific adjustments */
                foreach (local i in lst)
                    i.obj.scoreObject(cmd, role, lst, i);
            }
        }

        /*
         *   These methods are provided to allow an IAction to be invoked as an
         *   implicit action.
         */
        execResolvedAction()
        {
            /*
             *   Capture the output from this action in case we don't want to
             *   display it (if we're an implicit action).
             */
            local str = gOutStream.captureOutput({: execAction(gCommand) });

            /*
             *   If this action is being performed implicitly, we should display an
             *   implicit action report for it.
             */
            if(isImplicit)
                buildImplicitActionAnnouncement(!actionFailed);

            /* Otherwise, display the normal output from this action */
            else
                say(str);
        }

        execCycle(cmd)
        {
            try
            {
                /*
                 *   Add an output filter to display any pending implicit action
                 *   reports before any other text
                 */
                gOutStream.addOutputFilter(ImplicitActionFilter);

                /* Carry out the inherited handling. */
                inherited(cmd);
            }

            finally
            {
                /*
                 *   Remove the filter that displays pending implicit action
                 *   reports.
                 */
                gOutStream.removeOutputFilter(ImplicitActionFilter);
            }
        }

        /* Nothing to do here. */
        setResolvedObjects([objs]) { }

        /*
         *   An IAction has no resolved objects, so we simply return true to
         *   indicate that scope is not a problem.
         */
        resolvedObjectsInScope()  { return true;  }
    ;

    /*
     *   A TravelAction is one that moves (or at least tries to move) the player
     *   character from one place to another via a command like GO NORTH, or EAST.
     */
    class TravelAction: Action

        baseActionClass = TravelAction

        /*
         *   Use the inherited handling but first make a note of the direction the
         *   actor wants to travel in.
         */
        execCycle(cmd)
        {
            /*
             *   Obtain the direction from the verbProd of the current command
             *   object, unless this TravelAction already defines its direction
             */
            if(!predefinedDirection)
               direction = cmd.verbProd.dirMatch.dir;

            /* Display a debug message if we're debugging actions. */
            IfDebug(actions,
                        "[Executing <<actionTab.symbolToVal(baseActionClass)>>
                        <<direction.name>>]\n" );

            /* Carry out the inherited handling. */
            inherited(cmd);
        }

        /*
         *   Does this TravelAction already define a set direction on its direction
         *   property (so we don't need to look to what direction object the command
         *   refers)?
         */
        predefinedDirection = nil

        /*
         *   Execute the travel command, first carrying out any implicit actions
         *   needed to facilitate travel
         */
        execAction(cmd)
        {

            local getOutAction;

            /*
             *   If the actor is not directly in the room, treat OUT as a request to
             *   get out of the immediate container.
             */
            if(!gActor.location.ofKind(Room) && direction == outDir)
            {
                getOutAction = gActor.location.contType == On ? GetOff : GetOutOf;
                replaceAction(getOutAction, gActor.location);
                return;
            }

            /*
             *   If the actor is not directly in the room, make him/her get out of
             *   his immediate container(s) before attempting travel, unless the
             *   actor is in a vehicle.
             */

            local traveler = TravelConnector.getTraveler(gActor);

            while(!gActor.location.ofKind(Room) && traveler == gActor)
            {
                /* Note the actor's current location. */
                local loc = gActor.location;

                /*
                 *   The action needed to remove the actor from its immediate
                 *   container.
                 */
                getOutAction = loc.contType == On ? GetOff : GetOutOf;

                /*
                 *   Try to get the actor out of his/her current location with an
                 *   implicit action.
                 */
                tryImplicitAction(getOutAction, loc);

                /* Note and if necessary display the implicit action report. */
                "<<buildImplicitActionAnnouncement(true)>>";

                /*
                 *   if the command didn't work, quit the loop or we'll be stuck in
                 *   it forever.
                 */
                if(gActor.location == loc)
                    exit;

            }

            /*
             *   Note and if necessary display any other implicit action reports
             *   that may have been generated prior to executing this action.
             */
            "<<buildImplicitActionAnnouncement(true)>>";

            /* Carry out the actual travel. */
            doTravel();
        }

        /*
         *   Carry out travel in direction. For this purpose we first have to define
         *   what the corresponding direction property of the actor's current
         *   location refers to. If it's nil, no travel is possible, and we simply
         *   display a refusal message. If it's an object we execute its travelVia()
         *   method for the current actor. If it's a double-quoted string or a
         *   method we execute it and make a note of where the actor ends up, if the
         *   actor is the player character. If it's a single-quoted string we
         *   display it.
         *
         *   Note that we only display the various messages announcing failure of
         *   travel if the actor is the player character. We presumably don't want
         *   to see these messages as the result of NPCs trying to move around the
         *   map.
         */
        doTravel()
        {
            /* Note the actor's current location. */
            local loc = gActor.getOutermostRoom;

            /* Variable to hold the connector we're trying to use */
            local conn;

            /*
             *   Note whether the current location is illuminated, or whether it permits
             *   travel in the dark (in which case we treat it as illuminated for
             *   the purposes of allowing travel).
             */
            local illum = loc.allowDarkTravel || loc.isIlluminated;

            /*
             *   If we point to an object, assume it's a travel connector and attempt travel via the
             *   connector.
             */
            if(loc.propType(direction.dirProp) == TypeObject)
            {
                /* Note our connector */
                conn = loc.(direction.dirProp);

                /*
                 *   If the connector is visible to the actor then attempt travel
                 *   via the connector.
                 */
                if(conn.isConnectorVisible)
                {

                    /* if the actor is the player char, just carry out the travel */
                    if(gActor == gPlayerChar)
                        conn.travelVia(gActor);

                    /*
                     *   otherwise carry out the travel and display the appropriate
                     *   travel notifications.
                     */
                    else
                        gActor.travelVia(conn);
                }

                /*
                 *   Otherwise if there's light enough to travel and the actor is
                 *   the player character, display the standard can't travel message
                 *   (as if the connector wasn't there.
                 */
                else if(illum && gActor == gPlayerChar)
                    loc.cannotGoThatWay(direction);

                /*
                 *   Otherwise if the actor is the player character, display the
                 *   standard message forbidding travel in the dark.
                 */
                else if(gActor == gPlayerChar)
                    loc.cannotGoThatWayInDark(direction);
            }

            /*
             *   Otherwise, our direction of travel is trying to take us towards nil, a string, or a
             *   method, in which case call the nonTravel() function to handle it.
             */
            else
                nonTravel(loc, direction);
        }

    //
    //    doTravel()
    //    {
    //        /* Note the actor's current location. */
    //        local loc = gActor.getOutermostRoom;
    //
    //        local conn;
    //
    //        /*
    //         *   Note whether the current location is located, or whether it permits
    //         *   travel in the dark (in which case we treat it as illuminated for
    //         *   the purposes of allowing travel).
    //         */
    //        local illum = loc.allowDarkTravel || loc.isIlluminated;
    //
    //
    //        /*
    //         *   Find out what's attached to the direction property of the direction
    //         *   in which the actor wants to travel and respond accordingly.
    //         */
    //        switch (loc.propType(direction.dirProp))
    //        {
    //            /*
    //             *   If there's nothing there, simply display the appropriate
    //             *   message explaining that travel that way isn't possible.
    //             */
    //        case TypeNil:
    //            if(illum && gActor == gPlayerChar)
    //                loc.cannotGoThatWay(direction);
    //            else if(gActor == gPlayerChar)
    //                loc.cannotGoThatWayInDark(direction);
    //            break;
    //
    //            /*
    //             *   If the direction property points to an object, assume it's some
    //             *   kind of TravelConnector (which also includes Rooms and Doors)
    //             *   and then attempt travel via that object.
    //             */
    //        case TypeObject:
    //            /* Note our connector */
    //            conn = loc.(direction.dirProp);
    //
    //            /*
    //             *   If the connector is visible to the actor then attempt travel
    //             *   via the connector.
    //             */
    //            if(conn.isConnectorVisible)
    //            {
    //
    //                /* if the actor is the player char, just carry out the travel */
    //                if(gActor == gPlayerChar)
    //                    conn.travelVia(gActor);
    //
    //                /*
    //                 *   otherwise carry out the travel and display the appropriate
    //                 *   travel notifications.
    //                 */
    //                else
    //                    gActor.travelVia(conn);
    //            }
    //
    //            /*
    //             *   Otherwise if there's light enough to travel and the actor is
    //             *   the player character, display the standard can't travel message
    //             *   (as if the connector wasn't there.
    //             */
    //            else if(illum && gActor == gPlayerChar)
    //                loc.cannotGoThatWay(direction);
    //
    //            /*
    //             *   Otherwise if the actor is the player character, display the
    //             *   standard message forbidding travel in the dark.
    //             */
    //            else if(gActor == gPlayerChar)
    //                loc.cannotGoThatWayInDark(direction);
    //            break;
    //
    //            /*
    //             *   If the direction property points to a double-quoted method or a
    //             *   string, then provided the illumination is right, we display the
    //             *   string or execute the method. Otherwise show the message saying
    //             *   we can't travel that way in the dark.
    //             */
    //        case TypeDString:
    //        case TypeCode:
    //            if(illum)
    //            {
    //                /*
    //                 *   Call the before travel notifications on every object that's
    //                 *   in scope for the actor. Since we don't have a connector
    //                 *   object to pass to the beforeTravel notifications, we use
    //                 *   the direction object instead.
    //                 */
    //                Q.scopeList(gActor).toList.forEach({x: x.beforeTravel(gActor,
    //                    direction)});
    //
    //
    //                /*
    //                 *   If going this way would take us to a known destination
    //                 *   that's a Room (so that executing the travel should take the
    //                 *   actor out of his/her current room) notify the current room
    //                 *   that the actor is about to depart.
    //                 */
    //                local dest;
    //
    //                if(loc.propType(direction.dirProp) == TypeCode)
    //                    dest = libGlobal.extraDestInfo[[loc, direction]];
    //                else
    //                    dest = nil;
    //
    //                if(dest && dest.ofKind(Room))
    //                    loc.notifyDeparture(gActor, dest);
    //
    //                /*
    //                 *   Then execute the method or display the double-quoted
    //                 *   string.
    //                 */
    //                loc.(direction.dirProp);
    //
    //                /*
    //                 *   If we've just executed a method, it may have moved the
    //                 *   actor to a new location, so if the actor is the player
    //                 *   character note where the method took the actor to so that
    //                 *   the pathfinder can find a route via this exit.
    //                 */
    //                if(gActor == gPlayerChar)
    //                    libGlobal.addExtraDestInfo(loc, direction,
    //                                           gActor.getOutermostRoom);
    //            }
    //            else if(gActor == gPlayerChar)
    //                loc.cannotGoThatWayInDark(direction);
    //            break;
    //
    //            /*
    //             *   If the direction property points to a single-quoted string,
    //             *   simply display the string if the illumination is sufficient,
    //             *   otherwise display the message saying we can't go that way in
    //             *   the dark. If the actor isn't the player character, do nothing.
    //             */
    //        case TypeSString:
    //            if(gActor == gPlayerChar)
    //            {
    //                conn = loc.(direction.dirProp);
    //                if(illum)
    //                {
    //                    say(conn);
    //                    libGlobal.addExtraDestInfo(loc, direction,
    //                                               gActor.getOutermostRoom);
    //                }
    //                else
    //                    loc.cannotGoThatWayInDark(direction);
    //            }
    //            break;
    //
    //        }
    //    }

        /*
         *   The direction the actor wants to travel in. This is placed here by the
         *   execCycle method and takes the form of A Direction object, e.g.
         *   northDir.
         */
        direction = nil

        /* It's generally possible to undo a travel command. */
        canUndo = true
    ;

    /*
     *   This function can be called from a check nethod to prevent the display of text from within the
     *   check method halting the action. Calling it from anywhere else will have no effect. It's use is
     *   in conjunction with the TAction class defined immediatelty below.
     */
    noHalt()
    {
        /*
         *   If we have a current gAction, set its haltOnMessageCheck property to nil. Note that this
         *   property is set to true near the start of TAction.check() so that it starts out true and
         *   remains so unless noHalt() intervenes during the course of the check() stage.
         */
        if(gAction)
            gAction.haltOnMessageInCheck = nil;
    }

    /*
     *   A TAction is an action that applies to a single direct object. Other action
     *   classes that apply to more than one object, such as TIAction, inherit from
     *   this class so some of the code needs to take that into account.
     */
    class TAction: Action

        /*
         *   A list of the direct objects of this action that make it to the report
         *   stage.
         */
        reportList = []

        /*
         *   A list of the direct objects of this action that make it to the action
         *   stage.
         */
        actionList = []

        /*
         *   A LookupTable containing the verify results for this action. This is
         *   keyes on the object being verified, with the value being the worst
         *   verify result encountered for that object so far.
         */
        verifyTab = nil

        /*
         *   Store the last verify failure message so that if we get several
         *   identical ones in a row, we don't keep repeating them
         */
        lastVerifyMsg = nil

        /*
         *   set this property to true if you want to announce the object before the
         *   action result when there's more than one object. If the action routine
         *   summarizes the result at the end you don't want to do this so you
         *   should then set this to nil.
         */
        announceMultiAction = nil

        /* The current direct object of this action */
        curDobj = nil

        /*
         *   The current object being processed (in a TAction, always the curDObj;
         *   in a TI Action either the curDobj or the curIOoj).
         */
        curObj = nil

        /*
         *   Reset values to their starting state when an action is used to execute
         *   a new command.
         */
        reset()
        {
            scopeList = [];
            reportList = [];
            actionList = [];
            verifyTab = nil;
            isImplicit = nil;
            curDobj = nil;
            curObj = nil;
            lastVerifyMsg = nil;
            redirectParent = nil;
        }

        /*
         *   Information to allow the DEBUG ACTIONS command to express a complete
         *   topic phrase
         */
        #ifdef __DEBUG
        dqinfo = ''
        iqinfo = ''
        aqinfo = ''
        #endif

        /*
         *   Execute the command cycle for this action. This differs from the base
         *   Action class in not calling beforeAction directly, since the
         *   beforeAction() notifications occur within the execResolvedAction
         *   method.
         */
        execCycle(cmd)
        {
            /* If we're debugging actions, display some debugging information. */
            IfDebug(actions,
                    "[Executing <<actionTab.symbolToVal(baseActionClass)>> :
                        <<dqinfo>> <<cmd.dobj.name>> <<if cmd.iobj != nil>>
                        : <i><<iqinfo>></i> <<cmd.iobj.name>> <<end>>
                    <<if cmd.acc != nil>>
                        : <i><<aqinfo>></i> <<cmd.acc.name>> <<end>>]\n" );

            /*
             *   Disallow ALL (e.g. EXAMINE ALL) if the action does not permit it.
             *   Since we don't want to block plural matches (for which
             *   cmd.matchedAll is also true) we all test for the presence of 'all'
             *   among the command tokens.
             */
            if(cmd.matchedAll && !(allowAll || parentAllowAll) )
            {
                DMsg(all not allowed, 'Sorry; ALL is not allowed with this command.
                    ');
                abort;
            }

            try
            {
                /* Execute the action. */
                execAction(cmd);

                /*
                 *   If we're a repeatable action, note that we were the last action
                 *   performed (for use with an AGAIN command).
                 */
                if(isRepeatable)
                    libGlobal.lastAction = self.createClone();

            }

            catch(ExitSignal ex)
            {
                actionFailed = true;
            }

        }

        /* Execute this action */
        execAction(cmd)
        {
            /*
             *   Note the current direct object, which should be the direct object
             *   supplied by the current Command object.
             */
            curDobj = cmd.dobj;

            /*
             *   Note the current direct object as a possible antecedent for
             *   pronouns.
             */
            notePronounAntecedent(curDobj);

            /* Execute the action with the current direct object. */
            execResolvedAction();
        }

        /*
         *   Execute this action with a known direct object or objects. Call this
         *   method when there's no need to resolve the objects used in the command
         *   but we still want it to pass through every stage
         */
        execResolvedAction()
        {

            /* Create a new LookupTable for our verify results. */
            verifyTab = new LookupTable;

            /*
             *   We shouldn't really need to catch any signals here, but the author
             *   might put an exitAction macro in a check method, say, so we need to
             *   be able to handle it.
             */
            try
            {
                /*
                 *   Obtain the verify result for the current direct object. Note at
                 *   this point the objects have already been resolved so we're only
                 *   interested in whether the verify command is going to allow the
                 *   action to go ahead. If it doesn't allow the action return nil
                 *   to stop it here.
                 */
                if(!verifyObjRole(curDobj, DirectObject))
                   return nil;

                /*
                 *   If gameMain defines the option to run the before notifications
                 *   before the check stage, run the before notifications now.
                 */
                if(gameMain.beforeRunsBeforeCheck)
                    beforeAction();

                /*
                 *   Try the check stage. If the action fails the check stage, stop
                 *   the action here and return nil to tell our caller this action
                 *   has failed.
                 */
                if(!checkAction(cmd))
                    return nil;

                /*
                 *   If gameMain defines the option to run the before notifications
                 *   after the check stage, run the before notifications now.
                 */
                if(!gameMain.beforeRunsBeforeCheck)
                    beforeAction();

                /* Carry out the action on a single direct object. */
                doActionOnce();

                /* Return true to tell our caller the action succeeded. */
                return true;

            }
            catch (ExitActionSignal ex)
            {
                return nil;
            }

        }

        /*
         *   Flag: do we want the object name to appear before a check stage failure
         *   message if multiple objects are involved in the action. By default we
         *   do, otherwise it might not be clear which object the message referes
         *   to.
         */
        announceMultiCheck = true

        /*
         *   Run the check phase of the action, both on the direct object and on any
         *   preconditions.
         */
        checkAction(cmd)
        {
            /*
             *   Try the check phase of any preconditions. If that fails return nil
             *   to indicate failure of the entire check stage.
             */
            if(!checkPreCond(curDobj, preCondDobjProp))
            {
                return nil;
            }

            /*
             *   Then try the check method on the current direct object and return
             *   the result.
             */
            return check(curDobj, checkDobjProp);
        }

        /*
         *   This flag is used internally by the library to track whether the output of any text from a
         *   check() should stop the action, which it normally should. Game code should not directly
         *   override this property or change its value, other than indrectly via the noHalt() function.
         */
        haltOnMessageInCheck = true

        /*
         *   Call the check method (checkProp) on the appropriate object (obj).
         *   Return true to indicate that the action succeeds or nil otherwise
         */
        check(obj, checkProp)
        {
            local checkMsg = nil;

            /* Note which object is the current object of the command. */
            curObj = obj;

            /* Run the check method on the object and capture its output */
            try
            {
                /* Set this flag to true - the check routine may set it to nil. */
                haltOnMessageInCheck = true;

                checkMsg = gOutStream.captureOutputIgnoreExit({: obj.(checkProp)});
            }

            /*
             *   Game authors aren't meant to use the exit macro in check methods,
             *   but in case they do we handle it here.
             */
            catch (ExitSignal ex)
            {
                /*
                 *   If for some reason a check method uses exit without displaying
                 *   a method, we supply a dummy failure message at this point.
                 */
                if(checkMsg is in (nil, ''))
                   checkMsg = failCheckMsg;
            }

            /*
             *   If the check method tried to display something then it wants to
             *   block the action, so we display the failure message and stop the
             *   action.
             */
            if(checkMsg not in (nil, ''))
            {
                /*
                 *   If this action wants to announce the object of the action when it fails at the
                 *   check stage and our Command is processing more than one direct object, and we don't
                 *   want to report failed attempts after successful ones, announce the object.
                 */
                if(announceMultiCheck && gCommand.dobjs.length > 1 && !reportFailureAfterSuccess)
                    announceObject(obj);

                /*
                 *   If we're an implicit action then add a failure message to our
                 *   implicit action list and display the list ("first trying
                 *   to...")
                 */
                if(isImplicit)
                    "<<buildImplicitActionAnnouncement(nil)>>";
                else if(haltOnMessageInCheck)
                    /* first flush any pending implicit action reports */
                    "<<buildImplicitActionAnnouncement(true)>>";

                /*
                 *   Display our failure message. If this command is processing more than one direct
                 *   object, and we want to report failed attempts after successrul ones, use
                 *   reportAfter() so that the failure reports come after the report of any actions that
                 *   were successful, otherwise display the failure message straight away.
                 */
                if(gCommand.dobjs.length > 1 && reportFailureAfterSuccess)
                {
                    if(announceMultiCheck)
                        checkMsg = gOutStream.captureOutputIgnoreExit({: announceObject(obj)}) +
                        checkMsg;

                    reportAfter(checkMsg);
                }
                else
                {
                    say(checkMsg);
                    "\n";
                }

                /*
                 *   Note the outcome of the action -- it failed unless haltOnMesageInCheck was set to
                 *   nil.
                 */
                actionFailed = haltOnMessageInCheck;

                /*
                 *   Return the opposite of haltOnMessageInCheck to tell our caller whether this action
                 *   failed the check stage
                 */
                return !haltOnMessageInCheck;
            }

            /*
             *   Return true to tell our our caller this action passed the check
             *   stage on this object.
             */
            return true;
        }

        /*
         *   Flag: when a command processes multiple direct objects, do we want any failed attempts to
         *   be reported after successful ones?
         */
        reportFailureAfterSuccess = nil

        /* Run the check stage on the preCondProp of obj */
        checkPreCond(obj, preCondProp)
        {
            local preCondList;
            local checkOkay = true;

            /* Note which object we're checking */
            curObj = obj;

            /*
             *   Construct a list or preCondition objects on the appropriate object
             *   property.
             */
            preCondList = valToList(obj.(preCondProp));

            /* Sort the list in preCondOrder */
            preCondList = preCondList.sort(nil,
                                           {a, b: a.preCondOrder - b.preCondOrder});

            try
            {
                /* Iterate through the list to see if all the checks are satisfied */
                foreach(local cur in preCondList)
                {
                    /*
                     *   If we fail the check method on any precondition object,
                     *   note the failure and stop the iteration.
                     */
                    if(cur.checkPreCondition(obj, true) == nil)
                    {
                        checkOkay = nil;
                        break;
                    }
                }
            }
            /*
             *   Game authors aren't meant to use the exit macro in check methods,
             *   but in case they do we handle it here.
             */
            catch (ExitSignal ex)
            {
                checkOkay = nil;
            }

            /*
             *   If the check method failed on any of our precondition objects note
             *   that the action is a failure.
             */
            if(checkOkay == nil)
                actionFailed = true;

            /* Return our overall check result. */
            return checkOkay;
        }

        /* Carry out the action phase on the direct object */
        doActionOnce()
        {
            local msg;

            /*
             *   If we're iterating over several objects and we're the kind of
             *   action which wants to announce objects in this context, do so.
             */
             if(announceMultiAction && gCommand.dobjs.length > 1)
                announceObject(curDobj);

            /* Note that the current object is the direct object */
            curObj = curDobj;

            /*
             *   Add the ImplicitActionFilter to the current output stream so that
             *   any pending implicit action reports are prepended to any action
             *   reports output at this stage.
             */
            if(isImplicit)
                buildImplicitActionAnnouncement(true, nil);

            /*
             *   If the action method displays anything then we don't add this
             *   object to the list of objects to be reported on at the report
             *   stage, on the assumption that the action stage has either produced
             *   its own report for this object or reported on the failure of the
             *   action. If, however, the action is carried out silently then we'll
             *   add this object to the list of objects to be reported on at the
             *   report stage.
             *
             *   NOTE TO SELF: Don't try making this work with captureOutput(); it
             *   creates far more hassle than it's worth!!!!
             */
            try
            {
                gOutStream.addOutputFilter(ImplicitActionFilter);

                msg = gOutStream.watchForOutput({: doAction() });
            }
            finally
            {
                /* Remove any implicit action announcement from the output stream */

                gOutStream.removeOutputFilter(ImplicitActionFilter);
            }

            /*
             *   If there's no output from the action method, add this object to the
             *   list of objects to be reported on at the report stage.
             */
            if(!(msg))
            {
                reportList += curDobj;
            }

            /* Note that we've carried out the action on this object. */
            actionList += curDobj;

            /*
             *   Return true to tell our caller we succesfully completed the action.
             */
            return true;
        }

        doAction()
        {
            try
            {
               curDobj.(actionDobjProp);
            }
            catch(ExitActionSignal ex)
            {
    //            actionFailed = true;
            }
        }

        /*
         *   Flag, do we want to announce the object name before the verify message
         *   in cases where there's more direct object in the command? By default we
         *   don't since verify messages generally make it clear enough which
         *   objects they refer to.
         */
        announceMultiVerify = nil

        /*
         *   Return a list of direct objects corresponding to the word ALL in the
         *   player's command. By default we return everything in scope that isn't a
         *   a Room.
         */
        getAll(cmd, role)
        {
            return scopeList.subset({ x: !x.ofKind(Room)});
        }

        /*
         *   Add a verify result to this action's verify table. This method is
         *   normally called by one of the macros (logical, illogical, logicalRank,
         *   etc.) use in an object's verify routine.
         */
        addVerifyResult(verRes)
        {
            /* Note the object to which this verify result relates. */
            local obj = verRes.myObj;

            /*
             *   If it isn't the object we're currently meant to be verifying,
             *   adjust it.
             */
            if(obj != verifyObj)
            {
                obj = verifyObj;
                verRes.myObj = obj;
            }

            /*
             *   If we don't currently have a verify table for this action, create
             *   one
             */
            if(verifyTab == nil)
                verifyTab = new LookupTable();

            /*
             *   Add this verify result to this action's verify table only if it
             *   doesn't already contain a verify result for the same object with a
             *   lower resultRank.
             */
            if(!verifyTab.isKeyPresent(obj) ||
                verRes.resultRank < verifyTab[obj].resultRank)
                verifyTab[obj] = verRes;
        }

        /*
         *   reportAction() is called only after all the action routines have been
         *   run and the list of dobjs acted on is known. It only does anything if
         *   the action is not implicit. It can thus be used to summarize a list of
         *   identical actions carried out on every object in reportList or to print
         *   a report that is not wanted if the action is implicit. By default we
         *   call the dobj's reportDobjProp to handle the report.
         *
         *   Note that this method is usually called from the current Command object
         *   after its finished iterated over all the direct objects involved in the
         *   command.
         */
        reportAction()
        {
            /*
             *   If we're not an implicit action and there's something in our report
             *   list to report on, execute the report stage of this action.
             */
            if(!isImplicit && reportList.length > 0)
            {

                /* Output any pending implicit action reports */
                "<<buildImplicitActionAnnouncement(true)>>";

                curDobj.(reportDobjProp);
            }

        }

        /* install the resolved objects in the action */
        setResolvedObjects(dobj)
        {
            curDobj = dobj;
        }

        /* Check whether the resolved objects for this action are in scope */
        resolvedObjectsInScope()
        {
            buildScopeList();
            return scopeList.indexOf(curDobj) != nil;
        }

        /*
         *   Get a message parameter object for the action.  We define 'dobj'
         *   as the direct object, in addition to any inherited targets.
         */
        getMessageParam(objName)
        {
            switch(objName)
            {
            case 'dobj':
                /* return the current direct object */
                return curDobj;

            case 'cobj':
                /* return the current object */
                return curObj;

            default:
                /* inherit default handling */
                return inherited(objName);
            }
        }

        /*
         *   A convenience method for putting every game object in scope, which may
         *   be appropriate for certain commands (not least, certain debugging
         *   commands). It's intended to be called from addExtraScopeItems when
         *   needed.     */

        makeScopeUniversal()
        {
            /* Note the fist object of the Thing class. */
            local obj = firstObj(Thing);

            /* Create a vector to store our results. */
            local vec = new Vector;

            /* Go through every Thing in the game and add it to our vector. */
            do
            {
                vec.append(obj);
                obj = nextObj(obj, Thing);
            } while (obj!= nil);

            /*
             *   Convert the vector to a list and append it to our scopeList,
             *   removing any duplicates.
             */
            scopeList = scopeList.appendUnique(vec.toList());
        }

    ;

    /*
     *   A TIAction is an action that applies to both a direct object and an
     *   indirect object. Since it inherits from TAction we only need to define the
     *   additional methods and properties relating to the handling of indirect
     *   objects.
     */
    class TIAction: TAction

        /* The current indirect object of this action. */
        curIobj = nil

        /* The various methods to call on the indirect object of this action. */
        verIobjProp = nil
        checkIobjProp = nil
        actionIobjProp = nil
        preCondIobjProp = nil

        /*
         *   A list of the indirect objects that this actually actually ends up
         *   acting on at the action stage.
         */
        ioActionList = []

        /*
         *   Flag: should we resolve the indirect object of this action before the
         *   direct object?
         */
        resolveIobjFirst = true

        /* Reset the action variables to their initial state. */
        reset()
        {
            inherited;
            curIobj = nil;
            ioActionList = [];
        }

        /* execute this action. */
        execAction(cmd)
        {
            /*
             *   Note the current direct object of this command from the Command
             *   object.
             */
            curDobj = cmd.dobj;

            /*
             *   Note the current indirect object of this command from the Command
             *   object.
             */
            curIobj = cmd.iobj;

            /* Note both objects as possible pronoun antecedents. */
            notePronounAntecedent(curDobj, curIobj);

            /* execute the resolved action. */
            execResolvedAction();
        }

        /* Carry out the check phase for this command. */
        checkAction(cmd)
        {

            /*
             *   If we don't pass the check stage on both the iobj and the dobj's
             *   preconditions, then return nil to tell our caller we've failed this
             *   stage.
             */
            if(!(checkPreCond(curIobj, preCondIobjProp)
                 && checkPreCond(curDobj, preCondDobjProp)))
                return nil;

            /*
             *   Return the result of running the check phase on both the indirect
             *   and the direct objects.
             */
            return check(curIobj, checkIobjProp) && check(curDobj, checkDobjProp);

        }

        /* Set the resolved objects for this action. */
        setResolvedObjects(dobj, iobj)
        {
            curDobj = dobj;
            curIobj = iobj;
        }

        /*
         *   Test whether both the direct and the indirect objects for this action
         *   are in scope.
         */
        resolvedObjectsInScope()
        {
            buildScopeList();
            return scopeList.indexOf(curDobj) != nil
                && scopeList.indexOf(curIobj) != nil;
        }

        /*
         *   Carry out the report phase for this action. If there's anything in the
         *   ioActionList and we're not an implicit action, call the report method
         *   on the indirect object. Then carry out the inherited handling (which
         *   does the same on the direct object). Note that this method is called by
         *   the current Command object once its finished iterating over all the
         *   objects involved in the command.
         */
        reportAction()
        {

            /*
             *   Carry out the inherited handling, which executes the report stage
             *   on the direct object.
             */
            inherited;
            /*
             *   If we're not an implicit action and there's something to report on,
             *   carry out the report stage on our indirect object.
             */
            if(!isImplicit && ioActionList.length > 0)
                curIobj.(reportIobjProp);
        }

        /* Get the message parameters relating to this action */
        getMessageParam(objName)
        {
            switch(objName)
            {
            case 'iobj':
                /* return the current indirect object */
                return curIobj;

            default:
                /* inherit default handling */
                return inherited(objName);
            }
        }

        /*
         *   Execute this action as a resolved action, that is once its direct and
         *   indirect objects are known.
         */
        execResolvedAction()
        {
            try
            {
                /*
                 *   If the indirect object was resolved first (before the
                 *   direct object) then we run the verify stage on the indirect
                 *   action first. If it fails, return nil to tell the caller it
                 *   failed.
                 */
                if(resolveIobjFirst && !verifyObjRole(curIobj, IndirectObject))
                    return nil;

                /*
                 *   Run the verify routine on the direct object next. If it
                 *   disallows the action, stop here and return nil.
                 */
                if(!verifyObjRole(curDobj, DirectObject))
                    return nil;

                /*
                 *   If the indirect object was resolved after the direct
                 *   object, run the verify routines on the indirect object now, and
                 *   return nil if they disallow the action.
                 */
                if(!resolveIobjFirst && !verifyObjRole(curIobj, IndirectObject))
                    return nil;

                /*
                 *   If gameMain defines the option to run before notifications
                 *   before the check stage, run the before notifications now.
                 */
                if(gameMain.beforeRunsBeforeCheck)
                    beforeAction();

                /*
                 *   Try the check stage on both objects. If either disallows the
                 *   action return nil to stop the action here.
                 */
                if(!checkAction(nil))
                    return nil;

                /*
                 *   If gameMain defines the option to run before notifications
                 *   after the check stage, run the before notifications now.
                 */
                if(!gameMain.beforeRunsBeforeCheck)
                    beforeAction();

                /* Carry out the action stage on one set of objects */
                doActionOnce();

                /* Return true to tell our caller the action was a success */
                return true;
            }

            catch (ExitActionSignal ex)
            {

    //            actionFailed = true;

                return nil;
            }

        }

        /*
         *   Execute the action phase of the action on both objects. Note that
         *   although some TIActions can operate on multiple direct objects, none
         *   defined in the library acts on multiple indirect objects, so there's
         *   only minimal support for the latter possibility.
         */
        doActionOnce()
        {

            local msgForDobj, msgForIobj;

            /*
             *   If we're iterating over several objects and we're the kind of
             *   action which wants to announce objects in this context, do so.
             */
            if(announceMultiAction && gCommand.dobjs.length > 1)
                announceObject(curDobj);

            /*
             *   Note that the current object we're dealing with is the direct
             *   object.
             */
            curObj = curDobj;

            /*
             *   Add the ImplicitActionFilter to the current output stream so that
             *   any pending implicit action reports are prepended to any action
             *   reports output at this stage.
             */
            if(isImplicit)
                buildImplicitActionAnnouncement(true, nil);

            try
            {
                /*
                 *   First add the ImplicitActionFilter to the output stream so that
                 *   any text output from the action routines are preceeded by any
                 *   pending implicit action reports.
                 */

                gOutStream.addOutputFilter(ImplicitActionFilter);

                /*
                 *   Run the action routine on the current direct object and capture
                 *   the output for later use. If the output is null direct object
                 *   can be added to the list of objects to be reported on at the
                 *   report stage, provided the iobj action routine doesn't report
                 *   anything either.
                 *
                 *   NOTE TO SELF: Don't try making this work with captureOutput();
                 *   it creates far more hassle than it's worth!!!!
                 */
                msgForDobj =
                    gOutStream.watchForOutput({:curDobj.(actionDobjProp)});

                /* Note that we've acted on this direct object. */
                actionList += curDobj;

                /* Note that the current object is now the indirect object. */
                curObj = curIobj;

                /*
                 *   Execute the action method on the indirect object. If it doesn't
                 *   output anything, add the current indirect object to
                 *   ioActionList in case the report phase wants to do anything with
                 *   it, and add the dobj to the reportList if it's not already
                 *   there so that a report method on the dobj can report on actions
                 *   handled on the iobj.
                 */
                msgForIobj =
                    gOutStream.watchForOutput({:curIobj.(actionIobjProp)});
            }

            finally
            {
                /* Remove any implicit action announcement from the output stream */

                gOutStream.removeOutputFilter(ImplicitActionFilter);
            }

            /*
             *   If neither the action stage for the direct object nor the action
             *   stage for the direct object produced any output then add the
             *   indirect object to the list of indirect objects that could be
             *   reported on, and add the current direct object to the list of
             *   direct objects to be reported on at the report stage.
             */
            if(!(msgForDobj) && !(msgForIobj))
            {
                ioActionList += curIobj;

                reportList = reportList.appendUnique([curDobj]);
            }

            /*
             *   Return true to tell our caller we completed the action
             *   successfully.
             */
            return true;
        }

    ;

    /*
     *   A LiteralAction is an action that acts on a single literal object, e.g.
     *   TYPE HELLO
     */
    class LiteralAction: IAction
        exec(cmd)
        {
            /* Note the literal string associated with this command. */
            literal = cmd.dobj.name;

            /* carry out the inherited handling. */
            inherited(cmd);
        }

        /* The string literal on which this command is operating. */
        literal = nil

        /* The numerical value of our literal */
        num = tryNum(literal)
    ;

    /*
     *   A LiteralTAction is an action that involves one physical object and one
     *   string, e.g. TYPE HELLO ON TERMINAL.
     */
    class LiteralTAction: TAction
        execAction(cmd)
        {

            /*
             *   Determine which is the Thing-based object and which is the literal
             *   value and plug each into the right slot (so that the Thing ends up
             *   as the direct object of the command and the string as the literal).
             */
            if(cmd.dobj.ofKind(Thing))
            {
                curDobj = cmd.dobj;
                literal = cmd.iobj.name;
            }
            else
            {
                curDobj = cmd.iobj;
                literal = cmd.dobj.name;
            }

            /* Note the direct object as an antecedent for pronouns */
            notePronounAntecedent(curDobj);

            /* Execute the resolved action (as for a TAction) */
            execResolvedAction();
        }

        /*
         *   Whichever object slot a verify routine is notionally trying to verify
         *   for given the grammatical form of the command, in practice only the
         *   direct object (the thing involved in the command) can be verified. E.g.
         *   for WRITE FOO ON BALL we treat BALL as the direct object of the command
         *   and FOO as the literal, even if the Parser thinks it needs to verify
         *   the Indirect Object to disambiguate BALL.
         */
        verify(obj, role)
        {
            return inherited(obj, DirectObject);
        }

        /* The literal value associated with this command */
        literal = nil

        /* The numerical value of our literal */
        num = tryNum(literal)
    ;

    /*
     *   A TopicTAction is an action involving one physical object and one topic,
     *   e.g. ASK BOB ABOUT TOWER.
     */
    class TopicTAction: TAction
        execAction(cmd)
        {

            /*
             *   determine which is the Thing-type object and which is the topic
             *   value and plug each into the right slot. We ensure that the
             *   physical object (the Thing) ends up as the direct object and the
             *   ResolvedTopic as the indirect object.
             */
            if(cmd.dobj && cmd.dobj.ofKind(Thing))
            {
                curDobj = cmd.dobj;
                curIobj = cmd.iobj;
                curTopic = cmd.iobj;
            }
            else
            {
                curDobj = cmd.iobj;
                curIobj = cmd.dobj;
                curTopic = cmd.dobj;
            }

            /* Note the direct object as a potential pronoun antecedent. */
            notePronounAntecedent(curDobj);

            /* Attempt to resolve any pronouns within the ResolvedTopic */
            resolvePronouns();

            /* Execute the action as for a TAction */
            execResolvedAction();

        }

        /*
         *   Although we don't have an indirect object in the conventional sense, we
         *   use the curIobj property to store the ResolvedTopic involved in the
         *   command.
         */
        curIobj = nil

        /*
         *   We also store the current ResolvedTopic in the curTopic property so it
         *   can be found by the gTopic macro.
         */
        curTopic = nil

        /*
         *   This is a bit of a kludge to deal with the fact that the Parser doesn't
         *   seem able to resolve pronouns within ResolvedTopics. We do it here
         *   instead.
         */
        resolvePronouns()
        {
            if(curIobj == nil)
                return;

            for(local cur in curIobj.topicList, local i = 1;; ++i)
            {
                if(cur == Him && curDobj.isHim)
                    curIobj.topicList[i] = curDobj;

                if(cur == Her && curDobj.isHer)
                    curIobj.topicList[i] = curDobj;

                if(cur == It && curDobj.isIt)
                    curIobj.topicList[i] = curDobj;

                if(cur == Them && (curDobj.plural || curDobj.ambiguouslyPlural))
                    curIobj.topicList[i] = curDobj;
            }
        }

        /*
         *   Whichever object slot a verify routine is notionally trying to verify
         *   for given the grammatical form of the command, in practice only the
         *   direct object (the thing involved in the command) can be verified. E.g.
         *   for WRITE FOO ON BALL we treat BALL as the direct object of the command
         *   and FOO as the literal, even if the Parser thinks it needs to verify
         *   the Indirect Object to disambiguate BALL.
         */
        verify(obj, whichObj)
        {
            return inherited(obj, DirectObject);
        }

        /*
         *   Is the topic the grammatical Indirect object of this command? This is
         *   used by Redirector.doOtherAction() to encapsulate the appropriate
         *   string in a ResolvedTopic. The topic is the grammatical iobj if its the
         *   second object involved in the commamd, e.g. ASK BOB ABOUT FIRE, where
         *   FIRE is the topic.
         */
        topicIsGrammaticalIobj = true
    ;

    /*
     *   A NumericTAction is an action that involves one physical object and one
     *   number, e.g. DIAL 1234 ON PHONR.
     */
    class NumericTAction: TAction
        execAction(cmd)
        {

            /*
             *   Determine which is the Thing-based object and which is the numeric
             *   value and plug each into the right slot (so that the Thing ends up
             *   as the direct object of the command and the number as the num).
             */
            if(cmd.dobj.ofKind(Thing))
            {
                curDobj = cmd.dobj;
                num = cmd.iobj.numVal;
            }
            else
            {
                curDobj = cmd.iobj;
                num = cmd.dobj.numVal;
            }

            /* Note the direct object as an antecedent for pronouns */
            notePronounAntecedent(curDobj);

            /* Execute the resolved action (as for a TAction) */
            execResolvedAction();
        }

        /*
         *   Whichever object slot a verify routine is notionally trying to verify
         *   for given the grammatical form of the command, in practice only the
         *   direct object (the thing involved in the command) can be verified. E.g.
         *   for WRITE FOO ON BALL we treat BALL as the direct object of the command
         *   and FOO as the literal, even if the Parser thinks it needs to verify
         *   the Indirect Object to disambiguate BALL.
         */
        verify(obj, role)
        {
            return inherited(obj, DirectObject);
        }

        /* The numeric value associated with this command */
        num = nil
    ;

    /*
     *   A TopicAction is an action referring to a single Topic (e.g. TALK ABOUT THE
     *   TOWER). It behaves almost exactly like an IAction.
     */

    class TopicAction: IAction
        exec(cmd)
        {
            /*
             *   For a TopicAction the ResolvedTopic will be in the dobj property of
             *   the cmd object. Store it in the curTopic property.
             */
            curTopic = cmd.dobj;

            /* Then carry out the inherited handling. */
            inherited(cmd);
        }

        /* The ResolvedTopic object associated with this action. */
        curTopic = nil
    ;

    /*
     *   A NumericAction is an action referring to a single Number (e.g. Footnote
     *   1). It behaves almost like an IAction.
     */
    class NumericAction: IAction
        exec(cmd)
        {
            /* Note the number associated with this command. */
            num = cmd.dobj.numVal;

            /* carry out the inherited handling. */
            inherited(cmd);
        }

        /* The number on which this command is operating. */
        num = nil
    ;

    /* Try action as an implicit action with [objs] as its objects */
    tryImplicitAction(action, [objs])
    {

        local oldAction;

        /*
         *   Create a new copy of the action we're to try executing so we don't
         *   contaminate the properties of the same action if it'e being used
         *   elsewhere in the call chain.
         */
        action = action.createInstance();

        /* Our new action will be an implict action. */
        action.isImplicit = true;

        /* Note the previous action being executed. */
        oldAction = gAction;

        /* 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;

        /*
         *   Note that the previous current action is our new action's parent action
         */
        action.parentAction = gAction;

        /* Make our new action the current action. */
        gAction = action;

        try
        {
            /* Execute our new action. */
            action.execResolvedAction();

            /* Provide a hook for the objtime extension to use. */
            action.addImplicitTime();

            /*
             *   If all went well, return true to indicate that we were able to
             *   execute the action.
             */
            return true;
        }

        /*
         *   If the action threw an AbortImplicitSignal this means that its verify
         *   routine does not allow the action to be carried out implicitly; return
         *   nil to signal that we weren't allowed to attempt this implicit action.
         */
        catch (AbortImplicitSignal ex)
        {
            return nil;
        }

        finally
        {
            /* Restore the original current action. */
            gAction = oldAction;
        }

    }

    /*
     *   Have an actor other than the current gActor try an implicit action (e.g. if
     *   an npc moving as the result of an AgendaItem needs to implicitly open a
     *   door to proceed): actor is the actor performing the action, action is the
     *   action object to be performs, [objs] is the list of objects (if any) on
     *   which the action is to be performed.
     */
    tryImplicitActorAction(actor, action, [objs])
    {
        /*
         *   Set up a local variable to store the result of trying the implicit
         *   action.
         */
        local res = nil;

        /*  Make a note of the current actor of the current main command. */
        local oldActor = gActor;

        try
        {
            /* Temporarily make gActor the actor passed to this function. */
            gActor = actor;

            /* Try the implicit action with this actor and store the result. */
            res = tryImplicitAction(action, objs...);
        }

        finally
        {
            /* Restore the original gActor */
            gActor = oldActor;
        }

        /* Return the result of attempting the implicit action. */
        return res;
    }

    /* ------------------------------------------------------------------------ */
    /*
     *   Run a replacement action.
     */
    replaceAction(action, [objs])
    {
        /* run the replacement action as a nested action */
        execNestedAction(true, gActor, action, objs...);

        /* the invoking command is done */
        exit;;
    }

    /* Run a replacement action for another actor. */
    replaceActorAction(actor, action, [objs])
    {

        /* run the replacement action as a nested action */
        execNestedAction(true, actor, action, objs...);

        /* the invoking command is done */
        exit;
    }

    /*
     *   Run a nested action; execution of the parent action continues once the
     *   nested action is complete.
     */
    nestedActorAction(actor, action, [objs])
    {
        execNestedAction(nil, actor, action, objs...);
    }

    /* Run a nested action for the current actor. */
    nestedAction(action, [objs])
    {
        execNestedAction(nil, gActor, action, objs...);
    }

    /*
     *   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, actor, action, [objs])
    {
        local oldAction;
        local oldActor = gActor;

        /*
         *   Create a new instance of the desired action, so we don't override the
         *   current state of any similar action higher up the calling chain.
         */
        action = action.createInstance();

        /* Make the new action make a note of its parent action */
        action.parentAction = gAction;

        /*
         *   Treat us an an implicit action if the current (parent) action is
         *   implicit.
         */
        action.isImplicit = gAction.isImplicit;

        /* Note the previous (calling) action. */
        oldAction = gAction;

        /* Set the current actor to the value of our actor parameter. */
        gActor = actor;

        /* Install the new actor on the current Command object. */
        gCommand.actor = actor;

        /*
         *   Change the current Command object's action to the new action with its
         *   new objects.
         */
        gCommand.changeAction(action, objs.element(1), objs.element(2),
                              objs.element(3));

        /* If our objects aren't in scope we can't proceed with the action. */
        if (objs.length > 0 && !action.resolvedObjectsInScope())
            return nil;

        try
        {
            /* Execute the new action */
            action.execAction(gCommand);

            /*
             *   In principle we only want to show the reportAction report if we're
             *   not a replacement action, leaving a replacement action to display
             *   its report in the normal course of the Command's action-processing
             *   cycle, but there are certain circumstances where even a replacement
             *   action needs to display its reportAction here, specifically (1) if
             *   there's been a change of actor (in which case replaceAction has
             *   arguably been misused) or (2) if an object announcement has just
             *   been displayed for the parent action, in which case we need to
             *   ensure that the report corresponding to the object announcement is
             *   displayed immediately after the object name (by displaying the
             *   report straight away here).
             */
            if(!isReplacement || gActor != oldActor ||
             (action.parentAction.announceMultiAction && gCommand.dobjs.length > 1))
            {
                /* report the outcome of the action. */
                action.reportAction();

                /*
                 *   If this is a replacement action, there won't be anything
                 *   printed after reportAction displays a report so we need to
                 *   print a newline in case there's another object.
                 */
                if(isReplacement)
                    "\n";

                /*
                 *   Empty the report list to ensure the report isn't duplicated
                 *   later
                 */
                action.reportList = [];
            }

            /*
             *   Return true to indicate that the action was completed successfully.
             */
            return true;
        }

        catch (AbortImplicitSignal ex)
        {
            /* Return nil to indicate failure. */
            return nil;
        }

        finally
        {
            /*
             *   If we're not a replacement action we need to restore the old
             *   gAction when we're done; and if we're a nested action and not
             *   implicit, then we should show our action reports (if any) before
             *   handing back to the main action. We also need to do this if we've
             *   changed actor, since unpredictable results could occur from
             *   substituting an action by one actor with one by another.
             *
             *   We try to avoid doing this if this is a replacement action, because
             *   if possible we want all aspects of the new action, including its
             *   reporting and after action processing.
             */
            if(!isReplacement || gActor != oldActor)
            {
                /* Restore the original action on the current Command object. */
                gCommand.action = gCommand.originalAction;

                /* Restore the original current action. */
                gAction = oldAction;

                /* Restore the original actor. */
                gActor = oldActor;

                /* Restore the original actor on the Command object. */
                gCommand.actor = oldActor;
            }
        }
    }

    /*
     *   Ask for a missing object to fulfil role in action. First see if there's a
     *   uniquely best match to fill the role, and if so execute the action with
     *   that object. Otherwise ask the player to supply an object.
     */
    askMissingObject(action, role)
    {

        /* Make action the current action for the current Command. */
        gCommand.action = action;
        gCommand.action.reset();

        /*
         *   Store the current objects of the current action in the new action, in case
         *   action.scoreObjects() needs to refer to them below.
         */
        action.curDobj = gDobj;
        action.curIobj = gIobj;
        action.curAobj = gAobj;

        gCommand.dobj = action.curDobj;
        gCommand.iobj = action.curIobj;
        gCommand.acc = action.curAobj;

        /*
         *   Make the action the original action for the current Command; we need to
         *   do this because otherwise the Command object will overwrite our new
         *   action with its original one before we're done.
         */
        gCommand.originalAction = action;

        /*
         *   Slot the new action's verbRule into the Command's verbProd, so that the
         *   Command has a verbProd appropriate to the action.
         */
        gCommand.verbProd = action.verbRule;

        /* See if we can find an obvious best object to select. */

        /* First get the scope list for the new action. */
        action.buildScopeList(role);

        /*
         *   Then wrap the scopeList in a list of NP objects so we can use it as a
         *   parameter for scoreObjects().
         */
        local matchList = action.wrapObjectsNP(action.scopeList);

        /*   Make a note of the highest scoring object we find */
        local bestObj = nil;

        /*
         *   If we found any objects we could match, determine which of them is the
         *   best match.
         */
        if(matchList.length > 0)
        {
            /* Score all the objects in scope */
            action.scoreObjects(gCommand, role, matchList);

            /* Sort the list of objects in descending order of score */
            matchList = matchList.sort(SortDesc, {a, b: a.score - b.score});

            /* If there's only one object with the top score, select it */
            if(matchList.countWhich({o: o.score == matchList[1].score}) == 1)
                bestObj = matchList[1].obj;
        }

        /*
         *   If we have a best object, check that the command can actually use it
         *   before finally selecting it.
         */
        if(bestObj != nil)
        {
            /*
             *   Obtain the verify result for the best object for this action in
             *   this role.
             */
            local verResult = action.verify(bestObj, role);

            /*
             *   Only execute the action with the best object if the action would
             *   pass the verify stage and the verify stage would allow the action
             *   to be performed implicitly. That way we won't choose an object with
             *   a dangerous or nonObvious verify result, and we won't pointlessly
             *   attempt an impossible action.
             */
            if(verResult.allowAction && verResult.allowImplicit)
            {
                /*
                 *   Announce which object we've chosen; language-specific modules
                 *   will need to implement this.
                 */
                announceBestChoice(action, bestObj, role);

                /*
                 *   Slot our best choice of object into the appropriate object
                 *   property of the current command object.
                 */
                gCommand.(role.objProp) = bestObj;

                /* Execute the new action with the new set of objects. */

                gCommand.execDoer([action, gCommand.dobj, gCommand.iobj]);

                /*
                 *   If we were able to execute the new action with the new set of
                 *   objects, we're done; and we don't want to continue with the
                 *   original action.
                 */
                exit;
            }
        }

        /*
         *   If we couldn't find an obvious best object to use, prompt the player
         *   for his/her choice of object
         */

        /*
         *   First create a new error for a missing object for our Command in the
         *   desired role.
         */
        local err = new EmptyNounError(gCommand, role);

        /*
         *   When the player's response is reparsed, we only want to resolve the
         *   nound for the role we're asking about here, so tell the command which
         *   role we want to resolve for.
         */
        gCommand.npToResolve = role;

        /*
         *   Display the corresponding error message (which will be a request to
         *   specify the missing object.
         */
        err.display();

        /*
         *   Set the Parser's question property to a question asking for this
         *   missing object, so that the Parser is prepared to treat the next input
         *   as an answer to this question.
         */
        Parser.question = new ParseErrorQuestion(err);

        /* Skip to the next command line so the player can enter a response */
        abort;
    }

    /*
     *   This function displays msg, which should be a message inviting the player to choose a suitable
     *   object for action in role (DirectObject, IndirectObject or AccessoryObject). The action will
     *   then be performed using the selected object in role.
     */
    askChooseObject(action, role, msg)
    {
        /*
         *   Store the current objects of the current action in the new action, in
         *   case action.scoreObjects() needs to refer to them below.
         */
        action.curDobj = gDobj;
        action.curIobj = gIobj;
        action.curAobj = gAobj;

        /* Make action the current action for the current Command. */
        gCommand.action = action;
        gCommand.dobj = (role == DirectObject ? nil : gDobj);
        gCommand.iobj = (role == IndirectObject ? nil : gIobj);
        gCommand.acc = (role == AccessoryObject ? nil : gAobj);

        if(role == DirectObject)
        {
            gCommand.dobjNPs = [];
            gCommand.dobjs = new Vector();
        }

        if(role == IndirectObject)
        {
            gCommand.iobjNPs = [];
            gCommand.iobjs = new Vector();
        }

        if(role == AccessoryObject)
        {
            gCommand.accNPs = [];
            gCommand.accs = new Vector();
        }

        /*
         *   Make the action the original action for the current Command; we need to
         *   do this because otherwise the Command object will overwrite our new
         *   action with its original one before we're done.
         */
        gCommand.originalAction = action;

        /*
         *   Slot the new action's verbRule into the Command's verbProd, so that the
         *   Command has a verbProd appropriate to the action.
         */
        gCommand.verbProd = action.verbRule;

        local err = new EmptyNounError(gCommand, role);

        say(msg);

        Parser.question = new ParseErrorQuestion(err);

        abort;
    }

    //------------------------------------------------------------------------------

    /*
     *   Verify Results: objects of this class are created by macros like
     *   logicalRank() and illogical() that are used in verify routines and stored
     *   in the verTab table of the current action.
     */
    class VerifyResult: object
        /*
         *   Our resultRank; the lower this number the less likely it is that this
         *   action could succeed, or the more illogical it is.
         */
        resultRank = 0

        /*
         *   The error message to display if this verify result prevents an action
         *   from going ahead.
         */
        errMsg = ''

        /* Is the action allowed to proceed according to this verify result? */
        allowAction = true

        /* Can this action be performed as an implicit action? */
        allowImplicit = true

        /* The object to which this verify result refers */
        myObj = nil

        /* The constructor for creating a new verify result. */
        construct(score_, errmsg_, allowAction_, myObj_, allowImplicit_ = true)
        {
            resultRank = score_;
            errMsg = errmsg_;
            allowAction = allowAction_;
            myObj = myObj_;
            allowImplicit = allowImplicit_;
        }
    ;

    //------------------------------------------------------------------------------

    /* Note the objects in objlist as potential pronoun antecedents */
    notePronounAntecedent([objlist])
    {
        local itList = [];
        local themList = [];
        local himList = [];
        local herList = [];

        /*
         *   Go through each object in objlist and add it to the appropriate pronoun
         *   list
         */
        foreach(local cur in objlist)
        {
            /*
             *   If we refer to a SubComponent, we're really referring to its
             *   location
             */
            if(cur.ofKind(SubComponent) && cur.location)
                cur = cur.location;

            /* If the object is plural, it's a possible antecedent for 'them' */
            if(cur.plural || cur.ambiguouslyPlural)
                themList += cur;

            /*
             *   Add the object and any of its facets to the himList, herList and
             *   itList according to whether it's isHim, isHer or isIt property is
             *   true.
             */
            local lst = valToList(cur.getFacets) + cur;

            if(cur.isHim)
            {
                for(local obj in lst)
                    himList += obj;
            }

            if(cur.isHer)
            {
                for(local obj in lst)
                    herList += obj;
            }

            if(cur.isIt && (!cur.plural || cur.ambiguouslyPlural))
            {
                for(local obj in lst)
                    itList += obj;
            }

        }

        /*
         *   If any of the lists have anything in them, use them to set the
         *   antecedent list on the corresponding pronoun.
         */
        if(themList.length > 0)
            Them.setAntecedents(themList);
        if(itList.length > 0)
            It.setAntecedents(itList);
        if(herList.length  > 0)
            Her.setAntecedents(herList);
        if(himList.length > 0)
            Him.setAntecedents(himList);

    }

    /* The remainder of this file contains code  "borrowed" from the adv3 library */

    /* ------------------------------------------------------------------------ */
    /*
     *   PreSaveObject - every instance of this class is notified, via its
     *   execute() method, just before we save the game.  This uses the
     *   ModuleExecObject framework, so the sequencing lists (execBeforeMe,
     *   execAfterMe) can be used to control relative ordering of execution
     *   among instances.
     */
    class PreSaveObject: ModuleExecObject
        /*
         *   Each instance must override execute() with its specific pre-save
         *   code.
         */
    ;

    /*
     *   PostRestoreObject - every instance of this class is notified, via its
     *   execute() method, immediately after we restore the game.
     */
    class PostRestoreObject: ModuleExecObject
        /*
         *   note: each instance must override execute() with its post-restore
         *   code
         */

        /*
         *   The "restore code," which is the (normally integer) value passed
         *   as the second argument to restoreGame().  The restore code gives
         *   us some idea of what triggered the restoration.  By default, we
         *   define the following restore codes:
         *
         *   1 - the system is restoring a game as part of interpreter
         *   startup, usually because the user explicitly specified a game to
         *   restore on the interpreter command line or via a GUI shell
         *   mechanism, such as double-clicking on a saved game file from the
         *   desktop.
         *
         *   2 - the user is explicitly restoring a game via a RESTORE command.
         *
         *   Games and library extensions can use their own additional restore
         *   codes in their calls to restoreGame().
         */
        restoreCode = nil
    ;

    /*
     *   PreRestartObject - every instance of this class is notified, via its
     *   execute() method, just before we restart the game (with a RESTART
     *   command, for example).
     */
    class PreRestartObject: ModuleExecObject
        /*
         *   Each instance must override execute() with its specific
         *   pre-restart code.
         */
    ;

    /*
     *   PostUndoObject - every instance of this class is notified, via its
     *   execute() method, immediately after we perform an 'undo' command.
     */
    class PostUndoObject: ModuleExecObject
        /*
         *   Each instance must override execute() with its specific post-undo
         *   code.
         */
    ;

Adv3Lite Library Reference Manual
Generated on 01/03/2024 from adv3Lite version 1.6.2