Analysyis of a Transcript
Anatomy of a Transcript
Anatomy of a Transcript
When you start writing your own game and testing it, you'll find that the library takes care of a good deal of the work for you. Mainly, that's a benefit, but sometimes it can become a problem, particularly when the library doesn't quite do what you want but you can't see how to change it. That's where this article may be able to help. Immediately below we give a sample transcript from a simple TADS 3/adv3Lite game. Next to the various parts of the transcript you'll find numbered links, like this [1]. Clicking on the link will take you to an explanation of where the game's response to player input (the 'message') is coming from, often with some hints on how to change it, or a reference to some other documentation that explores the relevant library feature in more detail.
This article also contains the full source code for the game from which the sample transcript was taken. Where appropriate the explanations for a given feature are linked to the appropriate object in the source code. This lets you click on a numbered link in the transcript to read an explanation, and then click on a link in the explanation to see the relevant part of the TADS 3 source.
Parts of this sample game are deliberately unpolished; showing examples of things not working properly can sometimes be more instructive than showing everything working properly, and if you're using this sample transcript to locate things that aren't quite right in your own game, it may help if they match things that aren't quite right in this sample game.
Reading this article through sequentially from beginning to end may not be the best way to use it. You may find it more helpful to read through the sample transcript below, and then click on the numbered link when you see something that interests you.
A Sample Transcript
Welcome to this demo! [1] Box Room [2] This large box room is strewn with junk. The only way out is via a door to the east. [3] A small square table stands in the middle of the room. [4] A large red box sits in the corner. [4] You see a tennis ball, an old coat, an odd sock, and a small green book here. [5] On the small square table is a small blue box. [5] >about [6] This is just a brief demo game to illustrate where various messages come from in TADS 3. Or you could think of it as a high adventure with deep characterization, a riveting plot, and a myriad of amazing puzzles – except that if you do think of it this way you'll be sadly disappointed! [7] >credits Put credits for the game here. [8] >x table On the small square table is a small blue box. [5] >x red box It's large, red and box-shaped, and is currently closed. [9] >take box Which box do you mean, (1) the large red box or (2) the small blue box? [10] >red Taken. [11] >look in red box (first opening the large red box) [12] The large red box contains an old teddy bear, a large black torch, and a red lego brick. [5] >take torch Taken. [11] >drop box Dropped. [11] >look Box Room This large box room is strewn with junk. The only way out is via a door to the east. A small square table stands in the middle of the room. [4] You see a tennis ball, an old coat, an odd sock, a small green book, and a large red box (which contains an old teddy bear and a red lego brick) here. On the small square table is a small blue box. >x junk All sorts of stuff, the accumulated detritus of decades, mostly not worth bothering with. >search junk The junk isn’t important. [12a] >take table The small square table is too heavy. [13] >look in blue box (first unlocking the small blue box, then opening it) [12] In the small blue box you see a yellow crayon, a red crayon, and a blue crayon. [14] >x book It's a copy of Getting Started in TADS 6. >read book Reading the book brings back happy memories of writing IF in your early twenties. [15] With TADS 6 all you had to do was fill in a form specifying the genre of the game, the names of a few key NPCs, and the median age of the target audience, select a plot-type from a drop-down list, and then hit either the “generate two-hour version for IF-Comp” button or “generate long version” button, and a bug-free, typo-free TADS 6 game would be instantly created for you. Shame so many diehards on intfiction.org thought this took all the fun out of creating IF. >wear sock (first taking the odd sock) [12] Okay, you’re now wearing the odd sock. >wear sock You’re already wearing it. [16] >i You are carrying a large black torch, and you’re wearing an odd sock. [17] >e (first opening the door) [12] Landing Emerging from the box room you find yourself standing at the top of a flight of [18] stairs leading down to the south. >look Landing A flight of stairs leads down to the south, while a door leads into the box room just to the west. >d You walk briskly down the stairs. [19] Hall (East End) This large hall continues to the west. A flight of stairs leads up to the north. You can see a rubber duck here. In the hall west end you see a wooden chair. Mavis is sitting on a wooden chair at the far end of the hall. Harold is in the hall west end. [20] >x harold He's about your height and build, and really looks quite a lot like you. Since he's [21] your twin brother this is not altogether surprising. >x mavis She’s too far away to make out any detail. [21] >x dfwv You see no dfwv here. [22] If this was an accidental misspelling, you can correct it by typing OOPS followed by the corrected word now. Any time the story points out an unknown word, you can correct a misspelling using OOPS as your next command. >oops duck It's yellow.>x buck (x duck) [22a] It’s yellow.>w Hall (West End) This large hall continues to the east. A flight of stairs leads down to the south. At the far end of the hall, you see a rubber duck. [23] Harold is here. [24] Mavis is sitting on the wooden chair. [24] >exits Obvious exits lead south; and east, back to the hall (east end). [25] (You can control the exit listings with the EXITS command. EXITS STATUS shows the exit list in the status line, EXITS LOOK shows a full exit list in each room description, EXITS ON shows both, and EXITS OFF turns off both kinds of exit lists.) >x mavis She's a funny old woman, when all's said and done. She is sitting on the wooden chair. [26] She's rocking back and forth in her chair moaning “Woe!” >ask mavis about mavis The old woman simply rocks back and forth in her chair moaning, “Woe, woe, woe is me!” [27] >talk to harold “Hello, Harold!” you say. [28] “Hi there!” he replies. (You could ask him about Mavis.) [29] (Enumeration and/or hyperlinking of topic suggestions can be toggled on and off using the commands ENUM SUGGS and/or HYPER SUGGS respectively. ) >a mavis “What's up with Mavis?” you ask. [30] “She's inconsolable – she can't find her favourite photograph of Buster Keaton,” he tells you. >topics You could ask him about the photo. [31] >a photo “Where did Mavis leave the photo?” you ask. [32] “I think it may be in the cellar; but it's dark down there so I couldn't find it,” he tells you. >g “You think Mavis's photo of Buster Keaton may be in the cellar?” you ask. [32] “That's right,” he nods, “Be a good fellow and get it for her, her moaning is getting on my nerves.” >g “You think Mavis's photo of Buster Keaton may be in the cellar?” you ask. [32] “That's right,” he nods, “Be a good fellow and get it for her, her moaning is getting on my nerves.” >ask harold about american foreign policy “I think you'd better help poor Mavis before we discuss that,” he suggests. [33] >bye The conversation is ended. [34] >d In the dark [35] It’s pitch black. >turn on torch Done. [36] Cellar [37] The cellar is almost bare. A flight of stairs leads up to the north. You see a photo of Buster Keaton here. >x photo The picture shows Buster Keaton posing in a Confederate uniform in The General. >take it Taken. (Your score has just increased by ten points.) [38] (If you’d prefer not to be notified about score changes in the future, type NOTIFY OFF.) >i You are carrying a large black torch (providing light) and a photo of Buster Keaton, [39] and you’re wearing an odd sock. >e You can’t go that way. From here you could go north. [40] >turn off torch Done. You are plunged into darkness. [41] >e It’s too dark to see where you’re going. [42] From here you could go north. >up Hall (West End) This large hall continues to the east. A flight of stairs leads down to the south. At the far end of the hall, you see a rubber duck. Harold is standing here. Mavis is sitting on the wooden chair. You turn to Mavis and hand her the photograph of Buster Keaton. She snatches it [43] eagerly from your grasp, instantly stops her moaning, and showers the actor's picture with delighted kisses. (Your score has just increased by ten points.) *** You have made an old lady very happy *** [44] In 42 moves, you have scored 20 of a possible 20 points. [45] Would you like to RESTORE a saved position, RESTART the story, UNDO the last move, [44] see your FULL SCORE, or QUIT? >full score In 42 moves, you have scored 20 of a possible 20 points. Your score consists of: [46] 10 points for retrieving the photograph 10 points for making Mavis happy Would you like to RESTORE a saved position, RESTART the story, UNDO the last move, see your FULL SCORE, or QUIT? >q Thanks for playing! [47]
Explanations
[1] showIntro()
"Welcome to this demo!" This text is generated from the showIntro() method of gameMain. This is something you'll normally want to customize.
[back][2] roomTitle
The name of the room printed at the head of its description is taken from the room's roomTitle property. Normally this is assigned through the Room template, as in boxRoom, the starting location here.
By default the room name is shown in bold. If you want to change this you can do so by modifying roomnameStyleTag; e.g. to make the room name appear in italics in a fixed-spaced font:
modify roomnameStyleTag openText = '\n<i><FONT FACE=TADS-TYPEWRITER>' closeText = '</FONT></i>\n' ;
We could similarly modify statusroomStyleTag to change the way the room name is displayed in the status line (although here we would probably override the htmlOpenText and htmlCloseText properties instead.)
[back][3] Room Description
The normal description of a room is taken from its desc property. Normally this is assigned through the template, as shown in the boxRoom example.
Normally a room's description is diplayed in the standard font for the game. This can be changed by modifying roomdescStyleTag. For example to make room decriptions display in the same colours as the status line you could do this:
modify roomdescStyleTag openText = "<font bgcolor=statusbg color=statustext>" closeText = "</font>" ;
The fact that you can do this doesn't make it a particularly good idea, however, not least because the room description will then be displayed differently from all the items listed within the room (but perhaps this is what someone, somewhere will want for some mysterious purpose).
[back][4] specialDesc & initSpecialDesc
Items listed in separate paragraphs, like the table and the large red box here, probably have either a specialDesc or an initSpecialDesc (the latter applies until the object is moved). In the example the red box (which can be moved) has an initSpecialDesc and the table (which can't) simply has a specialDesc. The difference between the table and the red box becomes more apparent later in the transcript: once the box is taken and dropped again its initSpecialDesc is no longer used, and the box is listed among the other miscellaneous items in the room. The table can't be moved at all, however.
[back][5] Lists of Objects
The lists of objects lying on the floor and on the table are automatically generated as part of the room description by listContents(), which is in turn called by lookAroundWithin() (both defined on Thing, but in this instance called on the Room), all defined on Thing but called on the room in question. These are fairly complex methods that you probably won't want to mess with too much. If you want to change the way lists are displayed, see the Library Manual article on Lists and Listers.
[back][6] Command Prompt
Many, perhaps most, games don't bother to customize the command prompt, but if you're looking to do so it may help to know that it's generated by libMessages.mainCommandPrompt(which). So if you did want to change it, you could do something like this (though preferably in better taste):
CustomMessages [ Msg(command prompt,'\b<b><FONT COLOR=RED>What next?</FONT></b> ') ] ;
By default what the player types is then displayed in bold black type (with the standard black on white display — with other colour schemes this may differ). This may be customised in two different places. One way would be to modify InputDef. For example, to make the player's input appear simply in italics we could use:
modify InputDef beginInputFont() { "<i>"; } endInputFont() { "</i>"; } ;
Alternatively, we could modify inputlineStyleTag; e.g. to make what the player types appear in bold green print we could do this:
modify inputlineStyleTag htmlOpenText = '<b><font color=green>' htmlCloseText = '</font</b>' ;
Some caution is needed doing this kind of thing, however, since what might look well enough on your interpreter with the set of colours you're using might look quite dreadful, or even barely visible, on a different interpreter using a different colour scheme.
Note also that the default behaviour of InputDef.beginInputFont() and InputDef.endInputFont() is simply to invoke the opening and closing text of the inputLineStyleTag — so there's no point trying to modify the input font in both places (any changes to InputDef will take precedence).
[back][7] About Text
The game's ABOUT text, which has been customized here, is defined in the showAbout() method of the versionInfo object. In a more complex game this might launch a menu offering different kinds of information about the game; here just a simple message is displayed. This is something you will always want to customize in your own games.
[back][8] Credits
Although it probably should have been, the credits information for this game has not been customised. The place to do this is in the showCredit() method of the the versionInfo object.
[back][9] OpenableContentsLister
The text "It's large, red and box-shaped, and is currently closed" all comes from the desc property . Note especially that the "and is currently closed" part of the description is only there because it's been defined as part of the desc property of the largeBox object; it's not something the library adds automatically. If the box were open, though, the library would add a notice to that effect as part of the introduction to listing the box's contents. You could dispense with that notice by setting the red box's openStatusReportable property to nil, or make is use a pronoun rather than a noun ("It's open and contains..." rather than "The large red box is open and contains...") by setting openStatusReportable to UsePronoun. Alternatively, you could customise the whole introduction by overriding descContentsLister.showListPrefix().
[back][10] Disambiguation Prompt
This disambiguation prompt is being displayed because there's more than one object in scope that matches the noun 'box', in this case the large red box and the small blue box. The two boxes have quite distinct names, so that it's no trouble for the player to select one or the other (as in the next command). The parser uses the disambigName property of the objects concerned to list the objects the player is to choose from in a disambiguation prompt. By default, the disambigName is the same as the name. In special cases you might want to override disambigName to give distinct names for disambiguation purposes for objects you otherwise want to be named alike. But note that the parser also assigns each possibility a number so the player could also answer the question here by typimg 1 or 2. You can disable the enumeration of disambiguation options here by setting libGlobal.enumerateDisambigOptions
to nil (players can also toggle this option on and off with the DISAMBIG ENUM command).
You probaby wouldn't want to, but you could customize the 'Which do you mean' question by using a CustomMessages object, e.g.,:
CustomMessages [ Msg(which do you mean, 'which of these were you trying to refer to'] ] ;[back]
[11] Default Action Report
Laconic messages like 'Done' or 'Taken' or 'Dropped' are generally produced by the report methods for the appropriate actions defined on Thing. For further details of this see the Report section of the article on Action Results.
To change this on an individual object, simply override the appropriate action method on the object in question, calling its inherited handling and then adding your own custom message: e.g.
vase: Thing 'antique vase; delicate' "It looks incredibly delicate. " dobjFor(Drop) { action() { inherited; actionReport('You put the vase down very carefully. '); } } ;
Your custom message will automatically displace anything produce by a report method, so you don't have to worry about both messages appearing. What may be trickier is ensuring that your custom message isn't displayed in the event of an implicit action. Using actionReport()
takes care of that, otherwise you would need to do something like this:
vase: Thing 'antique vase; delicate' "It looks incredibly delicate. " dobjFor(Drop) { action() { inherited; if(!gAction.isImplicit) "You put the vase down very carefully. "; } } ;
If you want to make a global change, e.g. from the laconic "Dropped" to the slightly less laconic "Put down carefully", you can either use the above technique to modify Thing appropriately, or else modify the message used by the library. You could do this by overriding the report method:
dobjFor(Drop) { report(} { say ('Put down carefully. | {I} {put} <<gActionListStr>> down very carefully. '); } }
Or you could change it with:
CustomMessages [ Msg(report drop, 'Put down carefully. | {I} put {1} down very carefully. ') ] ;
[12] Implicit Action Announcements
Messages like "(first opening the large red box)" or "(first opening the door)" are implicit action announcements. That is they are messages from the parser telling the player that the parser has just carried out one or more commands on the player's behalf; these commands will generally have been executed in order to enable the command the player actually typed to be carried out. For example, in order to look inside the large red box it's necessary to open the red box, and in order to go through the door it's first necessary to open the door.
In a typical TADS 3 game most implicit actions will be generated by preconditions, such as objOpen. If you want to customize these implicit action reports, see the article on Implicit Action Reports later in this Technical Manual.
[12a] Not Important Message
The junk object is evidentally a Decoration. What's displayed here is a Decoration's default 'not important' message. This can be changed by overriding notImportantMsg on individual Decorations (or on Decoration itself, if so desired).
[back][13] Too Heavy Message
The table has evidently been defined of class Heavy, since this is the standard message that is displayed when a player tries to take or move an object of that class. Such messages are generated from either verify() or check(), depending on the action in question, but to change them you simply need to override cannotTakeMsgand/or cannotPutMsg and/or cannotMoveMsg, on the object in question (or the class). By default the first too of these are the same ("The X is too heavy. ") for a Heavy object.
[back][14] List Order
We can ensure we keep the three crayons listed together by overriding the listOrder property of the Crayon class to something other than the default value of 100. To ensure that the library attempted an implicit UNLOCK command when we looked in the small blue book we added the objUnlocked preoondition to its dobjFor(Open) handling, otherwise the action would have failed, telling us that the box was locked.
++ smallBox: LockableContainer 'small blue box' dobjFor(Open) { preCond = [touchObj, objUnlocked] } ;[back]
[15] readDesc
You'll notice that X BOOK and READ BOOK give different responses here (which is not the case for most objects). That's because the green book has been given a separate readDesc property.
[back][16] Verify Message
The first time the player types WEAR SOCK the parser is quite happy to carry out the command, since the sock is both within scope and defined as being a Wearable. On the second occasion, however, the parser complains that the sock is already being worn; this type of message, where an action is not (or is not yet or is no longer) reasonable is typically generated by a verify routine.
[back][17] Inventory Listing
This is a typical inventory listing, produced by the inventoryLister, which is a specialised type of itemLister.
[back]
[18] roomFirstDesc
Ordinarily it would be a bad idea to include any mention of how you arrived at the location in a room descrption, since this will generally not read well if the room is approached from some other direction, or the room is subsequently examined again. In this case, however, the landing is described differently when it is examined the second time. This is achieved by using roomFirstDesc to display a different description the first time the room is examined. This works well here since the first time the landing is examined the player character can only have just entered it from the box room.
[back][19] travelDesc
There's more than one way we could produce this description of the player character walking down the stairs. Perhaps the simplest, which is used in the code below, is to define the message we want on its travelDesc property.
[back][20] Remote Descriptions
The hall is represented in this game by two different locations, hallEast and hallWest, linked by being in the same SenseRegion. The implementation is less than perfect, however, as can be seen from the transcript.
First, the man is described and the chair are both decribed as being "in the hall west end", which doesn't read at all well, but is the best the library can do from the roomTitle. We can improve things by giving the hallWest room a name as well:
hallWest: Room 'Hall (West End)' 'west end of the hall[n]' "This large hall continues to the east. A flight of stairs leads down to the south. " east = hallEast south = hallStairsDown down asExit(south) ...
Here the first single-quoted string in the Room template is the roomTitle, which is displayed in the status line and at the head of a room description. The second single-quoted string is the vocab, in which we've just specified the name.
The next issue is the chair is mentioned twice, first as something being in the west end of the hall, and then as what Mavis is sitting on. We could probably do without the first of these. There's more than one way we could get rid of it, but for now we'll make the chair not listed if Mavis is on it:
+ woodenChair: Platform 'wooden chair' canLieOnMe = nil sightSize = large isListed = !(mavis.isIn(self)) // add this ;
With these changes we get:
Hall (East End) This large hall continues to the west. A flight of stairs leads up to the north. You can see a rubber duck here. Mavis is sitting on a wooden chair at the far end of the hall. Harold is in the west end of the hall.
The description of Mavis here hasn't been automatically generated by the library here (by default adv3Lite has no concept of postures like sitting, although we can add them via the postures extension). Here, we've generated the text about Mavis through defining the remoteSpecialDesc method on her ActorState:
Mavis starts out in an unresponsive ActorState (for details of ActorStates see the article on Creating Dynamic Characters). All we need to do is to add the following to the definition of this ActorState:
remoteSpecialDesc(pov) { "Mavis is sitting on the wooden chair at the far end of the hall. "; } specialDesc = "Mavis is sitting on the wooden chair. "We could improve the description of Harold by doing something similar on his ActorState:
+ hWaiting: ActorState specialDesc = "Harold is standing by Mavis's chair. " isInitState = true
You may be wondering why we suggest defining both a specialDesc and a remoteSpecialDesc on Mavis's ActorState but only a specialDesc on Harold's. The reason is that we want what's said about Mavis to vary according to whether the player character is viewing her from her location or from the other end of the hall, whereas the statement that Harold is standing by her chair will fit either perspective equally well. If remoteSpecialDesc(pov) is not otherwise defined, it simply uses specialDesc.
Then we'll get:
Hall (East End) This large hall continues to the west. A flight of stairs leads up to the north. You see a rubber duck here. Harold is standing at the far end of the hall. Mavis is sitting on the wooden chair at the far end of the hall.[back]
[21] Remote Descriptions and sightSize
It shouldn't be too difficult to see where the description of Harold is coming from; it's simply the desc property defined on the harold object. But why aren't we seeing a description for Mavis?
The answer is quite simple: the game defines sightSize = large
on Harold (which means we can get a description of him from a distance, including from a remote location, as here), but we have omitted to make the same change on Mavis. Mavis's sightSize is thus still at medium (the default), which means that although she can be seen from a distance, any attempt to view her from a remote location will be met with the response "She’s too far away to make out any detail."
If we want to change this we have several options, depending on what we want to achieve.
First, we could just define sightSize = large
on Mavis as well as Harold, so that we'd get her normal description too.
Second, if we're happy that Mavis is too small to be clearly discerned from a distance, but we want to customize the message that's shown in such circumstances, we use a CustomMessages object:
CustomMessages [ Msg(too far away to see detail, 'You haven\'t got your glasses on, so you can\'t really make out much of {the dobj} from this distance. ') ] ;
The third possibility is to override remoteDesc(pov) on Mavis, which will define how she's described from a remote point of view regardless of her sight size. For example:
mavis: Person 'old mavis/woman' 'Mavis' @woodenChair "She's a funny old woman, when all's said and done. " remoteDesc(pov) { "She's looks quite aged. "; } isProperName = true isHer = true posture = sitting ;
This would give us (when carried out in the east end of the hall):
>x mavis She looks quite aged.[back]
[22] Oops
The first part of the message, saying that you can't see ny dfwv" (a gross typo for "duck" here) here (meaning either that the game doesn't recognize it or there's no matching object in scope), is produced from the display()
method of UnmatchedNounError
, which is probably the easiest place to customize it. If you wanted instead to use a CustomMessages object the message key in this instance is unmatched noun
.
The second half (which would only be displayed the first time an unknown word is encountered) comes from oopsTip.tipText. For more on Tips see the Tips article in this Technical Manual.
[22a] Spelling Correction
We needed quite a gross typo to demonstrate this, since a more modest one would probably be caught and corrected by the spelling corrector, for example:
>x buck (x duck) It’s yellow.
The spelling corrector is usually helpful, but some game authors and players may occasionally find it a mixed blessing. To disable it we can set Parser.autoSpell to nil.
[back]
[23] inRoomName
The rubber duck is sensibly described as being "At the far end of the hall". This is because hallEast defines inRoomName(pov) accordingly; the inRoomName(pov) method can be used to define a prepositional phrase (e.g. "at the far end of the hall", "further up the street" or "in the north side of the field") used to describe the whereabouts of objects in this location when they're viewed from another location. The point-of-view parameter (pov) can be used to vary this phrase according to where we're looking from; we could, for example, check pov.getOutermostRoom
to decide whether to describe a particular stretch of street as "further up the street to the north" or "further down the street to the south".
[24] Listing Actors
Here Harold and Mavis are shown in the standard form for listing actors who are present in the player character's location (rather than actors in a remote location, as we discussed above). Tracing where the library actually generates these descriptions is a little complex; here's the chain:
- In the first instance, the way an actor is listed in the player character's location is determined by that actor's specialDesc. It can therefore be directly overridden here, but the standard library behaviour is to call the corresponding method on the actor's current ActorState. If the Actor is stateless or its current ActorState doesn't define a specialDesc then the actorSpecialDesc defined on the Actor will be used. Unless the actor will never change ActorStates during the course of the game it's generally a good idea not to override specialDesc directly on the actor; override actorSpecialDesc instead.
- By default, actorSpecialDesc displays "So-and-so is here" if the actor is directly located in a Room, or "So-and-so is in/on the whatever" if the Actor is in or one a nested room such as a Booth or Platform.
- The listing of actors in a remote location follows a similar logic. This is determined by that actor's remoteSpecialDesc(pov) method, which in turn calls the remoteSpecialDesc(pov) method on the actor's current ActorState (if there is one and it defines a SpecialDesc property). If Actor.remoteSpecialDesc(pov) can't obtain a remote specialDesc from the ActorState, it falls back on the actorRemoteSpecialDesc(pov) method, which by default produces an output of the form "So-and-So is in the wherever" or "So-and-so is in/on the whatever in the wherever", constructing these messages from the whatever's remoteObjInName(pov) and wherever's inRoomName(pov). Game code should avoid overriding remoteSpecialDesc(pov) on the Actor and usually refrain from overriding actorRemoteSpecialDesc(pov) unless this the way the actor's remote presence is describec is never going to vary by ActorState
- If the ActorState's remoteSpecialDesc(pov) is used it defaults to that ActorState's specialDesc.
So, for Harold, Actor.specialDesc() and Actor.remoteSpecialDesc(pov) both end up using his ActorState's specialDesc, while, for Mavis, we get her ActorState's specialDesc when the player character is in her end of the hall and its remoteSpecialDesc() when he's in the other end of the hall.
In the vast majority of cases, you'll probably want to stick to overriding the specialDesc (and, where appropriate, removeSpecialDesc(pov)) property of the relevant ActorState. For example, we might define the specialDescs on Harold's initial ActorState and Mavis's thus:
mavis: Actor 'Mavis; old; woman; her' @woodenChair "She's a funny old woman, when all's said and done. " remoteDesc(pov) { "She looks quite aged. "; } ; + ActorState isInitState = true noResponse = "The old woman simply rocks back and forth in her chair moaning, <q>Woe, woe, woe is me!</q>" stateDesc = "She's rocking back and forth in her chair moaningWoe!" specialDesc = "Mavis is slumped miserably in the wooden chair. " remoteSpecialDesc(pov) { "Mavis is sitting on a wooden chair at the far end of the hall. "; } ; harold: Actor 'Harold;;twin man brother;him' @hallWest "He's about your height and build, and really looks quite a lot like you. Since he's your twin brother this is not altogether surprising. " sightSize = large ; + hWaiting: ActorState specialDesc = "Harold is hovering anxiously over Mavis. " // improved version isInitState = true ; + hTalking: ActorState specialDesc = "Harold is standing by Mavis's chair, waiting for you to speak. " ;
To produce:
Hall (West End) This large hall continues to the east. A flight of stairs leads down to the south. At the far end of the hall, you see a rubber duck. Harold is hovering anxiously over Mavis. Mavis is slumped miserably in the wooden chair.[back]
[25] Listing Exits
First, the news in brief: the list of exits is produced by exitLister.showExitsCommand(), which in turn calls chain of methods too complex to go into here. Suffice to say that if you want to customize the way exits are listed, that's the place to look. It's also exitLister.showExitsCommand() that's responsible for displaying the explanation of the EXITS command ("You can control the exit listings with the EXITS command. EXITS STATUS ...") the first time it's used. This explanation is handled via DMsg(explain exits on off, '...') so you could use a CustomMessages object to change it if you wished.
[back][26] Actor Posture
Although you can use the postures extension if you wish, most adv3Lite games probably don't need it, and we're not using it here, which means our little game is making no attempt to keep track of anyone's posture. All the descriptions of npcs' postures are coming from the specialDesc, stateDesc, or their remote equivalents (remoteSpecialDesc and remoteDesc) on their current ActorState, which is both more flexible and easier to use in most cases. We can use one of these properties to describe our actor as standing, sitting, slouching, lounging, pacing up and down or anything else we like and simply change ActorState when we want to change this description, or we could even change the descrption within the same ActorState with a little extra custom coding, such as:
+ hWaitingState specialDesc = "Harold is <<one of>>standing<<or>hovering<<or>>pacing around<<shuffled>> Mavis' chair. " ;
Or else something like:
+ hWaitingState specialDesc = "Harold is <<postureDesc>> Mavis' chair. " postureDesc = (gRevealed('foobar') ? 'hovering' : 'standing') ;
Where postureDesc
is a custom property we've just devised for the purpose. In this little game, though, we'll keep things simple.
[27] noResponse
There are several ways in which Mavis's response, or rather non-response - could have been generated here, but given the nature of her (non-)response and the absence of any greeting protocols, the most likely (and most probable) way this has been generated is from the noResponse property of her ActorState.
[back][28] HelloTopic
Harold responds to the player character's greeting here because he's been given an explicit HelloTopic in his initially active ActorState. For a fuller explanation of this see the article on Programming Conversations with NPCs.
[back][29] Suggested Topics
The explicit greeting command has resulted in a list of topics the player could try asking Harold about (here there's actually only one). The greeting triggers a call to suggestTopics(true) on the actor initiating the conversation (in this case, and in most others, the player character); it's the 'explicit' argument that's being called as true here. The suggestTopics method then calls suggestTopicsFor(self, explicit) on the actor who his being addressed (in this case Harold), which in turn calls suggestTopicsFor() on that actor's current ActorState (which should by now be Harold's InConversationState). This then calls the ActorState's showSuggestedTopics() method.
Here, Mavis is listed as a possible topic of conversation because there's a currently active AskTopic for mavis that has a name
defined. There's also an AskTopic for the photo that has a name, but it's not active yet since at this stage the player character doesn't know about the photo.
The message about the use of the various ENUM commands the player can use is generated by the explainOptions() method of suggestedTopicLister where it is produced by DMsg(explain enumerating and hyperlinking, '...') and DMsg(explain numbering, '...), which could both be customised with a CustomMessages object if desired.
For a fuller explanation of all this see the article on Programming Conversations with NPCs.
[back][30] AskTopic
Here we see a fairly standard kind of response from an AskTopic. This one informs the player character about the photograph and so needs to mark that object as known about so that the player can now refer to it in subsequent conversation. At the same time the player's curiosity about Mavis is exausted (that is, she won't appear as a suggested topic of conversation again.
For a fuller explanation of all this see the article on Programming Conversations with NPCs.
[back][31] Explicit Topic Request
We have just met a topic inventory display above. The procedure for producing it is much the same here, except that the output is not enclosed in parentheses (since it's the response to an explicit TOPICS command, not a by-product of a greetings command). One point to note is that the list has changed: the player character's curiosity about Mavis has been exhausted, but he now knows about the photograph, so that has become available to be asked about.
[back][32] StopEventList
Once again we see the output from an ordinary AskTopic, but this one appears also to be a StopEventList, since we get a different response second time round, which repeats thereafter. See the article on Programming Conversations with NPCs for more information about providing sequential and random responses.
[back][33] DefaultTopic
The player is always likely to ask an NPC about topics for which the author has provided no specific response, as here. At least we can assume that the author of this game would not have thought to provide a response to asking Harold about American foreign policy, so this will almost certainly be the response from some kind of DefaultTopic. This one has the merit of keeping the player firmly focused on his next objective.
For more information on how DefaultTopics can be used in NPC conversations, see the article on Programming Conversations with NPCs.
[back][34] Missing ByeTopic
The rather abrupt "The conversation is ended." response occurs here because the author has forgotten to define an appropriate ByeTopic for Harold at this point. The response is defined on Actor.noGoodbyeResponseMsg so could be easily customised by overriding that property on the Actor class on or individual actors, but what we really ought to do is to define a ByeTopic and locate it in the hTalking ActorState:
++ ByeTopic "<q>Bye for now,</q> you say.\b <q>See you soon,</q> he replies. " ;
We'd then see:
>bye “Bye for now,” you say. “See you soon,” he replies.
For a more sophisticated implementation, we might want to provide a separate ImpByeTopic, LeaveByeTopic and so forth.
[back][35] Darkness
There are two library default messages here, which we may as well take together. The first, 'In the dark', is the default value of roomDarkName. The second "It's pitch black" is likewise the default value of roomDarkDesc. These give respectively the name and the description of a location when there's not enough light present to see it by.
It's easy enough to customize both messages when appropriate, as it probably is here, since the player character is presumably at least aware that the stairs lead down into the cellar. So we might change the definition of the cellar location thus:
cellar: DarkRoom 'Cellar' "The cellar is almost bare. A flight of stairs leads up to the north. " darkName = 'Cellar (in the dark)' darkDesc = "You're dimly aware of the flight of stairs leading back up, but otherwise it's too dark to see anything in here. " north = cellarStairs up asExit(north) ;
We'd then get:
>d Cellar (in the dark) You're dimly aware of the flight of stairs leading back up, but otherwise it's too dark to see anything in here.
Since the dark description now mentions the flight of stairs, it might be a good idea to define visibleInDark = true
on the stairs object, so that the flight of stairs can be referred to by the player (e.g. CLIMB STAIRS) even in the absence of light.
[36] Torch Turned On Message
This is simply the default message for turning something on, generated from reportDobjTurnOn(). The actual message used is If you wanted to customize it for a particular Flashlight you'd probably do so in its actionDobjTurnOn() (i.e. the action() method in the dobjFor(TurnOn) block).
[back][37] A Newly Lit Location
Turning on the torch also causes the room description to be re-displayed, now that the player character can see it. This is one of the things carried out by the afterAction() method of the current Action. Earlier in the cycle Action.exec() notes whether the Actor's location was located before the action was carried out. Action.afterAction() checks whether lighting conditions have changed, so requiring a new description of the newly-lit location or else an announcement of the onset of darkness.
[back][38] Score Notification
We get this score notification because actionDobjTake() on the photo object calls awardPointsOnce() on an associated Achievement (a number of other methods might have had a similar effect, but this is the one that was used here). Using awardPointsOnce() prevents the player building up an inflated score by repeatedly dropping and taking the photo.
Whatever the precise method use to invoke the scoring, it will usually be routed through the addToScoreOnce() method of the Achievement which in turn calls the addToScore() function which in turn calls libScore.addToScore_(points, desc), which does most of the actual work.
The actual message announcing the change in score comes from scoreNotifier.checkNotification(), which only issues a score notification if libScore.scoreNotify.isOn is true. If it is, libScore.firstScoreChange() is used if this is the first time a change of score is being notified in a game, or libScore.scoreChange() thereafter. Both these libScore.methods then call libScore.basicScoreChange() to announce the actual change in score, but libScore.firstScoreChange() then goes on to diplay the additional message "If you’d prefer not to be notified about score changes in the future, type NOTIFY OFF", via DMSg(first score change, '...').
[back][39] State-Related Text
We've already seen an inventory listing before. What's new here is the text '(providing light)' following the name of the torch in the inventory listing. Without going into arcane details of the way various inventory listers go about their business, we can say that this text ultimately comes from the additionalInfo property of the LitUnlit State object, so that one way to customize it would be with the following:
modify LitUnlit additionalInfo = [[true, ' (currently switched on)']] ;
We'd then see:
>i You are carrying a large black torch (currently switched on) and a photo of Buster Keaton, and you’re wearing an odd sock.
This is fine when the torch is the only light source in the game, but if there were multiple light sources it might not be so good, since this change would then apply to all of them. To allow for this we could make the more complicated change:
modify ItemLister listName(o) { local oldLitInfo = LitUnlit.additionalInfo; try { if(o.providingLightMsg && o.isLit) LitUnlit.additionalInfo = [[true, o.providingLightMsg]]; return inherited(o); } finally { LitUnlit.additionalInfo = oldLitInfo; } } ;
One could then override providingLightMsg on each and every light source that required a custom version.
See the Adv3Lite Libary Manual for a brief explanation of the State mechanism.
[back][40] Cannot Go That Way
This is the message you get when the player character tries to go in an unavailable direction. The message is generated by cannotGoThatWay() (called on the current room), which in turn first displays the room's cannotGoThatWayMsg, and then calls cannotGoShowExits(gActor) to list the exits that are available.
There are thus several places at which you can customize this, but two in particular are useful. If you want to change the first part of the message but leave the listing of exits as it is, then simply override cannotGoThatWayMsg, e.g.:
cellar: DarkRoom 'Cellar' "The cellar is almost bare. A flight of stairs leads up to the north. " north = cellarStairs up asExit(north) cannotGoThatWayMsg = 'There\'s obviously nothing in that direction. ' ;
This would result in:
>e There's obviously nothing in that direction. From here you could go north.
The alternative is to replace the entire output by overriding cannotGoThatWay(dir):
cellar: DarkRoom 'Cellar' "The cellar is almost bare. A flight of stairs leads up to the north. " north = cellarStairs up asExit(north) cannotGoThatWay(dir) { "There's no point blundering around in that direction; you know perfectly well that the only way out of here is back <> the stairs. "; } ;
Using the aHref() function is the icing on the cake here; it provides a hyperlink on the word "up" in the output text that the player can click to go up from the cellar, but there's no need to do this if you don't want to. The effect of this override is to produce:
>e There's no point blundering around in that direction; you know perfectly well that the only way out of here is back up the stairs. "[back]
[41] Announcement of Darkness
The mechanism for recognizing and responding to the onset of light or darkness has already been described above. The new point to note here is that the actual message announcing the darkness ("You are plunged into darkness.") comes from DMsg(onset of darkness, '\n{I} {am} plunged into darkness. ')
If we wanted to change it this message, this would therefore be the most convenient place to change it, e.g.:
CustomMessages [ Msg(onset of darkness, '\nNow it\'s too dark to see anything. ') ] ;[back]
[42] Cant Go in Darkness
This message is produced by the method cannotGoThatWayInDark(dir), called on the current location. By default it displays the location's cannotGoThatWayMsg, but obviously it could be overridden to display any message you like, e.g.:
cellar: DarkRoom 'Cellar' "The cellar is almost bare. A flight of stairs leads up to the north. " north = cellarStairs up asExit(north) cannotGoThatWayInDark(dir) { "Although it's dark and you can hardly see a thing down here, you're pretty certain that the only way out is back up the stairs. "; } ;[back]
[43] Winning Cut-Scene
There are several distant ways we could generate this brief closing cut-scene. This one was implemented by setting up a Fuse in the travelerEntering() method of hallWest if the player enters the room carrying the photograph; the fuse then executes at the end of the turn, displaying the message and ending the game.
[back][44] Winning Message
This winning message, which marks the end of the game, is here generated by the call to finishGameMsg() in the (custom) winGame() method on hallWest.
finishGameMsg() can either be called with a literal single-quoted string as its first argument (as in this example), or with a FinishType object. If it's called with a literal string, that literal string is displayed as the winning messages. If it's called with a FinishType object, that object's finishMsg is displayed. The library defines four FinishType objects: ftVictory ('You have won'), ftDeath ('You have died'), ftFailure ('You have failed') and ftGameOver ('Game Over'). You can define additional FinishType objects if you like, but this is only worthwhile if you want to use them more than once (i.e. if the same ending message can be displayed from calls to finishGameMsg at several different places in your code).
The second parameter used by finishGameMsg is a list of FinishOption objects. Once the game is over the parser will always offer the player the option to RESTORE, RESTART or QUIT, but here the author can offer the player additional options, in this case UNDO and FULL SCORE (using finishOptionUndo and finishOptionFullScore). Other FinishOption types that can be used include finishOptionScore, finishOptionAmusing, and finishOptionCredits.
[back][45] Final Score Notification
This final score notification is generated from a call to libGlobal.scoreObj.runScoreNotifier(); in the finishGameMsg() function (which was called from author code; see the definition of hallWest.winGame).
[back][46]
The FULL SCORE command generates this report of the score and list of achievements by calling libGlobal.scoreObj.showFullScore(), which will normally be equivalent to libScore.showFullScore(). The points awarded for and description of each achievement listed are defined on the various Achievement objects defined in the game and activated by a call to awardPointsOnce(). For a fuller account of the scoring system see the Library Manual chapter on Scoring.
[back][47] Goodbye Message
This farewell message is produced by the showGoodbye() method on gameMain.
[back]The Source Code
#charset "us-ascii" #include <tads.h> #include "advlite.h" /* * Our game credits and version information. This object isn't required * by the system, but our GameInfo initialization above needs this for * some of its information. * * IMPORTANT - You should customize some of the text below, as marked: * the name of your game, your byline, and so on. */ versionInfo: GameID name = 'TADS 3 Starter Game' byline = 'by An Author' htmlByline = 'by <a href="mailto:your-email@your-address.com"> YOUR NAME</a>' version = '1.0' authorEmail = 'YOUR NAME <your-email@your-address.com>' desc = 'CUSTOMIZE - this should provide a brief description of the game, in plain text format.' htmlDesc = 'CUSTOMIZE - this should provide a brief description of the game, in <b>HTML</b> format.' showCredit() { /* show our credits */ "Put credits for the game here. "; } showAbout() { "This is just a brief demo game to illustrate where various messages come from in TADS 3. Or you could think of it as a high adventure with deep characterization, a riveting plot, and a myriad of amazing puzzles -- except that if you do think of ir this way you'll be sadly disappointed! "; } ; gameMain: GameMainDef /* the initial player character is 'me' */ initialPlayerChar = me /* * Show our introductory message. This is displayed just before the * game starts. Most games will want to show a prologue here, * setting up the situation for the player, and show the title of the * game. */ showIntro() { "Welcome to this demo!<.p>"; } /* * Show the "goodbye" message. This is displayed on our way out, * after the user quits the game. You don't have to display anything * here, but many games display something here to acknowledge that * the player is ending the session. */ showGoodbye() { "<.p>Thanks for playing!\b"; } ; me: Player location = boxRoom desc = "You're neither as young as you used to be or as young as you'd like to be. " ; boxRoom: Room 'Box Room' "This large box room is strewn with junk. The only way out is via a door to the east. " east = boxDoor ; + boxDoor: Door 'door' "It's just a plain door, painted white. " ; + Decoration 'junk' "All sorts of stuff, the accumulated detritus of decades, mostly not worth bothering with. " ; + largeBox: OpenableContainer 'large red box' "It's large, red and box-shaped<<unless isOpen>>, and is currently closed<<end>>. " initSpecialDesc = "A large red box sits in the corner. " ; ++ teddy: Thing 'old teddy bear' "The old teddy stills shows the dreadful effects of too much love from an over-enthusiastic child, but has long since suffered years of neglect. " ; ++ legoBrick: Thing 'red lego brick' ; ++ torch: Flashlight 'large black torch;;flashlight' "It looks sturdy enough, and seems to be in good working order. " ; + Heavy, Surface 'small square table' specialDesc = "A small square table stands in the middle of the room. " ; ++ smallBox: LockableContainer 'small blue box' dobjFor(Open) { preCond = [touchObj, objUnlocked] } ; +++ redCrayon: Crayon 'red +' ; +++ blueCrayon: Crayon 'blue +' ; +++ yellowCrayon: Crayon 'yellow +' ; + oldCoat: Wearable 'old coat; brown' "It's brown, but not too motheaten. " ; + greenBook: Thing 'small green book; getting started' "It's a copy of Getting Started in TADS 6. " readDesc = "Reading the book brings back happy memories of writing IF in your early twenties. With TADS 6 all you had to do was fill in a form specifying the genre of the game, the names of a few key NPCs, and the median age of the target audience, select a plot-type from a drop-down list, and then hit either the <q>generate two-hour version for IF-Comp</q> button or <q>generate long version</q> button, and a bug-free, typo-free TADS 6 game would be instantly created for you. Shame so many diehards on intfiction.org thought this took all the fun out of creating IF." ; + oddSock: Wearable 'odd sock; green' "Washing machines have a habit of swallowing odd socks, and this one (which happens to be green) must be the survivor of what was once a pair. " ; + tennisBall: Thing 'tennis ball; split' "This one has been split open; it's no good for playing tennis with any more. " ; class Crayon: Thing 'crayon' /* This ensures that crayons will be listed together */ listOrder = 90 ; landing: Room 'Landing' "A flight of stairs leads down to the south, while a door leads into the box room just to the west. " roomFirstDesc = "Emerging from the box room you find yourself standing at the top of a flight of stairs leading down to the south. " west = landingDoor south = landingStairs down asExit(south) ; + landingDoor: Door ->boxDoor 'box room door' ; + landingStairs: StairwayDown 'flight of stairs[n];;;it them' travelDesc = "You walk briskly down the stairs. " destination = hallEast ; hallRegion: SenseRegion rooms = [hallEast, hallWest] ; hallEast: Room 'Hall (East End)' 'east end of the hall[n]' "This large hall continues to the west. A flight of stairs leads up to the north. " inRoomName(pov) { return 'at the far end of the hall'; } north = hallStairs up asExit(north) west = hallWest ; + hallStairs: StairwayUp 'flight of stairs[n]' destination = landing ; + rubberDuck: Thing 'rubber duck; yellow' "It's yellow. " afterAction() { if(gActionIs(Jump)) { moveInto(isIn(hallEast) ? hallWest: hallWest); "<.p>As if startled by your sudden exertion, the rubber duck lets out a clockwork quack and waddles to the other end of the hall. "; } } ; hallWest: Room 'Hall (West End)' 'west end of the hall[n]' "This large hall continues to the east. A flight of stairs leads down to the south. " east = hallEast south = hallStairsDown down asExit(south) travelerEntering(traveler, origin) { if(photo.isIn(traveler)) new Fuse(self,&winGame, 0); } winGame() { "You turn to Mavis and hand her the photograph of Buster Keaton. She snatches it eagerly from your grasp, instantly stops her moaning, and showers the actor's picture with delighted kisses.\b"; achievement.awardPointsOnce(); finishGameMsg('You have made an old lady very happy', [finishOptionUndo, finishOptionFullScore]); } achievement: Achievement { +10 "making Mavis happy" } ; + woodenChair: Platform 'wooden chair' canLieOnMe = nil sightSize = large ; + hallStairsDown: StairwayDown 'flight of stairs[n];;;it them' destination = cellar ; cellar: DarkRoom Room 'Cellar' "The cellar is almost bare. A flight of stairs leads up to the north. " north = cellarStairs up asExit(north) isLit = nil ; + cellarStairs: StairwayUp 'flight of stairs[n];;;it them' destination = hallWest ; + photo: Thing 'photo of Buster Keaton; favourite; picture photograph' "The picture shows Buster Keaton posing in a Confederate uniform in The General. " dobjFor(Take) { action() { inherited; achievement.awardPointsOnce(); } } achievement: Achievement { +10 "retrieving the photograph" } ; mavis: Actor 'Mavis; old; woman; her' @woodenChair "She's a funny old woman, when all's said and done. " // sightSize = large /* We could add: */ // remoteDesc(pov) { "She's lookes quite aged. "; } ; + ActorState isInitState = true noResponse = "The old woman simply rocks back and forth in her chair moaning,Woe, woe, woe is me!" stateDesc = "She's rocking back and forth in her chair moaningWoe!" specialDesc = "Mavis is sitting on the wooden chair. " remoteSpecialDesc(pov) { "Mavis is sitting on a wooden chair at the far end of the hall. "; } ; harold: Actor 'Harold;;twin man brother;him' @hallWest "He's about your height and build, and really looks quite a lot like you. Since he's your twin brother this is not altogether surprising. " sightSize = large ; + hWaiting: ActorState /* We should add: */ // specialDesc = "Harold is standing by Mavis\'s chair. " isInitState = true ; ++ HelloTopic "Hello, Harold!you say.\bHi there!he replies. " changeToState = hTalking ; + hTalking: ActorState specialDesc = "Harold is standing by Mavis\'s chair, waiting for you to speak. " ; ++ AskTopic @mavis "<q>What's up with Mavis?</q> you ask.\b <q>She's inconsolable -- she can't find her favourite photograph of Buster Keaton,</q> he tells you. <<gSetKnown(photo)>>" name = 'Mavis' ; ++ AskTopic, StopEventList @photo [ '<q>Where did Mavis leave the photo?</q> you ask.\b <q>I think it may be in the cellar; but it\'s dark down there so I couldn\'t find it,</q> he tells you. ', '<q>You think Mavis\'s photo of Buster Keaton may be in the cellar?</q> you ask.\b <q>That\'s right,</q> he nods, <q>Be a good fellow and get it for her, her moaning is getting on my nerves.</q> ' ] name = 'the photo' ; ++ DefaultAnyTopic "<q>I think you'd better help poor Mavis before we discuss that,</q> he suggests. " ;