Starting the Map

The first step is to create a new project. Follow the steps in Chapter Two above, but this time create a new folder/directory called 'aiport' to hold contain this new project, and when you've copied the template files to it rename the relevant template files to airport.t3m. Or, if you're using Workbench, use the New Project wizard to create a new adv3Lite project called airport. Then open the start.t (or airport.t) file either in Workbench or in whatever other text editor you're using.

We'll start by creating the first area (roughly speaking, the southern part) of the map described in the previous section, along with some of the more obvious objects it contains. You'll learn more if you type most of the code in yourself, but it's quite understandable if you prefer to copy and paste some of the longer descriptions. There's not a great deal that's new here, except that we'll be defining the vocab property on each room we define (the second single-quoted string in the object definition template). I'll explain why when we get to the end. I'll also interrupt the program listing every now and again to explain particular points as they arise.

#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

versionInfo: GameID
    IFID = 'bcb7d5c4-81ee-30b0-5aec-7672b10e2cd6' 
    name = 'Airport'
    byline = 'by Michael Roberts (and Eric Eve)'
    htmlByline = 'by <a href="mailto:an.author@somemail.com">
                  A.N. Author</a>'
    version = '1'
    authorEmail = 'A.N. Author <an.author@somemail.com>'
    desc = 'A brief demonstration of game design in TADS 3.'
    htmlDesc = 'A brief demonstration of game design in TADS 3.'    
    
;

Note that we've filled in some of the properties of versionInfo, including the IFID, from those shown in the game design article we're borrowing from in the TADS 3 Technical Menual.

gameMain: GameMainDef
    /* Define the initial player character; this is compulsory */
    initialPlayerChar = me
;


/* 
 *  The starting location; this can be called anything you like;
 *  here we've renamed it to 'terminal; 
 */

terminal: Room 'Terminal' 'terminal'   
   "You are in the airport's main terminal. To the east, you see some ticket
   counters; to the north is the main concourse. The main exit to the city and
   car parks lies directly to the south. "
    east = ticketArea
    north = securityGate
    
    /* 
     *  We use a method here so that this exit shows up in the exit lister, even though
     *  the player character can't actually go this way. This trick provides a "soft
     *  boundary" to the map, making it feel potentially bigger than it actually is.
     */
       
    south 
    { 
        "If you go back out that way you're likely to run straight into a hail
        of bullets. ";
    }
        
    out asExit(south)
;

/* 
 *   The player character object. This doesn't have to be called me, but me is a
 *   convenient name. If you change it to something else, rememember to change
 *   gameMain.initialPlayerChar accordingly.
 */

+ me: Thing 'you'   
    isFixed = true    
    proper = true
    ownsContents = true
    person = 2   
    contType = Carrier    
;


ticketArea: Room 'Ticket Area' 'ticket area'
    "You are in the ticket counter area. Ticket counters
    line the north wall; so many people are waiting in line that 
    you're sure you'll never manage to get to an agent. The main
    terminal is back to the west. "
    west = terminal
;

+ counter: Surface, Fixture 'ticket counter; untended' 
     "The ticket counter runs round two sides of the area, staffed by too few
     hard-pressed clerks, while at least half of it is totally untended. "  
;

++ IDcard: Thing 'an ID card; identification poor; photo'     
    "According to what's on the front it apparently belongs to one Antonio
    Velaquez. Fortunately the accompanying photo is so poor it could be of almost
    anyone, even you. A magnetic stripe runs down the back. "
;

+++ Fixture 'magnetic stripe; mag metallic brown; strip'
    "It's a brown metallic strip running down the reverse of the card. "
    
    cannotTakeMsg = 'It would be pretty hard to peel the magnetic stripe away
        from the card, and it would almost certainly render the card useless if
        you did so. '
;

Note how we've made the magnetic stripe a part of the ID card, by making it a Fixture (so it can't be taken separately) and then providing a custom cannotTakeMsg. The description also hints both that the card might be useful in a card lock and that it might trigger a metal detector.

+ Decoration 'people; of[prep]; line queue tourists businesspeople; them'
    "A motley collection of tourists and businesspeople, so far as you can
    tell, many of them looking increasingly frustrated at the length of the
    queue. "
  
    notImportantMsg = 'You don\'t have time to bother with them, and you don\'t
        want to risk drawing attention to yourself. '
;

+ Decoration 'booking clerks; ticket male female hard-pressed hard pressed
    ; men women agents agent clerk; them'
    "The booking clerks, male and female in roughly equal numbers, all seem
    equally lacking in any sense of urgency to service the ever-lengthening
    queues. "
    
    notImportantMsg = 'Unfortunately you can\'t get anywhere near any of them. '
;

Here we've simply used the Decoration class to implement people the player character isn't meant to interact with. Even this minimal implementation helps to make the airport feel a little more populated, as well as filling in some of the details of why it's impossible for the player character to purchase a ticket, but also why no one's paying much attention to the ID card that's been left lying around. We'll see how to implement rather livelier and more responsive non-player characters in later chapters.

