#charset "us-ascii"
#include <advlite.h>
    
modify Thing
    isCombustible = nil
    dobjFor(Burn) {
        verify() { 
            if (!isCombustible)
            illogical ( 'You can\'t just go around setting fire to things. ' );
        }
    }
;

class PDportable: Thing
    canAttackWithMe = true
//    cannotAttackWithMsg = 'Flailing around with whatever happens to be
//            handy is not likely to be useful. '
    iobjFor(AttackWith) {
        verify() { illogical ('Flailing around with whatever happens to be
            handy is not likely to be useful. ');
        }
    }
    // To prevent 'take all' from grabbing what's in the shopping bag and
    // then putting it back:
    hideFromAll(action) {
        if ((action == Take) && isIn(shoppingBag)) return true;
        return nil;
    }
    filterResolveList(np, cmd, mode) {
        if (cmd.action != Take) return;
    }
    stillNeeded() { return true; }
    ditchedOnce = nil
    isPortable = true
    // Just in case I forget to add the correct bulk to anything:
    bulk = 5
;

modify Thing
    isPortable = nil
;

masterKey: PreinitObject, Key, PDportable 'shiny aluminum key; (shop) (door) burnished master passkey' @brassHook
    "The key is of burnished aluminum. Conspicuously stamped on one side are the
    words <q>Do Not Duplicate.</q> "
    // Here, we build a list of all the doors that the key will unlock:
    execute() {
        for (local obj = firstObj(ShopDoorOutside) ; obj != nil ; obj = nextObj(obj, ShopDoorOutside))
        {
            actualLockList = actualLockList.append(obj);
            knownLockList = knownLockList.append(obj);
        }
    }
    bulk = 1
    actualLockList = []
    knownLockList = []
    plausibleLockList = []
    isHidden = true
    isPortable = true
    grabbed = nil
    unavailableMsg = 'Better not --- not while the guards are watching. '
    
    dobjFor(Take) {
        check() {
            if ((gPlayerChar.getOutermostRoom() == securityOffice) &&
                guardsGroup.isIn(securityOffice))
                "Tempting, to be sure --- the key is hanging right there in plain
                    sight, and it's just bound to be what you need. But ten years
                    ago there was only one guard, and he was asleep. This time there are two,
                    and they're wide awake. ";
        }
        action() {
            if (!moved && betsy.isIn(securityOffice)) "Choosing a moment when {the betsy} is
                gazing at the hypnotically flickering video display, you sidle over and
                remove the key from the hook. ";
            passkeyAch.awardPointsOnce();
            grabbed = true;
            guardsGroup.startSurveillance();
            inherited();
        }
    }
    dobjFor(Feel) {
        check() {
            if ((gPlayerChar.getOutermostRoom() == securityOffice) &&
                guardsGroup.isIn(securityOffice))
                say (unavailableMsg);
        }
    }
;

//------------------------------------------------------------------------------
// Carried by the PC, the purse and makeup mirror need special handling:
//------------------------------------------------------------------------------

modify Thing
    dobjFor(Open) {
         action() {
            makeOpen(true);
            if(!gAction.isImplicit) {              
                unmention(contents);
                // This bit should do nothing except in the case of the purse,
                // which has its own purseOpeningContentsLister:
                listSubcontentsOf(self, &myOpeningContentsLister);
            }           
        }
    }
    myOpeningContentsLister = openingContentsLister
    
    dobjFor(LookIn) {
        action() {     
            if(contType == In) {            
                if(hiddenIn.length > 0)                
                    moveHidden(&hiddenIn, self);                    
                if(contents.length == 0)
                    display(&lookInMsg);                    
                else
                {
                    unmention(contents);
                    if(gOutStream.watchForOutput(
                        {: listSubcontentsOf(self, &myLookInLister) }) == nil)
                      display(&lookInMsg);       
                }
            }
            
            else if(hiddenIn.length > 0)            
                findHidden(&hiddenIn, In);                               

            else
                display(&lookInMsg);
        }
    }
    myLookInLister = lookInLister
;


purseOpeningContentsLister: openingContentsLister
    showListPrefix(lst, pl, parent) {
        gMessageParams(parent);
            "In addition to the mirror mounted
            beneath the flap, your purse contain{s/ed} ";  
    }
    showListEmpty(parent) {
        if (purse.contents.length() == 1)
            "Your purse is empty, except for the mirror mounted beneath the flap. ";
    }
;

// The mirror has a component, which appears and disappears:
mirrorSunbeam: Component 'beam of sunlight; reflected; light sun sunbeam'
    "The beam of sunlight is bouncing off of the mirror. "
    // so that when it's present, trying to point it at the statue won't
    // try to refer to the basic sunbeam coming through the trees instead:
    vocabLikelihood = 5
    dobjFor(PutOn) {
        preCond = [objVisible]
        verify() {}
        check() {
            if (gIobj && (gIobj != buddhaJewel)) "You can\'t put a
                beam of sunlight anywhere! ";
        }
    }
    dobjFor(ShineOn) asDobjFor(AimAt)
    dobjFor(AimAt) {
        preCond = [objVisible]
        verify() {}
        check() {
            if ((gIobj != buddhaStatue) && (gIobj != buddhaJewel))
                "You aim the beam of reflected sunlight here and there, without
                accomplishing anything noteworthy. ";
        }
        action() {
            buddhaStatue.roar();
        }
    }
    cannotTakeMsg = 'You can\'t hold a beam of sunlight! '
    afterAction() {
        if (!gActionIn(AimAt, Examine, HoldIn)) moveInto(nil);
    }
;

class PhonePhoto: Fixture
    'photo of;; photo{xxxzzz} snapshot{xxxzzz} picture{xxxzzz} pic{xxxzzz} snap{xxxzzz}'
    "The snapshot looks pretty much the way you remember <<realObject.theName>> looking. "
    vocabLikelihood = -10
    theName = (realObject.theName)
    aName = (realObject.aName)
    realObject = nil
    dobjFor(Take) {
        verify() {
            illogical('You can\'t take a photo of a photo; nor can you take the photo. ');
        }
    }
    dobjFor(GiveTo) {
        verify() { illogical ( 'The photo is inside the cell phone. ' ); }
    }
    dobjFor(ShowTo) {
        verify() { illogical ( 'The photos in your phone are not something that would
            likely interest anyone. ' ); }
    }
;

purse: PDportable, OpenableContainer 'purse; leather scuffed my; pocketbook handbag' @me
    "It's not your favorite purse --- the leather is a bit scuffed. You grabbed it
    in a hurry on your way out the door, without loading it with the assortment of
    things you usually carry --- only the bare necessities. << if isOpen>>Attached
    to the underside of the flap is a makeup mirror. "
    stillNeeded { return true; }
    theName = 'your purse'
    aName = 'your purse'
    bulkCapacity = 20
    bulk = 15
    isPortable = true
    openStatusReportable = nil
    myOpeningContentsLister = purseOpeningContentsLister
    myLookInLister = purseOpeningContentsLister
;

+ makeupMirror: Component 'makeup mirror'
    "The makeup mirror is mounted beneath the flap of the purse. It's rectangular,
    and large enough that you can see a good portion of your face in it, or check your hair. "
    bulk = 0
    vocabLikelihood = -10
    hideFromAll(action) {
        return true;
    }
    lookInMsg = 'By holding the mirror at just the right angle, you can see
        yourself. (A suggestion: If you want to look in a different mirror,
        close your purse.) '
    cannotTakeMsg = 'You can\'t remove the mirror; it\'s part of the purse. '
    // Because the player may try 'put mirror in sunlight' instead of the canonical
    // 'hold mirror in sunlight,' we're going to kluge it:
    dobjFor(PutIn) {
        preCond = [touchObj]
        verify () {}
        check() {
            if (gIobj && (gIobj != clearingSunbeam))
                "You can't put the mirror anywhere; it's attached to the purse. ";
        }
        action() { doInstead(HoldIn, self, clearingSunbeam); }
    }
    dobjFor(HoldIn) {
        check() {
            if (gIobj != clearingSunbeam) "It's not clear (to me, anyway) what that
                would accomplish. ";
            else if (!purse.isDirectlyHeldBy(me))
                "To do that, you'll need to be holding your purse. ";
        }
        action() {
            mirrorSunbeam.moveInto(self);
            local term = 'hold';
            if (gVerbWord == 'position') term = 'position';
            "As you <<term>> the mirror in the sunlight and move it around, it reflects a beam of light
            here and there across the clearing. ";
        }
    }
    dobjFor(ShineOn) asDobjFor(AimAt)
    dobjFor(AimAt) {
        verify() {}
        check() {
            if (mirrorSunbeam.isIn(nil)) "You wave the mirror around in a vague way. ";
            else if ((gIobj != buddhaStatue) && (gIobj != buddhaJewel))
                "You aim the beam of reflected sunlight here and there, without
                accomplishing anything noteworthy. ";
        }
        action() {
            buddhaStatue.roar();
        }
    }
    iobjFor(ShineOn) asIobjFor(AimAt)
    iobjFor(AimAt) {
        verify() { illogical ( 'You can\'t aim anything at the makeup mirror. '); }
    }
;

// The cellPhone can't be attached to anything other than the idkCable, so it's not going to
// be an Attachable. It's just going to _pretend_ to be an attachable.

+ cellPhone: PDportable 'cell phone; smart; text message camera'
    "Your cell phone is a couple of years old, and its link to a carrier has become almost completely
    unreliable, to the point where you don't even think much about using it as a phone anymore. 
    Texts from Samantha show up once in a while, though, so you can always read the text. Also, the
    phone is still
    handy as a camera. <<if (photoList.length() > 0)>><<photoListObject.desc>><<end>> 
    <.p>On the bottom of the phone is a jack<<if (photoList.length() > 0)>> with which it could
    be attached to a power cable or a
    data cable<<end>>. <<if (attachedToCable)>>At the moment, it's attached to the cable on the machine. <<end>>"
    bulk = 3
    stillNeeded() { return true; }
    // Texts from yr darling daughter:
    readIndex = 0
    readDesc() {
        "The phone's message display reads: ";
        switch (readIndex)
        {
        case 1: "<q>hi mom you got the dress yet? kathi brought her ouija board
            to help. ouija says hairdresser dont know what that means lol</q> ";
            break;
        case 2: "<q>kathi says ouija says octopus does that mean anything to you luv ya!!!</q> ";
            break;
        case 3: "<q>now ouija says octopus guards thats really weird got the dress yet?</q> ";
            break;
        case 4: "<q>kathi says ouija now back on hairdresser says h smoke in closet hope
            you know what that means</q> ";
            break;
        case 5: "<q>ouija going crazy mom says belt stairwell screwdriver wait wait .. kathi
            says now its lamp cable note box behind .. this is crazy mom you ok got dress yet?</q> ";
            break;
        case 6: "<q>i think kathi is making stuff up ouija says oil hvac billiard snack bar</q> ";
            break;
        case 7: "<q>ouija says buttons white gray blue green red blue green then it fell on
            the floor sorry then it said nail polish!!!!! what are you up to mom no dont tell
            me i dont want to know</q> ";
            break;
        default: "<q>luv ya mom!!!!!</q> ";
        }
    }
    
    useSpecialDesc = (attachedToCable)
    specialDesc = "Your cell phone is dangling from the end of the cable that protrudes
        from the machine. "
    
    // To handle 'take picture with phone':
    iobjFor(GetWith) {
        verify() {}
        check() {
            if (gDobj == photoListObject) "A very reasonable impulse, but you'll need to phrase
                the command a different way. If you want to take a picture, try <q>photograph X</q>. ";
            else "I didn't understand that command. ";
        }
    }
    
    dobjFor(SwitchOn) {
        verify() {}
        check() { "The cell phone doesn't need to be switched on or off. ";
        }
    }
    dobjFor(SwitchOff) asDobjFor(SwitchOn)

    // We will only reach the attach/detach code after the action handler has determined that
    // attaching makes sense.
    //
    // To be clear, the dobj handlers on the cable will call these methods directly, as will the
    // dobj handlers on the phone itself. The iobj handlers do nothing except print error messages.
    
    attachedToCable = nil
    attachToCable () {
        if (attachedToCable) "The cell phone is already attached to the cable. ";
        else {
            attachedToCable = true;
            idkCable.attachedToPhone = true;
            idkCable.possiblyUpload();
            moveInto(printery);
            "You attach the phone to the cable. ";
        }
    }
    detachFromCable() {
        if (!attachedToCable) "The cell phone is not attached to the cable. ";
        else {
            attachedToCable = nil;
            idkCable.attachedToPhone = nil;
            moveInto(gPlayerChar);
            if (!gActionIn(Take, PutIn, PutOn)) {
                "You detach the phone from the cable and pick it up. ";
            }
        }
    }
    
    dobjFor(PlugInto) asDobjFor(AttachTo)
    iobjFor(PlugInto) asIobjFor(AttachTo)
    dobjFor(FastenTo) asDobjFor(AttachTo)
    iobjFor(FastenTo) asIobjFor(AttachTo)
    // attach phone to X:
    dobjFor(AttachTo) {
        // We don't want to force you to be holding the phone, because if you try 'attach phone to
        // cable' when the phone is already attached, it might try removing the phone first and then
        // reattaching it, which would be stupid.
        preCond = [touchObj]
        verify () {}
        check() {
            if ((gIobj != idkCable) && (gIobj != identikitMachine)) "You can't attach
                the cell phone to {the iobj}. ";
        }
        action() {
            // the attachToCable method will reject the idea if the phone is already
            // attached:
            attachToCable();
        }
    }
    // attach X to phone:
    iobjFor(AttachTo) {
        verify() {}
        check() {
            if ((gDobj != idkCable) && (gDobj != identikitMachine))
                "You can't attach {the dobj} to the phone. ";
        }
        // We'll let the cable or the machine handle the action, because it's the dobj.
    }
    dobjFor(Unfasten) asDobjFor(Detach)
    dobjFor(Unplug) asDobjFor(Detach)
    dobjFor(Detach) {
        verify() {}
        check() {
            if (!attachedToCable) "The cell phone isn't attached to anything. ";
        }
        action() {
            doInstead (DetachFrom, cellPhone, idkCable);
        }
    }
    dobjFor (Remove) {
        action () {
            if (attachedToCable) doInstead (DetachFrom, cellPhone, idkCable);
            else inherited();
        }
    }
    dobjFor(UnfastenFrom) asDobjFor(DetachFrom)
    iobjFor(UnfastenFrom) asIobjFor(DetachFrom)
    dobjFor(UnplugFrom) asDobjFor(DetachFrom)
    iobjFor(UnplugFrom) asIobjFor(DetachFrom)
    dobjFor(DetachFrom) {
        verify() {}
        check() {
            if (gIobj != idkCable) "The phone isn't attached to {the dobj}. ";
            // else if (!attachedToCable) "The phone isn't attached to the cable. ";
        }
        action() {
            detachFromCable();
        }
    }
    iobjFor(DetachFrom) {
        verify() {}
        check() {
            if (!attachedToCable) "The cell phone is not attached to anything. ";
        }
    }
    iobjFor(TakeFrom) asIobjFor(DetachFrom)
    // The next bit is not quite right, but it's a good first approximation. What happens
    // if the phone is attached to the cable and the player enters 'remove phone from purse'?
    dobjFor(TakeFrom) {
        action() {
            if (attachedToCable) doInstead (DetachFrom, self, idkCable);
            else inherited();
        }
    }
    // If the player tries to put the cell phone in a container, most likely
    // the purse but possibly the tray, or drops it to the floor,
    // we'll unplug it automatically. The travel actions will handle what happens if the player tries to
    // walk away with it while it's plugged in.
    dobjFor(PutIn) {
        action() {
            if (attachedToCable) {
                // "(first removing the phone from the cable)\b";
                detachFromCable(); 
            }
            inherited();
            // "You put the cell phone in {the iobj}. ";
        }
    }
    dobjFor(Take) {
        action() {
            if (attachedToCable)
                detachFromCable();
            inherited();
        }
    }
    photoList = []
    addToPhotos (name) {
        photoList += name;
    }
    showPhotoList () {
        local len = photoList.length();
        local incr = 0;
        foreach (local cur in photoList) {
            incr += 1;
            say (cur);
            if (len == incr) ". ";
            else {
                if (len > 2) ", "; else " ";
                if (len == incr + 1) "and ";
            }  
        }
    }
;

++ phoneJack: Component 'jack on the (phone)'
    "The jack is in the lower edge of the phone. "
    dobjFor(PlugInto) asDobjFor(AttachTo)
    iobjFor(PlugInto) asIobjFor(AttachTo)
    dobjFor(FastenTo) asDobjFor(AttachTo)
    iobjFor(FastenTo) asIobjFor(AttachTo)
    dobjFor(AttachTo) {
        verify() {}
        action() {
            doInstead(AttachTo, cellPhone, gIobj);
        }
    }
    iobjFor(AttachTo) {
        verify() {}
        action() {
            // doInstead(AttachTo, gDobj, cellPhone);
        }
    }
    dobjFor(Unfasten) asDobjFor(Detach)
    dobjFor(Unplug) asDobjFor(Detach)
    dobjFor(Detach) {
        verify() {}
        action() {
            doInstead (DetachFrom, cellPhone, idkCable);
        }
    }
    dobjFor (Remove) {
        verify() {}
        check() {
            "The jack is part of the cell phone. ";
        }
    }
    dobjFor(UnfastenFrom) asDobjFor(DetachFrom)
    iobjFor(UnfastenFrom) asIobjFor(DetachFrom)
    dobjFor(UnplugFrom) asDobjFor(DetachFrom)
    iobjFor(UnplugFrom) asIobjFor(DetachFrom)
    dobjFor(DetachFrom) {
        verify() {}
        action() {
            doInstead(DetachFrom, cellPhone, gIobj);
        }
    }
    iobjFor(DetachFrom) {
        verify() {}
        action() {
            doInstead (DetachFrom, gDobj, cellPhone);
        }
    }
;

++ photoListObject: Component
    'photo display ; graphic LCD; picture pictures photos photograph photographs snapshot snapshots pic pix'
    vocabLikelihood = -10
    desc {
        local len = cellPhone.photoList.length();
        
        if (len == 0) "Your phone\'s photo display is blank. ";
        else {
            "In your cell phone's video display, you can
            see <<if (len == 1)>>a photo<<else>>photos<<end>> of ";
            cellPhone.showPhotoList();
        }
    }
    dobjFor(Examine) {
        verify() {
            if (me.getOutermostRoom() == scootersPub) illogicalNow ( 'You probably mean the signed photos. ');
        }
    }
    // 'take photo of X' is going to bring us to this spot, unfortunately.
    dobjFor(Take) {
        verify() {
            if (me.getOutermostRoom() == scootersPub) illogicalNow ( 'You probably mean the signed photos. ');
        }
        check() {
            "A very reasonable idea, but you'll need to phrase it differently. If you want to take a
            picture, try <q>photograph X</q>. ";
        }
    }
;

+ wallet: OpenableContainer, PDportable 'wallet; cheerful canary yellow; canvas billfold'
    "Your wallet is made of cheerful canary-yellow canvas. Not your style, really, but Samantha gave it
    to you for your birthday. "
    bulkCapacity = 6
    isPortable = true
    bulk = 3
;
 
++ driversLicense: Decoration 'driver\'s license; driving; licence'
    "The photo on the license is not very flattering, but it doesn't actually make you look like a hardened criminal. "
    bulk = 1
;

DefineTAction(Photograph);
VerbRule(PhotoGraph)
    ((('take' | 'snap') ('photo' | 'picture' | 'snapshot') 'of') | 'photograph') singleDobj
    : VerbProduction
    action = Photograph
    verbPhrase = 'take/taking a snapshot of'
    missingQ = 'what do you want to photograph'
;
modify Thing
    dobjFor(Photograph) {
        verify() {}
        check() {
            if (gDobj == cellPhone) "The phone can't take a snapshot of itself! ";
            else if (!cellPhone.isDirectlyIn(gPlayerChar)) "At the moment
                you can't take a photo of anything, because you're not holding your cell phone. ";
            else if (gDobj.ofKind(PhonePhoto)) "You already have a photo of that. ";
        }
        action() {
            cellPhone.addToPhotos(aName);
            local p = new PhonePhoto;
            p.realObject = self;
            p.vocab = 'photo[weak] of ' + vocab;
            p.initVocab();
            p.name = name;
            if (qualified) p.qualified = true;
            p.moveInto(cellPhone);
            "You snap a photo of {the dobj}. ";
        }
    }
;

//------------------------------------------------------------------------------
// The shopping bag (initially nowhere, as it's hidden):
//------------------------------------------------------------------------------

modify BagOfHolding
    // modified to allow objects to be shifted to the bag if there are too many as well as
    // if the total bulk is too great:
     tryHolding(obj) {
        /* Obtain a Vector containing the BagsOfHolding carried by the actor. */
        local bohVec = gActor.contents.subset({x: x.ofKind(BagOfHolding)});
        
        /* 
         *   If the actor is not carrying a BagOfHolding, there's nothing more
         *   we can do, so just stop here.
         */
        if(bohVec.length == 0)
            return;
        
        /*  The amount of bulk we need to free up */
        local bulkToFree = gActor.getCarriedBulk + obj.bulk -
            gActor.bulkCapacity;
        
        // The number of items we need to offload to the bag:
        local countToFree = gActor.getCarriedCount + 1 - gActor.countCapacity;
        
        local idx = 1;
        local carriedList = gActor.contents.subset({o: o.wornBy == nil
                                                   && o.isFixed == nil});
        
        while((bulkToFree > 0 || countToFree > 0) && carriedList.length >= idx)
        {
            local objToMove = carriedList[idx];
            
            /* 
             *   If we have more than one BagOfHolding available, sort our
             *   vector of BagsOfHolding in descending order of suitability
             */
            if(bohVec.length > 1)
                bohVec.sort(SortDesc, {a, b: a.suitabilityFor(objToMove) -
                            b.suitabilityFor(objToMove) });
            
            /* 
             *   Choose the first one in the Vector, which will be the most
             *   suitable.
             */
            local bagToUse = bohVec[1];
            
            /* 
             *   If the most suitable bag of holding for the object we're trying
             *   to move isn't suitable, try again with another object.
             */
            if(bagToUse.suitabilityFor(objToMove) < 1)
            {
                idx++;
                continue;
            }
            
            /* 
             *   Get the action needed to move an object into the selected
             *   BagOfHolding.
             */
            local action = bagToUse.moveAction();
            
            /*  
             *   If the action is nil, then there's something wrong with the
             *   selected BagOfHolding. Break of of the loop so we don't get
             *   stuck in an infinite loop.
             */
            
            if(action == nil)
                break;
            
            /* 
             *   Try moving the selected object into the selected bag using the
             *   appropriate action depending on the bag's contType
             */
            tryImplicitAction(action, objToMove, bagToUse);
            
            /* 
             *   Reset the index into the contents list to 1 so that if we need
             *   to select another object we start from the beginning again.
             */
            idx = 1;
            
            /*  Recalculate the amount of bulk left to free */
            
            bulkToFree = gActor.getCarriedBulk + obj.bulk -
            gActor.bulkCapacity;   
            
            countToFree = gActor.getCarriedCount + 1 - gActor.countCapacity;
            
            /* 
             *   Remove objToMove from the carried list. (Even if it wasn't
             *   actually moved for any reason, we don't want to try moving it
             *   again).
             */
            carriedList -= objToMove;
        }  
    }
;

shoppingBag: BagOfHolding, Container 'shopping bag; sturdy canvas wrinkled'
    "On the side of the wrinkled but sturdy canvas shopping bag is printed, in
    bright primary colors, the Flogg & Grabby's Stufftown logo. The bag has
    handles, and appears to be surprisingly capacious --- large enough to hold quite a
    <<startNoteRevealDaemon()>>variety of merchandise. "
    hiddenIn = [crumpledNote]
    stillNeeded() { return true; }
    droppedOnce = nil
    bulkCapacity = 1000
    minBulk = 5
    bulk = 50
    isPortable = true
    affinityFor(obj) {
        if (obj.isLit) return 0;
        // Here, we'll try to make it a little less likely that the damn bag
        // will keep shuffling the matches and the candlestub into itself:
        if (obj == candleStub) return 40;
        if (obj == singleMatchbook) return 40;
        
        if (obj == stepladder) return 0;
        // if (obj == purse) return 0;
        if (obj == redWagon) return 0;
        if (obj == masterKey) return 0;
        if (obj == snookerBridge) return 0;
        if (obj == pteroChow && (!pteroChow.emptied)) return 0;
        if (obj == armyHelmet && armyHelmet.containsWater) return 0;
        if (obj == largePot) return 0;
        if ((obj == scarf) && (flyingPixies.isDirectlyIn(scarf))) return 0;
        return inherited(obj);
    }
    dobjFor(Open) {
        verify() {}
        check() {
            "It's already open. To see what's in it, all you need to do is look
            in it. ";
        }
    }
    dobjFor(PutIn) {
        verify() { illogical ('You\'ll want to be carrying the shopping bag. No sense putting
            it into anything else. '); }
    }
    notifyInsert(obj) {
        startNoteRevealDaemon();
        if ((obj.isLit) && (obj != flashlight)) {
            "You know better than to put things that are burning into a shopping bag. ";
            exit;
        }
        else if (obj == stepladder) {
            "The stepladder is much too big to fit in the shopping bag. ";
            exit;
        }
        else inherited(obj);
    }
    noteRevealDaemonID = nil
    noteRevealTimer = 0
    noteRevealDaemon {
        noteRevealTimer++;
        if (noteRevealTimer >= 3) {
            if(hiddenIn.length > 0)                
                moveHidden(&hiddenIn, self);
            noteRevealDaemonID.removeEvent();
        }
    }
    startNoteRevealDaemon() {
        if (noteRevealDaemonID == nil) {
            noteRevealDaemonID = new Daemon (self, &noteRevealDaemon, 1);
        }
    }
;

+bagHandles: Component '(bag) handles; ;; them'
    "The handles are part of the shopping bag. "
    dobjFor(Take) {remap = shoppingBag}
    // The idea here is to prevent my improvised garbage collector from removing
    // the parts of the bag. There are other ways to prevent it, but let's be careful:
    ditchedOnce = true
;

+bagLogo: Component
    'the Stufftown logo; bright primary stylized hundred dollar hundred-dollar; hands pair colors bill'
    "The logo depicts, in bright primary colors, a pair of hands outstretched
    toward one another, one hand extending a stylized hundred-dollar bill and
    the other a smaller version of the Flogg & Grabby's shopping bag
    itself ... on which can be seen a smaller version of the same logo, and
    so on in a seemingly infinite regress. "
    ditchedOnce = true
;

crumpledNote: Consultable, PDportable 'crumpled note; wadded; piece of[prep] paper'
    "The piece of paper has been crumpled up, but not torn. The text on it
    is still readable. "
    bulk = 1
    isPortable = true
    isCombustible = true
    stillNeeded() { if (junctionBox.isPowered) return nil;
        return true; }
    dobjFor(CutWith) asDobjFor(Cut)
    dobjFor(Cut) {
        verify() {}
        check() {
            "There are enough meaningless scraps of paper in the world already.
            No need to add a few more. ";
        }
    }
    dobjFor(Burn) {
        verify() {}
        check() {
            "There might be some useful information in the note. Better not. ";
        }
    }
    dobjFor(Read) {
        preCond = [objHeld]
    }
    readDesc = "The handwritten scrawl reads as follows:<.p>
	
    Theo --- The Public Works Department has requested that we power down most of the
        equipment when there's no one on site to oversee its use. I'm sure I
        don't need to remind you of the trouble we've had with vandals switching
        things on at night and causing havoc. Not to mention that mess with the text
        on the marquee, I guess that\'s just digital gremlins, which is another story 
        entirely, but nobody has fixed it yet, and I don\'t even want to think about it.
        So anyway, the box has been programmed with a set
        of switch settings that changes every week. This week's settings (you're a whiz
        at this computer math stuff, so I know you won't have any trouble working out
        how to set the switches) are B6 for the top and 9D for the bottom.

    <.p>Try not to get in any hot water, guy. --- Jason "
;

//------------------------------------------------------------------------------
// The trumpet:
//------------------------------------------------------------------------------

// Note: The trumpet will eventually have a role to play in bringing the toy soldier to life, at
// which time the dobjFor(Play) code will need to be rewritten.

batteredTrumpet: PDportable 'battered trumpet; dented musical tarnished brass; horn bugle'
    "The trumpet is somewhat dented and tarnished. It's equipped with the usual parts --- a bell, a mouthpiece,
    and three valves. "
    bulk = 15
    stillNeeded() { if (ajou.getOutermostRoom != nil) return nil;
        return true; }
    dobjFor(BlowInto) asDobjFor(Play)
    dobjFor(Play) {
        preCond = [objHeld]
        verify() { logical; }
        action() {
            if (!gPlayerChar.virtuoso) "An obvious thing to think of trying --- but three minor obstacles immediately
                present themselves: First, the mouthpiece looks none too clean; second, you're
                not eager to draw attention to yourself by making loud noises; and third, you've never played the
                trumpet in your life, and have not the faintest idea how to go about it. ";
            else
                "Putting the trumpet to your lips, you tootle out a snappy bugle call. ";
        }
    }
    dobjFor(Clean) {
        verify() {}
        action() {
            "You rub the mouthpiece on your sleeve, but it doesn\'t look much cleaner than
            it did before. ";
        }
    }
;

+mouthpiece: Component '(trumpet) mouthpiece'
    "The mouthpiece doesn't look very sanitary. "
    cannotTakeMsg = 'Why not just take the whole trumpet? '
    dobjFor(Taste) asDobjFor(Examine)
    dobjFor(Clean) {
        verify() {}
        action() {
            doInstead(Clean, batteredTrumpet);
        }
    }
;

+bell: Component '(trumpet) bell'
    "The bell of the trumpet has a certain flare. "
    cannotTakeMsg = 'Why not just take the whole trumpet? '
;

+valves: Component '(trumpet) valves; three;; them'
    "The trumpet is furnished with the usual three valves. "
;

//------------------------------------------------------------------------------
// The army helmet, large pot, and other elements are now in PDhelmet.t
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// The birdsNest starts out nowhere, as it's hidden in the hedge.
//------------------------------------------------------------------------------

class EggInterior: SubComponent, KeyedContainer
    lockability = indirectLockable
    isLocked = true
    isOpen = nil
    dobjFor(Open) {
        verify() {}
        check() {
            if (isOpen) "You've already opened the egg. ";
            else "You have a go at twisting the top of the egg, but it won't budge. ";
        }
    }
    dobjFor(Unlock) {
        verify() {}
        check() {
            if (isOpen) "You've already done that -- it's open. ";
            else "There's no keyhole, but perhaps some other method would work. ";
        }
    }
;

enum Red, Orange, Yellow, Green, Blue, Violet, Pink, Gray;

class EggNub: Component
    vocab = 'colored nub; raised lozenge-shaped shaped; lozenge button'
    desc = "It's a small, raised, lozenge-shaped area, <<colorString>> in color, on the Easter egg. "
    nubColor = Gray
    colorString = 'gray'
    
    // if the vocabLikelihood is lower, then 'press yellow' will have an unintended result if
    // something yellow is in scope...
    
    vocabLikelihood = 5
    
    dobjFor(Push) {
        verify() { logical; }
        action() {
            "You press the <<colorString>> nub, and it depresses slightly with a faint 'click.' ";
            easterEgg.nubPushed(self);
        }
    }
;

birdsNest: Container 'bird\'s nest; woven; twig twigs'
    "The bird's nest is made (not surprisingly) of cunningly woven twigs. "
    initSpecialDesc = "Among the branches of the hedge you notice a bird's nest.<<eggAch.awardPointsOnce()>> "
    
    dobjFor(Take) {
    	check() {
    		"It seems a shame to deprive a family of birds of their happy home. Deciding
    		you have no immediate need for a bunch of twigs, you elect to leave the nest
    		where it is. ";
    	}
    }
    bulkCapacity = 6
    bulk = 8
;

// Nub hints (to be checked) include the pennants, Grabby's tie, the Elvis portrait,
// the scarf in the wardrobe closet, and what else?

+ easterEgg: PDportable 'Easter egg; multicolored swirling opalescent'
    "The egg is rather large and fairly glows in opalescent hues, which seem to swirl
    faintly as you stare at them. A raised ridge runs around it,
    separating the top half from the bottom half. Arrayed along the ridge are seven colored
    nubs, each about the size of your fingertip. The nubs are colored red, orange, yellow,
    green, blue, violet, and pink. "
    foo() {
        eggAch.awardPointsOnce();
    }
    bulk = 5
    proper = nil
    remapIn: SubComponent, KeyedContainer {
        lockability = indirectLockable
        isLocked = true
        isOpen = nil
        dobjFor(Open) {
            verify() {}
            check() {
                if (isOpen) "At the moment, it's open. ";
                else "You have a go at twisting the top of the egg, but it won't budge. ";
            }
        }
        dobjFor(Unlock) {
            verify() {}
            check() {
                if (isOpen) "You've already done that -- it's open. ";
                else "There's no keyhole, but perhaps some other method would work. ";
            }
        }
        notifyInsert(obj) {
            if (gActionIs(PutIn)) {
                "The Easter egg may not be a good place to store things. ";
                abort;
            }
        }
    }
    
    // strictly for debugging:
    showSeq {
        "currentSequence is: ";
        local str;
        for (local i = 1; i < 5; i++) {
            str = returnStr (currentSequence[i]);
            say (str + ' ');
        }
    }
    returnStr (x) {
        if (x == Red) return 'red';
        else if (x == Orange) return 'orange';
        else if (x == Yellow) return 'yellow';
        else if (x == Green) return 'green';
        else if (x == Blue) return 'blue';
        else if (x == Violet) return 'violet';
        else if (x == Pink) return 'pink';
        else return 'gray';
    } 
    
    nubPushed (n) {
        if (remapIn.isOpen) {
            remapIn.makeOpen(nil);
            "The top half of the egg snaps shut. ";
            if (jellybeans.isIn(remapIn)) jellybeans.moveInto(nil);
            else if (silverKey.isIn(remapIn)) silverKey.moveInto(nil);
            else if (coin1.isIn(remapIn)) coin1.moveInto(nil);
        }
        // These two lines change currentSequence:
        local newList = currentSequence.append(n.nubColor);
        currentSequence = newList.removeElementAt(1);
        if (checkForMatch(keySolution)) {
            if (!silverKey.moved) silverKey.moveInto(remapIn);
                remapIn.makeOpen(true);
                "The upper half of the Easter egg pops open with a gentle tinkling sound like
                a chime, <<if (silverKey.isIn(remapIn))>>revealing a small silver
                key<<else>>but there seems to be nothing more inside<<end>>. ";
        }
        else if (checkForMatch(coinSolution)) {
            if (!coin1.moved) {
                coin1.moveInto(remapIn);
            }
            remapIn.makeOpen(true);
            "The upper half of the Easter egg pops open with a gentle tinkling sound
            like a chime, <<if (coin1.isIn(remapIn))>>revealing a gold
            coin<<else>>but there seems to be nothing more inside<<end>>. ";
        }
        else if (checkForMatch(jellybeanSolution)) {
            if (!jellybeans.moved) jellybeans.moveInto(remapIn);
                remapIn.makeOpen(true);
                "The upper half of the Easter egg pops open with a gentle tinkling sound
                like a chime, <<if (jellybeans.isIn(remapIn))>>revealing a handful of
                jellybeans<<else>>but there seems to be nothing more inside<<end>>. ";
        }
    }
    checkForMatch (lst) {
        local matched = true;
        if (currentSequence[1] != lst[1]) matched = nil;
        if (currentSequence[2] != lst[2]) matched = nil;
        if (currentSequence[3] != lst[3]) matched = nil;
        if (currentSequence[4] != lst[4]) matched = nil;
        return matched;
    }
    jellybeanSolution = [Pink, Yellow, Green, Blue]
    keySolution = [Orange, Green, Red, Violet]
    coinSolution = [Green, Pink, Blue, Violet]
    currentSequence = [Gray, Gray, Gray, Gray]
    dobjFor(Open) {
        check() {
            "You give the top half of the egg a twist, but it seems to
                be stuck. ";
        }
    }
    dobjFor(Break) {
        verify() {}
        check() {
            "But it's so beautiful! Perhaps there's a less destructive way
                to learn what's inside it. ";
        }
    }
    dobjFor(Attack) asDobjFor(Break)
;

++ redNub: EggNub
    '+; red'
    nubColor = Red
    colorString = 'red'
;

++ orangeNub: EggNub
    '+; orange'
    nubColor = Orange
    colorString = 'orange'
;

++ yellowNub: EggNub
    '+; yellow'
    nubColor = Yellow
    colorString = 'yellow'
;

++ greenNub: EggNub
    '+; green'
    nubColor = Green
    colorString = 'green'
;

++ blueNub: EggNub
    '+; blue'
    nubColor = Blue
    colorString = 'blue'
;

++ violetNub: EggNub
    '+; violet purple'
    nubColor = Violet
    colorString = 'violet'
;

++ pinkNub: EggNub
    '+; pink'
    nubColor = Pink
    colorString = 'pink'
;

jellybeans: Food 'handful of jellybeans; brightly colored; beans candy jellybean; it them'
    "The jellybeans are brightly colored. They look rather tasty. "
    stillNeeded { return true; }
    theName = 'a ' + name
    bulk = 2
    hasAppeared = nil
    dobjFor(Eat) {
        check() {
            "The jellybeans look to be a delectable treat, no doubt --- but you've
            been trying to stay on that diet. Maybe later you'll find a better use
            for them. ";
        }
    }
;

//------------------------------------------------------------------------------------
// a flashlight -- useless, but every game needs a flashlight:
//------------------------------------------------------------------------------------

flashlight: Flashlight 'flashlight; flash sturdy; lamp light lantern torch' @lostAndFoundBox
    "It's a sturdy black flashlight. "
    isPortable = true
    stillNeeded() { return true; }
    droppedOnce = nil
    dobjFor(AimAt) {
        verify() {}
        check() {
            if (!isOn) "That would probably not be a helpful thing to do in any event,
                but it\'s certainly not going to avail you aught if you don\'t switch the
                flashlight on. ";
            else if (gIobj && (gIobj != cubbyHoles)) "You direct the beam of light
                at {the iobj} for a moment, accomplishing nothing noteworthy. ";
        }
        action() {
            "By directing the beam of light into one cubbyhole after another, you're able to
            detect quite a lot of dust, but nothing that\'s actually useful. ";
        }
    }
    bulk = 8
;

//------------------------------------------------------------------------------------
// the parchment, in the cubbyHoles in the desk:
//------------------------------------------------------------------------------------

parchment: PDportable 'sheet of parchment; thick cream-colored; paper document' @cubbyHoles
    "The sheet of parchment is rather larger and thicker than a normal sheet of
    paper. On the parchment is written, in a bold, almost calligraphic style,
    <q>The bearer of this document is your commanding officer. Obey the orders
    given you by the bearer.</q> "
    initSpecialDesc = "Tucked away in one of the cubbyholes is a sheet of parchment. "
    bulk = 15
    isWaxed = nil
    isSealed = nil
    iobjFor(PutOn) {
        verify() {}
        check() {
            if (gDobj != candleStub) "You can\'t put that on the parchment. ";
        }
//        action() {
//            doInstead(PourOnto, candleStub, self);
//        }
    }
    iobjFor(PourOnto) {
        verify() {}
        check() {
            if (gDobj != candleStub)
                "You can't pour {that dobj) onto the parchment. ";
        }
        // The candleStub is going to handle the action.
    }
    dobjFor(Read) {
        verify() {}
        action() {
            "The parchment says, <q>The bearer of this document is your commanding
            officer. Obey the orders given you by the bearer.</q> ";
        }
    }
;

signetRing: PDportable
    'heavy brass ring; incised signet; surface'
    "The heavy brass ring has a flat upper surface on which has been incised (or more likely cast)
    the design of a stylized oak leaf. "
    bulk = 2
    stillNeeded() { if (parchment.isSealed) return nil;
        return true; }
    dobjFor(Wear) {
        verify() {}
        check() { "The ring is much too large. It would slip straight off of your finger. "; }
    }
    dobjFor(PutOn) asDobjFor(PressInto)
    // At a tester's suggestion, we'll allow 'press ring' if there's a blob of wax
    // nearby:
    dobjFor(Push) {
        verify() {}
        check() {
            if (!blobOfWax.isIn(me)) "It\'s not a secret ring, worse luck. Pressing it has no effect. ";
        }
        action() {
            doInstead(PressInto, self, blobOfWax);
        }
    }
    dobjFor(PressInto) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != blobOfWax))
                "Pressing the ring into {that iobj} would be unlikely to accomplish anything. ";
        }
        action() {
            blobOfWax.moveInto(nil);
            parchment.isSealed = true;
            officialSeal.moveInto(parchment);
            sealAch.awardPointsOnce();
            "You press the ring into the blob of wax and blow on the wax to cool it. Ah, yes.
            Now you have a document with an official-looking wax seal! ";
        }
    }
