This file is part of the TADS Author’s Manual.
Copyright © 1987, 1996 by Michael J. Roberts. All rights reserved.

The manual was converted to HTML by N. K. Guy, tela design.


TADS:

The Text Adventure Development System
Version 2.2 Release Notes

Part I

Welcome to TADS 2.2!


This booklet is a supplement to the TADS Author's Manual, version 2.0, that describes all of the new features that have been added since version 2.0.

This booklet has two parts. Part I describes the additions and changes to TADS since version 2.0. If you're already familiar with version 2.1, you've already seen some of this material before; we've marked the sections that were also described in the version 2.1 release notes to make it easy to see at a glance what's new.

Part II of this booklet is a full new chapter describing the TADS player command parser. Because so many of the new features since version 2.0 affect the parser, it would have been difficult to assemble a clear picture of how the parser works by reading unconnected descriptions of the numerous new features. Part II therefore is a complete description of the entire TADS parser, including all of the modern features, along with everything that was present in version 2.0.


"Replace" and "Modify" (2.1)

Most game authors find that, when writing a substantial game, they can't avoid modifying objects in adv.t. While there's nothing intrinsically wrong with this, it creates a problem when a new version of TADS is released: you must either continue to use your old version of adv.t, or take the time to reconcile your changes with any changes we made in the new version. If you continue to use the old version, you won't be able to take advantage of any improvements or corrections we've made to adv.t.

In version 2.1, we added a mechanism that lets you avoid this problem: the replace and modify keywords. These new keywords let you make changes to objects that have been previously defined in the game program. In other words, you can #include the standard definitions in adv.t, and then make changes to objects that the compiler has already finished compiling. Using these new keywords, you can make three types of changes to previously-defined objects: you can replace a function entirely, you can replace an object entirely, or you can add to or change methods already defined in an object.

To replace a function that's already been defined, you simply use the keyword replace before the new function definition. Following the keyword replace is an otherwise normal function definition.

       #include <adv.t>

       replace scoreStatus: function( points, turns )
       {
          setscore( cvtstr( pts ) + ' points/' + cvtstr( turns ) + ' moves' );
       }

You can do exactly the same thing with objects. For example, you can entirely replace the fastenVerb defined in adv.t:

       #include <adv.t>

       /* we don't want "buckle", so replace adv.t's fastenVerb */
       replace fastenVerb: deepverb
          verb = 'fasten'
	  sdesc = "fasten"
	  prepDefault = toPrep
	  ioAction( toPrep ) = 'FastenTo'
       ;

Replacing an object entirely deletes the previous definition, including all inheritance information and vocabulary. The only properties of a replaced object are those defined in the replacement; the original definition is entirely discarded.

You can also modify an object, retaining its original definition, including inheritance information, vocabulary, and properties. This allows you to add new properties and vocabulary. You can also override properties, simply by redefining them in the new definition.

The most common addition to an object from adv.t will probably be new verb associations and added vocabulary:

       modify pushVerb
          verb = 'nudge'
          ioAction( withPrep ) = 'PushWith'
       ;

Note several things about this example. First, no superclass information can be specified in a modify statement; this is because the superclass list for the modified object is the same as for the original object. Second, note that vocabulary has been added. The new vocabulary doesn't replace the original vocabulary, but simply adds to the original vocabulary. Further note that the verb association pseudo-properties, doAction and ioAction, are legal in a modify statement. Any new doAction or ioAction definitions are added to the original set of definitions.

In a method that you redefine with modify, you can use pass and inherited to refer to the replaced method in the original definition of the object. In essence, using modify renames the original object, and then creates a new object under the original name; the new object is created as a subclass of the original (now unnamed) object. (There is no way to refer to the original, unmodified object directly; you can only refer to it indirectly through the modified object.) Here's an example of using pass with modify:

        class testClass: object
	    sdesc = "testClass"
	;

        testObj: testClass
            sdesc =
	    {
	        "testObj...";
		pass sdesc;
	    }
        ;

        modify testObj
            sdesc =
	    {
	        "modified testObj...";
		pass sdesc;
	    }
	;

Evaluating testObj.sdesc results in this display:

  modified testObj...testObj...testClass

You can also replace a property entirely, erasing all traces of the original definition of the property. The original definition is entirely forgotten - using pass or inherited will refer to the method inherited by the original object. To do this, use the replace keyword with the property itself:

        modify testObj
            replace sdesc =
	    {
	        "modified testObj...";
		pass sdesc;
	    }
	;

This would result in a different display for testObj.sdesc:

  modified testObj...testClass

The replace keyword before the property definition tells the compiler to completely delete the previous definitions of the property. This allows you to completely replace the property, and not merely override it, meaning that pass and inherited will refer to the property actually inherited from the superclass, and not the original definition of the property.


Dynamic Object Creation

TADS now allows you to create and delete objects dynamically at run-time. This is done through two new operators: new and delete. To create a new object, use this syntax:

  x := new bookItem;

This dynamically creates a new object whose superclass is bookItem. When this statement is executed, the runtime creates a new object, assigns its superclass to be bookItem, and executes the construct method in the new object; this method can perform any creation-time setup that you wish to do. The default construct method in the class thing in adv.t simply moves the new object into its location - this is necessary so that the contents list of the location is updated to include the new object.

An expression involving the operator new applied to a class can be used wherever an object expression can be used. When the expression is evaluated, a new object of the given class is created.

Note that you can create a new object that has no superclass by using the keyword object:

  x := new object;

If you're familiar with C++ constructors, you should note an important difference between the construct method and C++ constructors. A C++ constructor in a derived class will automatically call the construct in the base class (except for a virtual base class). The TADS construct method does not automatically call superclass construct methods; instead, construct works exactly the same as any other TADS method. You can, of course, call the superclass construct method explicitly using the inherited mechanism, just as with any other method. The same is true of the destruct method (described below).

A new object inherits all of the vocabulary of its superclass.

To destroy an object create with new, use this syntax:

  delete x;

This first calls the destruct method of the object to notify it that it is about to be deleted, then destroys the object. Further references to the object are illegal, since its memory has been released (and thus may be given to an other object). The default destruct method in the class thing in adv.t moves the object into nil, which removes it from its container's contents list - this is necessary so that the reference to the object in that list is removed.

Only objects created with new can be destroyed with delete. Objects that are defined statically in your game's source file cannot be deleted at run-time.

Object creation and deletion works correctly with the UNDO mechanism. If the player uses UNDO after a move that created an object, the object will be destroyed; likewise, if a player uses UNDO after a turn that deletes an object, the object will be re-created with the same property values it had prior to deletion.

Similarly, dynamically-created objects are preserved across SAVE and RESTORE operations.

Note that TADS does not perform any garbage collection on dynamically-created objects. The system is not capable of determining whether an object is accessible or not. Hence, if you lose track of any objects you create with new, they will remain part of the game forever - they will even be saved along with saved games and restored when the games are restored. You must be careful to keep track of all objects you create to avoid filling all available memory (and the swap file) with unreachable objects. Even though TADS will eventually swap inaccessible objects out of memory, the objects will consume object identifiers, which are a limited resource: only 65535 objects can be created within a game, including both static and dynamically-created objects.


Dynamic Vocabulary

New built-in functions let you dynamically add to and delete from the vocabulary words of an object. You can also get the vocabulary words of an object at run-time.

To add to an object's vocabulary, use the new addword() built-in function. This function takes three arguments: an object, a vocabulary property pointer, and a word to add. For example, to add "red" as an adjective to the object myBook, you would do this:

  addword( myBook, &adjective, 'red' );

To delete the same word, you would write a similar call to the new built-in function delword():

  delword( myBook, &adjective, 'red' );

You can add to and delete from the words of any object, including both static objects (explicitly defined in your source code) and dynamically-created objects (created with the operator new).

Changes made by addword() and delword() are tracked correctly by the UNDO mechanism, and are saved and restored along with saved games.

To get the words belonging to an object, use the new getwords() built-in function. This function takes two arguments: an object, and a property pointer; it returns a list of (single-quoted) strings, which are the vocabulary words for the object. For example, assume we define myBook as follows:

myBook:  item
   sdesc = "small red book"
   adjective = 'small' 'red' 'tiny'
   noun = 'book'
   location = room2
;

Also assume we haven't made any calls to addword() or delword() for myBook. In this case,

  getwords( myBook, &adjective )

would return this list:

  [ 'small' 'red' 'tiny' ]

Note that the order of the words in the list is not predictable, so you shouldn't expect the words to be in the same order as they were when you defined them in the source file, or in the same order as they were added with addword().


Getting Verb Information

Another new built-in function lets you retrieve information on a verb. This new function, verbinfo(), takes as parameters a deepverb object and an optional preposition object, and returns a list that gives the parser information for this verb.

When you call verbinfo() with a single argument (a deepverb object), the return value is a list with two elements, giving information on the doAction definition for the deepverb object:

1] direct object verification property pointer (verDoVerb)

2] direct object action property pointer (doVerb)

When you call verbinfo() with two arguments (the first is a deepverb object, and the second is a preposition object), the return value is a list with four elements, giving information on the ioAction definition for the deepverb object that matches the given preposition object:

1] direct object verification property pointer (verDoVerb)

2] indirect object verification property pointer (verIoVerb)

3] indirect object action property pointer (ioVerb)

4] true if this ioAction has [ disambigDobjFirst ] flag, nil otherwise

In either case, if the deepverb doesn't have a corresponding doAction or ioAction method, the function returns nil.

As an example, suppose we define a verb that looks like this:

  pryVerb:  deepverb
    verb = 'pry'
    sdesc = "pry"
    doAction = 'Pry'
    ioAction( withPrep ) = 'PryWith'
  ;

With this definition, verbinfo( pryVerb ) would return this list, which provides information on the doAction definition:

  [ &verDoPry, &doPry ]

