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:

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

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.

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.

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:

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.