;
+ ringDesign: Component 'oak leaf insignia; stylized incised cast shield'
    "The design has a flower or oak leaf a bit like a major's insignia, surrounded by an outline that
    might represent a shield. "
;

blobOfWax: Thing 'blob of wax; soft red'
    "The blob of soft red wax is affixed to one corner of the piece of parchment. "
    dobjFor(Take) {
        check() {
            "The blob of wax is firmly stuck to the parchment. ";
        }
    }
    iobjFor(PutOn) asIobjFor(PressInto)
    iobjFor(PressInto) {
        verify() { logical; }
        check() {
            if (gDobj != signetRing) "Good idea, but you'd only leave fingerprints. ";
        }
    }
;

officialSeal: Thing 'official seal; military red wax; blob'
    "The official military seal, a blob of red wax into which has been pressed an
    officer's signet ring, is affixed to the piece of parchment. "
    dobjFor(Take) {
        check() {
            "The red wax seal is firmly stuck to the parchment. ";
        }
    }
;
    
candleStub: PDportable 'candle stub; (melted) wax bright red short; flame' @grotto
    "It's a short stub of a bright red candle.<<if isLit>> Its flame flickers cheerfully.<<end>> "
    initSpecialDesc = "Perched on a little ledge <<candleAch.awardPointsOnce()>>is the stub of a candle. "
    isLit = nil
    bulk = 4
    litDaemon = nil
    litCount = 0
    stillNeeded() { if (parchment.isSealed) return nil;
        return true; }
    startBurn () {
        litCount = 0;
        litDaemon = new Daemon(self, &burn, 1);
    }
    stopBurn () {
        if (litDaemon != nil) {
            litDaemon.removeEvent();
            litDaemon = nil;
            isLit = nil;
        }
    }
    burn() {
        litCount++;
        if (litCount > 4) {
            "Not wanting to waste what little remains of the candle stub,
            you blow out the flame. ";
            stopBurn();
        }
    }
    dobjFor(Drop) {
        action() {
            if (isLit) {
                isLit = nil;
                "You blow out the candle before letting go of it. ";
            }
            inherited;
        }
    }
    dobjFor(Light) {
        preCond = [objHeld]
        verify() {}
        check() {
            if (isLit) "The candle is already lit. ";
            else if (!singleMatchbook.isDirectlyIn(gPlayerChar)) "You
                don't seem to have any matches. ";
            else if (!isDirectlyIn(gPlayerChar)) "You need to be holding the
                candle when you light it. ";
        }
        action() {
            isLit = true;
            startBurn();
            "You light the candle. ";
        }
    }
    dobjFor(BurnWith) {
        verify() {}
        check() {
            if (isLit) "The candle is already lit. ";
            else if (gIobj && (gIobj != singleMatchbook)) "It would be tricky
                to light the candle with that. ";
        }
        action() {
            doInstead(Light, self);
        }
    }
    dobjFor(Extinguish) {
        verify() {}
        check() {
            if (!isLit) "The candle isn't burning. ";
        }
        action() {
            isLit = nil;
            "You blow out the candle. ";
        }
    }
    dobjFor(PutIn) {
        check() {
            if (isLit) "You should probably blow it out before you try that. ";
        }
    }
    // For the parchment and the seal:
    dobjFor(PutOn) {
        action() {
            if (gIobj == parchment) doInstead(PourOnto, self, parchment);
            else inherited;
        }
    }
    dobjFor(Pour) {
        verify() {}
        check() {
            if (!isLit) "The candle is solid, not liquid. ";
            else "Dribbling melted wax around at random is not likely to be useful.
                If you want to pour wax onto something specific, that <i>might</i>
                get you somewhere. (No guarantees.) ";
        }
    }
    dobjFor(PourOnto) {
        verify() {}
        check() {
            if (!isLit) "The candle is purely solid. There's no melted
                wax to pour. ";
            else if (gIobj && (gIobj != parchment))
                "Dribbling wax onto {the iobj} is not likely to be useful. ";
            else if (parchment.isWaxed || parchment.isSealed)
                "You already did that. ";
        }
        action() {
            "You dribble a blob of hot red wax onto one corner of the parchment. ";
            parchment.isWaxed = true;
            blobOfWax.moveInto(parchment);
        }
    }