verbinfo( pryVerb, withPrep ) would return this list:

  [ &verDoPry, &verIoPry, &ioPry, nil ]

If the ioAction definition had included the [ disambigDobjFirst ] flag, the fourth element of the list would have been true.

Calling verbinfo() for pryVerb and any preposition other than withPrep will return nil, because the verb doesn't define an ioAction for any other preposition. Similarly, if the verb didn't have a doAction method, verbinfo(pryVerb) would have returned nil.


Indistinguishable Objects

The ability to create new objects at run-time leads to some interesting problems involving indistinguishable objects. Although you could use addword() to make your newly-created objects distinguishable from one another, this will not always be desirable; for example, if you create new gold pieces that serve as currency, you will probably not want them to be uniquely named.

To support indistinguishable objects, especially those created dynamically at run-time, the system now has a property that you can set to indicate to the parser that an object does not need to be distinguished from others of the same class. The new property is isEquivalent. When isEquivalent returns true for an object, all other objects with the same immediate superclass are considered interchangeable by the parser. When a player uses one of these objects in a command, the parser will simply pick one arbitrarily and use it, without asking the player which one.

If a player uses a noun that is ambiguous with multiple equivalent items and one or more other items, the parser will need to disambiguate the objects as usual. In such cases, the parser's question will list the distinguishable items only once. For example, assume we have five gold coins that are all equivalent (in other words, they all have isEquivalent set to true, and they all are immediate subclasses of the same class). Assume further that a silver coin and a bronze coin are also present in the room.

  Treasure Room

  You see a bronze coin, five gold coins, and a silver coin here.

  >get coin

  Which coin do you mean, the bronze coin, a gold coin, or the silver coin?

Note that the objects which appear only once are listed with "the" (using the property thedesc), while the indistinguishable objects are listed only once, with "a" (using adesc).

The functions listcont and itemcnt in adv.t have been modified so that they list equivalent objects intelligently; the functions isIndistinguishable and sayPrefixCount have been added to adv.t to help with counting and listing indistinguishable objects correctly. The contents of the Treasure Room are listed in the example above in the new format. Refer to these functions in adv.t for examples of recognizing objects as equivalent, counting equivalent objects, and treating a set of equivalent objects together as a set.

Note that listcont uses the new property pluraldesc to display the name of an object when more than one equivalent object is present. In the example above, pluraldesc was used to list the gold coins. This property has been added to the class thing in adv.t, but you may need to override it for some objects - the default implementation simply displays the sdesc plus the letter "s."


Capturing Displayed Text

TADS now has a feature that lets you capture the text displays that are generated by double-quoted strings and by the say() built-in function. This feature allows you to examine the values of methods such as ldesc and sdesc; since these methods display text, there is no direct way of manipulating their text as strings. The new output capture feature makes it possible for you to examine and manipulate any text that ordinarily would simply be displayed.

To use this new feature, you first tell TADS that you wish to begin capturing output by calling a new built-in function:

  stat := outcapture( true );

This begins capturing output. The return value is a status code that you use in a subsequent call to turn off output capturing; this status code allows output capturing calls to be "nested," since the status code allows the capturing status that was in effect on a call to begin capturing to be restored on the subsequent call to end capturing. This status code is for use by TADS only - the only thing you do with it is use it in the subsequent call to end capturing.

While output is being captured, any text that would normally be displayed is instead saved by the system. The user does not see any text displayed while capturing is in effect. After you begin capturing, simply call any methods whose displays you want to examine; for example, you could call redbook.sdesc if you want to examine the short description of the object redbook.

After you've finished calling the methods whose displays you want to examine, end capturing by calling outcapture() again, passing as the argument the status code returned by the first call:

  str := outcapture( stat );

This second call tells TADS to stop capturing output. The return value of this function is a (single-quoted) string containing all of the text that was displayed since the corresponding call to outcapture(true).

When text is being captured, TADS expands all format strings (strings such as "%you%"), and processes "\^" and "\v" sequences (which convert the next character to upper- and lower-case, respectively). However, all other escape sequences (such as "\n" and " \t") are left in the string intact.

You can display the string returned from the call to outcapture(stat) using the say() built-in function. This should result in the same text display that would have occurred if you hadn't turned on capturing.

You can nest calls to outcapture(); as explained above, the status code returned from the first call is used to restore the capturing status of the previous call. The string returned by a call to turn off capturing includes only text that was generated after the corresponding call to turn on capturing. Because calls to outcapture() can be nested, you don't have to worry when using text capturing about whether any methods you're calling also use the function.

Note that the system automatically turns off output capturing whenever it prompts the user for a command, for a missing direct or indirect object, or for disambiguation information. When the system turns off capturing, it clears the captured text, so any subsequent calls to turn off capturing return empty strings. You therefore can only capture text within a single command line.


Parser Changes

Many of the changes since version 2.0 have been made to the TADS player command parser. Since Part II of these release notes provides full documentation of the modern features of the parser, including new features as well as features that were present in version 2.0, we won't go into too much detail in this section. Instead, we'll summarize the changes that have been made, and refer you to the appropriate section in Part II.

If you're not already familiar with the TADS parser, you shouldn't need to read this section at all, because everything described below is documented in Part II. This section is provided for users of past versions, so that you can see at a glance what's new.


validDoList and validIoList (2.1)

In version 2.1, we added a new mechanism for determining whether an object is accessible for a command. The new mechanism supplements the validDo and validIo methods, which test a single object to determine if it's accessible for a verb. The added methods are validDoList and validIoList; these methods return a list of valid direct objects and indirect objects (respectively) for a particular command. As with validDo and validIo, these new methods are defined in the deepverb object for a verb.

The new validDoList and validIoList methods are fully described the section on object resolution (page 94).

Note that this new mechanism, if you use it, requires that you use the new floatingItem class for any object that uses a method for its location property. The reason is that objects with non-object location values are not part of any contents list, so they will not be included in any validDoList or validIoList return value. You should make sure that any object in your game that has a method for its location property includes floatingItem in its superclass list.


floatingItem (2.1)

The new class floatingItem should be used for any object that uses a method for its location property, because of the new validDoList and validIoList validation mechanism, as described above. If you define an object with a method for its location property, the object must have floatingItem among its superclasses in order for it to be accepted as a valid object for the verbs defined in adv.t.

dobjGen and iobjGen (2.1)

In version 2.1, we enhanced the parsing process so that the TADS parser calls a general-purpose command handler in each object in a command prior to the normal verification and action method calls. These new general-purpose methods are intended to be "catch-all" handlers that let you treat all verbs, or any set of verbs, alike - since the same methods will be called in the objects regardless of the verb, you can give the same behavior to multiple verbs without having to explicitly code each verb handler individually. These new methods, dobjGen and iobjGen, are described fully in part II in the section "General Verb Processing" (page 102).

Command Prompt Customization (2.1)

Whenever the parser displays a command prompt, it now calls the commandPrompt function. This is a function that your game program can define. See the section on reading a command (page 72) for details.

New Message Customization Functions (2.1)

Several new functions have been added that let you customize messages that the parser generates. These functions are used to generate complex messages that are normally pieced together from multiple parseError messages. Since the parser needs to build these messages from a series of pieces, parseError lets you change the individual pieces, but not the overall method of construction - for example, it doesn't let you change the order of the phrases generated. These new functions address this problem.

The new functions are parseError2, which lets you control the message displayed when an object doesn't understand a verb (because it doesn't define or inherit a verification method for the verb); parseDefault, which lets you change the message generated when the parser is assuming a default object; parseDisambig, which lets you control the message asking the player which of several possible objects should be used for a command; and parseAskobj, which lets you change the message when the parser asks the player to supply a missing direct or indirect object. Note that parseAskobj should no longer be used, because the more useful parseAskobjActor function has been added; games written with parseAskobj will still work correctly, but new games should use the modern function. These functions are all described in detail in Part II.


parseAskobjActor Message Function

We added another new message customization function in version 2.2. This new function, parseAskobjActor, is similar to parseAskobj, but receives the actor as a parameter. This allows you to generate a better message when the actor isn't Me. See the section on verb templates (page 85) for details.

"Any" and "Either"

The parser now accepts noun phrases that begin with "any" or "either" (or different words that you define with an added slot in the specialWords statement). The player can use "any" plus a singular noun phrase, or "any of" plus a plural noun phrase ("either" is equivalent); this instructs the parser to choose one object arbitrarily when the noun phrase refers to more than one object. See the section on noun phrases (page 80) for details.

Verb Action Redirection

The compiler accepts new syntax that lets you redirect, with a single, concise declaration, the verification and action methods for a particular verb from one object to another object. For example, if you have a desk that contains a drawer, and you want the verbs "open" and "close" to be directed to the drawer when the player applies them to the desk, the new syntax lets you specify this indirection with these two lines in the definition of the desk object:

  doOpen -> deskDrawer

  doClose -> deskDrawer

This new syntax is described in Part II in the section on verb synonyms and redirection (page 108).


multisdesc

The parser calls a new property, multisdesc, when listing the current direct object while executing a command with multiple objects. For example:

  put all in box

  red ball:  Done.

  rubber raft:  The raft is too large.

