extras.t

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


/*
 *   ***************************************************************************
 *   extras.t
 *
 *   This module forms part of the adv3Lite library (c) 2012-13 Eric Eve
 *
 *
 *
 *
 *   This file contains a number of (in principle) optional classes that can be
 *   used in addition to Thing. A game does not need to use any of these classes
 *   since authors can always just set the appropriate properties on thing, but
 *   some authors may find them a convenience, or may find that using them
 *   improves the clarity of their code.
 */

property tooFarAwayToHearMsg;
property tooFarAwayToSmellMsg;
property smellSize;
property soundSize;

/* 
 *   We define SensoryEmanation to be the base class for Odor and Noise. It
 *   doesn't add any functionality here, but makes it possible for the sensory.t
 *   extension to add functionality to this class.
 */
class SensoryEmanation: Decoration
    
    /* 
     *   There's no point in including a SensoryEmanation in any command applying to ALL unless the
     *   command is one of our decoration actions.
     */
    hideFromAll(action) { return decorationActions.indexOf(action) == nil; }
;

/* 
 *   An Odor is an object representing a smell (as opposed to the object that
 *   might be emitting that smell). The desc property of an Odor is displayed in
 *   response to EXAMINE or SMELL; any other action is responded to with the
 *   notImportantMsg.
 */
class Odor: SensoryEmanation
        
    /*  An Odor responds to EXAMINE SOMETHING or SMELL SOMETHING */
    decorationActions = [Examine, SmellSomething]
    
    /*  
     *   The message to display when any other action is attempted with an Odor
     */
    notImportantMsg = BMsg(only smell, '{I} {can\'t} do that to a smell. ')
    
    /*   Treat Smelling an Odor as equivalent to Examining it. */
    dobjFor(SmellSomething) asDobjFor(Examine)   
    
    dobjFor(Examine) { preCond = [objSmellable] }
    
    /* 
     *   Since we turn SMELL into EXAMINE we want our sightSize to be our
     *   smellSize.
     */
    sightSize = smellSize
    
    /*   
     *   For the same reason we want to use our tooFarAwayToSmellMsg for our
     *   tooFarWayToSeeDetailMsg.
     */
    tooFarAwayToSeeDetailMsg = tooFarAwayToSmellMsg    
    
    isOdor = true
    
    
    
;


/* 
 *   A Noise is an object representing a sound (as opposed to the object that
 *   might be emitting that sound). The desc property of a Noise is displayed in
 *   response to EXAMINE or LISTEN TO; any other action is responded to with the
 *   notImportantMsg.
 */
class Noise: SensoryEmanation    
    
    /*  A Noise responds to EXAMINE SOMETHING or LISTEN TO SOMETHING */
    decorationActions = [Examine, ListenTo]
       
    /*  
     *   The message to display when any other action is attempted with a Noise
     */
    notImportantMsg = BMsg(only listen, '{I} {can\'t} do that to a sound. ')
    
    /*   Treat Listening to a Noise as equivalent to Examining it. */
    dobjFor(ListenTo) asDobjFor(Examine)    
    
    dobjFor(Examine) { preCond = [objAudible] }
    
    /* 
     *   Since we turn LISTEN TO into EXAMINE we want our sightSize to be our
     *   soundSize.
     */
    sightSize = soundSize
    
    /*   
     *   For the same reason we want to use our tooFarAwayToHearlMsg for our
     *   tooFarWayToSeeDetailMsg.
     */
    tooFarAwayToSeeDetailMsg = tooFarAwayToHearMsg
    
    isNoise = true
;

/* A Container is a Thing that other things can be put inside */
class Container: Thing
    /* Containers are open by default */
    isOpen = true
    
    /* 
     *   The containment type of a container is In; i.e. things located in a
     *   Container are considered to be in it.
     */
    contType = In
;

/*  An OpenableContainer is a Container that can be opened and closed. */
class OpenableContainer: Container
    
    /* Most OpenableContainers start out closed. */
    isOpen = nil
    
    /* An OpenableContainer is openable */
    isOpenable = true
;

/*  
 *   A LockableContainer is an OpenableContainer that can be locked and unlocked
 *   without the aid of a key.
 */
class LockableContainer: OpenableContainer
    lockability = lockableWithoutKey
    
    /* We usually want a LockableContainer to start out locked. */
    isLocked = true
;

/*  
 *   A KeyedContainer is an OpenableContainer that can be locked and unlocked
 *   only with the aid of a key.
 */
class KeyedContainer: OpenableContainer
    lockability = lockableWithKey
    
    /* We usually want a KeyedContainer to start out locked. */
    isLocked = true
;

