This file contains a list of changes that have been made to TADS, starting with the version 2.5.0 release.
The changes listed in this file are arranged chronologically: releases are listed with the most recent release first. Each release incorporates all new features and bug fixes of each prior release unless otherwise stated.
Since version 2.2.6, the TADS change log has been divided into two separate files: one for "generic" changes that apply to TADS on all types of computers and operating systems, and one for changes that apply only to specific platforms. This file contains the generic release notes. Platform-specific release notes are in a separate file for each platform:
These release notes sometimes refer to the "character-mode" or "text-only" TADS Interpreter. This is meant to distinguish the traditional TADS Interpreter, which can only display text, from the multimedia interpreters such as HTML TADS and HyperTADS, which can display graphics as well as text. The traditional TADS Interpreter has a graphical user interface on some systems, such as the Macintosh, so it's not really a character-mode application on those systems; nonetheless, we still refer to it here as the character-mode Interpreter simply to make it clear that we're not talking about one of the multimedia versions.
Changes prior to version 2.5.0 are not included in this file, to keep this file's size under control. Older release notes are in separate files:
Because the older revision history files are quite large and are
static (they are, after all, historical), they're not included in the
normal TADS distributions, but you can download them from the
Interactive Fiction Archive via the internet at
ftp://ftp.gmd.de/if-archive/programming/tads/manuals/.
At the start of the execution phase, before calling verbAction() for the first direct object in the command, the parser invokes the new function preCommand():
preCommand(actor, verb, dobj_list, prep, iobj);
The "actor", "verb", "prep", and "iobj" parameters are objects giving the actor, verb, preposition, and indirect object in the command, respectively. The "dobj_list" parameter is a list of the direct objects in the command, in the same order as they appear in the command.
This function can use exit to skip to fuses and daemons, or it can use abort to cancel the command entirely, in which case the parser will skip directly to the endCommand function (see below). If this function simply returns, the parser continues processing the command as normal.
In past versions of TADS, it wasn't possible to add special event processing at the end of a turn. The closest approximation was to put processing in a fuse or daemon, but this approach has several drawbacks: no information is available in a fuse or daemon on the last command executed, and it is not possible to specify the order of execution of fuses and daemons.
Two new parser hooks provide for structured event handling at the end of a turn. The first hook, the postAction function, lets you write code that the parser calls immediately after the "action" method, and before any fuses or daemons. The second hook, the endCommand function, lets you write code that the parser calls at the end of a turn, just after running all of the fuses and daemons for the turn.
The parser calls postAction once for each object in a command with multiple direct objects, just after it calls the "action" method for the object. If the command is terminated early with exit, exitobj, or abort, the parser invoked postAction immediately after the exit, exitobj, or abort statement executes. The parser calls postAction with the following arguments:
postAction(actor, verb, dobj, prep, iobj, status);
The first five parameters specify the current command; any of the objects except "actor" and "verb" can be nil. The "status" parameter has the same meaning as the return codes from the execCommand built-in function; it can be one of the following values, defined in adv.t:
|
The postAction function returns no value.
The parser invokes the endCommand after all of the fuses and daemons have finished running at the end of a turn. This function is called once per command, not per object; in a command with multiple direct objects, this function is called only once, just as fuses and daemons are called only once for the entire command.
The parser calls endCommand as follows:
endCommand(actor, verb, dobj_list, prep, iobj, status);
The "status" parameter has the same meaning as the status code parameter to postAction. The other parameters have the same values as they did in the call to preCommand that the parser makes at the start of the execution phase for the command.
endCommand is always invoked at the end of a turn. If an abort statement is executed in the course of a turn, the parser skips directly to endCommand, because abort skips the daemons and fuses. This means that endCommand is executed at the end of a turn even when fuses and daemons are skipped.
The endCommand function returns no value.
A new built-in function, inputdialog(), lets you ask the player a multiple-choice question. On graphical systems, inputdialog() displays a system dialog box, and lets the user respond by clicking a button. On text systems, this function displays a textual prompt and lets the user respond with the keyboard.
inputdialog takes the following parameters:
inputdialog(icon, prompt, response_list, default_idx, cancel_idx)
The "icon" parameter indicates which icon, if any, to display. The icon will only be displayed on graphical systems; text-only systems ignore this parameter. These icon constants are defined in adv.t:
|
The "prompt" parameter is the message string to display. For graphical systems, this message is displayed in a dialog box; for text systems, it's simply displayed on the terminal.
The "response_list" is a list of strings giving the valid responses. Each entry in the list is a string giving one possible response. On graphical systems, one button is displayed in the dialog for each response string; the response string is the button's label. On text systems, the responses are displayed to the player after the prompt string.
Each string in the response list can optionally include an ampersand character ("&") before the character that serves as a keyboard short-cut for the response. The ampersand is not displayed in the button label or response list displayed to the player. For example, the response list string '&Yes' makes the "Y" key a short-cut for the button, which is labeled "Yes" in the dialog. On some systems the short-cut key will be indicated visually in the dialog; on Windows, for example, the "Y" in the "Yes" button would be underlined to indicate that the letter "Y" is the short-cut for the button. If no ampersand appears in a response list item, the item has no short-cut.
On text-only systems, the keyboard short-cut will be indicated visually by enclosing the short-cut letter in parentheses when dispalying the list of possible responses to the player. If a response item has no short-cut key, the player must enter a sufficiently long leading substring of the response item so that the response is unambiguous with the other valid responses.
Each element of the list can be a number, rather than a string. If an element is a number, it specifies that the button should use a pre-defined standard label. You should use standard labels when possible, because these labels will follow local system conventions and will be localized to the player's language settings; these labels are read from external resources on platforms with appropriate operating system support, so they can be localized easily. To select a standard label, use one of the following values, defined in adv.t:
|
The strings shown above do not necessarily reflect the actual button text that the player will see, because the actual label will vary by platform and by language. Whatever label is displayed, though, will convey to the user the same meaning.
You can also select an entire standard set of buttons, rather than specifying each button individually. If the response_list parameter is a number, rather than a list, it indicates that a standard set of buttons is to be used, selected from a pre-defined list. The advantage of using one of these pre-defined button sets when possible is that the buttons will automatically follow local system conventions and be localized to the player's language settings, on platforms with appropriate operating system support. To select a pre-defined button set, use one of the following values, defined in adv.t, for the response_list parameter:
|
The "default_idx" parameter gives the index in the response list of the default response. If the user presses the "Return" key, or performs any other action appropriate to the system user interface that by local convention accepts the default response to a dialog, this response will be used. The first list entry is at index 1. Pass nil in this parameter if there is no default response, in which case TADS will require the user to select a specific button. (Note that, on some systems, passing nil for this parameter will not make a noticeable difference; on Windows, for example, one of the buttons will always have keyboard focus, so pressing the Return key will always select one of the buttons.)
The "cancel_idx" parameter gives the index in the response list of the cancellation response. Most GUI systems have a standard way of cancelling a dialog; the Escape key has this effect on Windows, for example, as does the Command-Period key combination on the Macintosh. If the user performs the appropriate system-specific action to cancel the dialog, this response is used. The first list entry is at index 1. Pass nil in this parameter if there is no cancel response, in which case TADS will not allow the player to cancel the dialog.
The dialog returns the index of the response that the player selects: 1 for the first response in the response list, 2 for the second entry in the list, and so on. For the standard response lists (INDLG_YESNO and so on), the response are in the order described for the constant name: INDLG_YESNO has a "Yes" button at index 1 and a "No" button at index 2, for example.
Here's an example of using this function.
ret := inputdialog('What would you like to do next?', ['&Restore', 'Re&start', '&Quit'], nil, 3); switch(ret) { case 1: /* restore a game... */ break; case 2: /* restart the game */ restart(); break; case 3: /* quit */ quit(); break; }
On a graphical system, this would display a dialog with the message text "What would you like to do next?", and three buttons: one with the label "Restore", one with the label "Restart", and one with the label "Quit". If the user presses the "R" key, the "Restore" button would be selected; if the user presses "S", the "Restart" button would be selected; if the user presses "Q", or cancels the dialog (by pressing the Escape key on a Windows machine, for example), the "Quit" button would be selected.
On a text-only system, TADS would display this text on the terminal, on a new line (TADS would output a "\n" sequence to start a new line):
What would you like to do next? (R)estore/Re(s)tart/(Q)uit >
TADS would then wait for the player to enter a line of text (as with the input() built-in function). If the player enters one letter, TADS would check the letter against each response's short-cut, and return the one that matches. If the player enters more than one letter, TADS would check the string against the leading substring of each possible response; if the string matches one of the responses unambiguously, TADS would return that response. If the player enters something invalid or ambiguous, TADS would redisplay the prompt and await another response.
inputdialog() has certain limits. The prompt string can be no longer than 256 characters. There can be no more than ten responses, and the total length of the text in all of the responses must not exceed 256 characters. In addition, to ensure portability, you should choose a reasonably short label for each button; some systems use buttons of a fixed size, so a long label name might not fit in the available space on some systems. Whenever possible, use a single word for each button label.
A new function, inputline(), is defined in adv.t. This function is a simple cover for the built-in function input(), with the additional feature that inputline() switches to the "TADS-Input" font when the game is running in HTML mode. This means that the text that the player enters during an inputline() has the same appearance as the text entered on a normal command line.
Note that the input() function does not switch to the "TADS-Input" font itself, because if it did, you'd have no way to override this behavior. Rather than forcing input text to be displayed in the "TADS-Input" font, the input() function leaves it up to the game author to determine how input text should look. In most cases, you should use inputline() rather than calling input() directly, to ensure that the player's input has the normal command line appearance. In some cases, however, you might wish to use a specific appearance for input text, rather than using the default setting; in these cases, you should set your own font choice, then call the input() function directly, since it won't change the appearance from what you choose.
The restore() built-in function's return code has been changed. In the past, this function returned nil to indicate success, and true to indicate failure. The return value is now a number; different values are returned for different error conditions, which makes it possible to provide better information to the player about the specific problem that caused the operation to fail.
The new return values, defined in adv.t, are:
|
For compatibility, RESTORE_SUCCESS is defined as 0, and all of the other values are non-zero. In most cases, this should allow existing code (that assumes the nil/true return value) to continue working without changes, since if (restore(fname)) will continue to have the same effect with this change. Only code that explicitly compared the return value to nil or true will need to be changed.
The code in adv.t that calls restore() has been updated to reflect this change, and uses the additional information to display a more precise message when an error occurs.
The askfile() built-in function's interface has been extended.
A new, optional fourth argument lets you specify additional flags to the askfile function. The possible flag values, defined in adv.t, are:
|
In order to specify the new flag value argument, you must specify the prompt type and file type arguments as well; if you omitted the prompt or file type argument, the askfile function would not be able to tell that you meant the last argument as the flags value.
If you omit the flags argument, askfile uses a default value of zero, which makes the function behave the same as in past versions. Because older code never specifies a flags value, the function will always behave compatibly with past versions when called from older code.
Traditionally, askfile returned a string on success, or nil for any type of failure; this didn't permit the caller to determine exactly what kind of failure occurred, and in particular did not allow the caller to distinguish between an actual error and the player cancelling the file selector dialog. When ASKFILE_EXT_RESULT is specified, the function will return additional information that allows the caller to distinguish these cases.
When the ASKFILE_EXT_RESULT flag is specified, askfile returns a list that contains two elements. The first element is a number which indicates the status of the file selection; the second element is a string if a file was successfully chosen, or nil if not. The possible values for the first element of the returned list, defined in adv.t, are:
|
All code in adv.t that calls askfile has been updated to use the new extended results information, so that it can provide more appropriate responses when the user cancels a file selector dialog.
Here's an example, from the "restore" command's implementation in adv.t, of using the new extended results.
local savefile; savefile := askfile('File to restore game from', ASKFILE_PROMPT_OPEN, FILE_TYPE_SAVE, ASKFILE_EXT_RESULT); switch(savefile[1]) { case ASKFILE_SUCCESS: return mainRestore(savefile[2]); case ASKFILE_CANCEL: "Canceled. "; return nil; case ASKFILE_FAILURE: default: "Failed. "; return nil; }
The standard library file adv.t provides a new function, switchPlayer, that you can use to switch the player character to a new actor. This function uses the built-in function parserSetMe() to change the parser's internal player character record, and in addition performs some book-keeping work that's necessary when switching to a new player.
Call switchPlayer() with one argument: the actor object that is to become the new player character.
First, switchPlayer() adds the outgoing player character object to its room's contents list, and removes the new player character object from its room's contents list. By convention in adv.t, the active player character is never in its location's contents list, but other actor objects are. Since the outgoing object is switching from being the active player object to an ordinary actor, it must be added to its room's contents list; likewise, since the new object is changing from an ordinary actor to the player character, it must be removed from its location's contents list.
Second, switchPlayer() removes the vocabulary words "me" and "myself" from the outgoing player character object, and adds these vocabulary words to the incoming player character. This way, these words always refer to the current player character.
If your game is based on adv.t, you should use switchPlayer() to change to a new player character object, rather than calling parserSetMe() directly, to ensure that the adv.t conventions for the active player character object are maintained through the change.
Thanks to Scott Starkey for his help defining this function.
The standard library file adv.t defines two new verbs: "inventory wide" and "inventory tall." These verbs let the player control the inventory display format. "Inventory wide" displays the player's inventory in the traditional paragraph-style format. "Inventory tall" displays the inventory in a single-column format, showing one object per line, and showing the contents of each object indented one tab stop from its container.
Once the player enters an "inventory tall" or "inventory wide" command, subsequent "inventory" commands (without a format specified) will default to the format of the previous command. The traditional "wide" format is the initial setting, but you can change this in your game by setting iVerb.useInventoryTall to true in your init function.
The standard library file adv.t has a new function, nestlistcont(), that displays a listing of an object's contents in a nested single-column format. This function can be used to display "tall" inventory listing, rather than the paragraph-style "wide" listings that adv.t normally displays.
nestlistcont() takes two arguments: the object whose contents are to be listed, and a number giving the initial indenting. The function indents the contents of the given object by the given number of tabs. For each object contained in the given object that has a contents list of its own, the function displays that object's contents indented one additional tab level, and so on for their contents.
nestlistcont() displays an object's contents only if the object's contents are visible, using the normal visibility rules. If the object's contents are not visible, this function has no effect. Furthermore, the function displays the contents of objects it displays only for those objects whose contents are themselves visible.
Thanks to Kevin Forchione for providing this addition to adv.t.
To support the nestlistcont() function, adv.t includes another new listing function, listcontgen(). This function is a general-purpose lister that provides the functionality of the traditional listcont() function as well as the new nestlistcont() function, which are essentially the same except for the display format.
listcontgen() takes three parameters:
listcontgen(obj, flags, depth);
The "obj" parameter is the object whose contents are to be listed, or simply a list of objects to display. "flags" specifies a set of flag values, defined in adv.t:
|
Note that listcontgen() allows you to produce an object listing for lists other than contents of objects. If you pass a list in the "obj" parameter, the function lists the items in the list using the same formatting that it would for a contents list. This allows you to display a contents-style listing for some collection of objects other than a contents list.
In adv.t, the basicMe.travelTo method now checks to see if "self" is actually the active player character object; if not, the method inherits the travelTo processing for a regular actor, rather than using the special behavior for the current player character. This change facilitates using basicMe as a base class for defining player character objects in a game with more than one player character.
In adv.t, the movableActor.travelTo method uses an improved algorithm for determining when to show the "leaving" and "arriving" messages for the actor. The new algorithm considers the actor's visibility to the player, before and after the move, in determining whether to show the messages; the old algorithm considered only the immediate container of the actor and of the player, which produced the wrong results when the actor was moving between locations that are both visible to the player, such as a nested room. In addition, the new algorithm handles obstacles (such as doors) properly.
Thanks to Kevin Forchione for providing this improved implementation.
A new compiler option, -ds2, tells the compiler to generate a new style of debugging information designed to work with the Windows HTML TADS Debugger (part of TADS Workbench). If you're running your game with the Windows debugger version 2.5.0 or later, you must compile with the -ds2 option. If you're using an older version of the Windows debugger, or you're using the debugger on another platform (DOS, Unix, Mac), you must continue to use the original -ds option as before.
The following player command parser bugs have been fixed:
The following debugger problems have been corrected:
This version corrects an obscure compiler bug that caused
preinit to execute incorrectly under certain circumstances.
This bug occurred when preinit called a method in an object,
and the method modified a list-valued property of the object, and the
property was defined earlier in the object than the method (i.e., the
property's definition in the source file preceded that of the
method). In such cases, the compiler's behavior was unpredictable;
sometimes it ran correctly, sometimes it stored incorrect values in
the list, and sometimes the compiler crashed. This problem should no
longer occur.