Recent Changes to the TADS 3 Library

This is a list of changes to adv3, the TADS 3 library. Changes are listed in reverse chronological order (the most recent changes are first).

3.0.16

Released 4/10/2008

The new class SimpleLister provides simplified interfaces for creating formatted lists of items. This class is useful when you just want to create a textual listing, using the standard serial-comma generation and so on, without all the hassle of specifying a sense environment, point of view, etc. The class also has a method that creates a text string for a listing (rather than displaying the list directly, which is what the base Lister normally does).

Two concrete instances of SimpleLister are provided, to further simplify particular listings: objectLister, which can be used to format a list of simulation objects; and stringLister, which can be used to format a list of string values.

Commands of the form "X, Y", where both X and Y were words not in the game's dictionary, caused a run-time error ("nil object reference"). This was due to a library bug in the code that resolves the actor object when giving orders to an actor ("BOB, GO EAST"). This has been corrected.
The Goal object has a new property, goalFullyDisplayed, that the hint system automatically sets to true upon displaying the last hint in the Goal's menu list. This can be used, for example, to close a hint for a red herring after the player's seen the full list of hints; this might be desirable to remove clutter from the menu, since the player probably won't worry about an object again once it's been revealed that it's just a red herring.
The templates for TravelMessage, NoTravelMessage, and DeadEndConnector now accept an event list (given as a list in square brackets) in lieu of a double-quoted message string. These classes were already mixable (via multiple inheritance) with EventList, so this change simply makes it more convenient to define instances when using this capability.
Top-level rooms (i.e., Room objects) didn't properly handle ENTER commands (e.g., ENTER HALLWAY) when attempted from a separate top-level room connected via a sense connector. The ENTER handling for Room incorrectly assumed that only one top-level room was in scope at a time, which isn't true when rooms have sense connections. The handler also failed to handle the simpler situation where the actor was in a nested room within the target room.

The Room dobjFor(Enter) handler has been improved to handle such situations. The new handling is as follows:

In the past, when NestedRoom.checkMovingActorInto() decided it had to perform an implied action to move the actor into the nested room, the routine assumed that the action failed if the actor didn't end up in the nested room and in the room's default posture. This condition does hold for the library NestedRoom subclasses when the defaults are inherited, but overriding either the default posture or the implied command for a particular instance can make the condition fail. When the condition fails, the routine infers that the implied action failed, and terminates the whole command.

This condition - specifically, the test for posture - is overly strict. The routine isn't documented as guaranteeing the default posture, and in fact it never did guarantee the default posture anyway, as the implied action was always bypassed if the actor's location was initially correct, regardless of posture.

So, the condition has now been changed to remove the default posture check. The implied command (if any) is now considered successful if the actor is directly in the nested room afterward.

The Decoration class now overrides the SEARCH action so that it uses the standard Thing handling, to be consistent with the way LOOK IN is handled. In the past, SEARCH instead was handled by the Decoration catch-all verb handler, which shows the generic "that's not important" message. This led to different default Decoration behavior for SEARCH vs LOOK IN, which was inconsistent with the library's usual default treatment of the two actions as synonymous.
The English library's typographical output filter object (typographicalOutputFilter) ordinarily adds an "en" space (a space that's slightly wider than a normal inter-word space) after any sentence-ending punctuation, such as a period or question mark. This produces looser spacing between sentences, which some people find easier to read. However, it had an undesirable side effect, which was to add the same spacing after honorific abbreviations like Mr. and Mrs. - these look exactly like sentence endings, as far as the simple substitution rule was concerned.

The filter now specifically looks for a pre-defined set of abbreviations, and suppresses the extra spacing when it finds one. The abbreviations are given by the property typographicalOutputFilter.abbreviations - to change or add to the set of abbreviations, modify typographicalOutputFilter to change this property. (Note that this property is only evaluated once, at program start-up, so changing it on the fly won't have any effect. If you do want to modify the set of abbreviations dynamically, you'll have to instead update typographicalOutputFilter.abbrevPat to use a new RexPattern object with the new set of abbreviations.)

DistanceConnector now allows moving objects through the connector via moveInto - in particular, DistanceConnector now overrides checkMoveThrough() to return a "success" indication. In the past, DistanceConnector inherited checkMoveThrough() from SenseConnector, which disallowed a move via moveInto() if the distance connector was involved.

In the abstract, there's usually no good reason that an object can't be moved through a distance connector. The only reason to disallow it might be that the distance involved is so great that it would take an impractical amount of time to complete the move.

More to the point, though, player commands will generally disallow this kind of operation for reasons of reachability - a touchObj precondition is involved for any basic command that moves an object from one container to another (TAKE, DROP, PUT IN, PUT ON, etc), and that precondition will fail separately because the distance object is unreachable. Because of this, a moveInto() involving a distance connector is almost certainly due to a programmatic (scripted) operation, rather than a basic player command. In the past, you could work around this by calling moveIntoForTravel() instead of moveInto(), but this was a needless complication. This change allows moveInto() to succeed even if a distance connector is involved.

The ShuffledEventList object has a new property, suppressRepeats, that lets you control whether or not a given event can occur twice in a row. This property corresponds to the suppressRepeats property of the ShuffledList object.

By default, this is set to true, which yields the former behavior. You can set this to nil if you don't want to suppress repeats. With very small lists (of three or four elements), the suppression of repeats sometimes makes the result sequence look fairly un-random, because the no-repeats constraint reduces the number of available permutations. If you see output for a small event list that doesn't look random enough, you might try setting suppressRepeats to nil to see if that produces more pleasing results.

ShuffledList now ignores suppressRepeats for a two-element list. If the list has only two elements, and repeats aren't allowed, the list will necessarily yield the predictable sequence A-B-A-B..., which defeats the purpose of the shuffled list.
The Lockable class's dobjFor(Open) check() method now inherits the default handling. Since Lockable is designed to be used as a mix-in class, this addition ensures that any additional Open checks inherited from other superclasses are properly invoked.
The comment pre-parser object (commentPreParser) now sets a runOrder value that's lower than the default, so that it runs before other pre-parsers that inherit the default runOrder. In most cases, it's unnecessary (and even potentially problematic) to run comments through other pre-parsers, since most are designed to parse the usual sorts of command syntax, not the free-form text allowed in comments. Running the comment pre-parser first effectively bypasses other pre-parsers when a comment is encountered, since the comment pre-parser halts further processing of the command when it detects that the command is actually a comment.
In the past, if an actor returned nil from obeyCommand(), the parser ignored the command for the purposes of the AGAIN command. A subsequent AGAIN command in this case simply repeated the previous command - the one just before the refused order. This has been corrected; the parser now remembers a command for AGAIN even if the actor to whom the command is directed refuses it.
Thing.setGlobalParamName() didn't correctly delete any previous name from the global table. (This would only have been a problem if you used the method more than once on a particular object, to change the object's global parameter name on the fly, which seems highly unlikely.) This has been corrected.
3.0.15.3

Released 11/2/2007

There are no library changes in this release.
3.0.15.2

Released 9/13/2007

NonObviousVerifyResult.resultRank is now defined as the same value as IllogicalVerifyResult.resultRank. This makes the parser treat illogical and non-obvious objects as equivalent in cases of ambiguity, so that the parser won't choose one over the other. In the past, non-obvious objects were ranked lower than illogical objects, which caused the parser to choose an illogical object over a non-obvious object in cases of ambiguity. This didn't quite make sense; for the purposes of choosing among ambiguous objects, non-obvious objects really should be treated as equivalent to ordinarily illogical objects, since the point of the non-obvious designation is that the object appears at first glance to be illogical for a given verb.
The parser now incorporates Michel Nizette's vocabLikelihood extension. This extension lets each object set an "intrinsic likelihood" value that affects how the parser chooses among ambiguous objects. When the parser can't tell the difference between two objects using the normal 'verify' procedure, it compares the vocabLikelihood properties of the two objects, and chooses the one with the higher value if they differ. This makes it easier to fine-tune the disambiguation process to reduce unnecessary prompting.
SensoryEmanation.endEmanation() now resets displayCount to nil. displayCount was documented as being automatically reset when a period of continuous presence ended, but in the past no actual resetting was done. We set the value to nil, rather than to 0 or 1, to make it more obvious (when debugging, for example) that the object is not currently in scope.
The English library now defines suitable custom objInPrep, actorInPrep, and actorOutOfPrep values for Surface, Underside, and RearContainer. This ensures that the default messages produce reasonable relational descriptions when referring to the contents of these specialized container types. (In the past, only Surface.objInPrep was suitably customized.)
BasicOpenable.tryImplicitRemoveObstructor() now only attempts an implied Open if the object is closed. When the object is already open, the routine now does nothing. This change is necessary because objects can conceivably create an obstruction even if they're already open: once the object's open, if it's still creating an obstruction, another implied Open would be pointless (and, in fact, could cause an infinite loop).
The new Thing method isOrIsIn(obj) returns true if 'self' is either inside 'obj' or equal to 'obj'. This is useful for cases where you want to check if a given object is anywhere within another object's containment tree, including the other object itself.
When a MultiLoc object is the point-of-view object in a sense calculation, it now connects all of its multiple containers to the scope in addDirectConnections(). In the past, a MultiLoc's containers were only considered part of the scope when the point of view was inside the MultiLoc, but it makes sense for the MultiLoc itself to be the same way, since it inherently "sees" all of its containers.
LocateInParent is now defined as a class. (It was always meant to be, but the definition lacked an explicit 'class' keyword in past version.)
3.0.15.1

Released 7/19/2007

Compatibility warning: The message property cannotTalkToSelf has been renamed to cannotTalkToSelfMsg, for consistency with other message property names. (You should search your existing game code and update any references to the old name to use the new name instead.)

The new gameMain property beforeRunsBeforeCheck lets you control the relative ordering of the "check" phase and "before" notifiers.

In the past, the library always ran the "before" notifiers - beforeAction and roomBeforeAction - before running the check() handlers for the objects involved in the action. To ensure compatibility, the default setting for the new property is beforeRunsBeforeCheck = true - this causes "before" to run before check(), as it always has.

However, in many ways, it's more logical and practical to run the check() phase first first, and then run the "before" notifiers. This ordering allows the "before" handlers to assume that the action is more or less committed to running to completion, since they know that the check() phase has already tested the action and allowed it to proceed. The most common use of "before" handlers is to carry out side effects of an action, so by running all of the check() tests first, the "before" handlers can more confidently invoke their side effects, without worrying that the action will later fail due to a check() condition.