The object name listed before the response for each object is now displayed with multisdesc (in previous versions, the parser used sdesc for this purpose; for compatibility with existing games, the parser will still call sdesc for any objects that don't define or inherit multisdesc). This is the only place where multisdesc is used, so if you want to customize this type of display, you can make changes to multisdesc without any effect on the general sdesc.


preparseCmd

The parser calls a new function, preparseCmd, before executing each individual command on a command line. This new command is similar to preparse, but is more powerful for certain situations, because it is called with a list of words (the result of scanning the command line and breaking it into individual words), and is called with only the words making up a single command, rather than the entire command line. Of course, preparse still works as before.

parseError improvements

Several new parseError codes have been added, and the default message generation process has been improved for certain cases. The main changes are to the messages that the parser generates when no parseAskobjActor or parseAskobj functions are present. The full set of parseError codes is listed on page 114, and the parseAskobjActor function, and the new default messages generated when it's not present, are described in the section on verb templates in (page 85).

Multiple Actors are allowed on a Command Line

The parser will now allow you to specify the actor to which a command is to be directed for each individual command on a command line. Previously, the parser only allowed the actor to be specified at the very beginning of the whole line, so any one command line could include commands only to a single actor. This restriction has been removed; each individual command can specify its own actor. Any command that doesn't specify an actor is directed to the same actor as the previous command on the same line. The parser now accepts sentences like this:

  go north.  Joe, get the box.  give it to Biff.  Biff, open the box.

The command "go north" is directed to the player actor (Me). "Get the box" and "give it to Biff" are directed to Joe, and "open the box" is directed to Biff.

Note that you can still direct each command to only a single actor.


More Notifiers, Daemons, and Fuses

The number of notifiers (set with the notify() built-in function) that can be simultaneously active has been increased to 200 (the previous limit was 100). The limit on the number of simultaneously-active fuses has been lifted from 50 to 100, and the limit on the number of simultaneously-active daemons has likewise been lifted from 50 to 100.

More Ambiguous Words

The number of objects to which a single word can refer has been lifted from 100 to 200. If you've received an error message such as "The word 'door' refers to too many objects," you've encountered this limit. As the TADS parser scans a noun phrase, it must build a list of the objects to which a particular object refers; these lists are limited in size. For this reason, you should avoid defining a word in your basic classes (such as thing). Although this limit has not been removed entirely, we have raised it to make it less likely that you'll encounter it.

disambigDobjFirst

You can now specify that the processing for an ioAction definition should resolve (disambiguate) the direct object first. Normally, TADS will disambiguate the indirect object first, and then resolve the direct objects with a known indirect object. For some verbs, it's desirable to be able to resolve the direct object first, so that you can decide which indirect object to use based on the known direct object. The new ioAction flag, disambigDobjFirst, allows you to tell TADS to reverse its normal object resolution order. The section on verb templates (page 85) describes this new flag.

Actor Disambiguation

Two new methods have been added that give you more control over the process of disambiguating actors: validActor and preferredActor. In previous versions of TADS, the parser disambiguated an actor as though the player were trying to "take" the actor - in other words, the parser used the takeVerb disambiguation methods to determine if an object was valid as an actor. TADS now gives you more control over this process by calling these new methods, which are used only for actor disambiguation. The section on checking for an actor (page 76) discusses these new methods.

For compatibility with games written for versions prior to 2.2, if your objects don't define or inherit a validActor method, the parser will continue to use the old mechanism.


Improved Plural Handling

The parser's handling of plurals has been improved. When you define a word as a plural, and the same word is used by another object as a noun, the parser failed to recognize the object with the noun in past versions. The parser will now try the plural first; if no objects are found matching the plural usage, the parser will try the noun usage instead.

"Of" can be used as a Preposition

The word "of" can now be used as a preposition, even if it's defined as a special word in the "of" slot. The parser no longer considers "of" to be a reserved word; the parser only checks for the special meaning in noun phrases (such as "piece of paper") when the context allows it. You can now create commands such as "accuse dobj of iobj."

Adjective-Preposition Overlaps

When we introduced the new sentence format VERB PREP IOBJ DOBJ (introduced in version 2.1.1) created a problem when the same word was defined as both an adjective and a preposition. The problem was that the parser always attempted to interpret a phrase such as "push off button," where "off" is defined as a preposition but also as an adjective for the button, with "off" as a preposition.

The parser has been changed so that the adjective affiliation is stronger. If an word that can be either a preposition or an adjective is used as part of a noun phrase that refers to at least one object, the parser will interpret the word as an adjective. Only when the word doesn't form a noun phrase with subsequent words will it be interpreted as a preposition.


rejectMultiDobj

The parser now lets you prevent the player from using multiple words for a particular command; you can control whether multiple objects are acceptable separately for each verb. The parser calls the new method rejectMultiDobj whenever the player uses a list of objects or "all" with a command; if this method returns true, the parser ignores the command. The section on multiple objects (page 99) discusses this new method.

Improved cantReach Handling

The parser gives you greater control over the error message generated when a set of objects are visible but not accessible for a command. If you define a cantReach method in a deepverb object, the parser will use that method rather than calling the cantReach method for each inaccessible object. The section on object resolution (page 94) describes this new method.

Using the Same Verb for Two Objects

The parser now generates a warning if you use the same vocabulary word as a verb property for multiple objects. The new message is TADS-452, "warning: same verb defined for two objects." If you define the same vocabulary word as a verb multiple times, the parser will arbitrarily use one of the deepverb definitions, and ignore the others; if you weren't aware you were re-using a verb, it could be difficult to track down why your verb handlers were never being called. The new warning flags this situation for you.

Multiple Numbers and Strings

The parser now properly sets the strObj or numObj value property for each individual number of string in a command if more than one appears in a single command. For example:

type 1, 2, 3 on keypad

The value property of the numObj object will be set to 1 when processing the first number, 2 for the second, and 3 for the third, as you'd expect. In past versions of TADS, the numObj and strObj value properties were set only for the first number or string in a command.

"One" and "Ones" are no longer Reserved

The special words "one" and "ones" (or the words you define in these specialWords slots) are no longer reserved, so you can use them as ordinary vocabulary words. The parser now treats the words as having their special meanings only in the contexts where they're needed (in particular, they'll have the special meaning in responses to disambiguation questions, so that the player can respond to a disambiguation question with a phrase such as "the red one").

"abort" within a Daemon or Fuse

You can now use the abort statement within a daemon or fuse, and it will work as expected, by terminating the current routine and skipping any remaining fuses or daemons on this turn. In past versions, abort was not allowed in a fuse or daemon.

New sentence format: VERB PREP IOBJ DOB

The parser now accepts sentences in the format VERB PREP IOBJ DOBJ. This construction is intended mostly for use in games written in non-English languages that use this sentence format, since it's not a common format for English sentences. Refer to the section on verb templates (page 85) for details of how this sentence format is treated.

nilPrep

In past versions of TADS, when the player entered a sentence in the format VERB IOBJ DOBJ, the parser used "to" as the preposition; there was no way to change this. You can now specify the preposition to use for sentences in this format by specifying the preposition object as the value of the property nilPrep in the deepverb object for the verb. This new property is discussed in the section on verb templates (page 85).

C-Style Operators

TADS users who are also C programmers often find the substantial similarity between TADS and C to be convenient, but also find the slight differences to be a source of confusion when switching between the two languages. TADS now offers the option to use C- style operators.

First, we've added the remaining C operators to the TADS language:

a % b : Returns the remainder of dividing a by b

a %= b : Assigns (a % b) to a

a != b : Equivalent to (a <> b)

!a : Equivalent to (not a)

a & b : Bitwise AND

a &= b : sets a to (a ∓ b) (the bitwise AND of a and b)

a | b : bitwise OR

a |= b : sets a to (a | b) (the bitwise OR of a and b)

a && b : equivalent to (a and b)

a || b : equivalent to (a or b)

a ^ b : bitwise XOR of a and b

a ^= b : sets a to (a ^ b) (the bitwise XOR of a and b)

~a : bitwise negation of a

a << b : a shifted left by b bits

a <<= b : sets a to (a b) (shifts a left by b bits)

a >> b : a shifted right by b bits

a >>= b : sets a to (a b) (shifts a right by b bits)

Some of these operators, such as !, &&, and ||, are merely synonyms for existing operators. The "bitwise" operators act on numeric values rather than logical values; they treat their operands as bit vectors, and apply the operation to each bit of the numbers. For example, 3 & 2 has the value 2, since the bit patterns are "011" and "010," respectively. The bit-shift operations are equivalent to multiplying or dividing by a power of 2: 1 << 5 has the value 32, since it's equivalent to multiplying 1 by 2 raised to the 5th power.

In addition, TADS now has a mode which uses the C-style assignment operator. Normally, the TADS assignment operator is :=, and the equality operator is =. In C, these operators are = and == respectively. You can now tell TADS to use the C-style operators instead of the TADS version. By default, TADS still uses its own version of the operators. There are two ways to switch into C-style operator mode: by using a command-line option, or by using a new #pragma compiler directive in your source code.

To compile an entire game in C mode, use the -C+ command line option (Macintosh users will find a menu item for C-style operators under the "Options" menu; check this item to enable C operators, and uncheck it to use standard TADS operators). Using the -C+ compiler option enables C operator mode for the entire game's source code. (The -C- option explicitly turns off C operator mode. This is the default mode.)

To specify that a particular file is to be compiled in C mode, you can use the new directive #pragma C+. The similar directive #pragma C- specifies that TADS operator mode is to be used. These directives can appear anywhere in a source file (outside of comments and strings); they must be alone on the line, and must not be preceded by any whitespace on the line.

A #pragma setting affects only the current source file, and any files it includes. The header files included with TADS (adv.t and std.t) both use TADS operators, so they explicitly specify #pragma C-; because these directives are limited to the header files, you can freely include adv.t from a file that uses C operator mode without having to worry about setting the mode to or from TADS mode. Simply #include adv.t exactly as you did before - even if your source file uses C mode, adv.t will compile correctly, because it sets the operator mode back to TADS for its own contents, and TADS automatically restores the enclosing file's mode at the end of adv.t.

Note that the C-style operator mode setting affects only the assignment and equality operators. You can use all of the new C operators (such as the new bitwise operators) in either mode - all of these symbols were invalid in previous versions of TADS, so there's no danger that they'll be misinterpreted for old games.

When the compiler is using C-style assignment operators, it issues a new warning, "possibly incorrect assignment," whenever it finds a statement in this format:

  if ( a = 1 ) ...

