#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

#define _ap &rsquo;

versionInfo: GameID
    IFID = '429dd9b2-db26-6f76-4e7a-c0e0288f6080'
    name = 'The Only Possible Prom Dress'
    byline = 'by Jim Aikin'
    htmlByline = 'by <a href="mailto:midiguru23@gmail.com">
                  Jim Aikin</a>'
    version = '1.1'
    authorEmail = 'Jim Aikin <midiguru23@gmail.com>'
    desc = 'Ten years after "Not Just an Ordinary Ballerina," an unfortunate event requires
        you to return to Stufftown on a shopping errand as urgent and difficult as what you
        went through the first time.'
    htmlDesc = 'Ten years after "Not Just an Ordinary Ballerina," an unfortunate event requires
        you to return to Stufftown on a shopping errand as urgent and difficult as what you
        went through the first time.'

    showAbout() {
        "This is version <<version>> of <q>The Only Possible Prom Dress,</q> by Jim Aikin.<.p>
        
        To learn more, use the utility command LEARN MORE.<.p>
        
        Other commands that you may find useful include DETAILS, INSTRUCTIONS, CREDITS, WALKTHROUGH, HELP, and HINT. ";
    }
    showCredit() {
        "This is the post-Comp release of <q>The Only Possible Prom Dress</q>.
        Thanks to all who voted for it in the IFComp! The game was created using the TADS 3 software by Mike
        Roberts and the adv3Lite library by Eric Eve. Many thanks to them both. Eric provided
        several custom tweaks to the library, and Jim Nelson on the IF forum suggested a couple more.
        Huge thanks are also owed to testers Michael Russo, eagle-eyed typo spotter Mike Carletta,
        Christopher Merriner, John Ziegler, Rafael Martins, Daniel Worm,
        Grueslayer, and Jessica Knoch. Without the patience and the tireless
        work of the testers, this game would have been quite a remarkable mess. Needless
        to say, I\'m responsible for any bugs that remain. ";
    }
;

gameMain: GameMainDef
    initialPlayerChar = me
    allVerbsAllowAll = nil
    // To be adjusted later:
    maxScore = 250
    initializeArches() {
        // A utility function. Included here because there are ways to get to
        // several of the exterior arches without ever learning to operate the
        // Octagonal Room:
        grottoArch.blocked = true;
        cloakRoomArch.blocked = true;
        behindStairsArch.blocked = true;
        fileRoomArch.blocked = true;
        gardenArch.blocked = true;
        secludedArch.blocked = true;
        balconyArch.blocked = true;
        promenadeEastArch.blocked = nil;
        narrowLedgeArch.blocked = true;
    }
    // We will receive occasional texts from Samantha:
    showIntro {
        randomize();
        "\b\b\b
        <center><IMG SRC=\"pd_5_small.PNG\"></center>
        \b\b\b
        <font size='+1'><center><b>Preliminary Notes:</b></center></font><.p>
        Visually impaired users can type NONVISUAL to switch to text versions of
        a couple of small ASCII graphics.<.p>
        A walkthrough and maps can be
        downloaded from http://www.musicwords.net/if/Prom_Dress_Extras.zip. This is
        a large game, so you may find that the maps will prevent any amount of confusion.
        For more suggestions that may be of interest
        to many players, use the command DETAILS.<.p>
        
        To start the game, type any key. ";
        inputManager.pauseForMore();
        cls();
        
        "\b\b\b<font size='+1'><b>The story so far....</b></font><.p>For the past ten years you've managed to avoid shopping
        at Flogg & Grabby's Stufftown.
        Not much of a hardship --- the stores there are second-rate at best. The drive to the
        mall at Emerald Ridge is longer, but the merchandise is higher-quality and the ambiance
        far more modern and pleasant.<<initializeArches()>><.p> ";
        
        "The real reason you stay away from Stufftown, though, isn't because you don't like the stores.
        No, it's the memories of that weird Christmas Eve, ten years ago now, when you had to burgle
        every single store in the shopping center in order to finally get your hands on Sugar Toes
        Ballerina, the impossible-to-find fad doll of the decade. Your young daughter Samantha was sure
        Santa was going to leave Sugar Toes for her when he came down the chimney, and you couldn't
        bear to disappoint little Sam on Christmas morning, not even if it meant
        wholesale breaking and entering. <<me.startSamText()>><.p>

        So Stufftown brings back bittersweet memories. But this afternoon you're going to have to
        face the memories. There's no way to avoid it.<.p>

        Samantha is seventeen now, and tonight is the big night she's been waiting for for months: her
        senior prom at Harry S.\ Truman High School. Coincidentally, the prom this year is on Harry
        Truman's actual birthday, May 8. Also coincidentally, today the Truman High School lacrosse team
        won the state championship.<.p>

        Lacrosse is about the biggest thing in town, and when the team brought home the trophy on
        Harry Truman's birthday, a parade was hurriedly organized. The parade is going on right
        now, on the other side of town, and just about everybody in town has gone off to cheer the team.<.p>

        Now, about the prom dress. Sam's little brother Stevie may or may not have spilled the black
        ink on it deliberately. He claims it was an accident, but it was a pretty unlikely one. You're
        planning to sort that out with him later. The problem is, Sam's date for the prom is the
        captain of the lacrosse team, and she <i>really, really</i> likes him, and this will be their
        first date, only now her prom dress is ruined, and the prom will be starting in about four hours.<.p>

        The fashion boutique where she bought the dress happens to be in Stufftown, and on the phone
        they told you they had one more in stock exactly like it --- the same size, same style, same
        color. But when you called, they were just closing up so the sales staff could rush off to watch the
        parade. The clerk you talked to was practically giddy about the lacrosse championship! You
        tried to explain to her that your daughter's prom date is the captain of the lacrosse team,
        figuring that might convince her to keep the store open until you got there, but somehow
        you got disconnected, and when you called back all you got was the machine.<.p>

        So now you're on your way back to Stufftown, this time to replace Sam's prom dress. You're hoping
        maybe this new foray will be easier than what you went through ten years ago, but you have a
        dreadful premonition that it may turn out to be even more of a challenge.\b\b\b
        <center>
        <font size=+3><b>The Only Possible Prom Dress</b></font>\b\b
        <font size=+1><b>or, Return to Stufftown</b></font>\b\b
        <b>An Interactive Shopping Emergency</b>\b\b
        <font size=+1><b>by Jim Aikin</b></font>\b\b\b
        </center>";
    }