Now, you can never truly call an action "committed" until it's been fully carried out, since even a "before" handler can conceivably cancel a command (with "exit", for instance). You could set up a situation where an action is affected by two "before" handlers, and the one that runs later cancels the command - thus invalidating any assumption in the first one that the command is committed. However, this is relatively unusual, and in any case it's under your control: if you don't use "before" handlers to cancel commands, it won't occur.

If you want to use the new alternative ordering, where "check" runs before the "before" notifications, simply set beforeRunsBeforeCheck to nil in your gameMain object. Some authors have tested this ordering with existing games and found it to work well with existing code, but be aware that you could trigger subtle changes - you might have unknowingly created dependencies on the relative ordering of check() and "before" that will only show up under certain conditions. We therefore recommend using the new alternative ordering only for new code or for projects that are relatively early in their development cycle, to ensure thorough testing of the new ordering.

ThingState now allows tokens to be shared among multiple states associated with the same object. In the past, state words had to be unique among an object's states: it wasn't possible to have a set of states such as "unpainted", "painted red", and "painted blue", because it was invalid for "painted" to appear in two states. This sort of sharing is now allowed.

The old rule was that a word could match an object only if the word didn't appear in any of the object's other (i.e., non-current) ThingState token lists. The new rule is that a word can match an object if (a) the word appears in the object's current ThingState token list, or (b) the word doesn't appear in any of the object's other ThingState token lists. Condition (a) is what's new; it allows words to be shared among states by allowing a word to match the current state as long as it's in the current state's list, regardless of whether it appears in other states' lists.

In the English library, when a command triggers a chain of several implied actions, the announcement for the implied actions now adds the word "then" in each conjunction: "(first standing up, then taking the box, then opening it)". In the past, "then" appeared only in the final conjunction; most people find it reads more naturally to use "then" in each conjunction.
The English library now uses the adjustDefaultObjectPrep() mechanism to generate implied action announcements. This means that verbs like SitOn and GetOutOf will use object-specific prepositions when they're announced as implied actions: "(first sitting on the bench)" vs "(first sitting in the car)".
The actions GetOutOf and GetOffOf now adjust the prepositions in their generated verb phrases according to the actorOutOfPrep definition for the direct object.
AccompanyingInTravelState.initiateTopic() now defers to the next state object. Since the in-travel state is designed to be ephemeral, it's unlikely that one of these would actually contain its own initiated topics, so in almost all cases the desired topic would come from the state that the actor will be in following the travel.
The library now defines a simple template for ShuffledList. The new template takes a single list parameter giving the value list.
ListGroupCustom now defines groupDisplaysSublist = nil by default. This type of group is intended mainly for cases where you want to display a collective message to describe the group, rather than listing its elements individually, so we usually don't want the inclusion of a ListGroupCustom message to trigger the "long list" format (i.e., with semicolons) in the overall list. This change makes this behvior the default.
Non-physical CollectiveGroup objects (i.e., those with 'nil' locations) didn't work when the associated individuals were MultiLoc or SenseConnector objects. This has been corrected.
Actor.knowAbout() now considers an object to be known if it's currently in sight. In the past, an object was known if it was specifically marked as known, or it was marked as having been previously seen. For the most part, an object that's currently visible will also be marked as having been previously seen, since this marking occurs whenever a Look command or travel to a new location lists an object. However, if an object comes into scope (via moveInto) while an actor is in a given location, the object won't be marked as having been seen until the next Look command or subsequent travel away from and then back to the location.

This change corrects a subtle problem that involved possessive phrases in commands. In the past, an object that just came into scope was excluded from matching a possessive noun phrase under certain circumstances, such as in the response to a disambiguation question. This should no longer occur.

In the past, the library seemed to allow commands like PUSH TV INTO BOX (i.e., commands to push a pushable object into a nested room), but on closer inspection it didn't really carry them out: instead, it carried out only the underlying basic travel command, without actually moving the object supposedly being pushed around. The library now rejects PUSH INTO nested room commands with a new message, &cannotPushObjectNested ("You can't push the TV there").

The library rejects these commands by default because we think it'll be relatively rare that games will need to allow them. Given that, it didn't seem worth complicating things by adding a bunch of new methods to provide generalized support, and we also didn't want to introduce that much new code at the current point in the release cycle.

However, the library does now provide a new hook that will make it easier for games to implement PUSH INTO nested commands if they need to. The new hook will also make it possible to implement riding vehicles into nested rooms, which wasn't previously possible. The new hook is a new Traveler method, travelerTravelWithin().

This new method is called on the Traveler object when a nested-room travel command (GET IN, STAND ON, SIT ON, LIE ON, PUSH X INTO, etc) is performed. In simple cases, the Traveler is simply the Actor performing command. However, if the actor is riding in a Vehicle, the Traveler is the Vehicle object; and if the command is a PUSH travel command, such as PUSH TV INTO BOX, the Traveler is a special ephemeral object, of class PushTraveler, created just for the command.

The library provides default implementations of the new hook method for Actor, Vehicles, and PushTraveler. For Actor, the method simply does the same thing that travelWithin() used to do, so existing code will work as it did before. For Vehicle, the method calls the same method on the Actor, so again it will work as it used to. For PushTraveler, the method simply terminates the command with the new message &cannotPushObjectNested.

If you want to allow pushing objects into nested rooms, you'll need to modify PushTraveler.travelerTravelWithin() to do basically the same thing that PushTraveler.travelerTravelTo() does. You'll probably want to set up a new set of notification methods parallel to beforeMovePushable() and movePushable() - you could just use those same routines, but you'll probably want to set up new custom nested-room versions instead, since the conditions and messages will probably need to be different for nested rooms from those used for room-to-room travel.