securityGate: Room 'Security Gate' 'security gate'
    "You are at the security gate leading into the main
    concourse and boarding gate areas. The concourse lies to the
    north, through a metal detector. The terminal is back to the
    south. "
    north = metalDetector
    south = terminal
;

+ metalDetector: Passage 'metal detector; crude; frame'
    "The metal detector is little more than a crude metal frame, just large
    enough to step through, with a power cable trailing across the floor. "
    destination = concourse
    
    isOn = true
    
    canTravelerPass(traveler)
    {
        return !isOn || !IDcard.isIn(traveler);
    }
    
    explainTravelBarrier(traveler)
    {
        "The metal detector buzzes furiously as you pass through it. The
        security guard beckons you back immediately, with a pointed
        tap of his holstered pistol. After a brisk search, he discovers the ID
        card and takes it off you with a disapproving shake of his head. ";
        
        IDcard.moveInto(counter);
    }
    
    travelDesc = "You pass through the metal detector without incident. "
;

+ powerCable: Decoration 'power cable; trailing; cord wire wires'
    "The cable trails rather slackly from one side of the metal detector across
    the floor and then out of sight behind a desk over to the right on the far
    side. You're not sure it's an arrangement that would find favour with health
    and safety inspectors back home, but it appears to do the job. "
    
    notImportantMsg = 'You\'re morally certain the security guard won\'t let you
        get anywhere near it. '
;

The main point of note here is the use of the Passage class (yet another combination of TravelConnector and Thing) to implement the metal detector. This has two uses: first it allows commands like GO THROUGH METAL DETECTOR and ENTER METAL DETECTOR to work just the same as NORTH in this context; second it enables us to put conditions on the travel (in the canTravelerPass() method) to stop the player character carrying the ID card through the metal detector when the detector is on. Note that we've added a custom isOn property to metalDetector to represent whether it is on or off, but at the moment there's no mechanism for turning it on or off (that will follow in the next chapter). The explainTravelBarrier() method not only explains why passage through the metal detector hasn't been allowed, it also returns the ID card to the ticket counter (as in Mike Roberts' specification). Once we add the security guard as an object in his own right, we can probably improve on this implementation, but it will do for now. Finally, note the power cable trailing across the floor from the metal detector. This is a hint that preventing power from reaching the metal detector might be a way to disable it, but its notImportantMsg should make it reasonably clear that interfering directly with the cable isn't the way to go about it.

concourse: Room 'Concourse' 'concourse; long; hallway'
    "You are in a long hallway connecting the terminal
    building (which lies to the south) to the boarding gates (which are
    to the north). To the east is a snack bar, and a door leads west.
    Next to the door to the west is a small slot that looks like it
    accepts magnetic ID cards to operate the door lock. "
//    north = gateArea
    south = securityGate
    east = snackBar
    west = securityArea
;

+ cardslot: Fixture 'card slot'  
    "The slot appears to accept special ID cards with magnetic encoding. If you
    had an appropriate ID card, you could put it in the slot to open the door. "
;   

Note that we've commented out the line north = gateArea for now, since gateArea hasn't been implemented yet. This will allow us to test this part of the map without first completing the rest of the map. The implementation of the card slot is obviously incomplete, but it will do for now.

snackBar: Room 'Snack Bar' 'snack bar'
    "The snack bar seems to be full of passengers jostling one another to get at
    the serving counter, or consuming their homogenized snacks at the crowded
    tables, though for some reason, one table remains free. To the west lies
    the relative calm of the concourse. "
    
    west = concourse
    out asExit(west)
;
    
+ Decoration 'passengers; ; locals americans people; them'
    "From the sound of their voices they seem to be a mixture of Americans and
    locals, all alike casually dressed<< if darkSuits.isIn(location)>> -- except
    for a handful of men in dark suits<<end>>. "
;

+ darkSuits: Decoration 'men[n] in[prep] dark suits; sinister senior; 
    lieutenants; them'    
    "Even to the untrained eye they'd probably look pretty sinister. To you they
    look worse than that; you're pretty sure they're some of El Diablo's senior
    lieutenants. Fortunately they seem too absorbed in their own discussion at
    the other end of the room right now to notice you. "
    
    notImportantMsg = 'Right now, you don\'t want to go near any of them. '
;

+ Decoration 'crowded tables;;;them'
    
;

+ Surface, Fixture 'free table; small round'
    "Perhaps the reason this small round table remains free is that there are no
    chairs round it; presumably they've all been borrowed by passengers at the
    other tables. "
;

