# Travel Connectors and Barriers

## TravelConnectors

In addition to Doors, the direction property of a Room can point to a TravelConnector (a Door is in fact a particular kind of TravelConnector). A TravelConnector is a kind of object that allows you to define:

- Whether or not the exit should be listed in the exit lister.
- Whether or not the exit is apparent to the player character (if not, then obviously it cannot be used).
- Whether or not travel should be allowed in that direction according to any condition you care to define.
- Any side-effects of travel, such as displaying a message describing the travel.
- Whether or not the player character knows where the exit leads (this is relevant to the routeFinder).

The methods and properties that enable a TravelConnector to be used for these purposes are as follows:

- destination (a Room) Where this TravelConnector (and hence the exit to which it is attached) leads to. This would normally be set to a Room, but can be defined as an expression or method that evaluates to a Room. If the expression/method is at all likely to change over the course of a single turn (when it will be evaluated many times), for example, because it involves an element of randomness, you should instead use the VarDest mix-in class with the TravelConnector and define its calcDesc method to give the destination instead.
- getDestination(origin): Where this TravelConnector leads to when setting out from origin. Normally this just returns destination, but some TravelConnectors, such as double-sided doors, may be two-way, in which case the destination we end up in depends on the origin we start from.
- getApparentDestination(origin) This is used by exit listers to determine where an exit leads to and hence whether it has been previously visited. In the library this simply returns the value of getDestination(origin), but game code can overridde it to return nil when we want to make an exit leading somewhere visited appear unvisited, for example in a maze or when a TravelConnector simply loops back to its origin.
- isConnectorApparent (true or nil) This must be true if travel is to be allowed via this connector. By default this property is true. In this context 'apparent' means 'not concealed'. To be usable the TravelConnector must be not only apparent but visible (i.e. isConnectorVisible must be true); this is the case if isConnectorApparent is true and the lighting conditions are sufficient to see the exit by.
- isConnectorListed (true or nil) If this is true then the corresponding exit will be shown in the exit lister. This can be set to nil to hide the exit (presumably until something occurs to reveal it, when you would then set it to true). By default isConnectorListed takes its value from isConnectorApparent.
- isDestinationKnown (true or nil) This is true when the player character knows where this exit leads. By default this is true if the destination of the connector has been visited (visited = true). You can, however, define it as true at the start of the game if the player character is meant to start out knowing where the exit leads (though it may be easier to define familiar = true on a region than to set isDestination = true on a whole lot of TravelConnectors). This property is mostly relevant to the routeFinder (the mechanism behind the GO TO command). Note that a Room is a TravelConnector whose destination is itself (so that if an exit leads directly to a room the destination of that exit is known if the destination room has been visited). Note also that a slightly different rule applies to doors: the isDestinationKnown property of both sides of a door becomes true once the player character passes through one or other side of it.
- noteTraversal(actor) This method is called when actor traverses this connector, and can be used to carry out any side-effects of the travel. By default we simply display the travelDesc here if actor is the player character and update the traversedBy property with the actor and/or vehicle and/or travel pushable that has just traversed us.
- travelDesc (double-quoted string) A message that's displayed when the player character traverses this TravelConnector (alternatively, this could be defined as a method that carried out all sorts of side effects of traversing this TravelConnector). Note that if noteTraversal() is overridden without a call to inherited(), the travelDesc will be disabled. Note also that the default behaviour of travelDesc is to call the TravelConnector's doScript() method if the TravelConnector is also a Script; this makes it easy to vary the message displayed when the player character traverses the connector by adding an EventList class to the TravelConnector's class list and defining an eventList property on it. If the only reason for def9ining a TravelConnector is to add a travelDesc we can do so using the tcMsg(dest, msg) macro (where dest is the destination and msg is the travel message), e.g., `out tcMsg(atEndOfRoad, {I} step{s/ed} outside. )`.
- traversedBy A list of all the actors, vehicles and push-travelers (objects pushed around by an actor) that have traversed this Travel Collector over the course of the game.
- hasBeentraversedBy(traveler) Method that returns true if an only if this TravelConnector has been traversed by traveler, where traveler can be an actor (including the player character), a vehicle, or a push-traveler.
- traversed (true or nil). Returns true if and only if this TravelConnector has been traversed by the player character, which is likely to be the most common traversal condition game code wishes to test.
- execTravel(actor, traveler, conn) This method is called by travelVia(actor) to move the actor to the destination when travelling via the corresponding exit. You won't normally want to override this method unless you want it to do something drastically different like killing the player character or ending the game for some other reason. The normal behaviour of this method is defer to the execTravel() method of the room being travelled to, which actually carries out the travel and issues all the appropriate travel notifications: in particular it issues the before travel notifications (by calling beforeTravel(actor, self) on every object in scope, including the current location), then, if the actor is the player character it displays the travelDesc message (if any) and notes that the player character now knows where this TravelConnector leads. It next calls execTravel(actor) and finally carries out the after travel notifications on the new location (by calling afterTravel(actor, self) on every object in scope in the new location).
- travelVia(actor) This is the principal method called when the actor tries to traverse this TravelConnector. It first checks for travel barriers (see below), and then if the travel barriers don't prevent travel, it calls the execTravel() method. The only reason to override travelVia() would be if you wanted attempting to travel via this connector to do something drastically different from a normal travel action.
- canTravelerPass(traveler) Returns true if the traveler is allowed to pass through this TravelConnector. Return nil to prevent travel (normally travel would be blocked conditionally, e.g., perhaps only certain objects would be allowed to pass, or the player character is only allowed to pass if he's wearing the magic ring or not carrying the great big ladder that won't fit through the narrow gap). The traveler parameter would normally be the traveling actor, but might be a vehicle in which the actor is traveling.
- explainTravelBarrier(traveler) If canTravelerPass(traveler) returns nil (or might return nil), explainTravelBarrier(traveler) should display a message explaining why travel is disallowed.
- travelBarriers An optional list of TravelBarrier objects (or a single TravelBarrier object) that enforce further conditions on traveling via this connector (see further below).
- transmitsLight: can be used to control whether or not a connector (typically a door) that leads to a lit room is visible from a dark room. This can be used, for example, to determine whether or not the player character can use a door from a dark room to a lit one. This is true by default.
- stagingLocations: if defined (on most TravelConnectors it probably won't need to be), this can contain a list of Things (or classes of Things) that the traveler must be in before attempting to travel through this connector (e.g., a chair needed to reach a high window).
- sayNotInStagingLocation(traveler): the message to display if the traveler is not in one of our stagingLocations (if we've defined any) when the traveler attempts to travel via us. By default the message is "[The traveler] can't access that exit from [his] current location."
- exitLocation(dest): if specified this method can return a nested room within dest that a traveler travelling via this connector should end up on or in on entering dest (the room this TravelConnector) leads to. This might be a stage or platform in the room, for example. If this method returns something that isn't in dest it will be ignored.
- nilBlocksTravel: flag, do we want a this TravelConnector to be treated as if it didn't exists if it leads nowhere (i.e. its destination is nil). This is true by default, since the alternative is to have the traveler moved into nil if the traveler attempts to traverse this TravelConnector, which is never going to look good in your game. This was, however, what happened in earlier versions of adv3Lite, so the option is provided of reverting to that bad behaviour by setting this property to `nil`.

Note that the parameter of canTravelerPass() etc. is called traveler: it will normally be the actor doing the traveling but it could also be an object that an actor is attempting to push through this TravelConnector (via a command like PUSH TROLLEY NORTH, for example). This means that canTravelerPass(traveler) can also be used to selectively prevent the pushing of objects in certain directions; for example, you might want to use this method to prevent a heavy object being pushed up a flight of stairs.

An example of a TravelConnector which conditionally blocks travel could be one that only allows travel down a smoke-filled passage if the player is wearing a wet blanket, which could be defined thus:

    landing: Room 'Landing' 'landing'
        "The smoke is already becoming so thick here that it's hard to see much.
        Your bedroom lies to the north -- if you can make your way through the
        smoke. Most of the other upstairs rooms are down the passage the other way,
        to the south, but the worst of the smoke seems to be coming from there. "

        down = landingStairs

        north: TravelConnector
        {
            destination = bedroom

            travelDesc = "You manage to force your way through the smoke, coughing
                and choking as you go. "

            canTravelerPass(actor)
            {
                return blanket.wornBy == actor && blanket.isWet;
            }

            explainTravelBarrier(actor)
            {
                if(blanket.wornBy == actor)
                    "You take a few steps down the corridor but the smoke forces you
                    back as the blanket starts to get singed. ";
                else
                    "The smoke is too thick; you find yourself coughing and choking
                    after the first step and are forced to retreat. ";
            }
        }

        south  { "The smoke is too thick that way; you almost choke to death
            with the first step south you take. Well, it's not as if there's
            anything down there you really need all that much right now. "; }

        regions = upstairs
    ;

This incidentally illustrates that if we only want to use a particular TravelConnector once, we don't need to define it as separate named object, since we can instead define it as an anonymous nested object directly on the appropriate direction property.

Note that a TravelConnector only establishes a connection in one direction: the TravelConnector defined on the north property in the example above creates a direction north from the landing to the bedroom, but no connection back south from the bedroom to the landing. Often, as here, that's what we want (since we wouldn't want the same travel restrictions to apply to the attempt to return from the bedroom to the landing), but if you do want a TravelConnector that works the same both ways, you could try using the SymConnector defined in the symconn extension.

The UnlistedTravelConnector class can be used to define a TravelConnector for an exit we don't want to be listed by the exit lister, e.g. because we're using a magic word such as XYZZY to perform the travel by defining a custom xyzzyDir direction with a corresponding xyzzy property we can use as a directional property on a room. This class is principally designed to be used via the ulExit(dest) and ulMsgExit(dest, msg) macros. In each of these, dest is the destination we want the exit to lead to. In the second, msg is the travelDesc we want to be displayed when travel in that direction takes place. Both macros are used in a manner analogous to `asExit()`, e.g.:

    insideBuilding: IndoorRoom 'Inside the Building'
        "{I} {am} inside the building, a well house for a large spring. "
        out = atEndOfRoad
        west asExit(out)
        xyzzy ulExit(inDebrisRoom, {I} {am} translated in an instant. )
        road ulExit(atEndOfRoad)
     ;

## Handling Multiple Options in the Same Direction

Occasionally we may have more than one destinaiton lying in the same direction, for example if there are two or three doors in the north wall or more than one one passage to the east. We can often deal with this by specifying that the doors are to the northeast and northwest, and the passages to northeast, east, and southeast, but in some situations this may not be appropriate, especially in a location where we're using shipboard directions and we can only choose between fore, aft, port and starboard.

In this situation we can use an AskConnector (a special kind of TravelConnector) and define its options property to contain the possible choices that lead in the relevant direction, for example, to implement a narrow gallow in which there are two doors to starboard, we could do this:

    galley: Room 'Galley Area'
        "The narrow galley is fitted out much as you would expect. To atarboard are a couple of doors
        marked <sq>Toilet</sq> and <sq>Shower</sq> respectively. The saloon cabin continues aft while
        a narrow paasage runs forward. "

        aft = saloon
        regions = [saloonRegion]

        fore = narrowPassage

        starboard: AskConnector
        {
            options = [toiletDoorOutside, showerDoorOutside]
        }
    ;

With this in place, typing the command STARBOARD or SB when the player character is in the galley will result in the response, "That way lie the toilet door and the shower door. Which do you want to use?" If the player types TOILET or SHOWER the player character will be taken through the door chosen (the player isn't restricted to those choices, but if s/he names another object it may be rejected as nonsensical or inappropriate, unless, of course, it's a door or passage leading in another direction).

The AskConnector defaults to the `TravelVia` action in such instances, which is likely to be appropriate in most cases where the `choices` is used, but if you'd prefer your TravelConnector to use a different action here, you can override its travelAction property to something different, such as Enter or GoThrough.

Player Character pathfinding should work fine through an `AskConnector`. In particular, if a GOTO or CONTINUE command would take the player character through an `AskConnector`, the player won't be asked which option to choose; instead, the `AskConnector` will select whichever one leads to the desired destination. So, for example, if the player typed GO TO SHOWER CUBICLE (assuming that's where `showerDoorOutside` leads to), then the `AskConnector` will send the player character through the shower door without the player needing to intervene further.

AskConnector is primarily designed to work with a list of physical connector objects such as doors, passages, patha and stairways. It can also work with a list of Rooms in the options property, but with certain limitations. First, you cannot mix Rooms with any other kind of object in the same `options`; an error will result if you try. Second, if an AskConnector's options property does contain a list of Rooms, then all those rooms will automatically be msrked as both `visited` and `familiar` the first time travel via that AskConnector is attempted, and the AskConnector's `travelAction` will be changed to `GoTo`; these steps are necessary to ensure that the AskConnector works as expected.

That said, we can create the appearance of mixing rooms and physical connector in the same options list by using an object of the ProxyRoom class in place of a Room, like so:

    blueRoom: Room 'Blue Room'
        "The way out is to the south. To the east lie a small closet and a red door. "
        south = startroom

        east: AskConnector
        {
            options = [redDoor, proxyCloset]
        }
    ;

    + proxyCloset: ProxyRoom ->smallCloset
    ;

    redDoor: DSDoor 'red door' @redRoom @blueRoom
    ;

`ProxyRoom` is a special kind of Passage designed just for this purpose. The `->` in the ProxyRoom template points to the Room this Passage leads. The ProxyRoom will borrow its vocab from its destination room (and so acts as a proxy for it).

Note that if you want to use an `AskConnector` in your game, the pathfind.t module must be included in your build. (`AskConnector` is defined in pathfind.t since most of the code on the AskConnector class is concerned with pathfinding).

## TravelBarriers

For many simple cases conditionally preventing travel by using the canTravelerPass() method of a TravelConnector will suffice, but there may be situations where it's not the most convenient way to do it. The two most common situations where this might arise are where:

1. You want to apply the same condition (and display the same refusal message if it's not met) to a number of TravelConnectors, and it would be tedious to have to define the same canTravelerPass() and explainTravelBarrier() methods on each of them.
2. Several different conditions apply on a TravelConnector, each requiring a different explanation if it's not met, so that the explainTravelBarrier() method would need to contain a messy mass of if statements that largely duplicate those in the canTravelerPass() method.

If either or both of those two conditions obtain, you might be better off defining and using a TravelBarrier object to represent the conditional barrier to travel. There are basically two methods you need to define when creating a TravelBarrier object:

- canTravelerPass(traveler, connector) Return true if traveler is allowed to traverse connector (the TravelConnector object to which this TravelBarrier instance is attached) and nil otherwise.
- explainTravelBarrier(traveler, connector) Explain why the traveler is not permitted to traverse this connector if travel is disallowed.

Note the additional connector parameter on these methods. This is most likely to be useful when you want the explainTravelBarrier() method to name the connector that's being blocked. For example, suppose our game had several exits that required the player character to be wearing a wet blanket in order to penetrate the smoke. We might set it up thus:

    landing: Room 'Landing' 'landing'
        "The smoke is already becoming so thick here that it's hard to see much.
        Your bedroom lies to the north -- if you can make your way through the
        smoke. Most of the other upstairs rooms are down the passage the other way,
        to the south, but the worst of the smoke seems to be coming from there. "

        down = landingStairs

        north: TravelConnector
        {
            destination = bedroom

            travelDesc = "You manage to force your way through the smoke, coughing
                and choking as you go. "

            theName = 'the corridor'

            travelBarriers = [smokeBarrier]
        }

        south: TravelConnector
        {
            destination = bathroom

            travelDesc = "With the aid of the blanket, you are able to make your way
    	     through the smoke to the bathroom. "

            theName = 'bathroom passage'

            travelBarriers = [smokeBarrier]
        }

        regions = upstairs
    ;

    smokeBarrier: TravelBarrier
            canTravelerPass(actor, connector)
            {
                return blanket.wornBy == actor && blanket.isWet;
            }

            explainTravelBarrier(actor, connector)
            {
                if(blanket.wornBy == actor)
                    "You take a few steps down <<connector.theName>> but the smoke forces you
                    back as the blanket starts to get singed. ";
                else
                    "The smoke is too thick; you find yourself coughing and choking
                    after the first step and are forced to retreat. ";
            }
    ;

Note how we've added a custom theName property to the two TravelConnectors so that smokeBarrier.explainTravelBarrier(actor, connector) can refer to them.

adv3Lite Library Manual
Table of Contents | The Core Library > TravelConnectors and Barriers
Prev: Doors Next: Keys