;

InitObject
    execute() {
        sessionHintStatus.hintWarning = true;
        gameHintStatus.hintWarning = true;
    }
;

//------------------------------------------------------------------------------
// The ShopSign class.
//------------------------------------------------------------------------------

class ShopSign: Decoration
    vocab = 'sign; shop store'
    cannotTakeMsg = 'You may want to be more selective in your vandalism, or at any
        rate more cautious about it. The sign is not detachable. '
    decorationActions = [Examine, Take, Read]
    desc = "The sign is positioned above the door of a shop. "
    proper = nil
    readDesc() { say(desc); } 
    notImportantMsg = 'The sign itself is not important. The name of the shop, on
        the other hand, might be distinctive enough to be memorable. '
;

//------------------------------------------------------------------------------
// A handy utility class.
//------------------------------------------------------------------------------

class RestrictedContainer: Container
    allowedContents = []
    notifyInsert (obj) {
        local foundIt = allowedContents.indexOf(obj);
        if (foundIt == nil) {
            "\^<<obj.theName>> won\'t fit in <<theName>>. ";
            exit;
        }
    }
;

//------------------------------------------------------------------------------
// The Stufftown Shop class.
//------------------------------------------------------------------------------

class Shop: Room
    dobjFor(Examine) {
        verify() { nonObvious; }
    }
    proper = true
;

modify Actor
    dobjFor(Feel) {
        check() {
            shouldNotFeelMsg;
        }
    }
    dobjFor(Taste) {
        check() {
            shouldNotTasteMsg;
        }
    }
    dobjFor(Push) {
        verify() {
            illogical (shouldNotPushMsg);
        }
    }
    shouldNotFeelMsg = "You don't know {the dobj} that well. "
    shouldNotTasteMsg = "Ewww! Yuck. "
    shouldNotPushMsg = 'That would be rude. '
    isKissable = nil
    allowKiss = nil
    cannotKissMsg = 'At the moment you\'re not feeling affectionate. '
    kissResponseMsg = 'At the moment you\'re not feeling affectionate. '
;

//------------------------------------------------------------------------------
// The Door classes.
//------------------------------------------------------------------------------

class ShopDoorOutside: Door
    vocab = '(shop) door'
    lockability = lockableWithKey
    isLocked = true
    keyList = [masterKey]
    dobjFor(Open) {
        check() {
            if ((betsy.getOutermostRoom == gPlayerChar.getOutermostRoom) &&
                (gPlayerChar.getOutermostRoom != archedPassage))
                betsyClinging.doorBlockResponses.doScript();
            else if (isLocked && (masterKey.isDirectlyIn(gPlayerChar))
                && (betsy.getOutermostRoom != gPlayerChar.getOutermostRoom))
                isLocked = nil;
            else inherited;
        }
    }
    checkPushTravel()
    {
        checkTravelBarriers(gActor);
    }
    dobjFor(Unlock) {
        verify() {}
        check() {
            if (!isLocked) "The door isn't locked. ";
            else if (!masterKey.isDirectlyIn(me))
                "You don't seem to have the key. ";
            else if ((betsy.getOutermostRoom == gPlayerChar.getOutermostRoom)
                && (gPlayerChar.getOutermostRoom != archedPassage))
                "While {the betsy} is watching? Maybe not such a great idea. ";
        }
        action() {
            makeLocked(nil);
            "You unlock the door. ";
        }
    }
    dobjFor(UnlockWith) {
        verify() {}
        check() {
            if (gIobj && gIobj != masterKey) "{The subj iobj} can\'t be used to unlock this door. ";
        }
        action() {
            doInstead(Unlock, self);
        }
    }
    lockedMsg() {
        me.lockedDoorCount++;
        if (me.lockedDoorCount == 5)
            return 'The shop door is locked. Really, you\'re going to need to find a key somewhere. 
                Until you do that, your progress in your quest is going to be zilch. ';
        else return 'The shop door is locked. ';
    }
;