;

//------------------------------------------------------------------------------------
// in the printery, eventually:
//------------------------------------------------------------------------------------

biancaIdentiKit: PDportable 'pink folder; fat identification mannequin bianca; papers documents'
    "The pink folder contains a driver's license with a photo of the
    mannequin, a Social Security card, and several other useful documents. "
    created = nil
    dobjFor(Open) asDobjFor(LookIn)
    dobjFor(LookIn) {
        verify() {}
        check() {}
        action() {
            "You flip through the documents in the folder. Amazingly, everything seems
            to be in apple-pie order. ";
        }
    }
    cannotPutInMsg = 'Better just leave the papers the way they are. '
    bulk = 25 // ???
;

ajouIdentiKit: PDportable 'blue folder; fat identification soldier lieutenant ajou; papers documents'
    "The blue folder contains a driver's license with a photo of the
    wooden soldier, a Social Security card, and several other useful documents. "
    dobjFor(Open) asDobjFor(LookIn)
    dobjFor(LookIn) {
        verify() {}
        check() {}
        action() {
            "You flip through the documents in the folder. Amazingly, everything seems
            to be in apple-pie order. ";
        }
    }
    created = nil
    cannotPutInMsg = 'Better just leave the papers the way they are. '
    bulk = 25 // ???
;

//------------------------------------------------------------------------------
// The little red wagon:
//------------------------------------------------------------------------------

// Places where the wagonBarrier needs to be deployed:
// We don't need it on either of the doors on the roof, because you can't
// get to the roof with it (other than by purloining it).

// In the upper level,
// we have stairways up and down in the west side and a stairway down in the
// promenade. We also have a hole.

// In the main level, we have a stairway up in the arcade and another in
// the hedge-bordered path. The lower side of the hole can be traveled using
// the stepladder. There's also a stairway down in Arcade East.

// In the lower floor, we have a stairway up at the foot of the stairs.

// In the subterranean area, we have the jewel-encrusted grotto, which has
// an uneven floor.

// And then there's the stairwell.

// To fill the pot with dirt, you'll need to take the wagon down to the lower
// level in the elevator and then out the back entrance. (Or you could get there
// through the Octagonal Room and the cloakroom.)

wagonBarrier: TravelBarrier
    canTravelerPass(traveler, connector) {
        if (traveler == redWagon) return nil;
        return true;
    }
    explainTravelBarrier(traveler, connector) {
        if (traveler == redWagon) "The wagon will only roll on a level surface. ";
        else "This should never print. ";
    }
;

redWagon: Container 'little red wagon' @toyShop
    "The little red wagon is equipped with sturdy wheels and a handle, with which you
    can pull it from place to place. "
    bulk = 1200
    bulkCapacity = 350
    canPullTravel = true
    canPushTravel = nil
    matchPullOnly = true
    matchPushOnly = nil
    dobjFor(PushTravelThrough) {
        // verify() {}
        action() {
            if (gIobj && (gIobj.ofKind(OctDoor)))
                gIobj.wagonMover();
            else inherited;
        }
    }
    afterTravel(t, c) {
        if (getOutermostRoom == upperPromenadeWest)
            wagonAch.awardPointsOnce();
    }
    dobjFor(PushTravelDir) {
        verify() {}
        check() {
            if(isIn(ancientCrypt) && (mummy.curState == mummyBound)
               && (largePot.isIn(self)))
                "You tug on the wagon, but the tangling of the vine with the 
                mummy defeats you. But maybe you could extricate the wagon
                by lifting the pot out of it. ";
        }
    }
    iobjFor(PutOn) asIobjFor(PutIn)
    dobjFor(Take) {
        // To deal with the possibility that the player may have carelessly (and not
        // on their first game run-through) used the wagon to immobilize the mummy
        // before they schlepped the pterodactyl chow up to the roof, we're going to
        // allow you to extricate the wagon from under the pot if the mummy is
        // immobilized.
        check() {
            local canTakeIt = true;
            if (mummy.curState != mummyBound) canTakeIt = nil;
            if (!largePot.isIn(self)) canTakeIt = nil;
            if (gPlayerChar.getOutermostRoom() != ancientCrypt) canTakeIt = nil;
            if (!canTakeIt) "You can't pick up the little red wagon, but you could probably pull it
                from one location to another. ";
        }
        action() {
            largePot.moveInto(ancientCrypt);
            "With a bit of tugging, you're able to free the little red wagon from under the heavy
            pot. ";
        }
    }
;

//------------------------------------------------------------------------------
// Stuff to deal with the mummy:
//------------------------------------------------------------------------------

vineSeedEnvelope: PDportable, OpenableContainer
    'yellow envelope; jungle vine seed (paper) little; seeds' @greenFriends
    "The little yellow paper envelope sports a drawing of a tangled profusion of green vines.
    Above the drawing are printed the words <q>JUNGLE VINE SEEDS.</q> <<if (tornOpen)>>The envelope
    has been torn open. <<end>>"
    initSpecialDesc = "Lying on the floor is a small yellow envelope. "
    tornOpen = nil
    stillNeeded() { if (mummy.curState == mummyBound) return nil;
        return true; }
    dobjFor(Open) {
        check() { if (tornOpen) "You have already torn the envelope open. ";
        }
        action() {
            tornOpen = true;
            makeOpen(true);
            "You tear open the envelope, revealing a handful of yellowish seeds. ";
        }
    }
    dobjFor(Close) {
        check() {
            if (!tornOpen) "You haven't opened the envelope yet. ";
            else "Closing the opening you've made by tearing the yellow envelope is not
                possible, and probably not necessary. ";
        }
    }
    dobjFor(Tear) {
        verify() { logical; }
        action() { doInstead (Open, self); }
    }
    bulk = 6
    bulkCapacity = 5
;

+ vineSeeds: PDportable 'seeds; jungle vine yellow yellowish; handful; them'
    "The seeds look a bit like pumpkin seeds, but as the envelope fails to show any pumpkins,
    they're probably not. "
    vocabLikelihood = 10
    bulk = 2
    allowPourOntoMe = true
    plural = true
    dobjFor(Eat) {
        verify() {}
        check() {
            "They don't look very tasty. ";
        }
    }
    iobjFor(PutOn) asIobjFor(PourInto)
    iobjFor(PourOnto) asIobjFor(PourInto)
    iobjFor(PourInto) {
        verify() {}
        check() {
            if (gDobj != miraklGro) "Pouring {that dobj} onto the seeds would likely
                accomplish nothing. ";
            else if (!isIn(largePot)) "If you want the seeds to sprout and grow,
                you\'ll need to put them somewhere suitable. ";
            else if (!largePot.fullOfDirt) "There\'s no dirt in the pot. Seeds need
                dirt. ";
        }
        action() {
            doInstead (PourInto, miraklGro, largePot);
        }
    }
;

bigVines: Fixture 'big vines; vigorous large leafy; vine plant tangle'
    "The large, vigorous vines are firmly wrapped around the mummy. "
    dobjFor(Take) {
        verify() {}
        check() {
            "The vines are too thickly tangled with the mummy and with one another. ";
        }
    }
    dobjFor(CutWith) asDobjFor(Cut)
    dobjFor(Cut) {
        verify() {}
        check() {
            "If you did that, the mummy might attack you. Better leave the
            vines alone. ";
        }
    }
;
    
//------------------------------------------------------------------------------
// The five gold coins:
//------------------------------------------------------------------------------