While this statement is legal, with C-style operators it has the effect of assigning the value 1 to a; since the value of an assignment is the value assigned, this if will always succeed. It's a common error for C programmers (even highly experienced ones) to write this type of statement when they really want to compare the values; we originally chose to use ":=" as the assignment operator in TADS to reduce the likelihood of this type of error. Now that TADS can be switched to C syntax for assignments and comparisons, we've added the "possibly incorrect assignment" warning to help catch these; the compiler will flag as signments made in if, while, and do statements, and in the condition clause of for statements. To suppress this warning, you can explicitly test the value of the assignment like this:

  if ( ( a = 1 ) != 0 ) ...

There are a couple of minor complications with the new operators.

First, the >> operator can't be used in an expression embedded in a string with the << >> construct, because it would be taken for the >> that terminates the embedded expression. Even adding parentheses won't help, because the compiler recognizes the << >> construct before it looks at any expression in progress. So, this type of code won't work:

  myprop = "x divided by 128 is << (x >> 7) >>! "     // wrong

You would have to code this instead as:

  myprop = { "x divided by 128 is "; x >> 7; "! "; }  // right

Second, the & operator now has a binary interpretation in addition to its unary interpretation. For the most part, this won't create any confusion, but there's one situation in which it might: in lists. You might have lists in your games that look like this:

  mylist = [ &prop1 &prop2 &prop3 ]

In past versions, since the & operator could only be a unary operator, this construct was unambiguous; however, now that & can be a binary operator, this could be interpreted either as three expressions involving unary & operators, or as a single expression involving one unary & operator and two binary & operators.