class ShopDoorInside: Door
    vocab = '(shop) door'
    lockability = lockableWithKey
    isLocked = true
    keyList = [masterKey]
    dobjFor(Unlock) {
        verify() {}
        check() {
            if (!isLocked) "The door isn't locked. ";
            else if (!masterKey.isDirectlyIn(me))
                "You don't seem to have the key. ";
        }
        action() {
            makeLocked(nil);
            "You unlock the door. ";
        }
    }
    dobjFor(UnlockWith) {
        verify() {}
        check() {
            if (gIobj && gIobj != masterKey) "{The subj iobj} can\'t be used to unlock this door. ";
        }
        action() {
            doInstead(Unlock, self);
        }
    }
;

// A mix-in class to prevent travel on certain connectors when Betsy is present.
// This will most likely only become relevant if you fail to lock her in the
// closet, but possibly the design of the game will change later, in which case
// this would need to be added to most (but not all) of the shop door exteriors.
BetsyBarrier: object
    canTravelerPass(traveler) {
        if (traveler != gPlayerChar) return true;
        local pcRoom = gPlayerChar.getOutermostRoom();
        local betsyRoom = betsy.getOutermostRoom();
        if (pcRoom != betsyRoom) return true;
        // We'll just totally block it here ... might need fine-tuning later:
        return nil;
    }
    explainTravelBarrier(traveler) {
        betsyClinging.doorBlockResponses.doScript();
    }
;

// So Betsy won't magically show up outdoors -- a mix-in class:
ExteriorLocation: object
    isExterior = true
;