class GoldCoin: PDportable
    vocab = 'gold coin'
    desc = "The coin is probably not real gold, it's just
        gold-colored. <<if (isIn(gPlayerChar))>>The obverse
        features an elaborate Gothic logo of an intertwined 'F' and 'G', and the
        reverse bears the legend <q>1 StuffDollar.</q> <<end>>"
    vocabLikelihood {
        if (!moved) return 10;
        return 0;
    }
    stillNeeded { return true; }
    formerLoc = nil
    dobjFor(Take) {
        verify() { if (isDirectlyIn(gPlayerChar)) illogicalAlready ( 'You already have
            the gold coin. ' );
        }
        action() {
            formerLoc = location;
            inherited;
//            if ((me.getOutermostRoom() == arcadeCenter) && (fakeGoldCoin.isIn(diorama)))
//              "To avoid confusion, if you\'re meaning to take the gold coin in the
//              diorama, you\'ll need to say so. ";
//            else if ((me.getOutermostRoom() == petShop) && (coin4.isIn(mynahCage)))
//                "To avoid confusion, if you\'re meaning to take the gold coin in the
//              cage, you\'ll need to say so. ";
//            inherited;
        }
        report () {
            "You take the coin from <<formerLoc.theName>>. ";
        }
    }
    // By default (see the code for the scrimshaw shop) Things have a default
    // bulk of 5.
    bulk = 1
    hasBeenTaken = nil
;

// coin 1 is in the Easter egg:
coin1: GoldCoin
    hasAppeared = nil
    dobjFor(Take) {
        action() {
            coin1Ach.awardPointsOnce();
            inherited;
        }
    }
;
// coin 2 is in the urn of the potted palm:
coin2: GoldCoin;

// coin 3 will be retrieved from underneath the diorama:
coin3: GoldCoin // '+;; zqzq'
    dobjFor(Take) {
        action() {
            coin3Ach.awardPointsOnce();
            inherited;
        }
    }
;
// coin 4 is in the mynah bird's cage:
coin4: GoldCoin
    location = mynahCage
    initSpecialDesc = "Lying in the shredded newspaper at the bottom of the bird cage
        is a gold coin. "
    // A higher value than the value for the mynah bird:
    specialDescOrder = 10
;
// coin5 will be in the rolltop desk:
coin5: GoldCoin
    dobjFor(Take) {
        action() {
            coin5Ach.awardPointsOnce();
            inherited;
        }
    }
;

//------------------------------------------------------------------------------
// Betsy's stolen ring:
//------------------------------------------------------------------------------

rubyRing: Wearable 'ruby ring; gold small deeply colored red; band' @betsy
    "The ruby is small, but deeply colored. The band is gold. Assuming it\'s the same
    ring (and really, there\'s not the slightest question about that), your grandmother gave it
    to you on your 18th birthday. "
    isHidden = true
    wornBy = betsy
    aName = 'your ' + name
    bulk = 3
    dobjFor(Take) {
        verify() {}
        check() {
            "That would probably start a fight, and you have more important things to do
            this afternoon than fight with an irritating hairdresser. ";
        }
    }
;

//------------------------------------------------------------------------------
// The tattered cloak:
//------------------------------------------------------------------------------
    
tatteredCloak: PDportable
    'tattered cloak' @ cloakRoomHook
    "The cloak has seen better days, clearly. It's all but falling apart. Various
    patches are of various colors, and none of them is very large. "
    bulk = 50
    dobjFor(Wear) {
        verify() {}
        check() {
            "It would probably just fall apart. ";
        }
    }
    dobjFor(CutUp) asDobjFor(Cut)
    dobjFor(Cut) {
        verify() {}
        check() {
            if (!scissors.isDirectlyIn(gPlayerChar))
                "Your teeth aren't sharp enough. If you want to do that, you'll need some scissors. ";
        }
        action() {
            doInstead(CutWith, self, scissors);
        }
    }
    dobjFor(CutUpWith) asDobjFor(CutWith)
    dobjFor(CutWith) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != scissors)) "Maybe you'd have better luck with a
                pair of scissors. ";
        }
        action() {
            moveInto(nil);
            scrapsOfCloak.moveInto(gPlayerChar);
            "You deftly snip apart the tattered cloak. Now you've supplied yourself
            with a handful of smallish scraps of cloth in a variety of bright colors. ";
        }
    }
;

scrapsOfCloak: PDportable
    'scraps of colored cloth; smallish multicolored squarish multicolored; squares; them'
    "The scraps are of various colors, and mostly squarish in shape. "
    bulk = 45
;

//------------------------------------------------------------------------------
// MiraklGro:
//------------------------------------------------------------------------------

miraklGro: PDportable 'bottle of MiraklGro; white plastic fertilizer (plant) food' @secludedLair
    "The green plastic bottle has a bright yellow label that reads <q>MiraklGro Plant Food.</q>
    <<if (isDirectlyIn(gPlayerChar))>>Lower down, in smaller letters, the label adds, <q>Instructions: Pour MiraklGro on any
    living plant to produce rapid growth.</q> Down at the bottom, in <i>really</i> tiny
    letters, it says, <q>Caution: Explosive plant growth cannot be halted or reversed by
    application of BioBGone or other herbicides. Do not use this product unless you're <i>sure</i>
    you want the plant to grow like crazy.</q><<end>> "
//    initSpecialDesc = 
//        "The little wooden people are riding their gerbils in circles around
//        a green plastic bottle of MiraklGro Plant Food. "
    bulk = 20
    isPourable = true
    // This will become not a fixture after you've pacified the marionettes:
    isFixed = true
    lookListed = (!isFixed)
    stillNeeded() { if (mummy.curState == mummyBound) return nil;
        return true; }
    dobjFor(Take) {
        verify() { logical; }
        action() {
            inherited;
            miraklgroAch.awardPointsOnce();
        }
    }
    dobjFor(Pour) {
        verify() {}
        check() {
            "You\'ll need to say where you want to pour it. ";
        }
    }
    dobjFor(PourOnto) asDobjFor(PourInto)
    dobjFor(PourInto) {
        verify() {}
        check() {
            // This could be made more thorough, but for now let's leave it:
            if (gIobj && (gIobj != largePot) && (gIobj != vineSeeds)) {
                if (gIobj == greenOoze) "That would have the ooze bubbling
                    up out of the grate and quite possibly crawling up your
                    legs. Better not. ";
                else "{The subj dobj} {is} probably large enough already. ";
            }   
        }
        // We'll let the iobj handle the action.
    }
;

//------------------------------------------------------------------------------
// BioBGone and other stuff needed for the grate puzzle:
//------------------------------------------------------------------------------

bioBGone: PDportable 'bottle of BioBGone; white plastic weed; weedkiller killer herbicide bottle' @bioBGoneRack
    "The white plastic bottle has a bright red label that reads
    <q>BioBGone Weed Killer.</q> Lower down, in smaller letters, the label
    adds, <q>Instructions: Pour BioBGone on any living plant to produce immediate
    death.</q> Down at the bottom, in <i>really</i> tiny letters, it says, <q>Caution:
    Fatal if swallowed. In case of contact with eyes, immediately type 'undo'.</q> "
    bulk = 20
    stillNeeded() { if (signetRing.moved) return nil;
        return true; }
    dobjFor(PourInto) asDobjFor(PourOnto)
    dobjFor(PourOnto) {
        verify() {logical; }
        check() {
            if ((gIobj != grate) && (gIobj != greenOoze))
                "Pouring the BioBGone onto {that iobj} would be mischievous, but
                probably not useful. ";
            else if (greenOoze.isDead) "You already killed the green ooze. ";
        }
        action() {
            greenOoze.moveInto(nil);
            deadOoze.moveInto(grate);
            greenOoze.isDead = true;
            "You slosh a healthy dose (well, an unhealthy dose) of BioBGone onto the
            green ooze. With a little sighing noise, the ooze shrivels, turns brownish-gray,
            and crumbles. ";
        }
    }
    dobjFor(Taste) asDobjFor(Drink)
    dobjFor(Drink) {
        verify() { logical; }
        check() { "Suicide is not the answer to this one. "; }
    }
    dobjFor(Open) {
        verify() {}
        check() {
            "You open the bottle, take a tentative whiff that sears your sinuses,
            and swiftly screw the top
            back on. When you're ready to pour this awful stuff on something, just go
            ahead and pour it. ";
        }
    }
;

oilCan: PDportable 'oil can; small metal' @broomCloset
    "It's a small metal oil can, the kind you might find on any workbench. "
    bulk = 6
    stillNeeded() { if (hvacSwitch.oiled) return nil;
        return true; }
    dobjFor(PourInto) asDobjFor(PutOn)
    dobjFor(PourOnto) asDobjFor(PutOn)
    dobjFor(PutOn) {
        verify() {}
        check() {
            if (gIobj && (gIobj != hvacSwitch))
                "Smearing oil on {the iobj} would only make a mess. ";
        }
    }
    iobjFor(OilWith) {
        preCond = [objHeld]
        verify() {}
        check() {
            if (gDobj != hvacSwitch) "{The subj dobj} seems not to be in need of oil. ";
        }
        // We'll let the switch handle the action.
    }
;

screwdriver: PDportable 'screwdriver; metal yellow plastic; blade handle' @broomCloset
    "It's a standard flat-bladed screwdriver, with a metal blade and a yellow plastic
    handle. "
    bulk = 4
    initSpecialDesc = "Lying forgotten on the floor is a screwdriver. "
    iobjFor(OpenWith) {
        verify() { logical; }
    }
    iobjFor(ScrewWith) {
        verify() { logical; }
    }
    iobjFor(UnscrewWith) {
        verify() { logical; }
    }
    iobjFor(TightenWith) {
        verify() { logical; }
    }
    iobjFor(LoosenWith) {
        verify() { logical; }
    }
    iobjFor(StraightenWith) {
        verify() { logical; }
    }
;

deadOoze: Thing
    'some shriveled brown ooze; dead dried dry brownish-gray brownish gray crumbled cracked; remains substance'
    "What remains of the green ooze is now brown and shriveled. It appears to be quite dead. "
    hiddenIn = [signetRing]
    autoTakeOnFindHidden = true
    allowPourIntoMe = true
    allowPourOntoMe = true
    searchListed = true
    feelDesc = "The shriveled brown substance feels dry and powdery. "
    dobjFor(LookIn) {
        check() {
            if (!grate.isOpen) "You can't very well search the dried-up ooze without opening the grate. "; }
        action() {
            ringAch.awardPointsOnce();
            inherited();
        }
    }
    dobjFor(TakeFrom) asDobjFor(Take)
    dobjFor(Take) {
        check() {
            "Your complexion is not dark enough for you to consider using the crumbled brown
            substance as face powder. Besides, it's probably toxic. ";
        }
    }
;

marlboros: PDportable 'pack of Marlboros; cigarette cellophane; cigarettes; it them'
    "The familiar red-and-white cigarette pack <<if !opened>>is cellophane-wrapped; unopened<<else>>has
    been ripped open and cigarettes are peeking out<<end>>. "
    bulk = 4
    dobjFor(Open) {
        verify() {}
        check() {
            if (!opened)
                "Not being a smoker, you're not tempted to open the pack of Marlboros. ";
            else "{The betsy} seems already to have opened them. ";
        }
    }
    isCombustible = true
    dobjFor(Light) asDobjFor(Burn)
    dobjFor(Burn) {
        verify() {}
        check() {
            "You're not a cigarette smoker. ";
        }
    }
    ripOpen() { opened = true; }
    opened = nil
    dobjFor(Take) {
        check() {
            if (isIn(betsy)) "{The betsy} is gripping the pack of Marlboros tightly. ";
        }
    }
;

cigar: PDportable 'cigar; large fat; stogie'
    "The cigar is large and fat and smells strongly of fresh tobacco. "
    bulk = 5
    stillNeeded() { if (!joe.pixelated) return nil;
        return true; }
    isLit = nil
    litDaemon = nil
    litCount = 0
    isCombustible = true
    startBurn () {
        litCount = 0;
        litDaemon = new Daemon(self, &burn, 1);
        joe.pixieCatchDaemonStart();
    }
    stopBurn () {
        if (litDaemon != nil) {
            litDaemon.removeEvent();
            litDaemon = nil;
            isLit = nil;
            if (flyingPixies.flying)
                joe.pixieCatchDaemonStop();
        }
    }
    burn() {
        litCount++;
        if ((litCount > 3) && (gPlayerChar.getOutermostRoom() != joe.getOutermostRoom())) {
            "Not wanting to pollute your lungs any further, you
            stub out the cigar on the sole of your shoe. ";
            stopBurn();
        }
    }
    dobjFor(Drop) {
        check() {
            if (isLit) "Dropping a lighted cigar is not advisable. Better put it out first. ";
        }
    }
    dobjFor(Smoke) asDobjFor(Light)
    dobjFor(Burn) asDobjFor(Light)
    dobjFor(Light) {
        preCond = [objHeld]
        verify() {}
        check() {
            if (isLit) "The cigar is already lit. ";
            else if (!singleMatchbook.isDirectlyIn(gPlayerChar)) {
                if (singleMatchbook.isIn(gPlayerChar)) "You need to
                    be holding the matchbook to do that. ";
                else "You don't seem to have any matches. ";
            }
        }
        action() {
            isLit = true;
            startBurn();
            "You ignite the cigar, take a tentative puff, and suppress (with
            difficulty) a coughing fit. This is not something you're keen to do,
            unless there's a good reason for it. ";
        }
    }
    dobjFor(BurnWith) {
        verify() {}
        check() {
            if (isLit) "The cigar is already lit. ";
            else if (gIobj && (gIobj != singleMatchbook)) "It would be tricky
                to light the cigar with that. ";
        }
        action() {
            doInstead(Light, self);
        }
    }
    dobjFor(Extinguish) {
        verify() {}
        check() {
            if (!isLit) "The cigar is not lit. ";
        }
        action() {
            isLit = nil;
            stopBurn();
            "You stub out the burning end of the cigar on the sole of your shoe. ";
        }
    }
    dobjFor(PutIn) {
        check() {
            if (isLit) "You should probably extinguish it before you try that. ";
        }
    }
    dobjFor(Smell) {
        verify() {}
        action() {
            "It smells quite a lot like a cigar. ";
        }
    }
;

moneyMagnet: PDportable 
    'u-shaped piece of metal; u curious-looking curious (money); u-shape shape magnet horseshoe' @safeShelves
    "It's more or less the shape of a horseshoe, but it
    doesn't look much like a real horseshoe; it's just a U-shaped piece of
    metal about the size of your hand. On the piece of metal is engraved the
    symbol <q>F&G$</q>, followed by the horizontal 8 sign that means infinity. "
    initSpecialDesc = "On one shelf you spy a curious-looking U-shaped piece of
        metal. "
    vocabLikelihood = 20
    stillNeeded() { if (coin5.moved) return nil;
        return true; }
    dobjFor(Wave) {
        check() {
            if (me.getOutermostRoom() != grandmasDrawers) "You wave the U-shaped piece of metal
                in the air, but it doesn\'t appear you\'ve accomplished anything. ";
            else if (!rolltopDesk.isOpen) "You wave the U-shaped piece of metal
                in the air, but it doesn\'t appear you\'ve accomplished anything. ";
        }
        action() {
            doInstead(AimAt, self, cubbyHoles);
        }
    }
    iobjFor(TouchWith) {
        check() {
            if ((gDobj != rolltopDesk) && (gDobj != cubbyHoles)) "Nothing obvious happens. ";
        }
        // The rolltopDesck or cubbyholes will handle the action.
    }
    dobjFor(WaveAt) asDobjFor(AimAt)
    dobjFor(AimAt) {
        verify() {}
        check() {
            if (gIobj && (gIobj != cubbyHoles) && (gIobj != squareHole) && (gIobj != rolltopDesk))
                "There's no obvious result. ";
        }
        // The cubbyHoles and squareHole will handle the event.
    } 
    iobjFor(GetWith) {
        verify() {}
        check() {
            "The U-shaped piece of metal doesn't seem to be an ordinary magnet. It may be
            specially attuned to one specific type of thing. ";
        }
    }
;

// ----------------------------------------------------------------------------
// the top, etc:
// ----------------------------------------------------------------------------

spinningTop: PDportable 'little toy top; spinning' @toyShop
    "The little toy top is the kind you could set spinning on any level surface. "
    painted = nil
    dobjFor(Spin) {
        preCond = [objHeld]
        verify() {}
        action() {
            "You set the top down and give it a twist. It spins for a minute,
            wobbles, and falls over. You pick it up. Oh, well --- nothing ventured,
            nothing gained. ";
        }
    }
    initSpecialDesc = "Lying on the floor is a little toy top. "
;

paintbrush: PDportable 'paintbrush; fine-tipped; tip brush' @artWorkbench
    "The paintbrush has a fine tip, suitable for detail work. "
    initSpecialDesc = "Lying on the workbench is a fine-tipped paintbrush. "
;

goldPaint: PDportable 'little bottle of gilt paint; gold-colored sparkly (gold)' @giftShop
    "The little bottle of gilt paint would probably be useful to a hobbyist. The paint is gold-colored
    and seems to be sparkly, but it's surely not actual gold. "
    initSpecialDesc = "On a display table you spot a little bottle of gold paint. "
    dobjFor(PourInto) asDobjFor(Pour)
    dobjFor(PourOnto) asDobjFor(Pour)
    dobjFor(Pour) {
        verify() {}
        check() {
            "That would only make a mess. ";
        }
    }
    dobjFor(Open) {
        verify() {}
        check() {
            "You open the bottle. Sure enough, it's full of gold paint. Having no need,
            yourself, for any gold paint, you tidily screw the top back on the bottle. ";
        }
    }
;

// Note: The marigolds are in PDexteriorFront, in northOfTower.
    
//------------------------------------------------------------------------------
// The shipping department access card.
//------------------------------------------------------------------------------

accessCard: PDportable 'plastic card; small access blue' @workbenchDrawer
    "The small plastic card is bright blue. On it, the word ACCESS in red is pierced by
    a stylized yellow lightning bolt. On the underside is a magnetic strip. "
    bulk = 2
;

+ Component 'lightning bolt; yellow stylized; zigzag'
    "The lightning bolt zigzags in the usual way. "
;

+ Component 'magnetic strip; dark gray'
    "It looks pretty much like any other dark gray magnetic strip on the back of a card. "
;

//------------------------------------------------------------------------------
// The paintings that will be moved into the gallery as you get the tour:
//------------------------------------------------------------------------------

class GalleryPainting: Fixture
    cannotTakeMsg = 'Under the watchful eye of the shopkeeper, picking up a painting and
        sauntering away with it would be a mistake. And he doesn\'t seem to be going anywhere. '
    vocabLikelihood = -5
;

butterflyPainting: GalleryPainting 'butterfly painting{xxx}; monarch outspread; wings'
    "The butterfly, larger than life, seems to be a monarch. Its wings are outspread. "
;

childPainting: GalleryPainting 'painting{xxx} of a child; golden golden-haired; hair ball'
    "The golden-haired child is reaching out happily to catch a ball. "
;

vasePainting: GalleryPainting 'vase painting{xxx}; yellow graceful; flowers'
    "The graceful vase is filled with yellow flowers. "
;

waterfallPainting: GalleryPainting 'waterfall painting{xxx};; height spray'
    "The waterfall is narrow, and leaps from a great height, trailing spray into the breeze. "
;

womanPainting: GalleryPainting 'painting{xxx} of a woman;; bench'
    "The woman is sitting on a bench, staring pensively off into space. "
