#charset "us-ascii"

/* Copyright (c) 2000, 2002 Michael J. Roberts.  All Rights Reserved. */
/*
 *   TADS 3 Library: Pre-Conditions.
 *   
 *   This module defines the library pre-conditions.  A pre-condition is
 *   an abstract object that encapsulates a condition that is required to
 *   apply before a command can be executed, and optionally an implied
 *   command that can bring the condition into effect.  Pre-conditions can
 *   be associated with actions or with the objects of an action.  
 */

#include "adv3.h"


/* ------------------------------------------------------------------------ */
/*
 *   An action pre-condition object.  Each condition of an action is
 *   represented by a subclass of this class.  
 */
class PreCondition: object
    /*
     *   Check the condition on the given object (which may be nil, if
     *   this condition doesn't apply specifically to one of the objects
     *   in the command).  If it is possible to meet the condition with an
     *   implicit command, and allowImplicit is true, try to execute the
     *   command.  If the condition cannot be met, report a failure and
     *   use 'exit' to terminate the command.
     *   
     *   If allowImplicit is nil, an implicit command may not be
     *   attempted.  In this case, if the condition is not met, we must
     *   simply report a failure and use 'exit' to terminate the command.
     */
    checkPreCondition(obj, allowImplicit) { }

    /*
     *   Verify the condition.  This is called during the object
     *   verification step so that the pre-condition can add verifications
     *   of its own.  This can be used, for example, to add likelihood to
     *   objects that already meet the condition.  Note that it is
     *   generally not desirable to report illogical for conditions that
     *   checkPreCondition() enforces, because doing so will prevent
     *   checkPreCondition() from ever being reached and thus will prevent
     *   checkPreCondition() from attempting to carry out implicit actions
     *   to meet the condition.
     *   
     *   'obj' is the object being checked.  Note that because this is
     *   called during verification, the explicitly passed-in object must
     *   be used in the check rather than the current object in the global
     *   current action.  
     */
    verifyPreCondition(obj) { }

    /*
     *   Precondition execution order.  When we execute preconditions for a
     *   given action, we'll sort the list of all applicable preconditions
     *   in ascending execution order.
     *   
     *   For the most part, the relative order of two preconditions is
     *   arbitrary.  In some unusual cases, though, the order is important,
     *   such as when applying one precondition can destroy the conditions
     *   that the other would try to create but not vice versa.  When the
     *   order doesn't matter, this can be left at the default setting.  
     */
    preCondOrder = 100
;

/* ------------------------------------------------------------------------ */
/*
 *   A pre-condition that applies to a specific, pre-determined object,
 *   rather than the direct/indirect object of the command.
 *   
 *   Because this type of precondition operates on a specific object that
 *   is not necessarily part of the command, the object will not have been
 *   subjected to the usual scope checks, and thus might not be in scope
 *   for the operation.  Because of this, we'll explicitly check to make
 *   sure the object is in scope; if it's not, we'll call a particular
 *   failure routine provided by the creator as a property of the object
 *   involved.  
 */
class ObjectPreCondition: PreCondition
    construct(obj, cond)
    {
        /* 
         *   remember the specific object I act upon, and the underlying
         *   precondition to apply to that object 
         */
        obj_ = obj;
        cond_ = cond;
    }

    /* route our check to the pre-condition using our specific object */
    checkPreCondition(obj, allowImplicit)
    {
        /* check the precondition */
        return cond_.checkPreCondition(obj_, allowImplicit);
    }

    /* route our verification check to the pre-condition */
    verifyPreCondition(obj)
    {
        cond_.verifyPreCondition(obj_);
    }

    /* the object we check with the condition */
    obj_ = nil

    /* the pre-condition we check */
    cond_ = nil
;


/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: object must be visible.  This condition doesn't
 *   attempt any implied command to make the object visible, but merely
 *   enforces visibility before allowing the command.
 *   
 *   This condition is useful for commands that rely on visibly inspecting
 *   the object, such as "examine" or "look in".  It is possible for an
 *   object to be in scope without being visible, since an object can be
 *   in scope by way of a non-visual sense.
 *   
 *   We enforce visibility with a verification test, not a precondition
 *   check.  
 */