++ newspaper: Thing 'newspaper; narcosia; paper herald'
    "It's a copy of the latest edition of the <i>Narcosia Herald</i>. "
    
    readDesc = "A quick skim of the paper reveals nothing unusual for this part
        of the world. Yet another government minister is denying charges of
        corruption, money-laundering and enjoying a surfeit of mistresses whose
        combined age would only just add up to his. Only eighteen gang-related
        killings were committed on the streets of Narcosia yesterday, making it
        an unusually quiet day in the capital. The President had defended
        spending another twenty billion trillion terapesos (about half a billion
        dollars) on yet another grand extension to his palace on the grounds
        that it's vital to national prestige and will surely attract the kind of
        foreign investment that will lift the poorest eighty per cent of the
        nation slightly closer to the breadline. The defence minister is
        congratulated for his acumen in buying up stock in Polemicorp
        International before placing a large arms order with them; the finance
        minister is quoted as saying that this is just the kind of
        entrepreneurial spirit the country needs. The police have once again
        failed to make any arrests of the enterprising drug barons who are
        largely funding their pension pots. All in all, it's business as usual
        -- except for some editorial speculation that El Diablo may be planning
        something <i>big</i>, but you knew that anyway; that's what brought you
        to this godforsaken hell-hole. "   
;

Once again we've used Decoration objects to represent people we don't want the player character to interact with, although the description of the men in dark suits also provides the player with a bit more plot information. We've named the darkSuits Decoration object so we can later move it out of the snack bar when these sinister characters board the plane; we've also defined the description of the general tourists so it will no longer mention the men in dark suits once the latter are moved. The newspaper provides another example of the use of the readDesc property to give a suitable response to a READ command; it also potentially provides the player with some more information about the game's setting. It does not yet provide an airline ticket when taken, but we'll be coming back to that in the next chapter.

securityArea: Room 'Security Area' 'security area'
    "This somewhat bare room seems to be a lobby for other areas. There are exits
    south and west, while the way out back to the concourse lies through the
    door to the east. "    
    
    east = concourse
    south = lounge
    west = securityCentre
    out asExit(east)
    
;

lounge: Room 'Pilot\'s Lounge' 'pilot\'s lounge'
    "Even if the somewhat faded decor of this room didn't suggest that it was
    meant to be some sort of relaxation area, this is plain enough from the long
    settee that runs along one wall and the scattering of easy chairs. The only
    way out is to the north. "
    
    north = securityArea
    out asExit(north)
;

+ suitcase: OpenableContainer 'suitcase;;case' 
    
    initSpecialDesc = "A suitcase stands neatly placed next to the settee. "   
;

++ uniform: Wearable 'pilot\'s uniform; timo large'  
    "It's a uniform for a Timo Airlines pilot. It's a little large for you, but
    you could probably wear it. "
;

+ Decoration 'long settee;;sofa'
;

+ Decoration 'easy chairs;;;them'
;


securityCentre: Room 'Security Centre' 'security centre'
    
    east = securityArea
    out asExit(east)
;

These last three rooms are quite sparsely implemented for now, and will need to be filled out more later, but we do at least have the suitcase and pilot's uniform in place. Note the use of the OpenableContainer class. This is equivalent to defining contType = In and isOpenable = true on the suitcase, and provides us with an object that can be open and closed and which can contain things that are only accessible when it's open. By default an OpenableContainer starts out closed.

Further Explanations

A minor point to note is the way in which we've used the asExit() construct to make OUT a synonym for whichever seems the most natural way out wherever it's appropriate. This can make for a smoother playing experience when OUT seems the most natural thing to the player to type, even when it isn't explicitly listed as an exit.

Of more importance is the defining of the vocab property on each of the rooms (via the second single-quoted string in each of our room definitions). At first sight this might look redundant, since in virtually every single case it simply appears to duplicate the roomTitle property defined in the first single-quoted string. It does give us something new, however, namely a means for the player to refer to rooms in commands, which wouldn't otherwise be possible. The main reason for this is to allow the player to move round the map using commands like GO TO TERMINAL or GO TO SNACK BAR. Since the map for this game will end up being quite a bit biggger than those for the previous two games we wrote, some players might find it a little tedious or cumbersome to be forced to use compass directions to move around areas they've already explored. GO TO wherever provides such players with an alternative means of getting round the map. A player can type GO TO SNACK BAR for example, and the player character will move one step in the appropriate direction (i.e. to the next room along the shortest route to the snack bar from his present location). The player can then keep entering the command CONTINUE (which can be abbreviated to C) until either the player character reaches his chosen destination, or something happens to interrupt the journey.

You don't have to do anything extra to provide this method of navigation; it's built into the adv3Lite library. But it's more effective if players can refer directly to the rooms they want the player character to travel to. GO TO does also work with the name of objects within a room, but this is less direct since if the player wanted to get to the snack bar s/he'd have to remember the name of an object that was there (and not named like an object anywhere else). Being able to type GO TO SNACK BAR is far more intuitive and direct than having to type GO TO FREE TABLE or the like.

Note that this GO TO mechanism doesn't remove the need to explore the map. It only works with rooms and objects the player character had already visited (or is meant to know the location of in advance).

Now try compiling and running the game. You should be able to move around the map, try out what happens when you attempt to go through the metal detector carrying the ID card, and find and wear the pilot's uniform. You can also experiment with using GO TO to move around. In the next section, we'll sketch in the rest of the airport map.