;

skatersPainting: GalleryPainting 'painting{xxx} of some skaters; glidig graceful frozen; pond ice'
    "The ice skaters are gliding gracefully across a frozen pond. This is the painting
    the gallery owner mentioned the bartender is fond of. "
    cannotTakeMsg = 'Doubtless {the steve} would love it if you absconded with this particular painting
        and presented it to him, but with the gallery owner watching your every
        move, that doesn\'t appear to be a practical option. '
;

vineyardPainting: GalleryPainting 'vineyard painting{xxx}; sun-drenched; rows hill'
    "The vines are planted in rows along the side of a sun-drenched hill. "
;

shipPainting: GalleryPainting 'sailing ship painting{xxx}; stormy; sea'
    "The sailing ship is being tossed on a stormy sea. "
;

clownPainting: GalleryPainting 'clown painting{xxx}; circus cheerful dancing'
    "The clown looks cheerful, but not in a very convincing way. He's standing on one foot, or
    possibly dancing. "
;

wolfPainting: GalleryPainting 'wolf painting{xxx}; baying howling snow tipped snow-tipped pine; trees moon'
    "The wolf is howling at the moon. In the background are some snow-tipped pine trees. "
;

sunsetPainting: GalleryPainting 'sunset painting{xxx}; glorious golden shimmering scattered; rays clouds'
    "The painting depicts a glorious golden sunset, complete with rays shimmering through scattered
    clouds. "
;

carPainting: GalleryPainting 'race car painting{xxx}; racing red white; curve'
    "The car is red and white. It's racing around a curve. "
;

//------------------------------------------------------------------------------
// The secret book:
//------------------------------------------------------------------------------

// Note: The identikit machine will also require data entry; possibly it can
// and should use the same Action.

secretBook: PDportable 'slim leather-bound volume; shaxpur clasped leather thin secret; book'
    "The book is slim but bound in leather. A rather tarnished brass clasp is evidently intended to
    hold it shut. The title embossed on the cover proclaims it, in large gilt letters,
    <q>The Secret Book of W.\ Shaxpur, Esq.</q> "
    bulk = 35
    initSpecialDesc = "Lying on the rug is a slim leather-bound book. "
    stillNeeded() { if (shelfDoor.isOpen) return nil;
        return true; }
    appeared = nil
    remapOn: SubComponent { }
    remapIn: SubComponent {
        isOpenable = true
        notifyInsert(obj) {
            "The tablet fills up the book. You can't put anything else into it. ";
            exit;
        }
        dobjFor(Open) {
            action() {
                inherited;
                "Nestled within the book is a data entry tablet. ";
            }
        }
        dobjFor(LookIn) {
            action() { "Tucked away inside the book is a data entry tablet. "; }
        }
    }
    readDesc () {
        if (remapIn.isOpen) "The book has been hollowed out, so there's nothing to read. Instead,
            its interior is occupied by a tablet computer of some sort. ";
        else "To read what\'s in the book, you would need to open it. ";
    }
;

// No + notation usable here, because the clasp and tablet are located in the remapOn
// and remapIn.

brassClasp: Component 'brass clasp; tarnished'
    "The brass clasp is evidently for holding the book closed. "
    location = secretBook.remapOn
    dobjFor (Open) {
        verify() { logical; }
        check() {
            if (secretBook.remapIn.isOpen) "You've already opened the book. ";
        }
        action() {
            doInstead(Open, secretBook);
        }
    }
;

codeEntryTablet: Component 'data entry tablet; computer virtual QWERTY; keyboard screen' 
    "Visible on the face of the tablet is a virtual QWERTY keyboard suitable for data
    entry. <<if (solved)>>Displayed above the QWERTY keyboard is the phrase <q>unclasp a secret book.</q> "
    location = secretBook.remapIn
    canTypeOnMe = true
    canEnterOnMe = true
    solved = nil
    dobjFor(Take) {
        verify() {}
        check() {
            "The tablet is firmly mounted inside the fake book. ";
        }
    }
    dobjFor(TypeOn) {
        verify() { logical; }
        check() {
            if (shelfDoor.isOpen) "The tablet makes a sad little noise, but nothing else happens. ";
        }
        action () {
            if (gLiteral == 'unclasp a secret book') {
                shelfDoor.makeOpen(true);
                solved = true;
                secretDoorAch.awardPointsOnce();
                if (me.getOutermostRoom == rareBookShop)
                    "With a dry whirring noise, the tall bookshelf in the south wall pivots outward,
                    revealing a way through to an interior room. ";
                else "The tablet makes a happy chiming noise, but nothing seems to have happened. ";
            }
            else {
                "The screen briefly displays <q>";
                say (gLiteral);
                "</q>, but then goes dark again. ";
                solved = nil;
            }
        }
    }
    dobjFor(EnterOn) asDobjFor(TypeOn)
;

//------------------------------------------------------------------------------
// The golf ball starts out nowhere, because you have to search the long grass:
//------------------------------------------------------------------------------

golfBall: PDportable 'golf ball; white little round'
    "It's a little round white golf ball, not too badly scuffed. "
    bulk = 3
    notYetFound = true
    dobjFor(Attack) {
        verify() {}
        check() {
            if (!putter.isDirectlyIn(gPlayerChar))
                "You seem to have nothing suitable to hit the ball with. ";
        }
        action() {
            doNested(AttackWith, golfBall, putter);
        }
    }
    dobjFor(Putt) {
        verify() {}
        check() {
            if (!putter.isDirectlyIn(me)) "You have nothing to putt with. ";
            }
        action() {
            doInstead (AttackWith, self, putter);
        }
    }
    dobjFor(AttackWith) {
        preCond = objNotHeld
        verify() {}
        check() {
            if (gIobj != putter) "You're not likely to score a hole-in-one using {that iobj}. ";
            else if (gPlayerChar.getOutermostRoom != peeWeeTee)
                "If you plan to play a little golf, you can probably find a better place to
                give it a try. ";
        }
        action() {
            "You give the ball a whack. It careens down the fairway toward the dragon's ";
            if (!junctionBox.isPowered) {
                "head, smites the dragon smartly on the nose, and bounces off. ";
                moveInto(byDragonHead);
            }
            else {
                "head and smacks into one of the exposed teeth. Whereupon the tooth, which
                may have been loose to begin with, falls on the fairway with a thump. The
                golf ball itself, after caroming off of a couple of other teeth, vanishes
                into the dragon's maw, presumably lost forever. ";
                moveInto(nil);
                dragonTooth.moveInto(byDragonHead);
                distantTooth.moveInto(peeWeeTee);
            }
        }
    }
;

//------------------------------------------------------------------------------
// the putter:
//------------------------------------------------------------------------------

putter: PDportable 'putter; golf; club' @narrowGap
    "It's a fairly ordinary putter --- a grip, a chrome-plated shaft, and a rectangular head suitable
    for striking a golf ball in a light, controlled manner. "
    bulk = 30
    initSpecialDesc = "Through the narrow gap you can see a putter that has fallen to
        the floor. "
    stillNeeded() { if (dragonTooth.moved) return nil;
        return true; }
    iobjFor(AttackWith) {
        preCond = [objHeld]
        verify() {}
        check() {
            if (gDobj != golfBall) "Smacking random objects with the putter is
                unlikely to win you many friends. ";
        }
    }
    dobjFor(GetWith) {
        verify() {}
        check() {
            if (!isIn(narrowGap)) "You could just pick it up. ";
        }
    }
    dobjFor(Take) {
        check() {
            if (isIn(narrowGap))
                "You try sliding your hand under the grille, but it's no use. You
                can't quite reach the putter. ";
        }
    }
    dobjFor(Feel) {
        verify() {}
        check() {
            if (isIn(narrowGap)) "You can't reach it. ";
        }
    }
;

+ Component, Decoration 'chrome-plated shaft'
    "The chrome-plated shaft is part of the putter. "
;
+ Component, Decoration 'grip; ; handgrip'
    "The grip is part of the putter. "
;
+ Component, Decoration 'rectangular head'
    "The rectangular head is down there at the end of the shaft. "
;

//------------------------------------------------------------------------------
// the dragon's tooth:
//------------------------------------------------------------------------------

// The desired solution is imageNum = 5

dragonTooth: PDportable 'long sharp tooth; smooth unblemished curved dragon dragon\'s white ivory; point'
    "The dragon tooth is long and curved, and tapers to a sharp point. It has a pleasing ivory
    color. <<if (carved)>><<carveDesc>><<else>>Its surface is smooth and unblemished.<<end>> "
    bulk = 7
    dobjFor(Take) {
        action() {
            inherited;
            distantTooth.moveInto(nil);
            toothAch.awardPointsOnce();
        }
    }
    carved = nil
    imageNum = 0
    carve(sel) {
        imageNum = sel;
        carved = true;
        switch(sel) {
            case 1: addVocab( '; carved; vase flowers spray'); break;
            case 2: addVocab( '; carved old seated; sailor net dock pipe'); break;
            case 3: addVocab( '; carved scenic long narrow light of; cascade waterfall spray'); break;
            case 4: addVocab( '; carved rocky crashing on; lighthouse island surf rocks'); break;
            case 5:
                addVocab( '; carved ice three small gliding; skaters pond');
                scrimshawAch.awardPointsOnce();
                break;
            case 6: addVocab( '; carved beautiful outspread; butterfly insect wing'); break;
            case 7: addVocab( '; carved roaring bull reared back; head walrus'); break;
            case 8: addVocab( '; carved reaching; child ball'); break;
            case 9: addVocab( '; carved dancing capering; clown'); break;
            case 10: addVocab( '; carved howling handsome; wolf moon'); break;
            case 11: addVocab( '; carved dragon breathing chinese; fire'); break;
            case 12: addVocab( '; carved ship\'s; anchor rope coil length'); break;
            case 13: addVocab( '; carved radiant scattered; sunset clouds rays'); break;
            case 14: addVocab( '; carved race speeding; car track'); break;
            case 15: addVocab( '; carved jazz saxophone woodwind alto tenor; musician saxophonist player'); break;
            case 16: addVocab( '; carved sailing ship tossed by old fashioned old-fashioned severe; storm'); break;
            case 17: addVocab( '; carved wicked hooked; witch nose eyes'); break;
            case 18: addVocab( '; carved marlin fish leaping from; sea'); break;
            default: addVocab( '; carved tumbling; pair dice'); break;
            
        }
    }
    carveDesc { "On its surface is carved ";
        switch(imageNum) {
            case 1: "a smoothly curved vase that holds a spray of flowers"; break;
            case 2: "an old sailor seated on a dock, smoking a pipe while he fixes his net"; break;
            case 3: "a long, narrow waterfall from which drifts a light cascade of spray"; break;
            case 4: "a lighthouse perched on a rocky island, with surf crashing on the rocks"; break;
            case 5: "a scene of three ice skaters gliding on a small pond"; break;
            case 6: "a beautiful butterfly with outspread wings"; break;
            case 7: "a bull walrus, his head reared back as he roars"; break;
            case 8: "a child reaching out to catch a ball"; break;
            case 9: "a clown dancing or capering"; break;
            case 10: "a handsome wolf howling at the moon"; break;
            case 11: "a Chinese dragon breathing fire"; break;
            case 12: "a ship's anchor around which coils a length of rope"; break;
            case 13: "a radiant sunset whose rays pierce the scattered clouds"; break;
            case 14: "a race car speeding along a track"; break;
            case 15: "a jazz saxophonist concentrating fiercely as he plays"; break;
            case 16: "an old-fashioned sailing ship tossed by a severe storm"; break;
            case 17: "a wicked witch with sharp eyes and a hooked nose"; break;
            case 18: "a marlin leaping from the sea"; break;
            // In effect, this is #19:
            default: "a pair of tumbling dice showing 6 and 1";
        }
        ". ";
    }
;

//------------------------------------------------------------------------------
// dowsingRod:
//------------------------------------------------------------------------------

dowsingRod: PDportable 'forked stick; Y-shaped dowsing (wood); rod branch bark twig twigs' @besideTheCreek
    "The forked stick is about the size of a child's tricycle handlebars. Bits of
    bark still cling to the wood, but most of the smaller twigs have been shorn
    away, leaving a Y-shaped branch. "
    bulk = 15
    initSpecialDesc = "Lying at the edge of the creek is a forked stick. "
;

//------------------------------------------------------------------------------
// rope:
//------------------------------------------------------------------------------

ropeClimbingBarrier: TravelBarrier
    canTravelerPass (traveler, connector) {
//        local b = traveler.getCarriedBulk();
//        if (b > 30) return nil;
//        return true;
        if (me.countHeld() > 1) return nil;
        return true;
    }
    explainTravelBarrier(t, c) {
        "You're carrying too much stuff for rope-climbing. ";
    }
;

// gPlayerChar.getCarriedBulk() will return how much stuff the traveler is carrying...

lowerEndOfRope: StairwayUp 'rope; lower end of trusty thick sturdy'
    "The rope is dangling down the cliff. Its lower end is only a couple of feet
    from the ground here. "
    travelBarriers = ropeClimbingBarrier
    destination = footOfPole
    dobjFor(Take) {
        verify() { logical; }
        check() { "You tug on the rope, but it proves to be quite firmly tied
            to the pole far above. (And a good thing, too.) ";
        }
    }
    dobjFor(Untie) asDobjFor(Take)
;

// The tricky thing we're going to do here is, when the rope is tied to the pole,
// we hustle it offstage and replace it with a StairwayDown object:

tiedAndDanglingRope: StairwayDown 'rope; thick sturdy trusty tied'
    "The trusty rope is tied to the base of the power pole and extends outward over
    the edge of the cliff. "
    travelBarriers = ropeClimbingBarrier
    destination = footOfCliff
    dobjFor(Untie) {
        verify() { logical; }
        check() {}
        action() {
            footOfPole.allowDescent(nil);
            lowerEndOfRope.moveInto(nil);
            rope.moveInto(footOfPole);
            "You untie the rope from the power pole and coil it neatly on the ground. ";
            moveInto(nil);
        }
    }
    dobjFor(Take) {
        verify() { logical; }
        check() {
            "It's tied to the power pole. You'll need to untie it before you
            can pick it up. ";
        }
    }
;

tiedLocalRope: Thing 'rope; thick sturdy trusty free of tied; end'
    "The rope is tied to the base of the power pole. "
    dobjFor(Take) {
        check() {
            "It's tied to the power pole. You'll need to untie it before you
            can pick it up. ";
        }
    }
    dobjFor(Untie) {
        verify() { logical; }
        check() {}
        action() {
            rope.moveInto (footOfPole);
            moveInto (nil);
            "You untie the rope from the power pole and coil it neatly on the
            ground. ";
        }
    }
    dobjFor(LowerOver) {
        verify() { logical; }
        action() {
            tiedAndDanglingRope.moveInto(footOfCliff);
            footOfPole.allowDescent(true);
            lowerEndOfRope.moveInto(footOfCliff);
            tiedAndDanglingRope.moveInto(footOfPole);
            "You lower the free end of the tied rope over the cliff. ";
            moveInto(nil);
        }
    }
    dobjFor(ClimbDown) {
        verify() { illogical ( 'But it\'s just lying there on the ground. ' );
        }
    }
;

