Beginnings
A work of Interactive Fiction has to start somewhere, and preferably with something a little more informative than a blank screen with a command prompt. In any case, for the game to work properly you need to define at least a basic set of data about the game and some of its starting conditions. We saw a brief example of this in the section on defining a minimal game, but now we should cover the topic a little more formally and in more depth.
The versionInfo Object
Your game should normally define a versionInfo object (of the GameID class) to:
- Specify basic information about your game (its name, author, etc.)
- Define the response to informational commands like ABOUT and CREDITS.
The properties and methods of versionInfo you'll typically need to define are:
- IFID: a random 32-digit hex number to uniquely identify the game; you can generate one at http://www.tads.org/ifidgen/ifidgen
- name: The name of your game, e.g. 'Amazing Quest'
- byline: Your name or pseudonym, e.g. 'by I.F. Author'
- htmlByline: the main author credit as an HTML fragment
- authorEmail: the authors' names and email addresses (in GameInfo format) (e.g. 'I.F.Author <i.author@whatevermail.com>')
- desc: a short blurb describing the game, in plain text format
- htmlDesc: the descriptive blurb as an HTML fragment
- version:the game's version number string
- showAbout(): the method to run in response to an ABOUT command.
- showCredit(): the method to run in response to a CREDITS command.
So, for example, a typical versionInfo definition might look like this:
versionInfo: GameID IFID = '0D9D2F69-90D5-4BDA-A21F-5B64C878D0AB' name = 'Fire!' byline = 'by Eric Eve' htmlByline = 'by <a href="mailto:eric.eve@nospam.com"> Eric Eve</a>' version = '1' authorEmail = 'Eric Eve <eric.eve@nospam.com>' desc = 'A test game for the adv3lite library.' htmlDesc = 'A test game for the adv3lite library.' showAbout() { aboutMenu.display(); "This is a demonstration/test game for the adv3Lite library. It should be possible to reach a winning solution using a basic subset of common IF commands.<.p>"; } showCredit() { "Fire! by Eric Eve\b adv3Lite library by Eric Eve with substantial chunks borrowed from the Mercury and adv3 libraries by Mike Roberts. "; } ;
In addition, you can override the following settings if you don't like the defaults inherited from GameInfoModuleID:
- releaseDate - the release date string (YYYY-MM-DD)
- licenseType - freeware, shareware, etc.
- copyingRules - summary rules on copying
- presentationProfile - Multimedia, Plain Text
For further details consult the comments on the GameInfoModuleID in the modid.t file and/or consult the article on "Bibliographical Metadata" in the TADS 3 Technical Manual (although this was written for the adv3 library, everything in the Metadata article should apply equally well to adv3Lite).
The gameMain Object
You game must define a gameMain object, which should be of the GameMainDef class. At a basic minimum it must define the initialPlayerChar property to identify the object that is to represent the player character at the start of the game (typically this is called me, though you can call it anything you like). A minimal gameMain definition will therefore look like this:
gameMain: GameMainDef
initialPlayerChar = me //although this isn't necessary if you define your player character with the Player class
;
In practice you'll normally want to define rather more than this on your gameMain object. The other properties and methods you may want to define include:
- showIntro(): Show the game's introduction. This routine is called by the default newGame() just before entering the main command loop. The command loop starts off by showing the initial room description,so there's no need to do that here. Most games will want to override this, to show a prologue message setting up the game's initial situation for the player.
- showGoodbye(): Show the "goodbye" message. This is called after the main command loop terminates. It doesn't show anything by default. If you want to show a "thanks for playing" type of message as the game exits, override this routine with the desired text.
- setAboutBox(): Define how the about box should appear (typically in response to selecting Help, About from the menu of an HTML TADS interpreter. For more details on how to do this see the notes below.
- maxScore(): The maximum number of points possible in the game. If the game includes the scoring module at all, and this is non-nil, the SCORE and FULL SCORE commands will display this value to the player as a rough indication of how much farther there is to go in the game. By default, the library initializes this on demand, by calculating the sum of the point values of the Achievement objects in the game. The game can override this if needed to specify a specific maximum possible score, rather than relying on the automatic calculation.
- beforeRunsBeforeCheck: Should the "before" notifications (beforeAction and roomBeforeAction) run before or after the "check" phase? The adv3 library traditionally ran the "before" notifiers first, but the adv3Lite runs the "before" notifiers after the check phase, so in the adv3Lite library the default value is nil. In many ways it's more logical and useful to run "check" first. That way, you can consider the action to be more or less committed by the time the "before" notifiers are invoked. Of course, a command is never truly committed until it's actually been executed, since a "before" handler could always cancel it. But this is relatively rare - "before" handlers usually carry out side effects, so it's very useful to be able to know that the command has already passed all of its own internal checks by the time "before" is invoked - that way, you can invoke side effects without worrying that the command will subsequently fail.
- usePastTense: This can be set to true if you want your game to be narrated in the past tense (it has the effect of making all the library messages appear in the past tense). If it's left at nil, the default, the game will be narrated in the present tense. The adv3Lite library, courtesy of the Mercury code it inherits, is actually capable of narration in six different tenses, but the present and the past are the ones most likely to be use in at least 99% of games. If you want to use one of the other tenses see notes below.
- allVerbsAllowAll: This can be set to nil to prevent the player using ALL (e.g. EXAMINE ALL) with all but a handful of inventory-handling verbs (TAKE, DROP, PUT and DOFF), since some game authors may feel that allowing ALL with every verb allows a sledge-hammer approach to their games. The default value is true.
- useParentheticalListing: If this flag is true (the default is nil) then room description listings and examine listings use a parenthetical style to show subcontents (e.g. "On the table you see a box (in which is a brass key)") instead of showing each item and its contents in a separate paragraph.
- paraBrksBtwnSubcontents: If this flag is true (the default) then room description listings will include a paragraph break between each set of subcontents listings (i.e. the listing of the contents of each item in the room that has visible contents). If it is nil the subcontents listings will all be run into a single paragraph. Note that the global setting defined here can be overridden on individual rooms.
- againRepeatsParse: This controls how the AGAIN command is interpreted. It is nil (the default, representing the previous behaviour of adv3Lite), AGAIN repeats the resolved command, that is, the command as it was interpreted on the previous turn, with the same objects. If it is true, then the text of the previous command is reparsed, which may result in a different interpretation involving different objects. For example, suppose there are two coins on the floor. If againRepeatsParse is nil then TAKE COIN followed by AGAIN will result in one of the coins being taken, followed by an attempt to take the same coin again (which will fail with a message that you're already holding the coin). If againRepeatsParse is true, however, than TAKE COIN followed by AGAIN will result in one coin being taken, and then the other, just as if the player had typed TAKE COIN twice. Which is the better behaviour depends on context; while in the coin example the second interpretation seems better, if a player were to type EXAMINE BOOK and be prompted to say which one, the second interpretation would result in the disambiguation prompt being repeated, which might seem perverse.
- autoSwitchAgain: this tries to give the player the best of both worlds by switching againRepeatsParse between true and nil depending on whether the command to be repeated is the kind of command it would make sense to repeat with the same objects (if it isn't, the command is reparsed in the hope of finding a more sensible match). This is far from absolutely foolproof, and should be regarded as experimental. It is true (switched on) by default, but game authors may wish to set it to nil if it proves troublesome.
- verbose: flag - is this game in verbose mode (full room descriptions are shown every time) or in brief mode (full room descriptions are shown only on the first visit or in response to an explicit LOOK command). By default verbose is true, but players can change it using the commands BRIEF and VERBOSE.
- fastGoTo. By default fastGoTo is nil, but if it is set to true then the GO TO command will move the player character continuously to his/her destination (unless some obstacle stops the journey) without the player needing to use the CONTINUE command.
If you look at the definition of GameMainDef in misc.t you'll see a number of other methods and properties. Some of these, such as newGame(), restoreAndRunGame(filename), setGameTitle() and getSaveDesc(userDesc) are probably left as the library defines them unless you really have a need to override them and you know what you're doing. The rest are not guaranteed to work in the current version of the adv3Lite library and are probably best left alone (they have been left in from the adv3 version of GameMainDef for possible implementation in a later version of adv3Lite).
A fairly typical gameMain definition might thus look something like this:
gameMain: GameMainDef initialPlayerChar = me showIntro() { cls(); george.startFollowing; "<b><font color='red'>FIRE!</font></b> You woke up just now, choking from the smoke that was already starting to fill your bedroom, threw something on and hurried downstairs -- narrowly missing tripping over the beach ball so thoughtgfully left on the landing by your <i>dear</i> nephew Jason -- you <i>knew</i> having him to stay yesterday would be trouble -- perhaps he's even responsible for the fire (not that he's around any more to blame -- that's one less thing to worry about anyway).\b So, here you are, in the hall, all ready to dash out of the house before it burns down around you. There's just one problem: in your hurry to get downstairs you left your front door key in your bedroom.<.p>"; } ;
Notes
When defining a showAboutBox() method you'd typically make it display a string beginning and ending with the <ABOUTBOX> and </ABOUTBOX> tags. A very basic setAboutBox() method that picks up all the relevant text from the versionInfo object might look like this:
setAboutBox() { "<ABOUTBOX><CENTER><FONT size=+2 color=red><b><<versionInfo.name>> </b></FONT>\b <<versionInfo.byline>>\b Version <<versionInfo.version>></CENTER></ABOUTBOX>"; }
For tenses other than past or present, override Narrator.tense to be one of Present ('Bob opens the box'), Past ('Bob opened the box'), Perfect ('Bob has opened the box'), PastPerfect ('Bob had opened the box'), Future ('Bob will open the box'), or FuturePefect ('Bob will have opened the box'). By default the library defines Narrator.tense as (gameMain.usePastTense ? Past : Present).
The showIntro() method is primarily for showing the game's introduction. It may also be a convenient place to put small amounts of start-up code used for initializing the game state (such as starting a Daemon running), but for initialization code you should also consider using PreinitObject and InitObject, which are definined in the System Library, and which you can read about in the "Program Initialization" section of the TADS 3 System Manual.
Defining the Player Character
Every TADS 3 game written with the adv3Lite library must define one object to be the player character (the character from whose viewpoint the game is played); the easiest way to do this is to define the initial player character using the Player class. It is possible to change the player character in the course of play, although many if not most games will probably stick to the same player character throughout; in any case a game must define which object is the player character at the start of play, in other words, the initial player character. If you do plan to change the player character during the course of the game, you should define every object that is going to represent the player character at one time or another (apart from the initial one) as an Actor. The player character should be defined as a Player object. Which object represents the initial player character can also be defined either by setting the initialPlayerChar property on the gameMain object, or by setting isInitialPlayerChar = true on the object in question; the Player class does that for you automatically. If both are done, the initialPlayerChar object defined on the gameMain object will take precedence.
To unpack that somewhat terse summary, we may begin by noting that if you create a new adv3Lite new game using the new Project Wizard in TADS 3 Workbench, the Wizard will create a minimal template game that does all this work for you thus:
#charset "us-ascii" #include <tads.h> #include "advlite.h" versionInfo: GameID IFID = '$IFID$' name = '$TITLE$' byline = 'by $AUTHOR$' htmlByline = 'by <a href="mailto:$EMAIL$">$AUTHOR$</a>' version = '1' authorEmail = '$AUTHOR$ <$EMAIL$>' desc = '$DESC$' htmlDesc = '$HTMLDESC$' ; gameMain: GameMainDef /* Define the initial player character; this is compulsory */ initialPlayerChar = me ; /* The starting location; this can be called anything you like */ startroom: Room 'The Starting Location' "Add your description here. " ; /* * 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: Player 'you' // person = 1 or 3 for a first-person or third-person game ;
Except that the versionInfo object will be filled in with the data you entered in the wizard. This way of declaring the initial player character has a couple of advantages: (1) it makes it clear just by looking at the gameMain object which object is the initial player character (you don't have to go hunting for it further down in the source code; (2) it makes it clear that the player character object is simply a Thing with a few extra properties set and in particular (3) it calls attention to the person property which you may want to change for a first-person or third-person game. If you've created a new game via the Workbench wizard this has no real downside, since the wizard will have done virtually all the work for you.
If you are starting a new game without using the wizard (perhaps because you aren't using Workbench) you can save yourself a small amount of typing by using the Player class instead of the Thing class to define the player character object:
The player character object can be called anything, me is just the conventional name to use. In game code the player character should be referenced as gPlayerChar.
Actor and Player
The Actor class defined in thing.t has hardly any of the functionality of the full Actor class defined in the Actormodule. The basic thing.t Actor class exists mostly for internal library purposes (to allow certain parts of the core library to reference an Actor class) and to provide a suitable superclass for the Player class. For an example of the sort of very simmple NPC that might be defined using this class (if you were using adv3Liter, say) see the discussion in the setion on modules.
The Player class should be used to define the initial player character. Only one such object should be defined in your game. Using the Player code for this purpose ensures that your initial player character has the right properties defined to act as such, and removes the need to define the initialPlayerChar property on gameMain. If the Actor module is present (as it is likely to be in most advLite games, although not in adv3Liter), the Player will then descend from the full Actor class, meaning amongst other things that if your player character changes during the course of your game, your initial player character will be a fully-fledged actor (which is essential if the new player character is to interact with him or her).