TADS Revision History - versions 2.2.3 through 2.4.0 ==================================================== This file contains a list of changes that have been made to TADS from version 2.1.0 through version 2.4.0. Most of the changes are fixes to bugs, so they don't change the documented behavior, but a few, as explained below, add new functionality to TADS. Releases are listed with the most recent release first; each release incorporates all new features and bug fixes of each prior release unless otherwise stated. Starting with version 2.2.6, these release notes are divided into two separate files: one for "generic" changes that apply to TADS on all types of computers and operating systems, and one for changes that apply only to specific platforms. This file contains the generic release notes. Platform-specific release notes are in a separate file: tadsv240.txt - MS-DOS and Windows changes, 2.2.3 through 2.4.0 These release notes sometimes refer to the "character-mode" run-time. This is meant to distinguish the traditional TADS run-time from the HTML TADS run-time. The traditional TADS run-time has a graphical user interface on some systems, such as the Macintosh, so it's not really a character-mode application on those systems; nonetheless, we still refer to it here as the character-mode run-time simply to make it clear that we're not talking about the HTML TADS version. Note that changes from version 2.0 up to 2.2.2 are not included in this file in order to keep its size under control. Older release notes are in separate files for each platform. For DOS, revisions from 2.0 through 2.1.0 are available in the file TADSV200.DOS, and those from 2.1.1 through 2.2.2 are in TADSV222.DOS. Similar files are available for other platforms; please refer to your platform- specific release notes for details. Because these files are quite large and are static (they are, after all, historical), they're not included in the normal TADS distributions, but you can download them from the Interactive Fiction Archive via the internet at ftp://ftp.ifarchive.org/if-archive/programming/tads2 (ifarchive.org is the new home, effective August 2001, of the former IF archive at ftp.gmd.de, and the TADS directories were split into programming/tads2 and programming/tads3 as of November 2001). ------------------------------------------------------------------------------ 2.4.0 - May 16, 1999 - A new built-in function, exitobj, provides a new method for skipping the remaining processing for a command at any point. This new function is very similar to the "exit" statement, but differs in one respect: whereas the "exit" statement terminates all further processing for a command and skips directly to the fuses and daemons, "exitobj" skips the remaining processing only for the current object in a command and proceeds to the next object. This difference is significant when the player types in a command involving multiple objects. For example, suppose that you define a roomAction method in the current room as follows: roomAction(actor, verb, dobj, prep, iobj) = { /* * when the player touches anything not already in their inventory, * make it vanish */ if (dobj != nil && !dobj.isIn(actor)) { "\^<> disappears in a flash of light!\n"; dobj.moveInto(nil); exit; } } Consider the following transcript: >take ball The ball disappears in a flash of light! >take hammer and chisel The hammer disappears in a flash of light! The first response makes sense, but the second isn't exactly what you wanted. The problem is that the "exit" statement tells the parser to skip processing of any objects other than the current one. To change this, you can simply change the "exit" statement in the code listing above to "exitobj". The result will be more sensible: >take hammer and chisel The hammer disappears in a flash of light! The chisel disappears in a flash of light! "exitobj" is useful when you want to skip the remaining processing for a command for the current object, but you still want the command to be considered successful. "exit" is more suited for situations where the outcome of the command is something less than total success, and you want to skip further processing of other objects involved in the command. "exitobj" is particularly useful with the new execCommand() built-in function (see below), because it allows you to completely redirect the processing of a command, skipping all or part of the normal processing for the original command without telling the parser that the original command was unsuccessful. You an use exitobj anywhere you can use exit. - A new built-in function, execCommand, gives a game program direct access to the parser's command execution system. This new function doesn't provide direct access to the string-parsing portion of the parser, but to the command execution portion, which takes the objects involved in the command and executes the command, performing object validation (validDo, validIo), room notification (roomAction), actor notification (actorAction), direct and indirect object checks (dobjCheck and iobjCheck), general object handling (dobjGen and iobjGen), object validation (verIoVerb and verDoVerb), and object action processing (ioVerb, doVerb, or verb.action, as appropriate). execCommand is called like this: errorCode := execCommand(actor, verb, dobj, prep, iobj, flags); The "actor" parameter is the object (usually of class Actor) of the character to perform the action; if the player character is to carry out the command, use Me as the "actor" parameter. The "verb" parameter is the deepverb object of the command to execute. "dobj" is the direct object of the command, and must be a single object (not a list); if you want to execute the same command on a series of direct objects, simply call execCommand in a loop. Use nil for the "dobj" parameter if the command takes no direct object. "prep" is the object (usually of class Prep) for the preposition that introduces the indirect object, or nil if there is no preposition. "iobj" is the indirect object of the command, or nil if the command takes no indirect object. The "flags" parameter lets you control how the parser processes the command. The flags are "bit field" values, which means that you can combine any number of the constants below using the bitwise "or" operator, "|". The constants below are defined in adv.t. EC_HIDE_SUCCESS - if this flag is included, the parser will hide any messages that the command generates when the command completes successfully. The parser considers the command successful if the return code from execCommand is zero (see below). If this flag is not included, and the command is successful, all messages will be displayed. Note that this flag is independent of EC_HIDE_ERROR; whether or not this flag is included has no effect on messages if the command is not successful. EC_HIDE_ERROR - if this flag is included, the parser will hide any messages that the command generates when the command fails. The parser considers the command to have failed when the return code from execCommand is non-zero (see below). If this flag is not included, and the command fails, all messages will be displayed. Note that this flag is independent of EC_HIDE_SUCCESS; whether or not this flag is included has no effect on messages if the command is successful. EC_SKIP_VALIDDO - if this flag is included, the parser skips validation of the direct object. If this flag is not used, the parser uses the normal validation procedure for the direct object. You can use this flag when you want to bypass the normal validation process and execute the command even when the actor would not normally have access to the direct object. EC_SKIP_VALIDIO - if this flag is included, the parser skips indirect object validation. If this flag isn't used, the parser uses the normal procedure for validating the indirect object. If you want to execute a command silently, so that the player doesn't see the messages the command would normally generate, you should specify both EC_HIDE_SUCCESS and EC_HIDE_ERROR: err := execCommand(actor, takeVerb, ball, nil, nil, EC_HIDE_SUCCESS | EC_HIDE_ERROR); In some cases, you may want to show messages only in case of error. This is particularly useful when you're executing an implied command, such as opening a door in the course of moving to a new room, because you'll usually show a message of some kind noting the implied action. In this situation, you can use EC_HIDE_SUCCESS to suppress the normal confirmation message you'd receive on success, but still show any errors that occur: "(Opening the door)\n"; err := execCommand(actor, openVerb, steelDoor, nil, nil, EC_HIDE_SUCCESS); if (err = EC_SUCCESS) { // successfully opened the door, so proceed with the movement... } All of the parameters to execCommand after "verb" can be omitted, in which case dobj, iobj, and prep will default to nil, and flags will default to 0 (zero). In addition, you can supply a 'flags' value but omit one or more of 'dobj', 'prep', and 'iobj', in which case the interpreter will use nil as the default value for the omitted object arguments. execCommand returns an error code indicating the parser's results. A return value of zero indicates that the command was processed without any parser errors. A return value of zero does not always mean that the command did what you wanted -- it merely indicates that all of the checks succeeded, including xobjCheck, xobjGen, roomAction, actorAction, verDoVerb, and verIoVerb, and that no "exit" or "abort" statement was executed. In some cases, though, the action, doVerb, or ioVerb method will make further checks and generate an error; in these cases, execCommand will return zero even though the command failed. You may therefore want to make an extra check to ensure that whatever state change you were attempting to accomplish actually occurred. This function can return the following values (the constants are defined in adv.t): EC_SUCCESS success - the doVerb or ioVerb method was successfully invoked, and no "exit" or "abort" statement was executed. EC_EXIT an "exit" statement was executed. This usually means that a roomAction, actorAction, xobjCheck, or xobjGen method disallowed the command. EC_ABORT an "abort" statement was executed. EC_INVAL_SYNTAX sentence structure is invalid. This indicates that the combination of verb, objects, and preposition does not form a valid command in the game. The parser does not display any error message in this case; this indicates an error in your source code, since you're attempting to execute a verb pattern that your game doesn't define. EC_VERDO_FAILED verDoVerb failed. The direct object's verification method displayed some text, indicating that verification failed. EC_VERIO_FAILED verIoVerb failed. The indirect object's verification method displayed text. EC_NO_VERDO no verDoVerb method is defined for the direct object. This is almost equivalent to EC_VERDO_FAILED, but indicates that a default parser message was displayed, because the object had no defined or inherited handler of its own. EC_NO_VERIO no verIoVerb method is defined for the indirect object. The parser displays a default message. EC_INVAL_DOBJ direct object validation failed. This indicates that that direct object isn't accessible for the verb; the parser will display the usual message (via the cantReach mechanism) before returning. EC_INVAL_IOBJ indirect object validation failed. The indirect object is not accessible for the verb; the parser will display the usual message before returning. Note that the parser does not check to see if the actor is present in the same room with the current actor, or perform any other addressibility validation for the actor. This allows you to use execCommand to script non-player character actions to be carried out autonomously, without regard to whether the player could have issued the same commands directly. The current actor, for the purposes of format string ("%xxx%") evaluation, is set to the "actor" parameter specified in the call. execCommand does not invoke any daemons or fuses. The recursive command is considered part of the current turn. You should not use execCommand in verification methods (verIoVerb or verDoVerb), because execCommand invokes complete commands, which may make changes in game state. Note that changes in game state will occur regardless of EC_HIDE_SUCCESS or EC_HIDE_ERROR - these flags merely hide the messages the command produces, but don't prevent the command from carrying out its other actions. One good use for execCommand is to treat different phrasings of a command the same way. For example, suppose you had a can of spray paint in your game. You might want to allow players to paint things with the spray paint using commands like "spray on " and "spray with ." To make these commands equivalent, you traditionally would have coded the verIoSprayOn, verDoSprayOn, ioSprayOn, and doSprayOn routines appropriately, then essentially duplicated the code in the SprayWith equivalents. Alternatively, you could have set up the SprayWith routines to call the SprayOn routines (or vice versa); this kind of indirection is tricky, though, because of the TADS parser's assymetrical handling of direct and indirect object verification -- note that the direct and indirect objects reverse roles between the two phrasings. This kind of redirection is easy using execCommand, though. First, choose a "canonical" phrasing -- this is the phrasing where you'll always implement your handlers for the command. Let's choose "spray on " as the canonical phrasing. We now set up command handlers for our canonical phrasing just as we would for any other command: we'd write verIoSprayOn and ioSprayOn methods for objects that we want to allow as targets for spraying, and we'd write verDoSprayOn methods for objects that can do the spraying. For example, in the sprayPaintCan object, we'd write a verDoSprayOn handler to allow spraying the paint on things: sprayPaintCan: item // put your sdesc, ldesc, vocabulary, etc. here verDoSprayOn(actor, iobj) = { } ; We'd proceed with the normal implementation of "spray on " until that worked correctly. Once the canonical phrasing worked, we'd set up the redirection. Rather than setting up a complicated series of method-by-method redirections, we can simply allow any "spray with " command to proceed all the way to the ioSprayWith handler, then redirect the entire command at that point. Since we want to redirect the command for every pair of objects, we can put all of the handlers in the basic "thing" object: modify thing /* allow ANY "spray with " command */ verIoSprayWith(actor) = { } verDoSprayWith(actor, iobj) = { } /* re-cast "spray with " as "spray on */ ioSprayWith(actor, dobj) = { execCommand(actor, sprayVerb, self, onPrep, dobj); } ; That's all that we need to do -- because execCommand will run through the entire parsing sequence for the new phrasing, we don't need to worry about doing any verification for the non-canonical phrasing. Note that we must put the execCommand call in ioSprayWith, and not in one of the verXoSprayWith methods -- if we put the call in one of the verification methods, we could execute the recursive command multiple times in silent calls during disambiguation. Note also that we can override the equivalence of "spray on " and "spray with " on an object-by-object basis, if we wanted, by overriding the SprayWith methods in the objects where we wanted different behavior; while the "spray" commands may never need such special handling, other equivalencies might benefit: "put in " and "fill with ," for example, might only be equivalent for liquids and containers of liquids. Another use for execCommand is to perform "implied" commands -- these are commands that the game carries out automatically, without the player specifically typing them in, because they're obviously needed in the course of what the player actually did type. As an example, suppose you want the player to be wearing sunglasses every time they enter a particular room. You could simply check to see if the player is wearing the sunglasses, and forbid travel into the room if not: >north You can't enter the fusion chamber without your sunglasses on. >wear sunglasses You're now wearing the sunglasses. >north This would work, but it's tedious for the player, in that the game tells the player exactly what to type, but still makes the player type it. Some people would still prefer to believe (despite evidence to the contrary) that computers are our servants and not our masters, and tend to balk at this type of laziness on the part of the game. Even more tedious, though, was writing the code traditionally necessary to make this operation automatic. The problem is that you'd have had to write code to make all of the same checks that the parser would normally make to find out if wearing the sunglasses is possible, and also make sure that any side effects are invoked. execCommand makes this kind of operation easy, by allowing you to use exactly the same code that the parser would invoke in order to carry out an explicit command from the player. In effect, this lets you automatically run obviously implied commands, rather than telling the player to run them manually. Here's how we might use this for the sunglasses: outsideChamber: room // normal sdesc/ldesc stuff north = { /* if the sunglasses aren't being worn, try putting them on */ if (sunglasses.isIn(parserGetObj(PO_ACTOR)) && !sunglasses.isworn) { /* * The sunglasses are here but not worn - put them on. * Tell the player what we're doing, then execute a "wear" * command recursively. Note that we use EC_HIDE_SUCCESS * in the execCommand call to suppress the normal success * confirmation message - we only want a message if the * player can't wear the sunglasses for some reason. */ "(First wearing the sunglasses)\n"; if (execCommand(parserGetObj(PO_ACTOR), wearVerb, sunglasses, nil, nil, EC_HIDE_SUCCESS) != 0) { /* * that failed - since execCommand showed error messages, * we have already explained what went wrong, so simply * return nil to disallow the travel */ return nil; } } /* if they're not wearing eye protection, don't allow entry */ if (!(sunglasses.isIn(parserGetObj(PO_ACTOR)) && sunglasses.isworn)) { /* explain the problem */ "%You% venture%s% a few steps, but the light in the chamber is so intense that %you're% forced to retreat. %You%'ll need some sort of eye protection to enter. "; /* don't allow travel */ return nil; } /* the sunglasses are deployed - we're good to go */ return fusionChamber; } ; Note that this example uses the new parserGetObj() built-in function (described below), rather than parserGetMe(), to determine which actor is performing the travel. In addition, the messages all use format strings (such as "%You%"). These two elements ensure that the travel method can be used for travel by the player, but also can be used for travel by a non-player character; this is especially important if you plan to use execCommand() to script NPC actions, since the current actor during recursive commands sent to an NPC would reflect the NPC object, rather than the player character ("Me") object. - A new action method gives the game program a chance to perform special handling on a command after the objects involved have been resolved, but before any of the usual parser processing begins. This new hook is similar to preparse() and preparseCmd(), but takes as parameters the objects involved in the command rather than the original text. The new hook is a method named verbAction. This new method is similar to the roomAction and actorAction methods, but gives the deepverb object a chance to act. The parser calls this method like this: verb.verbAction(actor, dobj, prep, iobj) 'actor' is the object representing the actor to whom the command is addressed; 'dobj' is the direct object, or nil if there is no direct object; 'prep' is the object for the preposition introducing the indirect object; and 'iobj' is the indirect object, or nil if there is no indirect object. This method returns no value. If this method wants to cancel execution of the command, it should use the "exit" statement (to continue execution of any remaining commands on the command line, but terminate processing of the current command) or the "abort" statement (to terminate processing of the current command line entirely). If can also use "exitobj" to terminate processing of the current object only, and continue execution with the next object of a multi-object command. The parser calls verbAction just before calling actorAction. The verbAction method can be used in conjunction with the new execCommand built-in function to substitute one sentence pattern for another, without the usual complexity of mapping several individual verification and action methods. For example, if you wanted to process all "fill x with y" commands as "put y in x" commands, you could put this in your fillVerb definition: fillVerb: deepverb verb = 'fill' sdesc = "fill" ioAction(withPrep) = 'FillWith' verbAction(actor, dobj, prep, iobj) = { /* check for fill-with syntax */ if (prep = withPrep) { /* handle "fill x with y" exactly like "put y in x" */ execCommand(actor, putVerb, iobj, inPrep, dobj); /* we're done with the command - do not process it further */ exitobj; } } ; - A new parser hook lets the game take control whenever the parser encounters an unknown verb, or unknown sentence syntax. The new hook is a function called parseUnknownVerb, which is defined like this: parseUnknownVerb: function(actor, wordlist, typelist, errnum) 'actor' is the current actor object. The 'wordlist' parameter is a list with the strings of the words in the command, in the same format as the list that is passed to preparseCmd. The 'errnum' parameter is the parser error number for the condition that caused the call to parseUnknownVerb; this is the same error number that is passed to parseError (and related functions). The 'typelist' argument is a list of the types of the words in the 'wordlist' parameter. Each element of 'typelist' gives the word type of the corresponding element of 'wordlist' (so typelist[3] gives the type of the word in wordlist[3], for example). Each type is a number, which can contain any number of the values below combined with the bitwise OR operator ("|"). To test for a particular type, use an expression like this: ((typelist[3] & PRSTYP_NOUN) != 0). The type values, defined in adv.t, are: PRSTYP_ARTICLE - the word is defined as an article PRSTYP_ADJ - adjective PRSTYP_NOUN - noun PRSTYP_PLURAL - plural PRSTYP_PREP - preposition PRSTYP_VERB - verb PRSTYP_SPEC - special word (".", "of", "and", etc.) PRSTYP_UNKNOWN - the word is not in the dictionary This function can return, true, nil, or a number, or it can use "abort" to abort the command. Returning true indicates that the function has successfully handled the entire command itself; the parser does not display any error messages, it executes fuses and daemons as normal, and it proceeds to continue parsing any remaining text on the command line (after a period or "then"). Returning a number (greater than zero) indicates success, just as true does, but also indicates that the function parsed only the words before the returned index, and that the remaining words (starting with the word at the index value returned) are to be considered an additional command. The parser will run fuses and daemons as normal, and then will resume its normal parsing, starting with the word at the index given by the return value. You can use this if you find "and" following a noun phrase that you parse, or if for any other reason you find that another sentence follows and should be parsed separately. For example, if you succesfully parse the first three words of the list, you should return the value 4 to indicate that you want the parser to apply the default parsing starting with the fourth word in the list. Returning nil indicates that the function has failed to handle the command, and wants the parser to display the default error message. The parser will display the message in the normal fashion, using parseErrorParam or parseError as appropriate, and will abort the command. No fuses or daemons will be executed, and any remaining text on the command line will be discarded. If this function uses "abort" to end the command, the parser will not execute any fuses or daemons, and it will ignore any remaining text on the command line. The difference between returning nil and executing an "abort" statement is that the parser will display the default message when this function returns nil, but will not display anything if the function uses "abort". The parseUnknownVerb function is currently called with the following error codes: 17 There's no verb in that sentence! 18 I don't understand that sentence. 19 There are words after your command I couldn't use. 20 I don't know how to use the word "%s" like that. 21 There appear to be extra words after your command. 23 internal error: verb has no action, doAction, or ioAction 24 I don't recognize that sentence. Error code 17 indicates that the first word in the sentence is not defined as a verb (which may mean that the word is entirely uknown, or that it's defined as another part of speech but not as a verb). 18 means that a noun phrase was not formed properly, or that the combination of the verb and verb preposition ("pick up," for example) is not defined. 19 means that a preposition occurred at the end of a sentence, but it could not be combined with the verb. 20 indicates that the word separating the indirect object is not defined as a preposition. 21 means that another word follows what the parser thinks should be the last word in the sentence (for example, the sentence ends with two prepositions). 23 means that the deepverb object has no defined templates (action, doAction, or ioAction). 24 indicates that too many objects were used with the sentence: a direct object is present, but the deepverb doesn't have a doAction, or an indirect object is present, but the deepverb doesn't have an ioAction. The purpose of this function is to let you defined your own parsing for commands outside of the bounds of the built-in parser. Although this function is similar to preparseCmd, it differs in that parseUnknownVerb runs only when the parser can't handle a command directly, which means that parseUnknownVerb doesn't have to decide whether or not to pass the command to the parser. In addition, parseUnknownVerb integrates into the turn-handling mechanism, in that it can control fuse and daemon execution as well as the handling of remaining text on the command line. - A new parser hook lets the game program parse noun phrases and perform the initial resolution to possible matching objects. The parser now calls a game-defined function named parseNounPhrase, which is defined like this: parseNounPhrase: function(wordlist, typelist, current_index, complain_on_no_match, is_actor_check) The parameter 'wordlist' is a list of strings, where each string is a token in the player's command. This is the same type of list that preparseCmd receives. 'typelist' is a list of word types for the tokens in the list. The types are bit flag values, so each element may have multiple types combined with OR. To test to see if a particular type flag is set, use the bitwise AND operator, "&"; for example, to test the second element to determine if it's a noun, use this expresion: ((typelist[2] & PRSTYP_NOUN) != 0) The type flag values, defined in adv.t, are: PRSTYP_ARTICLE - the word is defined as an article PRSTYP_ADJ - adjective PRSTYP_NOUN - noun PRSTYP_PLURAL - plural PRSTYP_PREP - preposition PRSTYP_VERB - verb PRSTYP_SPEC - special word (".", "of", "and", etc.) PRSTYP_UNKNOWN - the word is not in the dictionary 'current_index' is the index in the word list of the start of the noun phrase. This function can look at the previous words in the list if desired, but the parser has already determined that words before 'current_index' are part of the verb or of another part of the command. This function should start parsing at 'current_index'. 'complain_on_no_match' is a boolean value (true or nil) indicating whether the function should display an error message if the noun phrase has no matching objects. If this parameter is true, you should display an appropriate message on this type of error; otherwise, you should not display any message in this case. You should always display an error if the noun phrase is not syntactically correct; the 'complain_on_no_match' parameter applies only to error messages for syntactically correct noun phrases that don't refer to any objects. 'is_actor_check' is a boolean value indicating whether the function is being called to check for an actor noun phrase. When this is true, the function should not allow syntax that is obviously inappropriate for an actor, such as the word "all," or a string or number; in such cases, the function should simply return an empty object list to indicate that no valid actor is present. This function can do one of four things: it can parse the noun phrase, and return a list of matching objects; it can determine that no noun phrase is present; it can indicate that a noun phrase is present but contains a syntax error; or it can let the parser perform the default noun phrase parsing. If this function parses a noun phrase successfully, it should return a list. The first element of the list must be a number, which is the index of the next token in the word list after the noun phrase. For example, if 'current_index' is 3 when the function is called, and the noun phrase consists of one word, the first element of the returned list should be 4. This tells the parser where it should resume parsing. The remaining elements of the list are pairs of elements; the first of each pair is a game object matching the noun phrase, and the second is a number giving flags for the object. At this point, it's not necessary to determine whether or not the objects are accessible, reachable, visible, or anything else; the parser will disambiguate the list later, when it knows more about the sentence structure. For now, the routine can simply return a list of all of the objects that match the vocabulary words. Multiple flag values can be combined with the bitwise OR operator, "|". The flags, defined in adv.t, are: PRSFLG_ALL - the entry is for the word "all" or equivalent PRSFLG_EXCEPT - the entry is excluded from the "all" list PRSFLG_IT - the entry matched the pronoun "it" PRSFLG_THEM - the entry matched the pronoun "them" PRSFLG_HIM - the entry matched the pronoun "him" PRSFLG_HER - the entry matched the pronoun "her" PRSFLG_NUM - the entry is a number PRSFLG_STR - the entry is a string PRSFLG_PLURAL - the entry matched a plural usage PRSFLG_COUNT - the entry has a numeric count as the first word PRSFLG_ANY - the entry was qualified with "any" PRSFLG_UNKNOWN - the entry contains an unknown word PRSFLG_ENDADJ - the entry ends with an adjective PRSFLG_TRUNC - the entry uses a truncated word Some examples might be helpful, since the return value is complicated. For "all" and for the pronouns (it, him, her, them), you should return a list containing nil as the object, and the appropriate flag value. For example, if the noun phrase is simply the word "everything", you would return this (assuming that the index of the word "everything" was 2): [3 nil PRSFLG_ALL] Similarly, if the noun phrase was simply "her", you would return: [3 nil PRSFLG_HER] The construction "all except" is also special. You would return a list whose first entries are nil and PRSFLG_ALL, just as though you were parsing a simple "all" phrase, but then you'd add entries for all of the items in the "except" list, with each additional entry's flag including PRSFLG_EXCEPT. For example, if the player typed "take all except book and candle", you might return something like this: [3 nil PRSFLG_ALL book PRSFLG_EXCEPT candle PRSFLG_EXCEPT] Strings and numbers work the same way: return nil for the object, and set the appropriate flag. For example, if the player typed "type 'hello' on keypad", you'd return this: [3 nil PRSFLG_STR] If you encounter an unknown word in the noun phrase, and you want to let the parser resolve the unknown word using its normal mechanisms, you should return a nil object with the PRSFLG_UNKNOWN flag set: [4 nil PRSFLG_UNKNOWN] If you simply want to return a list of objects that match the noun phrase, it's easy: [4 book 0 candle 0] The parser also lets you omit the flags entirely, if you don't need to include any flags with an object. If an element that follows an object is another object (or nil), the parser will assume that the flag value for the preceding object is zero. So, for the example above, this list is equivalent: [4 book candle] If the function parses a noun phrase successfully, but can find no objects matching the words in the noun phrase (in other words, the words form a valid noun phrase syntactically, but don't actually refer to any object in the game), it should return a list that contains only the index of the next word after the noun phrase. If the function determines that a noun phrase appears to be present, but is not syntactically correct, you should display an error message and return PNP_ERROR. You should not display an error if it doesn't look like a noun phrase is present at all; instead, you should simply return a list consisting of the original 'current_index' value to indicate that you didn't parse any words at all. You should only display an error and return PNP_ERROR if you determine that a noun phrase is actually present, but is syntactically incorrect. If your function determines that it doesn't want to parse the noun phrase after all, it should simply return PNP_USE_DEFAULT. This tells the parser to proceed with the default parsing for the noun phrase. The default noun phrase parser (built in to the TADS interpreter) does not attempt to resolve or disambiguate objects at this stage. Instead, it simply creates a list of all of the objects that match every word in the noun phrase. The reason that the parser doesn't try to resolve the objects at this stage is that the parser doesn't have enough information when this routine is called. So, the parser merely determines the syntactic structure of the noun phrase, and ensures that at least one object in the game can match all of the words; later, after the parser has fully analyzed the sentence structure and knows the verb, prepositions, and number of objects, the parser resolves and disambiguates the noun phrase. If you write an implementation of this function, keep this design in mind. In most cases, you will not want to write a function that completely replaces the built-in noun phrase parser. Instead, you'll probably want to check for special cases. When you see a special case, you should perform your parsing and return an appropriate object list; when you don't see a special case, you should simply return PNP_USE_DEFAULT to use the default parsing routine. - A new parser hook allows the game program to control the disambiguation process, wherein noun phrases are resolved to specific game objects. The parser calls two new methods: dismabigDobj, to disambiguate direct objects; and disambigIobj, for indirect objects. These new methods are defined on a deepverb object. The parser calls disambigDobj as follows: verb.disambigDobj(actor, prep, iobj, verprop, wordlist, objlist, flaglist, numWanted, isAmbig, silent); The parser calls this routine, if defined, most times it resolves a noun phrase to a concrete list of objects, whether or not the phrase is ambiguous. This allows you to perform special resolution on specific noun phrases or for specific verbs, even when the parser wouldn't normally think the phrases require disambiguation. The 'actor' parameter is the actor involved in the command. 'prep' is the preposition object associated with the word that introduces the indirect object, if present; if there is no preposition, 'prep' will be nil. 'iobj' is the indirect object, if available; if there is no indirect object, or the indirect object has not yet been resolved when disambigDobj is invoked, 'iobj' will be nil. 'verprop' is the property address of the verification method (verDoVerb) defined for the direct object for the verb; this parameter is included because a single verb object could define several verification methods that very by preposition ("put x in y" usually has a different verification method than "put x on y"). 'wordlist' is a list of strings giving the tokens of the player's command that make up the noun phrase. This is the same type of list that preparseCmd receives. 'objlist' is a list of the objects that the parser found with dictionary entries matching all of the words in the word list. 'flaglist' is a list of numbers giving flags for the corresponding 'objlist' entries; for example, flaglist[3] gives the flags associated with objlist[3]. The flag values for 'flaglist' are defined in adv.t. Thee flags are bit-field values, so multiple flags can be combined with OR into a single value. To test if a particular flag is set, use the bitwise AND operator, "&"; for example, to test the second element to see if the PLURAL flag is set, use this expression: ((flaglist[2] & DISAMBIG_PLURAL) != 0) The flags are: PRSFLG_COUNT - the object matched with a numbered count. For example, if the noun phrase is "3 gold coins," objlist will contain one or more objects matching the plural phrase "gold coins," and the VOCS_COUNT flag will be set for each object to indicate that a count is present. In these cases, the first element of wordlist should always be the string with the user's number (the string '3' in the example). PRSFLG_PLURAL - the object matched a plural usage. PRSFLG_ANY - the noun phrase started with "any" PRSFLG_ENDADJ - the object matched with an adjective at the end of the noun phrase. For example, suppose the noun phrase is "letter", and the game defines parchmentLetter with noun = 'letter', and defines letterOpener with adjective = 'letter'. In this case, objlist would contain both parchmentLetter and letterOpener, and the flaglist entry corresponding to letterOpener would have the PRSFLG_ENDADJ flag set. This flag allows the disambiguator to select in favor of the noun interpretation in case of ambiguity, to avoid having to ask a stupid disambiguation question ("which letter do you mean, the parchment letter, or the letter opener?" should clearly not be asked). PRSFLG_TRUNC - the object matched with one or more truncated words. This will be set when a word in the player's noun phrase matches the leading substring of a dictionary word, and is at least six characters long, but doesn't match the entire dictionary word. For example, "flashlig" matches "flashlight" because "flashlig" is at least six characters long and matches "flashlight" in its first eight characters (i.e., the length of "flashlig"), but the parser will flag the word with PRSFLG_TRUNC to indicate that it wasn't an exact match. 'numWanted' is the number of objects that the parser wants in the resolved list. For a definite singular noun phrase ("take the box"), this will be 1. For a plural noun phrase, this will be the number of objects in the proposed resolution list (in the 'objlist' parameter). When the player specifies a count ("take 3 boxes"), this will be the give number. For an "any" phrase ("take any box"), this will be 1. You don't have to obey the 'numWanted' parameter, but this information may be helpful in some cases. 'isAmbig' is true if the parser thinks the noun phrase is ambiguous, nil if not. For example, if the player specified a singular definite noun phrase, but the parser found two matching objects, 'isAmbig' will be true. You can always deduce whether or not the list is ambiguous by examining the 'numWanted' value and all of the object flags, but doing so is complicated, so the parser provides 'isAmbig' for your convenience. If your disambigDobj or disambigIobj is interested only in resolving ambiguity, as opposed to performing special noun phrase resolution, you can simply return DISAMBIG_CONTINUE immediately if 'isAmbig' is nil. 'silent' specifies whether the disambiguation is in interactive or non-interactive mode. If 'silent' is true, it means that you should not display any messages or ask the player to help resolve any ambiguity. If 'silent' is nil, you can display messages or prompt for more information if you wish. The disambigIobj method is essentially the same: verb.disambigIobj(actor, prep, dobj, verprop, wordlist, objlist, flaglist, numWanted, isAmbig, silent); The only difference from disambigDobj is that disambigIobj receives the direct object in the 'dobj' parameter. Note that the direct object is not normally available during indirect object disambiguation, so 'dobj' will usually be nil. The only time the direct object will be available will be for verb templates with the [disambigDobjFirst] flag. For verbs without this flag, the indirect object is resolved before the direct object, hence the direct object is not yet known. The parser's built-in disambiguation routine calls these methods after it has done everything it can, short of asking the player, to resolve and disambiguate a noun list. The parser will apply the normal validation checks (validDo, validDoList, etc.) to the objects, eliminating any that don't pass; and it will apply the silent verification checks (verDoVerb, verIoVerb) as well. The parser will next call disambigDobj or disambigIobj, if the verb object defines it. These methods can return a status code from the list below, or they can return a list (see below). The status codes (defined in adv.t) are: DISAMBIG_CONTINUE - continue through the remainder of the disambiguation process as normal. DISAMBIG_ERROR - abort the current command entirely. This can be used when the method encounters an error and displays an error message to the player. The parser will simply terminate the current command. Note that the parser does not display an error of its own, so the method must display an appropriate message before returning this status code. DISAMBIG_PROMPTED - continue through the process as normal, but do not show a prompt for an interactive response ("which foo do you mean..."), because the disambigXobj function already displayed an appropriate prompt of its own. This allows the disambigXobj function to display a customized prompt (using more information than the traditional parseDisambig function has available), but still use the parser's default response reader and parser. DISAMBIG_PARSE_RESP - this indicates that your code has asked the player a question and read a response (via the input() built-in function, for example), but that you want to use the default response parser in the normal disambiguation mechanism to parse the response. Do not return this as a raw status code; instead, return a list containing this value as the first element, and the string to be parsed as the second element. DISAMBIG_DONE - consider the object list fully resolved. This skips any additional checking the disambiguator would normally perform and uses the current list as-is. This should generally never be returned directly, but is used when returning a list of objects. The method can also return a list, which contains objects that replace the original input list (in the 'objlist' parameter). The first element of the returned list is a status code, from the list above. Subsequent elements are the objects to use as the result of disambiguation. You can optionally specify a flag value for each object value. To specify a flag for an object, simply place the flag value after the object in the list. return [DISAMBIG_DONE redBox PRSFLG_PLURAL blueBox PRSFLG_PLURAL]; Each flag value pertains to the object immediately preceding it. If you omit a flag value, zero is assumed. So, the following two return lists are equivalent: [DISAMBIG_CONTINUE redBox 0 blueBox 0] [DISAMBIG_CONTINUE redBox blueBox] You can optionally omit the status code from the list. If the first element of the list is an object, the parser uses DISAMBIG_CONTINUE as the default status code, so it takes the list you returned and proceeds to the next step in the disambiguation process. Note that the parser doesn't call disambigDobj or disambigIobj for certain cases: resolving pronouns (it, her, him, them); strings; numbers that don't map to vocabulary words, but simply resolve to numObj; and "all" phrases. However, the parser does call these routines to resolve the items in an "except" phrase. - A new built-in function, parserGetObj, lets the game program learn the objects involved in the current command. This function takes a single argument, which indicates which object you want, using the following constants (defined in adv.t): PO_ACTOR - the current command's actor PO_VERB - the deepverb object for the command's verb PO_DOBJ - the direct object PO_PREP - the preposition object introducing the indirect object PO_IOBJ - the indirect object The return value is an object, or nil if there is no such object for the current command. For example, a command with no indirect object will return nil for PO_PREP and PO_IOBJ. Here's an example of using parserGetObj to get the direct object of the current command: local obj := parserGetObj(PO_DOBJ); parserGetObj returns valid information at any time from (and including) actorAction to (and including) the doVerb or ioVerb routines. You can't use parserGetObj() outside of these bounds; if you do, it will simply return nil. The reason for the limited lifetime of this function is that the parser simply doesn't know the final values for the command objects before actorAction, since it is still busy resolving the words in the command to objects until it's about to call actorAction. Note some particular times when you can't call parserGetObj: in the "init" and "preinit" functions; during object disambiguation (thus during some verDoVerb and verIoVerb calls); during roomCheck; during preparse() and preparseCmd(); and during fuse and daemon execution. Note one important exception to the limited lifetime: the actor can be retrieved at any time after the preparse() function returns. The parser determines the actor very early in the process of interpreting the command, so the actor is available throughout the parsing sequence. parserGetObj returns information on the current command. When a recursive command is in progress (using execCommand), parserGetObj returns information on the recursive command; once execCommand finishes and returns to the code that called it, parserGetObj will once again return information on the enclosing command. - A new built-in function lets the game invoke the parer's tokenizer to obtain a token list for a string of text. You can call this new function like so: tokenList := parserTokenize(commandString); The 'commandString' parameter is any string of text. The tokenizer will scan the string and break it up into tokens, and return a list of token strings. The token strings follow the same rules as the token list passed to the preparseCmd() function. If the command string contains any invalid characters (such as punctuation marks that the tokenizer doesn't accept), it will return nil, but it won't display any error messages. - A new built-in function lets you obtain a list of token types given a list of tokens. You can use this function to get the types of the tokens in a list returned by parserTokenize(), for example. Call the new function like this: typeList := parserGetTokTypes(tokenList); The result is a list of numbers. Each element of the result list gives the type of the corresponding element of the token list (typeList[3] contains the type of the token in tokenList[3], for example). The types in the result list are combinations of the following values, defined in adv.t: PRSTYP_ARTICLE - article (a, an, the) PRSTYP_ADJ - adjective PRSTYP_NOUN - noun PRSTYP_PREP - preposition PRSTYP_VERB - verb PRSTYP_SPEC - special word PRSTYP_PLURAL - plural PRSTYP_UNKNOWN - unknown word These type codes are bit-field values, so they can be combined with the bitwise OR operator ("|"). For example, a token that appears in the dictionary as both a noun and an adjective will have a token type value of (PRSTYP_ADJ | PRSTYP_NOUN). Because more than one PRSTYP_xxx value can be combined into a type code, you must use the bitwise AND operator ("&") to check a type code for a specific PRSTYP_xxx value. For example, if you want to check a token to see if has "noun" among its types, you'd write this: ((typeList[3] & PRSTYP_NOUN) != 0) - A new built-in function lets you perform a parser dictionary look-up to obtain the list of objects that define a set of vocabulary words. This function can be used to perform your own noun-phrase parsing. Call the new function like this: objList := parserDictLookup(tokenList, typeList); The 'tokenList' parameter is a list of the token strings you want to look up in the dictionary; this list uses the same format as the list returned by parserTokenize(), so you can use the result of parserTokenize() as input to parserDictLookup(). The 'typeList' parameter is a list of token types. Each entry in 'typeList' gives the token type of the corresponding entry in 'tokenList'. This list uses the same PRSTYP_xxx codes returned by parserGetTokTypes(), but each entry in the type list should have only a single PRSTYP_xxx code (a type code in this list should not be a combination of more than one PRSTYP_xxx code). Because the 'typeList' entries must contain individual PRSTYP_xxx type codes, rather than combinations of type codes, you should generally not pass the result of parserGetTokTypes() directly to to parserDictLookup(). Instead, you need to determine how you want to interpret the words in the token list by choosing a single token type for each entry. How you determine each single type is up to you. If you're parsing a noun phrase, for example, you might decide that all words in the noun phrase except the last must be adjectives, and the last must be a noun. The assignment of token types will depend on the type of parsing you're doing, and the syntax rules that you decide to implement for the type of input you're parsing. The return value of parserDictLookup() is a list of all of the game objects that match all of the vocabulary words, with the given types. If there are no objects that match all of the words, the result is an empty list. Verbs that use combining prepositions (such as "pick up" or "go north") use a special form of the token string. To look up a combining, two-word verb, use a token string that contains both words, separated by a space. parserTokenize() will never return such a string, because it will always break up the tokens according to word separators, so you must re-combine such tokens yourself. For example, to look up the deepverb object matching "pick up" as a verb, you could write this: objList := parserDictLookup(['pick up'], [PRSTYP_VERB]); Note that parserDictLookup() simply looks up words in the dictionary. This function doesn't perform any disambiguation, access checking, visibility checking, or any other validation on the objects. - A new built-in function allows the game to invoke the parser's internal function to parse a noun list. This can be used in conjunction with the parseUnknownVerb to allow the function to interpret part of the word list as a noun list. Your game program can call the function like this: ret := parseNounList(wordlist, typelist, startingIndex, complainOnNoMatch, multi, checkActor); 'wordlist' is a list of the strings making up the command. 'typelist' is a list of word types; each entry in 'typelist' is a number giving the type of the corresponding word in 'wordlist'. The values in 'wordlist' have the same meaning as the 'typelist' parameter to the parseUnknownVerb function: PRSTYP_ARTICLE - the word is defined as an article PRSTYP_ADJ - adjective PRSTYP_NOUN - noun PRSTYP_PLURAL - plural PRSTYP_PREP - preposition PRSTYP_VERB - verb PRSTYP_SPEC - special word (".", "of", "and", etc.) PRSTYP_UNKNOWN - the word is not in the dictionary 'startingIndex' is the index in 'wordlist' and 'typelist' of the first word to parse; the function will ignore all of the words before this index. This allows you to parse a portion of a word list in your own code, and start parsing a noun phrase that follows the portion you parsed. Set 'complainOnNoMatch' to true to make the function display an error message if it parses a syntactically valid noun phrase, but there are no objects in the game that match the noun phrase; set this to nil if you want to suppress this message. Note that the function will display any syntax error messages regardless of this setting. If you want to suppress all messages, you can use outhide() or outcapture() to hide any error messages displayed. 'multi' specifies whether you want the function to parse multiple noun phrases (separated by "and", for example) or just a single noun phrase. If 'multi' is true, the function will parse any number of noun phrases; if 'multi' is nil, the function will only parse a single phrase, stopping if it reaches "and" or equivalent. 'checkActor' specifies if you want to perform an actor check. If this is true, the function will reject "all", quoted strings, and phrases involving "both" or "any"; it will only parse a single noun phrase (regardless of the setting of 'multi'); and it will not display an error if the noun phrase cannot be matched. The parser uses this mode internally to check the beginning of a command to determine if the command is directed to an actor, and this is probably the only context in which 'checkActor' should ever need to be true. In most cases, you should set 'checkActor' to nil. Note that you should not use true just because noun phrase may happen to contain an actor or is expected to contain an actor; you should only use true when you want the special error-handling behavior. Note also that using true for 'checkActor' does not cause the parser to reject noun phrases that refer to non-actor objects; this flag simply controls the error-handling behavior and does not affect what objects can be matched. If the parser encounters a syntax error, the function returns nil. This indicates that the function displayed an error message (regardless of the value of 'complainOnNoMatch'), and that the words do not form a syntactically-correct noun phrase. If the parser finds a syntactically valid noun phrase, but finds no objects that match the noun phrase, it returns a list containing a single number. The number is the index of the next word in 'wordlist' following the noun phrase. For example, suppose we have this word list: ['take' 'red' 'ball' 'with' 'hook'] Suppose that we start parsing at index 2 ('red'), and that 'red' and 'ball' are in the dictionary as adjective and noun, respectively. The parser will parse the noun phrase "red ball", consuming two words from the word list. Now, suppose that there are no objects in the game matching both vocabulary words (i.e., there's no red ball in the game). The parser will indicate that a syntactically valid noun phrase is present, but that no objects match the noun phrase, by returning this: [4] The number is the index of the next word after the noun phrase (in this example, 'with'). If the parser finds a syntactically valid noun phrase, and finds one or more matching objects, it returns a list giving the matching objects. The first element of the list, as above, is the index in the word array of the next word after the noun phrase. Each additional element is a sublist. Each sublist gives information on one noun phrase. If 'multi' is nil, there can be at most one sublist. If 'multi' is true, there will be one sublist per noun phrase (each noun phrase is separated from the previous one by "and" or equivalent). The first element of the sublist is the index in the word array of the first word of the noun phrase, and the second element is the index of the last word of the noun phrase; the noun phrase is formed by the words in the array from the first index to the last index, inclusive, so the last index will always be greater than or equal to the first index. After these two elements, the sublist contains pairs of entries: a matching object, and flags associated with the matching object. Each matching object is a game object that matches all of the vocabulary words in the noun phrase. The flags value associated with each matching object is a combination of any number of the PRSFLG_xxx values described with the parseNounPhrase function. These flag values can be combined with the bitwise OR operator ("|"), so to test for a particular flag value, use the bitwise AND operator: ((flag & PRSFLG_EXCEPT) != 0). Since the return list is rather complicated, some examples might be helpful. Suppose that we start with this word list: ['take' 'knife' ',' 'cardboard' 'box'] Suppose also that we use 2 as the starting index (because we want to start at the word 'knife'), and that 'knife', 'cardboard' and 'box' are defined words in the game. Now, suppose we have the following game objects defined: rustyKnife: item noun='knife' adjective='rusty'; sharpKnife: item noun='knife' adjective='sharp'; dagger: item noun='dagger' 'knife'; box: item noun='box' adjective='cardboard'; Given all of this, the return list would look like this: [6 [2 2 rustyKnife 0 sharpKnife 0] [4 5 box 0]] The first element indicates that the next word after the noun list is element 6; since the list has only five elements, this simply means that the noun list runs all the way to the end of the word list. The next two elements are the sublists, one per noun phrase: [2 2 rustyKnife 0 sharpKnife 0] [4 5 box 0] The first sublist specifies a noun phrase that runs from word 2 to word 2, inclusive, hence 'knife'. The remaining pairs of elements in the list tell us that the matching objects are rustyKnife (with flags of 0) and sharpKnife (also with flags of 0). The second sublist specifies a noun phrase that runs from word 4 to word 5, inclusive, hence 'cardboard box'. The matching object for this phrase is box (with flags 0). To interpret this return value, consider this code: if (ret = nil) { /* the noun phrase had a syntax error; give up */ return; // or whatever we want to do in case of error } "Next word index = <>\b"; if (length(ret) = 1) { /* valid noun phrase, but no matching objects */ "I don't see that here."; return; } /* handle each sublist individually */ for (i := 2 ; i <= length(ret) ; ++i) { local sub; local firstWord, lastWord; local j; /* get the current sublist */ sub := ret[i]; /* get the first and last word indices for this noun phrase */ firstWord := sub[1]; lastWord := sub[2]; /* display the word list (or whatever - this is just an example) */ "\bNoun phrase #<> is: '"; for (j := firstWord ; j <= lastWord ; ++j) { say(wordlist[j]); if (j != lastWord) say(' '); } "'\n"; /* scan the objects in the list - each object takes two elements */ for (j := 3 ; j <= length(sub) ; j += 2) { /* display this object and its flags */ "matching object = <>, flags = <>\n"; } } Note that in many cases you won't care about interpreting this list directly; instead, you'll simply want to pass the list to the parserDisambig() built-in function for resolution and disambiguation. The return list is in the same format required for input to that function. This function directly invokes the parser's noun list parser, which is exactly the same code the parser uses while parsing a player's command line. The noun list parser will in turn invoke your parseNounPhrase() function, if you've defined such a function in your game. So, you should be careful not to set up an infinite recursion by calling this function from your parseNounPhrase() function. - A new built-in function lets you access the parser's object resolution and disambiguation subsystem programmatically. You can use the object resolver in conjunction with parseNounList() or with your own noun-list parser to implement your own command parsing system. In the standard TADS parser, object resolution occurs after the parser has finished parsing the syntax structure of a sentence, and thus knows the verb, all of the noun phrases, and connecting prepositions. Once all of this information is known, the parser can intelligently determine the objects to which each noun phrase refers. As a result of this design, the object resolver requires parameters that specify the other aspects of the sentence structure. The object resolution function is called like this: resultList := parserResolveObjects(actor, verb, prep, otherobj, usageType, verprop, tokenList, objList, silent); The 'actor' parameter is the actor object for the command for which the objects are to be resolved. The 'verb' parameter is the deepverb object involved in the command. The 'prep' parameter is the preposition object that introduces the indirect object; if there's no indirect object or no preposition, 'prep' should be nil. 'usageType' specifies the type of object that you're resolving. You should use one of these constants, defined in adv.t, for this parameter: PRO_RESOLVE_DOBJ - direct object PRO_RESOLVE_IOBJ - indirect object PRO_RESOLVE_ACTOR - actor: use this if you're resolving an object for use as an actor to whom the player is directing the command. 'verprop' is the verification method address; this is the address of the verDoVerb for your verb. This must be specified in addition to the deepverb object, because a single deepverb can be associated with multiple verification/action methods (for example, "put x on y" uses a different set of methods from "put x in y", but both are associated with putVerb). For example, for the direct object of "put x in y", you'd specify &verDoPutIn. If you're validating an actor (not a direct or indirect object that happens to be an actor, but rather an actor that the player is addressing and who is to carry out a command), the parser normally uses &verDoTake for 'verprop', rather than the actual verb being executed, because the point is to verify that the player can access the actor, not that the player can perform the command on the actor. "Taking" the actor has reasonably suitable accessibility rules for talking to an actor. You could conceivably define your own verb simply for the purposes of talking to an actor, and then use that verb and its appropriate verification method instead of takeVerb and verDoTake. 'tokenList' is the list of tokens which was parsed to build the input object list. If you obtained the object list from parseNounList(), you should simply use the same token list that you used with parseNounList(). The importance of 'tokenList' is that the token list indices in the object list refer to words in the token list. 'objList' is the input object list. The resolver starts with this list to produce the resolved list. 'objList' is in exactly the same format as the list returned by parseNounList(), so you can use the result of parseNounList() as the 'objList' parameter. If you use your own noun list parser instead, you must prepare a list that uses the same format as the parseNounList() result. 'silent' specifies whether the resolver is interactive or not. If 'silent' is true, the resolver will not display any messages to the player, and will not ask the player to resolve the list in case of ambiguity; instead, the resolver will simply return an error code. If 'silent' is nil, the resolver will display a message if an error occurs, and will ask the user to resolve ambiguity using the traditional interactive process ("Which foo do you mean..."). The return value of this function is always a list. The first element of this list is always a number giving a status code. The status codes are the same values and have the same meanings as the codes passed to parseError() and parseErrorParam(). The status code PRS_SUCCESS (this constant and the PRSERR_xxx constants mentioned below are defined in adv.t) indicates that the resolution was successful. In this case, the remainder of the list simply contains the resolved objects: [PRS_SUCCESS goldCoin shoeBox] PRSERR_AMBIGUOUS indicates that the result list is ambiguous. This code will only be returned if 'silent' is true, because in other cases the resolver will not return until the player resolves any ambiguity interactively, or an error occurs. When this status code is returned, the remainder of the list contains the partially-resolved objects; the resolver will have narrowed down the list as much as possible by including only objects that are accessible to the actor for the purposes of the verb, but the list will still require further disambiguation to obtain the final set of objects. [PRSERR_AMBIGUOUS goldCoin silverCoin shoeBox cardboardBox] PRSERR_DISAMBIG_RETRY indicates that the player entered a new command in response to a disambiguation query. This can only happen when 'silent' is nil, because the parser won't ask the player any questions at all when 'silent' is true. When this status code is returned, the list contains only one additional element, which is a string with the player's new command. If you want to execute the new command, you can use parserReplaceCommand() to abandon the current command and execute the new command instead. [PRSERR_DISAMBIG_RETRY 'go north'] Any other status code indicates an error which caused the resolver to fail. The list will contain no other elements in these cases. Note that this function calls the identical internal parser code that the player command parser normally uses to process a command. The object resolver in some cases calls the disambigDobj and disambigIobj methods defined in the deepverb object. As a result, you should be careful not to call this function from disambigDobj or disambigIobj methods, since doing so could result in infinite recursion. Here's an example that uses several of the new parser functions, including parserResolveObjects(). This function reads a string from the keyboard, tokenizes it, gets the token types, parses the token list as a noun list, and then resolves the noun list to an object. askForObject: function { local str; local toklist, typelist; local objlist; /* get an object */ "Type an object name: "; str := input(); /* tokenize it */ toklist := parserTokenize(str); if (toklist = nil) { "The object name is invalid!"; return nil; } /* get the token types */ typelist := parserGetTokTypes(toklist); /* parse a single noun phrase */ objlist := parseNounList(toklist, typelist, 1, true, nil, nil); if (objlist = nil) return nil; if (length(objlist) = 1) { "You see no such thing. "; return nil; } if (objlist[1] <= length(toklist)) { "There seem to be words after the object name that I can't use. "; return nil; } /* resolve and disambiguate */ objlist := parserResolveObjects(Me, takeVerb, nil, nil, PRO_RESOLVE_DOBJ, &verDoTake, toklist, objlist, nil); if (objlist[1] = PRS_SUCCESS) { /* success! return the objects, which follow the status code */ return cdr(objlist); } else if (objlist[1] = PRSERR_DISAMBIG_RETRY) { /* run the new command, which is in the second element */ parserReplaceCommand(objlist[2]); } else { /* we were in non-silent mode, so the resolver displayed an error */ return nil; } } - A new built-in function, parserReplaceCommand(), allows you to abort the current command and start executing a new command using a given text string. Call the function like this: parserReplaceCommand(commandString); This function doesn't return -- it effectively executes an "abort" statement to terminate the current command. The given command string is entered into the parser's internal buffer, and the system parses and executes the command as though the player had typed the command directly. - A new built-in function, setOutputFilter, allows the game program to intercept and optionally change all display output just before it's formatted for display. setOutputFilter() takes one argument, which is the address of a user-defined function; this tells the output formatter to call this function each time a string is displayed. You can also call the function with a value of nil, which cancels the current output filter function, restoring unfiltered output. The filter function is defined like this: myFilter: function(str) You can use any name in place of "myFilter". The parameter "str" is the string that the output formatter is about to display. Your filter function can return nil, in which case the original string is displayed unchanged; or it can return a (single-quoted) string value, which the formatter will display instead of the original string. Here's an example that converts all displayed output to upper-case when the player types the verb "uppercase on," and restored output to normal when the player types "uppercase off." ucFilter: function(str) { return upper(str); } uppercaseonVerb: deepverb verb = 'uppercase on' action(actor) = { "Upper-case mode is now ON. "; setOutputFilter(ucFilter); } ; uppercaseoffVerb: deepverb verb = 'uppercase off' action(actor) = { "Upper-case mode is now OFF. "; setOutputFilter(nil); } ; The output filter function is called before the output formatter does any processing on the text to display. After the filter returns, the formatter translates "%fmt%" sequences, applies caps() and nocaps() changes, translates "\t" and similar sequences, performs word-wrapping on the line, adjusts spacing for punctuation, and, when in HTML mode, interprets HTML mark-ups. As a result, you can perform your own translations on any of these sequences; in addition, you can use such sequences in the returned string, and the formatter will interpret them normally. Note that the output formatter will make a separate call to your filter function to display the result of translating each "%fmt%" sequences in the text to be displayed. These separate calls will occur after your filter function returns. This allows you to perform the same operations on the translated "%fmt%" sequences that you perform on any other text. - Fixed a bug in the objwords() built-in function. In the past, this function at times incorrectly returned a non-empty list of words for a command that didn't have the corresponding object at all. This has been corrected; objwords() will now return an empty list when the command does not have the requested object. - The parser now re-validates objects in a multi-object command after executing the command on the first object. This re-validation occurs before the verbAction method is called for each object, and is done only for the second and subsequent object in a multi-object command. Re-validation is necessary because an action performed on the first object in a multi-object command could change conditions in the game such that the second object becomes invalid for the command, even though it was valid during the object resolution phase. Consider this example: >x large box The large box is open. It contains a small box. >x small box. The small box is open. >close large box, small box large box: Closed. small box: You don't see that here. In the past, the parser only validated the objects in a multi-object command during the resolution phase, which happens before executing the command on any of the objects. Since the small box is accessible before the large box is closed, the parser would have allowed the player to close the small box after closing the large box in the last command above. By re-validating the second and subsequent objects, the parser now correctly detects when objects become inaccessible in the course of executing a command on multiple objects. If an object fails re-validation, the parser checks to see if the object is visible. If it is, the parser uses the cantReach method as usual to display the error. If the object is not visible, the parser displays error 38, "You don't see that here any more." - The parser error codes (the numbers passed to parseError and parseErrorParam, and returned by parserResolveObjects) now have constants defined in adv.t. These constants have names like PRSERR_xxx. Refer to adv.t for the complete list. You may prefer to use the constants in your code whenever possible for clarity. The numeric values will be stable in future releases, so it's perfectly safe to use the numbers directly, but using the symbolic constants will make the purpose of your code clearer to others (and to you, if you come back to some code after not having looked at it for a few weeks). - Several new parser error codes have been added. The new codes 40, 41, 42, 43, and 44 never result in a parser-generated error message, thus these codes have no default message. These codes can, however, be returned by parserResolveObjects(), so that callers can distinguish among different types of error conditions if necessary. Code 38, default message "You don't see that here any more." This message is used during object re-validation when an object that was valid at the start of the command is no longer valid. Code 39, default message "You don't see that here." This new message is used when object validation (via validDo or validIo) fails in the course of executing a command recursively with execCommand(). Code 40, no default message. This error code is used when the parser is unable to create a new generic numeric object when calling a newNumbered method. Code 41, no default message. This error occurs when disambigDobj or disambigIobj returns an invalid status code. Code 42, no default message. This error occurs when the parser gets an empty line of text in response to a disambiguation question. Code 43, no default message. This error occurs when the parser gets what looks like an entirely new command in response to a disambiguation question. Code 44, no default message. This occurs when the parser finds that it still has an ambiguous list of objects after applying validation and verification tests, but the disambiguator is being called in "silent" mode and hence is not allowed to ask the player for help. When this occurs, the parser simply returns this error to indicate that the list cannot be disambiguated. - Parser message 16 has been changed slightly. The parser generates message 16 when the player's answer to a noun disambiguation question ("which do you mean...") doesn't refer to any of the possible ambiguous objects. For example, suppose the parser asks "which book do you mean, the red book, or the blue book?," and the player answers "silver." In the past, message 16 simply said "I don't see that here." Some players found this slightly confusing, especially since the answer may have referred to some other object that wasn't a book but was indeed present. The new message is intended to be somewhat more explicit: "You don't see any %s %s here," where the first "%s" is replaced with the new noun phrase the player typed, and the second "%s" is replaced with the original noun phrase. >take book Which book do you mean, the red book, or the blue book? >the silver one You don't see any silver book here. Note that you can use the parseErrorParam() user-defined function if you want to intercept this message have have access to the string parameters. - In adv.t, the "doorway" class has a new method, setIslocked. This new method behaves analogously to setIsopen: when you call setIslocked on a door, the method automatically updates both sides of a two-sided door to the same locking status. All of the code in "doorway" that updates the islocked property now uses setIslocked rather than changing the property directly. - The default "sav" extension on newly-created saved game files is now in lower-case letters on operating systems that support mixed-case filenames. - A long-standing parser bug involving vocabulary words defined as both adjectives and nouns (for different objects) has been fixed. In the past, if a particular word was defined as a noun for one object, and as an adjective for another object, the word could not ever be used alone to refer to the second object. For example, suppose the game defined one object, "letter," and another, "letter opener"; the word "letter" is defined as a noun for the first object, and as an adjective for the second object. If the player typed "get letter," the parser formerly assumed that the word was to be used as a noun; even if the "letter" object wasn't present and the "letter opener" object was, the parser would never understand the sentence to mean "get letter opener." This was inconsistent with other objects, since in other cases an adjective alone could be used to refer to an object when the meaning was not ambiguous. This has now been fixed; the parser now will interpret the adjective alone to refer to an associated object when no other object defining the word as a noun is accessible. Note that this does not create a new ambiguity: if the player types "get letter," and both the letter and the letter opener objects are present, the parser will still assume that "letter" is being used as a noun. The parser will only attempt the adjective-only interpretation as a last resort, when no matching objects using the word as a noun are accessible. - Fixed a parser bug involving disambiguation of objects with redundant nouns or adjectives. When the player types a word, and the word is at least six characters long, the parser will match the word to any entry in the game's dictionary of which the player's word is a leading substring. For example, "flashlig" matches "flashlight", because "flashlig" is eight characters long (which is greater than or equal to the required six), and matches the first eight characters of "flashlight". Similarly, "headlight" will match "headlights", because "headlight" is nine characters long, and matches "headlights" in the first nine characters. This short-hand feature is meant as a convenience for the player, but occasionally caused ambiguity problems in past versions of TADS. In particular, when a single object had a vocabulary word that was a leading substring of another vocabulary word of the same object (for example, if an object defined as nouns both 'headlight' and 'headlights'), the object could in some cases show up twice in a list of ambiguous objects ("which headlight do you mean..."). The parser now always eliminates redundant entries in lists of ambiguous objects during object disambiguation. - Another parser bug involving truncated words has been fixed. Suppose the game defines one object with a noun 'prince', and another object with a noun 'princess'. In the past, these two objects both matched the word 'prince' in a player's command, so if both objects were present, the parser considered them ambiguous. The parser now considers an exact match to be better than a truncated match, so the word 'prince' in a player's command will now match the 'prince' object more strongly than it will the 'princess' object. If only the 'princess' object is accessible, 'prince' will still match the 'princess' object, but if both are present, the parser will simply assume the player is referring to the 'prince' object. - Fixed a parser bug that caused two error messages (without any spacing between them) when the first word in a command was unknown: the parser responded with both "There's no verb in that sentence!" and "I don't know the word 'xxx'". The parser now displays only the first ("no verb") message. - Fixed a parser bug that caused fuses and deamons to be skipped when an "exit" statement was executed in the midst of executing a command involving multiple direct objects. If the "exit" was executed during the processing of any but the last object in the direct object list, the parser incorrectly skipped fuses and daemons. This has been corrected; fuses and daemons will now run after an "exit" is executed, regardless of how many direct objects are involved in the command. - In adv.t, in the definition of movableActor, the definitions of the format strings fmtYou, fmtYour, fmtYoure, fmtYoum, and fmtYouve now vary according to the gender defined for the object with the isHim and isHer properties. If isHim evaluates to true, the format strings now use masculine pronouns; otherwise, if isHer evaluates to true, the format strings use feminine pronouns; otherwise, they use neuter pronouns. Note that, in the past, these format strings always used masculine pronouns; if you defined any Actor or movableActor objects with neither isHim nor isHer set to true, messages that in the past used masculine pronouns will now use neuter pronouns. You should be careful to ensure that you're defining the appropriate gender property (isHim = true or isHer = true) for your Actor and movableActor objects, so that messages using the format strings display the correct pronouns. - The text of system error messages 1021 ("index value too low") and 1022 ("index value too high") have been modified slightly so that they don't use ">" or "<" characters. (When running in HTML mode, the "<" and ">" characters in these messages caused problems, because the renderer tried to parse them as HTML tags.) - In the past, when the player's command contained a quoted string, the value that the parser passed to preparseCmd() did not contain a usable form of the string. This has been corrected; the value that the parser passes to preparseCmd() will now always be the original string text, enclosed in double quote marks. You can therefore detect a string token in the preparseCmd() list by checking to see if the first character (obtained with substr()) is a double quote mark, '"': if (substr(lst[i], 1, 1) = '"') /* this token is a quoted string */ ; - In adv.t, the implementation of clothingItem has been modified slightly, to correct some bugs and make some improvements. - An actor can now attempt to wear objects that aren't being carried; the game will now automatically attempt to take the item (via a recursive "take" command using execCommand) before wearing it if the actor isn't carrying it. - The game will also automatically take an object if it's within another object in the actor's inventory; this ensures that an object is always at the top level of an actor's inventory while being worn. - When doffing a clothingItem, the game first checks to ensure that the actor's maximum "bulk" carrying capacity is not exceeded; this check must be made because an item being worn doesn't encumber an actor with bulk since it's not being carried by hand. If the maximum bulk would be exceeded, the game will not let the actor doff the item. - The verDoWear method now checks to make sure that another actor isn't wearing the item; in the past, if the player attempted to wear an item already being worn by another actor, the game replied with the nonsensical message "you're already wearing that." ------------------------------------------------------------------------------ 2.3.0 02/01/1999 - A new systemInfo() feature code lets the game determine whether the interpreter is in HTML mode or plain text mode. The new code is __SYSINFO_HTML_MODE; systemInfo(__SYSINFO_HTML_MODE) returns true if the interpreter is currently interpreting HTML markups, nil if not. Note that this new code has nothing to do with whether the interpreter is a full multimedia system (such as HTML TADS) or a text-only system (such as the DOS "TR" interpreters); this new code instead indicates only whether or not a "\H+" sequence is currently in effect. - Several new systemInfo() feature codes have been added to provide information on MPEG audio support: __SYSINFO_MPEG_AUDIO - this returns 1 if MPEG 2.0 audio support of any kind is present, 0 if not. It is possible that some systems may support some types of MPEG audio, but not all three layers. This feature code indicates whether MPEG audio of any kind is supported; the specific layer codes below can be used to check for each individual layer. __SYSINFO_MPEG_AUDIO_1 - 1 if MPEG 2.0 layer I is supported __SYSINFO_MPEG_AUDIO_2 - 1 if MPEG 2.0 layer II is supported __SYSINFO_MPEG_AUDIO_3 - 1 if MPEG 2.0 layer III is supported - To accomodate the new TADS-Input font feature in HTML TADS, std.t now has a definition of the commandPrompt and commandAfterRead functions that automatically switch to the TADS-Input font. This is important only for HTML-enabled games. If your game uses HTML features, you should #define USE_HTML_PROMPT before including std.t in order to use these new functions. (If you're providing your own definitions of these functions, you should consider adding the font settings made in the new std.t versions.) - New feature: TADS now has a built-in regular expression matching facility. Regular expressions provide a powerful and simple way to search for a complex pattern within a text string. This feature is particularly useful for writing preparse() and preparseCmd() functions, since it allows you to perform complex pattern matching and replacing with very little code. The new function reSearch() searches for the first occurrence of a regular expression pattern within a string. It returns nil if the pattern is not found. If the pattern is found, the function returns a list, the first element of which is a number giving the character position within the string of the start of the match (the first character is at position 1), the second element giving the number of characters in the match, and the third element a string giving the actual text of the match. ret := reSearch(pattern, string_to_search); The pattern is specified using regular expression syntax similar to that used by "grep" and other similar utilities. Here are the basic building blocks of the regular expression syntax: | Alternation: matches the expression on the left or the expression on the right. This operator affects as many characters as it can, out to the nearest parentheses. ( ) Groups an expression. + Indicates that the immediately preceding character or parenthesized expression repeats one or more times. * Indicates that the immediately preceding character or parenthesized expression repeats zero or more times. ? Indicates that the immediately preceding character or parenthesized expression can occur zero or one time. . (a period) Wildcard: matches any single character. ^ Matches the beginning of the string. $ Matches the end of the string. % Quotes the following character, removing the special meaning of these characters: | . ( ) * ? + ^ $ % [ Also introduces the special sequences listed later. [ ] Indicates a character list or range expression. Matches any one of the listed characters. A range can be specified by following a character with '-' and another character; this matches all of the characters between and including these two characters. For example, [a-z] matches any one lower-case letter, and [0-9] matches any one digit. Ranges and single characters can be combined; for example, [a-zA-Z] matches any letter, upper- or lower-case. To include the character ']' in a list, make it the first character after the opening bracket; to include '-', make it the next character after that. For example, []] matches just ']', [-] matches just '-', and []-] matches '-' and ']'. [^ ] Exclusionary character list or range. This matches any character *except* the ones listed. For example, [^0-9] matches anything single character except a digit. %1 This matches the same text that matched the first parenthesized expression. For example, consider the pattern '(a*).*%1'. The string 'aaabbbaaa' will match, because the first three characters match the parenthesized 'a*' expression, which causes '%1' to match the last three characters; the middle three characters are matched by the '.*' expression. %2 Matches the text matching the second parenthesized expression. And so on through... %9 Matches the text matching the ninth parenthesized expression. %< Matches at the beginning of a word. Words are considered to be contiguous groups of letters and numbers. %> Matches at the end of a word. For example, '%' matches the "and" in 'ball and box' and 'and then', but not in 'rubber band' or 'the android'. Note that %< and %> do not actually contribute any characters to the match - they simply ensure that they fall on a word boundary. So, searching for '%' in 'ball and box' matches the string 'and' -- the spaces are not included in the match. %w Matches any word character (a letter or a digit). %W Matches any non-word character (anything but a letter or digit). %b Matches at any word boundary (beginning or end of a word). %B Matches except at a word boundary. Any character other than those listed above simply matches the exact same character. For example, 'a' matches 'a'. Here are some examples of simple regular expressions, to help clarify the meanings of the basic building blocks: abc|def either 'abc' or 'def' (abc) 'abc' abc+ 'abc', 'abcc', 'abccc', etc. abc* 'ab', 'abc', 'abcc', 'abccc', etc. abc? 'ab' or 'abc' . any single character ^abc 'abc', but only at the start of the string abc$ 'abc', but only at the end of the string %^abc literally '^abc' [abcx-z] 'a', 'b', 'c', 'x', 'y', or 'z' []-] ']' or '-' [^abcx-z] any character except 'a', 'b', 'c', 'x', 'y', or 'z' [^]-q] any character except ']', '-', or 'q' Here are some more complicated examples: (%([0-9][0-9][0-9]%) *)?[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9] This matches a North American-style telephone number, either with or without an area code in parentheses. If an area code is present, it can optionally be separated by spaces from the rest of the number: '(415)555-1212', '555-1212', '(415) 555-1212'. [-+]?([0-9]+%.?|([0-9]*)%.[0-9]+)([eE][-+]?[0-9]+)? This matches a floating-point number in the notation used by C and some other programming languages: either a string of digits optionally ending with a decimal point, or zero or more digits followed by a decimal point followed by one or more digits; optionally followed by an exponent specified with the letter "E" (upper- or lower-case), an optional sign ('+' or '-'), and one or more digits; all of this can be preceded by an optional sign. This matches: '3e9', '.5e+10', '+100', '-100.', '100.0', '-5e-9', '-23.e+50'. ^ *tell%>(.*)%(.*) This matches the word "tell" at the beginning of the string, preceded only by zero or more spaces, followed by any text, followed by the word "to", followed by any more text. This matches 'tell bob to go north' and 'tell teeterwaller to give me the mask'. Here's a code example: ret := reSearch('d.*h', 'abcdefghi'); if (ret = nil) "No match."; else "Start = <>, length = <>, text = \"<>\". "; When run, this code will display the following: Start = 4, length = 5, text = "defgh". - Another new built-in function, reGetGroup(), lets you retrieve the matching text for parenthesized groups within regular expressions. reGetGroup() returns information about the last call to reSearch(). The function takes one argument, which is a number giving the group number to retrieve: the first parenthesized expression is group 1, the second is group 2, and so on. (When groups are nested, the position of the open parenthesis is used to determine the group numbering. The leftmost open parenthesis is numbered as group 1.) reGetGroup() returns nil if there is no such group in the most recent regular expression or if the last call to reSearch() did not match the expression. Otherwise, reGetGroup() returns a list with three elements identifying the group. The first element is a number giving the character position within the original search string of the start of the text that matched the group. The second element is the length of the text that matched the group. The third element is the actual text that matched the group. Here's a code example: ret := reSearch('d(.*)h', 'abcdefghi'); if (ret != nil) { grp := reGetGroup(1); if (grp != nil) "Start = <>, len = <>, text = \"<>\". "; } This will display the following: Start = 5, len = 3, text = "efg". You can use regular expression grouping to carry out complicated transformations on strings with relatively little code. Since you determine exactly where in a string a particular group in a regular expression occurs, you can take the group text out of the string and put it back into the string in a different order or with other changes. For example, suppose you want to write a preparse() function that finds sentences of the form "tell to " and converts them to the normal TADS actor command format, ", ". You can use regular expression grouping to find this pattern of text and build the new command from pieces of the original: ret := reSearch('^ *tell%> *(.*)% *(.*)', cmd); if (ret != nil) cmd := reGetGroup(1)[3] + ', ' + reGetGroup(2)[3]; Or, suppose you have a telephone in your game, and you want to let the player dial numbers on the phone using normal North American telephone number notation, including an area code. The TADS parser won't normally let you do this, since it would try to parse the number as several words. You could solve this problem using preparse: after the player enters a command, find anything that looks like a telephone number, and enclose it in quotation marks; this will make the parser treat the phone number as a quoted string, so you can write your "dial" verb so that it uses strObj as the direct object. Here's how you could write the preparse routine: ret := reSearch('(%([0-9][0-9][0-9]%) *)?' + '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]', cmd); if (ret != nil) cmd := substr(cmd, 1, ret[1] - 1) + ' "' + ret[3] + '" ' + substr(cmd, ret[1] + ret[2], length(cmd)); - New feature: a new function extends the capabilities of the existing input functions, input() and inputkey(), to give you more control over event processing. The new function, inputevent(), can read multiple types of events, and can also apply a timeout to limit the how long it waits for an event to occur. The inputevent() function takes zero or one argument. With no arguments, inputevent() simply waits until an event occurs. With one argument, which must be a number, inputevent() waits until an event occurs, or until the number of milliseconds specified by the argument has elapsed without an event occurring, in which case the function "times out" and returns without any event having occurred. Note that the timeout value, if given, may not always be obeyed to the exact millisecond. Different types of computers have different system clock resolutions; in addition, multi-user and multi-tasking systems often have unpredictable latencies for event processing. As a result, if you specify a timeout value, the actual time that elapses before the function times out and returns may be slightly longer than the specified timeout value. Any additional latency should be no more than a few hundred milliseconds in most cases, so this shouldn't be noticeable for most purposes. The function returns a list value describing the event that occurred. The first element of the list is a number that specifies the type of the event. The rest of the list varies according to the event type. Constants for the event codes are defined in adv.t. The possible event codes are: INPUT_EVENT_KEY - the user pressed a key. The second element of the list returned by inputevent() in this case is a string containing the key that the user pressed. The string is the same that would be returned by inputkey() for the same keystroke. INPUT_EVENT_HREF - the user clicked on an link. This event is only returned by an HTML TADS interpreter, never by a character-mode TADS interpreter. The second element of the return list is a string containing the text of the HREF that the user clicked. INPUT_EVENT_TIMEOUT - no event occurred before the specified timeout elapsed. The return list contains no additional elements. INPUT_EVENT_EOF - this indicates that the TADS interpreter is terminating or an error occurred reading an event. INPUT_EVENT_NOTIMEOUT - this is not actually an event, but an error indicating that the current system does not support the timeout feature of inputevent(). If this occurs, you can still use inputevent(), but you cannot specify a timeout. The DOS TADS interpreters (TR, TRX, TR32) all support timeouts, as does HTML TADS for Windows; interpreters on most systems should be able to support this feature, but a few systems may not be able to. - New feature: the inputkey() function now returns a portable representation for certain extended keys. In the past, keys outside of the normal ASCII character set were almost impossible to use with inputkey(), because TADS returned a useless "raw" format for extended keys, such as cursor navigation keys and function keys. TADS now uses a portable string format to represent many common keys. Each extended key is represented as a string containing a key name enclosed in square brackets. The key name are: [bksp] - backspace (destructive backspace/delete left) [up] - up arrow (cursor navigation key) [down] - down arrow [right] - right arrow [left] - left arrow [end] - "end" key [home] - "home" key [del] - delete character (the "del" key) [page up] - "page up" key [page down] - "page down" key [f1] - function key F1 [f2] - F2 [f3] - F3 [f4] - F4 [f5] - F5 [f6] - F6 [f7] - F7 [f8] - F8 [f9] - F9 [f10] - F10 In addition, "control" keys (i.e., keys entered by holding down the "control" or "ctrl" key on the keyboard and pressing an alphabetic key) are returned as "[ctrl-X]", where "X" is the lower-case letter key; and "alt" keys are returned as "[alt-X]". Finally, the "Return" and "Enter" keys are returned as "\n", and the tab key is returned as "\t". Even though these key names are portable, be aware that not every computer has all of these keys, so you can't count on the player actually being able to enter them. The only keys that you can always count on being present are the regular ASCII keys, Enter/Return, Tab, and Backspace. So, if you're using these extended keys, you should always be sure to provide an alternative for each extended key using an ordinary key. For example, if you want to implement a menu system that uses the up and down arrow keys to navigate through a set of choices, you could use "N" (for "next") and "P" (for "previous") as synonyms for [down] and [up], respectively. The arrow keys ([up], [down], [left], and [right]) are probably the most portable of the extended keys, since most computers and terminals have some sort of arrow keys. The function keys ([f1] through [f10]) are also available on many systems, although some systems use some or all of the function keys for special purposes; for example, Windows uses the F10 key to begin navigating the menu bar, so your game will never receive the [f10] extended key when running on Windows. The ALT and CONTROL keys are also very non-portable. Here's an example of using the arrow keys. numberVerb: deepverb verb = 'number' action(actor) = { local num; local done; local changed; "Press the Up or Down arrow keys, or the + or - keys, to change the number. Press Enter when finished.\b"; num := 5; changed := true; for (done = nil ; !done ; ) { if (changed) { "\nCurrent value = <>"; changed := nil; } switch(inputkey()) { case '\n': done := true; break; case '+': case '[up]': ++num; changed := true; break; case '-': case '[down]': --num; changed := true; break; } } "\bThe final value was <>. "; } ; - New feature: the gettime() built-in function can now return additional system real-time clock information. The function now optionally takes an argument specifying what type of information to return. Constants for the argument values are defined in adv.t: GETTIME_DATE_AND_TIME - this returns the traditional date and time information that gettime() returned in the past. This is the same information that the function returns if called with no arguments (thus ensuring that existing code that calls gettime() will continue to work unchanged). GETTIME_TICKS - this returns the number of milliseconds since an arbitrary zero point, which is usually some system event, such as starting the current session of the TADS interpreter, or turning on the computer. The actual zero point is arbitrary, but it will remain fixed for a particular session, so you can use this form of gettime() to compute relative times between events over a short period of time. For example, if you're reading events with the new inputevent() function, you can use this time value to set a limit on how long you read events. For example: local max_time, cur_time, evt; /* process events for no more than 5 seconds (5000 milliseconds) */ max_time := gettime(GETTIME_TICKS) + 5000; for (;;) { /* check to see if we've reached our time limit */ cur_time := gettime(GETTIME_TICKS); if (cur_time >= max_time) break; /* get events, but time out if we exceed our time limit */ evt := inputevent(max_time - cur_time); /* process the event */ switch(evt[1]) // and so on } - New feature: a new built-in function lets you pause the game for a specified interval. You can use this function in the middle of displaying a long text passage, for example, to create a dramatic pause effect, without making the user press a key to continue. The new function is called timeDelay(), and takes a single argument giving the number of milliseconds to pause the game. For example, to pause the game for five seconds, use this: timeDelay(5000); - The player command parser now allows a preposition to be used when answering a request for an indirect object. For example: >unlock door What do you want to unlock it with? >with key - The "oops" command (which lets the player correct a misspelled word in the previous command) now accepts multiple words to substitute for a misspelled word. This lets you correct an omitted space in a command: >take redbox I don't know the word "redbox". >oops red box Taken. - New feature: the askfile() built-in function now takes two additional, optional parameters that let you specify what type of prompt to show and what type of file to request. These new arguments are hints to the system-specific code that displays the "open file" dialog; by specifying this new information, you help the system code show the correct type of dialog. The new askfile() syntax looks like this: filename := askfile(prompt_text, prompt_type_code, file_type_code); The prompt_type_code tells the open-file dialog whether you're opening an existing file or saving a file. On some systems (Windows and Macintosh included), the user interface uses one type of dialog for opening an existing file, and a different type of dialog for saving a file; you can use this parameter to select the appropriate dialog type on systems that make this distinction. This parameter can have one of the following values, defined in adv.t: ASKFILE_PROMPT_OPEN - open an existing file for reading ASKFILE_PROMPT_SAVE - open a file for saving information On some systems, the open-file dialog will filter the files it displays so that the player only sees files of the particular type being requested. The file_type_code parameter lets you specify the type of file you're interested in, so that the dialog can use the appropriate filtering on systems that support this. The file_type_code can be one of the following values, defined in adv.t: FILE_TYPE_GAME - a game data file (.gam) FILE_TYPE_SAVE - a saved game (.sav) FILE_TYPE_LOG - a transcript (log) file FILE_TYPE_DATA - general data file (used for fopen()) FILE_TYPE_CMD - command input file FILE_TYPE_TEXT - text file FILE_TYPE_BIN - binary data file FILE_TYPE_UNKNOWN - unknown file type adv.t and std.t have been updated to specify these new arguments in all of their calls to askfile(). If you leave out the new arguments in a call to askfile(), the function will behave as it did in the past. This means that your prompt string must contain the word "save" or "write" in order to show a "save file" dialog rather than an "open file" dialog on those systems that differentiate between these dialog types. - Fixed a bug with the parserSetMe() function: on restart(), the system did not correctly restore the current "Me" object to the original object. This works properly now. - Fixed a player command parser bug that caused problems with numbers in commands under certain circumstances. In particular, if an object happened to use a number as an adjective, and the player typed a command involving the same number, but to refer to the number itself rather than to the object with the numeric adjective, the parser incorrectly assumed that the player meant to use the object rather than the number. Consider this example: >i You are carrying Chapter 3. >turn dial to 3 Previously, the parser interpreted the "3" as referring to the "chapter 3" object. This problem has been corrected; now, the parser will still try the "chapter 3" object first, but when the parser finds that the command fails the verification pass (because verIoTurn or verDoTurn display an error), and the original word used in the command was simply a number, the parser will assume that the player means to refer to the number itself rather than any object that has an adjective matching the number. - Fixed a bug in adv.t that caused misleading messages when the player was in a chairItem and tried to refer to something in the enclosing room when the chairItem's reachable list did not include items in the enclosing room. In this case, adv.t displayed the message "I don't see that here," which was clearly incorrect in that the objects in a chairItem's enclosing room are generally visible even when not reachable. To correct this, the room object in adv.t now calls a new method from its cantReach() method when the actor is not directly in the room. The new method is cantReachRoom(); room.cantReach() calls actor.location.cantReachRoom(self) to display an appropriate message. In other words, if the actor is in a chair within a room, adv.t will call the chair's cantReachRoom() method with the chair's enclosing room as the argument. adv.t defines a default cantReachRoom() method for the classes thing, room, and chairItem. These default methods all generate similar messages saying roughly "You can't reach that from here." Note that the new cantReachRoom() method provides new versatility for situations where objects are visible from a location but not reachable. For example, if you want to create two rooms separated by a glass partition, so that objects in one room are visible from the other room but not reachable, you can override isVisible and getVisibleList for the rooms to make the contents of the other room visible from each room, and then add a cantReachRoom(otherRoom) method like this: cantReachRoom(otherRoom) = { if (otherRoom = eastSideOfGlassRoom) "That's on the other side of the glass partition. "; else inherited.cantReachRoom(otherRoom); } - Fixed a bug in the player command parser that caused the parser to repeatedly say "I don't know the word " when an invalid word was entered in a command *and* the preparseCmd function modified the command by returning a list value. In this situation, the parser would not stop saying "I don't know the word " until the player entered an "oops" command to fix the misspelled word. This no longer occurs. - The parser responded incorrectly to unknown words entered in response to a disambiguation question ("which box do you mean, the red box, or the blue box?"). If the player entered an unknown word in response to such a question, the parser responded by asking the same question over again, without mentioning that the word was unknown. The parser now correctly mentions the unknown word. Note that this change removes a certain amount of control from the game's processing of unknown words through parseUnknownXobj, since these methods are not called when unknown words are entered in response to disambiguation questions. - Fixed a parser bug that caused errors for certain valid commands involving an indirect object that used an adjective that also was used as a preposition. For example, if the game had a "south wall" object, the parser did not correct interpret the command "put box on south wall" (the response was "I don't understand that sentence"). This has been corrected. - Numerous changes in adv.t make the default messages more consistent at using parameterized pronouns and verbs to refer to objects. These changes generally make the default messages agree more automatically in number and gender with the objects described. The parameterized messages are mostly keyed on the isThem property; if you have an object that's described by a plural noun phrase ("the pants" or "some marbles," for example), setting isThem = true in the object will help adv.t generate the correct default messgaes when referring to the object ("you can't put the marbles in themselves," for example). Many thanks to Stephen Granade for his extensive work to fix these. - adv.t is now somewhat more consistent in its handling of reachability from nested rooms. In the past, chairItem objects and other nestedroom objects had a strange difference: from a normal nestedroom, you could reach anything in the nestedroom's "reachable" list, plus anything in any open containers in the list, to any nesting level; in contrast, from a chairItem, you could only reach the objects directly in the chairItem's "reachable" list. For compatibility with past versions, this behavior hasn't changed. However, it's now simple to make the behavior the same for both types of rooms. If you want chairItem objects to behave the same way that all other nestedroom objects behave, simply modify chairItem and set its "canReachContents" property to true: modify chairItem canReachContents = true ; This is probably the most logical behavior, so authors of new games may want to consider including this change. However, the default remains for chairItem objects to limit reachability to the items directly in the "reachable" list with no access to their contents, so that any existing games that depended (intentionally or otherwise) on the old behavior are not broken by an incompatible change. - Fixed a bug that caused a spurious error (TADS-601, "error writing to game file") when attempting to create a pre-compiled header file, when a #define symbol was defined to an empty string. This problem has been corrected. - The compiler went into an infinite loop reporting an error (TADS-353, "'local' is only allowed at the beginning of a block") when a misplaced "local" statement appeared. This is fixed; the compiler now simply ignores the entire "local" statement and continues attempting to parse the code. - The random number generator caused a divide-by-zero error (which could abruptly terminate the interpreter on some platforms) when the game called rand(0) at any time after calling randomize(). This has been corrected; rand(0) now simply returns 0 without causing a crash. - In adv.t, the clothingItem code was written so that, if the player attempted to wear something that another actor was wearing, the game responded with "You're already wearing that," which clearly isn't correct. The game now says "You don't have that," which makes more sense. ------------------------------------------------------------------------------ 2.2.6 09/30/98 bug fixes - The release notes are now separated into a generic section and a platform-specific section. This file (TADSVER.TXT) describes the changes that apply to all types of computers and operating systems. A separate file describes the changes specific to each platform; for example, DOSVER.TXT describes the changes that apply only to the MS-DOS and Windows versions of TADS. - adv.t now has a "knock" verb (knock, knock on, and knock at are all synonyms). The "doorway" class provides has a default doKnock method that simply displays "there is no answer" in response to the player knocking on the door. - adv.t now has a "go through" verb. The "doorway" class provides a default handler for "go through" that has the same effect as the player traveling in the direction of the door. - In adv.t, the "doorway" class now has a default handler for the "enter" verb that has the same effect as the player traveling in the direction of the door. - In adv.t, the default message for eating a "thing" now correctly incorporates the ordinality of the object, and the verb agrees with the subject (so "the tire doesn't appear appetizing" whereas "the keys don't appear appetizing"). - In the adv.t "doorway" class, the setIsopen method now calls setIsopen on the object representing the other side of the door, rather than updating its isopen property directly. In addition, the code that automatically opens the door when the player attempts to travel through the door calls setIsopen rather than updating the isopen properties of the door object and the other side object directly. These change make it easier to code a door that has special behavior when opened or closed, since all changes to the isopen property made by code in the doorway class now occur through the setIsopen method, so all of the special behavior code can be placed in an overridden setIsopen method. - The player command parser did not correctly respond to disambiguation questions (such as "which box do you mean, the green box, or the red box?"). In some cases, when the player ignored the question and simply typed a new command, the parser completely ignored the command and show a new command prompt with no other comment; alternatively, the parser responded to the new command as though it were an object, showing a message such as "I don't see any open door here." In yet other situations, when the player attempted to answer the question with a more detailed object description, the parser would complain that it didn't see such an object even though the additional player input was correct. All of these problems have been corrected; the parser should handle player responses to disambiguation questions correctly now. - A bug in the player command parser caused an infinite loop under certain conditions. The loop occurred if one of an object's nouns was a word that was also used as a verb, and the player typed a command of the form " of ", where is the word used as both a noun and a verb. For example, the command "discard ace of spades" caused the loop if "discard" was defined as a verb and was also used as a noun for some object. This has been corrected. - The compiler did not correctly handle the error "else without if". Rather than ignoring the offending "else" keyword, the compiler got stuck in an infinite loop generating the same error message over and over. This has been corrected; the compiler now only generates the error message once per occurrence of the error in the source code. - The compiler did not correctly handle the -case- option. Although games compiled correctly, they did correctly handle verbs with objects at run-time. This has been corrected. - The compiler did not correctly interpret expressions of this form: (x ? "first string" : "second string << 5 >> end of second string") In particular, when a string containing an embedded expression was used as the third operand of a ternary conditional operator (the "?:" operator), the compiler interpreted the embedded expression and everything that followed as an entirely separate expression, which was incorrect. The compiler now handles these expressions correctly. - The compiler did not correctly interpret a statement that consisted entirely of a parenthesized expression when used as the "true" branch of an "if" statement when an "else" clause was present: if (expr) (1+2+3); else ... The compiler incorrectly generated an "else without if" error in this situation, because it incorrectly ignored the semicolon that followed the statement. This has been corrected; the semicolon is now parsed properly. Note that if your game inadvertantly exploited this error by omitting semicolons in such cases, you will have to change your source code; because of the obscurity of this case we consider it extremely unlikely that anyone will be affected, but if you are, you will receive "expected semicolon" messages at the erroneous lines of code. - A compiler bug caused crashes when using __LINE__ under certain circumstances; this bug has been fixed. - The interpreter now displays a separate error message (TADS-617) when it attempts to load an external resource file (.RS0, etc) with an invalid header. The interpreter previously displayed the message for an invalid game file, which was not helpful in tracking down the problem. The error message for TADS-617 displays the name of the resource file causing the error, to help pinpoint the problem. ------------------------------------------------------------------------------ 2.2.5 08/24/98 enhancements and bug fixes - New parser feature: The parser now calls a new pair of methods on the direct and indirect object in the course of processing each command. The new methods are called dobjCheck and iobjCheck, and are called with these parameters: iobjCheck(actor, verb, dobj, prep) dobjCheck(actor, verb, iobj, prep) iobjCheck is called on the indirect object, if any, just before iobjGen is or would be called. (iobjCheck is always called, whether or not iobjGen is called.) Similarly, dobjCheck is called on the direct object just before dobjGen is or would be called. Note that the dobj, iobj, and prep parameters may be nil if the corresponding objects are not present in the command. These new methods are very similar to dobjGen and iobjGen, but have one difference: the new methods are ALWAYS called. This differs from dobjGen and iobjGen, which are called only when the object does not define the corresponding verb handler method (do or io) for the verb being used. The new dobjCheck and iobjCheck methods are called immediately BEFORE the corresponding dobjGen and iobjGen methods are called. So, the new calling sequence looks like this: actor.actorAction actor.location.roomAction *NEW* iobj.iobjCheck (if there's an indirect object) iobj.iobjGen (if the indirect object doesn't define io) *NEW* dobj.dobjCheck (if there's a direct object) dobj.dobjGen (if the direct object doesn't define do) and then on to the normal verIo and/or verDo, as appropriate. So, why have both xobjGen and xobjCheck? The reason is that each is useful in its own way. The xobjGen routines are meant as *default* handlers. They're catch-all methods that handle any command that isn't specially handled by the object. Because any normal verb handler (one of the io or do routines) "overrides" the xobjGen routines, it's not necessary to include a special check in the xobjGen method for the verbs that the object handles in a non-default fashion. The xobjCheck routines, on the other hand, are mandatory checks that are applied to all verbs used with an object, whether or not the verb is otherwise handled by the object. This makes these routines useful in cases when the object's behavior can change, because you can perform tests based on the object's state in a single place for all verbs, rather than having to apply the same test in every verb handler. - A bug in past versions handled doSynonym and ioSynonym incorrectly when they were used in classes. Consider the following example: class draggableItem: fixeditem verDoPull(actor) = { } doPull(actor) = { "You manage to drag <> along the ground for a few feet. "; // etc... } doSynonym('Pull') = 'Push' 'Move' ; desk: draggableItem // noun, location, sdesc, etc... doPull(actor) = { "The desk scrapes along the floor for a few feet, but everything falls off! "; // etc... } ; Now, if the player typed PULL DESK, the result would be as you'd expect: "the desk scrapes along the floor for a few feet, but everything falls off!" However, if the player typed PUSH DESK, you'd get the unexpected reply "You manage to drag the desk along the ground for a few feet." The problem was that the system was invoking the synonym routine for the definition in the class defining the synonym, rather than properly using the overriding method in the actual object used in the command. This problem has been corrected. - A player command parser bug allowed using AGAIN to repeat a command directed to an actor after the actor had left the room (or otherwise become inaccessible). The parser did not check to ensure that the actor was still present for the repeated command. This has been corrected; the parser now checks that the actor is still accessible when using AGAIN. - A bug in the player command parser, introduced with the parseUnknownXobj feature added in version 2.2.4, caused the parser to fail to display proper default prompts when the player entered a command with an unknown word that also required an additional object (for example, if the player typed "put foo", where "foo" was not a valid vocabulary word). This has been corrected; the parser now simply reports the unknown word in these cases. - A bug in the compiler caused a subtle problem when 'modify' was used to modify a class where the original class had vocabulary defined, but the 'modify' did not add any new vocabulary. In such cases, the vocabulary defined in the original class was lost, so any objects derived from the class did not have the original class vocabulary words. This problem has been corrected. Several people encountered this problem when using 'modify' with the 'basicMe' class defined in adv.t. In these cases, the bug prevented the 'Me' object from inheriting the nouns from the original 'basicMe' in adv.t, so at run-time a command such as "look at me" would not work properly. (The work-around, which was to add a noun='' definition to the modified object, is no longer necessary now that the bug has been fixed, but should not cause any problems.) - When restoring a saved game directly from the command line when starting the run-time, the run-time incorrectly invoked the init() function before invoking the initRestore() function. Since the initRestore() function is meant to be called *instead of* the init() function in this situation, this was incorrect behavior. This problem has been fixed; the run-time now invoked *either* init() or initRestore(), but not both, depending on whether the game is started normally or with a restored game on the command line. This problem affected both the character-mode and HTML run-time versions. - A bug in the character-mode run-time caused HTML markups to be misinterpreted if closely preceded by a percent sign that wasn't being used with a format string (such as "%You%"). In such cases, the character-mode run-time sometimes displayed any tags that closely followed the percent sign (within about forty characters of output), rather than interpreting them and removing them from the display as it normally would. This has been corrected. Note that this problem affected only the character-mode run-time when operating with an HTML-enabled game. - The run-time did not correctly handle backslashes in text captured by the outcapture() built-in function; extra backslashes were unnecessarily (and incorrectly) added to escape backslashes. This has been corrected. - The run-time was incompatible with games compiled with version of the compiler before 2.2.0. This incompatibility normally resulted in the game ignoring most commands. The problem has been corrected, so old games should work correctly again. - The character-mode run-time now recognizes the tag when the game is operating in HTML ("\H+") mode. The character-mode version simply suppresses all text displayed between the <TITLE> and corresponding tags. - The character-mode run-time now allows hexadecimal values to be specified in numeric entity markups ("&#nnn"). This change is for compatibility with the HTML TADS run-time, which allows such markups. - The character-mode run-time did not always break lines correctly in text that switched in and out of HTML mode (using \H+ and \H-). This has been corrected. Note that this problem only affected the character-mode run-time (TR32), not the HTML run-time. - The dialItem object in adv.t now has a minsetting property, which specifies the minimum setting for the dial; the default value is 1. ------------------------------------------------------------------------------ 2.2.4 07/20/98 enhancements and fixes - The TADS language has a new construct that allows you to write a method so that it inherits explicitly from a particular superclass. This new syntax adds to the existing 'inherited' syntax, which allowed you to inherit from the superclass according to rules that TADS uses to resolve the superclass that a particular method overrides. The new syntax lets you specify the name of the superclass after the 'inherited' keyword, but is otherwise similar to the normal 'inherited' syntax: inherited fixeditem.doTake(actor); This specifies that you want the method to inherit the doTake implementation from the fixeditem superclass, regardless of whether TADS might normally have chosen another superclass as the overridden method. This is useful for situations involving multiple inheritance where you want more control over which of the base classes of an object should provide a particular behavior for the subclass. - adv.t now includes all of the bugs fixes that Stephen Granade assembled in his BUGS.T file. If you've been using BUGS.T, you should be able to get the same effect by compiling with adv.t alone now. I'd like to thank Stephen for creating this excellent improvement to the original adv.t. - The parser now provides a way to change the player character dynamically during game play. In the past, the parser used the object named "Me" to represent the player character; there was no way to change this. This made it difficult to write a game with different personas for the player character. The parser still uses "Me" as the initial player character, but you can now switch the player character to a different object at any time using the new built-in function parserSetMe(newMe). The argument to parserSetMe() is the new object to use to represent the player character. Another new built-in function, parserGetMe(), allows you to get the parser's current player character object. This function takes no arguments. Note that adv.t and std.t no longer refer to the "Me" object directly in code related to the player character's status (such as inventory or room location descriptions). Instead, adv.t and std.t use the new parserGetMe() function to get the player character object. If you use parserSetMe() in your game, you should be careful not to refer to the "Me" object directly in contexts where you really want the current player character object; use parserGetMe() instead to get the correct object from the parser. Note that existing games should not be affected by this change; if you don't call parserSetMe(), then parserGetMe() will always return the "Me" object, so you can safely use a fixed "Me" object. IMPORTANT NOTE: this feature may be somewhat incomplete, in that additional adv.t support may be needed for some games to take full advantage of this feature. If you try using this feature in your game and you experience any problems or have suggestions on adv.t changes that you would help you use this feature, please contact TADS's author by email at mjr_@hotmail.com. - The compiler is now more tolerant of non-standard newline conventions used in source files. MS-DOS, Macintosh, and Unix each have different, incompatible conventions for how text files represent line endings (and it's likely that there are a few other operating systems with even more different conventions). When copying a file from one type of computer to another, the newline conventions aren't always correctly translated. In the past, the TADS compiler was sometimes unable to process a file that did not use the correct newline conventions for the machine running the compiler. The compiler can now accept most combinations of line ending conventions on any platform that runs the compiler. - The player can now specify a saved game to restore directly from the run-time command line. Use the new -r argument to specify the name of the saved game to be restored: tr -r mygame.sav Note that the full filename must be provided; no default suffix is applied to the -r argument. TADS now stores the name of the .GAM file in each saved game file, and uses this information with the -r option. If you start the run-time, and you specify a saved game file to restore using -r but do not specify a game file, the TADS run-time attempts to load the game file named in the saved game file. You can always specify the name of the game file explicitly, in which case TADS will ignore the game file name stored in the save file: tr -r mygame.sav deep.gam When you use the -r option, TADS loads the game, and then immediately restores the save file. Game authors: note that you can customize the way this new feature works by using the initRestore function; see below for details. - The system now calls a new optional game-defined function named initRestore() if the player specifies a game to restore on the run-time command line. initRestore() is called with a single-quoted string argument giving the name of the saved game file to be restored. If initRestore() is defined, and the player restores a game using the -r option, the system will *not* call your game's init() function. The reason that TADS skips init() in this case is that the player will normally want to skip your game's introductory text when jumping directly to a saved position at the start of the game. (Note, however, that your preinit() function will always run as usual.) If initRestore() is not defined in your game, TADS will simply call the init() function as usual, and will then restore the game itself. This provides compatibility with older games, although it may result in a confusing initial display for the player, because TADS will not be able to show the current location immediately after restoring the game. If possible, you should define initRestore() in your game. std.t provides a default implementation of initRestore() that simply restores the game, and then shows a full description of the current location. If you use std.t in your game, you should be aware that your init() function will not be called when the player restores a game explicitly from the run-time command line, and make any necessary adjustments to your init() routine and the initRestore() routine defined in std.t. If you have code in your init() function that sets up any variables, or if you enter HTML mode in your init() function, you should be sure to call the same code from your initRestore() function, because init() will not be called in this case. The best way to structure your initialization code is to break out the common initialization code into a separate function, and call this function from your normal init() routine as well as your initRestore() routine: initCommon: function { /* set up HTML mode */ "\H+"; ""; } init: function { /* perform common initialization */ initCommon(); /* display opening text messages */ "A long time ago in a cavern far, far away..."; } initRestore: function(fname) { /* perform common initialization */ initCommon(); /* restore the game */ mainRestore(fname); } - To facilitate the new initRestore function, adv.t now isolates the game restoration code that was previously in the restoreVerb object in the new mainRestore() function. You can call this function from your own initRestore() routine, if you want the normal game restore functionality. mainRestore() takes a single argument, which is a single-quoted string giving the name of the save file to restore. mainRestore() restores the saved game, then updates the status line and displays a full description of the current location. - Just as the system calls the game hook function commandPrompt prior to reading a command, the system now calls a new game hook function, commandAfterRead, immediately after reading a player command. The new function looks like this: commandAfterRead: function(code) where 'code' is the same prompt type code that is passed to the corresponding call to commandPrompt. This new function is intended to make it easier to customize certain aspects of your game's user interface. In particular, if you're using special formatting for the player command in the HTML run-time (for example, you want to use a special typeface or font color for player commands), you can use commandAfterRead to turn off the special formatting you turned on in commandPrompt. Each call to commandPrompt is matched by an equivalent call to commandAfterRead, so you can always count on being able to undo any formatting changes you make in commandPrompt by placing the matching formatting commands in commandAfterRead. - A new parser hook provides you with more flexibility in determining how unknown words in a player command are handled. The new parser hook operates during noun phrase resolution (this is the process by which the parser attempts to determine what object should be used for each noun phrase entered by the player in a command). In the past, the parser detected unknown words (i.e., words not defined as vocabulary properties, such as 'noun' or 'adjective', somewhere in the game program) during the initial dictionary lookup step of command processing. Upon encountering an unknown word at this step, the parser simply reported the error ("I don't know the word ") and aborted the command. The parser now marks unknown words found during the dictionary lookup step, but doesn't generate an error at this step. Instead, it tentatively considers such words to be parts of noun phrases, and continues parsing. The parser then attempts to determine the verb and general structure of the command as normal; if this fails, the parser goes back and reports the unknown word as it used to. However, if the parser is able to find a valid sentence structure, it continues to the next step, which is noun phrase resolution. Note that the parser considers unknown words to have a completely neutral part of speech, which means that unknown words are merged with any known nouns and adjectives to which they are adjacent to form the noun phrase. Since the parser doesn't know the word, it can't decide whether to treat it as a noun or adjective, so it simply considers it to be neutral and allows it to combine with whatever other words are present. Noun phrase resolution proceeds as normal until the parser once again encounters one of the unknown words. So, the parser will call the verification (verDoXxx) and validation (validDo, validDoList) routines as usual for any noun phrases containing recognized words, in the usual order (for most verbs, this means that the indirect object is resolved first, then the direct objects). Upon encountering an unknown word, however, the parser checks to see if your game program defines the new parser hook. The new hook is a method on the deepverb object called parseUnknownDobj (to resolve direct objects) or parseUnknownIobj (to resolve indirect objects). The methods are called with these parameters: parseUnknownDobj(actor, prep, iobj, wordlist) parseUnknownIobj(actor, prep, dobj, wordlist) In both cases, 'actor' is the actor object to which the command is directed; 'prep' is the preposition that introduces the indirect object, or nil if there is no preposition; and 'wordlist' is a list of single-quoted strings containing all of the words -- both known and unknown -- making up the noun phrase that contains the unknown word or words. The 'iobj' argument in parseUnknownDobj is the indirect object, if present and known; similarly, the 'dobj' argument in parseUnknownIobj is the direct object, if present and known. The 'iobj' or 'dobj' argument will be nil if there was no other object in the command, or if that object has not been resolved by the time this method is called. When the indirect object is resolved first, as it is with most verbs, parseUnknownIobj will always receive nil for the direct object, since the direct object cannot be resolved until the indirect object is resolved, which is what parseUnknownIobj is doing. These routines can return the following values: nil - This indicates that the routine did not successfully resolve the object, and the system should use the default handling. The parser reports the unknown word error as usual. true - This indicates that the routine has fully processed the command for this noun phrase, and no further parser action is necessary. The parser simply continues processing any other objects in the command, but treats this noun phrase as having no corresponding objects in the command and does no further processing on it. object - The routines can return an object value, which the parser treats as the resolution of the noun phrase. The parser proceeds to apply all normal processing to the object as though the parser had found an appropriate set of dictionary words for the noun phrase matching the returned object. So, all of the normal verification, validation, and action routines are called for the object. list - The routines can return a list containing object values. The parser uses *all* of the objects in the list as the resolution of the noun phrase, and applies all of the normal processing to each object in the list. The effect is the same as if the user had entered the objects in the command separated by commas. If your game doesn't define this new method for the verb, the parser simply uses the default handling, and reports the unknown word error. Here's a simple example that changes the ASK ABOUT command so that we always let the actor respond, even if a word that the player is asking about isn't defined anywhere in the game. To accomplish this, we add a parseUnknownIobj to askVerb. This routine will return a special object, unknownAskIobj, that we define solely as a placeholder for ASK ABOUT with an unknown word. We'll set unknownAskIobj's wordlist property to the list of unknown words, and the object uses the list of words to construct its sdesc. #pragma C- /* * Special object that we use as the indirect object of any * ASK ABOUT command that refers to unknown words */ unknownAskIobj: thing wordlist = [] sdesc = { local i; for (i := 1 ; i <= length(wordlist) ; ++i) { if (i != 1) " "; say(self.wordlist[i]); } } ; /* * For "ask about," use special handling for unknown words so * that the actor can respond directly to the unknown words. */ modify askVerb parseUnknownIobj(actor, prep, dobj, words) = { /* if we're asking about something, have the actor respond */ if (prep = aboutPrep) { /* use our special ASK ABOUT object for the unknown words */ unknownAskIobj.wordlist := words; return unknownAskIobj; } else { /* * it's not ASK ABOUT, return nil to use the * default system handling */ return nil; } } ; - The compiler can now capture all of the strings used in a game to a text file. This can be useful for spell-checking your game, since it gives you a listing of all of the text in the game, separated from your source code. To capture strings during compilation, use the -Fs option to specify the output file: tc -Fs mygame.lis mygame.t This compiles mygame.t, producing mygame.gam as usual, and writes all of the strings in the source code to the file mygame.lis. - The character-mode run-time now provides improved, but still limited, HTML support. As in version 2.2.3, after you display an "\H+" sequence, the character-mode run-time becomes sensitive to HTML tag and character markup sequences. However, whereas version 2.2.3 ignored all tags and ampersand sequences, the new version now obeys certain markups. In particular, the character-mode run-time will now process the following tags:
ends the current line; additional
's display blank lines. The HEIGHT attribute is accepted, and produces results that are consistent with the graphical handling.