rope: PDportable 'some rope; thick fairly sturdy trusty of[prep]; coil length' @behindStairs
    "The rope is twenty feet or so in length. It's thick and looks fairly sturdy. "
    bulk = 35
    initSpecialDesc = "A coil of rope lies forgotten in a corner here. "
    dobjFor(TieTo) {
        preCond = [objHeld]
        verify() { logical; }
        check() {
            // we don't need to check this, since the tied rope will never exist:
            // if (tiedToWhat) "The rope is already tied to <<tiedToWhat.theName>>. ";
            
            if (gIobj != powerPole) "Tying the rope to {the iobj} would accomplish
                very little. ";
        }
        action() {
            "You toss the rope to the ground and then tie one end neatly to the base of the pole. ";
            moveInto(nil);
            tiedLocalRope.moveInto(footOfPole);
        }   
    }
    dobjFor(ClimbDown) {
        verify() { illogical ( 'Before you can climb up or down on the rope, you\'ll need to
                tie it to something. ' );
        }
    }
    dobjFor(LowerOver) {
        verify() {}
        check() {
            if (gIobj == cliffFromTop)
                "Before tossing the rope over the cliff, you should probably tie it to
                something. ";
            else
                "That makes very little sense. ";
            }
        }
;

//------------------------------------------------------------------------------
// nightingale:
//------------------------------------------------------------------------------

silverKey: Key, PDportable 'small silver key; polished ornate'
    "The key is small, silvery, nicely polished, and rather ornate in design. "
    hasAppeared = nil
    bulk = 1
    isPortable = true
    stillNeeded() { if (coin4.moved) return nil;
        return true; }
    dobjFor(Take) {
        action() {
            nightingaleKeyAch.awardPointsOnce();
            inherited;
        }
    }
    iobjFor(WindUpWith) {
        verify() {
            if (gDobj && (gDobj != nightingale))
                illogical ('The silver key can\'t be used for that. ');
        }
    }
    dobjFor(Turn) {
        verify() {}
        check() {
            if (!isIn(nightingaleKeyhole)) "You waggle the key back and forth in the air, accomplishing
                nothing. ";
        }
        action() { doInstead (WindUp, nightingale); }
    }
;

nightingale: PDportable 'silver nightingale; ornate exquisite (mechanical) clockwork; bird'
    desc () {
        "The workmanship of the silver nightingale is exquisite. Intricate swirls of silver,
        onyx, and jade texture its surface. It's perched on a short branch that rises
        from a base of the same materials. ";
        if (isHeldBy(me)) "You notice a small hole
        on the side of the base<<if (silverKey.isIn(nightingaleKeyhole))>>, into which a
            small silver key has been inserted<<end>>. ";
        if (singingCount > 0) "The nightingale is singing sweetly. ";
    }
    initSpecialDesc = "Nestled among the styrofoam peanuts is a silver nightingale. "
    bulk = 15
    stillNeeded() { if (coin4.moved) return nil;
        return true; }
    dobjFor(Take) {
        action() {
            nightingaleAch.awardPointsOnce();
            inherited;
        }
    }
    dobjFor(WindUp) {
        preCond = objHeld
        verify() {}
        check() {
            if (!silverKey.isIn(me) && !silverKey.isIn(nightingaleKeyhole))
                "You can see no obvious way to do that. ";
        }
        action() {
            local starting = (mynahBird.canSee(nightingale) && (singingCount == 0)); 
            "You turn the silver key in the little hole, and the clockwork nightingale";
            if (singingCount) " continues ";
            else " begins ";
            "to sing. ";
            if (starting)
                "The mynah bird hops close the side of the cage and
                stares at the silver nightingale, transfixed. ";
            singingCount = 5;
            if (!singDaemonID) {
                singDaemonID = new SenseDaemon(self, &singDaemon, 1);
            }
        }
    }
    dobjFor(WindUpWith) asDobjFor(WindUp)        
    singDaemonID = nil
    singDaemon () {
        singingCount--;
        if (me.canHear(nightingale)) {
            if (singingCount > 0) 
                "The silver nightingale trills <<one of>>a winsome<<or>>a limpid<<or>>a lovely<<or>>an exotic<<at random>>
            melody. <<if (mynahBird.canSee(nightingale))>>The mynah bird gazes
                <<one of>>intently<<or>>fixedly<<or>>longingly<<or>>enraptured<<at random>>
                at the nightingale. <<end>>";
            else
                "The silver nightingale falls silent. ";
        }
        if (singingCount == 0) {
            singDaemonID.removeEvent();
            singDaemonID = nil;
        }
    }
    singingCount = 0
    sing() {}
;

+ nightingaleParts: Component 'silver swirls; onyx jade intricate beautiful; texture workmanship branch'
    "The swirls of silver, onyx, and jade are stunningly beautiful. "
;

+ nightingaleBase: Component 'base; (nightingale) (of) round'
    "A round base below the nightingale provides stability. On
    the side of the base is a small hole. "
;

// the silverKey is used to wind up the nightingale

++ nightingaleKeyhole: Component, Container 'small round opening; (nightingale\'s) little; hole keyhole'
    "The small hole is in the base on which the silver nightingale perches. "
    disambigName = 'nightingale\'s keyhole'
    notifyInsert(obj) {
        if (obj != silverKey) {
            "{The subj obj} do{es}n't seem to fit into the little hole. ";
            exit;
        }
    }
;

//------------------------------------------------------------------------------
// scissors:
//------------------------------------------------------------------------------
    
scissors: PDportable 'pair of scissors; small sharp;; it them' @giftShop
    "The scissors are longer than nail scissors, but not as long as a pair destined for
    desktop use. Possibly they're intended for cutting ribbon. "
    initSpecialDesc = "Lying forgotten on the floor is a small pair of scissors. "
    stillNeeded() { if (tatteredCloak.isIn(nil)) return nil;
        return true; }
    iobjFor(CutUpWith) asIobjFor(CutWith)
    iobjFor(CutWith) {
        verify() { logical; }
        check() { 
            if (gDobj && (gDobj != tatteredCloak) && (gDobj != scarf))
                "Slicing apart {the dobj} would serve no useful purpose. ";
        }
    }
;

//------------------------------------------------------------------------------
// instructionalDVD:
//------------------------------------------------------------------------------

instructionalDVD: PDportable 'shiny plastic disc; (plastic) instructional shiny music; dvd disc disk'
    "<<changeName()>>On the the disc is printed the slogan, <q>This DVD provides absolutely
    complete lessons for playing any
    musical instrument! No hassle! Instantly play like a pro!</q> "
    changeName() {
        replaceVocab('instructional DVD; shiny (plastic) music; disc disk');
    }
    bulk = 6
;

//------------------------------------------------------------------------------
// goodallPhoto, etc:
//------------------------------------------------------------------------------

class SignedPhoto: PDportable, Fixture
    bulk = 10
    dobjFor(Take) {
        verify() { logical; }
        check() {
            if (!steve.friendly)
                "{The steve} says, <q>That's a <<one of>>dandy<<or>>swell<<or>>
                heck of a<<or>>fine<<or>>special<<at random>> photo, isn't it?
                You notice it's autographed.</q> ";
        }
        action() {
            isFixed = nil;
            wallBehindBar.photoTaken = true;
            inherited;
        }
    }
;

rushmorePhoto: SignedPhoto
    'autographed photo of Mount Rushmore; signed framed carved presidents; photograph stone Lincoln Roosevelt Washington Jefferson'
    @wallBehindBar
    "The framed photo of Mount Rushmore appears to have been personally autographed
    in felt-tip pen by all four of the presidents. "
    collectiveGroups = [photosGroup]
;

goodallPhoto: SignedPhoto 
    'autographed photo of Jane Goodall; signed framed intelligent compassionate; portrait photograph woman'
    @wallBehindBar
    "The framed photo of Jane Goodall has been autographed. In the photo, she's
    gazing at the camera quite seriously. The look in her eyes is intelligent and
    compassionate. "
    stillNeeded() {
        if (monkeys.amenable) return nil;
        return true;
    }
    // isFixed = nil
    dobjFor(Take) {
        verify() {}
        check() {
            if ((me.getOutermostRoom == scootersPub) && (!steve.friendly))
                "<q>That's a swell photo,</q> {the steve} says, <q>but it's part
                of my collection. I hate to break up the set without a good reason.</q> ";
        }
        action() {
            inherited;
            goodallAch.awardPointsOnce();
        }
    }
    bulk = 4
    collectiveGroups = [photosGroup]
;

welkPhoto: SignedPhoto
    'autographed photo of Lawrence Welk; signed framed band; photograph bandleader leader musician'
    @wallBehindBar
    "In the signed photo of Lawrence Welk, he's holding his conductor's baton and smiling in his
    usual ineffable manner. "
    collectiveGroups = [photosGroup]
;

homerPhoto: SignedPhoto
    'autographed photo of Homer Simpson; signed framed; photograph cartoon' @wallBehindBar
    "The framed photo is inscribed with the message <q>To Steve from Homer --- doh!</q> "
    collectiveGroups = [photosGroup]
;
godzillaPhoto: SignedPhoto
    'claw-printed photo of Godzilla; framed; photograph marks of[prep] claws footprint dinosaur monster'
    @wallBehindBar
    "The photo of Godzilla is not autographed, but it appears to have been personalized with quite a
    large black footprint, complete with the marks of claws. "
    collectiveGroups = [photosGroup]
;
einsteinPhoto: SignedPhoto
    'autographed photo of Albert Einstein; framed; photograph mathematician genius al physicist'
    @wallBehindBar
    "The photo of Albert Einstein is inscribed, <q>Steve, I see you're not square. ---Al.</q> "
    collectiveGroups = [photosGroup]
;

//------------------------------------------------------------------------------
// aslPill: this is in the wall box in the dentist's office
//------------------------------------------------------------------------------

aslPill: PDportable, Food 'small white pill; little featureless'
    "The pill is small, white, and featureless. "
    initSpecialDesc = "In the box by the door is a little white pill. "
    bulk = 1
    dobjFor(Eat) {
        verify() {}
        check() {}
        action() {
            moveInto(nil);
            me.knowsASL = true;
            "You pop the little white pill into your mouth and swallow. For
            just a moment your fingers seem to be tingling, as if they would
            like to do a little dance, but essentially you
            don't feel any different than before. ";
        }
    }
    dobjFor(Take) {
        verify() { logical; }
        action() {
            if (isDirectlyIn(gPlayerChar)) doInstead (Eat, self);
            else inherited;
        }
    }
;

//------------------------------------------------------------------------------
// stunGums:
//------------------------------------------------------------------------------

stunGums: PDportable 'vial of StunGums; green greenish glass clear; liquid' @longShelf
    "The clear glass vial is nearly full of a greenish liquid.
    It has a rubber stopper in the top, and there's a dimple in the center
    of the stopper. According to the label, the vial contains something called
    StunGums. "
    bulk = 4
    initSpecialDesc = "Lying on the long shelf is a glass vial. "
    stillNeeded() { if (buddhaClearing.seen) return nil;
        return true; }
    dobjFor(JabWith) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != featheredDart)) "{The subj iobj} {is} not something
                you can jab into the vial. ";
        }
        action() {
            doInstead (PutIn, featheredDart, dimple);
        }
    }
    dobjFor(Drink) {
        verify() {}
        check() {
            "The greenish liquid doesn't look like something you could
            imbibe safely. ";
        }
    }
    dobjFor(Open) {
        verify() {}
        check() {}
        action() {
            doInstead (Take, rubberStopper);
        }
    }
    iobjFor(PutIn) {
        verify() {}
        check() {
            if (gDobj != featheredDart)
            "To put anything in the vial, you'd have to remove the stopper. If it was
            something long and slim, though, like a toothpick, you could probably poke
            it into the dimple. ";
        }
        action() {
            doInstead (PutIn, gDobj, dimple);
        }
    }
;
+ stunGumsLabel: Component 'label'
    "The label identifies the substance within the vial as containing a quantity of StunGums (whatever
    that is). "
;

+ rubberStopper: Component 'rubber stopper'
    "The rubber stopper is wedged into the top of the glass vial. It's round, a
    quarter-inch or so in diameter, and has a tiny dimple --- a depressed spot --- in the center. "
    cannotTakeMsg = 'The stopper proves to be wedged quite firmly into the vial. It
        won\'t budge. '
    dobjFor(JabWith) {
        verify() { logical; }
        action() { doInstead (JabWith, stunGums, gIobj); }
    }
    iobjFor(PutIn) {
        verify() {}
        check() {}
        action() {
            doInstead (PutIn, gDobj, dimple);
        }
    }
;

++ dimple: Component 'tiny dimple; little'
    "The little dimple is in the center of the stopper. "
    iobjFor(PushTravelEnter) asIobjFor(PutIn)
    iobjFor(PutIn) {
        verify() {}
        check() {
            if (gDobj != featheredDart) "{The subj dobj} would hardly fit
                into the dimple in the stopper. ";
            else if (!featheredDart.harmless) "You've already coated the
                tip of the dart with StunGums. ";
        }
        action() {
            featheredDart.harmless = nil;
            "You jab the tip of the dart into the dimple and give the vial a
            shake. When you draw the dart out, its tip is coated with a green
            layer of StunGums. ";
        }
    }
    dobjFor(JabWith) {
        verify() { logical; }
        action() { doInstead (JabWith, stunGums, gIobj); }
    }
;

//------------------------------------------------------------------------------
// snookerBridge:
//------------------------------------------------------------------------------

snookerBridge: PDportable 'snooker bridge; flat very long; widget stick' @billiardRoom
    "The snooker bridge is a very long stick with a small tripod affixed to one end. "
    bulk = 45
    isGrabber = true
    stillNeeded() { if (aPizza.seen && aSixPack.seen) return nil;
        return true; }
    initSpecialDesc = "Leaning against the snooker table is a long stick with a flat
        widget at one end. A dim recollection of hanging out at a pool hall with an old
        boyfriend suggests that this is what's called a bridge. "
;

+ bridgeTripod: Component 'small tripod'
    "The small tripod is attached to the end of the stick. "
;

//------------------------------------------------------------------------------
// angelHarp:
//------------------------------------------------------------------------------

angelHarp: PDportable 'small gold harp; Gaelic painted gold-painted; strings lyre' @tuneTime
    "The harp is painted gold, and has about 25 strings. It's not a concert
    harp; it's small enough to carry. Possibly it's a Gaelic harp, or a lyre. "
    initSpecialDesc = "Standing almost in the shadow of the piano is a
        small but handsome gold-painted harp. "
    bulk = 100
    dobjFor(Take) {
        check() {
            if (isIn(stoneAngel)) "Taking the harp away from the angel? Surely you didn\'t
                mean it. ";
        }
    }
    dobjFor(Play) {
        preCond = objHeld
        verify() {}
        check() {}
        action() {
            "You strum a few of the harp's strings, producing a pleasant sound. ";
        }
    }
    dobjFor(Pick) asDobjFor(Play)
;

//------------------------------------------------------------------------------
// crystalBall:
//------------------------------------------------------------------------------

crystalBall: PDportable 'crystal ball'
    "The crystal ball is a bit smaller than a bowling ball, but bigger than a
    canteloupe. Its spherical surface is nicely polished. "
    bulk = 20
    dobjFor(LookIn) {
        verify() {}
        action() {
            "Reply hazy --- ask again later. ";
        }
    }
;

//------------------------------------------------------------------------------
// playingCards:
//------------------------------------------------------------------------------

playingCards: PDportable 'cards; playing brightly colored colorful tarot; pictures; them' @cardTable
    "They may not be ordinary playing cards. There are some brightly
    colored pictures on them. "
    bulk = 10
    plural = true
    dobjFor(LookUnder) {
        check() {
            if (al.location == cardTableBenches)
                "There seems to be a game in progress. Messing with the
                cards would probably earn you the ire of the card players. ";
        }
    }
    dobjFor(Feel) {
        check() {
            if (al.location == cardTableBenches)
                "There seems to be a game in progress. Messing with the
                cards would probably earn you the ire of the card players. ";
        }
    }
    dobjFor(Play) {
        verify() {}
        check() {
            if (al.location == cardTableBenches) "The card players look
                like a pretty serious bunch. They probably wouldn\'t
                want you sitting in on their game. ";
            else "You have more important things to do. ";
        }
    }
    dobjFor(Take) {
        check() {
            if (al.location == cardTableBenches)
                "The card players probably wouldn't like it if you tried
                to run off with their cards. ";
        }
        action() {
            cardsAch.awardPointsOnce();
            inherited;
        }
    }
    playersGone () {
        isFixed = nil;
        al.moveInto(nil);
        bob.moveInto(nil);
        chuck.moveInto(nil);
        playersGroup.moveInto(nil);
        viewOfCardPlayers.moveInto(nil);
        ironGateOutside.makeLocked(nil);
        cardGame.moveInto(nil);
    }
;

//------------------------------------------------------------------------------
// wineBox:
//------------------------------------------------------------------------------

wineBox: PDportable 'box of wine{qxqx}; cardboard cheap screw screw-top; (top) varietal rotgut' @grapeNotion
    "The screw-top cardboard wine box is labeled <q>Incredibly cheap varietal --- not suitable for
    human consumption!</q> A quick whiff of something shockingly pungent 
    suggests that whatever is in there, it may not be alcohol. "
    bulk = 30
    initSpecialDesc = "A cardboard box is strategically positioned in an obvious
        location in the center of the floor. "
    stillNeeded() { if (!isFull) return nil;
        return true; }
    isFull = true
    dobjFor(SmellSomething) {
        verify() {}
        action() {
            "Whew! What a stink! ";
        }
    }
    dobjFor(Drink) {
        verify() {}
        check() {
            "Even if it were a nice merlot, which it isn't, you never drink wine until
            after supper. ";
        }
    }
    iobjFor(PutIn) {
        verify() {}
        check() {
            if (isFull) "Whatever you put in the box would probably just dissolve. ";
            else "Whatever you put in the box would just wind up smelling bad. It's not
                really a suitable container. ";
        }
    }
    dobjFor(Search) {
        verify() {}
        check() {
            "There's nothing in it but the fluid. ";
        }
    }
    dobjFor(Pour) {
        verify() {}
        check() {
            "You'll need to say what you want to pour the wine (or whatever is
            masquerading as wine) into. ";
        }
    }
    dobjFor(Open) asDobjFor(Unscrew)
    dobjFor(Unscrew) {
        verify() {}
        check() {
            "No need for special handling --- if you want to pour what's in
            the box, just go ahead and pour it. ";
        }
    }
    dobjFor(PourInto) {
        verify() {}
        check() {
            if (!isFull) "You've already emptied the wine box. ";
            else if (gIobj && (gIobj != motor))
                "You'd only splash it around everywhere and make a mess. ";
            else if (!fuelCap.unscrewed)
                "You'll need to unscrew the fuel cap first. ";
        }
        action() {
            motor.fueled = true;
            isFull = nil;
            "Glug, glug, glug! You pour the cheap wine (or whatever it is) into the motor. ";
        }
    }
;

//------------------------------------------------------------------------------
// brassTelescope:
//------------------------------------------------------------------------------

modify Distant
    dobjFor(LookAtThrough) {
        verify() {}
        check() {
            if (gIobj != brassTelescope) "That makes very little sense. ";
        }
//        action() {
//            "It looks pretty much the same, only bigger. "; 
//        }
    }
;
    
brassTelescope: PDportable 'brass telescope; (tube) handsome; scope' @squareTable
    "The handsome brass telescope is small enough to pick up and carry around. It has an
    eyepiece. "
    bulk = 15
    stillNeeded() { if (tCheating.familiar) return nil;
        return true; }
    isTurnable = true
    isOptical = true
    makeCheatingFamiliar () {
        tCheating.familiar = true;
    }
    dobjFor(LookIn) asDobjFor(LookThrough)
    dobjFor(LookThrough) {
        preCond = [objHeld]
        verify() { logical; }
        check() {}
        action() {
            "Peering through the eyepiece, you see a blurred closeup of various things. ";
        }
    }
    iobjFor(LookAtThrough) {
        preCond = [objHeld]
        verify() { logical; }
        check() { 
            if (gDobj == playersGroup) "You\'re too close to them. You might learn something
                by looking at them through the telescope, but you\'ll need to be farther away. ";
        }
        action () {
            if (gDobj != viewOfCardPlayers)
                "<<if (gDobj.plural)>>They look<<else>>It looks<<end>> pretty much the same, only bigger. "; 
            
        }
    }
;

+ eyepiece: Component 'eyepiece; (telescope) small round; (scope)'
    "The eyepiece is small and round. It's mounted at the smaller end of the telescope. "
    isOptical = true
    dobjFor(LookThrough) {
        verify() { logical; }
        check() { }
        action () { doInstead (LookThrough, brassTelescope); }
    }
    iobjFor(LookAtThrough) {
        verify() { logical; }
        check() { }
        action() { doInstead (LookThrough, brassTelescope); }
    }
;

//------------------------------------------------------------------------------
// pteroChow:
//------------------------------------------------------------------------------