Note that you could also take advantage of the new method to allow an actor to ride a vehicle into a nested room. The default handling in Vehicle defers to the underlying Actor, and the actor performs the travel as though the Vehicle were any other nested room - which means that the actor gets out of the vehicle, then gets into the new nested room. If you instead wanted this type of command to mean "ride vehicle into nested room," you can override travelerTravelWithin() on the vehicle, so that the method moves the Vehicle itself into the new nested room. (You'll also have to set up preconditions that move the vehicle to the proper staging locations using the same rules that are used for actors, which could take a little work - that's the main reason the library doesn't support it out of the box yet.)

In the past, if an NPC was using AccompanyingState to follow around the player character, and the pair went through an AutoClosingDoor, the door was reported as having closed twice during the turn - once for each actor going through. AutoClosingDoor now skips the auto-closing step if the actor going through is in an accompanying state.
The parser's "OOPS" mechanism had a problematic interaction with literal phrases that showed up under certain conditions. In particular, if the player entered a command that caused some kind of follow-up question, such as a disambiguation query ("which book do you mean...") or missing-object prompt ("what do you want to unlock it with?"), and the player chose to ignore the follow-up question and instead just enter a new command, and the new command contained a literal phrase ("type asdf on keypad", say), the OOPS mechanism was overly aggressive in deciding the command contained a typo.

The root of the problem was that follow-up questions are parsed according to their own special grammars, which are much more limited than the full grammar used at the main command prompt. Normally, when the player responds with something that doesn't match one of these special grammars, the parser assumes the player intended to bypass the follow-up question and just enter a new command. However, if parser manages to find a non-dictionary word in the player's input, it invokes the OOPS mechanism to see if the player actually intended to answer the follow-up question but made a typographical error entering the response. The problem is that if the player entered a literal-phrase verb, the presence of a non-dictionary word doesn't necessarily imply a typographical error. For normal commands (as opposed to follow-up questions), this isn't an issue, because the parser finds the literal when matching against the full grammar. For follow-up questions, though, the parser never noticed the literal-phrase interpretation of the input, since it wasn't matching against the full grammar, so it prematurely decided that the input as mis-typed.

The OOPS mechanism now makes an extra check when presented with a non-dictionary word in the response to a follow-up question. Before deciding that the input really does contain a typo, the OOPS mechanism first checks the input to see if it matches anything in the full command-line grammar; if the parser finds a match, it assumes that the entry was meant as a brand new command, and so it bypasses the follow-up question and re-parses the input as a new command.

In the past, calling getOrigTokenList() on an EventAction object caused a run-time error. This was because EventActions don't have the token list data that the parser normally attaches when creating an Action to represent a parsed command line, which in turn is because these actions aren't created from command parsing but are instead "synthetic" actions, created internally by the library for bookkeeping purposes. (This same problem might have affected other synthetic actions as well, but it's only known to have shown up with EventActions.)

This has been corrected. Action.getOrigTokenList() now checks to make sure there's a parser token list attached to the action, and if not, the method return the parent action's token list; and if there's no parent action, the method simply returns an empty list.

HermitActorState now sets its property limitSuggestsion to true by default, to prevent the TOPICS command from displaying any topic suggestions from the actor or conversation node. This is desirable because the hermit state effectively blocks any topics defined outside the state: the whole point of the state is that it makes the actor essentially unresponsive to conversational overtures. So there's generally no point in offering topic suggestions while the actor is in the state.

In some cases, you might want to override this for a particular hermit state object. In particular, if it's not outwardly obvious that the actor will be unresponsive, you might still want to allow suggestions, since the player has no way of knowing that the actor won't respond to questions until she attempts asking them. Similarly, if the hermit state is expected to persist for a short time only, you might want to continue to allow suggestions so that the player knows there are useful topics to explore when the actor becomes responsive again.

ByeTopic now overrides impliesGreeting to suppress any implied HELLO greeting. This means that if the player explicitly says GOODBYE to an NPC, and no conversation is in progress, the goodbye response will be displayed by itself, without an implied greeting exchange. In the past, ByeTopic inherited the default 'true' value for impliesGreeting, so saying GOODBYE to an NPC outside of a conversation caused an implied greeting, followed immediately by the explicit goodbye response. In most cases, this ended up looking pretty strange - not only was the self-canceling HELLO-GOODBYE exchange odd on its face, but in most cases the implied greeting also looked redundant from the player's perspective, because a player tended to say GOODBYE explicitly only when she was under the impression that some kind of conversational interaction was already under way.
In the past, conversation-flow problems sometimes occurred if a topic changed its "isConversational" status as a side effect of showing the response for the topic. This was because ActorTopicDatabase.showTopicResponse() waited until after invoking the response to determine whether the response should affect the conversation flow, so the status change side effect caused showTopicResponse() to use the new setting - meant for the next response - rather than the setting for the response it actually showed. This has been corrected; showTopicResponse() now makes its determination based on the isConversational status as it stands just before invoking the response.
The finishGameMsg() function now explicitly runs the score notifier daemon before it displays the end-of-game message. This ensures that the usual score-change message is displayed if any points were awarded in the course of the action that triggered the end of the game. Without the explicit invocation, the notifier daemon wouldn't typically get a chance to show a notification for the final point award: the notification is normally shown just before the next command prompt after points are awarded, and at the end of the game there's not usually another command prompt forthcoming.
In the English library, inlineListingContentsLister didn't always generate correct verb agreement for its prefix message for a "wide" listing ("You are carrying tongs (which contains ...)"). This has been corrected.
In the past, searching a RoomPart nominally containing a RoomPartItem sometimes caused a "nil object reference" run-time error. This was due to a missing point-of-view object setting; the code where the error occurred now applies a default POV of the command actor when no explicit POV is set.
Container and Openable objects now apply a touchObj precondition to the Search action. This requires that the actor be able to reach the object in order to search it. This change is intended to better enforce the idea that searching a container involves some actual physical manipulation, such as shifting the contents around to look behind or under things, momentarily removing contents to see what's underneath, and so on. The actor at least has to be able to reach the object to perform this kind of manipulation.
In the past, AGAIN didn't work properly when used to repeat a FOLLOW command involving an NPC using the "guided tour" mechanism (the TourGuide and GuidedTourState classes). This was due to a subtle turn-timer problem with AGAIN that affected commands like FOLLOW that use nested or replacement actions in the course of their execution. This problem was observed in the FOLLOW/AGAIN situation, but could also have affected other commands. The root problem has been corrected.
3.0.15

Released 3/8/2007

The Follow action has always adjusted the scope list to include any NPCs that the target actor remembers having seen leave, whether or not they're still within sight. This allows you to FOLLOW a character who's no longer present - the whole point of FOLLOW would be defeated if this weren't possible, after all. However, in the past, this scope list expansion created duplicate entries in the scope list if an actor with a "follow memory" was also still physically present - the actor was added to the scope list once for ordinary visibility, then again for the "follow memory." This duplication caused problems in some situations, since other parts of the library expect the scope list to contain only unique entries. The Follow action now ensures that the list remains unique after its additions.
3.0.14

Released 2/9/2007

There are no library changes in TADS 3.0.14.

3.0.13

Released 1/19/2007

The adv3 English library has a new option setting for the parser's truncation length. The truncation length has always been customizable in the low-level system objects that the parser uses to look up and match vocabulary words, but the library didn't formerly provide a good way for games to override its truncation length setting. In effect, the truncation length was hard-coded into the English library, and the only way for authors to change it was to edit en_us.t; most authors would rather not do that because of the hassle of merging their own changes into future library updates.

The new setting is in gameMain.parserTruncLength. The default setting is 6 characters - this is the same as the English parser's old hard-wired setting, so existing code will behave the same as it did in the past.

As part of this change, the new method languageGlobals.setStringComparator() lets you change the active string comparator dynamically. You can use this method if you want to change the truncation length, or other comprator settings, on the fly during the game.

The English library now allows a TAction VerbRule to specify a custom preposition for a default object announcement, overriding the preposition coded in the verbPhrase. This is useful for a few verbs where the preposition tends to vary according to the direct object. For example, SIT might use ON in some cases but IN for other cases: "on the stool" vs "in the car."

To accomplish this, TAction.announceDefaultObject() first parses the verbPhrase to get the standard preposition, then calls a new method on self, adjustDefaultObjectPrep(prep, obj). 'prep' is the default preposition from the verbPhrase, and 'obj' is the direct object of the command. The routine returns the new preposition string. The default implementation simply returns 'prep', but the StandOn, SitOn, and LieOn actions override this to return the direct object's actorInPrep.

InitiateTopics can now be located within ActorState objects. In the past, InitiateTopics were only found when they were directly within an Actor or a TopicGroup within an actor. The library now looks for InitiateTopics in the current actor state object as well, which is more consistent with other topic types.
inputManager.pauseForMore() now re-activates the transcript if it was active before the pause. In the past, this function simply flushed output and left the transcript deactivated. This was problematic for certain library subsystems that depend upon the transcript being active throughout a command. Now, the function flushes the transcript's pending output up to the pause, then reactivates the transcript after the pause, so that transcript capture proceeds as normal throughout the rest of the command.
In the past, the library generally assumed that NestedRoom objects would only be located inside either Rooms or other NestedRooms, but never inside ordinary Things. However, in practice it's often useful to be able to put a nested room inside something that's not itself a nested room.

A number of small library changes should now make NestedRooms work properly when located inside ordinary Thing objects. The changes are mostly internal and should have no impact on existing code, but for reference, here's what's changed:

In addition to the changes above, NestedRoom no longer assumes that its immediate location is the destination when an actor within the nested room leaves the nested room. Instead, NestedRoom now consistently uses the value given by exitDestination. Further, NestedRoom.exitDestination itself no longer uses the immediate container as its default setting, but instead uses the default staging location as given by the defaultStagingLocation method.

A change in 3.0.10 introduced a parsing problem with distinguishing nouns from plurals when the words were right at the parser's truncation length. To be more precise, if you defined a noun that was exactly the truncation length, and you also defined a plural for that noun as the noun plus "s" (or, actually, as the noun plus any other letters), then the parser would incorrectly interpret the noun word in player input as though it were plural.

For example, the default truncation length is 6 characters, so this problem occurred with "button" and "buttons" defined as a noun and plural, respectively. In this case, if you had two or more objects in scope with vocabulary like 'red button*buttons', and the player typed something like "x button", the parser incorrectly treated the "button" in the player's input as though it were plural, so it applied the Examine command iteratively to all of the buttons in scope, rather than asking the player which singular button they intended to examine.

This problem has now been corrected.

The message definition for cannotSetToMsg was missing from the English message list (in en_us/msg_neu.t). The message is now there.
A command of the form PUSH pushable INTO object, where the second object was some random, non-enterable thing, yielded an unformatted message ("{that/he dobj} {is} not something you can enter"). The underlying problem was that the action remapping invoked by PushTravelViaIobjAction didn't properly set up the object parameters for the remapped verb, leaving the message system unable to find the direct object. This has been corrected.
The Attachable class had a problem that caused a run-time error when the player entered a command of the form DETACH object, where object was an Attachable and the command had no indirect object. The problem came from a name conflict between an Attachable method called cannotDetachMsg(obj), which took one parameter, and a library messages property of the same name, which takes no parameters. When the message resolution system tried to retrieve the library message from the direct object, it invoked the zero-parameter version of the property, which caused the run-time error due to the parameter mismatch with the one-parameter version implemented in Attachable.

To fix this problem, the method in Attachable has been renamed to cannotDetachMsgFor(obj). The library message has the same name as before.

Any existing code that overrode the Attachable method will have to adjust for the name change. That said, it seems almost impossible for this change to affect existing code, since the very bug that we're talking about here would have prevented the override from working properly in the first place.

A library bug caused a run-time error ("stack overflow") on commands like REMOVE ME or REMOVE nested room containing me. This has been corrected.

(The specific problem is as follows. The library assumes that a REMOVE X command will result in the actor taking X, and so as a sanity check calculates how much weight the actor would be holding if that were allowed. When X is the actor or contains the actor, the hypothetical weight check created a circular containment situation; the stack overflow came from the library's attempt to recursively visit all of the hypothetical contents of the actor, which is of course an infinite loop in a circular containment situation. To avoid this problem, the library now ignores hypotheticals that would create circular containment. It's safe to ignore these hypothetical tests because commands that perform them should always be disallowed anyway, since actual circular containment is never allowed.)

A library bug caused a run-time error ("nil object reference") on entering a command of the form object, unknown word, where object was any in-scope non-Actor object. This has been corrected.

(The problem came about because the parser attempted to treat such a command as though it were directed to an NPC. This is some special handling that applies when the command has a syntax error; the point is to let the author customize the parsing error messages for orders given to particular actors. However, when object isn't an actor, this is problematic, because the library assumes that it can call certain Actor methods on the object in question. The fix is that the library will only apply this handling in cases where the object is actually an Actor; in other cases, it won't assume that the command was intended as an order to an NPC, so it will simply use the default parsing error messages.)

3.0.12

TADS 3.0 General Release version - Released 9/15/2006

The library didn't properly handle situations where an NPC order involved multiple objects and a failed implied sub-action. If an implied action failed for one of the objects involved, the implied action was assumed to have failed for all subsequent objects in the multiple object list as well, even if the subsequent implied actions actually succeeded. This resulted in self-contradictory transcripts, where an implied action was reported as successful, but then was followed by a message that the implied action had failed:

  Bob takes the coin.
  Bob must be holding the coin first.

The problem was that the implied action mechanism was incorrectly considering the failure status for the entire top-level command when determining if the subsequent implied actions failed. Instead, it should have been checking the status of the implied actions themselves. It now does this by looking for a failure report within the implied action's reports, rather than looking for a failure anywhere within the entire transcript.

SpaceOverlay.getWeight() now omits the object's "contents" from the weight calculation if the contents are to be abandoned when the object is moved. If the contents are to be abandoned, it means that they're not actually attached to or contained within the space overlay, but are simply colocated with it; they thus have no contribution to the overlay's total weight. If the contents are not to be abandoned on moving the overlay object, they're effectively attached to it, so they do contribute to its weight as normal.
CaptureFilter is now a subclass of OutputFilter (it was formerly just an 'object'); and SwitchableCaptureFilter is a subclass of CaptureFilter (it also was just an 'object'). This should make no difference functionally, as an output filter is only required to implement the filterText() method, but is desirable anyway in that it makes an ofKind(OutputFilter) test recognize these object types as output filter subclasses.
3.0.11

Released 9/8/2006

Very slight compatibility risk: NameAsOther (and thus NameAsParent) no longer maps the "in" names to its target object. The "in" names are the names generated when an object is described as contained within the NameAsOther. In the past, these were mapped to the target object along with all of the ordinary names for the object. However, this was the wrong behavior for ComplexComponent, which inherits from NameAsOther, because the containment relationship between a ComplexContainer and its contents is defined by the container subclass mixed with ComplexComponent in the object's superclass list, not by the target object, which in this case is the ComplexContainer of which the ComplexComponent is a part.

Although it's conceivable that some other applications of NameAsOther would actually want the old behavior, it seems highly unlikely, so we don't expect any practical compatibility list.

However, we have provided a new mix-in class, ChildNameAsOther, that adds mappings for all of the "in" names to the target object. So, if you have a NameAsOther that depends upon the old "in" name mapping, just add ChildNameAsOther to the object's superclass list (right after NameAsOther or NameAsParent), and you'll get the same behavior as before.

In 3.0.10, the ImpByeTopic was differentiated into a couple of subclasses to allow handling implicit goodbyes differently when desired (see below). However, this change didn't handle one type of goodbye correctly, namely the NPC-initiated goodbye, via npc.endConversation(). The change incorrectly made it so that those goodbyes were treated the same as explicit player-initiated goodbyes.

Prior to the 3.0.10 change, NPC-initiated goodbyes were subsumed into the undifferentiated "implicit goodbye" category, and they should clearly remain in that category; they simply need to be differentiated like the other implicit goodbyes were in the 3.0.10 change.

To this end, the new topic class ActorByeTopic has been added; this is analogous to BoredByeTopic and LeaveByeTopic, and is used when the NPC terminated the conversation via npc.endConversation(). In the absence of an active ActorByeTopic, the active ImpByeTopic will be used instead. This restores compatibility with pre-3.0.10 code (where there was no differentiation among "implied goodbye" types, and npc.endConversation() events were handled as implied goodbyes), while providing a specific topic type to handle this one type of goodbyes.

The English library's "instructions" module (instruct.t) now uses HTML markups to display typographical quotes and apostrophes ("curly quotes") throughout the text of the standard instructions. Thanks to Greg Boettcher for making this improvement.
The detailed-naming scheme for object announcements (see below) introduced in 3.0.10 has been made optional, and disabled by default. Testing reveals that the mechanism as currently designed is too twitchy for some people's taste, so we've disabled it by default for the time being; however, the code is all intact, for those who want to use it as-is or tweak it for their needs. To enable the detailed announcement naming, set gameMain.useDistinguishersInAnnouncements to true.
The BannerWindow system had a flaw in the way it re-initialized banners after a RESTART. The problem showed up in cases where there were dependency orderings among the windows, so that one window's initBannerWindow() had to call another's initBannerWindow() in order to create the windows in the correct order. The problem didn't always happen even in cases of ordering dependencies, since it also depended on the arbitrary ordering of the VM's internal object lists. When it happened, the problem manifested itself by creating extra copies of an affected banner window at each RESTART. This has now been corrected.
In banner.t, the formerly anonymous InitObject that handles banner initialization (and post-RESTART re-initialization) now has a name, bannerInit. This is so that games can use "modify" and "replace" with the object, and also so they can refer to it from the execBeforeMe properties of other InitObjects, for initialization dependency ordering purposes.
"Follow mode" for NPCs ("Bob, follow me") didn't work correctly when the actor being followed moved between two top-level rooms connected by a sight sense connector. This has been corrected. (The problem was that the code that carried out the "follow" attempted to move the follower using a "local travel" action - something like STAND ON STAGE or ENTER BOOTH - any time the target actor was still in sight. The follower now attempts local travel only if the target is still in sight and the target is within the same top-level room; otherwise, the follow uses a suitable full-fledged travel action.)
In Actor.actorActionFollow(), if the actor is already in "follow" mode for the requested other actor, a message is now displayed to this effect (alreadyFollowModeMsg: "I'm already following Bob"). This won't be a factor by default, since the library automatically cancels "follow" mode any time a new command is issued to an actor before attempting to enact the new command. However, games might want to override this auto-cancel behavior, in which case they might encounter this situation in actorActionFollow(). In the past, the routine did nothing at all - it didn't even show a message, so the generally undesirable "Nothing happens" was displayed by default.
In the past, an actor that started in an InConversationState triggered a run-time error at start-up, due to an initialization order problem in the library. This has been corrected. (In particular, an actor's boredAgendaItem property is now initialized via a perInstance() definition rather than in initializeActor(). This ensures that the property is initialized before it's needed. In the past, the order of initializations sometimes resulted in the library trying to use the actor's BoredAgendaItem object before it was initialized, leading to a "nil object reference" error.)
Room's condition for remapping the GetOutOf action to the Out action has changed slightly. In the past, this remapping was performed only if the 'out' direction had an apparent destination, which is only the case when the actor attempting the travel already knows the destination (such as from past experience or at-hand information). This condition wasn't quite right, though. Instead, the condition should have been simply that the 'out' direction has an apparent connector - that is, there's a visible way to travel in the 'out' direction. Room has been changed to use the new condition.
The template (in en_us/en_us.h) for DeadEndConnector now makes the apprentDestName entry optional. This allows using the template to define a dead-end connector that merely displays a message when traversed, without giving it a name.
In the English library, PathPassage now limits its remapping of the Take action to the TravelVia action to cases where the entered verb phrase was literally "take." This prevents other phrasings, such as "pick up path" or "get path," from being interpreted as attempts to travel along a path.
The message sayTravelingRemotely in the English library has been corrected to add the word "to" before the destination name.
3.0.10

Released 8/17/2006

Incompatibility warning: (This note concerns a change that was made in 3.0.9 but inadvertantly omitted from the 3.0.9 release notes. We're mentioning it now in case anyone was affected by it and needs help adjusting their code for the change.)

In version 3.0.9, gameMain.verboseMode was changed from a simple true/nil property to an object of class BinarySettingsItem. Any existing game code that attempted to turn verbose mode on or off by setting gameMain.verboseMode to true or nil will now encounter a run-time error the first time the player enters a travel command.

If you want to set the default verbosity mode explicitly in your game, you can't do it any more by setting gameMain.verboseMode to true or nil. Instead, you can add a line like this to a start-up routine, such as gameMain.newGame():

   gameMain.verboseMode.isOn = true; // turn on verbose mode

Note that the verbosity mode is now part of the "global preferences" mechanism, so in most cases it's best for games not to change it explicitly, instead leaving it up to the player to decide on the setting. In the past, some authors liked to set a verbosity mode that they felt was most suitable for the game. Now that the player can specify a set of default preferences that they wish to apply to all games, it's better for authors not to presume to change the player's default settings without a good reason. As with any other rule, there are bound to be exceptions, so if you have a really good reason to override the player's preferences then you should feel free to do so. But if you're tempted to override the player's preferences just because you like it a particular way, you might want to reconsider.

Minor incompatibility warning: The Lockable class now has initiallyLocked set to true. This means that all Lockable objects now start out locked by default (i.e., unless your game code specifically overrides initiallyLocked to set it to nil for a given Lockable). In the past, Lockable didn't define initiallyLocked at all (so it defaulted to nil), but Door and IndirectLockable defined it as true - so some Lockables formerly started out locked by default while others were unlocked by default. This change should be generally beneficial, since (1) it simplifies matters by making the initial lock status consistent across all Lockables, and (2) the vast majority of Lockables start out locked anyway, so "locked" is the better default.

If you have any Lockables in existing code that you specifically intended to start out unlocked, and you didn't explicitly set initiallyLocked to nil in those objects, you'll have to do so now.

Minor incompatibility warning: The method Action.callVerifyPrecond has been renamed to callVerifyPreCond - that is, the 'c' in 'precond' is now capitalized. This change is for better naming consistency, since all of the other symbol names in the library that include the substring "precond" capitalize the C. This routine is intended mostly for internal use within the library, so it should affect little or no existing game code.
Minor incompatibility warning: The NOTE command has been removed, and replaced by a new comment syntax that lets the player enter a comment by starting a command line with a punctuation-mark prefix.

The default comment prefix is now an asterisk ("*"). You can change this to any prefix string you'd like by modifying the commentPrefix property of the commentPreParser object. You can also control whether or not leading whitespace is allowed before the comment prefix (it is by default) by modifying commentPreParser's leadPat property.

Using NOTE as the comment prefix was problematic because NOTE is a common enough word that games often want to use it in an object's name, and this creates situations where a user might want to start an input line with NOTE with the intention of referring to an object, not of entering a comment. It was essentially impossible in some of these cases to reliably determine which the player meant. The new approach avoids these problems by using syntax that should be unambiguous in nearly all games.

In the past, there was a StringPreParser object in the English library that helped the NOTE command by quoting the note text in some cases. This preparser has been removed, and a new preparser has been added in its place, but this time in the general library, in misc.t. The new object is called commentPreParser, and it performs all of the comment handling itself, without the need for a separate NOTE action.

As part of this change, NoteAction and NoteIAction have been eliminated. In the unlikely event that you used 'modify' to change the behavior of these actions, you'll have to rework your code. Look at the commentPreParser object for details of the new arrangement.

Minor incompatibility warning: Each actor now has its own separate lookup table of ConvNode names. This means that you don't have to worry about making ConvNode names unique globally - you only have to make sure that names are unique within a single actor's set of nodes.

This shouldn't affect existing game code, except for cases where you refer directly to conversationManager.convNodeTab. You should scan your source for occurrences of "convNodeTab" and change any that you find to "actor.convNodeTab", where "actor" is the Actor who owns the table. The most likely place for game code to refer to convNodeTab is in a "modify conversationManager" definition.

Minor incompatibility warning: Thing.getExtraScopeItems() is now required to return a list - nil is no longer a valid return value for this method. Use an empty list instead of nil to indicate that there's nothing to add to the current scope. The old nil return code was inconsistent and unnecessary, as an empty list always functionally meant the same thing anyway.

It's very unlikely that this change will affect any existing game code, since any game code that overrides this method almost certainly did so to add something to the scope. However, you do a quick search through your game code for getExtraScopeItems(), and make sure that you don't have any nil returns; if you do, simply change them to empty lists ("return [];").

Minor incompatibility warning: Thing.getListedContentsInExamine() has been renamed to getContentsForExamine(), and its operation changed slightly. The old name was a bit misleading, in that the method actually needs to return all of the visible contents of the object, whether or not they're marked as listable. Unlistable contents need to be included in this list so that their listable contents can be included recursively. This is the operational change: in the past, only listable contents were included in the returned list; now, all visible contents are included.

This change corrects an inconsistency that occurred in cases where an object had fixed-in-place contents that themselves had contents. In the past, directly examining such an object didn't mention anything about the contents of the second-level containers, while a simple LOOK did include the inner contents. The change makes the two cases consistent.

This change should have little or no effect on existing code, since the former getListedContentsInExamine() method is an internal method that's unlikely to have been called or overridden in game code. If you overrode this routine, though, you'll need to apply the name change to your code, and make any adjustments for the slight change in the method's semantics.

Minor incompatibility warning: the "equivalent object" mechanism has changed substantially, although existing game code shouldn't be affected unless it was modifying the equivalence mechanism itself.

In the past, equivalence was based on the superclass of the object. Two objects were equivalent if (a) they both had isEquivalent set to true, and (b) they had identical superclass lists. This approach to equivalence was occasionally problematic, particularly when using "proxy" objects to modify the behavior of objects involved in equivalence groups. In addition, the superclass approach was somewhat counterintuitive: the whole point of the equivalence mechanism is to treat objects with identical names as interchangeable, but using superclasses as the basis of the equivalence had nothing to do with the naming.

The new scheme is based on a new property called equivalenceKey, which is defined in the language module. In the English library, the default setting of this new property is the disambigName of the object. So, equivalence groups are now simply based on the basic disambiguation name of the object. This is much more consistent with the disambiguation mechanism itself, because it means that the parser decides whether objects are interchangeable using essentially the same logic that the player uses intuitively: if the game always refers to two objects by the same name, they're interchangeable.

Another advantage of the new scheme is that it's much more customizable than the old scheme. Since the basis of the equivalence decision is now distinguished as a separate property (equivalenceKey), you can control equivalence groups simply by overriding the property. You could even effectively restore the old scheme by defining equivalenceKey as getSuperclassList() - this would make the immediate superclass list of an object the basis of its equivalence grouping, producing the same behavior as in the past.

Note that if you change an object's equivalenceKey dynamically during play - or if you change its underlying disambigName property - the object's listing group won't be automatically updated. If you do make such a change and you want to update the object's listing group, just call initializeEquivalent() on the object. (Under the old scheme, there was really no way to change an object's grouping - even if you changed its name, it was still grouped based on its class, which could have caused strange results in listings. The new scheme at least allows for this kind of change, although it requires this manual step to keep the list grouping settings in sync.)

In the past, when EXAMINE was applied to a Room, the Room simply turned the command into a nested LOOK AROUND command. This has changed slightly. Now, the Room directly does the work that LOOK AROUND would do, without the nested command, and with the difference that the initial line with the room name in boldface isn't included in the output. (This is accomplished by omitting the LookRoomName flag from the 'verbose' flags for the Actor.lookAround() call.)

There are two reasons for this change. First, when the player explicitly types EXAMINE room name, it's somewhat redundant to include the room name in the output. Second, when the player types EXAMINE ALL or EXAMINE list of things, the standard multi-object-command output format already prefaces the results for each item in the list with the name of the item ("kitchen:"), which made the room name line especially redundant in this case.

An object can now be associated with more than one CollectiveGroup. This allows an object to be a member of multiple disjoint groups. For example, you could create a group for "treasure," and a group for "jewelry," and put some objects in both groups, while leaving others in one group or the other.

To define multiple CollectiveGroups for a given object, use the new property 'collectiveGroups' on the objects that you want to associate with the groups. Set this property to a list with the CollectiveGroup objects to associate with the given object.

You can still use the single-valued 'collectiveGroup' property, so existing game code will continue to work unchanged. However, 'collectiveGroup' is now obsolescent, so you should not use it for new code - use 'collectiveGroups' instead. For compatibility with existing code, the library's default definition of Thing.collectiveGroups is a method that returns an empty list if collectiveGroup is nil, or a one-element list containing the collectiveGroup value if the value is not nil. Since this is slightly ineffecient, support for the old 'collectiveGroup' will probably eventually be removed, at which point the default for 'collectiveGroups' will simply be an empty list.

Several new classes bring functionality parallel to RestrictedContainer to surfaces, undersides, and rear containers and surfaces. The new classes are RestrictedSurface, RestrictedUnderside, RestrictedRearContainer, and RestrictedRearSurface. These new classes (along with RestrictedContainer itself) are all based on the new mix-in class RestrictedHolder, which defines generic containment restriction rules that can be applied to any container subtype.

The new classes work just like RestrictedContainer did, and RestrictedContainer itself hasn't changed its behavior (it's been refactored slightly for the new base class, but this is just an internal change that shouldn't affect any existing code). To accommodate the new types of restriction, suitable new library "player action" messages have been added (cannotPutOnRestrictedMsg, cannotPutUnderRestrictedMsg, cannotPutBehindRestrictedMsg).

In the past, the default Search action handling was the same as for LookIn. Now, the Search handling is slightly different for Container and Openable.

First, Openable adds an objOpen precondition for Search if the actor isn't inside the object; for LookIn, this is skipped if the object is transparent.

Second, in the Search check() method, Container requires that the object be openable or transparent to the "touch" sense, or that the actor is inside the container. LookIn is similar, but only requires that the container to be non-opaque in the "sight" sense.

The reason for these changes is that Search implies a more thorough, physical examination than LookIn does. To most people, an explicit search involves physically poking through an object's contents, while "look in" is more passive, implying just having a look inside.

In the past, the Thing handlers for PUT UNDER and PUT BEHIND included touchObj preconditions on the direct object instead of objHeld preconditions. These have been changed to objHeld conditions; since these verbs generally require physically moving the direct object, the correct precondition is that the direct object be held, just as for the other PUT verbs.
A new Action method, getEnteredVerbPhrase(), returns the action's original verb phrase text as typed by the player. The return value is a string giving the verb phrase in a canonical format: specifically, the result is all in lower-case, and each noun phrase in the player's input is replaced by a placeholder token: '(dobj)' for the direct object, '(iobj)' for the indirect object, '(topic)' for a topic phrase, and '(literal)' for a literal text phrase. Only the verb phrase is included in the result - there's no target actor phrase, and no sentence-ending punctuation included. The noun phrase replacements apply to the entire noun phrases, so the single placeholder '(dobj)' could represent an entire list of direct objects. For example, if the player types "BOB, PUT THE BOOK AND PENCIL IN THE BOX AND GIVE IT TO ME", calling getEnteredVerbPhrase() on the PutIn action would yield 'put (dobj) in (iobj)', and calling the method on the GiveTo would yield 'give (dobj) to (iobj)'.

There are two reasons why the method returns the canonical format rather than the full text of the entire command. First, the full text is already readily available, via gAction.getOrigText(), so there's no need for a new method to retrieve this information. Second, and more importantly, the canonical format isoaltes the verb phrase structure of the player's input, independently of any noun phrases, making it easy to determine exactly which verb phrasing the player actually used.

This method is most useful in cases where the library's verb rules define two or more different phrasings for the same Action, and you need to be able to distinguish exactly which variant the player entered. For the most part, this is unnecessary: when the library's verb rules include synonyms, it's because the different phrasings usually have exactly the same abstract meaning, hence it's enough to know which Action matched the grammar. In some cases, though, a particular verb applied to a particular object has an idiomatic meaning different from the usual meaning for other objects, and in those cases the generic synonyms often fail to be idiomatic synonyms.

For example, the library defines "get (dobj)" and "take (dobj)" as synonyms for the Take action, because GET and TAKE can almost always be used interchangeably. However, if you were defining an "aspirin" object, you might want to treat the command "take aspirin" as meaning "eat aspirin," but you would still want "get aspirin" to mean "pick up aspirin." You could handle this by overriding the dobjFor(Take) action() method on the aspirin object, comparing gAction.getEnteredVerbPhrase() to 'take (dobj)', and calling replaceAction(Eat, self) if it's a match.

The new Action method getPredicate() returns the verb phrase match tree object that the parser resolved to the Action. The English library uses Action objects as predicate match tree objects, so in the English version this simply yields the original Action. However, non-English libraries can use separate objects for verb phrase match tree objects, so if you need any information from the verb phrase's grammar tree (for example, if you need to call getOrigText() or getOrigTokenList()), you should use action.getPredicate() rather than using the action object directly. (This does not apply to the new method getEnteredVerbPhrase() above - that method is explicitly defined on the Action, not the grammar object. getEnteredVerbPhrase() itself calls getPredicate() to get the grammar information, so you can simply call getEnteredVerbPhrase() directly on the Action object.)
The library looks to a new global variable, gLibMessages, to determine which object to use as the "library message" source. (This is the object used for messages that don't logically relate to the current actor; generally, these messages are replies to meta-commands, or text fragments used to construct descriptions.)

In the past, the library simply used the libMessages object directly as the source of these messages. Now, the library instead uses the current value of gLibMessages as the message source. The default value of gLibMessages is libMessages, and the library never changes this value itself, so the default behavior is exactly the same as before.

The purpose of this change is to allow a game to switch to a new set of library messages dynamically during play. For example, if your game is structured into chapters with different points of view, you might want to use a distinctive writing styles for the different chapters, in which case you'd want to change all of the library messages at each chapter transition. To do this, simply set gLibMessages to refer to a new object at each point when you want to switch message sources; subsequent messages will come from your new source object. Note that it's not necessary to do this if you only want to customize messages statically (i.e., throughout the entire game); for that, you can just 'modify libMessages'.

In the past, the Floor class replied to THROW object AT FLOOR with the same default message ("you should just put it down instead") in all cases. This reply is no longer used in cases where the Floor is unreachable; instead, the standard THROW handling applies, since the player couldn't accomplish the same thing with DROP.
RoomPart.getHitFallDestination() will now take into account any explicit 'location' setting in the RoomPart when determining the destination. In the past, an explicit 'location' setting was ignored, and a valid destination could only be found if the RoomPart was in the same top-level room as the actor. Now, if the RoomPart has an explicit non-nil location setting, that takes precedence in determining the destination. This is useful when creating connected top-level locations with room parts that you want capable of serving as THROW targets.
When throwing an object at a target, and the target happens also to be the place where the object is described as landing when thrown at that target (i.e., the target is the nominal drop destination of its own the "hit-fall" destination), a new default message is used, of the form "The projectile lands on the target." In the past, the message was the awkward "The projectile hits the target without any obvious effect, and falls to the target." This was most likely to occur when the target was something like a floor or ground object, since that's the only case where the target is likely to also be the nominal drop destination. This change is in the routine DropTypeThrow.standardReport().
GIVE TO, SHOW TO, and THROW TO now use different messages depending on whether the indirect object is an actor or an inanimate object. For ordinary Things, the message is now of the form "You can't give anything to X"; for Actors, the message is "The X does not appear interested." In the past, the latter message was used for all objects, animate or not. Imputing "interest" to an inanimate object could be read as either intentionally snide (as though the library were sarcastically calling the player dense for trying such an obviously stupid thing) or simply wrong (as though the library didn't know the difference); since msg_neu is supposed to affect a neutral narrative tone, neither alternative is desirable.

In addition, showing or giving an Actor to itself is now handled with a separate message ("Showing X to itself would accomplishing nothing").

REMOVE dobj now uses a separate reply if the object is already being held: "There's nothing to remove the X from." In the past, the command was unconditionally turned into TAKE dobj, which showed "(from you)" as the implied indirect object part, which was a bit awkward.
When the THROW direction command is used with a nonportable object (as in THROW DESK EAST), the result is now the same as for commands like MOVE and PUSH: "The desk cannot be moved."
The default response for THROW DOWN has been changed to use the same message as for DROP when applied to an object that's not currently being held: "You're not carrying that."
In statusLine.beginStatusLine(), in the StatusModeApi case, there was some leftover old code ("<body bgcolor=statusbg text=statustext>") right after the call to setColorScheme() that effectively undid the color settings made in setColorScheme(). The old code has been deleted.
The library now uses the same default message in response to a command to throw the player character in a direction (as in THROW ME EAST) as it does as for THROW ME AT something.
A new Thing method, adjustThrowDestination(), gives a prospective landing site for a thrown object a chance to redirect the landing to another object. getHitFallDestination() now treats the result it calculates using getDropDestination() as tentative, and calls this new method on the tentative result object to determine the actual destination. The default implementation of the new method in Thing simply returns 'self', which confirms the tentative destination as the actual destination. BulkLimiter overrides the new method to redirect the landing site to the BulkLimiter's location's drop destination if the thrown object would overflow the BulkLimiter's capacity.

This change corrects a problem that occurred when a thrown object landed in a BulkLimiter that was already near capacity. In the past, the BulkLimiter applied its capacity control by effectively blocking the THROW before it happened. With the change, the THROW will be allowed to proceed, and the thrown object will land in the nearest suitable container of the original target.

Note that any game code that overrides getHitFallDestination() should be sure to call adjustThrowDestination() on its tentative return value. Games usually won't have any reason to override getHitFallDestination(), so this change shouldn't affect most existing code.

In ActorState, the beforeTravel() method no longer ends the current conversation if the actor is merely moving between nested locations within the same top-level location. These aren't usually true departures that should trigger conversation termination, so the method no longer treats them as such.
A new travel connector class, DeadEndConnector, can be used for situations where travel through the connector is impossible, but for reasons that can only be learned by attempting to go through the connector. For example, this can be used for a passage that turns out to be blocked by a cave-in that isn't visible from the passage entrance, or in a situation where you describe the actor as wandering around in a direction for a while but giving up and returning to the point of origin.

DeadEndConnector supplements FakeConnector, which is most useful for exits that look like connections but can't be traversed for reasons that are apparent before the travel is ever physically attempted - in particular, for motivational reasons ("You can't leave town without finding your missing brother"). DeadEndConnector differs from FakeConnector in that DeadEndConnector acts as though the physical travel were actually occurring. It fires all of the normal travel notifications, so any side effects that would occur on ordinary travel will also occur with a DeadEndConnector.

"Push travel" has been changed slightly to handle situations where the object being pushed has side effects that affect the description of the destination location. The most common situation where this arises is when the object being pushed is (or contains) a light source, but it could happen in many other custom-coded situations.

In the past, the object being pushed wasn't moved to its new location until after the new location had been described. The point of this sequencing was to exclude the pushed object from the new location's description, simply by keeping it out of the new location until after the description was displayed. This exclusion is desirable because it would otherwise look as though the pushed object were already in the new location on the player character's arrival, which would be confusing. However, it prevented any side effects of the pushed object's presence from being taken into account in the new location's description. If the pushed object was a light source, for example, and the new location was otherwise unlit, the new location was described as dark.

Now, the library tentatively moves the pushable object to the destination of the travel, and marks the object (via the new Actor.excludeFromLookAround mechanism) for exclusion from the location description. It then moves the actor and shows the description of the new location. Finally, it moves the pushable back to the origin location, and then moves a second time, this time for real, to the destination location.

(The reason for moving the pushable twice - first tentatively, before the travel, then again "for real," after the travel - is that the method that makes the final move is overridable and so might actually do something other than move the pushable to the travel destination. Even if it leaves the object unmoved, though, or moves it to a different final destination, the tentative first move is still a valid intermediate step. At the point we're generating the description of the new room, the player character is in the process of pushing the pushable into the new room - the PC is notionally walking along with the pushable at that stage. If the overridable final-move method wants to do something different, it can do so; it will simply have to describe the change, which it had to do in the past anyway. At that point, the PC will already be in the new location, and so will in fact have pushed the pushable this far; anything that happens in the overridable method happens after the intermediate stage where we generated the description, so any side effects of the pushable's tentative presence were valid at that point, no matter what happens afterwards.)

In the English library, the Push-Travel commands (PUSH X NORTH, PUSH X UP RAMP, etc) now only accept single direct objects. In the past, the grammar phrasings allowed direct object lists, but actually pushing multiple objects into a new room is a practical impossibility, so there's no point in accepting it in the grammar.
The mechanism for describing "local" NPC travel has been enhanced. Local NPC travel is travel where an NPC moves from one top-level location to another, and both top-level locations are in view of the PC. In these cases, special handling is needed because the NPC isn't truly arriving or departing in the usual sense; the NPC is instead moving closer to, further away from, or laterally with respect to, the PC.

In the past, the local NPC travel mechanism did everything via the "local arrival" message: the NPC generated only this one special message at the destination end of the travel. The new mechanism adds two messages analogous to the local arrival message: a "local departure" message and a "remote travel" message. Here's how they're used:

These new rules obviously require some new methods. Here's the new arrangement of methods:

  • Existing code should continue to work correctly with the new framework, although with one caveat: if you customized a describeLocalArrival() or sayArrivingLocally() method, you might want to add corresponding customizations for the new local-departure and/or remote-travel methods. If you don't, your local-arrival customization will still work in the cases where it applies under the new rules, but it won't be invoked in all the cases it was in the past.
  • AccompanyingInTravelState now describes local travel using its standard departure message. This ensures that the correct messages are displayed when accompanying an actor in local travel or travel between two connected top-level locations (such as locations linked by distance).
    TravelAction.actionOfKind(cls) now properly handles the case where 'cls' is the base TravelAction class. (In the past, asking about TravelAction itself caused a run-time error.)
    The various SettingsItem objects defined in the library are now all named objects. In the past, some of these (such as gameMain.verboseMode) were defined as embedded objects; this made it more difficult to modify their behavior, since it wasn't possible to use 'modify' to alter the objects directly. The behavior should be exactly the same as in the past; the only difference is that the objects are now easier to customize.
    The putDestMessage that was defined in defaultFloor and defaultGround has been moved to the base Floor class instead - this message should be common to most floor/ground type objects, not just for the default ones in the library.
    The method standing.tryMakingPosture(loc) now generates the command STAND ON loc, rather than simply STAND as it did in the past. This makes the behavior consistent with the sitting and lying postures.
    Room can now be used as the direct object of STAND ON, SIT ON, and LIE ON. These are handled simply by remapping the commands to the room's "floor" object (specifically, the object returned from the room's roomFloor method). This is in part to accommodate the change above to standing.tryMakingPosture(), and in part to provide better automatic handling for player commands like SIT IN YARD.
    RoomPart now applies some additional disambiguation filtering in cases where two or more top-level locations are linked by a sense connector. When a resolve list contains multiple RoomPart objects, and some of those RoomParts are local and some remote, RoomPart will reduce the list to include only the local RoomParts. (A "local" object is one within the same top-level location as the actor; a "remote" object is one that's in a separate top-level location that's linked by a sense connector.)
    If a RoomPart has an explicit 'location' setting, that location will no longer add the RoomPart redundantly to the location's 'contents' list. In the past, the RoomPart would show up twice in the contents list, because it was added once by virtue of being in the RoomParts list, and again by virtue of its 'location' setting.

    In addition, when a RoomPart has an explicit 'location' setting, it will now automatically add itself to that location's 'roomParts' list. This means that you don't have to manually set both properties, which saves a little work and also makes your game easier to maintain, since you won't have to remember to make coordinated changes to both settings in your source code if you change the room part later.

    RoomPart and TravelConnectorLink now have a default sightSize of 'large'. This means that it's possible to examine these objects at a distance. Room parts are things like walls and ceilings that tend to be large and to contain large-scale details that would be realistically discernible at a distance - the details are typically things like doors and windows. Similarly, TravelConnectorLink (which you'd mainly use via its subclasses Enterable and Exitable) is for things like building exteriors, which likewise tend to have large-scale details.

    In cases where you create a RoomPart or an Enterable or Exitable that has fine-grained details that would be too small to see at a distance, and the object is visible from a separate top-level location linked by distance, you might want to override this to set the sightSize back to medium. When there's no distance-linked top-level location, this shouldn't be an issue, since there'd be no way to examine the object from a distance to begin with.

    RoomPartItem now overrides useSpecialDescInRoom() and useSpecialDescInContents() to return nil, and no longer overrides useSpecialDesc and useInitSpecialDesc.

    In the past, RoomPartItem overrode useSpecialDesc and useInitSpecialDesc (setting them to nil) in order to prevent room part items from being included in LOOK descriptions, but this had the bad side effect of preventing showSpecialDesc() from showing the initSpecialDesc. This change uses the more precise methods to select exactly where the special desc should be shown, without affecting the selection of which special desc to show.

    Thing.isListedInContents now calls useSpecialDescInContents(location) to make its determination. In the past, it called useSpecialDesc directly; this was incorrect because it didn't properly take into account the differentiation among contexts that the various useSpecialDescInXxx methods provide.
    The exit-list generator (exitLister.showExitsWithLister) now considers two destination locations to be equivalent based on the destination room object rather than the destination room name. This means that two distinct exit locations will now be listed separately even if they have the same name. For example, in the past, if the east and west exits led to separate rooms that both happened to be named "the hallway," the listing formerly read "east (or west), to the hallway", but will now read "east, to the hallway; west, to the hallway". The old approach of merging list entries based on name alone produced odd results in some cases, and didn't have any obvious benefits; the new approach should produce more predictable results.
    TIAction has a new method, retryWithAmbiguousIobj(), that lets an action handler specifically ask for disambiguation from a list of possibilities. This is the TIAction equivalent of the TAction method retryWithAmbiguousDobj(), and works the same way; the only difference is that the disambiguation list applies to the indirect object rather than to the direct object.
    The TAction and TIAction methods retryWithAmbiguousDobj() and retryWithAmbiguousIobj() can now be called before the "iteration" phase of the command execution, specifically during Action.beforeActionMain(), and they'll work properly: they'll ask their disambiguation question, then apply it to the entire iteration for the other object list. For example, if you call retryWithAmbiguousIobj() during beforeActionMain() to prompt for a new indirect object for a PUT IN command, the player's response will automatically be applied to the whole direct object list if the player specified multiple direct objects. In the past, it wasn't possible to call these "retry" methods during beforeActionMain(), so if they were used during commands with multiple objects in the other object slot, the result was that the "retry" - and its question to the user - was repeated for each object in the list.

    (This new flexibility involves two supporting changes. First, the various initForMissingXxx() methods in various Action subclasses now detect that the iteration over the object list hasn't begun yet, and they simply retain the entire object list (rather than the current iteration item, as they did in the past) for the retry. Second, to support the first change, the class PreResolvedAmbigProd now accepts an entire object list instead of just a single iteration element. These are internal methods that game code is unlikely to call directly, so the only visible effect of these changes should be the new flexibility in how the "retry" methods can be used.)

    In the past, initializeThing() was sometimes called multiple times in the course of dynamically creating an object (with 'new'). This happened when creating objects based on classes that inherited from Thing more than once (classes like this include Flashlight, Passage, Room, Chair, Bed, Platform, NominalPlatform, Booth, and UntakeableActor). This happened because the default constructor for any object based on multiple base classes simply inherits each of the base class constructors, one after the other; when more than one base class itself inherits from Thing, this results in multiple inherited invocations of the Thing constructor, which in the past resulted in multiple invocations of initializeThing() for the new object.

    Now, initializeThing() is only called once. The Thing constructor now tests a flag before calling initializeThing; it only calls the method if the flag isn't set, and it sets the flag after calling the method. This ensures that subsequent inherited constructor calls simply skip the call to initializeThing(). The constructor also skips the call to its own inherited base class constructor when the flag is set; this ensures that the vocabulary initializations in VocabObject are only invoked once per object.

    Room has a few command-handling enhancements, for rooms that have associated vocabulary. LOOK IN room is now treated the same as EXAMINE room; LOOK UNDER and LOOK BEHIND are refused ("You can't look under that"); SMELL and LISTEN TO room are now equivalent to simply SMELL and LISTEN; BOARD and ENTER are treated as redundant ("You're already in that"); and GET OUT OF is a little smarter about remapping, so that it only remaps to GO OUT if there actually is an OUT direction defined for the room, and fails with an error if not ("You'll have to say which way to go").
    NestedRoom.makeStandingUp() now leaves the actor's posture unchanged if the travel to the new location fails. It does this by noting the original posture before the travel attempt, and checking after the travel attempt to see if the actor is still in the starting nested room; if so, the routine restores the saved posture. This ensures that a failed travel attempt won't cause a posture change in the original nested location.
    NominalPlatform now overrides hideFromDefault() to always return true. Nominal platforms are meant to be used as internal objects and not to appear as simulation objects, so it's generally not desirable for them to be used as defaults.
    AccompanyingState had a problem that caused infinite recursion (leading to a stack overflow) when an actor in an AccompanyingState was explicitly moved via scriptedTravelTo(). The problem was simply that the actor attempted to accompany itself on its own travel, and that accompanying travel triggered a further accompanying travel, and so on. This has been corrected: actors in accompanying travel states now explicitly ignore their own travel for the purposes of accompanying travel.
    The library uses a separate default message in cases where an Actor is holding an object used in ENTER, BOARD, SIT ON, etc. In the past, the message was the rather awkward "You can't do that while the chair in in Bob." The new message is phrased "...while Bob is holding the chair" instead. The default message for non-Actors is unchanged.
    FOLLOW caused a run-time error if the follower was holding the NPC to be followed. This was because effectiveFollowLocation wasn't defined for ordinary objects. This has been corrected by defining Thing.effectiveFollowLocation to return the location's effectiveFollowLocation, or just 'self' if the object has no location.
    There are two new subclasses of ImpByeTopic, to allow you to differentiate between the two implicit ways of ending a conversation. BoredByeTopic handles cases where the NPC ends the conversation because of "boredom" (that is, inactivity in the conversation that exceeds the NPC's attentionSpan setting), while LeaveByeTopic handles cases where the PC simply walks away from the NPC in mid-conversation.

    BoredByeTopic and LeaveByeTopic extend the hierarchy that already existed with ByeTopic and ImpByeTopic. If there's an active ImpByeTopic and no active BoredByeTopic or LeaveByeTopic objects at the time of an implied "goodbye", the ImpByeTopic will be used for both cases (this also happens to be exactly the way the library worked in the past, before the two new subclasses were added, so this change won't disturb existing code). If there's an active BoredByeTopic as well as an active ImpByeTopic, the BoredByeTopic will be selected over the ImpByeTopic to handle "boredom" goodbyes; likewise, if there's an active LeaveByeTopic, it will be selected over an active ImpByeTopic to handle goodbyes triggered by the PC's departure.

    The "boredom" mechanism in InConversationState has been changed slightly. In the past, it was implemented directly in the takeTurn() method of InConversationState. Now, it's handled with an AgendaItem instead - specifically, a new subclass called BoredomAgendaItem, which the Actor and InConversationState classes manage automatically. This change should have no effect on existing code, since the new code implements the same behavior as the old version. The benefit of this change is that it makes it easier for a game to customize the boredom behavior, since it's now part of the more general "agenda" mechanism instead of being buried in ad hoc code inside the conversation state class.
    The suggested topic lister had a problem that showed up when several topic suggestions were present with the same listing name, and some of the topics were inactive while others were active. The lister automatically removes redundant entries from suggestion lists by removing multiple items with the same name, but in the past, the lister sometimes incorrectly removed the active elements instead of the inactive ones, effectively eliminating the suggestion entirely from the list. This has been corrected: the lister now only removes a redundant suggestion if there's another active suggestion with the same listing name.
    InitiateTopic no longer sets any pronoun antecedents when triggered. (It did set its match object as a pronoun antecedent in the past. This was undesirable because an InitiateTopic is triggered by internal calculations in the game, not by anything the player has done.)
    ConvAgendaItem now checks to make sure the player character is present before declaring itself ready. This ensures that an NPC won't attempt a conversational agenda item unless the PC is actually present to hear what the NPC has to say. (This change is in ConvAgendaItem.isReady.)
    Actor.executeAgenda() formerly marked an agenda item as done if invoking the item threw any sort of exception. Now, this is only done on a run-time error (a RuntimeError exception); in cases of other exceptions, the item's doneness isn't changed. The original purpose of the doneness change was to reduce debugging hassles in cases where an agenda item encountered an error; in such cases, if the item wasn't marked as done, the scheduler would end up invoking the item in an infinite loop, because it would look perpetually ready to run. However, doing this on all exceptions interfered with certain non-error cases where exceptions were used to jump out of the action handling. Limiting the caught exceptions to runtime errors should retain most of the intended benefits while avoiding the problems with a more general error catcher here.
    Actor.initiateConversation() now handles a nil 'state' argument slightly differently. In the past, if 'state' was nil, the actor's state was simply left unchanged. Now, the method will switch the actor to the state returned from the current state's getImpliedConvState() method. In most cases, the net effect is exactly as before - i.e., the actor's state is left unchanged - because the default getImpliedConvState() simply returns 'self'. However, ConversationReadyState overrides getImpliedConvState() to return the associated InConversationState. This change makes it easier to initiate a conversation when using an actor with conversational states, since it will generally pick the correct conversational state automatically.

    Note that the ActorState method getImpliedConvState() is new with this change.

    Due to a bug, <.convnode> tags displayed inside npcGreetingMsg methods weren't handled properly if the greeting was displayed due to NPC initiation of the conversation (via npcInitiateConversation()). This has been corrected.
    ConvNode.canEndConversation() can now return a special value, blockEndConv, that indicates that the actor said something to force the conversation to keep going. You should always use this if you display a message in the routine and you want to prevent the conversation from ending. Returning blockEndConv is almost the same as returning nil from the method, but has the additional side effect that the caller will call noteConvAction() on the other actor, to prevent this actor from generating any further scripted remarks on the same turn.
    When the library displays a parser error message for a command directed to an NPC, it now shows the error message in a neutral sense context. This ensures that the message is displayed even if the NPC is in a remote location (this could be the case if we're talking over the phone, for example). In the past, the parser message was generated in the NPC's sense context, so the NPC was in a remote location that wasn't in scope to the PC, the parser message was suppressed, resulting in a "Nothing happens" message or the like.
    When a command of the form "actor, xxx" is entered, and the portion after the "actor" phrase is unparseable, the parser now attempts to parse at least the "actor" part to determine if the command is being directed to an NPC. In the past, the parser didn't do this; the actor phrase is part of the basic sentence grammar, so if the parser failed to match the rest of the basic sentence grammar, it didn't bother trying to figure out if a target actor was included. To find the target actor, the parser now makes a second attempt at parsing a command to see if it matches a very basic sentence syntax that only includes the target actor specification; if it can match the syntax, and resolve the noun phrase to an in-scope actor, the parser takes the result to be the target actor.

    The main result of this change is that a few of the low-level parser message methods - askUnknownWord, specialTopicInactive, commandNotUnderstood - are now invoked on the target actor when one is present in the command. In the past, because the parser didn't even figure out that a target actor was present in these cases, these methods were always invoked on the player character actor. With this change, it's now possible to customize a NPC's responses for unknown words and command phrasings individually by NPC.

    The library now gives a replacement action zero time if it's replacing an action that itself has zero time. This corrects some timing anomalies (particularly with respect to NPCs) that showed up in certain unusual cases where replaceAction() was used in the course of an action that itself was being run as a nested action.
    A new parser option lets you cancel remaining commands on the command line when an action fails.

    To implement this, Action.afterActionMain() checks to see if the action failed, as indicated by a 'reportFailure()' message in the course of the action handling. If the action failed, and gameMain.cancelCmdLineOnFailure is set to true, afterActionMain() throws a CancelCommandLineException. This exception is in turn caught in executeCommand(), which cancels any remaining commands on the command line and simply proceeds to the next turn.

    In addition, the new playerMessages method explainCancelCommandLine() lets you display an explanation when a command line is canceled due to the failure of a command. executeCommand() invokes this new method when it handles a CancelCommandLineException if there are in fact any remaining tokens on the command line. (This check for remaining tokens skips the explanation when there's nothing left on the command line to cancel, as the cancellation obviously has no effect in such cases.) This new message method doesn't display anything by default; it's just a hook that you can use if you want to provide an explanation. Note that you'll probably want to show this explanation only once per game, rather than every time a command is canceled (you can use a flag property to do this - if the flag is nil, show the message and set the flag to true; otherwise skip the message).

    The default setting for gameMain.cancelCmdLineOnFailure is nil. This provides the traditional handling, which simply continues executing any remaining commands on the command line even when an action fails.

    The reason this new feature is an option rather than simply being the default new policy is that neither possibility is ideal in every case. On the one hand, continuing to execute commands after an action has failed can lead to confusing results: the failure of the earlier command can leave things in a state other than what the player was anticipating, which could change the behavior of subsequent commands. So the argument in favor of canceling remaining commands is that it avoids this possible source of player confusion by halting processing once it appears that the player's thinking is out of sync with the game's internal state. On the other hand, exactly what constitutes failure might not always be apparent to the player, so halting halfway through a command line might sometimes appear arbitrary to the player, or even buggy. The argument in favor of continuing to plow through the rest of the command line like nothing happened, then, is that it's simple and consistent. A prime virtue of any UI is predictability, so that the user has an easier time forming a working mental model of the software, and the most predictable behavior is to unconditionally execute everything on the command line. Further, given that multi-level UNDO is available by default in tads3 games, any unintended effects from extra commands can always be manually undone as soon as the player realizes what happened. The arguments on both sides are valid, so the library leaves it up to the author to set the game's policy. (It's even been suggested that this ought to be left up to the player, via a command that selects the mode, but I think this would be overkill, so for now this is just an author-controlled option. Games are free to add their own command to let the player control it, of course; it's just a matter of flipping the cancelCmdLineOnFailure setting at run-time.)

    The Action class has a new method, checkAction(), that is called just before the existing execAction() method. TAction and TIAction define the new method, and perform the calls to the direct and indirect object 'check' methods in this new method rather than in execAction(), as was done in the past.

    This change should have no effect on existing code, since it's a simple internal rearrangement. The benefit is that it's now possible for game code to call the 'check' phase of a verb explicitly, without also invoking the 'execute' phase.

    OopsIAction is now defined as an IAction. (In the past, it was based directly on Action, which was problematic if an OopsIAction grammar match was used in certain contexts, such as CommandRanking.sortByRanking.)
    ObjectPreCondition now uses the same ordering as the underlying condition.
    During calls to the "verify" methods for remapped actions (i.e., actions mapped to different actions using remapTo), the library now sets gAction to the remapped action. In the past, during the initial remap testing stage, the library left gAction with the original action that triggered the remapping. This gives game code more consistent information during the verify phase.
    VocabObject now defines the methods isOwnedBy(obj) and getNominalOwner(). This allows player input to refer to non-physical objects, such as topics, using possessive syntax.
    The INSTRUCTIONS command is now ignored for the purposes of UNDO and AGAIN.
    The parser no longer chooses Unthings as default objects in cases where noun phrases are missing in player input. (This is handled via Unthing.hideFromDefault().)
    The new Thing method setGlobalParamName() lets you add a global message parameter name dynamically. In the past, there was no straightforward way to do this; you had to manually update the message builder's internal table of names in order to add a new name. This method takes care of all of the necessary internal table updates.

    You only need to use this method if you want to add or change a parameter name dynamically at run-time. The library still automatically initializes the message builder's tables for globalParamName settings defined at compile-time.

    The parser now marks a plural-phrase match for a direct or indirect object as "unclearly disambiguated" if the phrase also matches a singular noun for an object that's also in scope. This flag makes the parser generate an extra message to notify the player that the parser chose an object from a potentially ambiguous set:

      >show bob the rags
      (the piece of cloth)
      Bob does not appear interested.
    

    If the parser's decision was wrong, the message will alert the player, so that the player will know to try rephrasing the command rather than being left with the impression that the intended command didn't work.

    The event manager (the part of the library that manages fuses and daemons) now automatically disables a daemon or fuse that throws an exception out of its main execution method. The reason this is important is that an infinite loop would otherwise occur in many cases: if the object remained active, the event manager would re-invoke it after the exception, and in most cases the re-invocation would simply encounter the same exception, at which point the event manager would invoke it once again, and so on ad infinitum.

    Note that this change only comes into play when a fuse/daemon throws an exception out of its main method. It won't affect a daemon/fuse that merely encounters an exception in the course of its processing, as long as the fuse/daemon catches and handles the exception.

    A new class, OneTimePromptDaemon, makes it easy to set up a prompt daemon that only fires once. It works like an ordinary prompt daemon, but removes itself from the active event list as soon as it fires. This can be handy for cases where you want to defer some non-recurring processing until just before the next command prompt.
    Thanks to Greg Boettcher, the English library's default messages now use typographical ("curly") quote marks and apostrophes. In cases of open/close quote marks, these are generated with <Q>...</Q> markups; in cases of apostrophes, &rsquo; entities are used.

    This change is purely cosmetic, so it should require no changes to existing game code. It shouldn't even affect test scripts (where you run an input script and "diff" the output against a reference log) - by default, the log file mechanism by default generates transcript output in plain ASCII, and the curly quotes are mapped to ordinary straight quotes when rendered in plain ASCII.

    The library now attempts to be as precise as possible when announcing objects chosen in vague disambiguations, as defaults for missing noun phrases, and for multiple objects to a verb. In the past, the library simply used the base name of the object being announced, but this didn't take into account the various things that the parser can use to distinguish objects on input, such as lit/unlit state, location, and owner. The library now generates these object announcements using the same Distinguisher mechanism it uses to resolve ambiguous noun phrases in input, ensuring that the generated names are as precise as possible in distinguishing announced objects from others in scope.

    To accomplish this, the announcements call upon a new Thing method, getInScopeDistinguisher(). This method looks at the Distinguisher objects associated with the object to be announced, and tries to find one that can distinguish the object to be announced from every other object in scope. If it finds one, it returns it. If it fails to find one, it returns the distinguisher that does the best partial job - that is, the one that distinguishes the object from the largest number of other in-scope objects. (The method never returns nil; in the worst case, it simply returns basicDistinguisher, which distinguishes objects based on their disambigName properties.) The announcements then use the returned Distinguisher to generate the announced name.

    Note that in the simplest case, this change results in the disambigName being used in object announcements; in the past, the base name was used. In most cases, this won't cause any change in game behavior, since the disambigName for most objects is just the base name anyway.

    The library now suppresses vague disambiguation announcements for objects specified with indefinite articles when the announcement would be redundant. That is, if there's no Distinguisher that can tell apart any of the possible matches for an object specified with an indefinite article, the library doesn't bother making the announcement, because it would add no useful information for the player. For example, in the past, you might have seen an exchange like this:

       >take a silver coin
       (the silver coin)
    

    The parser was making the announcement because it had chosen arbitrarily from one of several silver coins. But since all of the possible matches were indistinguishable anyway, the announcement doesn't help the player see which one in particular was chosen. The library now suppresses the message in this case. Note, however, that if it's possible to distinguish any of the possible matches from one another, you'll still see a message. For example, if there's a silver coin and a gold coin present, and the player types TAKE A COIN, the library will announce which one (gold or silver). Similarly, if there's a silver coin on the table and another on the floor, and the player types TAKE A SILVER COIN, the library will mention the location of the one chosen: "(the silver coin on the floor)", for example.

    The English library can now correctly generate a plural name for an object whose name includes an "of" phrase; for example, the library now correctly generates the plural for "piece of paper" as "pieces of paper". In the past, the library pluralized the last word of the entire phrase, so the result in this example would have been "piece of papers". The new algorithm when there's an "of" is simply to pluralize the last word before the first "of", using the same rules as for a phrase without "of".
    In the past, the English parser treated a command of the form GIVE adjective noun as though it meant GIVE noun TO adjective. This was almost never what the player actually meant; the player almost always really meant GIVE object TO the current interlocutor, where object is named adjective noun. The parser now applies that more likely interpretation when confronted with this syntax. The same applies to SHOW.

    (As part of this change, a new parser class was added: ImpliedActorNounPhraseProd. This is a subclass of EmptyNounPhraseProd that works almost the same way, but doesn't apply a grammar ranking penalty for the missing noun phrase if a default interlocutor can be identified. The single-noun-phrase grammars for GIVE and SHOW use this new class, ensuring that they're chosen over the non-prepositional two-noun-phrase phrasings whenever a default interlocutor is present.)

    In the past, the English parser treated the word "of" as a non-weak token when matching noun phrases of the form "x of y". This meant that if a noun phrase consisted only of weak tokens and "of", the parser matched it. This has changed; "of" is now ignored for the purposes of the weak-token test, so a phrase consisting only of weak tokens and "of" is now considered weak overall.
    A bug in the English library caused run-time errors in some cases involving TopicAction verbs. The problem was that some of the TopicAction message generators assumed that they were the current active verb, which isn't always the case. This has been fixed.
    The English library defines the new verb phrasings ASK actor topic and TELL actor topic; these are phrasings for the new actions AskVague and TellVague, respectively. These are defined entirely to provide more helpful error messages in cases where the player enters an ASK or TELL verb without an ABOUT phrase. The default handling for the new actions simply displays an explanation of the proper ASK ABOUT or TELL ABOUT phrasing. Some players have been seen to misinterpret custom phrasings that show up in SpecialTopic prompts, such as "ask bob why", as general-purpose command phrasing, and then attempt to use similar phrasing elsewhere in the game. This results in "invalid command" errors from the parser, of course, but the standard error messages were often unhelpful with this particular kind of phrasing error. The new handlers are meant to improve on the standard error messages for this common case.
    The English library now treats the