displays a blank line.

displays a blank line and start boldface mode. and end boldface mode. indents to the next tab stop (exactly like "\t") and accept the ALT attribute, and display the text of the ALT attribute value in place of the image or sound. No other decoration is added; the ALT value is simply included in the text display as though it had appeared as ordinary text.
starts a new line, displays a line of dashes, and starts another new line The character-mode run-time ignores all other tags. As before, unrecognized tags are simply removed from the text entirely. In addition, the ampersand character-code markups are now supported. Since the DOS character set does not contain all of the characters in the HTML character set (ISO Latin 1), some ampersand markups are displayed as blanks, and others are displayed as approximations. The following characters are displayed correctly: &endash; &emdash; ¡ ¢ £ ¥ ¦ ª « ¬ ° ± ² µ · º » ¼ ½ ¿ Ä Å Æ Ç É Ñ Ö Ü à á â ä å æ ç è é ê ë ì í î ï ñ ò ó ô ö ÷ ù ú û ü ÿ The following are displayed using approximations: „ is displayed as a normal double-quote ‹ is displayed as '<' ‘ is displayed as a normal single-quote ’ is displayed as a normal single-quote “ is displayed as a normal double-quote ” is displayed as a normal double-quote › is displayed as '>' ­ is displayed as '-' ´ is displayed as a normal single-quote ¸ is displayed as a comma × is displayed as an 'x' ‚ is displayed as a normal single-quote ß is displayed as a "beta" ™ is displayed as "(tm)" © is displayed as "(c)" The following are displayed using the corresponding unaccented character, since the accented version of the character is not in the DOS character set: Ÿ À Á Â Ã È Ê Ë Ì Í Î Ï Ò Ó Ô Õ Ø Ù Ú Û Ý ã õ ø ý The following are not supported at all, since the DOS character set has no equivalents for these characters. Each of these markups is rendered as a single space. † ‰ Œ œ ¤ § ¨ ® ¯ ³ ¶ ¹ ¾ Ð Þ ð þ - The new built-in function morePrompt() allows your game to explicitly display the system MORE prompt. You can use this for such special effects as a dramatic pause prior to a chapter change. The new function takes no arguments and returns no value. - The built-in file manipulation functions now allow you to read and write text-mode files. Files written in text mode can be used by other applications as ordinary text files. To use text-mode files, you must specify the new 't' file mode suffix in fopen (for symmetry, a new 'b' mode suffix, for binary mode files, is also allowed, but for compatibility with past versions, binary mode is the default if no mode suffix is specified). You can use the 't' suffix with 'r' (read) and 'w' (write) modes; 't' is not currently allowed with 'r+' or 'w+' modes. When a file is opened in text mode, fwrite() can only be used with string values. Strings passed to fwrite() can contain the escape characters '\t', '\n', and '\\'; other escapes are not allowed. '\t' is translated to a tab, '\n' is translated to a newline (using the appropriate local conventions for the current system), and '\\' is translated to a single backslash. fwrite() does NOT add any newlines to the text you provide, so you must explicitly include any newlines you want to write to the file. Because TADS obeys local newline conventions, fwrite() always produces the correct sequence of characters for the current machine when you include '\n' in a string, so you don't have to worry about how newlines are handled on each platform. fread() always reads a line of text from the file. If the end of the file is not reached, the line returned will end with a '\n' sequence (as with fwrite(), fread() translates newlines according to local conventions, and always returns the TADS '\n' sequence to represent a newline in the file). If fread() encounters the end of the file in the middle of a line, it will return the text up to the end of the file, with no trailing newline. The subsequent call will return nil to indicate that the end of the file has been reached. - Because TADS now has several ways of reading and writing files, some game players may be uncomfortable about the possibility that a malicious game author could harm their systems by writing a game that, for example, modifies their AUTOEXEC.BAT files. To address any concerns that players may have, TADS now provides a "file safety level" setting. This is an optional setting that allows the player to control the amount of access that a game has to files on the system at run-time. The file safety level is set through a command line option (note that HTML TADS also provides this setting through the "Preferences" dialog). Use the new -s option to specify one of the possible safety levels: -s0 (default) minimum safety - read and write in any directory -s1 read in any directory, write in current directory -s2 read-only access in any directory -s3 read-only access in current directory only -s4 maximum safety - no file I/O allowed If the game attempts a file operation that is not allowed by the current safety level, the fopen() function returns nil to indicate that the file open failed. These options affect only explicit file I/O operations performed by the game. Operations handled by the system, such as saving and restoring games and logging a transcript to a file, are not affected by the file safety level setting. - The TADS resource manager (TADSRSC) now supports building external resource files for HTML TADS games. Refer to the resource file documentation (RES.HTM) that accompanies the HTML TADS distribution for details. - The new TADS built-in function systemInfo() allows your game to determine programmatically whether the TADS run-time that is currently executing the game has certain capabilities. This function is called like this: result = systemInfo(__SYSINFO_xxx); where __SYSINFO_xxx is one of the pre-defined constants (defined automatically by the compiler) listed below. The result tells you about the particular run-time that is executing your game, so you can customize the game, if you wish, for certain system capabilities. For example, you might want to change some text in your game slightly depending on whether sound effects can be played. Before calling systemInfo() with any of the other codes, you *must* check to see if systemInfo() itself is supported. Versions of the run-time prior to 2.2.4 do not support this function, so the return codes are meaningless. Fortunately, you can determine if systemInfo() is itself supported using the following code fragment: if (systemInfo(__SYSINFO_SYSINFO) = true) { /* * systemInfo IS supported by this run-time - other * systemInfo codes will return meaningful results */ } else { /* * systemInfo is NOT supported by this run-time */ } Only one version of HTML TADS (version 2.2.3) was ever released without systemInfo support, and this was the first public beta release. So, it should be fairly safe to assume that any system that doesn't support systemInfo() doesn't support any of the HTML TADS features. The __SYSINFO_xxx codes are: __SYSINFO_VERSION - returns a string with the run-time version number. This will be a string such as '2.2.4'. __SYSINFO_HTML - returns 1 if HTML markup is supported, 0 if not. 0 indicates that this is a standard text-mode run-time system. If this returns 0, then JPEG, PNG, WAV, MIDI, WAV/MIDI overlap, WAV overlap, and the images, sounds, music, and links preference items can all be assumed to be unsupported, since these features are only provided by HTML TADS. Note that __SYSINFO_HTML returns 0 (HTML not supported) for the character-mode run-time, even for the newer versions of the run-time that do provide some limited HTML support, because this information code is intended to indicate whether the full HTML feature set is supported. The character-mode version only supports a limited subset of HTML features, so it indicates that HTML is not supported. __SYSINFO_OS_NAME - returns a string with the name of the operating system on which the run-time is currently executing. (The name is the same as the string that the compiler uses to pre-define the __TADS_SYSTEM_NAME preprocessor symbol, but this lets you determine what system is executing at run-time, rather than the system that was used to compile the game.) __SYSINFO_JPEG - returns 1 if JPEG images are supported, 0 if not. __SYSINFO_PNG - returns 1 if PNG images are supported, 0 if not. __SYSINFO_WAV - returns 1 if WAV sounds are supported, 0 if not. __SYSINFO_MIDI - returns 1 if MIDI music is supported, 0 if not. __SYSINFO_MIDI_WAV_OVL - returns 1 if MIDI and WAV sounds can be played back simultaneously (overlapped), 0 if not. If this returns 0, it means that WAV playback will suspend MIDI playback. __SYSINFO_WAV_OVL - returns 1 if multiple WAV sounds can be played back simultaneously (overlapped), 0 if not. If this returns 0, it means that any WAV played back in a foreground layer will suspend a WAV being played in any background layer. __SYSINFO_PREF_IMAGES - returns 1 if the user preferences are set to allow images to be displayed, 0 if not. Note that, even if this preference is set so that images are not displayed, the preferences for JPEG and PNG images will still return 1 for each of those image types that are supported by the platform. The image format codes (__SYSINFO_PNF and __SYSINFO_JPEG) indicate whether the image formats are supported at all, whereas this preference code indicates whether images are currently allowed for display. __SYSINFO_PREF_SOUNDS - returns 1 if the user preferences are set to allow digitized sound effects (WAV files) to play back, 0 if not. __SYSINFO_PREF_MUSIC - returns 1 if the user preferences are set to allow music (MIDI files) to play back, 0 if not. __SYSINFO_PREF_LINKS - returns 0 if the user preferences are set so that links are not highlighted at all (in which case they'll be displayed as ordinary text; they won't be highlighted as links and won't be active for the mouse); returns 1 if links are highlighted and active; and returns 2 if links are set to a "hot key" mode, in which case links aren't highlighted except when the user is holding down a special key to explicitly illuminate the links. - TADS has a new mechanism for better supporting non-US character sets in a more portable fashion. The TADS Compiler, Run-Time, and Debugger (including the HTML TADS versions) now provide an option that lets you specify a character set translation to use for your game. The character set translation allows your game to use a standardized character set, such as ISO Latin 1, but still run on any system by providing a mapping from your game's internal character set to the native system character set for each player's system. Refer to CHARMAP.HTM for information on how to use this new feature. - The parser now accepts a comma immediately after "oops" or "o". - The parser now allows strings in player commands to be entered with single quotes as well as double quotes. So, the following commands are now interchangeable: type "hello" on keyboard type 'hello' on keyboard Note that this change means you can't create a vocabulary word that starts with a single quote, such as "'til", although you can still use a single quote within a vocabulary word, as in "bob's". The parser treats a single quote within a word as part of the word, but it now treats a single quote at the start of a word as a quotation mark intended to mark a string. - In adv.t, 'q' is now a synonym for 'quit' for player commands. - The #define preprocessor directive did not work correctly in past versions when the "-case-" option (for case-insensitive compilation) was used. In particular, preprocessor symbols defined with capital letters were not matched. This has been corrected so that #define works properly when "-case-" is used. - The #define preprocessor directive sometimes did not work correctly with long source lines (over about 128 characters). This has been corrected; #define now works correctly with long input lines, which are often necessary for lengthy macro expansions. - The TADS error code 1026, "wrong number of arguments to user function," now includes the name of the function or object.method that was the target of the invalid function call. This can be helpful in situations where the TADS parser calls the function directly, since there was previously no simple way of determining which function was being called in these cases. Note that you must compile your code with debugging enabled, and run under the TADS Debugger, to see the extra information in the error message, since only the Debugger can load a game's symbol table, and can only do so if the game was compiled for debugging. - A bug that caused debugger crashes under certain obscure circumstances has been fixed. In the past, if a game used the "replace" keyword to replace a method in an object, tracing into other methods in the same original object (the base object, before applying the modifications using the "modify" construct) could sometimes crash the debugger. This has been corrected. - The run-time is now more consistent about converting special escape sequences in strings obtained externally, such as from the input() function or from quoted strings in a player command. In particular, when these strings contain characters such as backslashes or newlines, the run-time now consistently converts these characters into a backslash sequence (a backslash turns into '\\', a newline turns into '\n', and a tab turns into '\t'). - adv.t and std.t are now considerably more consistent in terms of indenting and punctuation style. ------------------------------------------------------------------------------ 2.2.3 03/25/98 corrections and enhancements, HTML - This new version of the TADS compiler and run-time supports HTML TADS, the new HTML-enabled version of the TADS run-time. You should use this new version of the compiler to generate games for the HTML TADS run-time. - Fixed a parser bug that caused various problems when the player issued a command to an actor, but used a pronoun to refer to the actor: "him, go east." (This always failed, but the type of problem depended on the platform; in most cases, a TADS error such as "error loading object on demand" resulted.) - Fixed up the indenting in adv.t. At some point adv.t was detabified with an incorrect tab size setting, which randomized the indenting. The file has now been fully re-indented with spaces; since it has no tabs in it any more, it should look the same on any editor, regardless of the local tab size setting. - Added 'n', 's', and the other one-letter direction words to dirPrep's preposition vocabulary list in adv.t. This corrects the problem that a command such as "push box n" was not accepted, even though "push box north" was. - A new parser error handling function has been added. The new function, parseErrorParam(), is similar to parseError(), but provides additional arguments that provide the actual values of the "%" parameters in the error message: parseErrorParam: function(errNum, errMsg, ...) The errNum and errMsg parameters are the error code number and default error message text string, just as with parseError(). The additional arguments give the values of the "%" parameters; for example, for message 2, "I don't know the word '%s'", the first extra parameter will be a string with the word that the parser didn't recognize. If your game defines a parseErrorParam() function, the parser will call this function rather than parseError(); parseError() is essentially obsoleted by the new function. However, if your game does not contain a parseErrorParam() function, the parser will call parseError() as before. - Fixed a parser bug: if an object had the same name as a verb (for example, the defined an object with a noun of 'lock'), and the player attempted to issue a command to an actor starting with the re-used verb, the parser incorrectly issued the error "There's no verb in that sentence!" ------------------------------------------------------------------------------ Please consult TADSV222.DOS for information on releases from 2.1.1 through 2.2.2, and refer to TADSV200.DOS for information on releases prior to 2.1.1. See the note at the top of this file for details on where to find these older release notes files.