/*  A Surface is a Thing that other things can be placed on top of */
class Surface: Thing
    
    /* The contents of a Surface are considered to be on the Surface */
    contType = On
    
    /* 
     *   Searching a Surface may involve look for what's on it as well as what's
     *   in it.
     *
     *   We put this code here rather than checking for contType == On on a
     *   Thing to avoid burdening Thing with overcomplicated code for a rather
     *   special case.
     */
    
    dobjFor(Search)
    {
        preCond = [objVisible, touchObj]
        verify() {}
        check() {}
        action()
        {
            if(hiddenIn.length == 0 
               && contents.countWhich({x: x.searchListed}) == 0)
            {
                say(nothingOnMsg);
                return;
            }
            
            local onList = contents.subset({x: x.searchListed});
            
            if(onList.length > 0)
                listContentsOn(onList);
            
            if(hiddenIn.length > 0)            
                findHidden(&hiddenIn, In);
        }
    }
    
    nothingOnMsg = BMsg(nothing on, '{I} {find} nothing of interest on
        {the dobj}. ')
    
    /* 
     *   List what's on me. We put this in a separate method to make it easier
     *   to customise on a per object basis.
     */
    listContentsOn(lst)
    {
        lookInLister.show(lst, self, true);
    }    
;

/* 
 *   A Plaftorm is a Surface that the player character (and other actors) can
 *   get on.
 */
class Platform: Surface
    isBoardable = true
;

/*   
 *   A Booth is a Container that the player character (and other actors) can get
 *   in.
 */
class Booth: Container
    isEnterable= true
;


/*  An Underside is a Thing that other things can be put under */
class Underside: Thing
    
    /* The contents of an Underside are considered to be under it. */
    contType = Under
;

/*  An RearContainer is a Thing that other things can be put behind */
class RearContainer: Thing
    
    /* The contents of an RearContainer are considered to be behind it. */
    contType = Behind
;

/*  A Wearable is a Thing that can be worn */
class Wearable: Thing
    isWearable = true
;

/*  A Food is a Thing that can be eaten */
class Food: Thing
    isEdible = true
;

/*  
 *   A Fixture is a Thing that can't be picked up and carried around (and so
 *   can't be put anywhere either).
 */
class Fixture: Thing
    isFixed = true
;

/*   
 *   A Decoration is a Fixture that can only be EXAMINEd. Any other action
 *   results in the display of its notImportantMsg. It's normally used for
 *   objects that are purely scenery.
 */
class Decoration: Fixture
    isDecoration = true
    
    
    /* 
     *   Game code may wish to hide decorations from all commands applied to ALL. Tbis can be
     *   achieved by overriding hideFromAll() as shown below. This is not done in the library since
     *   making this change at version 1.61 would compromise backward compatibility.
     */    
    // hideFromAll(action) { return true; }
;

/* 
 *   A Component is an object that's (usually permanently) part of something
 *   else, like the handle of a suitcase or a dial on a safe.
 */
class Component: Fixture    
    cannotTakeMsg = BMsg(cannot take component, '{I} {can\'t} have {that cobj},
        {he dobj}{\'s} part of {1}. ', location.theName)
    
    locType = PartOf
;


/*  
 *   A Distant is a Decoration that's considered too far away to be interacted
 *   with by any command other than EXAMINE
 */
class Distant: ProxyDest, Decoration
   
    /* Message to say that this object is too far away. */
    notImportantMsg = BMsg(distant, '{The subj cobj} {is} too far away. ')
    
    /* 
     *   The base Decoration class includes GoTo as a decorationAction. For a
     *   Distant object (e.g. the sun or moon or a distanct mountain) this will
     *   often be inappropriate. Where an object is 'locally distant' as it
     *   were, i.e. a sign mounted high up on a wall, you can override the
     *   destination property to give the location that a GO TO action should
     *   take the player character to; decorationActions will then include GoTo,
     *   since GO TO SIGN would then be a reasonable way for the player to get
     *   to the location of the sign.
     */
    decorationActions = destination ? [Examine, GoTo] : [Examine]
    
    /* 
     *   If this Distant represents an object that's notionally in another
     *   location, setting destination to that location will allow the GO TO
     *   command to take the actor to that location. If destination is specified
     *   it should normally be a room (usually one adjacent to that this Distant
     *   object is located in. For an object like the sky, the sun or a distant
     *   mountain this should be left as nil.
     */
    destination = nil
    
    
    /* 
     *   Going To a Distant object is unlike a normal GoTo, since if the object
     *   is meant to be Distant, going to its location (the normal response to a
     *   GO TO command) is precisely what we don't want to happen. We therefore
     *   need to provide custom GoTo handling on a Distant object.
     */
    dobjFor(GoTo) 
    {
        verify()
        {
            /* 
             *   Unless the actor is in our location and we have a destination,
             *   we don't want to field a GoTo command, so we make it as
             *   illogical as possible. If we don't have a destination we
             *   shouldn't reach this point anyway since GoTo shouldn't be one
             *   of our decorationActions, but writing the test this way will
             *   make this verify routine prevent the action even if
             *   decorationActions is overridden, if there's a nil destination.
             */
            if(!gActor.isIn(getOutermostRoom) || destination == nil)
                inaccessible(notImportantMsg);
        }
        
        action() 
        {
            /* 
             *   Calculate the route from the actor's current room to the
             *   location where the target object was last seen, using the
             *   routeFinder to carry out the calculations if it is present. In
             *   this case we use routeFinder rather than pcRouteFinder since
             *   the PC can presumably see the way s/he needs to go even if s/he
             *   hasn't gone that way before.
             */
            local route = defined(routeFinder)? routeFinder.findPath(
                gActor.getOutermostRoom, destination.getOutermostRoom) : nil;
            
            /*  
             *   If we don't find a route, just display a message saying we
             *   don't know how to get to our destination.
             */
            if(route == nil)
                sayDontKnowHowToGetThere();
            
            /*  
             *   If the route we find has only one element in its list, that
             *   means that we're where we last saw the target but it's no
             *   longer there, so we don't know where it's gone. In which case
             *   we display a message saying we don't know how to reach our
             *   target.
             */
            else if(route.length == 1)
                sayDontKnowHowToReach();
            
            /*  
             *   If the route we found has at least two elements, then use the
             *   first element of the second element as the direction in which
             *   we need to travel, and use the Continue action to take a step
             *   in that direction.
             */
            else
            {
                local dir = route[2][1];
                Continue.takeStep(dir, destination.getOutermostRoom);               
            }; 
        }
    }
       
    
;



  

/* An Unthing is an object that represents the absence of a thing */
class Unthing: Decoration
    
    /* An Unthing can't respond to any actions, as it isn't there */
    decorationActions = []
    
    /* 
     *   The message to display when the player character tries to interact with
     *   this object; by default we just say it isn't there, but game code will
     *   normally want to override this message to explain the reason for the
     *   absence.
     */  
    notImportantMsg = BMsg(unthing absent, '{The subj cobj} {isn\'t} {here}. ')
                   
    /* 
     *   Users coming from adv3 may be used to Unthings having a notHereMsg, so
     *   we'll define this to be the same as the notImportantMsg
     */
    notHereMsg = notImportantMsg
    
    
    /* An Unthing should never be included in ALL */
    hideFromAll(action) { return true; }
    
    /* 
     *   A player is more likely to be trying to refer to something that is
     *   present than something that isn't, so we give Unthings a substantially
     *   reduced vocabLikelihood
     */
    vocabLikelihood = -100
             
    /* 
     *   If there's anything else in the match list, remove myself from the
     *   matches
     */
    filterResolveList(np, cmd, mode)
    {
        if(np.matches.length > 1)
            np.matches = np.matches.subset({m: m.obj != self});
    }
    
    
    /* Make Unthings verify with the lowest possible score */         
    dobjFor(Default)
    {
        verify()
        {
            inaccessible(notHereMsg);               
        }
    }
    
    iobjFor(Default)
    {
        verify()
        {
            inaccessible(notHereMsg);               
        }
    }
    
    /* 
     *   Giving an order to an Unthing should give the same response as any
     *   other command involving an Unthing.
     */
    handleCommand(action)
    {
        say(notHereMsg);
    }
;

/*  
 *   A CollectiveGroup is a an object that can represent a set of other objects
 *   for particular actions. For any of the objects in the myObjects list the
 *   CollectiveGroup will handle any of the actions in the myActions list; all
 *   the other actions will be handled by the individual objects.
 */
class CollectiveGroup: Fixture
    
    /* 
     *   The list of actions this CollectiveGroup will handle; all the rest will
     *   be handled by the individual objects.
     */
    collectiveActions = [Examine]
    
    /* 
     *   Is action to be treated as a collective action by this group (i.e.
     *   handled by this CollectiveGroup object); by default it is if it's one
     *   of the actions listed in our collectiveActions property.
     */
    isCollectiveAction(action)
    {
        return collectiveActions.indexWhich({a: action.ofKind(a)}) != nil;
    }
    
    /* 
     *   If the current action is one of the collective Actions, then filter all
     *   myObjects from the resolve list; otherwise filter myself out from the
     *   resolve list.
     */
    filterResolveList(np, cmd, mode)
    {
        /* If there are fewer than two matches, don't do any filtering */
        if(np.matches.length < 2)
            return;
        
        if(isCollectiveAction(cmd.action))
           np.matches = np.matches.subset(
               {m: valToList(m.obj.collectiveGroups).indexOf(self) == nil});
        else
            np.matches = np.matches.subset({m: m.obj != self });           
           
    }    
    
    /* Obtain the list of the objects belonging to me that are in scope. */
    myScopeObjects()
    {
        return Q.scopeList(gActor).toList.subset(
            {o: valToList(o.collectiveGroups).indexOf(self) != nil});
    }
    
    /* 
     *   The default descriptions of a CollectiveGroup: By default we just list
     *   those of our members that are in scope.
     */
    desc()
    {
        /* 
         *   Get a list of all the objects in scope that belong to this
         *   Collective Group.
         */
        local lst = myScopeObjects();
        
        /*  
         *   Obtain the sublist of these that are currently held by the player
         *   character
         */
        local heldLst = lst.subset({o: o.isIn(gPlayerChar)});
        
        /*  
         *   Subtract the list of held items from the list of in-scope items to
         *   obtain a list of other items in scope.
         */
        lst -= heldLst;
        
        /*   If none of our items are in scope, say so */
        if(nilToList(lst).length == 0 && nilToList(heldLst).length == 0)
            DMsg(collective group empty, 'There{\'s} no {1} {here}. ', name);
        
        /*  
         *   Otherwise display lists of which of my members the player character
         *   is carrying and which others are also present.
         */
        else
        {
            if(heldLst.length > 0)
                DMsg(carrying collective group, '{I} {am} carrying {1}. ',
                     makeListStr(heldLst));
            
            if(lst.length > 0)            
                DMsg(collective group members, 'There{\'s} {1} {here}. ',       
                     makeListStr(lst));
        }
    }
    
    /* A CollectiveGroup isn't normally listed as an item in its own right. */
    isListed = nil
;



/* 
 *   A Heavy is a Fixture that's too heavy to be picked up. We make Heavy a
 *   Fixture rather than an Immovable since it should normally be obvious that
 *   something is too heavy to be moved.
 */

class Heavy: Fixture
    cannotTakeMsg = BMsg(too heavy, '{The subj cobj} {is} too heavy to move. ')
    
;

/*  
 *   An Immovable is something that can't be picked up, although it may not be
 *   immediately obvious that it can't be moved. For that reason we rule out
 *   taking an Immovable at the check stage rather than the verify stage (this
 *   is what distinquished an Immovable from a Fixture).
 */
class Immovable: Thing
    
    /* Respond to an attempt to TAKE by ruling it out at the check stage. */
    dobjFor(Take)
    {
        check() { say(cannotTakeMsg); }                
    }
    
    /* The message to display to explain why this object can't be taken. */
    cannotTakeMsg = BMsg(cannot take immovable, '{I} {cannot} take {the cobj}.
        ')
    
    /* 
     *   Although an Immovable can't be picked up it's possible that it could be moved around, e.g.,
     *   by pushing or pulling it, and this must be the case if it can PushTravel and/or PullTravel.
     */
    isMoveable = (canPushTravel || canPullTravel)    
;


/*  
 *   A StairwayUp is Thing the player character can climb up. It might represent
 *   an upward staircase, but it could also represent a tree, mast or hillside,
 *   for example. A StairwayUp is also a TravelConnector so it can be defined on
 *   the appropriate direction property of its enclosing room.
 */
class StairwayUp: TravelConnector, Thing
    
    /* Climbing a StairwayUp is equivalent to travelling via it. */
    dobjFor(Climb)
    {        
        action() { travelVia(gActor); }        
    }
    
    /* Climbing up a Stairway up is the same as Climbing it. */
    dobjFor(ClimbUp) asDobjFor(Climb)
    
    /* A StairwayUp is usually something fixed in place. */
    isFixed = true
    
    /*  A StairwayUp is climbable */
    isClimbable = true
    
    /* 
     *   The appropriate PushTravelAction for pushing something something up a
     *   StairwayUp.
     */
    PushTravelVia = PushTravelClimbUp
    
    /*  
     *   Display message announcing that traveler (typically an NPC whose
     *   departure is witnessed by the player character) has left via this
     *   staircase.
     */
    sayDeparting(traveler)
    {
        gMessageParams(traveler);
        DMsg(say departing up stairs, '{The subj traveler} {goes} up
            {1}. ', theName);
    }
    
    
    /* 
     *   Display message announcing that follower is following leader up
     *   this staircase.
     */
    sayActorFollowing(follower, leader)
    {
        /* Create message parameter substitutions for the follower and leader */
        gMessageParams(follower, leader);  
        
        DMsg(say following up staircase, '{The subj follower} follow{s/ed} {the
            leader} up {1}. ', theName);
    }
    
    traversalMsg = BMsg(traverse stairway up, 'up {1}', theName)
;


/*  
 *   A StairwayDown is Thing the player character can climb down. It might
 *   represent an downward staircase, but it could also represent a tree, mast
 *   or hillside, for example. A StairwayDown is also a TravelConnector so it
 *   can be defined on the appropriate direction property of its enclosing room.
 */
class StairwayDown: TravelConnector, Thing
    
    /* Climbing down a StairwayDown is equivalent to travelling via it. */
    dobjFor(ClimbDown)
    {       
        action { travelVia(gActor); }
    }
    
    /* A StairwayDown is usually something fixed in place. */
    isFixed = true
    
    /*  A StairwayDown is something one can climb down */
    canClimbDownMe = true
    
    /* 
     *   The appropriate PushTravelAction for pushing something something down a
     *   StairwayDown.
     */
    PushTravelVia = PushTravelClimbDown
    
    /*  
     *   Display message announcing that traveler (typically an NPC whose
     *   departure is witnessed by the player character)has left via this
     *   staircase.
     */
    sayDeparting(traveler)
    {
        gMessageParams(traveler);
        DMsg(say departing down stairs, '{The subj traveler} {goes} down
            {1}. ', theName);
    }
    
    /* 
     *   Display message announcing that follower is following leader down
     *   this staircase.
     */
    sayActorFollowing(follower, leader)
    {
        /* Create message parameter substitutions for the follower and leader */
        gMessageParams(follower, leader);  
        
        DMsg(say following down staircase, '{The subj follower} follow{s/ed}
            {the leader} down {1}. ', theName);
    }
    
    traversalMsg = BMsg(traverse stairway down, 'down {1}', theName)
    
    cannotClimbMsg = BMsg(cannot climb stairway down, '{I} {can\'t} climb {the
        dobj}, but {i} could go down {him dobj}. ')
;

/* A double sided (aks two-way) Stairway */
class DSStairway: DSCon, StairwayUp
    
    /* 
     *   The room at the upper end of this staircase. The library will try to determine which end of
     *   this stairway is the upper and end which the lower according to whether it's room1 or room2
     *   that points to us on its up or down property, so game code need not specify the upper or
     *   lower end unless neither room at the ends of this stairway point to us via up or down,
     *   either directly or via an asExit() macro. If the ends need to be specified manually, there
     *   is no need to specify both, since whiohever end is explicitly defined will implicitly
     *   define what the other end is.
     */     
    upperEnd = nil
    
    /* The room at the lower end of this staircase */
    lowerEnd = nil
    
    /* 
     *   initialise this SymStairway by first carrying out the inherited initialization and then
     *   trying to determine which end of the stairway is the upperEnd and which the lowerEnd.
     */
    preinitThing
    {
        /* Carry out the inherited handling. */
        inherited();
        
        /* Attempt to determine which end of the Stairway is which. */       
        findUpperAndLowerEnds();
    }
    
    /* 
     *   Method which attempts to determine which end of the staircase is the upper and which the
     *   lower based on the up and/or down properties defined in room1 and room2.
     */
    findUpperAndLowerEnds()
    {
        /* 
         *   If the lower end is not yet defined and room1 points to us on its up property, then our
         *   lower end must be room1.
         */
        if(lowerEnd == nil && room1 && room1.getConnector(&up) == self)        
            lowerEnd = room1;
        
        /* 
         *   If the upper end is not yet defined and room1 points to us on its down property, then
         *   our upper end must be room 1.
         */
        if(upperEnd == nil && room1 && room1.getConnector(&down) == self)        
            upperEnd = room1;
        
        /* 
         *   If the lower end is not yet defined and room2 points to us on its up property, then our
         *   lower end must be room 2.
         */
        if(lowerEnd == nil && room2 && room2.getConnector(&up) == self)        
            lowerEnd = room2;
        
        /* 
         *   If the upper end is not yet defined and room2 points to us on its down property, then
         *   our upper end must be room 2.
         */
        if(upperEnd == nil && room2 && room2.getConnector(&down) == self)        
            upperEnd = room2;
        
        /* 
         *   If the upper end is not yet defined but the lower end is, then our upper end must be
         *   whichever room isn't the lower end.
         */       
        if(upperEnd == nil && lowerEnd)
            upperEnd = lowerEnd == room1 ? room2 : room1;
        
        /* 
         *   If the lower end is not yet defined but the upper end is, then our lower end must be
         *   whichever room isn't the upper end.
         */ 
        if(lowerEnd == nil && upperEnd)
            lowerEnd = upperEnd == room1 ? room2 : room1;    
    }
    
    /* 
     *   The appropriate PushTravelAction for pushing something something up or down a
     *   SymStairway.
     */
    PushTravelVia = location.isOrIsIn(lowerEnd) ? PushTravelClimbUp : PushTravelClimbDown
    
    /*  
     *   Display message announcing that traveler (typically an NPC whose
     *   departure is witnessed by the player character) has left via this
     *   staircase. 
     */
    sayDeparting(traveler)
    {
        if(location.isOrIsIn(lowerEnd))
            inherited(traveler);
        else
            delegated StairwayDown(traveler);
    }
    
    /* 
     *   Display message announcing that follower is following leader up
     *   this staircase.
     */
    sayActorFollowing(follower, leader)
    {
        /* Create message parameter substitutions for the follower and leader */
        if(location.isOrIsIn(lowerEnd))
            delegated StairwayUp(follower, leader);
        else
            delegated StairwayDown(follower, leader);
    }
    
    /* The message for traversing this stairway - we delegate to Thing's message. */
    traversalMsg = location.isOrIsIn(lowerEnd) ? delegated StairwayUp : delegated StairwayDown
    
    
    /* a trio of short service methods to provide convenient abbreviations in game code */
    
    /* Is the player character in our upper end room? */
    inUpper = (upperEnd && gPlayerChar.isIn(upperEnd))
    
    /* Is the player character in our lower end room? */
    inLower = (lowerEnd && gPlayerChar.isIn(lowerEnd))
    
    /* 
     *   Return a or b depending on whether or not the player character is in our upperEnd room.
     *   This is primarily intended to ease the writing of descriptions or travelDescs which vary
     *   slightly according to which end we're at, e.g. "The stairs lead steeply <<byUpLo('down',
     *   'up')>> "
     */
    byEnd(arg) { return inUpper ? arg[1] : arg[2]; }
    
    /* 
     *   Retuen 'down' or 'up' depending on whether we're at the upper or lower end of the stairway.
     */
    upOrDown = inUpper ? downDir.name : upDir.name
    
    dobjFor(Climb)
    {        
        verify()
        {
            /* You can't climb up from the upper end of a staircase. */
            if(gActor.isIn(upperEnd))
                illogicalNow(cannotClimbMsg);
        }
    }
    
    cannotClimbMsg = BMsg(cant climb from here, '{I} {can\'t} climb up {the dobj} from {here}. ')
    
    dobjFor(ClimbDown)
    {
        verify()
        {
            /* You can't climb up from the upper end of a staircase. */
            if(gActor.isIn(lowerEnd))
                illogicalNow(cannotClimbDownMsg);
        }
        
        action()
        {
            delegated StairwayDown();
        }
        
        report()
        {
            delegated StairwayDown();
        }
    }
    
    cannotClimbDownMsg = BMsg(cant climb doen from here, 
                              '{I} {can\'t} climb down {the dobj} from {here}. ')
    
    iobjFor(PushTravelClimbUp)
    {
        verify()
        {
            inherited();
            
            if(gActor.isIn(upperEnd))
                illogicalNow(cannotPushClimbMsg);
        }        
    }
    
    iobjFor(PushTravelClimbDown)
    {
        verify()
        {
            inherited();
            
            if(gActor.isIn(lowerEnd))
                illogicalNow(cannotPushClimbDownMsg);
        }        
    }
    
    cannotPushClimbMsg = BMsg(cannot push climb here, '{I} {can\'t} ascend {the iobj} from {here}.
        ')
    
    cannotPushClimbDownMsg = BMsg(cannot push climb down here, '{I} {can\'t} descend {the iobj} from {here}.
        ')
;





/* A Passage represents a physical object an actor can travel through, like a
     passage or portal. A Passage is also a TravelConnector so it
 *   can be defined on the appropriate direction property of its enclosing room.
 */
class Passage: TravelConnector, Thing

    /* Going through a Passage is equivalent to travelling via it. */
    dobjFor(GoThrough)
    {        
        preCond = [travelPermitted, touchObj]
        
        action() { travelVia(gActor); }
    }
    
    iobjFor(PushTravelThrough)
    {
        preCond = [travelPermitted, touchObj]
    }  
    
    
    /* Entering a Passage is the same as going through it */
    dobjFor(Enter) asDobjFor(GoThrough)
    
     /* Going along a Passage is the same as going through it */
    dobjFor(GoAlong) asDobjFor(GoThrough)
    
    /* A Passage is usually something fixed in place. */
    isFixed = true
    
    /*  A Passage is something one can go through */
    canGoThroughMe = true
    
    /* 
     *   The appropriate PushTravelAction for pushing something something
     *   through a Passage.
     */
    PushTravelVia = PushTravelThrough
;



/*  
 *   A PathPassage is a Passage that represents a path, so that following it or
 *   going down it is equivalent to going through it.
 */
class PathPassage: Passage
    /* Make following a path the same as going through it. */
    dobjFor(Follow) asDobjFor(GoThrough)
    
    /* Make going down a path the same as going through it. */
    dobjFor(ClimbDown) asDobjFor(GoThrough)
    
    /* Make going up a path the same as going through it. */
    dobjFor(ClimbUp) asDobjFor(GoThrough)
    
    /* One most naturally talks of going 'down' a path */
    traversalMsg = BMsg(traverse path passage, 'down {1}', theName)
;


/* A double sided (i.e., two-way) Passage. */
class DSPassage: DSCon, Passage
;

/* A double sided (.e., two-way) PathPassage. */
class DSPathPassage: DSCon, PathPassage
;

/* 
 *   A double-sided (or two-way) TravelConnector. The room1 and room2 properties must be defined to
 *   stipulate the two rooms the TravelConnector connects.
 */
class DSTravelConnector: DSBase, TravelConnector        
;


/* 
 *   ProxyDest is a mix-in class for use with an object that represents the
 *   exterior of a room, such as a hut in a field. Its purpose is to remove such
 *   an object as the possible target of a GO TO command provided there's
 *   another alternative. This prevents a GO TO command from taking the player
 *   character from the appropriate room to an inappropriate destination.
 */
class ProxyDest: object
    filterResolveList(np, cmd, mode) 
    {
        /* Carry out the inherited handling. */
        inherited(np, cmd, mode);
        
        /* 
         *   If we're the potential target of a GO TO command and there are
         *   other possible matches, remove ourselves from the list of matches.
         */
        if(cmd.action == GoTo && np.matches.length > 1)
            np.matches = np.matches.subset({m: m.obj != self});
    }   
;

/*  
 *   An Enterable is a Thing one can go inside. It is usually used to represent
 *   the exterior of an object like a building, so that going inside will take
 *   the actor to a new location representing the interior.
 */
class Enterable: ProxyDest, Fixture
    
    /* To enter an Enterable the actor must travel via its connector. */
    dobjFor(Enter)
    {
        verify() {}
        action() { connector.travelVia(gActor); }
    }
    
    /* The handling of an attempt to push something inside this object */
    iobjFor(PushTravelEnter)
    {
        /* 
         *   To push something inside this object we must be able to touch this
         *   object.
         */
        preCond = [touchObj]
        
        /* Check whether our connector allows push travel through it */
        check() { connector.checkPushTravel(); }
        
        /* Carry out the attempt to push something inside us. */
        action()
        {
            /* 
             *   If our connector defines a PushTravelVia action, use that
             *   action to push the direct object via our connector. The use of
             *   replaceAction means that the handling will end there.
             */
            if(connector.PushTravelVia)
                replaceAction(connector.PushTravelVia, gDobj, connector);
            
            
            /* Otherwise, make our direct object travel vis our connector */
            connector.travelVia(gDobj);
            
            /* 
             *   If the push travel attempt was successful, which we assume it
             *   was if the direct object ended up in our connector's
             *   destination, display a message to say the push travel succeeded
             *   and make the actor travel via our connector.
             */
            if(gDobj.isIn(connector.destination))
            {
                say(okayPushIntoMsg);
                connector.travelVia(gActor);
            }           
        }
    }
    
    /*   
     *   Our connector is the TravelConnector via which an actor travels on
     *   entering this object. This may be a Room, or some other TravelConnector
     *   object such as a Door on our outside through which the actor must pass
     *   to get inside.
     *
     *   We set connector = destination since on occasion it may seem more
     *   natural to authors to set the destination property (when it leads
     *   straight to a room) and sometimes more natural to set the connector
     *   property (when it's a door, say). With connector = (destination) by
     *   default, it should work either way.
     */
    connector = (destination)
    
    /*   
     *   If entering this object leads straight to another room, it may seem
     *   more natural to define a destination property; provided we don't
     *   override connector as well, this has the same effect as pointing
     *   connector directly to the room.     
     */
    destination = nil
    
    /* An Enterable is usually enterable */
    isEnterable = true
    
;

/* 
 *   A SecretDoor is a Door that doesn't appear to be a door when it's closed,
 *   and which must be opened by some external mechanism (other than by an OPEN
 *   command)
 */
class SecretDoor: Door
    /* You can only go through a SecretDoor when it's open */
    canGoThroughMe = isOpen
    
    /* A SecretDoor only functions as a TravelConnector when it's open */
    isConnectorApparent = isOpen   
    
    /* 
     *   We can't use an OPEN command to open a SecretDoor, but by default we'll
     *   allow a CLOSE command to close it. To disallow this override isOpenable
     *   to nil
     */
    isOpenable = isOpen
    
    /*   
     *   The vocab string (including the name) that applies to this SecretDoor
     *   when it's open. This might be somewhat different from that which
     *   applies when it's closed. For example, opening a bookcase might turn it
     *   into a passage or an opening. If you don't want to the vocab to change
     *   when this SecretDoor is opened and closed, leave vocabWhenOpen as nil.
     */     
    vocabWhenOpen = nil
    
    /*  
     *   The vocab string (including the name) that applies to this SecretDoor
     *   when it's closed. If the SecretDoor starts out closed there's no need
     *   to define this explicitly as it will be copied from the vocab property
     *   at preInit.
     *
     *   To define a SecretDoor that's effectively invisible when closed, give
     *   it a vocab property comprising an empty string (i.e. '', not nil); this
     *   will make it impossible for the player to refer to it when it's closed.
     */
    vocabWhenClosed = nil
    
    /*   Preinitialize a SecretDoor */
    preinitThing()
    {
        /* Carry out the inherited handling */
        inherited();
        
        /* 
         *   If the door starts out open, copy its initial vocab to its
         *   vocabWhenOpen property.
         */
        if(isOpen)
            vocabWhenOpen = vocab;
        
        /*  
         *   If the door starts out closed, copy its initial vocab to its
         *   vocabWhenClosed property.
         */
        else
            vocabWhenClosed = vocab;
    }
    
    /* Carry out opening or closing a SecretDoor */
    makeOpen(stat)
    {
        /* Perform the inherited handling */
        inherited(stat);
        
        /* 
         *   If we're opening the SecretDoor and it has a non-nil vocabWhenOpen
         *   property and its vocabWhenOpen property is different from its
         *   current vocab, then reinitialize its vocab from the vocabWhenOpen
         *   property.
         */
        if(stat && vocabWhenOpen && vocab != vocabWhenOpen)
            replaceVocab(vocabWhenOpen);
        
        /* 
         *   If we're closing the SecretDoor and it has a non-nil
         *   vocabWhenClosed property and its vocabWhenClosed property is
         *   different from its current vocab, then reinitialize its vocab from
         *   the vocabWhenClosed property.
         */
        if(!stat && vocabWhenClosed && vocab != vocabWhenClosed)
            replaceVocab(vocabWhenClosed);
    }
;

/* A Switch is a Thing than can be turned on and off */
class Switch: Thing
    
    /* A Switch is switchable */
    isSwitchable = true
    
    /* FLIP SWITCH is equivalent to SWITCH SWITCH */
    dobjFor(Flip) asDobjFor(SwitchVague)
;

/*  
 *   A Flashlight is a light source that can be turned on and off; in other
 *   words it's a Switch that's lit when on.
 */
class Flashlight: Switch
    
    /* Turning a Flashlight on and off makes it lit or unlit */
    makeOn(stat)
    {
        inherited(stat);
        makeLit(stat);
    }
    
    /* Lighting a Flashlight is equivalent to switching it on */
    dobjFor(Light) asDobjFor(SwitchOn)
    
    /* Extinguishing a Flashlight is equivalent to switching it off */
    dobjFor(Extinguish) asDobjFor(SwitchOff)
;

/* 
 *   A ContainerDoor can be used as part of a multiply-containing object to
 *   represent the door of the container-like object defined on its remapIn
 *   property. A ContainerDoor is open or closed if the underlying container is
 *   open or closed, and remaps all container-appropriate actions to the remapIn
 *   object of its location.
 */
class ContainerDoor: Fixture
    /* We're open if our location's container is open */
    isOpen = (location.remapIn.isOpen)
    
    /* 
     *   Redirect all container-appropriate actions to the remapIn object of our
     *   location, so that opening, closing, locking and unlocking this door
     *   will perform the equivalent action on our container object.
     */
    remapIn = location.remapIn
    
    cannotTakeMsg = BMsg(cannot take container door, '{I} {can\'t} have {the
        cobj}; {he dobj}{\'s} part of {1}. ', location.theName)
;



/* 
 *   A MinorItem is an unobtrusive and possibly unimportant portable object that's worth
 *   implementing in the game but sufficiently minor as to be not worth mentioning (in response to X
 *   or FOO ALL) unless it's either directly held by the player character or directly in the
 *   enclosing room. (Based on Joey Cramsey's Trinket class).
 */
class MinorItem: Thing 
    /* 
     *   We're listed in response to a LOOK command only if we're not fixed in place and we can't be
     *   ignored for the current action or our current location.
     */    
    lookListed = (!canBeIgnored(gAction) && !isFixed)
    
    /* We're excluded from a FOO ALL action if we can be ignored for FOO. */
    hideFromAll(action) 
    {
        return canBeIgnored(action);
    }

    /* 
     *   We can be ignored for action unless we're directly in our enclosing room or directly in the
     *   player character's location or carried by the player character or, if desired, the action
     *   is TAKEFROM or PutXX
     */
    canBeIgnored(action) 
    {
        /* 
         *   If we are exposed, in the middle of the room or the actor's location (e.g. a
         *   platform or booth), then we cannot be ignored.
         */
        if (location is in (getOutermostRoom(), gActor.location))         
            return nil;
        
        
        /* Allow actions like DROP ALL if we are being directly carried. */
        if (isDirectlyHeldBy(gActor))         
            return nil;
        
        
        /* Allow an explicit TAKE FROM or PUT somewhere if our allowTakeFromPut flag allows it. */        
        if(action is in (TakeFrom, PutIn, PutOn, PutBehind, PutUnder))
            return !includeTakeFromPutAll;
        
        
        // Otherwise, pay us no mind in X ALL.
        return true;
        
    }   
        
    /* 
     *   Flag: do we want to be included in TAKE ALL FROM X, and PUT ALL IN/ON/UNDER/BEHIND X? by
     *   default we do.
     */
    includeTakeFromPutAll = true
;





 
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1