rawChow: Thing 'some pterodactyl chow; brown brownish meaty; odor chunks of[prep]'
    "The brownish chunks of pterodactyl chow have a meaty odor. "
    dobjFor(Take) {
        check() {
            "Your husband and children would find it not very palatable. ";
        }
    }
    dobjFor(Eat) {
        preCond = [touchObj]
        verify() { logical; }
        check() {
            "Seriously? Yuck! ";
        }
    }
;

pteroChow: PDportable 'sack of pterodactyl chow; red and white checked checkered; bag'
    @petShop
    "On the front of the big red-and-white checkered sack are the words
    <q>Purina Pterodactyl Chow.</q> <<if emptied>>The bag is rather flaccid,
    as you've emptied it. "
    bulk() { if (emptied) return 35; return 200; }
    opened = nil
    emptied = nil
    stillNeeded() { if (emptied) return nil;
        return true; }
    initSpecialDesc = "Lying in one corner is a big red sack containing, if the label is to be
        believed, pterodactyl chow. "
    dobjFor(PutIn) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != redWagon) && (gIobj != trough))
                "You can't put the sack of pterodactyl chow there. ";
        }
    }
    dobjFor(Take) {
        check() {
            if (!emptied && (!redWagon.isIn(gPlayerChar.getOutermostRoom())))
            "You grip the bag of pterodactyl chow firmly and try to lift it, but after staggering
            a couple of steps, you put it down again. It's just too darn heavy to carry around. ";
            else if (isIn(redWagon))
                "It's too heavy to carry it around. ";
        }
        action() {
            if (redWagon.isIn(gPlayerChar.getOutermostRoom())) {
                moveInto(redWagon);
                moved = true;
                "You heft the sack into the wagon. ";
            }
            // else the sack is emptied:
            else inherited();
        }
    }
    dobjFor(Saw) asDobjFor(Cut)
    dobjFor(SawOpen) asDobjFor(Cut)
    dobjFor(Cut) {
        verify() {}
        check() {
            if (opened) "You've already opened the sack. ";
            // Somebody might try using the saw....
            else if ((!scissors.isDirectlyIn(gPlayerChar)) && (!shortSaw.isDirectlyIn(gPlayerChar)))
                "You don't seem to have a suitable cutting implement. ";
        }
        action() {
            opened = true;
            local whichCutter = nil;
            // We're going to assume that one or the other must be handy, because otherwise
            // the check() routine would have bounced the action.
            if (scissors.isDirectlyIn(gPlayerChar)) whichCutter = scissors;
            else whichCutter = shortSaw;
            "You deftly <<if (whichCutter == scissors)>>slice<<else>>rip<<end>> open the sack of pterodactyl chow. ";
        }
    }
    dobjFor(CutWith) {
        verify() {}
        check() {
            if (opened) "You've already opened the sack. ";
            else if (gIobj && gIobj != scissors)
                "{The iobj} may not be a suitable cutting instrument. ";
        }
        action () {
            opened = true;
            "You deftly slice open the sack of pterodactyl chow. ";
        }
    }
    dobjFor(Open) {
        preCond = [touchObj]
        verify() { logical; }
        check() {
            if (opened) "You've already opened the sack of pterodactyl chow. ";
        }
        action() {
            opened = true;
            "The paper of the bag is so stiff that after wrestling with it for a minute,
            you resort to using your teeth. This works nicely; you're able to rip a small
            opening at one corner of the top of the bag. A rich meaty smell envelops you. ";
        }
    }
    dobjFor (Pour) {
        preCond = [touchObj]
        verify() { logical; }
        check() {
            if (emptied) "You have already disposed of the contents of the bag. ";
            else if (!opened) "If you want to pour out any of the pterodactyl chow, you'll
                have to open the sack first. ";
            else if (gPlayerChar.getOutermostRoom() != ledgeSouth) "Scattering pterodactyl
                chow around at random is not likely to be a useful activity. ";
        }
        action () {
            replaceVocab ('empty (pterodactyl) (chow) bag; red and white checked checkered');
            emptied = true;
            bulk = 45;
            moveInto(gPlayerChar);
            "You heft the bag and dump its contents into the trough. ";
            rawChow.moveInto(trough);
            zarbolphung.appear();
        }
    }
    dobjFor (PourInto) {
        preCond = [touchObj]
        verify() { logical; }
        check() {
            if (!opened) "If you want to pour out any of the pterodactyl chow, you'll
                have to open the sack first. ";
            else if (gPlayerChar.getOutermostRoom() != ledgeSouth) "Scattering pterodactyl
                chow around at random is not likely to be a useful activity. ";
            else if (gIobj != trough) "Possibly the trough would be a better place to
                pour it. ";
        }
        action() {
            nestedAction(Pour, self);
        }
    }
;

//------------------------------------------------------------------------------
// featheredDart:
//------------------------------------------------------------------------------

featheredDart: PDportable 'feathered dart; brass; tip vanes' @dartBoard
    "The dart has a sharp-looking brass tip<<if !harmless>>, which is
    coated with a green layer of sticky-looking liquid,<<end>> and three feathered vanes in the other
    end. "
    initSpecialDesc = "Stuck near the center of the dartboard is a feathered dart. "
    bulk = 5
    harmless = true
    iobjFor(JabWith) {
        verify() {}
        check() {
            if (gDobj == gPlayerChar) "That would be painful. ";
        }
    }
    dobjFor(PushTravelEnter) asDobjFor(JabInto)
    dobjFor(JabInto) {
        verify() {}
        check() {
            if ((gIobj == stunGums) || (gIobj == rubberStopper) || (gIobj == dimple)) return;
            else if (gIobj == gPlayerChar) "That would surely be painful. ";
            else if (gIobj.ofKind(Actor)) "That wouldn't be a friendly thing to do. ";
            else "Poking random holes in random objects isn't likely to be very useful. ";
        }
    }
    dobjFor(ThrowAt) {
        verify() { logical; }
        check() {
            if (gIobj && 
                (gIobj != ocelot) && (gIobj != dartBoard)) "That wouldn't be a very friendly thing
                to do. ";
        }
        action() {
            // if the iobj is the dartboard, the dartboard will handle the action:
            if (gIobj == ocelot) {
                moveInto (nil);
                "You throw the dart at the ocelot, but miss. It disappears into the
                jungle undergrowth, where you'll never be able to find it again. This
                is not a good thing. ";
            }
            else return;
        }
    }
    // For the BlowAt action, we're not going to insist that you aim the blowgun first:
    dobjFor(BlowAt) {
        verify() {}
        check() {
            if (!isIn(blowgun)) "Your unaided breath is not sufficient to have
                any effect. You need a blowgun. ";
            else if (gIobj && (gIobj != ocelot))
                "{The subj iobj} {is} not a suitable target for that action. ";            
        }
        action() {
            "You put one end of the tube in your mouth, aim the other end at the ocelot,
            and give a mighty puff. ";
            moveInto(nil);
            if (harmless) {
                "The dart flies out and finds its mark. but the ocelot barely
                notices the tiny pinprick. It brushes off the
                dart and goes back to snarling at you. Oh, dear --- where are you
                ever going to find another dart? ";
            }
            else {
                ocelot.makeFriendly();
                "The point of the dart sinks deep into
                the ocelot's shoulder. The animal shakes itself (thereby flinging the
                dart away into the ferns), blinks in a confused way, and then lies
                down on its back, purring. ";
            }   
        }
    }
;

//------------------------------------------------------------------------------
// a stepladder:
//------------------------------------------------------------------------------

stepladder: StairwayUp 'aluminum stepladder; step; ladder' @lightFantastic
    "The aluminum stepladder is about eight feet long. It can be folded and unfolded. Carrying
    it when it's unfolded would be awkward, and climbing it when it's folded would be
    impossible. "
    // bulk = 1001 // Thus it won't fit in the shopping bag.
    initSpecialDesc = "A handy aluminum stepladder stands near one of the displays. "
    bulk = 150
    stillNeeded() { if (stackOfBoxes.shovedAside) return nil;
        return true; }
    isFixed = nil
    unfolded = nil
    dobjFor(Take) {
        check() {
            if (unfolded) "Carrying the stepladder while it's unfolded would be
                very awkward. ";
        }
    }
    dobjFor(Close) asDobjFor(Fold)
    dobjFor(Fold) {
        check () {
            if (!unfolded) "The ladder is already folded. ";
        }
        action () {
            unfolded = nil;
            "You fold up the stepladder. ";
        }
    }
    dobjFor(Open) asDobjFor(Unfold)
    dobjFor(Unfold) {
        check() {
            if (!isDirectlyIn(getOutermostRoom())) "Before unfolding the stepladder,
                you'll need to set it down. ";
            }
        action() {
            unfolded = true;
            "You unfold the ladder and set it upright in conventional climbing position. ";
        }
    }
    // Note: ClimbUp is defined in the library asDobjFor(Climb)
    dobjFor(Climb) {
        check() {
            if (!unfolded) "Before climbing the ladder, you'll need to unfold it. ";
            else if (me.getOutermostRoom == northOfTower) "Even at the top of the
                ladder, you find that the balcony remains out of reach. Oh, well --- nothing
                ventured, nothing gained. You climb back down. ";
            else if (me.getOutermostRoom != restroom)
                "You take a couple of steps up the ladder to enjoy the view, and
                then climb back down. ";
        }
        action() {
            "By climbing to the top of the ladder and maneuvering yourself carefully over
            the lip of the hole, you're able to reach the upper room.\b ";
            me.moveInto(damagedHallwayNorth);
            damagedHallwayNorth.lookAroundWithin();
        }
    }
;

//------------------------------------------------------------------------------
// In the wardrobeCloset:
//------------------------------------------------------------------------------

// These start out as hiddenIn, so they won't clutter up the room description when
// the door slams.

scarf: Wearable, PDportable, RestrictedContainer 'scarf; large flowing'
    "The scarf is vividly patterned in wide alternating stripes of orange, green, red, and violet. "
    findHiddenDest = wardrobeShelf
    allowedContents = [flyingPixies]
    containsPixies = nil
    stillNeeded() { if (flyingPixies.groggy) return nil;
        return true; }
    dobjFor(Spray) {
        verify() {}
        check() {
            "If you want to spray something onto the scarf, you\'ll need to be specific. ";
        }
    }
    iobjFor(SprayOn) asIobjFor(PourOnto)
    iobjFor(PourOnto) {
        verify() {}
        check() {
            if (gDobj != eauBottle) "Pouring random liquids onto the scarf would only ruin it. ";
            else if (!containsPixies) "At the moment there\'s probably no reason to do that. ";
        }
        action() {
            doInstead(PourOnto, eauBottle, flyingPixies);
        }
    }
    iobjFor(CatchWith) {
        verify() {}
        check() {
            if (gDobj == invisiblePixies)
                "Not a bad idea, but you'll need to be able to see them. ";
            else if (gDobj != flyingPixies)
                "Trying that would probably rip holes in the scarf. ";
            else if (flyingPixies.isIn(self))
                     "You've already caught them. ";
        }
    }
    dobjFor(PutIn) {
        action() {
            if (containsPixies) {
                "Oh, no! When you try that, the pixies escape! ";
                containsPixies = nil;
                if (cigar.isLit)
                    flyingPixies.moveInto(joe);
                else
                    invisiblePixies.moveInto(joe);
            }
            else inherited;
        }
    }
    dobjFor(Throw) asDobjFor(Drop)
    dobjFor(Drop) {
        action() {
            if (containsPixies) {
                "Oh, no! When you try that, the pixies escape! ";
                containsPixies = nil;
                flyingPixies.moveInto(joe);
            }
            else inherited;
        }
    }
    // Because you might try to give the marionettes cloaks made of the scarf:
    dobjFor(CutUp) asDobjFor(Cut)
    dobjFor(Cut) {
        verify() {}
        check() {
            "The scarf is too pretty for you to want to cut it into pieces. ";
            if ((me.getOutermostRoom() == secludedLair) && (lairMarionettes.curState == marionettesCircling))
                "Maybe you could find something that's already in poor condition and cut it
                up instead. ";
        }
    }
    dobjFor(CutUpWith) asDobjFor(CutWith)
    dobjFor(CutWith) {
        verify() { logical; }
        check() {
            "The scarf is too pretty for you to want to cut it into pieces. ";
            if ((me.getOutermostRoom() == secludedLair) && (lairMarionettes.curState == marionettesCircling))
                "Maybe you could find something that's already in poor condition and cut it
                up instead. ";
        }
    }
;

pairOfShoes: Wearable, PDportable
    'pair of shoes; work large sturdy men\'s thick-soled; boot boots shoe; it them'
    //@wardrobeCloset
    "The shoes are not new, but they seem to be in good condition. They're men's
    work boots, and rather large and thick-soled. "
    bulk = 25
    initSpecialDesc = "Also tucked away on the shelf is a large pair of work boots. "
    findHiddenDest = wardrobeShelf
    // Trying to avoid the situation where you drop a bunch of stuff at the
    // foot of the pole and later want to take it all:
    hideFromAll(action) {
        if ((action == Take) && (isIn(joe))) return true;
        else return inherited(action);
    }
    dobjFor(Wear) {
        verify() {}
        check() {
            "You're already well shod. ";
        }
    }
;

carmenMirandaHat: Wearable, PDportable
    'elaborate ladies\' hat; carmen miranda extraordinary; confection flowers fruit'
    @wardrobeCloset
    "The ladies' hat is an extraordinary confection of flowers and fruit. Carmen
    Miranda would have been proud to wear it! <<if (!hatpin.found)>>Protruding
    from the hat is a hatpin. <<end>>"
    // findHiddenDest = wardrobeShelf
    initSpecialDesc = "Lying on the floor is an elaborate hat adorned with fruit. "
    hiddenIn = [hatpin]
    dobjFor(Take) {
        check() { "It's not really your style. "; }
    }
    dobjFor(Examine) {
        action() {
            inherited;
            if(hiddenIn.length > 0) {               
                moveHidden(&hiddenIn, self);
                hatpin.found = true;
            }
        }
    }
;

//------------------------------------------------------------------------------
// The hatpin:
//------------------------------------------------------------------------------

hatpin: PDportable 'hatpin; shiny sharp; pin'
    "<<find>>The hatpin is several inches long. It's a shiny metal pin. "
    find() { found = true; }
    found = nil
    iobjFor(UnlockWith) {
        verify() {}
    }
    bulk = 2
;

//------------------------------------------------------------------------------
// A simple leather belt:
//------------------------------------------------------------------------------

leatherBelt: PDportable 'leather belt; long supple; strap' @asteroidBelt
    "The leather belt is <<if attached>>firmly attached to the gear assembly
    <<else>>long and supple<<end>>. "
    bulk = 10
    attached = nil
    isWearable = true
    initSpecialDesc = "A long supple leather belt lies coiled on the floor. "
    dobjFor(Wear) {
        verify() {}
        check() {
            "You're already wearing a belt (a fact that is of absolutely no
            importance, except that it keeps your pants from falling down). You
            don't need to wear another belt. ";
        }
    }
    dobjFor(AttachTo) asDobjFor(PutOn)
    dobjFor(TieTo) asDobjFor(PutOn)
    dobjFor(PutOn) {
        verify() {}
        check() {
            // We'll let you put it either on the gears or on the motor:
            if (gIobj && (gIobj != gearAssembly) && (gIobj != motor))
                "The belt doesn't seem to fit securely. Maybe it would fit
                better someplace else. ";
        }
    }
    dobjFor(Take) {
        verify() {}
        check() {
            inherited;
            if (attached) "The belt is firmly attached to the gear assembly. ";
        }
    }
;

//----------------------------------------------------------------------
// the eau d'ennui:
//----------------------------------------------------------------------

eauBottle: PDportable 'big square perfume bottle; ennui eau boring; d\'ennui' @glassCabinet
    "The bland gray label on the big square bottle identifies it as <q>l'eau d'ennui.</q>
    You remember just enough of your high-school French to know that that means <q>water of
    boredom.</q> "
    bulk = 12
    stillNeeded() { if (flyingPixies.groggy) return nil;
        return true; }
    smellDesc = "You remove the stopper and hold the neck of the bottle up to your nose
        for a quick sniff. Wow!
        This is the most boring perfume you have ever encountered. You replace the stopper
        hurriedly and shake your head to clear it. This stuff is so boring it would put an ox
        to sleep! "
    dobjFor(Spray) {
        verify() {}
        check() {
            "If you want to spray the perfume somewhere, you\'ll need to be specific. ";
        }
    }
    dobjFor(SprayOn) asDobjFor(PourOnto)
    dobjFor(PourOnto) {
        verify() {}
        check() {
            if (gIobj && (gIobj != flyingPixies) && (gIobj != scarf))
                "Don't you think this game is boring enough already, without you go splooshing
                such a boring fragrance all over everywhere? ";
        }
        // We'll let the pixies take care of the action.
    }
;

//------------------------------------------------------------------------------
// Sir Ralph's book, together with the actions that it needs:
//------------------------------------------------------------------------------

