Table of Contents |
Airport > Extending the Map
Extending the Map
Our next job is to sketch out the other half of the airport terminal building. There won’t be much here that’s new for now, apart from one thing. We’re going to take advantage of separate compilation to put the next section of the map in a new source file. Both The Adventures of Heidi and Goldskull were such small games that their source code could easily be fitted into a single file. Airport is rather a larger game, so it’s worth splitting over several. There are two main advantages: (1) It’s easier to keep track of where everything is and keep things manageable if we split our source over several reasonably sized files rather than lumping it all together into one gargantuan one; (2) each time a TADS 3 game is recompiled it only has to recompile the source files that have changed, so that if we make changes to one file, we don’t have to recompile all the rest (TADS 3 in any case takes advantage of this so it doesn’t have to keep recompiling the library files each time). If you want more information on separate compilation you can read the article “Understanding Separate Compilation” in Part I of the TADS 3 Technical Manual.
So let’s create a new source file now. If you’re using Workbench, select File -> New from the menu (or type Ctrl-N, or click on the new file icon in the task bar, the third icon along from the left-hand end, representing an empty page). Select ‘TADS Source file’ from the dialogue that follows and then click ‘Open’. Then select File -> Save from the menu (or press Ctrl-S, or click the file save icon in the task bar). When prompted for a file name type “gatearea” (without the quotation marks). You should see a dialogue box asking “Would you like to add gatearea.t to the Project file list?” Click Yes. If you’re not using Workbench use your text editor to create a new blank file called “gatearea.t”, then edit your copy of airport.t3m to add the following line at the end:
-source gatearea
Whichever way you used to create the new file, the next step is to make sure it starts with the following three lines:
#charset "us-ascii"
#include <tads.h>
#include "advlite.h"
The easiest way to do that is probably to copy these lines from start.t and paste them into gatearea.t. Once you’ve done that, you’re good to go.
There’s not much new about the first version of this area of the map, so we’ll just go straight away and list it:
#charset "us-ascii"
#include <tads.h>
#include "advlite.h"
gateArea: Room 'Gate Area' 'gate area'
"The ways to Gates 1, 2 and 3 are signposted to the northwest, north and
northeast respectively, while a display board mounted high up on the wall
indicates what flights are boarding and departing where and when.
Immediately to the east is a metal door, while the main concourse lies
south. "
south = concourse
northwest = gate1
north = gate2
northeast = gate3
east = maintenanceRoom
;
+ Distant 'display board; departure'
"The display imparts the following information:\b
TI 179 to Buenos Aires <FONT COLOR=GREEN>BOARDING GATE 3</FONT>\n
RO 359 to Mexico City <FONT COLOR=RED>DELAYED</FONT>\n
PZ 87 to Houston <FONT COLOR=RED>DELAYED</FONT>\n
BU 4567 to Bogota <FONT COLOR=RED>DELAYED</FONT>"
;
maintenanceRoom: Room 'Maintenance Room' 'maintenance room'
west = gateArea
out asExit(west)
;
gate1: Room 'Gate 1' 'gate[n] 1[adj]; one'
"Disconsolate passengers lounge around on seats waiting for a flight that
seems never to arrive. The gate to the west is closed, so the only way out
from here is back to the southeast. "
southeast = gateArea
;
+ Decoration 'disconsolate passengers;;;them'
;
+ Decoration 'seats;;;them'
;
gate2: Room 'Gate 2' 'gate[n] 2[adj]; two'
"This area is totally deserted, as if no one even expects a flight will ever
board here. The gate to the north looks firmly locked, so the only
practicable way out would seem to be back to the south. "
south = gateArea
;
gate3: Room 'Gate 3' 'gate[n] 3[adj]; three'
"The area is practically deserted, apart from the odd belated passenger
dashing off through the open gate to the east. "
southwest = gateArea
east = openGate
;
+ Decoration 'seats; empty deserted unoccupied; seating; them'
"All the seats at this departure gate are unoccupied, suggesting that any
passengers for the current flight have already boarded the plane. "
;
+ openGate: Passage 'open gate; unattended wide'
"The gate is wide open, and for some reason totally unattended. That
hardly seems like a high level of security. "
cannotOpenMsg = 'It\'s already open. '
cannotCloseMsg = 'That hardly seems appropriate. '
destination = jetway
;
jetway: Room 'Jetway' 'jetway;short enclosed; walkway'
"This is little more than a short enclosed walkway leading west-east from
the gate to the plane. You seem to be the only person here, as if everyone
else has already boarded. "
west = gate3
// east = planeFront
;
Note that we’ve commented out the line east =
planeFront
on the jetway, since the planeFront room hasn’t been
implemented yet. At this stage you should uncomment the line
north = gateArea
on the concourse room,
otherwise this new section of map won’t have a connection from the old
one.
Note also how we use [n] and [adj] in the vocab properties of the three gates, where the usual order of adjective and noun is reversed, so we have to mark which is which. The use of <FONT COLOR=GREEN>BOARDING GATE 3</FONT> and the like in the description of the display board illustrate how we can use (a subset of) HTML markup to format the text we display to the player. The difference between \b and \n in the same description is that \b leaves a blank line following while \n just moves to a new line.
Also noteworthy is our first use of the Distant class. This is almost identical to the Decoration class, except that it responds to any command except Examine with “The Whatever is too far away”; here we’re using this to represent the fact that the display board is mounted high up on the wall.
Let’s think a moment more about this Distant display board. As things
stand the player can EXAMINE it, and that’s all, but you may think that
players should equally well be able to READ it and get the same response
as they do for examining. But if you try adding
dobjFor(Read) asDobjFor(Examine)
to the
definition of the display board you’ll find that nothing has changed.
That’s because the handling for all actions apart from Examine is
carried out by dobjFor(Default) before any more specific action-handling
gets a look-in.
To change this, we need to override the decorationActions property of the display board. This contains the list of actions the display board will respond to specifically, instead of simply using the dobjFor(Default) handling (which displays the “too far away” message). The display board should then look like this:
+ Distant 'display board; departure'
"The display imparts the following information:\b
TI 179 to Buenos Aires <FONT COLOR=GREEN>BOARDING GATE 3</FONT>\n
RO 359 to Mexico City <FONT COLOR=RED>DELAYED</FONT>\n
PZ 87 to Houston <FONT COLOR=RED>DELAYED</FONT>\n
BU 4567 to Bogota <FONT COLOR=RED>DELAYED</FONT>"
decorationActions = [Examine, GoTo, Read]
readDesc = desc
;
We have included GoTo in the list of decorationActions because a player might reasonably issue the command GO TO DISPLAY BOARD from another location, and there’s no reason why we should not allow it to work. The Decoration class includes GoTo in its decorationActions list by default (since players may often wish to go to the location of a Decoration), but the Distant class does not, since it’s often used to represent something that’s too far away for the player character to go to (such as the sun, moon, sky or a distant mountain). For the display board, however, it is appropriate to include GoTo among the actions that can be handled. Where a Distant object is used to represent an object in a neighbouring location, handling the GoTo action on it may require more care, since it may be that GO TO CABIN (when the cabin is a Distant object representing a cabin in a neighbouring location) should take the player character to that neighbouring location; it may well be easiest to handle such cases with Doers.
Overall the implementation of this part of the airport is a little sparse; we shall be returning to parts of it later to fill in more details, but the description of the seats and passengers at Gate 1 is left as an exercise for the reader. Instead we’ll introduce something quite new at this point: a series of announcements over the public address system (most of them the last call for passengers boarding at Gate 3, in a variety of languages, just to add a little more sense of urgency to the proceedings). To do this we’ll define a special object just to provide the announcements; this can be put right at the end of the gatearea.t file:
announcementObj: ShuffledEventList
eventList =
[
'<<prefix>><q>Last call for passengers on Flight TI 179 to Buenos Aires;
this flight is now boarding at Gate 3.</q> ',
'<<prefix>><q>Ultima llamada a pasajeros en Vuelo TI 179 a Buenos Aires;
este vuelo se aloja ahora en la Puerta 3.</q> ',
'<<prefix>><q>La dernière demande des passagers sur le Vol TI 179
à Buenos Aires; ce vol est maintenant boarding à la Porte
3.</q> ',
'<<prefix>><q>Chamada última a passageiros em TI de Vôo
179 a Buenos Aires; este vôo est´ embarcando agora na Porta
3.</q> ',
'<<prefix>><q>Will passenger Quixote please report to the airport
information desk.</q> ',
'<<prefix>><q>This is a security announcement. Any unattended baggage
will be removed and auctioned for the airport security personnel
benevolent fund.</q> '
]
eventPercent = 67
eventReduceTo = 33
eventReduceAfter = static eventList.length
start()
{
if(daemonID == nil)
daemonID = new Daemon(self, &announce, 1);
}
stopDaemon()
{
if(daemonID != nil)
{
daemonID.removeEvent();
daemonID = nil;
}
}
daemonID = nil
announce()
{
doScript();
}
prefix = 'An announcement comes over the public address system: '
;
Okay, so let’s explain this one step at a time.
First off, we’ve created an object of the ShuffledEventList class.
This is one of a number of EventList classes
that you can read about in more detail in the adv3Lite Library Manual.
In brief, an EventList is an object which steps through each item in its
eventList
property in turn when its
doScript()
method is called. A ShuffledEvent
list steps through its items in random order, then shuffles the list
into a new random order when it gets to the end before repeating the
process. If an item in the eventList property is a single-quoted string,
it is simply displayed.
To prevent these messages from becoming too tedious through
over-exposure we can control how often they’re displayed. The
eventPercent
property determines the frequency
of turns (as a percentage) on which an item will be displayed. Here
we’ve chosen a value of 67, meaning we’ll see a message on roughly
two-thirds of turns. The eventReduceTo
property is the frequency of occurrence this will eventually drop to,
here 33 (i.e. roughly one third of the time). The
eventReduceAfter
property determines when this
reduction in frequency takes place. Here we’ve defined it to take place
after every item in the eventList has been displayed once. The
expression eventList.length()
gives the number
of items in the list. The static
keyword
preceding it means that this number is calculated at compile time and
then stored in the compiled game file.
Second, we need some way of driving this ShuffledEventList, and to that
end we use a Daemon to keep calling its
doScript()
method each turn. You can read all
about Daemons and Fuses in the Events section of
the adv3Lite Library Manual. In brief, a Daemon is a special kind of
object that calls a specified method on a specified object at a
specified interval. The definition above, new
Daemon(self, &announce, 1)
creates a new Daemon that calls the
announce
method of the
self
object (i.e. announcementObj) every turn
(i.e. with an interval of 1). We store a reference to this Daemon in the
daemonID
property both so that the
start()
method can be prevented from creating
another new Daemon if it has already created one that’s still running,
and so that the stopDaemon()
method can call
the Daemon’s removeEvent()
method if and when
we want to stop it running. The stopDaemon()
method also checks that daemonID
is not nil
before trying to call its removeEvent()
method
(so that it doesn’t try to stop a non-existent Daemon) and finally
resets daemonID
to nil in case either
stopDaemon()
or
start()
is called again. [Note: in earlier
versions of this Tutorial the stopDaemon()
method was simply called stop()
, but from
version 1.4 of adv3Lite onwards stop
has
another meaning as a macro, so it can no longer be used as a method
name].
Note how the second parameter of the call to new Daemon() is specified
as &announce
and not just
announce
. Placing the ampersand (&) in front
of the property/method name makes it a property pointer. We use a
property pointer in a context like this when what we want to pass to a
method isn’t the value of a property, but a reference to the property.
Here, we’re telling Daemon to call the
announce()
method each turn, so we need to
pass a pointer to announce()
so it knows which
method to call. You may be wondering why we couldn’t simply have passed
&doScript
, since all the
announce()
method does is to call
doScript()
. The answer is that we could
perfectly well have done here, but that we’ll shortly be tweaking the
announce()
method in the next section.
The prefix
property simply defines some text
we can use in each element of the eventList
without having to type it out in full each time. Note the use of various
HTML entities (e.g. à for à or ⊚ for ô) for accented letters
that might otherwise be hard to display or for the compiler to deal with
if it found them in your source file. By the way, the translations into
Spanish, French and Portugeese of the last call for passengers boarding
at Gate 3 were carried out using an online automated translation
facility, so they’re not guaranteed to be correct! By all means feel
free to correct them.
Finally, we need some means of starting this Daemon. A convenient point
at which to do so might be when the player character passes through the
metal detector, so we’ll make a small adjustment to the method’s
travelDesc
method to make this happen:
+ 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. ";
announcementObj.start();
}
;
The fact that the player character may pass through the metal detector
several times doesn’t really matter, since once the Daemon is running,
announcementObj.start()
doesn’t actually do
anything. In the next section we’ll set about laying out the plane.
Once you’ve made all the necessary changes to your versions of start.t and gatearea.t, try compiling and running the game again and moving round the now extended map to make sure everything works as it should.
adv3Lite Library Tutorial
Table of Contents |
Airport > Extending the Map