# travel.t | documentation

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

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

    property lastTravelInfo;
    property cannotGoShowExits;
    property pcArrivalTurn;

    /*
     *   A Room is a top location in which the player character, other actors and
     *   other objects may be located. It may represent any discrete unit of space,
     *   not necessarily a room in a building. Normally actors may only interact
     *   with objects in the same room as themselves, but the senseRegion module
     *   allows us to define sensory connections between rooms.
     */
    class Room: TravelConnector, Thing

        /*
         *   The direction properties (north, south, etc.) define what happens when
         *   travel is attempted in the corresponding direction. A direction
         *   property may be defined as another Room (in which case traveling in the
         *   corresponding direction takes the actor directly to that Room), or to a
         *   TravelConnector (including a Door or Stairway), or to a single-quoted
         *   or double-quoted string (which is then simply displayed) or to a method
         *   (which is then executed). It is recommended that methods only be used
         *   when the effect of attempted travel is something other than ordinary
         *   travel; to impose conditions on travel or define the side-effects of
         *   travel it's usually better to use a TravelConnector object.
         */
        north = nil
        east = nil
        south = nil
        west = nil
        up = nil
        down = nil
        in = nil
        out = nil
        southeast = nil
        southwest = nil
        northeast = nil
        northwest = nil
        port = nil
        starboard = nil
        aft = nil
        fore = nil

        /*
         *   Are compass directions allowed for travel from this room? By default
         *   we'll allow thema anywhere, but game code may wish to override this for
         *   rooms that are aboard a vessel.
         */
        allowCompassDirections = true

        /*
         *   Are shipboard directions meaningful in this room? By default we'll make
         *   them so if and  only if this room defines at least one shipboard
         *   directional exit. Game code may wish to modify this, for example, on
         *   the hold of a ship that only defines an up direction but where
         *   shipboard directions would still in principle be meaningful.
         */
        allowShipboardDirections()
        {
            for(local dir in ShipboardDirection.shipboardDirections)
            {
                if(propType(dir.dirProp) != TypeNil)
                    return true;
            }

            return nil;
        }

        /*
         *   A Room is normally lit, but if we want a dark room we can override
         *   isLit to nil.
         */
        isLit = true

        /*   A Room is always fixed in place. */
        isFixed = true

        /*   A Room is always open */
        isOpen = true

        /*
         *   A Room is lit within it it's illuminated (it's either lit itself or
         *   contains a light source
         */
        litWithin()
        {
            return isIlluminated;
        }

        /*
         *   Since a Room provides the TravelConnector interface, we need to define
         *   where it leads to when one attempts to travel via it; a Room always
         *   leads to itself (i.e. traveling via a Room takes one to that Room).
         */
        destination { return self; }

        /* By default our destination is known if we've been visited */
        isDestinationKnown = (visited)

        /* Has this room been visited? */
        visited = nil

        /*
         *   Although we don't define room parts in general, we do give every Room a
         *   floor so that the parser can refer to objects 'on the ground' when
         *   asking disambiguation questions. By default we supply every Room with
         *   the defaultGround MultiLoc object to represent its floor. You can if
         *   you like replace this with a custom floor object in particular rooms,
         *   but it's highly recommended that you define your custom floor to be of
         *   the Floor class. It's also legal to define floorObj as nil on a Room
         *   that represents an obviously floorless place, such as the top of a mast
         *   or tree.
         */
        floorObj = defaultGround

        /*
         *   When executing travel we move the traveler into the room. Then, if the
         *   traveler is the player char we perform a look around in the room,
         *   provided we should look around on entering the room. actor is the actor
         *   doing the traveling, traveler is the traveler doing the traveling
         *   (normally the same as actor unless actor is in a Vehicle, in which case
         *   traveler will be the Vehicle) and conn is the TravelConnector the
         *   vehicle is traversing in order to reach this room.
         */
        execTravel(actor, traveler, conn)
        {
            /*   Note whether we want to look around on entering this room. */
            local lookAroundOnEntering = lookOnEnter(actor);

            /*
             *   Note the traveler's current location, so we can check subsequently
             *   whether travel actually took place.
             */
            local oldLoc = traveler.getOutermostRoom();

            /*
             *   Get our destination when starting from oldLoc (for a room this
             *   should normally evaluate to self)
             */
            local dest = getDestination(oldLoc);

            /*  Carry out the before travel notification */
            conn.beforeTravelNotifications(traveler);

            /*
             *   Note the actor's old travel info in case we have to restore it
             *   after a failed travel attempt.
             */
            if(actor != gPlayerChar)
                local oldTravelInfo = actor.lastTravelInfo;

            if(actor == gPlayerChar)
            {
                /*
                 *   Before carrying out the travel make a note of the room the
                 *   player character is about to leave.
                 */
                libGlobal.lastLoc = oldLoc;
            }

            /*
             *   Otherwise if the player character can see the actor traverse the
             *   connector, note the fact on the actor, so that the information is
             *   available should the player character wish to follow the actor.
             */
            else if(Q.canSee(gPlayerChar, actor))
                actor.lastTravelInfo = [oldLoc, conn];

            /*
             *   Note that actor is traversing the Travel Connector. This can be
             *   used to carry out any side-effects of the travel, such as
             *   describing it.
             */

            conn.noteTraversal(actor);

            /* Notify the actor's current room that the actor is about to depart. */
            oldLoc.notifyDeparture(actor, dest);

            /*  Move the traveling object into its destination */
            traveler.actionMoveInto(dest);

            if(gPlayerChar.isOrIsIn(traveler))
            {
                /*
                 *   Notify any actors in the location that the player character has
                 *   just arrived.
                 */

                if(defined(Actor))
                {
                    local notifyList = allContents.subset({o: defined(Actor) && o.ofKind(Actor)});

                    notifyList.forEach({a: a.pcArrivalTurn = gTurns });
                }

                /* Show a room description if appropriate */
                if(lookAroundOnEntering)
                    lookAroundWithin();
            }

            /*
             *   Execute the after travel notifications, provided that the actor
             *   actually ended up in a new location.
             */
            if(self != oldLoc)
            {
                conn.afterTravelNotifications(traveler);
            }

            /*
             *   If we're not the player character and we failed to go anywhere,
             *   restore our old travel info.
             */
            if(actor != gPlayerChar && actor.getOutermostRoom == oldLoc)
                actor.lastTravelInfo = oldTravelInfo;
        }

        /*
         *   Should we look around on entering this room? By default we should; this
         *   is overridden in senseRegion.t to provide for the possibility of a
         *   "continuous space" implementation.
         */
        lookOnEnter(obj)
        {
            return true;
        }

        /*  A Room's outermost room is itself. */
        getOutermostRoom { return self; }

        /*  A Room's outermost visible parent is itself. */
        outermostVisibleParent() { return self; }

        /*  A Room's outermost parent is itself. */
        outermostParent = self

        /*
         *   The Message to display if travel is disallowed in any given direction
         *   (because the corresponding direction property of the Room is nil).
         */
        cannotGoThatWayMsg = BMsg(cannot go, '{I} {can\'t} go that way. ' )

        /*
         *   The method that is called when travel is attempted in a direction
         *   (given the dir parameter) for which nothing is defined. By default we
         *   simply display the cannotGoThatWayMsg followed by a list of exits, but
         *   this can be overridden if desired, and different responses given for
         *   different directions. Note that the dir parameter will be passed as a
         *   direction object. e.g. northDir.
         */
        cannotGoThatWay(dir)
        {
            "<<cannotGoThatWayMsg>>";
            if(gExitLister != nil)
                gExitLister.cannotGoShowExits(gActor, self);

            "<.p>";
        }

        /*
         *   The message to display when travel is attempted in the dark, either in
         *   a direction for which no destination (or other handling) is defined, or
         *   in a direction in which the exit is not visible in the dark.
         */
        cannotGoThatWayInDarkMsg = BMsg(cannot go in dark, 'It{dummy}{\'s} too dark
            to see where {i}{\'m} going. ')

        /*
         *   The method that's called when travel is attempted by an undefined or
         *   invisible exit in the dark. By default we display the
         *   cannotGoThatWayInDarkMsg followed by a list of visible exits, but game
         *   code can override this.
         */
        cannotGoThatWayInDark(dir)
        {
            "<<cannotGoThatWayInDarkMsg>><.p>";
            if(gExitLister != nil)
                gExitLister.cannotGoShowExits(gActor, self);

            "<.p>";
        }

        /*
         *   Normally we don't allow travel from this location if both it and the
         *   destination are in darkness. To allow travel from this location in any
         *   case set allowDarkTravel to true.
         */
        allowDarkTravel = nil

        /* Call the before action notifications on this room and its regions */
        notifyBefore()
        {
            /* Call our own roomBeforeAction() */
            roomBeforeAction();

            /*
             *   Notify all the regions we're in of the action that's about to take
             *   place.
             */
            foreach(local reg in valToList(regions))
                reg.notifyBefore();
        }

        /* Call the after action notifications on this room and its regions */
        notifyAfter()
        {
            /* Call our own roomAfterAction() */
            roomAfterAction();

            /*
             *   Notify all the regions we're in of the action that's just taken
             *   place.
             */
            foreach(local reg in valToList(regions))
                reg.notifyAfter();
        }

        /*
         *   roomBeforeAction and roomAfterAction are called just before and after
         *   the action phases of the current action. Individual instances can
         *   override to react to the particular actions.     */

        roomBeforeAction() { }
        roomAfterAction() { }

        /*
         *   beforeTravel(traveler, connector) is called on the room traveler is
         *   in just as traveler is about to attempt travel via connector (a
         *   TravelConnector object).
         */
        beforeTravel(traveler, connector) { }

        /*
         *   afterTravel(traveler, connector) is called on the room traveler has
         *   just arrived in via connector.
         */
        afterTravel(traveler, connector) { }

        /* show the exit list in the status line */
        showStatuslineExits()
        {
            /* if we have a global exit lister, ask it to show the exits */
            if (gExitLister != nil)
                gExitLister.showStatuslineExits();
        }

        /*  The name of the room as it appears in the status line. */
        statusName(actor)
        {
            local nestedLoc = '';

            /*
             *   If the actor is not directly in the room we add the actor's
             *   immediate container in parentheses after the room name.
             */
            if(!actor.location.ofKind(Room))
                nestedLoc = BMsg(actor nested location name,
                                 ' (<<actor.location.objInPrep>>
                    <<actor.location.theName>>)');

            /*
             *   If the Room is illuminated, display its ordinary room title,
             *   followed by the actor's immediate location if it's not the Room. If
             *   the Room is in darkness, use the darkName instead of the roomTitle.
             */
            if(isIlluminated)
                "<<roomTitle>><<nestedLoc>>";
            else
                "<<darkName>><<nestedLoc>>";
        }

        /*
         *   Anything in the Room is deemed to be inside it (this sounds
         *   tautologous, but it's why we give Room a contType of In).
         */
        contType = In

        /*
         *   This method is invoked on the player char's current room at the end of
         *   every action. By default we run our doScript() method if we're also a
         *   Script (that is, if the Room has been mixed in with an EventList
         *   class), thereby facilitating the display of atmospheric messages.
         */
        roomDaemon()
        {
            if(ofKind(Script) &&!(noScriptAfterListen && gActionIs(Listen)))
                doScript();
        }

        /*
         *   Flag, do we want to prevent out script firing after a LISTEN command? By default we do
         *   because otherwise the respose to a LISTEN command might clash with an atmospheric message
         *   appearing on the same turn.
         */
        noScriptAfterListen = true

        /*
         *   This room can optionally be in one or more regions. The regions
         *   property hold the region or a list of regions I'm in.
         */
        regions = nil

        /*
         *   A Room can't be in another Room or a Thing, but it can notionally be in
         *   a Region, so we check to see if we're in the list of our regions.
         */
        isIn(region)
        {
            return valToList(regions).indexWhich({x: x.isOrIsIn(region)}) != nil;
        }

        /* Add this room to the room list of all the regions it's in */
        addToRegions()
        {
            foreach(local reg in valToList(regions))
                reg.addToRoomList(self);
        }

        /*
         *   The list of all the regions this room belongs to. This is calculated
         *   the first time this property is queried and then stored in the
         *   property.
         */
        allRegions()
        {
            local ar = getAllRegions();
            allRegions = ar;
            return ar;
        }

        /* Calculate a list of all the regions this room belongs to */
        getAllRegions()
        {
            local thisRegions = new Vector(valToList(regions));
            foreach(local reg in valToList(regions))
                thisRegions.appendUnique(reg.allRegions);

            return thisRegions.toList();
        }

        /* return a list of regions that both this room and other are common to. */
        regionsInCommonWith(other)
        {
            return allRegions.subset({x: x.roomList.indexOf(other) != nil});
        }

        /*
         *   Carry out the notifications for a traveler leaving this room to go to
         *   dest.
         */
        notifyDeparture(traveler, dest)
        {
            /* Notify the current room of the impending departure */
            travelerLeaving(traveler, dest);

            /*
             *   Notify any regions the traveler is about to leave of the impending
             *   departure         */

            local commonRegs = regionsInCommonWith(dest);

            /*
             *   The regions I'm about to leave are all the regions this room is in
             *   less those that this room has in common with my destination.
             */
            local regsLeft = allRegions - commonRegs;

            /*
             *   Notify all the regions that the traveler is leaving that the
             *   traveler is leaving to go to dest.
             */
            foreach(local reg in regsLeft)
                reg.travelerLeaving(traveler, dest);

            /*
             *   The regions I'm about to enter are all the regions the destination
             *   room is in, less those this room has in common with the
             *   destination.
             */
            local regsEntered = dest.allRegions - commonRegs;

            /* Notify any regions I'm about to enter of my impending arrival. */
            foreach(local reg in regsEntered)
                reg.travelerEntering(traveler, self);

            /* notify the destination room of the impending arrival */
            dest.travelerEntering(traveler, self);
        }

        /*
         *   This method is invoked when traveler is about to leave this room and go
         *   to dest.
         */
        travelerLeaving(traveler, dest) { }

        /*
         *   This method is invoked when traveler is about to enter this room
         *   from origin.
         */
        travelerEntering(traveler, origin) { }

        /*    A Room has no interiorParent since it's a top-level container. */
        interiorParent()
        {
            return nil;
        }

        /*
         *   Add extra items into scope for the action. By default we simply add the
         *   items from our extraScopeItems list together with those of any regions
         *   we're it. This allows commonly visible items such as the sky to be
         *   added to scope in dark outdoor rooms, for instance.
         */
        addExtraScopeItems(action)
        {
            /*
             *   Append the extra scope items defined on this Room to the action's
             *   scope list.
             */
            action.scopeList =
                action.scopeList.appendUnique(valToList(extraScopeItems));

            /*  Add any extra scope items defined on any regions we're in. */
            foreach(local reg in valToList(regions))
                reg.addExtraScopeItems(action);

            /*
             *   By default we'll also add our floor object to scope if we have one
             *   and it isn't already in scope.
             */
            if(floorObj != nil)
                action.scopeList = action.scopeList.appendUnique([floorObj]);
        }

        /*
         *   A list of extra items to be added to scope when an action is carried
         *   out in this room.
         */
        extraScopeItems = []

        /*   The location at which a Room was last seen is always itself. */
        lastSeenAt = (self)

        /*
         *   Convenience method to set information about the destination dirn from
         *   this room. The dirn parameter should be specified as a direction object
         *   (e.g. northDir) and the dest parameter as a room. Note this is only
         *   meaningful for direction properties specified as methods (as opposed to
         *   Rooms, Doors or other TravelConnectors or as strings), and is only
         *   useful for priming the route finder at the start of the game before the
         *   player has tried to go in this direction from this room. Once the
         *   player tries this direction the dest info table will be overwritten
         *   with information about where it actually leads.
         */
        setDestInfo(dirn, dest)
        {
            libGlobal.addExtraDestInfo(self, dirn, dest);
        }

        /*
         *   The getDirection method returns the direction by which one would need
         *   to travel from this room to travel via the connector conn (or nil if
         *   none of the room's direction properties point to conn).
         */
        getDirection(conn)
        {
            for(local dir = firstObj(Direction); dir != nil; dir = nextObj(dir,
                Direction))
            {
                if(propType(dir.dirProp) == TypeObject && self.(dir.dirProp) == conn)
                    return dir;
            }

            return nil;
        }

        /*
         *   The getDirectionTo method returns the direction by which one would need to travel from this
         *   room to travel to dest not via an UnlistedProxy Connector (normally defined by the asExit()
         *   macro. If none of the room's direction properties clearly leads to dest via a
         *   TravelConnector including a Room) then return nil.
         */
        getDirectionTo(dest)
        {
            for(local dir = firstObj(Direction); dir != nil; dir = nextObj(dir,
                Direction))
            {
                local conn;

                if(propType(dir.dirProp) == TypeObject)
                {
                    conn = self.(dir.dirProp);

                    if(conn && !conn.ofKind(UnlistedProxyConnector)
                       &&  conn.getDestination(self) == dest)
                        return dir;
                }

            }

            return nil;
        }

        /* Rooms are generally large emough to allow them to be smelt or listened to. */
        smellSize = large
        soundSize = large

        /*
         *   By default we don't want the examineStatus method of a Room to do
         *   anything except displaying the stateDesc, should we have defined one.
         *   In particular we don't want it to list the contents of the Room, since
         *   Looking Around will do this anyway.
         */
        examineStatus() { display(&stateDesc); }

        /*  Examining a Room is the same as looking around within it. */
        dobjFor(Examine)
        {
            action() { lookAroundWithin(); }
        }

        /*  Going out of a Room is the same as executing an OUT command */
        dobjFor(GetOutOf)
        {
            action() { GoOut.execAction(gCommand); }
        }

        /*
         *   Pushing an object out of a Room is the same as pushing it via the OUT
         *   exit.
         */
        iobjFor(PushTravelGetOutOf)
        {
            action()
            {
                gCommand.verbProd.dirMatch = object { dir = outDir; };
                gAction = PushTravelDir;
                PushTravelDir.execAction(gCommand);
            }
        }

        /*
         *   Optional method that returns a single-quoted string explaining why
         *   target (normally an object in a remote location) cannot be reached from
         *   this room. By default we just return the target's tooFarAwayMsg but
         *   this can be overridden, for example, to return the same format of
         *   message for every target that can't be reached from this room (e.g.
         *   "You can't reach [the target] from the meadow. ") ]
         */
        cannotReachTargetMsg(target)
        {
            return target.tooFarAwayMsg;
        }

        /*
         *   Get the connector object explicitly or implicitly defined on prop, even if it uses the
         *   asExit macro. If it's not an object, return nil;
         */
        getConnector(prop)
        {

            if(propType(prop) == TypeObject)
            {
                local conn = self.(prop);

                if(conn.ofKind(UnlistedProxyConnector))

                {
                    local dir = conn.direction;

                    prop = dir.dirProp;

                    if(propType(prop) == TypeObject)
                        return self.(prop);
                }

                return conn;

            }

            return nil;

        }

        /*
         *   If we've defined a roomFirstDesc and this room description hasn't been displayed before,
         *   display our roomFirstDesc, otherwise display our desc.
         */
        interiorDesc()
        {
            if(propType(&roomFirstDesc) != TypeNil && !examined)
                roomFirstDesc;
            else
                desc;
        }

        /*
         *   The description of this room to be used when it has not previously examined (and is thus
         *   being described fot the first time). If this is left as nil, we simply use the desc
         *   instead.
         */
        roomFirstDesc = nil

        /*
         *   Check whether this room is familiar by farming out the question to the relevant xxxFamiliar
         *   prop, which game code will need to define if this is different for different actors.
         */
        isFamiliar(prop = &familiar)
        {
            return self.(prop);
        }
    ;

    /*
     *   A Door is something that can be open and closed (and optionally locked),
     *   and which must be open to allow travel through. Doors are defined in pairs,
     *   with each Door representing one side of the door and pointing to the other
     *   side via its otherSide property. */

    class Door: TravelConnector, Thing

        /* A door is generally openable */
        isOpenable = true

        /* Most doors start out closed. */
        isOpen = nil

        /* Doors generally aren't listed separately in room descriptions. */
        isListed = nil

        /*
         *   A door is something fixed in place, not something that can be picked up
         *   and carried around.
         */
        isFixed = true

        /*
         *   By default we leave game authors to decide if and how they want to
         *   report whether a door is open or closed.
         */
        openStatusReportable = nil

        /*
         *   Flag, do we want to attempt to unlock this door via an implicit action
         *   if someone attempts to open it while it's locked?
         */
        autoUnlock = nil

        /*
         *   A physical door is represented by two objects in code, each
         *   representing one side of the door and each present in one of the two
         *   locations the door connects. Each side needs to point to the other side
         *   through its otherSide property.
         */
        otherSide = nil

        /*
         *   We're visible in the dark if the room on the other side of us is
         *   illuminated
         */
        visibleInDark
        {
            if(destination != nil && transmitsLight)
                return destination.isIlluminated;

            return nil;
        }

        /*   A Door is something it makes sense to go through. */
        canGoThroughMe = true

        /*   Make a Door open (stat = true) or closed (stat = nil) */
        makeOpen(stat)
        {
            /*  Carry out the inherited handling. */
            inherited(stat);

            /*
             *   If we have an otherSide, make it open or closed at the same time so
             *   both sides of the Door stay in sync.
             */
            if(otherSide != nil)
            {
                otherSide.isOpen = stat;
                if(stat)
                    otherSide.opened = true;
            }
        }

        /*  Make a Door locked (stat = true) or unlocked (stat = nil) */
        makeLocked(stat)
        {
            /* Carry out the inherited handling. */
            inherited(stat);

            /*
             *   If we have an otherSide, make it locked or unlocked at the same
             *   time so both sides of the Door stay in sync.
             */
            if(otherSide != nil)
                otherSide.isLocked = stat;
        }

        /*
         *   The most likely barrier to travel through a door is that the door is
         *   closed and locked, so we check for than after the other kinds of travel
         *   barrier.
         */

        checkTravelBarriers(traveler)
        {
            /*
             *   Carry out the inherited checking of travel barriers and return nil
             *   if they fail to indicate that travel through the door is not
             *   possible.
             */
            if(inherited(traveler) == nil)
                return nil;

            /*  If the Door isn't open, try to open it via an implicit action. */
            if(!isOpen)
            {
                /*
                 *   If it's the player character that's trying to move, try opening
                 *   the door via an implicit action and display the result as an
                 *   implicit action report.
                 */
                if(gPlayerChar.isOrIsIn(traveler))
                {
                    if(tryImplicitAction(Open, self))
                        "<<gAction.buildImplicitActionAnnouncement(true)>>";
                }

                /*
                 *   Otherwise get the traveler to try to open the door via an
                 *   implicit action.
                 */
                else if(tryImplicitActorAction(traveler, Open, self))
                {
    //                /*
    //                 *   If the player character can see the traveler open the door,
    //                 *   report the fact that the traveler does so.
    //                 */
    //                if(gPlayerChar.canSee(traveler))
    //                    sayTravelerOpensDoor(traveler);
    //
    //                else if(otherSide && gPlayerChar.canSee(otherSide))
    //                    sayDoorOpens();

                }

                /*
                 *   If we're not allowed to open this door via an implicit action
                 *   (because opening it is marked as dangerous or nonObvious at the
                 *   verify stage) display a message explaining why the travel can't
                 *   be carried out, provided the player char can see the traveler.
                 */

                else if(gPlayerChar.canSee(traveler))
                {
                    local obj = self;
                    gMessageParams(obj);

                    say(cannotGoThroughClosedDoorMsg);
                }
            }

            /*
             *   We pass the travel barrier test if and only if the door ends up
             *   open.
             */
            return isOpen;
        }

        /*
         *   Message to display when the player character sees the traveler opening
         *   this door.
         */
        sayTravelerOpensDoor(traveler)
        {
            gMessageParams(traveler);
            local obj = self;
            gMessageParams(obj);
            DMsg(npc opens door, '{The subj traveler} open{s/ed} {the
                obj}. ');

        }

        /*
         *   Message to display when the door is opened from the other side so the
         *   player character can't see who is opening it.
         */
        sayDoorOpens()
        {
            local obj = otherSide;
            gMessageParams(obj);
            DMsg(door opens, '{The subj obj} open{s/ed}. ');
        }

        /*  Execute travel through this door. */
        execTravel(actor, traveler, conn)
        {
            /*
             *   Carry out the inherited handling (which delegates most of the work
             *   to our destination).
             */
            inherited(actor, traveler, conn);

            /*
             *   If the actor carrying out the travel is the player character, note
             *   that the player character now knows where both sides of the door
             *   lead to.
             */
            if(otherSide != nil && actor == gPlayerChar &&
               actor.isIn(destination))
            {
                otherSide.isDestinationKnown = true;
                isDestinationKnown = true;
            }
        }

        /*
         *   The message to display if travel is attempted through this door when
         *   it's closed and we're not allowed to open it via an implicit action.
         */
        cannotGoThroughClosedDoorMsg =  BMsg(cannot go through closed door,
                                             '{The subj obj} {is} in the way. ')

        /*
         *   By default the player character doesn't start off knowing where this
         *   door leads. Once the pc has been through the door in either direction
         *   this becomes true on both sides of the door.
         */
        isDestinationKnown = nil

        /*   Preinitialize a door */
        preinitThing()
        {
            /*  Carry out the inherited handling */
            inherited;

            /*
             *   in addition to carrying out Thing's preinitialization, carry out
             *   some additional housekeeping to ensure that this door is in sync
             *   with its other side.
             */
            if(otherSide == nil)
            {
                /*
                 *   If the otherSide hasn't been defined and we're compiling for
                 *   debugging, display a warning message.
                 */
                #ifdef __DEBUG
                "WARNING!!! <<theName>> in << getOutermostRoom != nil ?
                  getOutermostRoom.name : 'nil'>> has no otherside.<.p>";

                #endif
            }
            else
            {
                /*
                 *   If our otherSide doesn't already point to us, make it do so.
                 *   This allows game authors to get away with only specifying one
                 *   side of the connection.
                 */
                if(otherSide.otherSide != self)
                    otherSide.otherSide = self;

                /*
                 *   If we've made one side of the door locked, the chances are we
                 *   intend the other side of the door to start out locked too.
                 */
                if(isLocked)
                    otherSide.isLocked = true;

                /*
                 *   Likewise, if we've made one side of the door open, the chances
                 *   are we intend the other side of the door to start out open too.
                 */
                if(isOpen)
                    otherSide.isOpen = true;

                /*  Add the other side to our list of facets. */
                getFacets += otherSide;

            }
        }

        /*  The destination is the room to which this door leads. */
        destination()
        {
            /*  If we don't have an other side, then we don't lead anywhere. */
            if(otherSide == nil)
                return nil;

            /* Otherwise this door leads to the room containing its other side */
            return otherSide.getOutermostRoom;
        }

        /*  Going through a door is the same as traveling via it. */
        dobjFor(GoThrough)
        {
            preCond = [travelPermitted, touchObj, objOpen]

            action() { travelVia(gActor); }
        }

        /*  Entering a door is the same as going through it. */
        dobjFor(Enter) asDobjFor(GoThrough)

        iobjFor(PushTravelThrough)
        {
            preCond = [travelPermitted, touchObj, objOpen]
        }

        /*
         *   The appropriate action for push an object via a door is
         *   PushTravelThrough
         */
        PushTravelVia = PushTravelThrough

        /*
         *   Display message announcing that traveler has left via this door. The
         *   traveler would normally be an NPC visible to the player character.
         */
        sayDeparting(traveler)
        {
            gMessageParams(traveler);
            DMsg(say departing through door, '{The subj traveler} {leaves} through
                {1}. ', theName);
        }

        /*
         *   Display message announcing that follower is following leader through
         *   this door.
         */
        sayActorFollowing(follower, leader)
        {
            /* Create message parameter substitutions for the follower and leader */
            gMessageParams(follower, leader);

            DMsg(say following through door, '{The subj follower} follow{s/ed} {the
                leader} through {1}. ', theName);
        }

        traversalMsg = BMsg(traverse door, 'through {1}', theName)
    ;

    /* Base mix-in class for defining various types of double-sided (two-way) travel connectors */
    class DSBase: object
       /* The two rooms we connnect. */
        room1 = nil
        room2 = nil

        /* Short service methods that can be used to abbreviate game code */
        /* Test whether the player character is in our room1 */
        inRoom1 = (room1 && gPlayerChar.isIn(room1))

        /* Test whether the player character is in our room2 */
        inRoom2 = (room2 && gPlayerChar.isIn(room2))

        /* return a or b depending on which room the player char is in */
        byRoom(args) { return inRoom1 ? args[1] : args[2]; }

        /*
         *   Our destination depends on our origin.
         */
        getDestination(origin)
        {
            /* If we start out from room1 then this connector leads to room2 */
            if(origin == room1)
                return room2;

            /* If we start out from room2 then this connector leads to room1 */
            if(origin == room2)
                return room1;

            /* Otherwise, it doesn't lead anywhere. */
            return nil;
        }

        /*
         *   Returns the direction property to which this connector object is connected in
         *   the player character's current location, e.g. &west. This is used on by
         *   DirState to add the appropriate adjective (e.g. 'west') to our vocab,
         *   so that the player can refer to us by the direction in which we lead.
         *   If you don't want this direction to be included in the vocab of this
         *   object, override attachedDir to nil.
         */
        attachedDir()
        {
            /*
             *   Get the direction this door leads in from the player character's current location.
             */
            local dir = doorDir();

            /*
             *   Return the direction property of that location which points to this
             *   door.
             */
            return dir == nil ? nil : dir.dirProp;
        }

        /*
         *   Returns the direction in which this connector object leads from the player character's
         *   current location (or nil, if the player character isn't in one of the rooms this door is
         *   located it).
         */
        doorDir()
        {
             /* Get the player character's current room location. */
            local loc = gPlayerChar.getOutermostRoom;

            /*
             *   Return the direction that points to us from the player character's current location.
             */
            return connDir(loc);

        }

        connDir(origin)
        {
            /*
             *   Get the direction object whose dirProp corresponds to the dirProp
             *   on origin (a room) which points to this object (we do this because
             *   Direction.allDirections provides the only way to get at a list of
             *   every dirProp).
             */
            local dir = Direction.allDirections.valWhich(
                { d: origin.propType(d.dirProp) == TypeObject
                && origin.(d.dirProp) == self });

            return dir;
        }

        /*
         *   The direction an actor needs to travel in to travel via us from room1. This is set up in
         *   Room initObj();
         */
        room1Dir() { return connDir(room1); }

        /*
         *   The direction an actor needs to travel in to travel via us from room2. This is set up in
         *   Room initObj();
         */
        room2Dir()  { return connDir(room2); }

        /*
         *   The name of our direction of travel from the point of view of the player character
         *   depending on whether the pc is in room1 or room2.
         */
        dirName = inRoom1 ? room1Dir.name : room2Dir.name

        /* Our destination is known if each of the rooms we connect is either visited or familiar. */
        isDestinationKnown = (room1.familiar || room1.visited) && (room2.familiar || room2.visited)

    ;

    /* Mix-in class for creating double-sided (two-way) doors, passages, stairs and the like */
    class DSCon: DSBase, MultiLoc

        /* We are located in the two rooms we connect. */
        initialLocationList = [room1, room2]

        /*
         *   Our destination depends on which room the actor going through us starts out in. If it's
         *   room1 our destination is room2, otherwise it's room1.
         */
    //    destination = gActor.getOutermostRoom == room1 ? room2 : room1

        /*
         *   By default we can vary the description of the passage according to the
         *   location of the actor (and hence, according to which side it's viewed
         *   from), but if we want the passage to be described in the same way from
         *   both sides then we can simply override the desc property with a single
         *   description. [SYMCOMM EXTENSION]
         */
        desc()
        {
            if(gActor.isIn(room1))
                room1Desc;
            else
                room2Desc;
        }

        /*  Our description as seen from room1  */
        room1Desc = nil

        /*  Our description as seen from room2 */
        room2Desc = nil

        /*
         *   We're visible in the dark if the room on either side of us is
         *   illuminated [SYMCOMM EXTENSION]
         */
        visibleInDark
        {
            if(transmitsLight && room1 && room2)
                return room1.isIlluminated || room2.isIlluminated;

            return nil;
        }

    ;

    /*
     *   A DSDoor (Double Sided Door) can be used to implement a door as a single object present in two
     *   locations (defined on its room1 and room2 properties) instead of having to define the two sides
     *   of the door as two separate Door objects. This will often be convenient whenever the two sides
     *   of the door are sufficiently similar (in having the name name and vocab, even if their
     *   descriptions vary slightly, which can be achieved by writing a description that varies
     *   according to the location of the player character). Although both sides of a DSDoor need to be
     *   referred to by the player using the same vocab words, players can additionally refer to a
     *   DSDoor by its direction relative to the player character (e.g. EAST DOOR or NW DOOR); this is
     *   handled automatically by the DSDoor class without game authors needing to handle such
     *   directional adjectives themselves.
     */
    class DSDoor: DSCon, Door

        /*
         *   As we're a double-sided door, we only need to manage our own isOpen status; we don't need
         *   to refer to our other side.
         */
        makeOpen(stat) { isOpen = stat; }

        /*
         *   As we're a double-sided door, we only need to manage our own isOLocked status; we don't
         *   need to refer to our other side.
         */
        makeLocked(stat) { isLocked = stat; }

        /*
         *   We need to use DSCon's preinitTbing() method rather than Door's, since Door's does a whole
         *   lot with our otherSide property, which we don't need or want to use.
         */
        preinitThing() { inherited DSCon(); }

        /*
         *   The lockability of this Door (notLockable, lockableWithKey, lockableWithoutKey, or
         *   indirectLockable). This can be different for each side of the door, in which case set
         *   room1Lockability and room2Lockability individually and the game will use the lockability
         *   appropriate to the location of the current actor. If you want the same lockability for both
         *   sides of the door, simply override lockability accordingly.
         */
        lockability = (gActor.getOutermostRoom == room1 ? room1Lockability : room2Lockability)

        /*
         *   Our lockability on the room1 side of the door.
         */
        room1Lockability = notLockable

        /*
         *   Our lockability on the room2 side of the door.
         */
        room2Lockability = notLockable

    ;

      /*
       *   A TravelConnector is an object that can be attached to the directional
       *   exit property of a room to facilitate (or optionally block) travel in the
       *   associated direction and carry out any side-effects of that travel. A
       *   TravelConnector may be used as an abstract object to implement travel, or
       *   a subclass of TravelConnector such as Door, Passage, StairwayUp or
       *   StairwayDown may be used to represent a physical object via which travel
       *   occurs. The Room class also inherits from TravelConnector.
       *
       *   Whether the base TravelConnector class or one of its subclasses is used,
       *   travel is carried out via a TravelConnector by calling its travelVia()
       *   method.
       */
    class TravelConnector: object

        /*
         *   Is this connector apparent? That is, would it be apparent to an
         *   observer under normal lighting conditions, as opposed to being
         *   concealed? By default we'll suppose a TravelConnector is apparent
         *   unless it's explicitly hidden.
         */
        isConnectorApparent = !isHidden

        /*
         *   Should this exit be shown in the exit lister? By default we'll assumed
         *   it should be it it's visible.
         */
        isConnectorListed = isConnectorVisible

        /*
         *   Does light pass through this TravelConnector from its destination (so
         *   that it's visible in the dark even its location is dark.).
         */
        transmitsLight = true

        /*
         *   A TravelConnector (or at least, the exit it represents) is visible if
         *   it's apparent (i.e. not concealed in some way) and if the lighting
         *   conditions are adequate, or if it's visible in the dark.
         */
        isConnectorVisible()
        {
            local loc = gPlayerChar.getOutermostRoom();
            local dest = getDestination(loc);
            return (isConnectorApparent &&
                              (loc.isIlluminated
                                  || (dest != nil && dest.isIlluminated
                                      && transmitsLight)
                               || visibleInDark));
        }

        /* The room to which this TravelConnector leads when it is traversed */
        destination = nil

        /*
         *   The room to which this TravelConnector leads when it is traversed from
         *   origin.
         */
        getDestination(origin)
        {
            return destination;
        }

        /*
         *   Does the player char know where this travel connector leads? By default
         *   s/he doesn't until s/he's visited its destination, but this could be
         *   overridden for an area the PC is supposed to know well when the game
         *   starts, such as their own house.
         */
        isDestinationKnown()
        {
            local loc = gPlayerChar.getOutermostRoom();
            local dest = getDestination(loc);
            return (dest != nil && dest.isDestinationKnown);
        }

        /*   A travel connector is usually open. */
        isOpen = true

        /*
         *   Carrier out travel via this connector, first checking that travel
         *   through this connector is permitted for this actor.
         */
        travelVia(actor)
        {
            /*
             *   The traveler is the object actually doing the travelling; usually
             *   it's just the actor, but if the actor is in a vehicle, it will be
             *   the vehicle.
             */
            local traveler = getTraveler(actor);

            /*
             *   Check the travel barriers on this TravelConnector to ensure that
             *   travel is permitted. If so carry out the travel. If not
             *   checkTravelBarriers will have reported the reason why travel is
             *   blocked.
             */
            if(checkTravelBarriers(traveler))
                execTravel(actor, traveler, self);
        }

        /*
         *   Get the traveler associated with this actor. Normally the traveler will
         *   be the same as the actor, but if the actor is in a vehicle, then the
         *   traveler will be the vehicle.
         */
        getTraveler(actor)
        {

            local loc = actor.location;

            while(loc != nil && !loc.ofKind(Room))
            {
                if(loc.isVehicle)
                    return loc;

                loc = loc.location;
            }

            return actor;
        }

        /*  Execute the travel for this actor via this connector */
        execTravel(actor, traveler, conn)
        {
            local loc = traveler.getOutermostRoom();
            local dest = getDestination(loc);

            /* If we have a destination, let our destination handle it */
            if(dest != nil)
                dest.execTravel(actor, traveler, conn);

            else
            {
                /*
                 *   Carry out the beforeTravel notifications, since this can't be
                 *   done by our destination, but something in scope may still want
                 *   to react to or prohibit the attempt to travel.
                 */
                beforeTravelNotifications(actor);

                /*
                 *   Then call our noteTraversal method to carry out the
                 *   side-effects of travel; since we don't lead anywhere this may
                 *   be the only reason we exist. If, however, our noteTraversal()
                 *   method fails to display any output, instead display a report
                 *   that this connector doesn't lead anywhere.
                 */
                if(gOutStream.watchForOutput({:noteTraversal(actor)}) == nil)
                    sayNoDestination();
            }
        }

        /*
         *   Display a message saying that this travel connector doesn't actually
         *   lead anywhere; this may be needed if our destination is nil and our
         *   noteTraversal() method doesn't display anything.
         */
        sayNoDestination()
        {
            DMsg(no destination, 'That{dummy} {doesn\'t} lead anywhere. ');
        }

        /*
         *   If the actor doing the traveling is the player character, display the
         *   travelDesc. Note that although this might normally be a simple
         *   description of the travel, the travelDesc method could also be used to
         *   carry out any other side-effects of the travel via this connector.
         */
        noteTraversal(actor)
        {
            if(actor == gPlayerChar && !(gAction.isPushTravelAction && suppressTravelDescForPushTravel))
            {
                travelDesc;
                "<.p>";
            }

            /*
             *   Note that the actor has traversed us. If the actor is in a vehicle, also note the
             *   vehicle has traversed us.
             */
            local travelers = (actor.location && actor.location.isVehicle)
                ? [actor, actor.location] : [actor];

            traversedBy = traversedBy.appendUnique(travelers);
        }

        /*
         *   A list of the actors, vehicles and pushTraverers that have traversed this TravelConnector.
         *   This is maintained by the noteTraversal(), so game code should normally treat this property
         *   as read-only.
         */
        traversedBy = []

        /*
         *   Test whether this TravelConnector has been traversed by traveler (which may be an actor, a
         *   vehicle, or something pushed through the TravelConnector by an actor).
         */
        hasBeenTraversedBy(traveler)
        {
            /* Return true if traveler is in our travdersedBy list. */
            return traversedBy.indexOf(traveler) != nil;
        }

        /* Have we been traversed by the player character? Return true if and only if we have. */
        traversed = (hasBeenTraversedBy(gPlayerChar))

        /* Carry out the before travel notifications for this actor. */
        beforeTravelNotifications(actor)
        {
            /*
             *   Call the before travel notifications on every object that's in
             *   scope for the actor.
             */
            Q.scopeList(actor).toList.forEach({x: x.beforeTravel(actor, self)});

             /*
              *   Finall, carry out before travel notifications in all the regions
              *   the traveler starts out in.
              */
            foreach(local reg in actor.getOutermostRoom.allRegions)
                reg.regionBeforeTravel(actor, self);
        }

        /* Carry out the after travel notifications for this actor */
        afterTravelNotifications(actor)
        {
            /*
             *   Call the after travel notification for every object that's in scope
             *   for this actor.
             */
            Q.scopeList(actor).toList.forEach({x: x.afterTravel(actor, self)});

            /*
             *   Finally, carry out after travel notifications in all the regions
             *   the traveler ends up in.
             */
            foreach(local reg in actor.getOutermostRoom.allRegions)
                reg.regionAfterTravel(actor, self);

        }

        /*
         *   Method that should return true is actor is allowed to pass through this
         *   TravelConnector and nil otherwise. We allow travel by default but this
         *   could be overridden to block travel under certain conditions.
         */
        canTravelerPass(actor) { return true; }

        /*
         *   If canTravelerPass returns nil explainTravelBarrier should display a
         *   message explaining why travel has been prohibited.
         */
        explainTravelBarrier(actor) { }

        /*
         *   Carry out any side effects of travel if the traveler is the player
         *   character. Typically we might just display some text describing the
         *   travel here, but this method could be used for any side-effects of the
         *   travel. If the TravelConnector is mixed in with an EventList class then
         *   the default behaviour is to call the doScript() method here to drive
         *   the EventList.
         */
        travelDesc()
        {
            if(ofKind(Script))
                doScript();
        }

        /*
         *   an additional TravelBarrier or a list of TravelBarriers to check on
         *   this TravelConnector to see if travel is allowed.
         */
        travelBarriers = nil

        /*
         *   Check all the travel barriers associated with this connector to
         *   determine whether the traveler is allowed to pass through this travel
         *   connector.
         */
        checkTravelBarriers(traveler)
        {
            /* first check our own built-in barrier test. */
            if(!canTravelerPass(traveler))
            {
                /*
                 *   If travel is not permitted display a message explaining why and
                 *   then return nil to cancel the travel.
                 */
                explainTravelBarrier(traveler);
                return nil;
            }

            /* Then check any additional travel barrier objects */
            if(valToList(travelBarriers).indexWhich({b: b.checkTravelBarrier
                                                    (traveler,  self) == nil}))
                return nil;

            /*
             *   If we've reached this point then no travel barrier is objecting to
             *   the traveler traveling to this connector, so return true to signal
             *   that travel is permitted.
             */
            return true;
        }

        /*
         *   Display a message to say that an actor is departing via this connector.
         *   On the base class the default behaviour is to describe the departure
         *   via a compass direction. The actor in question would normally be an NPC
         *   visible to the player character.
         */
        sayDeparting(traveler)
        {
            /* Create a message parameter substitution for the traveler */
            gMessageParams(traveler);

            /* Find the direction to which this connector is attached. */
            local depdir = getDepartingDirection(traveler);

            if(depdir == nil)
                DMsg(say departing vague, '<.p>{The subj traveler} {leaves} the
                    area. ');
            else
                DMsg(say departing dir, '<.p>{The subj traveler} {goes} {1}. '
                     , depdir.departureName);

        }

        /*
         *   Display a message to say that follower is following leader in the
         *   direction of this connector.
         */
        sayActorFollowing(follower, leader)
        {
            /* Create a message parameter substitution for the traveler */
            gMessageParams(follower, leader);

            /* Find the direction to which this connector is attached. */
            local depdir = getDepartingDirection(follower);

            if(depdir == nil)
                DMsg(say following vague, '<.p>{The subj follower} follow{s/ed} {the
                    leader}. ');
            else
                DMsg(say following dir, '<.p>{The subj follower} follow{s/ed} {the
                    leader} {1}. ', depdir.departureName);

        }

        /*
         *   Create a phrase describing the direction of travel through this
         *   connector (e.g. 'to the north')
         */
        traversalMsg()
        {
            local depDir = getDepartingDirection(gActor);

            return  BMsg(traverse connector, '{1}', depDir.departureName);
        }

        /*
         *   Get the direction traveler needs to go in to traverse this connector
         *   from traveler's current location.
         */
        getDepartingDirection(traveler)
        {
            /* Note what room the traveler is in prior to departure */
            local room = traveler.getOutermostRoom;

            /* Return the direction to which this connector is attached. */
            return room.getDirection(self);
        }

        /*
         *   The TravelVia action is supplied so game code can execute a TravelVia
         *   action on a TravelConnector; there is no TRAVEL VIA command that can be
         *   issued directly by a player, but a player command may be translated
         *   into this action.
         */
        dobjFor(TravelVia)
        {
            preCond = [travelPermitted]

            action()
            {
                /*
                 *   For now, we just call the travelVia() method on the
                 *   TravelConnector. Subsequentlly we might add appropriate code
                 *   for the other action phases.
                 */
                travelVia(gActor);
            }
        }

        dobjFor(GoThrough)
        {
            preCond = [travelPermitted]
        }

        iobjFor(PushTravelThrough)
        {
            preCond = [travelPermitted]
            verify()
            {

            }

            check() { checkPushTravel(); }
        }

        /* Check the travel barriers on the indirect object of the action */
        checkPushTravel()
        {
            /*
             *   First check the travel barriers for the actor doing the pushing.
             *   Only go on to check those for the item being pushed if the actor
             *   can travel, so we don't see the same messages twice.
             */
            if(checkTravelBarriers(gActor))
                checkTravelBarriers(gDobj);
        }

        /*
         *   The appropriate PushTravelAction for pushing something something
         *   through a TravelConnector.
         */
        PushTravelVia = PushTravelThrough

        /*
         *   If we display a message for pushing something via us, we probably don't also want the
         *   travelDesc describing the actor's travel. Game code can override if both messages are
         *   wanted when push-travelling.
         */
        suppressTravelDescForPushTravel = true

    ;

    /*
     *   An UnlistedProxyConnector is a special kind of TravelConnector created by
     *   the asExit macro to make one exit do duty for another. There is probably
     *   never any need for this class to be used explicitly in game code, since
     *   game authors will always use the asExit macro instead.
     */
    class UnlistedProxyConnector: TravelConnector

        /* The direction property for which we're a proxy. */
        proxyForProp = direction.dirProp

        /*
         *   The loc parameter should contain the room in which this UnlistedProxyConnector is used, but
         *   calling code will need to supply it.
         */
        proxyForConnector(loc)
        {
            local ptype = loc.propType(proxyForProp);

            return ptype == TypeObject ? loc.(proxyForProp) : ptype;

        }

        /* An UnlistedProxyConnector is never listed as an exit in its own right. */
        isConnectorListed = nil

        /*
         *   We'll assume an UnlistedProxyListedConnector is always 'visible', since
         *   it's a proxy for some other connector which will handle the actual
         *   visibility conditions.
         */
        isConnectorVisible = true

        /* Carry out travel via this connector. */
        travelVia(traveler)
        {
            /* Get the travel connector for which we're a proxy. */
            local conn = proxyForConnector(traveler.getOutermostRoom);

            /*
             *   If the connector is actually a TravelConnector, then execute travel via that connector.
             */
            if(objOfKind(conn,TravelConnector))
                conn.travelVia(traveler);

            /*
             *   Otherwise, the direction we're a proxy for points to something else that's not a
             *   TravelConnector, such as a string or method, in which case call the nonTravel()
             *   function to handle
             it.*/
            else
                nonTravel(traveler.getOutermostRoom, direction);

        }

        /* Construct a new UnlistedProxyConnector. */
        construct(dir_)
        {
            /* Note the direction this connector is a proxy for. */
            direction = dir_;

        }

        /*
         *   We don't want an UnlistedProxyConnector to trigger any travel
         *   notifications since these will be triggered - if appropriate - on the
         *   real connector we point to.     */

        beforeTravelNotifications(actor) {}
        afterTravelNotifications(actor) {}

        /*
         *   Return the actual destination, if any, an actor will arrive at by traversing the connector
         *   we're a proxy for from origin.
         */
        getDestination(origin)
        {
            local conn = proxyForConnector(origin);

            return objOfKind(conn, TravelConnector) ? conn.getDestination(origin) : nil;

        }
         /*
          *   Handle going through this connector by calling our travelVia() method to execute travel
          *   via the connector for which we're a proxy.
          */
        dobjFor(GoThrough)
        {
            preCond = [travelPermitted]

            action { travelVia(gActor); }
        }
    ;

    /*
     *   A TravelBarrier is an object that can optionally be associated with one or
     *   more TravelConnectors to define additional conditional (or even
     *   unconditional) barriers preventing travel.
     */
    class TravelBarrier: object

        /*
         *   This method should return true to permit the traveler to travel via
         *   connector and nil to prohibit travel. By default we simply allow travel
         *   but particular instances will need to override this method to specify
         *   the conditions under which travel is or is not permitted.
         */
        canTravelerPass(traveler, connector)
        {
            return true;
        }

        /*
         *   Display some text explaining why traveler is not permitted to travel
         *   via connector when canTravelerPass() returns nil.
         */
        explainTravelBarrier(traveler, connector)
        {
        }

        /*
         *   Check whether traveler can pass through this connector. If it can,
         *   return true; otherise explain why travel is disallowed and return nil.
         */
        checkTravelBarrier(traveler, connector)
        {
            if(canTravelerPass(traveler, connector))
                return true;

            explainTravelBarrier(traveler, connector);
            return nil;
        }
    ;

    /*
     *   A Direction object represents a direction in which an actor might attempt
     *   to travel. The library defines eight compass directions (north, south,
     *   etc.) and a further eight special directions (in, out, up, down, port,
     *   starboard, fore and aft), but game code can define additional directions if
     *   required.
     *
     *   The convention that should be followed in naming a Direction object is to
     *   use the name of the direction followed by Dir; e.g. the Direction object
     *   corresponding to north is called northDir. Custom directions should follow
     *   the same convention, since it is assumed by the goInstead() and goNested()
     *   macros.
     */
    class Direction: object

        /*
         *   The exit property of a room association with this Direction, e.g.
         *   &north (corresponding to northDir).
         */
        dirProp = nil

        /*
         *   The name of this direction, e.g. 'north'. This is the name that appears
         *   in the exit lister.
         */
        name = nil

        /*   Class property: a LookupTable matching names to direction objects. */
        nameTab = static new LookupTable()

        /*
         *   The name to use when departing via this direction, e.g. 'to the north'
         */
        departureName = nil

        /*
         *   Initialize.  We'll use this routine to add each Direction instance to
         *   the master direction list (Direction.allDirections) during
         *   pre-initialization.
         */
        initializeDirection()
        {
            /* add myself to the master direction list */
            Direction.allDirections.append(self);

            /* add myself to the master direction table */
            Direction.nameTab[name] = self;
        }

        /*
         *   Class initialization - this is called once on the class object.
         *   We'll build our master list of all of the Direction objects in
         *   the game, and then sort the list using the sorting order.
         */
        classInit()
        {
            /* initialize each individual Direction object */
            forEachInstance(Direction, { dir: dir.initializeDirection() });

            /*
             *   sort the direction list according to the individual Directin
             *   objects' defined sorting orders
             */
            allDirections.sort(SortAsc, {a, b: a.sortingOrder - b.sortingOrder});
        }

        /*
         *   Our sorting order in the master list.  We use this to present
         *   directions in a consistent, aesthetically pleasing order in
         *   listings involving multiple directions.  The sorting order is
         *   simply an integer that gives the relative position in the list;
         *   the list of directions is sorted from lowest sorting order to
         *   highest.  Sorting order numbers don't have to be contiguous,
         *   since we simply put the directions in an order that makes the
         *   sortingOrder values ascend through the list.
         */
        sortingOrder = 1

        /*
         *   A Class property containing a Vector of all the directions defined in
         *   the game (the 16 defined in the library plus any additionasl custom
         *   directions defined in game code)
         */
        allDirections = static new Vector(12)

        /*   The direction that is opposite to this one. */
        opposite = nil

        /*   The dirProp that's the opposite to prop */
        oppositeProp(prop)
        {
            local dir = allDirections.valWhich({d: d.dirProp == prop});

            return dir == nil ? nil :
            (dir.opposite == nil ? nil : dir.opposite.dirProp);
        }

        /* The direction to which prop points. */
        propDir(prop)
        {
            return allDirections.valWhich({d: d.dirProp == prop});
        }

    ;

    /* The compass directions */
    class CompassDirection: Direction
        initializeDirection()
    	{
    	   /* Carry out the inherited handling */
    	   inherited();

    	   /* Add myself to the list of compass directions */
    	   CompassDirection.compassDirections.append(self);
    	}

    	/*
    	  * A Class property containing a Vector of all the compass
    	  * directions defined in the game.
    	  */
    	compassDirections = static new Vector(8)
    ;

    /*  The sixteen directions defined in the library */
    northDir: CompassDirection
        dirProp = &north
        name = BMsg(north, 'north')
        departureName = BMsg(depart north, 'to the north')
        sortingOrder = 1000
        opposite = southDir
    ;

    eastDir: CompassDirection
        dirProp = &east
        name = BMsg(east, 'east')
        departureName = BMsg(depart east, 'to the east')
        sortingOrder = 1100
        opposite = westDir
    ;

    southDir: CompassDirection
        dirProp = &south
        name = BMsg(south, 'south')
        departureName = BMsg(depart south, 'to the south')
        sortingOrder = 1200
        opposite = northDir
    ;

    westDir: CompassDirection
        dirProp = &west
        name = BMsg(west, 'west')
        departureName = BMsg(depart west, 'to the west')
        sortingOrder = 1300
        opposite = eastDir
    ;

    northeastDir: CompassDirection
        dirProp = &northeast
        name = BMsg(northeast, 'northeast')
        departureName = BMsg(depart northeast, 'to the northeast')
        sortingOrder = 1400
        opposite = southwestDir
    ;

    northwestDir: CompassDirection
        dirProp = &northwest
        name = BMsg(northwest, 'northwest')
        departureName = BMsg(depart northwest, 'to the northwest')
        sortingOrder = 1500
        opposite = northeastDir
    ;

    southeastDir: CompassDirection
        dirProp = &southeast
        name = BMsg(southeast, 'southeast')
        departureName = BMsg(depart southeast, 'to the southeast')
        sortingOrder = 1600
        opposite = northwestDir
    ;

    southwestDir: CompassDirection
        dirProp = &southwest
        name = BMsg(southwest, 'southwest')
        departureName = BMsg(depart southwest, 'to the southwest')
        sortingOrder = 1700
        opposite = northeastDir
    ;

    downDir: Direction
        dirProp = &down
        name = BMsg(down, 'down')
        departureName = BMsg(depart down, 'down')
        sortingOrder = 2000
        opposite = upDir
    ;

    upDir: Direction
        dirProp = &up
        name = BMsg(up, 'up')
        departureName = BMsg(depart up, 'up')
        sortingOrder = 2100
        opposite = downDir
    ;

    inDir: Direction
        dirProp = &in
        name = BMsg(in, 'in')
        departureName = BMsg(depart in, 'inside')
        sortingOrder = 3000
        opposite = outDir
    ;

    outDir: Direction
        dirProp = &out
        name = BMsg(out, 'out')
        departureName = BMsg(depart out, 'out')
        sortingOrder = 3100
        opposite = inDir
    ;

    /* Directions for use aboard a vessel such as a ship */
    class ShipboardDirection: Direction
        initializeDirection()
    	{
    	   /* Carry out the inherited handling */
    	   inherited();

    	   /* Add myself to the list of shipboard directions */
    	   ShipboardDirection.shipboardDirections.append(self);
    	}

        /*
    	  * A Class property containing a Vector of all the shipboard
    	  * directions defined in the game.
    	  */
    	shipboardDirections = static new Vector (4)
    ;

    portDir: ShipboardDirection
        dirProp = &port
        name = BMsg(port, 'port')
        departureName = BMsg(depart port, 'to port')
        sortingOrder = 4000
        opposite = starboardDir
    ;

    starboardDir: ShipboardDirection
        dirProp = &starboard
        name = BMsg(starboard, 'starboard')
        departureName = BMsg(depart starboard, 'to starboard')
        sortingOrder = 4100
        opposite = portDir
    ;

    foreDir: ShipboardDirection
        dirProp = &fore
        name = BMsg(forward, 'forward')
        departureName = BMsg(depart forward, 'forward')
        sortingOrder = 4200
        opposite = aftDir
    ;

    aftDir: ShipboardDirection
        dirProp = &aft
        name = BMsg(aft, 'aft')
        departureName = BMsg(depart aft, 'aft')
        sortingOrder = 4300
        opposite = foreDir
    ;

    /*
     *   A Region is an object representing several rooms or even several other
     *   Regions.
     */
    class Region: object

        /*
         *   This region can optionally be in one or more regions. The regions
         *   property hold the region or a the list of regions I'm in.
         */
        regions = nil

        /*
         *   A Room can't be in another Room or a Thing, but it can notionally be in
         *   a Region, so we check to see if we're in the list of our regions.
         */
        isIn(region)
        {
            return valToList(regions).indexWhich({x: x.isOrIsIn(region)}) != nil;
        }

        /*  Is this Region either itself region or contained within in region */
        isOrIsIn(region)
        {
            return region == self || isIn(region);
        }

        /*
         *   A list of all the regions this Region is within; in addition to any
         *   regions this Region is directly in (defined on its regions property)
         *   this will include all the regions it's indirectly in (i.e. any regions
         *   the regions it's in are in and so forth).
         */
        allRegions()
        {
            /* Start with a vector of all the regions we're directly in */
            local thisRegions = new Vector(valToList(regions));

            /*
             *   For each of the regions we're directly in, append all the regions
             *   they're in.
             */
            foreach(local reg in valToList(regions))
                thisRegions.appendUnique(reg.allRegions);

            /*   Convert the Vector to a list and return it. */
            return thisRegions.toList();
        }

        /*
         *   A list of all the rooms in this region. This is built automatically at
         *   preinit and shouldn't be altered by the user/author.
         */
        roomList = nil

        /*
         *   A user-defined list of the rooms in this region. At Preinit this will
         *   be used along with the regions property of any rooms to build the
         *   roomList of this Region.
         */
        rooms = nil

        /*
         *   Build the list of regions in all the rooms in this this region by going
         *   through every room defined in the roomList and adding us to its list of
         *   regions. Note that this is provided as an alternative way to define
         *   what rooms start out in which regions.
         */

        makeRegionLists()
        {
            if(rooms != nil)
            {
                foreach(local r in rooms)
                    r.regions = valToList(r.regions).appendUnique([self]);
            }
        }

        /*
         *   Is the player char familiar with every room in this region. This should
         *   be set to true for a region whose geography the PC starts out familiar
         *   with, such as the layout of his own house.
         */
        familiar = nil

        /*
         *   For games that track different familiarity on different Actors, we can call this method
         *   with &xxxFamiliart to farm out the answer to the appropriate xxxFamiliar property, which
         *   we'll need to define on this region.
         */
        isFamiliar(prop = &familiar)
        {
            return self.(prop);
        }

        /*
         *   Go through all the rooms in this region setting them to familiar if the
         *   region is familiar.
         */
        setFamiliarRooms(prop = &familiar)
        {
            if(isFamiliar(prop))
            {
                /*
                 *   If this Region is familiar then go through each room in the
                 *   list of rooms in the Region and mark it as familiar.
                 */
                foreach(local rm in valToList(roomList))
                {
                    rm.(prop) = true;
                }
            }
        }

        /*
         *   To add an object to our contents we need to add it to the contents of
         *   every room in this region. If the optional vec parameter is supplied it
         *   must be a vector; the rooms will then be added to the vector as well.
         *   The vec parameter is primarily for use by the MultiLoc class.
         */
        addToContents(obj, vec?)
        {
            foreach(local cur in roomList)
            {
                cur.addToContents(obj, vec);
            }
        }

        /*
         *   To remove an object from our contents we need to remove it from the
         *   contents of every room in the region. If the optional vec parameter is
         *   supplied it must be a vector; the rooms will then be removed from the
         *   vector as well.
         */
        removeFromContents(obj, vec?)
        {
            foreach(local cur in roomList)
            {
                cur.removeFromContents(obj, vec);
            }
        }

        /*
         *   Add an additional room (passed as the rm parameter) to our list of
         *   rooms. This method is intended for internal library use at PreInit
         *   only.
         */
        addToRoomList(rm)
        {
            /*
             *   Add rm to our existing roomList, making sure we don't duplicate an
             *   existing entry, and converting the roomList from nil to a list if
             *   isn't a list already.
             */
            roomList = nilToList(roomList).appendUnique([rm]);

            /*  Add rm to the room list of all the regions we're in */
            foreach(local cur in valToList(regions))
                cur.addToRoomList(rm);
        }

        /*
         *   Put extra items in scope when action is carried out in any room in this
         *   region.
         */
        addExtraScopeItems(action)
        {
            /*
             *   Add our list of extraScopeItems to the existing scopeList of the
             *   action, avoiding creating any duplicate entries.
             */
            action.scopeList =
                action.scopeList.appendUnique(valToList(extraScopeItems));

            /*
             *   Add any further additional scope items from any of the regions
             *   that this region is in.
             */
            foreach(local reg in valToList(regions))
                reg.addExtraScopeItems(action);
        }

        /*
         *   A list of items that should be added to the standard scope list for
         *   actions carried out in any room in this region.
         */
        extraScopeItems = []

         /*
          *   This method is invoked when traveler is about to leave this region and
          *   go to dest (the destination room).
          */
        travelerLeaving(traveler, dest) { }

         /*
          *   This method is invoked when traveler is about to enter this region
          *   from origin (the room traveled from.
          */
        travelerEntering(traveler, origin) { }

        /* Carry out before notifications on the region */
        notifyBefore()
        {
            /* First call our own regionBeforeAction() method */
            regionBeforeAction();

            /*
             *   Then call the beforeAction notification on all the regions we're
             *   in.
             */
            foreach(local reg in valToList(regions))
                reg.notifyBefore();
        }

        /*
         *   This method is called just before an action takes places in this
         *   region.
         */
        regionBeforeAction() { }

        /* Carry out after notifications on the region */
        notifyAfter()
        {
            /* First call our own regionAfterAction() method */
            regionAfterAction();

            /*
             *   Then call the afterAction notification on all the regions we're
             *   in.
             */
            foreach(local reg in valToList(regions))
                reg.notifyAfter();
        }

        /* Method called just after an action has taken place in this region. */
        regionAfterAction() { }

        /*
         *   This method is called just before travel takes places in this
         *   region (when traveler is about to travel via connector).
         */
        regionBeforeTravel(traveler, connector) { }

        /*
         *   Method called just after travel has taken place in this region (when
         *   traveler has just traveled via connector).
         */
        regionAfterTravel(traveler, connector) { }

        /*
         *   Should the fastGoTo option be used in this region (i.e. traveling from
         *   one room in the region to another is all done in one turn without the
         *   need for CONTINUE, even if several steps are involved)? Note that the
         *   value of this setting has no effect if gameMain.fastGoTo is true, since
         *   then the fastGoTo setting is always in effect.
         */
        fastGoTo = nil

        /*
         *   Move a MultiLoc (ml) into this region, by moving it into every room in
         *   this Region.
         */
        moveMLIntoAdd(ml)
        {
            roomList.forEach({r: ml.moveIntoAdd(r)});
        }

        /*
         *   Move a MultiLoc (ml) out of this region, by moving it out of every room
         *   in the Region.
         */
        moveMLOutOf(ml)
        {
            roomList.forEach({r: ml.moveOutOf(r)});
        }

        /*
         *   The regionDaemon method is executed on ever region in which the player character is
         *   currently located. By default we call the region's doScript() method so that the if the
         *   region is mixed in with an EventList class, that EventList can be executed.
         */
        regionDaemon { doScript(); }

    ;
    /*
     *   Go through each room and add it to every regions it's (directly or
     *   indirectly) in. Then if the region is familiar, mark all its rooms as
     *   familiar.
     */
    regionPreinit: PreinitObject
        execute()
        {
            forEachInstance(Region, {r: r.makeRegionLists });

            forEachInstance(Room, {r: r.addToRegions()} );

            forEachInstance(Region, { r: r.setFamiliarRooms() } );
        }

    ;

    /*
     *   Function to handle what will probably be non-travel in a direction that doesn't point to exit.
     *   The loc parameter specifies the room we're attempting travel from. For use as a common routine
     *   called by TravelAction, PushTravelDir and UnlistedProxyConnnector.
     *.
     */
    nonTravel(loc, dir)
    {

        /* Note whether we meet the lighting conditions to permit travel */
        local illum = loc.allowDarkTravel || loc.isIlluminated;

        local conn;

        switch (loc.propType(dir.dirProp))
        {
            /*
             *   If there's nothing there, simply display the appropriate message explaining that travel
             *   that way isn't possible.
             */
        case TypeNil:
            if(illum && gActor == gPlayerChar)
                loc.cannotGoThatWay(dir);
            else if(gActor == gPlayerChar)
                loc.cannotGoThatWayInDark(dir);
            break;

            /*
             *   If the direction property points to a double-quoted method or a string, then provided
             *   the illumination is right, we display the string or execute the method. Otherwise show
             *   the message saying we can't travel that way in the dark.
             */
        case TypeDString:
        case TypeCode:
            if(illum)
            {
                /*
                 *   Call the before travel notifications on every object that's in scope for the actor.
                 *   Since we don't have a connector object to pass to the beforeTravel notifications,
                 *   we use the direction object instead.
                 */
                Q.scopeList(gActor).toList.forEach({x: x.beforeTravel(gActor,
                    dir)});

                /*
                 *   If going this way would take us to a known destination that's a Room (so that
                 *   executing the travel should take the actor out of his/her current room) notify the
                 *   current room that the actor is about to depart.
                 */
                local dest;

                if(loc.propType(dir.dirProp) == TypeCode)
                    dest = libGlobal.extraDestInfo[[loc, dir]];
                else
                    dest = nil;

                if(dest && dest.ofKind(Room))
                    loc.notifyDeparture(gActor, dest);

                /*
                 *   Then execute the method or display the double-quoted string.
                 */
                loc.(dir.dirProp);

                /*
                 *   If we've just executed a method, it may have moved the actor to a new location, so
                 *   if the actor is the player character note where the method took the actor to so
                 *   that the pathfinder can find a route via this exit.
                 */
                if(gActor == gPlayerChar)
                    libGlobal.addExtraDestInfo(loc, dir,
                                               gActor.getOutermostRoom);
            }
            else if(gActor == gPlayerChar)
                loc.cannotGoThatWayInDark(dir);
            break;

            /*
             *   If the direction property points to a single-quoted string, simply display the string
             *   if the illumination is sufficient, otherwise display the message saying we can't go
             *   that way in the dark. If the actor isn't the player character, do nothing.
             */
        case TypeSString:
            if(gActor == gPlayerChar)
            {
                conn = loc.(dir.dirProp);
                if(illum)
                {
                    say(conn);
                    libGlobal.addExtraDestInfo(loc, dir,
                                               gActor.getOutermostRoom);
                }
                else
                    loc.cannotGoThatWayInDark(dir);
            }
            break;

        }
    }

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