For compatibility with past versions, TADS will interpret the & operators as unary operators. When it finds this construct, though, it will warn you that it is ambiguous. (The new warning is TADS-357, "operator '&' interpreted as unary in list.) You can suppress this warning in one of two ways. First, you can render the list unambiguous. To do this, use a comma between each pair of list elements:

  mylist = [ &prop1, &prop2, &prop3 ]

Note that if you actually want the binary interpretation, you should simply enclose the expression in parentheses:

  mylist = [ ( 2 & 3 & 4 ) ]

The other way you can suppress this message is with the new -v-abin compiler option, which tells the compiler not to generate the warning. The compiler still interprets the operator the same way when you specify -v-abin - it just doesn't tell you about it.

Note that TADS will treat any operator which has both a unary and binary interpretation as a unary operator when it finds it within a list, and will generate the TADS-357 warning. For the - operator, this is a change from past versions, which used the binary interpretation when in a list. We don't anticipate that this will be a compatibility problem, because the old binary interpretation was almost never desirable, and we think users avoided it; however, if you have an older game, you may wish to compile without the -v-abin option at least once, and check any lines where the TADS-357 warning is generated for - or + operators, to determine if your game's behavior will change with the new version. Any TADS-357 warnings generated for the & operator can be safely ignored for a game written with a previous version of TADS.


New Preprocessor Directives

TADS now lets you define preprocessor symbols. The #define directive creates a preprocessor symbol definition:

  #define TEST  "This is a test! "

This defines the symbol TEST, and specifies that this symbol is to be replaced by the text "This is a test!" whenever it appears (outside of strings or comments) in your source code. Note that the definition of a symbol need not be a string - the entire text of the rest of the line is assigned to the symbol.

You can use a preprocessor symbol that you've defined simply by putting the symbol in your code:

  sdesc =
  {
    TEST;
  }

When the compiler encounters the preprocessor symbol, it replaces the symbol with its definition, so the compiler treats this the same as:

  sdesc =
  {
    "This is a test! ";
  }

You can delete a preprocessor symbol that you've defined using the #undef directive:

  #undef TEST

The compiler automatically defines a number of preprocessor symbol:

__TADS_VERSION_MAJOR is defined as the major version number of the current compiler. (Note that this symbol starts with two underscore characters, as do most of the symbols that the compiler defines for you.) Currently, this is defined as 2.

__TADS_VERSION_MINOR is the minor version number, currently 2.

__TADS_SYSTEM_NAME is defined as a single-quoted string giving the name of the current system. For DOS systems, this is 'MSDOS'; for Macintosh systems, this is 'Macintosh'. TADS also defines the value of this symbol as a symbol itself - on MSDOS systems, the compiler defines the symbol MSDOS, and on Macintosh systems, the compiler defines Macintosh. (The value of the system name symbol is simply 1; it's purpose is to allow for conditional compilation, so the value isn't important.)

__DEBUG is defined if the game is being compiled for debugging (with the -ds command line option). By testing this symbol with #ifdef, you can easily include parts of your game (such as special "magic" verbs that you use for debugging) only in testing versions of your game, without having to worry about removing them manually when producing the final version of the game for players. The value of this symbol is 1 if it's defined; the purpose of this symbol is to allow for conditional compilation, so its value isn't important.

__DATE__ is defined as a single-quoted string giving the date when the current compilation began, in the format 'Jan 01 1994'.

__TIME__ is defined as a single-quoted string giving the time when the current compilation began, in the 24-hour format '01:23:45'. The time isn't updated during the course of a compilation - it's always the time when compilation began.

__FILE__ is defined as a single-quoted string giving the name of the current source file being compiled.

__LINE__ is defined as a number giving the line number currently being compiled.

You can define symbols from the compiler command line. The new -D option lets you specify a symbol to define, and optionally a value. Specify the symbol, then an equals sign, then the value; if you omit the equals sign, the default value is 1. For example, to define the symbol DEMO from the command line, you could use this command:

  tc -DDEMO mygame

You can also specifically delete preprocessor symbols that the compiler defines (other than __FILE__ or __LINE__). You can also undefine any symbols defined earlier on the command line with -D options, which may be useful if you're using a configuration file that defines certain symbol. To undefine a symbol, use the -U option:

  tc -UDEMO mygame

If the symbol DEMO was defined earlier on the command line, the definition is deleted.

The Macintosh compiler has a new preprocessor options dialog that you can access from the "Options" menu. You can use this dialog to enter symbols to define; in the text box, simply list the symbols (one per line) that you wish to define. If you want to a ssign values to these symbols, use an equals sign, followed by the value. Another text box in the same dialog lets you list pre-defined symbols that you want to delete; simply list the symbols to be deleted, one per line.

You can test for the existence of a preprocessor symbol with #ifdef ("if defined") and you can test to see if a preprocessor symbol is undefined with the #ifndef ("if not defined") directive. These directives let you conditionally include code in your program, depending on whether or not a symbol is defined. For example:

  #ifdef TEST
  sdesc = { TEST; }
  #endif

The code between the #ifdef and the #endif is included only if the preprocessor symbol TEST is defined. There's also a #else directive, which lets you include a block of lines if the most recent condition failed.

Conditional compilation is particularly useful with the symbols you define from the command line with the -D option, since it allows you to write your game in such a way that certain features are enabled when you use a particular set of -D options. This allows you to use a single set of source files, but produce a variety of different .GAM files. For example, if you want to be able to generate a subset of your game as a demo, you could use conditional compilation to include or discard sections of the game depending on whether you're compiling the demo or the full game. Similarly, you could use conditional compilation to include certain features only when you're compiling a debugging version of your game; note that the compiler makes this even easier, because it defines the symbol __DEBUG when you're compiling with debugging symbols.

We've also added the #error directive. This directive lets you generate an error from within your game. Any text following the #error on the line is displayed to the user as the text of the error message. For example:

  #ifndef TEST
  #error TEST is not defined!
  #endif


New Output Escape Sequences

The output formatter now recognizes two new escape sequences.

The first is "\-" (a backslash followed by a hyphen), and was added in version 2.1. This sequence tells the output formatter to pass the following two bytes as-is, without any interpretation. This is useful for multi-byte character sets (such as on a Japanese-localized Macintosh), where a single character may be represented by two bytes. For the most part, you can freely mix single- and double-byte characters within text strings without any special work. However, some double-byte characters contain the ASCII code for a backslash as one of their two bytes; in these cases, the output formatter incorrectly interprets the byte whose code is "\" as introducing an escape sequence. By preceding such characters with "\-", you can prevent the parser from interpreting the byte with the backslash code as an escape sequence, so the character will be displayed correctly.

The second new escape sequence is "\v" (a backslash followed by the small letter "v"). This sequence is the opposite of "\^" (a backslash followed by a circumflex). Whereas "\^" converts the next character displayed to a capital letter, "\v" converts the next character to a small letter. Displaying "\v" is equivalent to calling the nocaps() built-in function.


File Operations

TADS now has support for reading and writing files from within your game program. This new feature is intended to let you save information independently of the game-saving mechanism, which allows you to transfer information between sessions of a game, or even between two different games.

Operations on files are all performed through a file handle. This is a special value, generated by TADS, that you use to refer to a file. TADS creates a file handle when you open a file; once you've opened a file and obtained its file handle, you can read and write the file. Once you're done with the file, you close the file, which deletes the file handle.

To open a file, use the fopen() function. This function takes two arguments: a (single-quoted) string giving the name of the file to open, using local file system conventions, and a "mode." The mode argument is one of these single -quoted string values:

'r' open file for reading; file must already exist

'r+' open file for reading and writing; the file is created if it doesn't already exist

'w' create a new file for writing; the file is deleted if it already exists

'w+' create a new file for reading and writing; the file is deleted if it already exists

For maximum portability, you should avoid using volume names, directories, folders, or other path information in filenames, because any such information might be specific to your computer.

The return value of fopen() is a file handle; you must save the file handle so you can use it later to perform operations on the file. If the operation fails, and the file cannot be opened as requested, fopen() returns nil. Opening a file could fail for a number of reasons; for example, if you attempt to open a non-existent file with mode 'r', fopen() will return nil, because this mode requires that the file already exists.

This example opens a new file called TEST.OUT for writing:

  fnum := fopen('test.out', 'w');

To close an open file, use fclose():

  fclose(fnum);

Once you've closed a file, the file handle is no longer valid; TADS may re-use the same file handle on a subsequent fopen() call. Note that the TADS runtime allows only a limited number of files (currently 10) to be open simultaneously, so you should close a file when you're done with it.

To write to a file, use fwrite(). This function takes a file handle, and a value to write; the value can be a string, a number, or true. The value can't be nil (this is because the fread() function returns nil to indicate failure; if you could write nil to a file, there would be no way to distinguish reading a valid nil from an error condition). fwrite() stores the value, along with information on its type.

The fwrite() function returns nil on success, true on failure. If the function returns true, it usually means that the disk is full.

  if ( fwrite( fnum, 'string value!' ) or fwrite( fnum, 123 ) )
   "Error writing file!";

If the file is open for reading, you can read from the file with the fread() function. This function takes a file handle, and it returns a value it reads from the file. The value returned is of the same type as the value originally written at this position in the file with fwrite(). If this function returns nil, it indicates that an error occurred; this usually means that no more information is in the file (which is the case once you've read to the end of the file).

  res := fread( fnum );
  say( res );

You can get the current byte position in the file with the ftell() function:

  "The current seek position is  ftell( fnum ) . ";

The ftell() function returns a number giving the byte position that will be read or written by the next file operation.

You can set the file position with fseek() and fseekeof(). The fseek() function moves the file position to a particular byte position, relative to the beginning of the file. For example, this seeks to the very beginning of a file:

  fseek( fnum, 0 );

The fseekeof() function positions the file at its end (EOF stands for "end of file"):

  fseekeof(fnum);

Note that you must be careful with fseek(). You should only seek to positions that you obtained with the ftell() function; other positions may be in the middle of a string or a number in the file, so seeking to an arbitrary location and writing could render the file unusable by partially overwriting existing data.

Note that the TADS file operations are not designed as general-purpose file system operations; in particular, these new functions don't have any provisions for creating or reading formatted files, or for exchanging information with programs other than TADS games. TADS reads and writes files in its own binary format, which is portable across platforms - you can take a binary file written on DOS, and read it on the Macintosh; however, the TADS format isn't designed to be interchanged with other programs, or even to produce simple text files that can be viewed or edited by a user.


New and Improved Built-In Functions


addword(obj, &prop, word)

Adds the word (a single-quoted string value) to the object as the given part of speech. The prop parameter can be noun, adjective, plural, verb, article, or preposition. You can add words to any object, including objects defined statically in your game, as well as objects created dynamically at run-time.

For examples of using this function, see the section on Dynamic Vocabulary (page 8).


clearscreen()

Clears the screen. Note that this function may have no effect on some platforms; for example, when the DOS runtime is operating in plain ASCII mode, clearscreen() will have no effect.

debugTrace(1, flag)
This new form of debugTrace lets you turn a new player command parser diagnostic mode on and off. If flag is true, this function activates the new diagnostic mode; if flag is nil, it turns the diagnostic mode off.

When the new diagnostic mode is active, the parser generates a series of progress messages as it analyzes the player's command. These messages provide information on how the parser interprets the words in the command. When first reading a sentence, the parser displays all possible parts of speech for each word. As the parser further analyzes the sentence, it displays information on each noun phrase: the words involved, the part of speech that the parser uses for each word in the noun phrase (when a word can be used as multiple parts of speech, the parser chooses one part of speech as it reads the noun phrase), and the objects that match the words in the noun phrase.

The parser diagnostic mode may help you track down problems in which the parser refuses to recognize certain noun phrases that you would expect to be valid. Since the parser chooses among ambiguous interpretations of words, it's frequently helpful to understand exactly how the parser is interpreting your commands; this new debugging mode should make it easier to gain this understanding.

This new mode is available in the standard runtime as well as the debugger, so debugTrace(1, flag) always succeeds. The function returns no value.

The original debugTrace() function still works as it always has.


delword(obj, &prop, word)
Deletes the word (a single-quoted string value) from the object's vocabulary for the given part of speech. The prop parameter can be noun, adjective, plural, verb, article, or preposition. You can delete words from any object, including objects defined statically in your game, as well as objects created dynamically at run-time. Furthermore, you can delete words that were added dynamically, as well as words that were statically defined in your game.

For examples of using this function, see the section on Dynamic Vocabulary (page 8).


firstsc(obj)
Returns the first superclass of the given object. Returns nil if the object has no superclass (which will only be the case if the object was defined as being of type object).

This function is provided primarily to facilitate handling equivalent objects. In conjunction with the isEquivalent property, this function lets you determine if two objects are indistinguishable from one another. If two objects have the same immediate superclass, and they both have the isEquivalent property set to true, the two objects are equivalent.


fclose(filehandle)
Closes the file indicated by filehandle. Once the file is closed, no further operations on filehandle are valid.

fopen(filename, mode)
Opens the file whose name is given by the single-quoted string value filename; the file is opened in a manner according to the mode argument, which is a single-quoted string value:

'r' open file for reading; file must already exist

'r+' open file for reading and writing; the file is created if it doesn't already exist

'w' create a new file for writing; the file is deleted if it already exists

'w+' create a new file for reading and writing; the file is deleted if it already exists

The function returns a file handle that is used in subsequent file operations (fwrite(), fread(), fclose(), and the like) to refer to the open file.

If the operation fails, fopen() returns nil. This function can fail for a number of reasons; for example, if you attempt to open a file that doesn't exist with mode 'r', the operation will fail because this mode can only be used to open an existing file.


fread(filehandle)
Reads the next data item from the file and returns its value. The value will be of the same type as that originally written at the current position in the file with fwrite(). If an error occurs, this function returns nil; this usually indicates that you are attempting to read past the end of the file.

fseek(filehandle, byteposition)
Seeks to a byte position in the file. The byteposition value should be a value previously returned by ftell(), since other values may not correspond to a value in the file.

fseekeof(filehandle)
Positions the file at its end.

ftell(filehandle)
Returns the current seek position in the file, given as a number of bytes from the beginning of the file.

fwrite(filehandle, value)
Writes the given value to the file. The value argument must be a number, a single-quoted string, or true. Returns nil on success, true on failure; a true return value usually indicates that the disk is full.

getwords(obj, &prop)
This function returns a list of single-quoted strings giving the vocabulary words defined for the given part of speech for the specified object. The prop parameter can be noun, adjective, plural, verb, article, or preposition.

For examples of using this function, see the section on Dynamic Vocabulary (page 8).


getfuse(funcptr, parm) (2.1)
The new function getfuse(funcptr, parm) lets you determine if the indicated fuse is still active, and if so, how many turns are left until it is activated. If the fuse is not active (it has already fired, or it has been removed with a call to remfuse, or it was simply never set), the function returns nil. Otherwise, it returns the number of turns before the fuse is activated.

getfuse(obj, &msg) (2.1)
This form of getfuse() lets you check on a fuse set with the notify() function. If the fuse is not active, the function returns nil, otherwise it returns the number of turns before the fuse is activated.

gettime() (2.1)
Returns the current system clock time. The time is returned as a list of numeric values for easy processing. The list elements are:
          year        - calendar year (e.g., 1992).
          month       - month number (January = 1, February = 2, etc.)
          day         - number of the day within the current month
          weekday     - day of the week (1 = Sunday, 2 = Monday, etc.)
          yearday     - day of the year (1 = Jan 1)
          hour        - hour of the day on 24-hour clock (midnight = 0,
                        noon = 12, 3 PM = 15, etc.)
          minute      - minute within the hour (0 to 59)
          second      - second within the minute (0 to 59)
          elapsed     - the number of seconds since January 1, 1970,
                        00:00:00 GMT.  This last value is useful for
                        computing the difference between two points
                        in time.


incturn(num)
This function has been extended to allow you to run a series of turns all at once. You can now specify a numeric argument to incturn(); the argument gives the number of turns that should pass. An argument of 1 is equivalent to calling incturn() with no arguments.

When an argument higher than 1 is specified, the function runs all of the fuses that are set to turn down within the number of turns specified, but not after that number of turns. Note that the normal incturn() doesn't actually execute any fuses, but simply burns all fuses down by one turn.

For example, if you call incturn(2), the system will first run any fuses that are set to burn down after 1 turn, then will shorten all remaining fuses by one turn. Similarly, incturn(3) first runs any fuses that are set to burn down after 1 turn, then runs any fuses set to burn down after 2 turns, then shortens any remaining fuses by 2 turns.

Fuses set with setfuse() and notify() are both affected by this routine. Note that this function has no effect on daemons.


inputkey() (2.1)
Reads a single keystroke from the keyboard, and returns a string consisting of the character read. inputkey() takes no arguments. When called, the function first flushes any pending output text, then pauses the game until the player hits a key. Once a key is hit, a string containing the character is returned. Note that this function does not provide a portable mechanism for reading non-standard keys, such as cursor arrow keys and function keys. If the user hits a non-standard key, the return value is the representation of the key on the operating system in use. To ensure portability, you should use this function only with standard keys (alphabetic, numeric, and punctuation mark keys). Note that you will encounter no portability problems if you simply ignore the return value and use inputkey() only to pause and wait for a key.

Note that inputkey() takes its input directly from the keyboard. It does not look at the command line


intersect(list1, list2) (2.1)
Returns the intersection of two lists - that is, the list of items in both of the two lists provided as arguments. For example:

  intersect( [ 1 2 3 4 5 6 ], [ 2 4 6 8 10 ] )

yields the list [ 2 4 6 ].

Note that the behavior for lists with repeated items is not fully defined with respect to the number of each repeated item that will appear in the result list. In the current implementation, the number of repeated items that is present in the shorter of the two source lists will be the number that appears in the result list; however, this behavior may change in the future, so you should try not to depend on it.


nocaps()
This function is the opposite of caps() - it specifies that the next character displayed will be converted to lower case. Note that you can use the new escape sequence "\v" to achieve the same effect.

Note that calls to caps() and nocaps() override one another; if you call caps(), and then immediately call nocaps(), the next character displayed will be lower-case - the call to caps() is forgotten after the call to nocaps().


objwords(num) (2.1)
Provides a list of the actual words the user typed to refer to an object used in the current command. The argument num specifies which object you're interested in: 1 for the direct object, or 2 for the indirect object. The return value is a list of strings; the strings are the words used in the command (converted to lower case, stripped of any spaces or punctuation). If a special word such as "it," "them," or "all" was used to specify the object, the list will have a single element, which is the special word used.

For example, if the player types "take all," then objwords(1) will return [ 'all' ] and [ objwords2 ] will return []. Note that objwords(1) will return [ 'all' ] even if the player typed a variation on "all," such as "take everything" or "take all but the book."

If the player types "put all in red box," the objwords(1) returns [ 'all' ] and objwords(2) returns [ 'red' 'box' ].

If the player uses multiple direct objects, the function will return the current object's words only. For example, if the player types "put blue folder and green book in red box," objwords( 1 ) will return [ 'blue' 'folder' ] while the first direct object is being processed, and [ 'green' 'book' ] while the second object is being processed.

This function could potentially be useful in such cases as "ask actor about object," because it allows you to determine much more precisely what the player is asking about than would otherwise be possible.

You can call objwords() during a verb verification or action method (verDoVerb, verIoVerb, doVerb, ioVerb), and during a doDefault method. Note that the return value is slightly different during a doDefault method: if the word "all" is used in the command, the function will return the list [ 'A' ] rather than [ 'all' ], due to the internal order of processing of the word list.


objwords( 1 ) words with doDefault
You can now call objwords( 1 ) from within the doDefault method, as described above. Until version 2.2, you could call objwords( 1 ) only from verb verification and action methods.

outhide(flag) (2.1)
Turns hidden output on or off, simulating the way the parser disambiguates objects. The parameter flag is either true or nil. When you call outhide(true), the system starts hiding output. Subsequent output is suppressed - it won't be shown to the player. When you call outhide(nil), the system stops hiding output - subsequent output is once again displayed. outhide(nil) also returns a value indicating whether any (suppressed) output was generated since the call to outhide(true), which allows you to determine whether any output would have resulted from the calls made between outhide(true) and outhide(nil).

This is the same mechanism used by the parser during disambiguation, so it should not be called by a verDoVerb or verIoVerb method. This function is provided to allow you to make calls to verDoVerb and verIoVerb to determine if they will allow a particular verb with an object, just as the parser does.

There is no way to recover the text generated while output is being hidden. The only information available is whether any text was generated.


outhide(flag) - nested calls
When you call outhide(true), the function returns a status indicator, which is a value that can be used in a subsequent call to outhide() to restore output hiding to the state it was in before the outhide(true). This allows you to nest text hiding - you can hide output in a subroutine or method call, without interfering with the routine that called you or any routines you call.

To use the nested form, save the return value of outhide(true), and then use the saved value as the parameter - in place of nil - to the subsequent call to outhide(). The value returned by the second outhide() indicates whether any text output occurred between the nested calls. For example:

  local old_stat1, old_stat2;
  local new_stat1, new_stat2;
  old_stat1 := outhide(true);
  "This is some hidden test.";
  old_stat2 := outhide(true);
  // don't write any text here
  new_stat2 := outhide(old_stat2);
  new_stat1 := outhide(old_stat1);

Because outhide(old_stat2) indicates whether any output occurred during the nested outhide(true), new_stat2 is nil. However, new_stat1 is true, since output occurred after the first outhide(true). Consider another sequence:

  old_stat1 := outhide(true);
  // write no text here
  old_stat2 := outhide(true);
  "This is some hidden text.";
  new_stat2 := outhide(old_stat2);
  new_stat1 := outhide(old_stat1);

In this case, both new_stat1 and new_stat2 will be true, because hidden output occurred within both nested sections (even though only a single message was displayed, it was within both nested sections).

The general form of a nested outhide() section is:

{
   local old_stat;
   local new_stat;

   old_stat := outhide(true);
   // do whatever you want here
   // output will be hidden
   new_stat := outhide(old_stat);
}

The new_stat will indicate whether any output occurred between the outhide(true) and the outhide(old_stat). In addition, output hiding will be restored to the same state as it was prior to the outhide(true).


rand() is more random
The random number generator has been improved. Some people noticed that the old random number generator was producing somewhat less than ideally random results, especially when small upper limits were used. The interface to the new random number generator is exactly the same as before.

Note that the old random number generator is still used if you don't call randomize(). This allows test scripts (which require a fixed sequence of random numbers in order to be repeatable) that were written with older versions of TADS to continue to work unchanged. If you want numbers from the improved generator, be sure to call randomize().


restart(funcptr, param) (2.1)
This new form of restart() lets you specify a function to be called after restarting the game, but before the init() function is invoked. This new feature has been added because it would otherwise be impossible to pass any information across a restart operation: the restart() function doesn't return, and all game state is reset to its initial state by restart(). You can use this new form of the function if you want a restarted game to have different startup behavior than the game has when it's first started. Note that adv.t passes a pointer to the initRestart function (defined in adv.t when it invokes restart() in response to a "restart" command from the player; the adv.t implementation of initRestart() simply sets the flag global.restarting to true to indicate that the game is being restarted rather than first entered. You can replace the initRestart function defined in adv.t if you want to customize the restart behavior.

The param value is simply passed as the parameter to the function to be called; this allows you to pass information through the restart. For example, if you start the game with a questionnaire asking the player's name, sex, and age, you could pass a list containing the player's responses to your restart function, and have the restart function store the information without making the player answer the questionnaire again. The call to restart() in adv.t uses global.initRestartParam as the parameter for the initRestart function; so, if you provide your own version of initRestart, you can simply store the necessary information in global.initRestartParam to ensure that it's passed to your function.


restore(nil) (2.1)
This special new form of the restore() function allows you to choose the time during startup that the player's saved game is restored when the player starts your game with a saved game already specified. When you call restore(nil), the system checks to see if a saved game was specified by the player at startup, and if so, immediately restores the game and returns nil. If no game was specified, the function returns true.

Current, it's possible for a player to start a game in this manner only on the Macintosh, but the new restore() functionality will work correctly on all platforms. On the Macintosh, the operating system allows an application to be started by opening one of the application's documents from the desktop; the application is started, an d is informed that the user wishes to open the specified file. Saved game files on the Macintosh are associated with the game executable that created them in such a way that the game is executed when a saved game is opened. This is simply a convenience feature on the Mac that allows a player to run a game and restore a saved position in a single operation.

You can use restore(nil) in your init function to choose the point at which the saved game will be restored. If your game has extensive introductory text, you could call restore(nil) prior to displaying your introductory text, and then skip the introductory text if the function returns nil.

The reason that the system doesn't restore the saved game prior to calling your init function is that you may want parts of your init function to be invoked regardless of whether a game is going to be restored or not. For example, you may want to display your copyright message, or ask a copy-protection question, even when a saved game is going to be restored.

If you do not make a call to restore(nil) in your init function, TADS will automatically restore the saved game specified by the player immediately after your init function returns. Hence, omitting the call to restore(nil) doesn't do any harm; this function is provided to give you greater control.


rundaemons() (2.1)
Runs all of the daemons. This function runs daemons set with setdaemon() and notify(). This function returns no value.

runfuses() (2.1)
Runs all expired fuses, if any. Returns true if any fuses were ready to run, nil otherwise. This function runs fuses set both with setfuse() and with notify().

setit( nil ) (2.1)
You can now use nil as the argument to setit(). This prevents the player from using "it" in the next command.

setit( obj, num ) (2.1)
You can now specify which pronoun you want to set. The new optional parameter num specifies which pronoun to set: 1 for "him," and 2 for "her." When num isn't specified, setit(obj) sets "it" as usual. Note that nil can be used as the obj argument to clear "him" or "her," just as it can with "it."

setit(list) (2.1)
You can now set "them" directly, simply by passing a list of objects (rather than a single object) to setit(). Calling setit() with a list argument has the side effect of clearing "it."

skipturn(num)
This function is similar to incturn(num); this function skips the given number of turns, which must be at least 1. The difference between skipturn() and incturn() is that skipturn() doesn't run any of the fuses that burn down during the turns being skipped - instead, it simply removes them without running them.

Fuses set with setfuse() and notify() are both affected by this routine. Note that this function has no effect on daemons.


verbinfo(deepverb)
verbinfo(deepverb, prep)

This function lets you retrieve information on the doAction and ioAction definitions for the given deepverb object. This function returns a list giving the verification and actor property pointers for the appropriate verb template. Refer to the section on getting verb information (page 9) for details on this function.


Debugger Enhancements


Case Insensitivity in Commands
Commands are no longer case-sensitive. The command "BP" is now equivalent to "bp."

Call Logging (2.1)
We've added a mechanism that lets you capture a log of all method and function calls, and then inspect the log. This feature can help you determine the exact sequence of calls that TADS itself makes into your game, and also lets you see how your game and lower-level classes (such as those from adv.t) interact. Four new command let you control logging (on the Macintosh, these are also accessible from the "Execution" menu):

c+ Begins call logging. Any previous call log is cleared, and all subsequent method and function calls and returns will be added to the new call log.

c- Ends call logging.

cc Clears the current call log.

c Displays the current call log. Shows all function and method calls after the most recent c+ and up to the current time, or the most recent c-.

The reason that commands are provided to turn call logging on and off is that the logging process can slow down your game's execution substantially. When call logging is activated, the system must do extra work every time a function or method is entered or exited. You should enable call logging at the point you're about to execute a command that you want to trace, then turn it off when you're finished with the command.

Note that the call log is limited in capacity. If the log becomes full, the oldest information is discarded to make room for new information. If you leave call logging activated for an extended period of time, information toward the beginning of the log may be lost.

The lines of text displayed in the call log will be indented to show nesting. The functions and method called directly by TADS will not be indented at all; anything called by these functions and methods will be indented one space; and so on. If a function or method has a return value, it will be indicated in the log (prefixed by "=") at the point where the function or method returns. Each call will show the object and method or function name involved, along with the arguments; the format is the same as in a stack trace.


Run-time Error Catching (2.1)
The debugger will now take control when a run-time error occurs. The error message will be displayed, and execution will be suspended at the line where the error occurred. When you resume execution, the current turn will be aborted (as though an abort statement had been executed).

Infinite Loop Break-out (2.1)
You can now break out of an infinite loop in your game. Hit Ctrl-Break if you're running on a DOS machine, or Command-Period on a Macintosh. The debugger will take control.

Breakpoints in Inherited Methods (2.1)
The debugger will now stop at a breakpoint in a method inherited by an object. For example, if you set a breakpoint at room.lookAround, execution will stop any time a subclass of room executes the inherited lookAround method. Of course, if a subclass overrides the lookAround method, execution will not stop at room.lookAround.

Memory Size Reductions (2.1)
The run-time memory requirements for the debugger have been reduced, which should allow larger games to be debugged on smaller machines (such as DOS machines) without the out-of-memory conditions that some people have experienced with past versions.

New Compiler Options


-v level (2.1)

This option sets the warning verbosity level. The default level is 0 (minimum verbosity: suppress warnings which are purely informational or generally do not indicate an actual problem). Other levels are 1 (suppress purely informational messages, but display warnings even when they generally do not indicate a problem), and 2 (maximum verbosity: display all warnings and informational messages).

-fv type
Sets the .GAM file format. The type can be a for file format "A" (used by TADS versions prior to 2.1), b for format "B" (used by versions 2.1 up to but not including 2.2), c for format "C" (introduced in version 2.2), or * for the most recent version (currently "C"). The default is -fv*. You can use this option if you wish to compile your game so that it can be used with an earlier version of the TADS runtime.

Version 2.1 of TADS uses file format "B," which is slightly different from the original file format, and 2.2 uses format "C," which differs slightly from "B." File formats other than "A" will not be accepted by versions of the runtime earlier than 2.1. The version 2.2 runtime is able to read any previous file format.

The changes made in version "B" make the .GAM file much more compressible with archiving utilities. If you use file format "A," your games will not compress very well with programs such as ZIP.

The changes made in version "C" enable the disambigDobjFirst flag. If you compile with an earlier file format, you will not be allowed to use this flag.


-e file (2.1)
This option tells the compiler to log all error messages to file. The compiler writes all of the messages generated during compilation to the indicated file; the messages will also be displayed on your screen as usual. The file will be created if it doesn't exist, and it will be overwritten with the new error log if it already exists.

-C
(Note that this is a capital "C.") This is a toggle option that lets you turn C operator mode on and off (see the section on C operators on page 20). The default is -C-, which enables the standard TADS operators. If you specify -C+, TADS will change the assignment operator to "=" and the equality operator to "==" to match the C language.

-v-abin
Disables the warning message "operator x interpreted as unary in list." (TADS-357). If you're compiling a game written with a previous version of TADS, and you get this warning, you may want to use -v-abin to disable the warning. This warning is intended to notify you that the new meaning of the operator is not being used - in other words, TADS is warning you that it's using an interpretation that's compatible with past versions of the compiler. If your game was written for an older version of the system, this is exactly what you want, so you probably don't need the warning.

Improved error checking for -m
The compiler now checks more carefully to ensure that the memory size settings specified with the various -m parameters (in particular, -mg, -mp, -ml, -ms, and -mh) are valid. These settings must fit within certain limits, which are now enforced more carefully. Previous versions of the compiler responded unpredictably to invalid settings.

-case-
The compiler now lets you turn off case sensitivity. If you don't want to distinguish between upper- and lower-case letters in your symbols, you can use the -case- option to turn off the compiler's case sensitivity. By default, the compiler is set to -case+, which makes the compiler treat an upper-case letter as distinct from the same letter in lower-case. When the compiler is case-sensitive, deepverb and deepVerb are different symbols. If you would prefer not to keep track of the exact case of the letters in the symbols defined in your game (or in adv.t), you can make the compiler treat upper- and lower-case letters the same by using -case-.

Note that adv.t will work either way. All of the references to a particular symbol in adv.t use the same case, so adv.t will work properly with case sensitivity turned on. However, adv.t will also work properly with case sensitivity turned off, because it doesn't have any symbols that depend on being in a particular case (for example, it doesn't define one symbol called "a" and a distinct symbol called "A" - if it did, these symbols would collide when compiling without case sensitivity).


Macintosh Compiler no longer adds ".t" Suffix
The Macintosh compiler no longer adds a default suffix to input files. In past versions, the compiler added ".t" to the name of a source file if the source file didn't have any periods in its name. While adding a default suffix is convenient on other platforms (since it saves the user the trouble of typing the suffix on the command line), it's obviously not desirable on the Macintosh, since the user specifies the file by pointing at it - the full filename is always given, so a suffix shouldn't be added.

Changes to adv.t


distantItem (2.1)
The new class distantItem has been added. This new class is similar to fixeditem, except that it's intended to be used to objects that are not actually part of a room, but are visible from the room in the distance. For example, you might use this class for distant mountains visible from a location - the player obviously shouldn't be able to do anything to the mountains except look at it them. A distantItem can be inspected, but any other attempts to manipulate the object will receive the response "It's too far away."

scoreStatus (2.1)
The new scoreStatus(points, turns) function has been added. This new function simply calls setscore() with the same arguments. All other calls to setscore() in adv.t have been replaced with calls to this new function, which makes it easy to provide a new scoring format, if you wish, simply by using replace to substitute your own scoreStatus() function for the one in adv.t.

nestedroom.statusPrep, outOfPrep (2.1)
Two new properties have been added to the nestedroom class: statusPrep, which displays an appropriate preposition for status line displays while the player is in the nested room; and outOfPrep, which displays the correct preposition when leaving the nested room. The default values are "in" for statusPrep and "out of" for outOfPrep. The class beditem provides values of "on" and "out of" instead. If you're defining a new subclass of nestedroom, you can override these properties to provide the most appropriate messages for the subclass.

fixeditem and "throw" (2.1)
You can no longer throw a fixeditem at anything.

follower changes (2.1)
The follower class's actorAction method now appropriate executes an exit statement. In addition, the follower class now uses dobjGen and iobjGen to provide more sensible responses to most verbs.

clothingItem: "get out of" (2.1)
You can now "get out of" a piece of clothing; this is equivalent to "take off."

"l at" (2.1)
The various verbs which use the word "look" plus a preposition have been modified to allow "l" in place of "look"; this applies to several verbs, including "look at" (which now accepts "l at" as equivalent), "look on," "look in," "look under," "look around, " and "look through."

takeVerb.doDefault (2.1)
The doDefault method in takeVerb has been corrected so that it doesn't return the contents of a closed object. In previous versions of adv.t, "take all from object" would succeed even when the object was closed.

vehicle manipulation from within (2.1)
The vehicle class has been corrected so that the player cannot generally manipulate the vehicle while occupying it. For example, the player can no longer take a vehicle or put it anywhere while inside it.

initRestart (2.1)
A new function, initRestart(param), has been added. This function is used when adv.t calls the restart() built-in function (to start the game over from the beginning) in response to a "restart" command. The initRestart() in adv.t simply sets the property global.restarting to true. Your game can inspect this flag in your init function (or elsewhere) to take a different course of action when restarting a game than when starting up for the first time. The parameter is not used by adv.t implementation of the function, but if you replace the initRestart function defined in adv.t, you might find the parameter useful for passing information through the restart.

restartVerb (2.1)
The "restart" verb passes a pointer to the initRestart function when it calls the restart() built-in function. This causes initRestart to be invoked after the game has been restarted, but before the system calls init. Note that the call to restart() passes global.initRestartParam as the parameter to the initRestart function. If you replace initRestart with your own implementation, and you need to pass some information to this function from the game running before the restart, simply store the necessary information in global.initRestartParam at any time before restarting, and the information will automatically be passed to your initRestart function.

pluraldesc
We've added the new property pluraldesc to the class thing in adv.t. The definition simply adds an "s" to the end of the sdesc property. This new property is used by listcont() when multiple equivalent objects are present in a list; for details, see the information on the changes to listcont() described in the section on indistinguishable objects (page 10).

listcont, itemcnt - indistinguishable objects
The listcont and itemcnt functions in adv.t have been enhanced to list multiple occurrences of the same indistinguishable item only once, with the number of such items. See the section on indistinguishable objects (page 10) for more information.

"abort" usage changes
All of the system verbs in adv.t that use the abort statement have been modified slightly to make it easier to augment their behavior with the modify statement. All of the processing other than the abort has been moved out of the doVerb (or action) method, and put into a new method. For example, the action method in saveVerb now looks like this:

  action( actor ) =
  {
    self.saveGame( actor );
    abort;
  }

The new method saveGame in saveVerb now performs all of the processing that the action method previously performed.

The benefit of this change is that you can modify the saveGame method, and inherit the original behavior, without having to worry about an abort statement interfering with the order of operations. For example:

  modify restoreVerb
     restoreGame( actor ) =
     {
        // restore the game, and check for success
        if ( inherited.restoreGame( actor ) )
        {
           // re-randomize the puzzle
           "The carnival hawker flashes a mischievous
           smile at you.  \"There's no use trying to
           guess the answer,\" he says.  \"I changed
           around the shells while you were busy
           restoring!\"";
           puzzle.answer := rand( 100 );
        }
     }
  ;

fmtMe
The format mask fmtMe has been added. You can now use "%me%" in messages to refer to the actor. For basicMe, fmtMe is set to the message "me"; for other actors, it's set to the actor's thedesc. In adv.t, thing.ldesc has been changed to use "%me%": "It looks like an ordinary sdesc to %me%." This makes the default sentence somewhat more adaptable if you ask another actor to describe something:

  >guard, look at the card

  It looks like an ordinary card to the guard.

again, wait, sleep are darkverbs
The verbs "again," "wait," and "sleep" are now all darkVerb subclasses. None of these verbs logically requires any light (in fact, one could argue that "sleep" works better in the dark).

"there" = "it"
The specialWords definition in adv.t now adds "there" as a synonym for "it." This allows commands such as: "Take the box and put the ball in there."

isqsurface correction
The showcontcont() function no longer displays the contents of an object with the isqsurface property set to true.

seethruItem
We added a new class, seethruItem, which the player can look through. This is intended for objects such as windows or magnifying glasses. If the player looks through the object (with a command such as "look through the window"), t he object calls its thrudesc method to display an appropriate description of what the player sees. You should customize this method to display an appropriate message for each seethruItem you create; the default method displays "You can't see much through the window," which obviously won't be what you wanted.

The class thing previously defined a thrudesc method as described above. To preserve compatibility with any existing games that depended on the presence of this method in thing, we left this as it was.

Note that seethruItem is not the same as transparentItem. The class transparentItem is for objects whose contents are visible, whereas seethruItem is for objects that the player can look through. Use seethruItem for windows, binoculars, magnifying glasses, and other non-containers. Use transparentItem for glass bottles and other transparent containers.


DOS Color Customization (2.1)

The player can now customize the colors used by the DOS runtime. A small new program, TRCOLOR, lets the player select the colors to use while running a game. The program is self-explanatory: it displays instructions on-screen, and uses the arrow keys to select colors. The program lets a player customize the colors to use for the main text area, highlighted text, and the status line. Type TRCOLOR at the DOS prompt to run the program.

Once you've selected your color scheme, the TRCOLOR program creates a small file called TRCOLOR.DAT in the current directory. The runtime reads this file at the start of subsequent game sessions.

Note that you can use multiple TRCOLOR.DAT files, in the same way that you can use multiple CONFIG.TC files. The runtime looks for TRCOLOR.DAT first in the current directory; if no such file exists, the runtime uses the TRCOLOR.DAT in the directory contain ing TR.EXE. This allows you to set up a separate color scheme for each game you're playing, and in addition set up a default color scheme for games with no color schemes of their own.


Improvements to MAKETRX

We've made a couple of changes to the MAKETRX program (on Macintosh, this program is called the "Executable Game Builder"). First, in version 2.1, we simplified the command line for the DOS version by letting you leave off arguments when MAKETRX is able to determine a suitable default. Second, in version 2.2, we added the ability to "bind" a set of run-time configuration options into your game executable - both the DOS and Macintosh versions provide this new feature.


DOS Default Arguments (2.1)
In version 2.1, we improved the user interface of the DOS version of MAKETRX. For compatibility with existing makefiles, the old command line syntax is still allowed; however, you can now omit most of the arguments, and MAKETRX will provide suitable defaul ts.

First, you can omit the filename extensions for any of the arguments. The extension assumed for the TADS runtime executable is ".EXE"; for the game file it is ".GAM"; and for the output (executable) file it is ".EXE".

Second, you can now omit everything except the name of the game file, and the program will attempt to provide defaults. If you omit the name of the TADS runtime executable, MAKETRX looks for a copy of TR.EXE in the same directory as the MAKETRX program; so , if you simply keep all of your TADS executables in a single directory, you don't need to specify the location of TR.EXE when running MAKETRX. If you omit the destination filename, MAKETRX will use the same name as the game file, with the extension replac ed by ".EXE".

The new command line formats for MAKETRX are:

  maketrx mygame

Converts MYGAME.GAM into MYGAME.EXE, using the copy of TR.EXE that resides in the same directory as MAKETRX.EXE.

  maketrx mygame myprog

Converts MYGAME.GAM into MYPROG.EXE, using the copy of TR.EXE that resides in the same directory as MAKETRX.EXE.

  maketrx c:\tads2\tr mygame myprog

Converts MYGAME.GAM into MYPROG.EXE, using C:\TADS2\TR.EXE as the runtime executable.


Options File Binding
The MAKETRX command (the Executable Game Builder on the Macintosh) now takes an additional parameter that lets you specify the run-time command options that should be used when the game is executed. MAKETRX still accepts the original command formats; using one of the old-style command formats will not bind any command options into the resulting .EXE file, in which case the game will use the default runtime settings.

To specify command options for your game executable, you must first create a file containing the command options. Use the same format as CONFIG.TR - simply enter your options into the file as you would on the TR command line; separate options by newlines or spaces. For example, to specify a minimal cache size and a swap file of SWAP.DAT, you could make your CONFIG.TR file look like this:

  -m0 -tf swap.dat

For DOS users: Once you've created a file with your command options, specify that file to MAKETRX by using it as the first parameter on the command line, prefixed with an "at" sign (@):

maketrx @config.tr mygame

The @config option can be used with any of the original command formats for MAKETRX. Once the options file is bound into your executable, its options will be used every time a player runs your game's .EXE file.

For Macintosh users: an extra dialog has been added at the end of the process of building the application. This new dialog asks you for the options file you want to bind into your game. If you don't want to include an options file, simply hit "Cancel." Otherwise, select the file containing the options and hit "OK." Note that the options file for the Macintosh version is in the same format as for the DOS version; refer to the Author's Manual for information on the format of the run-time command options.

Note that you may should avoid specifying anything specific to your system in your options file, such as drives or directories, since doing so may prevent the game from working properly on someone else's system. For example, if you specify the swap file as "D:\SWAP.DAT," the game won't work on a system that doesn't have a "D:" disk, since TADS will be unable to create a swap file on a drive that doesn't exist.


Miscellaneous Runtime Improvements
DOS: Ctrl-Left and Ctrl-Right Arrow Keys
The DOS runtime now lets the player move the cursor to the start of the next word with control-right arrow, and to the start of the previous word with control-left arrow. This is consistent with many other DOS command-editing environments.

DOS Plain ASCII mode
The DOS runtime now has a plain ASCII mode, in which it uses only DOS character input/output functions. When in plain ASCII mode, the DOS runtime won't use any low-level I/O functions, such as BIOS or memory-mapped video, but goes entirely through DOS. Th e runtime also won't display the status line or any highlighting. If you have a special need for DOS-only I/O (for example, if you want to use a voice synthesizer to read the text generated by the game), you can use this mode.

To tell the runtime to use plain ASCII mode, specify -plain on the runtime command line:

  tr -plain deep

You can also specify -plain to an executable game, as long as the game executable was built using a version of the runtime (such as 2.2) that supports the plain ASCII mode:

  deep -plain

Hyphenation Improvements
The output formatter's handling of hyphenation has been improved. In particular, the output formatter will not split multiple consecutive hyphens across lines; if you use two or three hyphens together as a dash, the formatter will no longer split these up across lines. In addition, the formatter will keep hyphens at the end of a line, and will not break a line in such a way that it starts with a hyphen.

The Macintosh formatter has been fixed so that it no longer inserts spaces after hyphens when the spaces weren't in the original text.


Escapes in askfile Prompts
Prompt strings passed to askfile can now contain \n and \t sequences; these sequences are converted properly for display in the dialog.

DOS file selector bug fix
The DOS file selector has been corrected so that the Alt-key mappings are correct. In a previous version, the Alt-key mappings were off by one (Alt-B selected the A drive, for example).

Macintosh File Selector "All" Button
The Macintosh runtime's initial file selector dialog, which lets you specify the game you want to run, now has an "All Files" button. Clicking this button makes the file selector show all files, rather than just files that have the special signature that indicates they were created by the Macintosh version of the TADS compiler. If you've transferred a .GAM file from a non-Macintosh system, you can use the "All Files" button to open the file - you no longer have to use ResEdit or another file utility to set the creator and type flags in the file.

Status Line on Startup
The runtime system (on both DOS and Macintosh) no longer displays anything as the game is being initialized. In previous versions, the runtime displayed "TADS" at the left end of the status line, and "0/0" at the right end, until the game changed the display. This was slightly incongruous for games that changed to a different status line format. The status line is now completely blank during game initialization.

Note that this change may have a slight effect on your game: by default, the score/turn area of the status line will be completely blank until a call to setscore() is made. To compensate for this change, we've added a call to scoreStatus(0, 0) at the beginning of the init function defined in adv.t. If you provide your own init function, you may want to initialize the status line's score display in your init; if you don't, the status line's score/turn area will be empty until the player enters the first command, at which point the normal turn-counting daemon will usually update the status line.


Compiler Exit Codes

The compiler now sets system exit codes according to whether any errors occurred during compilation. If there are no errors (even if there are warnings), the compiler sets the exit code to 0. If any errors occur, the compiler sets the exit code to a non-zero value. This makes the compiler work better with "make" utilities, since these utilities usually check the exit code of each command to determine if the operation encountered an error.

In addition, the compiler will no longer generate a .GAM file if any errors occur. In the past, the compiler generated a .GAM file in most cases, even when the source file contained errors. Attempting to run a .GAM file produced from a source file with errors had unpredictable results.

The Macintosh compiler now displays a different message if a compilation fails or is interrupted than it does when the compilation completes successfully. The compiler displays "Compilation Terminated" if errors occur; it still displays "Compilation Completed" upon successful completion without errors.


TADS 2.1 Revision Notes Table of Contents TADS 2.2 Part II