travelGuide: Consultable, PDportable
    'fat leather-bound book; (my) leather bound well-thumbed well thumbed thick travel; guide travels'
    // starts out nowhere, as we'll move it onto the table in the ghost's first speech
    "The leather-bound book is a couple of inches thick, and well-thumbed. On the cover,
    in ornate gold-embossed letters, it says, <b>My Travels by Sir Ralph Warburton-Leaming.</b>"
    readDesc = "The book is far too thick to read from cover to cover. "
    bulk = 22
    stillNeeded() { if (monkeys.talented) return nil;
        return true; }
    isPortable = true
    familiar = true
    dobjFor(Feel) {
        verify() {}
        check() {
            if ((ralph.curState == ralphHovering) && (gPlayerChar.getOutermostRoom() == stockRoom))
            {
                "You reach for the book, but {the ralph} snatches it away from your
                outstretched fingers. <q>Ah, my book. Yes, I'm sure you're keen to
                learn about my travels.</q> ";
            }
        }
    }
    dobjFor(Take) {
        check() {
            if ((ralph.curState == ralphHovering) && (gPlayerChar.getOutermostRoom() == stockRoom))
            {
                "You reach for the book, but {the ralph} snatches it away from your
                outstretched fingers. <q>Ah, my book. Yes, I'm sure you're keen to
                learn about my travels.</q> ";
            }
        }
    }
    dobjFor(LookIn) asDobjFor(Read)
    dobjFor(Open) asDobjFor(Read)
    dobjFor(Read) {
        // preCond = [objVisible, objHeld]
        verify() {}
        check() {
            if (!isDirectlyIn(me)) "You'll need to be holding the book in order to
                read it. ";
        }
        action() {
            "Leafing through the book at random, you're
            <<one of>>mesmerized<<or>>fascinated<<or>>enthralled<<or>>amused<<or>>entertained<<at random>> by ";
            readRandomStuff();
            ". ";
        }
    }
    dobjFor(ConsultAbout) {
        preCond = [objVisible, objHeld]
    }
    getBareNum (str) {
        local pat = '<digit>+';
        local result = rexSearch(pat, str);
        if (result == nil) return nil;
        else {
            local x = new BigNumber(str);
            return x;
        }
    }
    getPageNum (str) {
        local words = str.split(' ');
        local inner_str;
        // This test seems to be needed because the nature of gLiteral is
        // somewhat unknown or ambiguous following 'x page 19':
        if (words.length() > 1)
            inner_str = words[2];
        else
            inner_str = words[1];
        local pat = '<digit>+';
        local result = rexSearch(pat, inner_str);
        if (result == nil) {
            // "Oops -- not a number. ";
            return nil;
        }
        else {
            local x = new BigNumber(inner_str);
            return x;
        }
    }
    dobjFor(ReadLiteralIn) {
        preCond = [objVisible, objHeld]
        check() {
            local str = gLiteral;
            local x;
            // At this point, gLiteral may start with 'page ', or it may simply be a number
            // (or something completely different)...
            if (str.toLower.startsWith('page '))
                x = getPageNum(str);
            else
                x = getBareNum(str);
            
            if (x == nil) "The pages are numbered. ";
            else if (x > 600) "Flipping clear to the back, you find that the book has only
                600 pages. ";
            else if (x < 1) "The book is certainly thicker than that. ";
            else if (x != 327) 
                "The description of Sir Ralph's travels is fascinating, especially
                <<readRandomStuff()>>, but you're unable
                to find any information that seems immediately useful. "; 
            // if none of those conditions obtains, we'll fall through to the action() code...
        }
        action() {
           "Aha! On page 327, you find a brief description of Sir Ralph's expedition
           to the jungle area where he encountered the ocelot and the statue of a seated
           Buddha. Apparently he never did find what he was looking for --- a tribe
           of surprisingly talented monkeys. He traveled
           using a teleportation device, which he set to the coordinates 563912. At
           the bottom of the page, in a footnote, he adds, <q>To return, of course, I
           used the standard code 100000.</q> ";
        }
    }
    readRandomStuff() {
        "<<one of>>the description of hand blossom pollination among the natives of
        Tahiti<<or>>the complete and lucid explanation of the quantum theory of
        gravity<<or>>the doodled rabbit cartoons in the margin<<or>>the recipes
        for braised rattlesnake<<or>>the aerial photographs of the glaciers in
        Greenland<<or>>the explanation of how to make snowshoes out of rubber bands
        and pencil shavings<<or>>the detailed analysis of the mating habits of
        shellfish<<or>>the closeup photos of the tattoos of the natives of the
        Faroe Islands<<at random>>";
    }
;

+ DefaultConsultTopic
    "Leafing through the book, you're unable to find anything relevant. "
;

//------------------------------------------------------------------------------
// stuff in the Beauty Parlor:
//------------------------------------------------------------------------------

nailPolishBottle: PDportable
    'bottle of nail polish; clear fast-drying fast drying' @beautyParlorCounter
    "The bottle of clear nail polish is rather large, and nearly full. The
    label on the bottle says it's a special fast-drying variety. "
    initSpecialDesc = "On the counter is a large bottle of clear nail polish. "
    bulk = 5
    stillNeeded() { if (monitorControlPanel.frozen) return nil;
        return true; }
    sightSize = small
    dobjFor(Pour) {
        verify() {}
        check() {
            "You'll need to say where you want to pour it. ";
        }
    }
    dobjFor(PourInto) asDobjFor(PourOnto)
    dobjFor(PourOnto) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != monitorControlPanel))
                "That would only make a mess -- and as it's fast-drying nail polish
                and you don't have any nail polish remover, the mess would become
                a permanent fixture. ";
        }
    }
        // We'll let the control panel handle its own stuckness.
;

//------------------------------------------------------------------------------
// the knitting needle:
//------------------------------------------------------------------------------

knittingNeedle: PDportable 'knitting needle; hefty red'
    "The knitting needle is rather hefty, and more than 30cm long (that's a
    foot, for you Americans). "
    bulk = 20
    stillNeeded() { if (!blowgun.isClogged && putter.moved) return nil;
        return true; }
    iobjFor(JabWith) {
        verify() { logical; }
        check() {
            "It's not clear what that would accomplish. ";
        }
    }
    iobjFor(CleanWith) {
        verify() { logical; }
        check() {
            if (gDobj != blowgun) "The knitting needle isn't likely to be
                useful for doing that. ";
        }
    }
    dobjFor(PutIn) {
        action() {
            if (gIobj == narrowGap) doInstead (SlideInto, self, narrowGap);
            else inherited;
        }
    }   
    dobjFor(SlideInto) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != narrowGap))
                "Sliding the knitting needle into {the iobj} might be possible, but
                it\'s not likely to prove useful. ";
            else if ((gIobj == narrowGap) && (!putter.isIn(narrowGap)))
                "There's no reason to keep doing that. ";
        }
        action () {
            putter.moveInto(byTheKiosk);
            "Inch by inch, you nudge the putter toward the gap. At last it slides out! ";
        }
    }
    iobjFor(GetWith) {
        verify() {}
        check() {
            if (gDobj != putter) "The knitting needle is not an ideal implement with which to
                pick things up. ";
            else if (!putter.isIn(narrowGap))
                "You've already retrieved the putter. ";
        }
        action () {
            putter.moveInto(byTheKiosk);
            "Inch by inch, you nudge the putter toward the gap using the knitting
            needle. At last it slides out! ";
        }
    }
;

//------------------------------------------------------------------------------
// the saw:
//------------------------------------------------------------------------------

shortSaw: PDportable 'short-bladed saw; short bladed narrow; blade' @sportingGoods
    "The blade of the saw is narrow and only a few inches long. "
    initSpecialDesc = "Lying on the floor is a short-bladed saw. "
    bulk = 30
    stillNeeded() { if (!mummyToe.isIn(mummy)) return nil;
        return true; }
    iobjFor(CutWith) {
        verify() {}
        check() {
            if ((gDobj != pteroChow) && (gDobj != mummyToe))
                "The saw doesn't seem to be the right implement for that. ";
        }
    }
;

//------------------------------------------------------------------------------
// the mallet:
//------------------------------------------------------------------------------

// The point of the mallet is that you can tumble into the crypt from the
// mirror maze, in which case we have no idea what you'll be carrying. The
// mallet is guaranteed to let you exit through the metal door.

mallet: PDportable 'hefty mallet; heavy sturdy; head handle hammer' @mineshaft
    "The mallet has a heavy head and a sturdy handle. "
    initSpecialDesc = "Lying near one wall is a mallet. "
    bulk = 30
    stillNeeded() { if (sarcophagus.remapIn.isOpen && rustyDoorInterior.isOpen) return nil;
        return true; }
    iobjFor(AttackWith) {
        verify() { logical; }
        check() {
            if ((gDobj != hinges) && (gDobj != sarcophagusLid)
                && (gDobj != sarcophagus) && (gDobj != rustyDoorInterior))
                "Flailing about with the mallet is likely to do more harm than good. ";
        }
    }
;

//------------------------------------------------------------------------------
// the blowgun:
//------------------------------------------------------------------------------

// This will probably need to be deployed in some sort of umbrella holder or something.

blowgun: PDportable, RestrictedContainer 'long piece of bamboo; hollow; tube pole stick blowgun' @greenFriends
    "The slim piece of bamboo is longer than your arm. It's very straight,
    light-weight, and cleanly trimmed at the ends. A cursory inspection of one
    end suggests that it's hollow.<<if (!isClogged)>> Looking down the interior
    of the tube, you find it's quite smooth on the inside.<<end>> "
    initSpecialDesc = "Leaning against one wall is a long piece of bamboo. "
    bulk = 30
    stillNeeded() { if (buddhaClearing.seen) return nil;
        return true; }
    allowedContents = [knittingNeedle, featheredDart]
    isClogged = true
    dobjFor(Cut) asDobjFor(Break)
    dobjFor(CutWith) asDobjFor(Break)
    dobjFor(Break) {
        verify() {}
        check() {
            "It wouldn't be much use if you broke it into sections. ";
        }
    }
    dobjFor(SlideInto) {
        verify() { logical; }
        check() {
            if (gIobj && (gIobj != narrowGap))
                "Sliding the bamboo pole into {the iobj} might be possible, but
                it\'s not likely to prove useful. ";
            else if ((gIobj == narrowGap) && (!putter.isIn(narrowGap)))
                "There's no reason to keep doing that. ";
        }
        action () {
            putter.moveInto(byTheKiosk);
            "Inch by inch, you nudge the putter toward the gap. At last it slides out! ";
        }
    }
    iobjFor(GetWith) {
        verify() {}
        check() {
            if (gDobj != putter) "The bamboo pole is not an ideal implement with which to
                pick things up. ";
            else if (!putter.isIn(narrowGap))
                "You've already retrieved the putter. ";
        }
        action () {
            putter.moveInto(byTheKiosk);
            "Inch by inch, you nudge the putter toward the gap using the long
            slim piece of bamboo. At last it slides out! ";
        }
    }
    dobjFor(LookIn) {
        verify() { logical; }
        check() {
            if (isClogged) "You can't see anything in the tube. It appears
                to be clogged. ";
        }
        action() {
            if (featheredDart.isIn(blowgun)) "The feathered dart is in the tube. ";
            else "The interior of the tube is smoothly polished. ";
        }
    }
    dobjFor(Empty) {
        verify() {}
        check() {
            if (!isClogged) "You've already cleaned it out. ";
            else "Whatever is clogging it up is in too deep to be reached. Possibly
                if you had something else that was long and thin, you could use it to
                dislodge whatever is in there. ";
        }
    }
    dobjFor(Unplug) asDobjFor(Clean)
    dobjFor(Clean) {
        verify() { logical; }
        check() {
            if (!isClogged) "You've already cleaned it out. ";
            else {
                if (!knittingNeedle.isDirectlyIn(gPlayerChar))
                "It's not entirely clear how you would accomplish that. ";
            }
        }
        action () {
            doInstead (PutIn, knittingNeedle, self);
        }
    }
    dobjFor(CleanWith) {
        verify() { logical; }
        check() { 
            if (!isClogged) "You've already cleaned out the tube. ";
            if (gIobj && gIobj != knittingNeedle) "You can't clean the tube with {that iobj}. ";
        }
        action () { doInstead (PutIn, knittingNeedle, self);
        }
    }
    notifyInsert(obj) {
        if ((obj == featheredDart) && isClogged) {
            "Before you do that, you'll probably want to clean out the interior of
            the tube. ";
            exit;
        }
        else if (obj == featheredDart) return;
        else if (!isClogged) {
            "You've already cleaned out the tube. ";
            exit;
        }
        else if (obj == knittingNeedle) {
            "By ramming the knitting needle into the tube a few times, you're
            able to dislodge the clog. Now the tube is clear. ";
            isClogged = nil;
            exit;
        }
        else inherited(obj);
    }
    okayAimedAt = nil
    // For the BlowAt action, we're not going to insist that you aim it first:
    dobjFor(BlowAt) {
        preCond = [objHeld]
        verify() {}
        check() {
            if (isClogged) "You huff and puff into the tube, but it seems to be
                clogged. ";
            else if (!featheredDart.isIn(self)) "A jet of air streams out of the far
                end of the blowgun, accomplishing nothing. ";
            else if (gIobj && (gIobj != ocelot))
                "{The subj iobj} {is} not a suitable target for that action. ";            
        }
        action() {
            "You put one end of the tube in your mouth, aim the other end at the ocelot,
            and give a mighty puff. ";
            featheredDart.moveInto(nil);
            if (featheredDart.harmless) {
                "The dart flies out and finds its mark. but the ocelot barely
                notices the tiny pinprick. It brushes off the
                dart and goes back to snarling at you. Oh, dear --- where are you
                ever going to find another dart? ";
            }
            else {
                ocelot.makeFriendly();
                "The point of the dart sinks deep into
                the ocelot's shoulder. The animal shakes itself (thereby flinging the
                dart away into the ferns), blinks in a confused way, and then lies
                down on its back, purring. ";
            }   
        }
    }
    dobjFor(AimAt) {
        verify() {}
        check() {
            if (gIobj && (gIobj != ocelot))
                "Pointing the bamboo tube at {the iobj} is not going to impress anybody. ";
            else if (!ocelot.isHostile) "You've already taken care of the ocelot. ";
        }
        action() {
            okayAimedAt = true;
            "You point the bamboo tube at the ocelot. ";
        }
    }
    dobjFor(BlowInto) {
        verify() { logical; }
        check() {
            if (isClogged) "Your cheeks puff out, but no air goes down the
                tube. Possibly it's clogged. ";
            else if (!featheredDart.isIn(blowgun)) "You blow into the tube,
                causing a whistling sound. ";
            else if (gPlayerChar.location != jungleClearing) "The dart would
                only fly out the other end, and then you'd never be able to
                find it. ";
            // At this point we know you're in the jungle clearing and the
            // dart is in the blowgun, and since the dart is there, we know
            // the ocelot is still hostile, but we don't actually know that
            // the player has tried to go south, causing the ocelot to show
            // up. Also, we don't know if the dart has stunGums on it!
            else if (ocelot.location != jungleClearing) "It's not entirely
                clear why you would want to do that. ";
            else if (!okayAimedAt) "Perhaps you ought to point it at something
                before you try that. ";
        }
        action() {
            "You put one end of the tube in your mouth, aim the other end at the ocelot,
            and give a mighty puff. ";
            featheredDart.moveInto(nil);
            if (featheredDart.harmless) {
                "The dart flies out and finds its mark. but the ocelot barely
                notices the tiny pinprick. It brushes off the
                dart and goes back to snarling at you. Oh, dear --- where are you
                ever going to find another dart? ";
            }
            else {
                ocelot.makeFriendly();
                "The point of the dart sinks deep into
                the ocelot's shoulder. The animal shakes itself (thereby flinging the
                dart away into the ferns), blinks in a confused way, and then lies
                down on its back, purring. ";
            }   
        }
    }
;

//------------------------------------------------------------------------------
// the round key:
//------------------------------------------------------------------------------

// The roundKey will appear when Bianca knocks over the bowl of combs as she flees.
// It opens the glassCabinet.

roundKey: Key, PDportable 'shiny round key; (little) (small); cylinder barrel'
    "The barrel of the key is round, like a small cylinder. "
    bulk = 3
    isPortable = true
    stillNeeded() { if (eauBottle.moved) return nil;
        return true; }
    actualLockList = [glassCabinet]
    initSpecialDesc = "Lying among the scattered combs is a shiny little key with
        a round barrel instead of a row of teeth. "
    iobjFor(UnlockWith) {
        verify() {}
        check() {
            if (gDobj != glassCabinet) "The shiny round key doesn\'t seem to be able to do that. ";
        }
        // We'll let the cabinet do the unlocking.
    }
;

//------------------------------------------------------------------------------
// the prom dress:
//------------------------------------------------------------------------------

promDress: Dress 'prom dress; beautiful blue teal satin; gown'
    "It's the one, all right! Teal satin, with spaghetti straps and a white tulle ruffle at the
    hem. "
    bulk = 20
    aName = 'the ' + name
;
+ spaghettiStraps: Component 'spaghetti straps; thin;; them'
    "The straps (two on each side) look pretty much the way you'd expect spaghetti straps to look. "
;
+ whiteRuffle: Component 'white tulle ruffle;; hem'
    "The ruffle runs around the hem of the dress. "
;
+ skirt: Component 'full skirt; three-quarter'
    "The full skirt of the prom dress is three-quarter length. "
;
+ bodice: Component 'low-cut bodice'
    "The bodice is a little lower-cut than you'd really like to see your teenage daughter wearing,
    but it's not <i>too</i> daring. "
;

//-----------------------------------------------------------------------------
// the other dresses:
//-----------------------------------------------------------------------------

class Dress: Wearable
    dobjFor(Wear) {
        check() {
            "You're already dressed. Anyway, the style wouldn't suit you. ";
        }
    }
;
straplessDress: Dress 'strapless gown; beautiful small light green; dress'
    "The strapless gown is a beautiful light green. Not the right style, not the right
    color, and on peering at the tag you see it's also a size 4 --- too small for Sam.
    A beige plastic anti-shoplifting tag is firmly attached to the hem. "
    bulk = 150
;
+ straplessTag: ShopliftingTag '+; (green) (strapless)';

bouffantDress: Dress 'bouffant gown; beautiful large red; dress'
    "The red gown has bouffant sleeves and a full skirt supported by stiff petticoats.
    Very attractive, in a sort of old-fashioned way, but it's the wrong style, the wrong
    color, and on glancing at the tag you see it's a size 12 --- too large for Sam. 
    A beige plastic anti-shoplifting tag is firmly attached to the hem. "
    bulk = 150
;
+ bouffantTag: ShopliftingTag '+; (red) (bouffant)';

seeThruDress: Dress 'sheer see-through gown; slim beautiful gorgeous see-thru seethrough see thru though black revealing; dress'
    "The sheer, slim black gown has a semi-see-through top. It's gorgeous, but definitely too daring for your
    17-year-old daughter. A beige plastic anti-shoplifting tag is firmly attached to the hem. "
    bulk = 150
;
+ seeThruTag: ShopliftingTag '+; (sheer) (black)';