modify Thing
    isExterior = nil
    // Tomas suggests this modification to the BagOfHolding code:
    checkRoomToHold() {
        if(bulk > gActor.maxSingleBulk)
            DMsg(too big to carry, '{The subj dobj} {is} too big for {me} to
                carry. ');
        
        /* 
         *   If the BagOfHolding class is defined and the actor doesn't have
         *   enough spare bulk capacity, see if the BagOfHolding class can deal
         *   with it by moving something to a BagOfHolding.
         */
        if(defined(BagOfHolding) 
           && (bulk > gActor.bulkCapacity - gActor.getCarriedBulk || 1 > (gActor.countCapacity - gActor.getCarriedCount))
           && BagOfHolding.tryHolding(self));
        
        else if(bulk > gActor.bulkCapacity - gActor.getCarriedBulk())
            DMsg(cannot carry any more, '{I} {can\'t} carry any more than
                {i}{\'m} already carrying. ');
    }
    // This is just a nicer phrase; I added "of interest".
    lookBehindMsg = BMsg(look behind, '{I} {find} nothing of interest behind {the
        dobj}. ')
    dobjFor(Light) asDobjFor(BurnWith)
    dobjFor(BurnWith) {
        verify() { illogical ('You\'re not a pyromaniac. '); }
    }
;

//------------------------------------------------------------------------------
// The PC.
//------------------------------------------------------------------------------

me: Thing 'you' @parkingLot
	desc = "Your hair is sort of limp this afternoon, but right now you have more important things to think about. "
    bulkCapacity = 250
    // maxSingleBulk = 200 
    isFixed = true       
    person = 2  // change to 1 for a first-person game
    contType = Carrier 
    virtuoso = nil
    knowsASL = nil
    visual = true
    // lockedDoorCount is intended to spit out an error message when the player has
    // tried to open five doors that can't yet be opened.
    lockedDoorCount = 0
    
    // The system for receiving text messages from yr darling daughter -- texts
    // are in the phone itself.
    checkHairdresser() { return beautyParlor.visited; }
    checkOllie() { return ollie.released; }
    checkMasterKey() { return masterKey.grabbed; }
    checkSmoking() { return (betsy.curState == betsySmoking); }
    checkScrewdriver() { return screwdriver.moved; }
    checkPower() { return junctionBox.isPowered; }
    checkGuards() { return guardsGroup.youreSafeNow; }
    samTextID = nil
    startSamText {
        if (samTextID == nil) samTextID = new Daemon (self, &samText, 1);
    }
    stopSamText {
        if (samTextID != nil) {
            samTextID.removeEvent();
            samTextID = nil;
        }
    }
    samTextCount = 0
    samText {
        samTextCount++;
        if (samTextCount == 25) {
            samTextCount = 0;
            local oldText = cellPhone.readIndex;
            // we're going to start at zero so the initial
            // message will cause the phone to ring...
            cellPhone.readIndex = 1;
            if (checkHairdresser) cellPhone.readIndex = 2;
            if (checkOllie) cellPhone.readIndex = 3;
            if (checkMasterKey) cellPhone.readIndex = 4;
            if (checkSmoking) cellPhone.readIndex = 5;
            if (checkScrewdriver) cellPhone.readIndex = 6;
            if (checkPower) cellPhone.readIndex = 7;
            if (checkGuards) cellPhone.readIndex = 8;
            // Okay, we may have advanced to a new game state:
            if (oldText != cellPhone.readIndex) {
                // maybe ring the phone:
                if (cellPhone.isIn(me) || (cellPhone.getOutermostRoom == me.getOutermostRoom))
                    "<.p>Your phone chimes with the special text-from-Samantha sound. ";
            }
        }
    }
    dobjFor(JabWith) {
        verify() {}
        check() {
            if (gIobj != featheredDart) "You're unable to figure out how to do that. ";
        }
    }
    iobjFor(JabInto) {
        verify() {}
        check() {
            if (gDobj != featheredDart) "You're unable to figure out how to do that. ";
        }
        action() {
            doInstead (JabWith, me, featheredDart);
        }
    }
    notifyRemove(obj) {
        // This is sensible, but it raises the question of whether the PC is able to get into
        // some physical condition (such as crawling under the diorama) where carrying a helmet
        // full of water would be ridiculous.
        if ((obj == armyHelmet) && (armyHelmet.containsWater)) {
            "Setting the helmet down anywhere while it's full of water would only make a mess. ";
            exit;
        }
    }
    // Tomas's suggestion for inventory management -- can now replace my own countHeld. However,
    // it's not yet working, because the BagOfHolding's tryHolding(obj) method still focuses strictly
    // on bulk:
    countCapacity = 5
    getCarriedCount() {
        
        local cnt = contents.length();
        foreach (local obj in contents) {
            if (obj.isWornBy(me)) cnt--;
            // This is a kluge because I'm carrying the Samantha topic around as an Unthing:
            if (obj == tSamantha) cnt--;
        }
        return cnt;
    }
    // For climbing up and down the rope, we need to know how many things you're carrying:
    countHeld() {
        local cnt = contents.length();
        foreach (local obj in contents) {
            if (obj.isWornBy(me)) cnt--;
            if (obj == tSamantha) cnt--;
        }
        return cnt;
    }
;

// Always in the player's possession, yet invisible:
+ tSamantha: PDtopic, Unthing 'Samantha;; daughter sam'
    notHereMsg = 'Your lovely daughter is not with you on this shopping trip. '
    familiar = true
    proper = true
    stillNeeded = true
    decorationActions = [Call]
    dobjFor(Call) {
        verify() {}
        check() {
            "Your cell phone occasionally receives texts, but it's too old and funky
            to be used for outgoing calls or texts. ";
        }
    }
;

+myClothing: Wearable 'some clothing; unremarkable ; garments slacks clothes'
    "Your clothing is unremarkable. "
    vocabLikelihood = -5
    wornBy = me
    theName = 'your clothing'
    stillNeeded = true
    hideFromAll(action) {
        return true;
    }
    dobjFor(Drop) asDobjFor(Doff)
    dobjFor(PutIn) asDobjFor(Doff)
    dobjFor(Doff) {
        check() {
            "A quick glance around confirms that this is neither the French Riviera nor
                Malibu. Therefore, you're not on a nude beach. Also, you're painfully shy
                about the fact that you've put on a few pounds since your own high-school prom. Also,
                your husband would probably misunderstand. Also ... why did you want to do that, anyway?
                No, don't answer. I don't want to know. ";
        }
    }
;

//-----------------------------------------------------------------
// adding something obvious to a library class:
//-----------------------------------------------------------------

modify StairwayDown
    dobjFor(Climb) {
        verify() {
            illogical ('You can\'t climb {the dobj} here, as you\'re at the top,
                but you could descend. ');
        }
    }
;

modify StairwayUp
    dobjFor(ClimbDown) {
        verify() {
            illogical ('You can\'t descend {the dobj} here, as you\'re at the bottom,
                but you could go up. ');
        }
    }
;

modify Distant
    decorationActions = [Examine, Photograph]
;

modify Thing
    // Do we not have any cranks or spigots that can be turned? (And if not, why not?)
    // On the other hand, if something defines isTurnable itself, this code in the class
    // will never be used, so that's not a problem, is it?
    isTurnable {
        return (isDirectlyIn(gPlayerChar));
    }
    isOptical = nil
;

//-----------------------------------------------------------------
// CustomMessages to get rid of "Okay" and for other things
//-----------------------------------------------------------------

CustomMessages
    messages = [
        Msg(report switch, '{I} turn{s/ed} {1} {2}. '),
        Msg(okay wear, '{I}{\'m} now wearing {1}. '),
        Msg(okay doff, '{I}{\'m} no longer wearing {1}. '),
        Msg(okay get outof, '{I} {get} {outof dobj}. '),
        Msg(okay turn to, '{I} turn{s/ed} {1} to {2}'),
        Msg(okay set to, '{I} {set} {1} to {2}'),
        Msg(describe move pushable, '{The subj obj} {is} right behind you. ' ),
        Msg(before push travel dir, 'You pull {the dobj} {1}. ')
    ]
;

CustomMessages
     messages = [
Msg(cannot reach, 'To get at anything that\'s in the cage, you\'ll need to open the cage. ')
    ]
    active = (gPlayerChar.isIn(petShop))
;

//------------------------------------------------------------------------------
// A utility function that seems not to be in the library. Probably
// not being used, but let's leave it here anyway.
//------------------------------------------------------------------------------

spellInt(int) {
    local txt = 'zero';
    switch (int) {
        case 1: txt = 'one';
        break;
        case 2: txt = 'two';
        break;
        case 3: txt = 'three';
        break;
        case 4: txt = 'four';
        break;
        case 5: txt = 'five';
        break;
        case 6: txt = 'six';
        break;
        case 7: txt = 'seven';
        break;
        case 8: txt = 'eight';
        break;
        case 9: txt = 'nine';
        break;
        case 10: txt = 'ten';
        break;
        case 11: txt = 'eleven';
        break;
        case 12: txt = 'twelve';
        break;
        case 13: txt = 'thirteen';
        break;
        case 14: txt = 'fourteen';
        break;
        case 15: txt = 'fifteen';
        break;
        case 16: txt = 'sixteen';
        break;
        case 17: txt = 'seventeen';
        break;
        case 18: txt = 'eighteen';
        break;
        case 19: txt = 'nineteen';
        break;
        case 20: txt = 'twenty';
        break;
        default: txt = 'this should never print';
    }
    return txt;
}
          
//------------------------------------------------------------------------------
// To allow each transparent container to manage its own reachBlockedMsg:
//------------------------------------------------------------------------------

modify Thing
    reachBlockedMsg(target)
    {
        local obj = self;
        gMessageParams(obj);
        gMessageParams(target);
        return  BMsg(cannot reach, '{I} {can\'t} reach {the target} through
            {the obj}. ');
    }
;

modify ReachProblemBlocker    
    reachBlockedMsg()
    {        
        return obstructor_.reachBlockedMsg(target_);
    }
;

// Here, we prevent 'go to stufftown' from spitting out a disambiguation request.
// This code could be modified to iterate through a list of to-be-excluded
// things, as needed. This could also be done with a verify() routine in 
// dobjFor(GoTo) for the specific scenery items.
modify GoTo
    addExtraScopeItems(whichRole?) {
        scopeList = scopeList.appendUnique(Q.knownScopeList);
        // modification:
        local indx = scopeList.indexOf(nearbyStufftown);
        if (indx)
            scopeList = scopeList.removeElementAt(indx);
    }
;

// Because you need to be able to examine the dragon's teeth:
modify bodyParts
    exceptions = [byDragonHead]
;

// For the golf ball, we need to know you're not holding it before you try to
// hit it with the putter:
objNotHeld: PreCondition
    verifyPreCondition(obj) {
        if(obj.isIn(gActor))
            illogical('You can\'t do that while you\'re holding <<obj.theName>>. ');
        /* 
         *   If the actor is carrying the object it's slightly less likely to
         *   be the one the player means.
         */        
        if(obj.isIn(gActor))
            logicalRank(90);
    }
    checkPreCondition(obj, allowImplicit) { 
        /* 
         *   if the object is not held, we're already done.
         */
        if (obj == nil || !obj.isIn(gActor))
            return true;

        if(allowImplicit) {    
            /* 
             *   Try dropping obj implicitly and note if we were allowed to make
             *   the attempt.
             */
            local tried = tryImplicitAction(Drop, obj);
            /*  
             *   If obj is now not held by the actor return true to signal that this
             *   precondition has now been met.
             */
            if(!obj.isIn(gActor))
              return true;   
            if(tried)
                return nil;            
        }
        
        /* 
         *   If we reach here obj isn't being held by the actor and we weren't
         *   allowed to try to drop it; display a message explaining the
         *   problem.
         */
        gMessageParams(obj);
        DMsg(need to drop, '{I} need{s/ed} to drop {the obj} to do that. ');
        /* Then return nil to indicate that the precondition hasn't been met. */
        return nil;        
    }
;

// -----------------------------------------------------------------------------
// fixing a library bug, code from Jim Nelson:
// -----------------------------------------------------------------------------

replace 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. */
        local result = action.execResolvedAction();

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

        /*
         *   Return result of action.
         */
        return result;
    }

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

#ifdef __DEBUG

//------------------------------------------------------------------------------
// For debugging.
//------------------------------------------------------------------------------

modify Thing
    decorationActions = [Examine, GoTo, GoNear]
;

//------------------------------------------------------------------------------
// Test scripts.
//------------------------------------------------------------------------------

Test 'fullrun' [ 'test run1', 'test run2', 'test run3', 'test run4', 'test run5',
    'test run6', 'test run7', 'test run8', 'test run9', 'test run10', 'test run11',
    'test run12', 'test run13', 'test run14', 'test run15'
    ]
;

// The first four challenges have to be done in a set order: (1) Get the key.
// (2) Get rid of Betsy. (3) Restore the power. (4) Sabotage the monitors.

// Getting the key:
Test 'run1' [
    'x pennants', 's', 'look in trash can', 'take bag', 'take trumpet', 'look in bag',
    'take note', 'read note', 's', 's', 'w', 'knock on window', 'take nail polish',
    'e', 'e', 'e', 's', 'nw', 'lift lid', 'se', 'u', 'n', 'n', 'w',
    'tell guards about octopus', 'x corkboard', 'take key'
    ]
;
// pennants = pink, yellow, green, blue

// Getting rid of Betsy:
Test 'run2' [
    'e', 'unlock shop door', 'e', 'open case', 'take cigarettes', 'take matchbook',
    'w', 's', 'w', 'w', 'n', 'put matches and cigarettes on shelf', 's',
    's', 'x elvis', 'n',
    'tell hairdresser about cigarettes', 'lock closet door'
    ]
;
// Elvis = green, pink, blue, and violet

// Restoring power to the junction box:
Test 'run3' [
    'e', 'e', 'e', 'd', 'nw', 'sw', 'e', 'u', 'e', 'get screwdriver and oil can',
    'w', 'u', 'u', 'n', 'd', 'd', 'e', 's', 'nw', 'n', 'unlock light door',
    'nw', 'take cable', 'se', 's', 'se', 's', 's', 'w', 'x box', 'open box',
    'x square connector', 'take screwdriver', 
    'straighten square connector with screwdriver',
    'put square connector in square jack', 'put round connector in round jack',
    'switch a', 'switch c', 'switch d', 'switch f', 'switch g', 'switch i', 'switch l',
    'switch m', 'switch n', 'switch p'
    ]
;

// Getting the guards out of the way and sabotaging the monitor:
Test 'run4' [
    'e', 'n', 'n', 'e', 'x machine', 'take oil can', 'oil switch', 'switch switch',
    'w', 'n', 'n', 'unlock pottery door', 'ne', 'ne', 'se', 's', 'take dart and bridge',
    'n', 'n', 'sw', 'sw', 'n', 'n', 'w', 'unlock sandwich door', 's', 'open cooler',
    'get beer with bridge', 'get pizza with bridge', 'drop bridge', 'n', 'e', 's',
    'w', 'give beer and pizza to guards', 'x monitor', 'press blue', 'press green',
    'press red', 'press blue', 'press green', 'press blue', 'press red', 'press blue',
    'pour nail polish on panel'
    ]
;

// In run5 we sabotage the lamborghini, get the rope and the egg, retrieve the
// matches, try to get the dress, get the eau, get the golf ball, and get the stuff
// from the wardrobe closet. At the end, we have returned to Joe.
Test 'run5' [
    'e', 'n', 'n', 'sw', 'get in lamborghini', 'release lever', 'out',
    'e', 'press red button', 'e', 'open drawer', 'take card',
    'open crate with screwdriver', 'search peanuts', 'take nightingale', 's', 'put card in slot',
    'n', 'w', 'w',
    'ne', 's',
    'e', 'look behind stairs', 'e', 'take rope', 'w', 'search hedge', 'take egg', 'w', 's',
    's', 'w', 'w', 'unlock closet door', 'n', 'take matches', 's', 'e', 'e', 'e', 's',
    'nw', 'n', 'n', 'search racks', 's', 'take key', 'unlock case', 'open case', 'take eau',
    's', 's', 'se',
    's', 's', 'se', 'n', 'search grass', 's', 's', 's', 's', 'w', 'x hat', 'take pin',
    'look on shelf', 'take helmet and shoes and scarf', 'x scarf', 'e', 'put pin in latch keyhole', 'e',
    'n', 'n', 'n', 'nw', 'w'
    ]
;
// scarf = orange, green, red, violet

// In run6, we get Joe working and use him to find the money magnet and Bianca. But first
// we need the dowsing rod.
Test 'run6' [
    'tie rope to pole', 'drop rope over cliff', 'drop all', 'd', 's', 'get stick', 'n', 'u',
    'take all', 'ask man about himself', 'give shoes to joe', 'joe, follow me', 'e', 'n',
    'n', 'n', 'w', 'u', 'n', 'n', 'e', 'tell flogg about lamborghini', 'e',
    'look under flogg\'s desk', 'look under grabby\'s desk', 'joe, hold flogg\'s button',
    'press grabby\'s button', 'search safe', 'take u-shaped piece',
    'w', 's', 'w', 'd', 'e', 's', 's', 's', 'w',
    'take matches', 'take cigar', 'light cigar', 'take scarf', 'catch pixies in scarf',
    'take ennui', 'pour ennui on pixies', 'give stick to joe', 'joe, follow me',
    'e', 'n', 'n', 'nw', 'n', 'n', 'ask mannequin about herself', 'open purse', 'take phone',
    'take photo of bianca', 'e', 'unlock mall door', 'sw'
    ]
;

// In run7, the first thing you're going to need is the stepladder, in order to go up and
// get the wagon, but while you're in the toy stop, may as well do the run in the Virtual
// Capsule.
Test 'run7' [
    'nw', 'get ladder', 'se', 's', 'se', 'n', 'w', 'w', 'nw', 'drop ladder', 'unfold ladder',
    'u', 'x keypad', 'press ro', 'press od', 'press kc', 'press ab', 'n', 'take easter egg',
    'press pink nub', 'press yellow nub', 'press green nub', 'press blue nub', 'move cartons',
    'give jellybeans to dinosaur', 'w', 'get in capsule',    
    'push button', 'move lever right', 'z', 'z', 'z', 'z', 'move lever left',
    'z', 'z', 'z', 'z', 'z', 'z', 'z', 'move lever right', 'z', 'z', 'z', 'z',
    'move lever left', 'z', 'z', 'z', 'z', 'z', 'out',
    'e', 'unlock door', 'get top', 'pull wagon n'
    ]
;

// In run8, it's time to go back downstairs using the elevator and get the pterodactyl chow,
// and while we're there we'll get the gold coin from the mynah bird's cage,
// but in order to run through the Octagonal Room puzzles we're going to need, also, the
// scissors from the gift shop and the cloak from the cloak room, so we may as well
// say hello to Sir Ralph while we're down here. Also we'll need the harp
// and the telescope, both of which are upstairs, so let's start there.

Test 'run8' [
    'pull wagon e', 's', 'take harp', 'look behind piano', 'n', 'pull wagon sw', 'pull wagon s', 'pull wagon w',
    'sw', 'take telescope', 'ne', 'press button', 'pull wagon w', 'press bottom button',
    'pull wagon e', 'open wide door', 'pull wagon e', 'pull wagon s', 'open fur door',
    'pull wagon w', 'x cage', 'open cage', 'take coin', 'take egg', 'press orange nub', 'press green nub',
    'press red nub', 'press violet nub', 'take silver key', 'take nightingale', 'wind up nightingale',
    'take gold coin', 'put chow in wagon', 'pull wagon e',
    'e', 'take gold paint and scissors', 'n', 'take book', 'ask man about himself', 'take sheet',
    'show tall mirror to man', 'take book', 'n', 'open desk', 'point u-shaped piece at cubbyholes',
    'take coin from desk', 'take parchment', 'x parchment', 'ne', 'take cloak', 's', 'unlock shop door', 'w', 's',
    'pull wagon n', 'pull wagon w',
    'pull wagon w', 'press top button', 'pull wagon e', 'pull wagon n', 'pull wagon n',
    'pull wagon n', 'pull wagon ne'
    ]
;

// In run9, we're actually in the Octagonal Room, ready to go! The destinations can be
// taken in any order, as long as the garden is after the balcony and the ledge
// is preferably (though not necessarily) after the garden, so let's try
// grotto, lair and balcony, garden, ledge. 

Test 'run9' [
    'press green button', 'pull black lever', 'g', 'press red button', 'pull black lever',
    'go through rose door', 'take candle', 'se', 'pull white lever', 'g', 'g',
    'press red button', 'pull black lever', 'g', 'press green button', 'pull white lever', 'g',
    'go through ivy door', 'take miraklgro', 'take scissors', 'take cloak', 'cut cloak with scissors',
    'give scraps to marionettes', 'take miraklgro', 'go through arch', 'go through rose door',
    'x card players', 'look at card players through telescope', 's',
    'press green button', 'pull white lever', 'press red button', 'pull white lever', 'g',
    'pull black lever', 'g', 'go through rose', 'give harp to angel', 'take crystal ball',
    'x card players', 'tell men about cheating', 'take cards', 'nw', 'pick marigolds', 'se',
    'go through arch', 'press green button', 'pull white lever', 'g', 'press red button',
    'pull white lever', 'g', 'pull wagon through ivy door', 'pull wagon s'
    ]
;

// In run10, we'll befriend the wizard and learn what he needs. Possibly the next step
// will be to go get the monkeys. But first,
// bring the wagon down in the elevator to the main floor and leave it in the center
// of the arcade. And get the other three coins.

Test 'run10' [
    'open sack', 'pour sack into trough', 'ask man about himself',
    'tell wizard about bianca', 'give crystal ball to wizard', 'pull wagon north',
    'pull wagon northwest', 'press red button', 'pull white lever', 'pull black lever',
    'pull wagon through ivy door',
    'pull wagon southwest', 'pull wagon south', 'pull wagon west', 'pull wagon west',
    'press middle button', 'pull wagon east', 'pull wagon east',
    'take screwdriver', 'x diorama', 'crawl under diorama', 'unscrew plate',
    'reach into square hole', 'out', 'n', 'n', 'nw', 'search urn',
    'take coin', 'se', 's', 's', 'take egg', 'press green nub', 'press pink nub',
    'press blue nub', 'press violet nub', 'take coin'
    ]
;

// In run11, we need to get the knitting needle
// from the fortune teller, which will be used both to retrieve the putter and to
// clean the blowgun. While we're visiting her, we'll have a seance. 
// Then it's up to the roof to get the tooth. And let's not forget
// the stunGums, the ASLpill (both in the dentist's office), and the blowgun,
// none of which has been retrieved yet. Finally, we collect the goodall photo
// and head for the TARZAN.

Test 'run11'
    [
        'se', 'x woman', 'ask her about herself', 'give cards to woman', 'show book to woman',
        'madame, summon the ghost', 'read page 327 in book', 'n', 'u', 's', 'se', 'look in box',
        'take pill', 'eat pill', 'take vial', 'n', 'sw', 'take wine box', 'e', 'u', 'n', 'x shed', 'x grille',
        'look in gap', 'take needle', 'slide needle into gap', 'take putter', 'n', 'take golf ball',
        'drop ball', 'hit ball with putter', 'e', 'take tooth', 'sw', 'd', 'd', 'e', 'd', 'nw',
        'sw', 'take belt', 'ne',
        'w', 'x tank', 'take helmet', 'catch fish in helmet', 'e', 'se', 'u', 'n', 'nw', 'x man',
        'ask man about himself', 'pour helmet into birdbath', 'ask archie about paintings', 'n',
        'take paintbrush', 's', 'se', 's', 'drop egg', 'u', 'n', 'nw', 'x machine', 'read instructions',
        'put tooth in hopper', 'close lid', 'put coin in slot', 'g', 'g', 'g', 'g', 'read LED',
        'press down button', 'g', 'g', 'g', 'press red button', 'open hopper', 'take tooth', 'x tooth',
        'se', 'e', 'ne', 'take bamboo', 'take envelope', 'take biobgone bottle', 'sw', 'se', 'take saw',
        'nw', 'n', 'w', 'd', 'e', 'e', 'x bartender', 'x photos', 'greet bartender', 'show tooth to bartender',
        'take goodall photo', 'w', 'ne'
        
    ]
;

// In run12, we go to the jungle and bring the monkeys back. Next we need to equip them with instruments
// and take them downstairs to the psychiatrist. At the end of this chapter you have brought the monkeys
// to the Caboose and told them to wait.
Test 'run12' [
    'in', 'read page 327 in book', 'x panel', 'press 5', 'press 6', 'press 3', 'press 9', 'press 1', 'press 2',
    'press trip', 'out', 's', 'take blowgun and knitting needle', 'clean blowgun with needle',
    'take dart and vial', 'put dart into dimple', 'take blowgun', 'put dart in blowgun', 'point blowgun at ocelot',
    'blow into blowgun', 's', 'take purse', 'hold mirror in sunbeam', 'aim beam at buddha', 'e', 'x monkeys',
    'show goodall to monkeys', 'sign follow', 'w', 'n', 'in', 'press 1', 'press 0', 'g', 'g', 'g', 'g',
    'press trip', 'out', 'sw', 'n', 'n', 'n', 'e', 'u', 's', 'show instruments to monkeys', 'n', 'd', 'w',
    's', 's', 'e', 'd', 's', 'w', 'put disc in dvd player', 'take trumpet', 'sign play',
    'press play', 'sign play', 'play trumpet', 'e', 'n', 'nw', 'n', 'ne', 'sign wait'
    ]
;

// In run13, it's time to get the mirrorball spinning, get the signet ring, and put the seal on the
// parchment. At the end of this run, the soldier is waiting in the Caboose and you're back in the
// center of the arcade, ready to pull the wagon into the elevator.

Test 'run13' [
    'sw', 's', 'se', 'u', 'n', 'ne', 'x wheel', 'x motor', 'unscrew cap', 'pour wine into motor',
    'take belt', 'attach belt to gears', 'switch motor on', 'take pot', 'sw', 'n', 'n', 'w',
    'open grate with screwdriver',
    'x ooze', 'pour biobgone on ooze', 'search ooze', 'e', 'e', 'u', 'drop shopping bag',
    'put pot and screwdriver in bag', 'put purse in bag', 'take parchment',
    'take candle and matches', 'light candle', 'drip wax onto parchment', 'press ring into wax',
    'blow out candle', 'put candle and ring and matchbook into bag', 'take bag', 'sw', 'w', 'take cell phone',
    'photograph soldier', 'play trumpet', 'soldier, follow me', 'show parchment to soldier', 'soldier, follow me',
    'e', 'n', 'd', 'w', 'd', 's', 'n', 'u', 's', 's', 's'
    ]
;

// It's time to get the mummy's toe. Then it's back upstairs -- time to unclasp the secret book.
Test 'run14' [
    'pull wagon w', 'pull wagon sw', 'press bottom button', 'pull wagon e', 'g', 'pull wagon s', 'pull wagon se',
    'pull wagon s', 'g', 'pull wagon se', 'put pot in wagon', 'take helmet', 'fill pot with dirt',
    'pull wagon nw', 'pull wagon n', 'g', 'pull wagon nw', 'pull wagon n', 'pull wagon w', 'g', 'take screwdriver',
    'x brass plate', 'loosen screws', 'push plate down', 'press bottom button', 'pull wagon e', 'take mallet',
    'pull wagon n', 'hit lid with mallet', 'g', 'g', 'take envelope', 'open envelope', 'put seeds in pot',
    'take saw', 'saw mummy\'s toe', 'take miraklgro', 'pour miraklgro in pot', 'saw mummy\'s toe', 's', 'w',
    'press top button', 'e'
    ]
;

// In run15 we get the identikits. At this point in the test run, we're ready to go. Everything is set up,
// except that Bianca is still in the room behind the panel. However, it's theoretically possible for the
// player to have all this stuff ready to go before she has even summoned Zarbolphung! He could still be on
// the roof, or he could be downstairs but still grumpy. Or Ajou could still be upstairs or the mirror ball
// not yet spinning.This will all need to be coded and tested, so we'll keep run15 short so as to quick-march
// to the testing area.
Test 'run15' [
    's', 'touch shakespeare', 'x slim volume', 'take it', 'open it', 'look in it', 'x tablet', 'type unclasp a secret book',
    's', 'x machine', 'attach cell phone to cable', 'press 3', 'press 8', 'press 1', 'press 5', 'pull lever', 
    'take folders', 'n', 'n', 'e', 'se',
    'give marigolds to wizard', 'give toe to wizard', 'give toy top to wizard', 'give paintbrush to wizard',
    'give gold paint to wizard', 'give pink folder to wizard', 'give blue folder to wizard', 'w', 
    'tell bianca about lieutenant'
    ]
;

#endif