objVisible: PreCondition
    verifyPreCondition(obj)
    {
        /* if the object isn't visible, disallow the command */
        if (obj != nil && !gActor.canSee(obj))
        {
            /*
             *   If the actor is in the dark, that must be the problem.
             *   Otherwise, if the object can be heard or smelled but not
             *   seen, say so.  In any other case, issue a generic message
             *   that we can't see the object.  
             */
            if (!gActor.isLocationLit())
                inaccessible(&tooDarkMsg);
            else if (obj.soundPresence && gActor.canHear(obj))
                inaccessible(&heardButNotSeenMsg, obj);
            else if (obj.smellPresence && gActor.canSmell(obj))
                inaccessible(&smelledButNotSeenMsg, obj);
            else
                inaccessible(&mustBeVisibleMsg, obj);
        }
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: object must be audible; that is, it must be within
 *   hearing range of the actor.  This condition doesn't attempt any
 *   implied command to make the object audible, but merely enforces
 *   audibility before allowing the command.
 *   
 *   It is possible for an object to be in scope without being audible,
 *   since an object can be inside a container that is transparent to
 *   light but blocks all sound.
 *   
 *   We enforce this condition with a verification test.  
 */
objAudible: PreCondition
    verifyPreCondition(obj)
    {
        /* if the object isn't audible, disallow the command */
        if (obj != nil && !gActor.canHear(obj))
            inaccessible(&cannotHearMsg, obj);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: object must be within smelling range of the actor.
 *   This condition doesn't attempt any implied command to make the object
 *   smellable, but merely enforces the condition before allowing the
 *   command.
 *   
 *   It is possible for an object to be in scope without being smellable,
 *   since an object can be inside a container that is transparent to
 *   light but blocks all odors.
 *   
 *   We enforce this condition with a verification test.  
 */
objSmellable: PreCondition
    verifyPreCondition(obj)
    {
        /* if the object isn't within sense range, disallow the command */
        if (obj != nil && !gActor.canSmell(obj))
            inaccessible(&cannotSmellMsg, obj);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: actor must be standing.  This is useful for travel
 *   commands to ensure that the actor is free of any entanglements from
 *   nested rooms prior to travel.  
 */
actorStanding: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* check to see if the actor is standing - if so, we're done */
        if (gActor.posture == standing)
            return nil;

        /* the actor isn't standing - try a "stand up" command */
        if (allowImplicit && tryImplicitAction(Stand))
        {
            /* 
             *   make sure that leaves the actor standing - if not,
             *   exit silently, since the reason for failure will have
             *   been reported by the "stand up" action 
             */
            if (gActor.posture != standing)
                exit;
            
            /* indicate that we executed an implicit command */
            return true;
        }
        
        /* we can't stand up implicitly - report the problem and exit */
        reportFailure(&mustBeStandingMsg);
        exit;
    }
;

/*
 *   Pre-condition: actor must be "travel ready."  The exact meaning of
 *   "travel ready" is provided by the actor's immediately container.  The
 *   'obj' argument is always the travel connector to be traversed.  
 */
actorTravelReady: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        local loc = gActor.location;
        
        /* check to see if the actor is standing - if so, we're done */
        if (loc.isActorTravelReady(obj))
            return nil;
        
        /* the actor isn't standing - try a "stand up" command */
        if (allowImplicit && gActor.location.tryMakingTravelReady(obj))
        {
            /* 
             *   make sure that the actor really is travel-ready now - if
             *   not, exit silently, since the reason for failure will have
             *   been reported by the implicit action 
             */
            if (!loc.isActorTravelReady(obj))
                exit;
            
            /* indicate that we executed an implicit command */
            return true;
        }
        
        /* we can't make the actor travel-ready - report failure and exit */
        reportFailure(loc.notTravelReadyMsg);
        exit;
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the traveler is directly in the given room.  This will
 *   attempt to remove the traveler from any nested rooms within the given
 *   room, but cannot perform travel between rooms not related by
 *   containment.
 *   
 *   Note that the traveler is not necessarily the actor, because the actor
 *   could be in a vehicle.
 *   
 *   This is a class, because it has to be instantiated with more
 *   parameters than just a single 'obj' passed by default when evaluating
 *   preconditions.  In particular, we need to know the actor performing
 *   the travel, the connector being traversed, and the room we need to be
 *   directly in.  
 */
TravelerDirectlyInRoom: PreCondition
    construct(actor, conn, loc)
    {
        /* remember the actor, connector, and room */
        actor_ = actor;
        conn_ = conn;
        loc_ = loc;
    }
    
    checkPreCondition(obj, allowImplicit)
    {
        /* ask the traveler to do the work */
        return actor_.getTraveler(conn_)
            .checkDirectlyInRoom(loc_, allowImplicit);
    }

    /* the actor doing the travel */
    actor_ = nil

    /* the connector being traversed */
    conn_ = nil

    /* the room we need to be directly in  */
    loc_ = nil
;

/*
 *   Pre-condition: the actor is directly in the given room.  This differs
 *   from travelerDirectlyInRoom in that this operates directly on the
 *   actor, regardless of whether the actor is in a vehicle.  
 */
actorDirectlyInRoom: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* ask the actor to do the work */
        return gActor.checkDirectlyInRoom(obj, allowImplicit);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: actor is ready to enter a nested location.  This is
 *   useful for commands that cause travel within a location, such as "sit
 *   on chair": this ensures that the actor is either already in the given
 *   nested location, or is in the main location; and that the actor is
 *   standing.  We simply call the actor to do the work.  
 */
actorReadyToEnterNestedRoom: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* ask the actor to make the determination */
        return gActor.checkReadyToEnterNestedRoom(obj, allowImplicit);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the target actor must be able to talk to the object.
 *   This is useful for actions that require communications, such as ASK
 *   ABOUT, TELL ABOUT, and TALK TO.  
 */
canTalkToObj: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* 
         *   if the current actor can't talk to the given object, disallow
         *   the command 
         */
        if (obj != nil && !gActor.canTalkTo(obj))
        {
            reportFailure(&objCannotHearActorMsg, obj);
            exit;
        }

        /* we don't perform any implicit commands */
        return nil;
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: object must be held.  This condition requires that an
 *   object of a command must be held by the actor.  If it is not, we will
 *   attempt a recursive "take" command on the object.
 *   
 *   This condition is useful for commands where the object is to be
 *   manipulated in some way, or used to manipulate some other object.
 *   For example, the key in "unlock door with key" would normally have to
 *   be held.  
 */
objHeld: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* if the object is already held, there's nothing we need to do */
        if (obj == nil || obj.meetsObjHeld(gActor))
            return nil;
        
        /* the object isn't being held - try an implicit 'take' command */
        if (allowImplicit && obj.tryHolding())
        {
            /* 
             *   we successfully executed the command; check to make sure
             *   it worked, and if not, abort the command without further
             *   comment (if the command failed, presumably the command
             *   showed an explanation as to why) 
             */
            if (!obj.meetsObjHeld(gActor))
                exit;

            /* tell the caller we executed an implicit command */
            return true;
        }

        /* it's not held and we can't take it - fail */
        reportFailure(&mustBeHoldingMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);

        /* abort the command */
        exit;
    }

    /* lower the likelihood rating for anything not being held */
    verifyPreCondition(obj)
    {
        /* if the object isn't being held, reduce its likelihood rating */
        if (obj != nil && !obj.meetsObjHeld(gActor))
            logicalRankOrd(80, 'implied take', 150);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: a given source object must be able to touch the
 *   object.  This requires that the source object (given by our property
 *   'sourceObj') has a clear 'touch' path to the target object.
 *   
 *   This is a base class for arbitrary object-to-object touch conditions.
 *   In most cases, you'll want to use the more specific touchObj, which
 *   tests that the current actor can touch the current object.  
 */
class TouchObjCondition: PreCondition
    /* construct with a given source object */
    construct(src) { sourceObj = src; }

    /* 
     *   the source object - this is the object that is attempting to
     *   touch the target object 
     */
    sourceObj = nil

    /* check the condition */
    checkPreCondition(obj, allowImplicit)
    {
        local pastObs;
        
        /* 
         *   If we can touch the object, we can proceed with no implicit
         *   actions.
         */
        if (sourceObj.canTouch(obj))
            return nil;
        
        /* we haven't tried removing any obstructors yet */
        pastObs = new Vector(8);
        
        /*
         *   Repeatedly look for and attempt to remove obstructions.
         *   There could be multiple things in the way, so try to remove
         *   each one we find until either we fail to remove an
         *   obstruction or we run out of obstructions.  
         */
        for (;;)
        {
            local stat;
            local path;
            local result;
            local obs;

            /* get the path for reaching out and touching the object */
            path = sourceObj.getTouchPathTo(obj);

            /* if we have a path, look for an obstructor */
            if (path != nil)
            {
                /* traverse the path to find what blocks our touch */
                stat = sourceObj.traversePath(path, new function(ele, op)
                {
                    /*
                     *   If we can continue the reach via this path element,
                     *   simply keep going.  Otherwise, stop the reach here. 
                     */
                    result = ele.checkTouchViaPath(sourceObj, obj, op);
                    if (result.isSuccess)
                    {
                        /* no objection here - keep going */
                        return true;
                    }
                    else
                    {
                        /* stop here, noting the obstruction */
                        obs = ele;
                        return nil;
                    }
                });

                /* 
                 *   if we now have a clear path, we're done - simply return
                 *   true to indicate that we ran one or more implicit
                 *   commands 
                 */
                if (stat)
                    return true;
            }
            else
            {
                /* 
                 *   we have no path, so the object must be in an
                 *   unconnected location; we don't know the obstructor in
                 *   this case 
                 */
                obs = nil;
            }

            /*
             *   'result' is a CheckStatus object explaining why we can't
             *   reach past 'obs', which is the first object that
             *   obstructs our reach.
             *   
             *   If the obstructor is not visible or we couldn't find one,
             *   we can't do anything to try to remove it; simply report
             *   that we can't reach the target object and give up.  
             */
            if (obs == nil || !gActor.canSee(obs))
            {
                reportFailure(&cannotReachObjectMsg, obj);
                exit;
            }

            /* 
             *   Ask the obstructor to get out of the way if possible.
             *   
             *   If we've already tried to remove this same obstructor on
             *   a past iteration, don't try again, as there's no reason
             *   to think an implicit command will work any better this
             *   time.
             */
            if (pastObs.indexOf(obs) != nil
                || !allowImplicit
                || !obs.tryImplicitRemoveObstructor(touch, obj))
            {
                /* 
                 *   We can't remove the obstruction - either we've tried
                 *   an implicit command on this same obstructor and
                 *   failed, or we can't try an implicit command at all.
                 *   In any case, use the explanation of the problem from
                 *   the CheckStatus result object.  
                 */
                reportFailure(result.msgProp, result.msgParams...);
                exit;
            }

            /* 
             *   if the implied command failed, simply give up now -
             *   there's no need to go on, since the implied command will
             *   have already explained why it failed 
             */
            if (gTranscript.isFailure)
                exit;

            /* 
             *   We've tried an implied command to remove this obstructor,
             *   but that isn't guaranteed to make the target touchable,
             *   as there could be further obstrutions, or the implied
             *   command could have failed to actually remove the
             *   obstruction.  Keep iterating.  To avoid looping forever
             *   in the event the implicit command we just tried isn't
             *   good enough to remove this obstruction, make a note of
             *   the obstruction we just tried to remove; if we find it
             *   again on a subsequent iteration, we'll know that we've
             *   tried before to remove it and failed, and thus we'll know
             *   to give up without making the same doomed attempt again. 
             */
            pastObs.append(obs);
        }
    }

    verifyPreCondition(obj)
    {
        /*
         *   If there's no source object, do nothing at this point.  We can
         *   have a nil source object when we're resolving nouns for a
         *   two-object action, and we have a cross-object condition (for
         *   example, we require that the indirect object can touch the
         *   direct object).  In these cases, when we're resolving the
         *   first-resolved noun phrase, the second-resolved noun phrase
         *   won't be known yet.  The only purpose of verification at times
         *   like these is to improve our guess about an ambiguous match,
         *   but we have nothing to add at such times, so we can simply
         *   return without doing anything.  
         */
        if (sourceObj == nil)
            return;

        /* if we can't touch the object, make it less likely */
        if (!sourceObj.canTouch(obj))
        {
            /* 
             *   If we can't see the object, we must be able to sense it
             *   by some means other than sight, so it must have a
             *   sufficiently distinctive sound or odor to put it in
             *   scope.  Explain this: "you can hear it but you can't see
             *   it", or the like.  
             */
            if (gActor.canSee(obj))
            {
                local info;
                
                /* 
                 *   It's visible but cannot be reached from here, so it
                 *   must be too far away, inside a closed but transparent
                 *   container, or something like that.
                 *   
                 *   If it's at a distance, rule it illogical, since
                 *   there's not usually anything automatic we can do to
                 *   remove the distance obstruction.
                 *   
                 *   If it's not distant, don't rule it illogical, but do
                 *   reduce the likelihood ranking, so that we'll prefer a
                 *   different object that can readily be touched.  Since
                 *   we can see where the object is, we might know how to
                 *   remove the obstruction to reachability.  
                 */
                info = gActor.bestVisualInfo(obj);
                if (info != nil && info.trans == distant)
                {
                    /* it's distant - assume we can't fix this */
                    inaccessible(&tooDistantMsg, obj);
                }
                else
                {
                    /* 
                     *   it's not distant; rank it logical (since we might
                     *   be able to clear the obstruction with an implied
                     *   action), but at reduced likelihood (in case
                     *   there's something that doesn't need any prior
                     *   implied action to reach) 
                     */
                    logicalRankOrd(80, 'unreachable but visible', 150);
                }
            }
            else
            {
                /* 
                 *   if it has a sound presence, then "you can hear it but
                 *   you can't see it"; if it has a smell presence, then
                 *   "you can smell it but you can't see it"; otherwise,
                 *   you simply can't see it 
                 */
                if (obj.soundPresence && gActor.canHear(obj))
                {
                    /* it can be heard but not seen */
                    inaccessible(&heardButNotSeenMsg, obj);
                }
                else if (obj.smellPresence && gActor.canSmell(obj))
                {
                    /* it can be smelled but not seen */
                    inaccessible(&smelledButNotSeenMsg, obj);
                }
                else if (!gActor.isLocationLit())
                {
                    /* it's too dark to see the object */
                    inaccessible(&tooDarkMsg);
                }
                else
                {
                    /* it simply cannot be seen */
                    inaccessible(&mustBeVisibleMsg, obj);
                }
            }
        }
    }

    /*
     *   This condition tends to be fragile, in the sense that other
     *   preconditions for the same action have the potential to undo any
     *   implicit action that we perform to make an object touchable.  This
     *   is most likely to happen when we implicitly move the actor (moving
     *   in or out of a nested room, for example) to put the actor within
     *   reach of the target object.  To reduce the likelihood that this
     *   fragility will be visible to a player, try to execute this
     *   condition after other conditions.  Most other preconditions tend
     *   to be "stickier" - less likely to be undone by subsequent
     *   preconditions.  
     */
    preCondOrder = 200
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: actor must be able to touch the object.  This doesn't
 *   require that the actor is actually holding the object, but the actor
 *   must be able to physically touch the object.  This ensures that the
 *   actor and object are not, for example, separated by a transparent
 *   barrier.
 *   
 *   If there is a transparent barrier, we will attempt to remove the
 *   barrier by calling the barrier object's tryImplicitRemoveObstructor
 *   method.  Objects that can be opened in an obvious fashion will
 *   perform an implicit recursive "open" command, and other types of
 *   objects can provide customized behavior as appropriate.  
 */
touchObj: TouchObjCondition
    /* we want to test reaching from the current actor to the target object */
    sourceObj = (gActor)
;

/*
 *   Pre-condition: the indirect object must be able to touch the target
 *   object.  This can be used for actions where the direct object is going
 *   to be manipulated by an "agent" of the action (i.e., the indirect
 *   object), rather than directly by the actor: MOVE X WITH Y, for
 *   example.
 *   
 *   Note that the target object of this condition should be the direct
 *   object in most cases, so this condition should usually be used like
 *   this:
 *   
 *   dobjFor(MoveWith) { preCond = [iobjTouchObj] }
 *   
 *   In other words, this is a precondition that we apply in most cases to
 *   the *direct* object.  
 */
iobjTouchObj: TouchObjCondition
    /* the indirect object has to be able to touch the target object */
    sourceObj = (gIobj)
;

/*
 *   Pre-condition: the direct object can touch the target object.  This
 *   is useful for situations where the direct object is being manipulated
 *   directly and the indirect object is more of a passive participant in
 *   the action, such as PLUG CORD INTO OUTLET.
 */
dobjTouchObj: TouchObjCondition
    /* the direct object has to be able to touch the target object */
    sourceObj = (gDobj)
;

/* ------------------------------------------------------------------------ */
/*
 *   A precondition ensuring that the target object is in the same
 *   immediate location as a given object.
 */
class SameLocationCondition: PreCondition
    /* 
     *   construct dynamically, setting the other object whose location we
     *   must match 
     */
    construct(obj) { sourceObj = obj; }

    /* the object whose location we must match */
    sourceObj = nil

    /* check the condition */
    checkPreCondition(obj, allowImplicit)
    {
        local moveObj;
        local targetLoc;
        
        /* if we're in the same container, we're fine */
        if (obj.location == sourceObj.location)
            return nil;

        /* 
         *   Pick an object to move.  By default, pick the target object;
         *   but if the target object is non-portable, then trying to move
         *   it will fail, so pick the source object.  
         */
        if (obj.ofKind(NonPortable))
        {
            /* 'obj' is unportable, so try moving the source object */
            moveObj = sourceObj;
            targetLoc = obj.location;
        }
        else
        {
            /* 'obj' is portable, so try moving it by default */
            moveObj = obj;
            targetLoc = sourceObj.location;
        }

        /* try moving the object, and return the result */
        if (allowImplicit && targetLoc.tryMovingObjInto(moveObj))
        {
            /* if it didn't work, abort the action */
            if (obj.location != sourceObj.location)
                exit;

            /* tell the caller we executed an implied action */
            return true;
        }

        /* we can't move it - report the failure and abort the action */
        targetLoc.mustMoveObjInto(moveObj);
        exit;
    }
;

/* 
 *   require that the target object be in the same immediate location as
 *   the direct object 
 */
sameLocationAsDobj: SameLocationCondition
    sourceObj = (gDobj)
;

/* 
 *   require that the target object be in the same immediate location as
 *   the indirect object 
 */
sameLocationAsIobj: SameLocationCondition
    sourceObj = (gIobj)
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: actor must have room to hold the object directly (such
 *   as in the actor's hands).  We'll let the actor do the work.
 */
roomToHoldObj: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* let the actor check the precondition */
        return gActor.tryMakingRoomToHold(obj, allowImplicit);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the actor must not be wearing the object.  If the
 *   actor is currently wearing the object, we'll try asking the actor to
 *   doff the object.
 *   
 *   Note that this pre-condition never needs to be combined with objHeld,
 *   because an object being worn is not considered to be held, and
 *   Wearable implicitly doffs an article when it must be held.  
 */
objNotWorn: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* if the object isn't being worn, we have nothing to do */
        if (obj == nil || !obj.isWornBy(gActor))
            return nil;
        
        /* try an implicit 'doff' command */
        if (allowImplicit && tryImplicitAction(Doff, obj))
        {
            /* 
             *   we executed the command - make sure it worked, and abort
             *   if it didn't 
             */
            if (obj.isWornBy(gActor))
                exit;

            /* tell the caller we executed an implicit command */
            return true;
        }

        /* report the problem and terminate the command */
        reportFailure(&cannotBeWearingMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);

        /* abort the command */
        exit;
    }

    /* lower the likelihood rating for anything being worn */
    verifyPreCondition(obj)
    {
        /* if the object is being worn, reduce its likelihood rating */
        if (obj != nil && obj.isWornBy(gActor))
            logicalRankOrd(80, 'implied doff', 150);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the object is open.
 */
class ObjOpenCondition: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* if the object is already open, we're already done */
        if (obj == nil || obj.isOpen)
            return nil;
        
        /* try an implicit 'open' command on the object */
        if (allowImplicit && tryImplicitAction(Open, obj))
        {
            /* 
             *   we executed the command - make sure it worked, and abort
             *   if it didn't 
             */
            if (!obj.isOpen)
                exit;

            /* tell the caller we executed an implied command */
            return true;
        }

        /* can't open it implicitly - report the failure */
        conditionFailed(obj);
        exit;
    }

    /*
     *   The condition failed - report the failure and give up.  We
     *   separate this to allow subclasses to report failure differently
     *   for specialized types of opening.  
     */
    conditionFailed(obj)
    {
        /* can't open it implicitly - report failure and give up */
        reportFailure(&mustBeOpenMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);
    }

    /* reduce the likelihood rating for anything that isn't already open */
    verifyPreCondition(obj)
    {
        /* if the object is closed, reduce its likelihood rating */
        if (obj != nil && !obj.isOpen)
            logicalRankOrd(80, 'implied open', 150);
    }
;

/*
 *   The basic object-open condition 
 */
objOpen: ObjOpenCondition;

/*
 *   Pre-condition: a door must be open.  This differs from the regular
 *   objOpen condition only in that we use a customized version of the
 *   failure report. 
 */
doorOpen: ObjOpenCondition
    conditionFailed(obj)
    {
        /* 
         *   We can generate implicit open-door commands as a result of
         *   travel, which means that the actor issuing the command might
         *   never have explicitly referred to the door.  (This is not the
         *   case for most preconditions, which refer to objects directly
         *   used in the command and thus within the actor's awareness, at
         *   least initially.)  So, if the door isn't visible to the
         *   actor, don't tell the actor they have to open the door;
         *   instead, just show the standard no-travel message for the
         *   door.  
         */
        if (gActor.canSee(obj))
        {
            /* they can see the door, so tell them they need to open it */
            reportFailure(&mustOpenDoorMsg, obj);

            /* set this as the pronoun antecedent */
            gActor.setPronounObj(obj);
        }
        else
        {
            /* 
             *   they can't see the door - call the door's routine to
             *   indicate that travel is not possible 
             */
            obj.cannotTravel();
        }
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the object is closed.
 */
objClosed: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* if the object is already closed, we're already done */
        if (obj == nil || !obj.isOpen)
            return nil;
        
        /* try an implicit 'close' command on the object */
        if (allowImplicit && tryImplicitAction(Close, obj))
        {
            /* 
             *   we executed the command - make sure it worked, and abort
             *   if it didn't 
             */
            if (obj.isOpen)
                exit;

            /* tell the caller we executed an implied command */
            return true;
        }

        /* can't close it implicitly - report failure and give up */
        reportFailure(&mustBeClosedMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);

        /* abort the command */
        exit;
    }

    /* reduce the likelihood rating for anything that isn't already closed */
    verifyPreCondition(obj)
    {
        /* if the object is closed, reduce its likelihood rating */
        if (obj != nil && obj.isOpen)
            logicalRankOrd(80, 'implied close', 150);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the object is unlocked.
 */
objUnlocked: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* if the object is already unlocked, we're already done */
        if (obj == nil || !obj.isLocked)
            return nil;
        
        /* try an implicit 'unlock' command on the object */
        if (allowImplicit && tryImplicitAction(Unlock, obj))
        {
            /* 
             *   we executed the command - make sure it worked, and abort
             *   if it didn't 
             */
            if (obj.isLocked)
                exit;

            /* tell the caller we executed an implied command */
            return true;
        }

        /* can't unlock it implicitly - report failure and give up */
        reportFailure(&mustBeUnlockedMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);

        /* abort the command */
        exit;
    }

    /* reduce the likelihood rating for anything that's locked */
    verifyPreCondition(obj)
    {
        /* if the object is locked, reduce its likelihood rating */
        if (obj != nil && obj.isLocked)
            logicalRankOrd(80, 'implied unlock', 150);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: destination for "drop" is an outermost room.  If the
 *   drop destination is a nested room, we'll try returning the actor to
 *   the outermost room via an implicit command.  
 */
dropDestinationIsOuterRoom: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        local dest;
        
        /* 
         *   if the actor's location's drop location is the outermost
         *   room, we don't need to do anything special 
         */
        dest = gActor.getDropDestination(obj, nil);
        if (dest.getOutermostRoom() == dest)
            return nil;

        /* 
         *   the default drop destination is not an outermost room; try an
         *   implicit command to return the actor to an outermost room 
         */
        return actorDirectlyInRoom.checkPreCondition(
            dest.getOutermostRoom(), allowImplicit);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: object is burning.  This can be used for matches,
 *   candles, and the like.  If the object's isLit is nil, we'll attempt a
 *   "burn" command on the object.  
 */
objBurning: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        /* if it's already burning, there's nothing to do */
        if (obj == nil || obj.isLit)
            return nil;

        /* try an implicit 'burn' command */
        if (allowImplicit && tryImplicitAction(Burn, obj))
        {
            /* we executed a 'burn' - give up if it didn't work */
            if (!obj.isLit)
                exit;

            /* tell the caller we executed an implied command */
            return true;
        }

        /* we can't burn it implicitly - report failure and give up */
        reportFailure(&mustBeBurningMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);

        /* abort the command */
        exit;
    }

    verifyPreCondition(obj)
    {
        /* if the object is not already burning, reduce its likelihood */
        if (obj != nil && !obj.isLit)
            logicalRankOrd(80, 'implied burn', 150);
    }
;


/* ------------------------------------------------------------------------ */
/*
 *   Pre-condition: the object is empty.  This ensures that the object
 *   does not contain any other objects.
 *   
 *   Note that we unconditionally try to remove all objects.  If a
 *   container needs to have some objects that can be removed and others
 *   that can't (such as components within the container), then the
 *   container will have to be implemented as a ComplexContainer - the
 *   non-removable components should be made contents of the enclosing
 *   ComplexContainer, and the secret inner container should be the one
 *   subject to this precondition.  
 */
objEmpty: PreCondition
    checkPreCondition(obj, allowImplicit)
    {
        local chi;

        /* 
         *   if there's no object, or the object already has no contents,
         *   there's nothing to do 
         */
        if (obj == nil || obj.contents.length() == 0)
            return nil;

        /* 
         *   Try an implicit 'take x' on the object's first child.
         *   
         *   Note that we only try this on the first object, because the
         *   precondition mechanism automatically re-applies all
         *   preconditions after any one of them performs an implied
         *   command.  If we have multiple objects that must be removed,
         *   that basic loop will ensure that we'll come back here as many
         *   times as necessary.  
         */
        chi = obj.contents[1];
        if (allowImplicit && tryImplicitAction(TakeFrom, chi, obj))
        {
            /* make sure it worked */
            if (chi.isIn(obj))
                exit;
            
            /* tell the caller we tried an implied command */
            return true;
        }
            
        /* we can't remove the objects implicitly, so give up */
        reportFailure(&mustBeEmptyMsg, obj);

        /* make it the pronoun */
        gActor.setPronounObj(obj);

        /* abort the command */
        exit;
    }
;
