Recent Changes to the TADS 3 Library
This is a history of changes to Adv3, the TADS 3 adventure game
library. Changes are listed in reverse chronological order (the most
recent changes are first).
Released May 16, 2013
A bug in the parser caused a run-time error when a player attempted to
traverse an AskConnector. This is now fixed.
(
bugdb.tads.org #0000169)
For conversational commands such as HELLO, GOODBYE, YES, and NO, the
parser now sets the sense context to the issuing actor (that is, the
actor who issued a command, which is usually the player character)
rather than the target actor (the target in BOB, GOODBYE is Bob). For
most commands, the target actor is the one who's meant to carry out
the command - BOB, GO NORTH asks Bob to go north - so it's appropriate
to execute most actions in the sense context of the target actor. In
the past, the parser did just this, unconditionally for all types of
actions. However, this isn't appropriate for conversational commands,
because the meaning of the target actor is different in these
commands; the target specifies the person being addressed in the
conversation rather than the actor carrying out the command. BOB,
HELLO doesn't tell Bob to say hello, but rather tells the player
character to say hello to Bob. In these types of commands, then, the
actor actually carrying out the command is the issuer, not the target,
so the sense context should remain with the issuer. The parser now
makes this distinction when setting the sense context.
(
bugdb.tads.org #0000190)
A parser bug caused the same object to be matched multiple times in a
disambiguation query under certain circumstances. If an object
defined multiple 'noun' words that all had a common prefix up to the
truncation limit, and there were two or more objects with the same
list of similar nouns, and the player entered a noun phrase that
matched the common leading substring, a normal disambiguation question
would be triggered. If the player then answered this question with
ALL, the parser matched each object multiple times, equal to the
number of matching (truncated) nouns per object.
The key element to triggering the bug is multiple noun synonyms
that share a common prefix that's longer than the truncation limit.
It's rare to find examples of this in English games, but it's common
in inflected languages where nouns have multiple forms with different
endings (the bug was discovered in a Czech translation). Here's a
somewhat contrived English example. The kind of compass you use to
inscribe circles can be called a COMPASS or COMPASSES - it's one of
those odd English words like "scissors" or "pants" where the plural
noun form can be used for the singular object, but (unlike scissors or pants)
the singular form can be used interchangeably. If you set the TADS
truncation limit to 6 characters, and then create a red compass and a
blue compass with COMPASS and COMPASSES as noun synonyms, the bug
would have caused the following odd interaction:
>x compas
Which compas do you mean, the red compass or the blue compass?
>all
red compass: It's an ordinary red compass.
blue compass: It's an ordinary blue compass.
blue compass: It's an ordinary blue compass.
red compass: It's an ordinary red compass.
This is now fixed.
(bugdb.tads.org #0000188)
The library showed the wrong default message for the THROW DOWN
command (that is, the ThrowDir action, when the direction is Down).
The message used to be the rather non sequitur "You're not carrying
that"; the message now says "You should just put it down instead."
Released August 20, 2012
In the past, atmosphere messages (via the Room atmosphereList
property) didn't display a paragraph separator before the message, so
if whatever item happened to be displayed just before an atmosphere
message didn't add a paragraph separator at the end, the atmosphere
message was shown as a continuation of the preceding paragraph.
Room.roomDaemon() now explicitly adds a <.commandsep> before
each atmosphere message to ensure that a paragraph break is displayed.
(
bugdb.tads.org #0000161)
Released July 14, 2012
The "inventory tall" lister incorrectly listed the contents of
contentsListedSeparately containers both in-line and separately.
This has been fixed, so that only the separate contents list is shown.
(
bugdb.tads.org #0000116)
The Thing methods for LOOK and EXAMINE that generate listings of contents
now pass an additional named parameter, "examinee:", to the list methods.
This is occasionally useful when one of the listing-related methods on
one of the contents objects needs to know exactly what the context of
the listing is.
The specific situation that motivated this change was a problem
involving MultiLoc items with special descriptions, as described in
the next item.
In the past, a MultiLoc with a special description was incorrectly
included in the miscellaneous object list portion of a LOOK
description for its room, or an EXAMINE description of its container.
A special description is supposed to supersede inclusion in the
ordinary list, since it provides the same information. This
now works correctly.
The reason for the double listing was that the method that decides
whether or not to include an object in the ordinary list,
isListedInContents, didn't deal properly with MultiLoc objects. In
fact, it couldn't deal with them properly, because it didn't have
enough information, namely the identity of the object being
examined.
For an ordinary Thing, the examinee is implicit in the call to
isListedInContents, because this method is called only on direct
children of the examinee; a Thing has only one direct container, so
the examinee is implicitly the Thing's location. This logic doesn't
work for a MultiLoc, though, because a MultiLoc can have multiple
direct containers. So, to work properly, this method needed to know
the examinee. This information is now available to the method via
the new "examinee:" named parameter that the various Thing methods
for LOOK and EXAMINE now supply. A new BaseMultiLoc.isListedInContents
method uses the "examinee:" parameter to get the correct listing
information.
(bugdb.tads.org #0000119)
openableContentsLister has been renamed to openableDescContentsLister,
because the original name was inconsistent with the corresponding
listers for other classes. xxxContentsLister is supposed to be the
lister assigned to the 'contentsLister' property for class xxx, and
xxxDescContentsLister is the one for 'descContentsLister'.
('contentsLister' is the lister used for descriptions of objects
containing the xxx, such as the enclosing room description;
'descContentsLister' is for EXAMINE descriptions of the xxx itself.)
There isn't (and never was) a separate 'contentsLister' for Openable;
the former openableContentsLister was always its 'descContentsLister',
but unfortunately wasn't named accordingly.
To minimize the impact on existing code, adv3.h provides a #define
that maps the old name to the new name. In the case of Openable,
there's no need for a specialized 'contentsLister' at all, so there's
no practical need to distinguish the two names, allowing us to create
the mapping for the sake of compatibility. Existing code shouldn't
notice the difference unless it defined an openableDescContentsLister
object of its own, which seems unlikely given that
openableContentsLister already filled the role implied by that name.
The dobjFor() and iobjFor() macros in adv3.h now have some special
code that catches handler definitions for non-existent Actions. If
the Action named in the dobjFor() or iobjFor() doesn't exist, the
compiler will report an "undefined symbol" error naming the Action
object. This only happens in debug builds (e.g., using the "-d"
option for the command-line compiler), since it generates some extra
code to create a reference to the action object. The warning can be
helpful for catching typos (misspelled action names) or references to
actions you intended to define but never got around to.
(
bugdb.tads.org #0000135)
The English parser is a little more nuanced about the way it handles
queries for missing direct objects for verbs with prepositional phrasing
for the direct object. For example:
>DIG
What do you want to dig in?
>IN THE DIRT
In the past, the parser treated the reply "IN THE DIRT" as a new
command meaning ENTER DIRT. The assumption was that if the reply
matched the phrasing of a new command, it must in fact be a new
command rather than a reply to the missing object query. In many
cases where there's this kind of ambiguity, the parser would have to
be able to read the user's mind to know what was intended, but in this
particular case the parallel phrasing of the query ("...dig in?") and
the reply ("in noun") suggests pretty strongly that the user
did indeed intend the input as a reply to the query rather than as a
new command.
To address this, the parser has a new method for production
matches, isSpecialResponseMatch. This new method returns true if the
production matches special phrasing that makes it more likely to be a
query response than a new command, otherwise nil. The English parser
uses this for the various prepositional response phrasings, such as
inSingleNoun and forSingleNoun; these are all subclasses of
PrepSingleNounProd. When one of these productions matches input that
starts with its preposition, it will return true from
isSpecialResponseMatch, causing the missing object query processor to
treat the match as a reply to the query rather than as a new command,
even when the phrasing could also be interpreted as a new command.
(bugdb.tads.org #0000147)
A bug in the parser caused the wrong message to be displayed when a
command was directed to an actor, and the predicate portion of the
command contained an unknown word, and the noun phrase wording for the
actor portion matched at least one grammatical structure phrasing that
could only refer to an out-of-scope object. In such a case, the
parser reported "You see no
actor here" rather than "The word
word is not necessary in this story". This was obviously the
wrong message when an actor matching the noun phrase actually was in
scope, but was also inconsistent with the "unknown word" message shown
in other similar cases that didn't meet all of the bug conditions.
This has now been fixed so that the "unknown word" message is used.
(
bugdb.tads.org #0000149)
In the past, the library incorrectly called breakTopicTie() on each
matching topic with the topic object as the first argument, instead of
passing the whole list of matching topics as advertised. This has
been corrected.
(
bugdb.tads.org #0000138)
Released 12/21/2011
The on-again, off-again
detailed-naming scheme is now on
again, this time with enhancements that will (hopefully) make it here
to stay. This feature was first introduced in version 3.0.10, then
disabled by default in 3.0.11 due to rough edges in that first
attempt. It's reinstated in this release, with improvements. This
improved version has been separately available for some time in an
extension called newNames.t, so it's seen some testing and we think
it'll provide good results.
The problem with the 3.0.10 implementation was that it made the
object-name announcements unnaturally verbose - it added an excess of
qualifiers to object names compared to what a human speaker would use.
The new version tries to be smarter about adding only useful detail,
by using more contextual information. For a multi-object
announcement, for example, the new version only adds enough detail to
distinguish the objects being announced from one another (rather than
from everything else in scope), and also considers the context at a
fixed point in time. The new version also uses the ordinary name for
an object whenever that's enough to distinguish it; it only uses the
(usually more verbose) disambig name when it's needed.
If you find that the new version still produces awkward
announcements, or if you just prefer the original style, you can
disable this feature: just set
gameMain.useDistinguishersInAnnouncements = nil. This makes the
parser revert to the basic object names in announcements.
Note that this change makes the newNames.t extension superfluous,
since the library now incorporates it in its entirety.
It's now possible for a game to provide custom code to break ties in
topic matches, when more than one topic matches the player's input
with the same score. In the past, the parser just chose a matching
topic entry arbitrarily. In some cases, it's possible for the game to
make a smarter choice based on the exact input text. In addition, the
parser now automatically filters tie lists by selecting exact text
matches over truncated text matches.
To add a custom tie-breaker, you add the method breakTopicTie to
one or more of your TopicEntry objects. This method receives as
arguments the list of tied TopicEntry objects, the original
ResolvedTopic object from the parser, the atcor, and the original
input token list. When a topic tie occurs, the parser calls this
method on each tied TopicEntry object in turn, in in arbitrary order.
This method can either select the tie-breaker or do nothing. If it
selects the tie-breaker, the selection process ends immediately with
the selected winner; otherwise, the parser continues calling
breakTopicTie on the remaining TopicEntry objects in the list of ties.
This method was added to address a situation where two topics
happened to match the same input due to vocabulary truncation.
Suppose we have a game object that matches the vocabulary "elephant",
and another that matches "elephants". If the player enters ASK ABOUT
ELEPHANT, the word ELEPHANT in the input will match both game objects,
since the parser allows the user to abbreviate words by truncating to
the first six characters only. Now suppose we have separate topic
entries for these two objects, and that both topics are active at the
same time and have the same match score. This results in a topic tie,
triggering the new tie-breaking code.
For the sake of this example, let's suppose that you want to
distinguish between ASK ABOUT ELEPHANT and ASK ABOUT ELEPHANTS (which
stands to reason, as you probably wouldn't have created both topic
entries otherwise). In the past, since we have a tie between the two
topic entries, the parser simply chose one arbitrarily. You can now
specifically select one or the other for a given entry, by writing a
breakTopicTie method. Since we want ASK ABOUT ELEPHANT to match the
singular version of the topic, here's how the method would look:
breakTopicTie(matches, topic, actor, toks)
{
if (toks[1].toLower() == 'elephant')
return self;
return nil;
}
You can add this method to either TopicEntry object, but it really
belongs conceptually with the singular version of the topic.
The default handling for breakTopicTie() is simply to return nil,
which defers to other objects in the list to make the selection.
For the specific case of truncation matching, you actually don't
have to write a breakTopicTie() method after all, since the parser now
also uses vocabulary truncation to break ties on its own. It
does this after applying any custom breakTopicTie() results.
That is, if breakTopicTie() fails to break the tie, because none of
the TopicEntry objects return a non-nil result for the method, the
parser tries to break the tie on its own by comparing vocabulary
truncation flags. If one topic entry matched with truncation, and
another didn't, the parser will select the one that matched without
truncation.
The parser selects by truncation because this is probably the most
common basis for selection. But it's certainly not the only basis.
Other selection criteria could include word order, and the presence or
absence of certain words. Those sorts of criteria would be very
difficult to encode into a general framework, thus breakTopicTie()
leaves it to the game to make the selection.
This feature addresses bugdb.tads.org #0000078.
The scheme that selects which message to display for a reportFailure()
or similar message call had a problem when a verb had both a direct
and an indirect object,
and both of those objects had overrides
for the same message. The message generator always gave priority to
the direct object when both objects had overrides, which was wrong if
the message was due to a condition relating to the indirect object.
For example, given an Underside and a Container, both with limited
bulk, "PUT Underside IN Container" showed the Underside's bulk error
message ("The Container won't fit under the Underside"), since the
Underside is the direct object.
The message generator now gives priority to the calling
object. The generator looks in the stack trace for a "self" object in
the source list, and gives priority to the first source object it
finds. This is the source object whose method is actually generating
the message; this will usually be a verify(), check(), or action()
method to process a verb, so presumably it's the object to which the
error condition applies.
(It's certainly valid to generate messages from other places than
the verify(), check(), and action() methods, or indeed from an object
that's not one of the message sources. But the source priority
shouldn't matter in those cases, because the message presumably has no
particular object affiliation anyway if it's not being generated by
code attached to one of the source objects.)
(bugdb.tads.org #0000061)
The new TAction methods getDobjTokens() and getDobjWords() make it easier
to retrieve the original text entered by the player to refer to the current
direct object. These work only when there's an active direct object
(a non-nil object in gDobj).
The corresponding new TIAction methods getIobjTokens() and getIobjWords()
retrieve the original text of the current indirect object.
The token lists returned generally contain the full phrase that the
player entered, including qualifiers like articles ("the") and possessives.
When the player enters a list ("take book, bell, and candle"), the
token list will only include the phrase for the current object, not
the whole list.
Original text isn't always available even when there's a valid
direct/indirect object in play. For example, text isn't generally
available when the action is a nested or implied action, since these
actions are synthesized internally within the game rather than being
parsed from user input. The new methods return nil when there's no
original text available for the corresponding objects.
In the past, the getOrigTokenList() method on a noun phrase that had
been manually disambiguated ("which book do you mean...") returned
only the tokens of the user's last answer. For example, if the player
typed "take book", and answered the "which book" question with "blue",
then getOrigTokenList() on the resulting noun phrase returned just the
"blue" token.
The parser now builds a token list that has the complete original
noun phrase plus the disambiguation answer (or answers, if multiple
questions were needed to narrow the list sufficiently). The naive way
to do this (at least in English) might be to simply prepend the
disambiguation answer to the original noun phrase, on the assumption
that the answer is simply an adjective modifying the original phrase;
this would work for the "blue book" example above. One problem with
this is that some names include prepositional phrases, such as "stack
of bills"; we'd want a "which one?" answer of "$20" to turn this into
"stack of $20 bills" instead of "$20 stack of bills", since that would
mean something rather different. The bigger problem is that the
parser accepts a number of answer formats that aren't mere adjectives,
such as "the blue one", "both", "any of them", and "2" or "second" or
"the second one" to pick the second item listed in the parser's "which
book" question. These obviously would produce nonsense using the
naive algorithm.
The parser's solution is to append each new answer parenthetically
to the end of the original phrase. This produces a result that's at
least comprehensible (and usually pretty good) for any answer format,
because it parallels the way the phrase was actually built, as a
series of post hoc clarifications: "book (the blue one)" or "tea (Earl
Gray) (hot)". Further, if there's a need to display the phrase to the
player, it should make sense to the player because it parrots back
what they just typed, retaining the same word order and grouping.
SenseConnector now overrides checkThrowViaPath(), to handle throwing
objects across the connector independently of "move" and "reach"
operations. In the past, throwing was handled as equivalent to
moving, but this caused an unintended change of behavior when
DistanceConnector was recently changed (in 3.0.16) to allow moving
objects across a distance.
SenseConnector also defines a new method, checkThrowThrough().
This method is the "throw" equivalent of the existing methods
checkMoveThrough() and checkTouchThrough(). The default definition of
the new method returns a failure indication, parallel to the
corresponding existing methods for "move" and "reach".
Finally, DistanceConnector overrides checkThrowThrough() in
parallel to its corresponding overrides for "move" and "reach".
This change (a) restores the pre-3.0.16 behavior for throwing
across a distance, by disallowing it, and (b) makes it possible to
define independent move, reach, and throw handling for sense
connectors.
The English parser now accepts "LOOK
thing" and "L
think" as synonyms for "LOOK AT
thing", since it's been
observed that many novice players use this syntax.
(bugdb.tads.org #0000091)
Thing.addToContents() now skips adding the child object to the
'contents' list if it's already there. In the past, it was possible
to cause an item to added to its parent's contents list twice, by
explicitly using moveInto() in an initializeThing() override; this
caused strange behavior, such as listing the child item twice in
contents list messages. The extra check avoids this unintended
duplication.
(bugdb.tads.org #0000067)
Using a RoomPart as a regular contents item in a room (rather than as
an element of a roomParts list) caused a run-time error on LOOK AROUND
in some cases. This has been corrected.
(bugdb.tads.org #0000076)
In the past, if you put one StretchyContainer inside another, the library
didn't properly enforce the bulkCapacity of the outer container when adding
new contents to the inner container. (Since the inner container is itself
a StretchyContainer, its bulk can increase when a new item is added, which
can make it too large for the outer container.) This has been corrected.
(bugdb.tads.org #0000068)
ASK ABOUT and other topic verbs sometimes didn't work properly when
plural words were used in the topic phrase. This has been corrected.
(bugdb.tads.org #0000069)
The English command grammar now allows "the" as an article in
possessive phrases, as in "the orc's axe". In the past, possessive
phrases didn't allow any article qualifier (the library took the
overly narrow view that possessives are usually proper names, as in
"Bob's book").
In addition, plural possessives, signified by an apostrophe at the
end of a plural word, are now accepted. For example, "the Smiths'
house" would be accepted if "Smiths" is defined as a plural word.
(bugdb.tads.org #0000077)
The Lister had a bug involving list groups with minimum group sizes.
If there weren't enough candidate items to list with a group,
according to the group's minGroupSize property, but there were more
than one item, the list builder incorrectly included only one of the
items in the final list, omitting the others. This has been corrected
- the list builder now includes moves all of the candidate items to
the "singles" list, so they all show up in the final list.
(
bugdb.tads.org #0000083)
In the past, if a command contained a noun phrase of the form THE ONE
IN
CONTAINER, and the phrase required interactive
disambiguation, the parser incorrectly acted as though the player
answered the "which one do you mean?" prompt with ALL. This was due
to a parser bug; it's now fixed.
(
bugdb.tads.org #0000095)
The parser now gives priority to a possessive-phrase interpretation of
a noun phrase that doesn't match anything in scope, in cases where the
same words could be interpreted as a plain noun phrase without any
possessive qualifiers. For example, in the library sample game, we
have an object defined with vocabulary words including "Bob's fish
cleaner", which means that "Bob's" is defined as an adjective in the
dictionary. "Bob" also is the name of an actor in the game, so the
word "Bob's" in player input can be interpreted in two ways: as an
adjective, or as a possessive qualifier. If the player enters a noun
phrase such as "Bob's desk", the same phrase can be interpreted with
two very different grammatical structures: the possessive qualifier
"Bob's" modifying an underlying noun phrase "desk", or the noun phrase
"Bob's desk", where the first word is a simple adjective from the
dictionary. In the past, the parser considered the two phrasings be
equally strong at the structural level. If one phrasing has a valid
resolution (i.e., it could be mapped to in-scope objects) and the
other doesn't, the structural equivalence doesn't matter, because the
parser always picks the phrase that has the best resolution to game
objects. However, in cases where neither phrasing has a resolution -
because there's no desk in scope at all, say - the parser picks the
interpretation based on structural strength. Since the two phrasings
were equally strong, the parser picked one arbitrarily. If the winner
happened to be the adjective interpretation, this produced the odd
error message "You see no bob's desk here." Now, with this change,
the parser gives priority to the possessive interpretation in cases
where other things are equal, so the error will be "You see no desk
here." This is a better message because the unmatched object really
is simply "desk", regardless of whose desk it is; reporting that
"bob's desk" is the missing object seems overly literal.
(
bugdb.tads.org #0000096)
Room now provides a suitable message for cases where objects are
thrown within the room, but there's no floor object among the room
part list for the room. In the past, this caused a run-time error,
because there was no message defined for this situation.
When an object is thrown, the library's default handling is simply
to drop the object into the room, as though the player had typed DROP
PROJECTILE. The default message, though, describes the object
as "falling to the floor", where to the floor is
replaced by a message that depends on the "nominal drop destination"
for the location - i.e., the object where things are described
as landing when dropped in the room. The standard nominal drop is the
room's floor, which is to say the Floor object in the room's roomParts
list. If there's no floor object, though, the fallback is the Room
itself. The run-time error occurred because Room didn't have the
associated message property for this situation. The to the
floor phrase in the message is obtained via the putDestMessage
property, which Room didn't define. Room now defines this property as
the message property pointer &putDestRoom. libMessages in the
English library in turn defines putDestRoom to display "into the
room". The result is that THROW PROJECTILE AT WALL in a room
without a floor now properly replies "...and falls into the
room", instead of causing a run-time error.
(bugdb.tads.org #0000078)
Using replaceAction with GiveTo sometimes caused a run-time error.
(This was due a complex interaction with the giveMeToAskFor global
remapping. AskFor is a "topic" action, but GiveTo isn't; the remapping
tried to resolve the GiveTo direct object as a topic for AskFor, but
within the context of the GiveTo verb, which didn't provide one of
the required resolver methods, createTopicQualifierResolver. This
method is now defined for the base Action class, since this situation
creates a dependency on it even for ordinary non-topic action types.)
(bugdb.tads.org #0000103)
The English library message for an empty topic inventory has been reworded
slightly for the past-tense version, replacing "right now" with "just then".
Released 5/5/2009
A typo in the English message for refuseCommand has been fixed.
Released 4/28/2009
More of the lister classes have been reorganized along the same lines
as the BaseContentsLister refactoring in version 3.0.17. In particular:
- surfaceLookInLister, undersideLookUnderLister, and rearLookBehindLister
are now trivial classes based on the new class LookWhereContentsLister.
For consistency, thingLookInLister is based on the new class as well,
but is non-trivial due to its different wording.
- surfaceInlineContentsLister, undersideInlineContentsLister, and
rearInlineContentsLister are now trivial classes based on the new class
BaseInlineContentsLister. For consistency, inlineContentsLister
is also based on the new base class, but is non-trivial due to
its different wording.
When an action remapping is inherited from a base class, a subclass or
instance can now define a verify() method for the original action. In
the past, only the
new action's verify() was called - the
remapping bypassed any dobjFor() or iobjFor() definitions for the
old action. This made it impossible to tweak the subclass handling
for verbs that were remapped in base classes.
With this change, the verify() for the new and old actions
are both called, if there's a verify() for the old
action defined on a subclass of the class where the remap() itself is
defined. In other words, if the verify() "overrides" the remap(), the
subclassed verify() is invoked.
Because the verify() methods for both the old and new actions are
called, you can't actually override the new action's verify() call.
But verify() results are cumulative, so you can use it to
adjust the logicalness of the action, or to make the action illogical.
The parser can now has an option to generate more descriptive messages
when noun phrases are disambiguated using the logicalness rules.
Disambiguation occurs when the player uses a noun phrase that
matches more than one in-scope object. For example, if the current
room contains a red door and a blue door, and the player types OPEN
DOOR, the noun phrase DOOR could refer to either of the two door
objects. The parser must decide which of the two objects the player
is referring to; this process is called disambiguation. The parser
does this by applying the various logical, logicalRank, illogicalNow,
illogicalAlready, and related rules defined on the matching objects in
the library and in your game code.
There are two kinds of disambiguation results: "clear" and
"unclear". When exactly one of the objects involved is logical, it's
a clear disambiguation result: we consider it safe to assume that the
player was referring to that exact object, since the other choices
simply don't make sense. When more than one object is logical,
though, the parser can sometimes still pick one automatically (i.e.,
without generating an extra question asking the player to specify
which one she intended) based on the "likelihood" rankings given by
logicalRank rules. A pick based on likelihood rankings is an unclear
result, because the player could plausibly have been referring to one
of the other logical matches.
For unclear results, the parser has always generated a
parenthetical announcement saying which object it chose. This is to
alert the player to the parser's choice, so that any misunderstanding
is immediately apparent.
However, in past versions, the parser didn't say anything to
explicate clear disambiguation results. In practice, even clear
results can sometimes disagree with the player's intentions, for the
simple reason that the player sometimes misunderstands the current
state of the objects. So some authors have found that it's better to
make some kind of object announcement in every case, regardless of how
clear the parser thinks the resolution is.
The parser now provides two new modes, in addition to the
traditional mode, for announcing clear disambiguation results. These
new modes are selected via the new gameMain property ambigAnnounceMode.
You can set this in your gameMain object to select the behavior you
prefer. The settings are:
- AnnounceUnclear: The parser makes a parenthetical announcement
(such as "(the red door)") only when a disambiguation result is
unclear. It doesn't make any announcements for clear disambiguation
results, or when only one in-scope object matches a noun phrase.
This is the traditional behavior that the library used before the
new options were added.
- AnnounceClear: The parser makes a parenthetical announcement
for any disambiguation result, whether clear or unclear.
No announcement is generated when there's no disambiguation, though
(that is, the noun phrase itself matches only one in-scope object).
- DescribeClear: For unclear disambiguation, the parser makes the
traditional parenthetical announcement. For clear disambiguation,
the parser skips the parenthetical announcement, but it uses more
detailed default reply messages in lieu of the ultra-terse defaults,
such as "Taken" or "Dropped". For example, instead of just "Taken",
the parser replies "You take the dusty old tome." The longer messages
mention the objects involved by name, so they provide the same
information as the parenthetical announcements, but in a somewhat
less conspicuous and mechanistic way.
Note that the new default behavior differs from the old behavior
from before this feature was added. If you prefer the old behavior,
you must explicitly set gameMain.ambigAnnounceMode to AnnounceUnclear.
The short-vs-long message selection for the DescribeClear option is
done through a couple of new MessageHelper methods, shortTMsg() and
shortTIMsg(). If you're adding new messages of your own, you can use
these methods to do the same type of selection.
The parser can now accept short-hand replies to disambiguation queries
involving a locational qualifier, using nothing more than an adjective
or noun from one of the location names. For example:
>take torch
Which torch do you mean, the torch in the left sconce, or the torch in
the right sconce?
>left
Taken.
In the past, it was necessary for the response to be explicit about
the location qualification: THE TORCH IN THE LEFT SCONCE, THE ONE IN
THE LEFT, etc. In particular, the response had to actually use the
locational phrasing to refer to the distinguishing location. This is
no longer necessary; a player can now simply name one of the
locations, and can even abbreviate to a single distinguishing word
from the location name.
To support this change, the DisambigResolver now keeps track of the
Distinguisher that was used to generate the question (the prompting
message - "Which torch do you mean...?"). This lets the resolver look
for qualifying phrases relevant to the distinguishing characteristic
called out in the prompt.
The Distinguisher, meanwhile, has a couple of new methods that let
it provide the added information: objInScope() lets the distinguisher
add qualifying objects to the scope list, and matchName() lets the
distinguisher recognize qualifying objects in addition to the original
ambiguous objects. The locational and ownership distinguishers
defined in the library provide suitable definitions for these new
methods. Games that define their own custom Distinguisher subclasses
might want to add these methods if they also make use of ancillary
qualifier objects.
In the English library, PathPassage adjusts the logicalness of
the verbs Enter and Go Through downward (to logical rank 50). These
verbs aren't usually used with path-like objects in English;
this logicalness adjustment tells the parser to pick other objects
first in cases of ambiguity.
The new conversationManager method setRevealed(key) adds the given key
to the revealed topic table. The library now calls this method any
time it needs to add a key to the table (whereas it used to manipulate
the table directly).
These changes don't affect any existing behavior - existing code
will continue to work unchanged. The purpose of the changes is to
provide a single point for overriding the default library behavior via
'modify'. In particular, you can override setRevealed() to store
additional information about the revealed topic in its table entry,
beyond the mere fact of the revelation. You could, for example, store
the location where the key was revealed, or a time stamp marking when
it was revealed.
A new ConvNode method, noteActiveReason(reason), extends the older
noteActive() method by providing information on why the node is being
activated. The 'reason' parameter is a string indicating what
triggered the node change:
- 'convnode' - processing a <.convnode> tag
- 'convend' - processing a <.convend> tag
- 'initiateConversation' - a call to Actor.initiateConversation()
- 'endConversation' - a call to Actor.endConversation()
By default, this new method simply ignores the reason code and
calls the noteActive() method. This ensures that existing code will
work unchanged.
If you need the reason information when activating the node,
override noteActiveReason(). Otherwise, you can override noteActive()
as in the past.
The new reason code information is supplied when the caller changes
the node by calling the new Actor method setConvNodeReason(). This is
essentially an extended version of the existing setConvNode() method.
The new method takes an additional argument giving the reason code,
which it passes to noteActiveReason().
Note that the reason code will be nil if the node change is
initiated by calling Actor.setConvNode() directly. All of the library
calls to setConvNode() have been replaced by calls to
setConvNodeReason(), so unless your game code is calling setConvNode()
directly, the reason code will always be set properly. For
compatibility with existing code, setConvNode() can still be called;
it works as before, simply passing a nil reason code.
In the past, when an actor used
conversational states (ConversationReadyState, InConversationState),
terminating the conversation didn't take into account any ByeTopic
objects in the current ConvNode within the conversation. This was
because the conversation states have some special transition handling
that overrides the usual code where the ConvNode ByeTopic would be
found. The conversation states now take care to check the ConvNode
in effect at the end of the conversation, so ByeTopic objects within
a ConvNode should now interact properly with conversation states.
The transformation from "X, GIVE ME Y" to "ASK X FOR Y" that was
introduced in 3.0.17 caused a run-time error if "Y" was a valid word
but didn't resolve to an object. This has been corrected.
(
bugdb.tads.org #0000021)
In the past, when EXAMINE was used with ItemizingCollectiveGroup, the
output omitted any object that wasn't listable through the room
contents lister. This was a problem for objects such as Fixtures,
which normally aren't displayed in a room listing.
ItemizingCollectiveGroup now calls a new method on self,
examineUnlisted(x), to list each object x that isn't listed
via the room contents lister. By default, this new method performs
a nested EXAMINE command on the unlisted object to show its full
description.
In Settable, the message routing properties have been changed
slightly. In the past, the message properties okaySetToMsg and
setToInvalidMsg were doubly redirected: okaySetToMsg had the value
&okaySetToMsg, and setToInvalidMsg had the value
&setToInvalidMsg. The TurnTo action handler that used these
messages evaluated the properties directly, to get the property
pointers, which the library then processed in the usual way.
The point of this arrangement was that subclasses of Settable (Dial
in particular) could easily redirect these messages to different
library messages, just by overriding the properties. However, this
was inconsistent with the message scheme most other objects use, in
that you couldn't override okaySetToMsg(val) in the Settable itself to
yield the message text, which is how the message scheme works for
most other objects.
The new version keeps the redirection step, but renames it to allow
the standard override scheme to be used. The new property okaySetToMsgProp
evaluates to &okaySetToMsg, and setToInvalidMsgProp evaluates to
&setToInvalidMsg. This means that you can override okaySetToMsg(val)
and setToInvalidMsg(val) using the standard pattern.
(bugdb.tads.org #0000045)
Underside and RearContainer now provide tryMovingObjInto() method
definitions. The new methods try the obvious implied commands
suitable for these objects: Underside tries an implied PutUnder, and
RearContainer tries an implicit PutBehind.
The BannerWindow method updateForRestore() is now called during a
Restore or Undo operation for every banner, even when the banner is
already displayed. In the past, this method wasn't called on banner
windows that were already present; it was only called when the window
had to be created in the course of the Restore/Undo. It's now called
uniformly for all banners.
Released 8/12/2008
This version has no library changes; this release is to correct
a TADS 3 compiler problem introduced in 3.0.17.
Released 8/9/2008
Risk of incompatibility: The standard handling for the TakeFrom
action has been changed slightly. The changes generally make it
easier to code objects by unifying the handling of Take and TakeFrom
for most objects, but some slight adjustments to existing code might
be required - see below.
First, Thing.dobjFor(TakeFrom)'s action() handler now actually
replaces the TakeFrom with a Take action. In the past, the handler
merely invoked the Take action() handler. For the most part, this had
the same effect as replacing the action, but not always; some
arrangements of subclasses and overrides caused subtle differences.
Actually replacing the action ensures that a successful TakeFrom is
handled exactly like a regular Take, which means that custom
behavior for Take will automatically apply exactly the same way to
TakeFrom without any explicit TakeFrom overrides. Of course, you can
still override dobjFor(TakeFrom) separately if you do want it to
behave differently from Take.
Second, Immovable.dobjFor(Take) now fails the action in the check()
handler rather than the action handler, which allows us to remove
Immovable.dobjFor(TakeFrom) entirely. In the past, TakeFrom was
separately overridden to ensure that the indirect object wouldn't
attempt to carry out the action, but this is no longer necessary,
since the basic Take check() phase (which TakeFrom's check() phase
invokes) will fail the command.
These two changes should essentially eliminate the need to override
TakeFrom when all you want to do is make it behave the same as Take.
TakeFrom should now always follow Take's lead, so if you simply want
TakeFrom and Take to behave the same way, just override Take and
you're set.
Some existing code might not work properly with the changes to
Immovable. In particular, you'll need to look at any objects of class
Immovable (or any subclass of Immovable) where you've overridden the
dobjFor(Take) action() handler. In the past, overriding the action()
handler for Take was enough to override Immovable's handling of the
command, since Immovable failed the command in the action() handler
and did nothing in the check() handler. With the change, the check()
handler cancels the action. This means that your overridden action()
handler might never be invoked. The solution is simply to insert an
empty dobjFor(Take) check() handler in your object:
boulder: Immovable 'boulder' 'boulder' "It's a big boulder. "
dobjFor(Take)
{
verify()
{ /* my original verify routine... */ }
check() { } // THIS HAD TO BE ADDED DUE TO THE LIBRARY CHANGE !!!
action()
{ /* my original action routine... */ }
}
;
If you're not sure whether you have any objects needing this change,
you can add this bit of code to your game, and then run the game and
type IMMTEST to get a list of objects that you should inspect:
VerbRule(immtest) 'immtest' : IAction
execAction()
{
"Immovables with dobjFor(Take) action() overrides:\n";
forEachInstance(Immovable, new function(x)
{
if (overrides(x, Immovable, &actionDobjTake))
"\t!!! <>\n";
});
}
;
Krister Fundin's Tips extension has now been integrated into the
library as a standard feature. This system makes it easy to create
custom one-time tips of the sort that the library has traditionally
used for things like the explanation of the NOTIFY command the first
time the game's score changes. The library now uses the Tips system
for its standard tips, and the system is readily extensible with
custom, game-specific tips. A new chapter in the Technical
Manual describes the system.
It's now possible to determine if a script file is currently being
read, and if so, the modes being used to read it. To get this
information, call
setScriptFile(ScriptReqGetStatus). If
input is currently being read from a script, the function returns an
integer value giving a combination of
ScriptFileXxx flags
describing the reading mode; for example, the return value will be
(ScriptFileQuiet | ScriptFileNonstop) for a script being read
in quiet, non-stop mode. Note that a return value of 0 (zero)
indicates that a script
is being read, but that none of the
mode flags are applicable - that is, the script is being read in the
default modes, with output and "more" prompts displayed. If a script
is
not currently being read, the return value is nil.
The new flag ScriptFileEvent will be included in the
return value if applicable. This lets you determine whether the
script being read is an event script or a plain command-line script.
Note that this new flag is "query only" - if you include it in the
'flags' argument in a call to setScriptFile() to start reading a
script, the function ignores it, since the script reader determines
the type of the script to be read automatically by examining the
file's contents. The purpose of this flag is to let you find out how
the script reader is treating the file, rather than letting you tell
the script reader how to treat the file.
Note that this new form of setScriptFile() is only supported in VM
versions 3.0.17 and higher. If you call
setScriptFile(ScriptReqGetStatus) on an earlier VM, a
run-time error result, because older implementations of the function
accept only a filename string or nil as the first argument. You can
use try-catch to handle this situation - a run-time error indicates
that this form of the function isn't supported, in which case the
script status information is not available.
Two new actions have been added: RecordEvents and RecordEventsString.
These are variations on the Record and RecordString actions that
record event scripts (rather than ordinary command scripts, as Record
and RecordString do). The English library defines verb syntax for
these new actions ("record events", "record events on", and "record
events 'filename'").
A new parsing mechanism makes it possible to rewrite commands based on
"global" conditions. Unlike remapTo, which lets you associate
remapping rules with the objects involved in the command, the new
mechanism lets you rewrite a command even before the objects are
resolved. There are cases where it's important to be able to apply
the rewrite before any object resolution has taken place, because the
resolution process itself can vary substantially depending on the type
of action being performed. remapTo has to wait until after objects
are resolved, which is sometimes too late.
This new mechanism is implemented via a new class, GlobalRemapping.
To create a rewrite rule, you create a GlobalRemapping object, and
define its getRemapping() method. This method looks at the action to
determine if it's of interest, using whatever criteria you want. In
most cases, this means checking the action type to see if it's an
action you want to rewrite, and possibly checking to see if certain
objects are involved as well.
The library defines one GlobalRemapping object, giveMeToAskFor,
which rewrites commands of the form "X, GIVE ME Y" to the alternative
format "ME, ASK X FOR Y". The library formerly tried to
perform this same rewriting via an iobjFor(GiveTo) check() method in
Actor, but this approach didn't work in many cases due to interference
from several other checks made earlier in the execution process. The
old check() scheme has been deleted in favor of the new mechanism.
For full details, see the new chapter on Global Command Remapping
in the Technical Manual.
A new set of Action methods makes it possible to temporarily override
the meaning of a pronoun, for as long as the action is in effect.
Action.setPronounOverride() creates an override, which associates an
object with a particular pronoun type (PronounMe, PronounYou, etc).
This new meaning overrides the usual meaning, but only for the
duration of the action. Action.getPronounOverride() looks for an
override defined with setPronounOverride() for this action.
This new mechanism is designed mainly to support GlobalRemapping.
In particular, a remapping that changes the target actor usually needs
to override the meaning of "you" in the rewritten action, so that
"you", "your", and "yours" are interpreted as referring to the
original target actor rather than the target actor of the
rewritten format.
For details, see the new chapter "Global Command Remapping" in
the Technical Manual.
The classes BaseSurfaceContentsLister, BaseRearContentsLister, and
BaseUndersideContentsLister are now all based on a new class,
BaseContentsLister. This new class unifies the code that was formerly
defined separately in the three subclasses -
BaseSurfaceContentsLister, BaseRearContentsLister, and
BaseUndersideContentsLister are now trivial subclasses with no
overrides of their own. (They're still present as separate classes,
(1) for the sake of compatibility with existing code, and (2) because
the distinct classes could still be useful for other types of
container-type-specific overrides beyond what the library defines.)
The three subclasses exist to provide customized wording in the
contents listings generated for the corresponding specialized
container types. However, the variation in the wording provided by
the library definitions was always very simple - the only thing that
varies is the preposition used to describe the relationship between
the container and its contents ("on" for a Surface, "under" for an
Underside object, "behind" for a RearContainer or RearSurface). As it
turns out, the needed preposition is available as a property of the
container object, namely objInPrep. This makes it possible to unify
the code for the three classes by referring to this property rather
than hard-coding the preposition - by doing this, the code for the
three subclasses becomes identical, which allows it to be consolidated
in a common base class.
The advantage of this change is that it's no longer necessary to
change a specialized container's contents-lister class simply because
you want to change the preposition used to describe the relationship
between contents and container. In the past, you had to change
both objInPrep and the contents lister. Now, changing
objInPrep is enough - since the default contents listers refer to
objInPrep, changes to objInPrep will automatically flow through to the
wording in contents listings.
Because the class structure is the same as before, existing code
should work unchanged, and you can still use contents lister
customizations to achieve effects beyond the simple wording changes
that objInPrep provides, if needed.
The new class ActorHelloTopic makes it easier to
create a greeting message for cases where the NPC initiates a
conversation through a script. Place an ActorHelloTopic object
anywhere a HelloTopic would go to create a greeting specifically
for the case of an NPC-initiated conversation.
[Update for 3.0.18: disregard the following. These changes
were not effected as described, and weren't quite what was intended.
See this entry for an update.]
ByeTopic objects can now be used with actors who use conversational
states (ConversationReadyState, InConversationState). In the past,
the conversational states largely bypassed the topic-based Goodbye
processing, so goodbyes had to be written with the specialized goodbye
methods on the state objects. This made the conversation states
harder to use, since it created special cases you had to be aware of.
Now, you can set up a goodbye message by locating a ByeTopic within
the InConversationState or within any ConvNode.
The class ComplexComponent now defines stagingLocations as the lexical
parent's (which is normally the ComplexContainer's) location. This
makes it easier to embed a nested room (a platform, booth, etc) as a
component of a complex container object, by allowing inbound travel
into the nested room to bypass the complex container and move directly
between the enclosing location and the component/nested room. As with
other aspects of complex containers, the container and component are
meant to look and act like a single object, from the player's
perspective, so it's not desirable to treat the container and
component as separate objects for the purposes of nested room travel.
Along the same lines, ComplexContainer now tries to remap the
actions StandOn, SitOn, LieOn, Board, and Enter to a suitable
component. These actions are now automatically remapped to the object
returned from the new method getNestedRoomDest(), if any. If that
method returns nil, the actions are not remapped after all.
The new getNestedRoomDest() method of ComplexContainer looks for a
suitable component to use as the destination of a nested room travel
command. By default, it first looks to see if the sub-surface
component is a NestedRoom object, and if so, returns that; otherwise,
it checks the sub-container component, and returns that if that's a
NestedRoom; otherwise it returns nil. This default behavior makes
everything automatic for cases where you have exactly one component
that's a nested room. If you want to define more than one nested room
within a complex container (for example, a large crate that you can
STAND ON and also GET IN), you will need to override
getNestedRoomDest() to return the appropriate destination by verb, or
you can simply override the dobjFor(action) remapTo() definition to
point remap to the correct component.
A bug in the parser sometimes caused "truncated plurals" to be
selected ahead of exact matches in a player's answer to a
disambiguation question. By design, the parser prefers an exact match
to a singular noun ahead of a partial match to a plural (for example,
with the default six-letter truncation, the word "object" in command
input would match either the singular "object" or the plural
"objects", but the parser assume the singular word was intended
because it's an exact match for the input). However, this principle
was being ignored in this case due to the bug. This has now been
corrected.
(
bugdb.tads.org #0000006)
In the past, if a ConvNode was activated via the <.convnode>
tag, and the ConvNode's noteActive() method displayed any text, the
text displayed within noteActive() appeared out of order in the final
output relative to the text containing the <.convnode> tag.
This happened because <.convnode> is processed within an output
filter, so the entire string containing the <.convnode> had to
be processed by the filter before any of it was actually displayed; if
any text was displayed in the course of recursively called routines
such as noteActive(), that text would as a result show up first in the
final output.
The <.convnode> output filter now captures any text that's
displayed in the course of activating a new ConvNode, and re-inserts
the text into the stream at the proper point. This ensures that the
text appears in the correct sequence, so it's now safe to display text
from within a noteActive() routine.
(bugdb.tads.org #0000003)
In the past, TIAction verbs (those taking a direct object and an
indirect object, such as PUT IN or UNLOCK WITH) had the side effect of
clearing the parser's memory of gender-specific pronoun antecedents.
For example, after typing "LOOK AT BOB; UNLOCK THE DOOR WITH HIS KEY",
the parser would forget that "him" refers to Bob, even though there
wasn't anyone else mentioned in the second command to supersede the
meaning of "him". This has been fixed.
(
bugdb.tads.org #0000020)
The Room class now applies a lower level of disambiguation
"likelihood" for the Examine action to a remote room than for a room
containing the actor. In the past, the class applied a
lower-than-default likelihood for a room containing the actor, on the
assumption that a player who types EXAMINE FOO probably means to
examine a nearby object rather than the enclosing room, when both are
called FOO. However, there's a third possibility, which is that
there's a remote room (i.e., a separate top-level room that's
connected to the current location by a sense connection) in scope
that's also called FOO. In these cases, we'd still want to
select the nearby object over either room; but in cases where we only
have the two rooms to decide between, it seems most likely that the
player would want to refer to the enclosing room rather than the
remote one. This new distinction in the likelihood levels of
enclosing and remote rooms accomplishes this: the most likely
selection is still a nearby non-room object, the next most likely is
the enclosing room, and the least likely is a remote room.
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:
- If the actor is already directly in the target room (for
example, the player types ENTER HALLWAY while already in the hall),
the behavior is as before ("You're already in the hallway").
- If the actor is indirectly in the target room (for example,
the PC is sitting in a chair in the hall, and types ENTER HALLWAY),
the command is replaced with GET OUT OF outermost nested room
containing actor within target room.
- If the actor is in a separate top-level room, and there's
a travel connector from the actor's current location to the target
room, the command is replaced with TRAVEL VIA connector.
- Otherwise, the command fails with the message &whereToGoMsg
("You'll have to say which way to go").
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.
Two changes to BasicChair simplify the way the "get in" commands for
the object and its subclasses are implemented.
First, the tryMovingIntoNested() routines in BasicChair and
its subclasses have been unified into a single base-class method. In
the past, each BasicChair subclass overrode tryMovingIntoNested() to
perform the implied action matching the subclass type - SitOn for
BasicChair, LieOn for BasicBed, StandOn for BasicPlatform.
Now, the overrides have been removed, and the functionality has
been been unified into a single method on BasicChair that the
subclasses all inherit. The BasicChair version of the method handles
all of the subclasses by deferring to the default posture object (as
returned from the defaultPosture property).
Second, the dobjFor(Board) definitions have been unified
in a single BasicChair definition, rather than requiring overrides
in each subclass as in the past. As with tryMovingIntoNested(),
the dobjFor(Board) definition in BasicChair now calls upon the
default posture object to carry out the appropriate command. This
removes the need to override dobjFor(Board) in each subclass.
The benefit of these changes is that there's less to override when
creating a custom type of BasicChair subclass. Setting the
defaultPosture also automatically takes care of setting the implied
action for "get in" and the mapped action for "board", since
tryMovingIntoNested() and dobjFor(Board) now choose the sub-action
that matches the default posture. Of course, if for some reason you
want these actions to differ from what the default posture selects,
you can still override tryMovingIntoNested() and/or dobjFor(Board) to
achieve whatever special effects you want. For typical objects where
the two match, though, these changes means less work is needed to keep
everything in sync.
The "standing" posture object's setActorToPosture() method has been
changed slightly. In the past, the routine performed an intransitive
Stand action, which causes the actor to stand up in whatever location
it's currently occupying. This was at odds with the specification of
the method: the routine should not only make the actor stand up, but
make the actor stand in a given location; the old version ignored the
target location. The routine now does use the target location, and
executes a transitive StandOn action with the target location as
direct object.
The old implementation was the way it was because the transitive
StandOn action didn't always work properly when applied to top-level
rooms. However, the handling of StandOn for top-level rooms has since
been improved, so this is now a viable implementation. This makes the
method work consistently with its specification and with the
corresponding methods in the other pre-defined posture objects.
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.
Released 11/2/2007
There are no library changes in this release.
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.)
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.
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.
Released 2/9/2007
There are no library changes in TADS 3.0.14.
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:
- The methods getRoomPartLocation(), getLocTraveler(),
atmosphereList(), roomDaemon(), getDropDestination(), and
effectiveFollowLocation() have been moved to Thing, with essentially
no changes. These are the methods the library calls on containers of
nested rooms, so moving them to Thing allows any game object to
contain a nested room.
- The definitions of most of the above methods have been removed
from BasicLocation, since BasicLocation now simply inherits the Thing
definitions. The definitions of roomDaemon() and atmosphereList()
have been moved to Room instead, since those definitions were intended
for top-level rooms.
- Room now defines checkActorOutOfNested() to find and defer to
the child object containing the actor. This ensures that the check
is forwarded to the nested room containing the actor.
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.)
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.
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.
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:
- If the PC is moving closer to the PC, the local
arrival message is displayed at the destination end of the travel.
"Closer" means that the NPC is moving from a top-level location that
doesn't contain the PC to a top-level location that does
contain the PC.
- If the PC is moving further away from the PC, the local
departure message is displayed at the origin end of the travel.
"Further away" means that the NPC is moving from the top-level
location that contains the PC to a different top-level location that
doesn't.
- If the PC is moving laterally, the remote travel
message is displayed at the destination end of the travel.
"Laterally" means that the NPC is moving between two top-level
locations, neither of which contain the PC. Note that there's no good
basis for preferring to show the message on the origin or destination
side of the travel in this case, since the generic situation is so
symmetrical, but at the same time it's necessary to choose one or the
other because we don't want two messages for this kind of travel. So,
we have to make an arbitrary choice, and the library chooses to show
the message on the destination side.
These new rules obviously require some new methods. Here's the
new arrangement of methods:
- The local arrival message is embodied in
TravelConnector.describeLocalArrival(), which by default calls
Traveler.sayArrivingLocally(), which by default calls
libMessages.sayArrivingLocally(). Note that these aren't new - this
part of the mechanism carries forward unchanged from past versions.
However, their usage has changed, in that the library in the
past called the local arrival messages in all cases of local
travel, but now calls it only in certain cases, as described
above, and calls the local-departure or remote-travel methods in other
cases that formerly all folded into the local-arrival case.
- The local departure message is embodied in
TravelConnector.describeLocalDeparture(), which by default calls
Traveler.sayDepartingLocally(), which by default calls
libMessages.sayDepartingLocally(). These methods are new.
- The remote travel message is embodied in
TravelConnector.describeRemoteTravel(), which by default calls
Traveler.sayTravelingRemotely(), which by default calls
libMessages.sayTravelingRemotely(). These methods are new.
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, ’
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 command phrasing CLIMB IN/INTO/IN
TO as a Board action, and CLIMB ON/ONTO/ON TO as a StandOn action. In
the past, both forms of CLIMB were defined for both of these actions,
which made the choice of which action matched the syntax arbitrary and
unpredictable. This change ensures that each of these phrasings is
unambiguously assigned to only one action.
The English library now tries a little harder to figure out whether a
command of the form ENTER text means ENTER text ON
some implied keypad-type object, or GO IN object. In
the past, the library always used the GO IN interpretation. Now, the
library assumes the ENTER ON keypad interpretation if there's a
suitable default object present; if there isn't, the library reverts
to the GO IN interpretation.
The English library formerly allowed any object to match the pronoun
"them"; it now limits "them" matches to (a) objects used in lists in
player input, as in TAKE IRON KEY AND BRASS KEY, and (b) objects
marked as having plural usage via isPlural.
The parser now lowers the grammar match ranking for any phrasing
involving a plural in a noun slot requiring a single object. This
makes the parser a bit smarter about picking the right vocabulary
interpretation in cases of nouns that have both singular and plural
usage, such as FISH and SHEEP.
In the English library, the isHim and isHer properties are now taken
into account for arbitrary Thing objects in parameter substitutions in
messages. In the past, gender was only taken into account for Actor
objects; non-Actor objects were always "it" or "them" in messages.
In the English library, the Actor methods theName, theNameObj,
theNamePossAdj, theNamePossNoun, aName, and aNameObj now properly
handle the first-person plural case ("we", "us", "our", "ours", etc).
In the past, a plural Actor with first-person referralPerson
incorrectly used the singular first-person pronouns.
The PourOnto verb in the English library now correctly uses
onSingleNoun as its indirect object response phrase, matching the
regular grammar for the verb. (In the past, it incorrectly used
withSingleNoun as the response phrase.)
The English library now tries harder to avoid duplicating vocabulary
in the lists it constructs from vocabWords properties during
initialization. First, VocabObject.inheritVocab() now avoids
redundantly scanning vocabulary inherited from superclasses. Since
this routine's whole point is to explicitly scan the class hierarchy
for each object and add the vocabulary inherited from each superclass,
it needs to bypass the normal automatic inheritance mechanism, which
it now does. Second, initializeVocabWith() now explicitly removes
duplicates from each word list it updates, to avoid duplication in
cases where an object explicitly defines the same vocabulary word at
multiple inheritance levels.
The new function isListSubset() determines if one list is a subset of
another list: that is, if every element of the first list also appears
somewhere in the second list. (This function was formerly defined as
a method in the Lister class, but it's generically useful and wasn't
tied in any way to Lister, so it's been broken out as a function to
make it more readily accessible to other code.)
The library functions main() and mainRestore() have been enhanced to
catch QuittingException signals. These functions, of course, are the
main entrypoints for the entire program - the former is for normal
start-up, and the latter is for the special kind of start-up that
restores a saved state directly from the operating system shell, such
as when the player launches the game by double-clicking a saved-state
file on the Windows desktop. Catching QuittingException in these
functions allows game code to throw QuittingException just about
anywhere, without worrying about the context. In the past, this
exception was only usable after the main command loop started; in
particular, it wasn't usable from startup or initialization code (such
as the code that displays the game's introductory messages), because
there was no handler to catch the exception in effect while that code
was running.
Released 8/29/2005
Minor compatibility-breaking change: In the English library,
the ThingState template now uses "+" rather than "@" to introduce the
listingOrder property value. The choice of punctuation here is pretty
arbitrary; the reason for the change is that a consensus emerged among
users that "+" would be easier to remember in this template because
the other library templates generally use "+" to mark integer slots.
If you have existing game code that defines any ThingState objects
using the old template, you'll need to change the definitions to use
"+" instead of "@". Alternatively, you could put your own definition
of the old ThingState template into a common header file that your
game use, in which case you wouldn't have to change any of your
existing object definitions; but you're probably better off just
changing your code to use the new convention, since defining your own
template could cause some slight confusion for other users if you
should share your code in the future.
Slight risk of incompatibility: Thing.getDropDestination()
must now accept a nil value for the 'obj' argument. A nil value of
'obj' asks for the drop destination for a "typical" object, rather
than for any specific object. In the past, it wasn't specified that
'obj' could be nil, and the library never directly called the method
with a nil 'obj' value, so it's possible that some existing game code
might assume that 'obj' is non-nil. You should do a quick scan of
your game code for getDropDestination() definitions to ensure that
they'll work properly when 'obj' is nil. In particular, make sure
that you don't evaluate any properties of 'obj' without first checking
that 'obj' is non-nil:
getDropDestination(obj, path)
{
if (obj.isReallySmall) ...; // Don't do this!!!
if (obj != nil && obj.isReallySmall) ...; // Do this instead
}
Slight risk of incompatibility: The default value of
Event.eventOrder is now 100 (it was formerly 50). This default is
essentially arbitrary (since it's only important for relative
ordering), but a value of 100 is more consistent with other similar
ordering defaults elsewhere in the library.
In addition, the eventOrder of the standard sense-change daemon is
now 500, to ensure that the sense-change daemon runs after most other
events. The library automatically creates the sense-change daemon at
game start-up; this is the daemon that displays messages about new,
ongoing, or ending sounds and odors. In the past, this daemon had
default priority, so its order relative to other fuses and daemons was
arbitrary. If another fuse or daemon ran after the sense-change
daemon, and made some change that affected the sensory environment, a
message about the change wasn't displayed until the following turn,
since the chance had already passed to generate the message on the
turn during which the change actually occurred. This change ensures
that the sense-change daemon runs after most other events, so any
changes that occur in daemons and fuses will be reflected in the
sense-change update for the same turn.
If you've explicitly set eventOrder for any fuses or daemons in
your game, you might need to adjust the values you set. In
particular, if you set an eventOrder value between 50 and 100 for an
event object to ensure that the event is processed after events that
inherit the default eventOrder, you'll need to raise that value above
100. You should also consider if it's desirable for those events to
run after the sense-change daemon, as they would have in the past
(since the sense-change daemon used to have default priority), and if
so, raise their eventOrder values to above 500. Finally, if you have
an existing eventOrder that's over 500, you might want to consider
whether you still want the event to run after the sense-change daemon;
if not, you should lower the value to under 500.
Possibly incompatible change: The parameter lists of some of
the internal library routines that handle point-of-view have changed.
These aren't likely to affect any existing game code, since these are
mostly used only inside the library itself, but you should check your
code for any mentions of these names to be sure. You'll need to
adjust any code in your game that either calls or overrides any of
these methods or functions:
- Thing.fromPOV() has a new parameter giving the actor who's doing
the looking.
- Function pushPOV() takes a new parameter giving the actor.
- Function callFromPOV() takes a new parameter giving the actor.
- Function setPOV() takes a new parameter giving the actor.
- Function setRootPOV() takes a new parameter giving the actor.
Very slight risk of incompatibility: In the following methods,
the parameter now gives the point-of-view
actor instead of the
point-of-view object. The POV actor and object are usually the same
anyway, so the effects on existing code should be minimal. If you
actually need the POV object, you can get it with the getPOV()
function.
- Thing.showRemoteSpecialDesc
- Thing.remoteInitSpecialDesc
- Thing.remoteSpecialDesc
- Thing.showSpecialDescInContents
- Thing.showObscuredSpecialDescInContents
- Thing.showDistantSpecialDescInContents
- Thing.showRemoteSpecialDescInContents
- Actor.listActorPosture
The new function getPOVActor() returns the "point-of-view actor."
This is the actor who's doing the looking that is, the actor who's
performing the action (LOOK AROUND, EXAMINE, SMELL, etc) that's
generating the current description. This is
usually, but not
always, the same as gActor, and it
usually, but not always,
returns the same object as getPOV().
The point-of-view actor can differ from gActor in cases where game
code explicitly generates a room description from the point of view of
one actor in the course of processing a command given to a different
actor. In these cases, gActor will be the actor who's performing the
command, and getPOVActor() will return the actor who's generating
the description.
The point-of-view actor can differ from the point-of-view object
(the object returned by the getPOV() function) when the actor is
observing the location being described through an intermediary object.
For example, if we're generating a description of a remote room
because an actor is examining a closed-circuit TV monitor showing the
remote location, the point-of-view actor will be the actor who's
looking at the monitor, while the point-of-view object will be the
camera in the remote location. Note that the POV is the camera, not
the monitor: the POV is intended to be the object that's physically
absorbing the light rays, or other sensory equivalents, from the
surroundings being described.
The library now selects remote descriptions for rooms and other
objects a little differently than before. In the past, the remoteness
of the point-of-view object was the deciding factor - that is, remote
descriptions were used when the POV wasn't in the same room as the
object being described. Now, the library bases the decision on the
point-of-view actor, not the POV object: if the actor who's doing the
looking isn't in the same room, the remote description is used. This
means, for example, that the remote description will be generated when
observing a room through a closed-circuit TV system: even though the
POV (the camera) is in inside the room being described, the actor
who's observing the TV monitor is in a different room, so the remote
description is used.
This change is implemented in Thing.showSpecialDescWithInfo()
and Thing.showSpecialDescInContentsWithInfo().
The method Thing.lookAroundWithinContents(), which is the part of the
room describer that lists the room's contents, had a bug in past
versions that caused a single object to be listed twice in some
unusual cases. In particular, if the room itself had brightness 1,
and it contained an object that also had brightness 1, the object was
listed twice: once in a list starting "In the dark you see," then
again in a list starting "The room contains." The same problem
occurred in a dark room if an object with brightness 1 was inside a
container with brightness 1. This has been fixed.
A new mechanism in Actor allows objects to be explicitly excluded from
the "look around" description of a room. To exclude an object, call
the method excludeFromLookAround(obj) on the actor, passing in the
object 'obj' to be excluded. The object will be suppressed from room
descriptions until further notice - it won't show up in any of the
visible-object lists, including special descriptions, room contents
lists, or nested contents lists. To remove an object from the
exclusion list, call unexcludeFromLookAround(obj).
The exclusion that this method applies is over and above any other
listing-selection mechanisms, such as obj.isListed. It's intended
mostly for ephemeral exclusions that are applied other than by the
object itself, since the object itself can usually more easily control
its own listability via isListed and the like.
In Thing.adjustLookAroundTable(), the point-of-view actor is no longer
removed from the table; instead, just the point-of-view object is
removed. In cases where the POV and actor differ and the actor is in
sight of the POV, we usually want to include the actor in the
generated description.
In addition, the method now calls adjustLookAroundTable() on the
actor and POV objects, giving them a chance to make further adjustments
to the table.
Suppose we have an NPC who's in scope but out of hearing range, such
as when the NPC is on the other side of a transparent, soundproof
window from the player character. If the player addresses a command
to the NPC, the library now uses the same error message that it uses
for ASK ABOUT and other conversation commands in the same situation.
(This corrects a bug that used to occur. In the past, the library
attempted to let the NPC respond, rather than generating a parser
error. The problem with this approach was that the soundproof barrier
stopped the PC from hearing the NPC's response, thus the library could
only report that "Nothing obvious happens." Now that this is handled
as a parsing error instead of a conversational response, the more
helpful "Bob does not appear to hear you" is displayed.)
Thing.isListedInRoomPart has changed slightly. In the past, an object
was listed (i.e., shown in a miscellaneous list of portable contents)
as part of an EXAMINE FLOOR (or EAST WALL or the like) if the object
was nominally part of the room part (that is, nominally on the floor,
or on the given wall, etc)
and the object was normally listed
the same way in a LOOK AROUND command. Now, the object is listed if
it's nominally part of the room part
and the new RoomPart
method isObjListedInRoomPart() returns true.
The new method RoomPart.isObjListedInRoomPart returns true by
default if the object does not have a special description for
the purposes of examining the room part - that is, if the object's
useSpecialDescInRoomPart returns nil. We don't want to include the
object in the miscellaneous list if it has a special description
because the special description will be automatically shown - it would
thus be mentioned twice if we included it in the miscellaneous list as
well.
Floor overrides isObjListedInRoomPart to list an object only if
it's listed normally - that is, the object's isListed returns true.
This is the old behavior that applied to all room parts, but
now it only applies to Floor objects. The reason we treat Floor
differently is that everything that's directly in a room is by default
nominally on the floor in the room, so if we didn't limit the listings
like this in the case of EXAMINE FLOOR, we'd include all of the
internal fixtures and components of the room, which is generally
undesirable.
The main purpose of this change is that it makes it a little easier
to set up Fixture-type objects that are described as being situated on
walls, ceilings, and the like. Now, all that's necessary is to give
the object a specialNominalRoomPartLocation - even if the object isn't
normally listed in a room description, as is the case for a Fixture,
we'll now list the object when we examine its room part as long as it
doesn't have an explicit special description for the room part.
The new mix-in class RoomPartItem can be used to create an object that
shows its special description when an associated room part is
described. This is useful for things like doors, windows, ceiling
fans, and other objects attached to the room. The main thing this
class does is to make the object show its special description when its
associated room part is examined (as in LOOK AT EAST WALL), but
not in the main room description (as in LOOK AROUND). This is
desirable because this kind of object is usually not remarkable enough
to call out with a special description in the overall room, but does
warrant a mention when its associated room part is explicitly
examined.
SensoryEmanation has a new property, isEmanating, that lets you turn
the object on and off. By default, is Emanating is true; if you set
this to nil, it shuts off the emanation. This makes it easier to make
sounds and odors start and stop, which is often useful when the
emanations are associated with dynamic objects such as actors.
Settable has a new method, canonicalizeSetting(), that converts a
proposed new setting to a "canonical" format. Settable now uses this
to process the player's input before passing the value to
isValidSetting() or makeSetting(), ensuring that these routines don't
need to perform any further format conversion of their own.
This method makes it much easier to handle superficial formatting
variations in the player's input by centralizing the conversions to
this single routine. In the past, both isValidSetting() and
makeSetting() had to handle formatting variations since they received
the raw player input.
Settable's default implementation of canonicalizeSetting() doesn't
do anything - it just returns the original player input unchanged.
NumberedDial overrides the method to convert spelled-out numbers to
numeral format and to strip off any leading zeroes from an entered
numeral format; LabeledDial overrides it to convert the input to the
exact case of the matching entry in the list of valid labels.
The new library routines parseInt() and parseIntTokens() parse a
string or token list (respectively) that contains a spelled-out or
digit-string numeric value. These routines parse the input and return
the corresponding integer value, or nil if the input can't be parsed
as a number. For example, parseInt('one thousand and twenty-four')
returns 1024, parseInt('72') returns 72, and parseInt('dusty tome')
returns nil.
The English library now incorporates Michel Nizette's Past Tense
extension. This enables the author to select the tense - present or
past - used in library messages, and even allows you to change the
tense dynamically during the game.
The 'master switch" for the active tense is gameMain.usePastTense.
Set this to true to activate past tense in library messages. The
default is nil, which selects present tense.
A number of new message parameters and verbEndingXxx properties
help in writing messages that can change tense. The standard library
messages defined in en_us/msg_neu.t use these new features to adapt
automatically to the current tense.
See the comments in en_us/en_us.t for full details.
The library has a new framework for "global defaults," which lets the
user save certain preference settings as default values for future
sessions. These saved defaults are global: they apply to
any
new game subsequently loaded, provided that the new game supports the
new global defaults system. Games compiled with library versions
prior to 3.0.9 didn't support this new system, but games compiled with
3.0.9 or later will support it by default, as long as an author
doesn't explicitly disable it for some reason. The settings are saved
to a file whose name and location are determined by the interpreter
(and which might vary by system, to conform to local conventions).
Two new commands let the user control the global defaults: SAVE
DEFAULTS saves the current settings to the configuration file, and
RESTORE DEFAULTS explicitly loads the file and applies its settings.
These commands both list the full set of saved settings, so that the
user can easily tell exactly which settings are affected.
By default, the library uses the new global settings framework
for the VERBOSE, FOOTNOTES, NOTIFY, and EXITS settings.
The new mechanism is implemented mostly in the new file settings.t.
The main API is exposed through the new settingsManager object, and
the command-line user interface is implemented in the new settingsUI
object. The new SettingsItem class is used to encapsulate individual
configuration variables. If you want to create your own extensions to
the set of global default variables, simply create a SettingsItem
object for each new variable you want to add. The settings framework
will automatically find your new SettingsItem objects, and save and
restore them as needed.
The new macro gSetSeen(obj) marks an object as having been seen by the
player character.
The new macro gTopicText returns a string containing the literal text
of the current topic phrase, when the current action contains a topic
phrase. The text returned is as the user typed it, but converted to
lower case.
The LiteralAction and LiteralTAction class are now based on a new
common mix-in base class, LiteralActionBase. This is parallel to the
structure of TopicAction and TopicTAction, with their common mix-in
base class TopicActionBase. This new arrangement shouldn't at all
change the behavior of LiteralAction or LiteralTAction, and should be
entirely transparent to existing code.
The change has two main benefits. First, it eliminates some code
duplication, by replacing a few bits of code that were effectively
duplicated in LiteralAction and LiteralTAction with common versions
that both classes now inherit from LiteralActionBase. Second, game
code can now easily determine if an action involves any sort of
literal phrase, by testing gAction.ofKind(LiteralActionBase).
The new Thing method failCheck() makes it a little more convenient to
write check() routines. check() routines frequently use this pattern:
check()
{
if (condition)
{
reportFailure('You can\'t do that for some reason.');
exit;
}
}
The failCheck() routine encapsulates the display of the failure
report and the termination of the action with 'exit'. Using
failCheck(), you can rewrite the code above more concisely,
like so:
check()
{
if (condition)
failCheck('You can\'t do that for some reason.');
}
Occluder.occludeObj() has a new default implementation. By default,
the method now asks the object being tested for occlusion to make the
decision, by calling the object's isOccludedBy() method.
Thing provides a default implementation of the new isOccludedBy()
method, which simply returns nil to indicate that the object is not
occluded by the given Occluder.
Existing code won't be affected by this change, since any existing
Occluder code already overrides isOccludedBy() to make the per-object
occlusion decision. The reason for this change is that it's sometimes
easier to decide about occlusion in the individual objects rather than
in the Occluder; the new default Occluder.occludeObj() makes it easer
to write the code that way by providing a pre-defined framework that
already works that way.
In the past, if a Container was used as the indirect object of PUT IN
or POUR INTO, the library applied an objOpen precondition to ensure
the container was open. The library now applies the objOpen condition
only if the actor performing the action isn't itself inside the
container. The reason for the objOpen condition is that putting or
pouring things into a container requires access to the container's
interior, and a container's interior is only reachable from outside
the container when the container is open. However, when an actor is
inside a container, the actor can reach the inside of the container
whether or not the container is open, so there's no need for the
objOpen condition.
Fixture, Component, and Immovable now define custom handlers for PUT
BEHIND that are consistent with their handlers for PUT IN, PUT ON, and
PUT UNDER.
On an actor's attempt to take an item that was inside something the
actor was already carrying, Actor.tryMakingRoomToHold() formerly
double-counted the item's weight in enforcing the actor's carrying
limit - it counted the item first for the weight of carrying the new
item itself, then again for its contribution to the weight of its old
container (because the old container was in the actor's inventory).
The routine now correctly counts the weight only once. (The new
algorithm uses the whatIfHeldBy() method, which actually tests the
proposed new arrangement to calculate the new weight carried and thus
yields reliable results no matter what configuration previously
existed.)
TAKE ALL now tries to take everything that's directly in the actor's
current "drop destination", or that's directly in the actor's
direct container, or that's in any intermediate container
between the two. The former behavior was to take everything in the
actor's direct location only. This change improves the behavior when
the actor is in something like a chair or bed. In these sorts of
nested rooms, items dropped usually land in the enclosing room; the
new behavior ensures that a TAKE ALL command attempted while in such a
location will include items that were just dropped while in the same
location. With the former behavior in this situation, TAKE ALL missed
the items in the drop location, which was likely to confuse players.
NonPortable now defines a default weight and bulk of zero. Since
these objects aren't meant to be carried or moved around by actors,
their weight and bulk are essentially irrelevant; but the previous
non-zero defaults occasionally had undesirable side effects in
cases where a non-portable was a component of another object or
among another object's contents.
An attempt to give an object to an NPC when the NPC is already holding
the object in question is now ruled out as illogical ("Bob already has
that").
The library now provides commands that let the player control the
display of the exits listing in the status line. EXITS ON now turns
on
both the status line and room description exit listings, and
EXITS OFF turns them both off. The new command EXITS STATUS (which
can also be entered as EXITS STATUSLINE or EXITS STATUS LINE) turns on
the status line exit listing only, and EXITS LOOK turns on the room
description exit listing only.
In addition, these new commands are now described in the standard
instructions.
The "probabilistic firing" feature of the RandomEventList class is now
available in a separate mix-in class, RandomFiringScript. This new
class can be mixed into the superclass list of any Script subclass to
add the random firing feature.
RandomEventList and ShuffledEventList are now based on the new
RandomFiringScript. This doesn't change their behavior; existing
code based on these classes should continue to work unchanged.
The SyncEventList class now delegates calls to its scriptDone() method
to its master list object. This ensures that any explicit calls to a
SyncEventList's scriptDone() method will be handled via the master
list, which is consistent with SyncEventList's handling for most of
its other methods.
The library now has a better response to the FOLLOW command if the
target NPC departed the player character's present location while the
PC was somewhere else. Suppose the PC sees Bob in the living room,
then goes to the dining room; while the PC is in the dining room, Bob
departs for a third room (due to some scripted activity). The player
then returns to the living room. At this point, it's not possible to
FOLLOW BOB, since the PC didn't see where Bob went. This much hasn't
changed; what's changed is the library's default response if player
types FOLLOW BOB in this situation. In the past, the response was
"The last place him was the living room," which is true but not very
helpful (and a little non sequiturish). Now, the response is "You're
not sure where he went from here."
NominalPlatform now returns its location's effectiveFollowLocation as
its own effectiveFollowLocation. A nominal platform isn't meant to be
part of the visible structure of the game world, so, for FOLLOW
purposes, a character on a nominal platform is effectively in the
enclosing room as far as the other characters in the room are
concerned.
A bug in the parser caused possessive pronouns to be treated
differently in a TopicTAction when the direct object phrase was
missing than when it was provided. For example, ASK BOB ABOUT HIS KEY
was treated differently from the abbreviated equivalent A HIS KEY.
This has been corrected.
The parser now drops its preference for untruncated name matches when
resolving an object in "global scope," and instead treats truncated
and exact name matches on an equal footing in these cases. This
change is analogous to the global scope changes for plurals and
adjective-ending phrases introduced in 3.0.6q and 3.0.6p. See the
adjective-ending notes from 3.0.6p
for details on what global scope is and why its rules on matching are
more lenient.
The practical effect of this change is that topic words will now
match properly even when there are longer and shorter versions of the
same vocabulary defined for various objects. In the past, if one
object was defined with a six-letter noun, and another object had a
seven-letter noun with the same first six letters as the other object
("coffee" and "coffees", say), the parser filtered out the
seven-letter match in favor of the six-letter match, even though both
objects ought to be equally valid in global scope. With the change,
both objects are now properly kept as possible resolutions when
parsing a topic phrase.
The parser now selects Collective objects instead of their
corresponding individuals when matching "all" and "all except
list" noun phrases.
Whether it's better to select the Collective or the individuals
probably varies according to context, since the "right" one is the one
the player intends in a particular command, and that will likely vary
from one command to the next (and from one player to the next). But
the design of the parser requires that we choose one interpretation or
the other across the board; context sensitivity isn't an option. Even
if context sensitivity were an option, we might still not have enough
information to correctly read the player's mind every time. This
change - matching the Collective object instead of the individuals -
seems more likely to yield intuitive results more of the time than the
old behavior.
The parser now applies the normal filterResolveList() filtering when
resolving a target-actor phrase (such as the "bob" in "bob, go
north"), the same as it does when resolving other kinds of noun
phrases. In the past, the parser omitted this filtering step for
target actors, which didn't give objects like Unthings and Collectives
a chance to do their special filtering.
When the player enters a command of the form TAKE ALL FROM
iobj, the TakeFrom action now lets the indirect object
determine what ALL means. It does this via a new method,
getAllForTakeFrom(scopeList). Thing implements this method using
almost the same behavior that TakeFrom used in the past: it returns a
list of everything in scope that's directly inside 'self' and isn't a
component of 'self'. The old behavior simply returned everything in
scope that was directly inside the indirect object.
ComplexContainer overrides getAllForTakeFrom() to return everything
that's in scope and that's directly in (a) the sub-container or
sub-surface, if the object has either, or (b) the sub-rear-surface or
sub-underside, if the object doesn't have a sub-container or
sub-surface; and as with Thing's version, this list is then limited to
objects that aren't components of their direct containers. Note that
if there's both a sub-surface and a sub-container, the method
will return everything that's in either of these; and likewise
with the sub-rear-surface and sub-underside.
This change is designed primarily to improve the behavior for
ComplexContainers, which wasn't ideal under the old mechanism, as it
only returned the complex container's sub-components, which are
internal implementation objects that are meant to be invisible to the
player. Clearly, in the case a complex container, it's the contents
of the inner containers that are of interest in TAKE ALL FROM. This
change also makes it easier to customize the TAKE ALL FROM behavior
for your own special containers, since it gives the container a chance
to determine what ALL means in context.
The grammar class VagueContainerNounPhraseProd, which matches
locational phrases such as "all in box" and "the one in the box," also
uses the new method to determine which objects are involved. This
provides the same improvement for commands such as LOOK AT EVERYTHING
IN THE BOX when a complex container or other special container is
involved.
The NounPhraseWithVocab method getVocabMatchList() now takes an extra
parameter, specifying any extra flags that should be OR'd into the
ResolveInfo flags for the items in the match list. In the past, the
version of the method in the adjective-phrase subclasses took the
extra flags parameter, but the base class method didn't, which made it
difficult to create classes for production types that could define
grammars with either the adjective or noun subproductions.
In the past, if the player typed STAND while the PC was sitting or
lying on an object derived from BasicPlatform, the library simply made
the PC stand up, remaining on the platform. In particular, the
library didn't bother checking the allowedPostures property to see if
the platform actually allowed the 'standing' posture; it simply
assumed that standing was allowed on any platform. This didn't easily
allow for things like booths with limited headroom. The
makeStandingUp method of BasicPlatform now checks allowedPostures
property, to make sure that 'standing' is listed. If 'standing' isn't
among the allowed postures, the makeStandingUp method now inherits the
base class handling, which removes the actor from the nested room and
sets the actor's posture to the default for the enclosing location.
This will produce better results in most cases, since the natural
implication of STAND UP when in a vertically confined space would be
to remove oneself from the space.
It's now possible to control the order in which preconditions are
executed, at the level of the individual preconditions. The new
PreCondition property preCondOrder gives the order of execution of a
given precondition. The default for most types of preconditions is
100; for TouchObjCondition (and thus for touchObj), it's 200, which
means that touchObj conditions will execute after other conditions by
default.
It's occasionally useful to be able to fine-tune precondition order
because preconditions can sometimes interfere with one another. This
is particularly a problem with implied actions that require the actor
to be in a particular posture or location, because many of the
standard preconditions can move the actor around as a side effect.
For example, a doorOpen condition could execute an implied STAND as
part of its implied OPEN DOOR; if another precondition requires the
actor being to be sitting, its effect would be undone if the doorOpen
were executed second. We can safely STAND and OPEN DOOR first, then
SIT DOWN second, because we'll still satisfy the doorOpen condition
even though we're no longer standing when we're done.
In addition to the new preCondOrder ordering, the default ordering
has changed for two-object verbs (those with direct and indirect
objects). By default, the direct object preconditions are now
executed first, followed by the indirect preconditions; in the past
this order was reversed. This is only the default order - the
preCondOrder takes precedence. That is, the action builds a list
consisting of the direct object preconditions followed by the indirect
object preconditions, then sorts that list in ascending order of
preCondOrder, preserving the existing relative order of objects with
the same preCondOrder.
adv3.h now defines a couple of new templates for your convenience:
SyncEventList template ->masterObject inherited;
Hint template 'hintText' [referencedGoals]?
When an Unthing is used as a possessive or locational qualifier ("the
key in the unthing" or "the unthing's key," for example), the parser
now shows the Unthing's "not here" message. In the past, the parser
showed the same default messages for an Unthing as for any other
object, using wording like "The unthing doesn't appear to have that"
or "You see no key in the unthing." This wording was incongruous for
Unthings because an Unthing represents the absence of an object, and
hence an Unthing can't "appear" to have or contain - or even not have
or not contain - anything. Showing the Unthing's "not here" message
yields more sensible results.
An Unthing now responds to LISTEN and SMELL commands with the same
message it uses for EXAMINE (by default, "You don't see that here").
This applies even if the Unthing is distant or obscured. (In the
past, the inherited Decoration messages for these verbs were used.)
The English library provides a new template for Unthing:
Unthing template 'vocabWords' 'name' @location? 'notHereMsg'?;
The finishGame() function now updates the global turn counter to
reflect any turn currently in progress, or, when called from a daemon
or fuse, the turn the player has already completed. In the past, the
turn counter displayed in finishGame() didn't reflect the turn in
progress that triggered the call to finishGame(). Internally, a turn
isn't counted in the global turn counter until it's finished; but from
the player's perspective, a turn should count as soon as it starts.
This change ensures that the count that finishGame() displays is
consistent with the player's expectations.
A Lockable that's also an Openable no longer reports its
locked/unlocked status when the object is open. By default, an
Openable must be closed in order to be locked, so if it's open, it's
always unlocked. This means that the locked/unlocked status is
effectively redundant information when the object is open.
By way of implementation, the Lockable class has a new method,
lockStatusReportable, that indicates whether or not the lock status is
worth mentioning in the object's current state. (This doesn't
supersede lockStatusObvious, but rather complements it.
lockStatusObvious still indicates whether or not the lock status is
visually apparent; lockStatusReportable indicates whether it's worth
mentioning from an aesthetic point of view.) Lockable defers to any
mix-in class that provides the method, and Openable takes advantage of
this by providing an implementation of the method that returns true if
the object is closed, nil if it's open.
Door and SecretDoor are now based on a new common base class,
BasicDoor. The new BasicDoor class defines the core internal behavior
of a door: a travel connector object that can be open or closed, that
allows travel only when open, and that can have two objects linked
together to represent the two sides of the door.
The relationship between BasicDoor and Door is analogous to that
between BasicOpenable and Openable. BasicDoor defines the internal
behavior, while Door adds handling for user commands to manipulate the
door (in particular, OPEN, CLOSE, LOCK, UNLOCK).
This change doesn't affect the functionality of Door; it merely
rearranges the code for Door internally by moving a few methods to the
new BasicDoor base class. SecretDoor isn't much affected, either, but
does now add support for remembering a recent traversal for
disambiguation purposes, as Door did in the past (and still does). No
changes should be needed in existing game code.
The main benefits of this change are that it makes SecretDoor
behave more consistently with the Door class, and it provides a single
base class for all door-like objects. In addition, the new BasicDoor
class makes it easier to define custom doors with special controls,
since it has all of the necessary functionality but doesn't make any
assumptions about handling commands.
BasicDoor.noteRemoteOpen() now calls a new method,
describeRemoteOpen(), to generate the message describing the change.
This makes it easier to override the message for a door, since you can
simply display the custom message without having to worry about the
sense context.
The Enterable and Exitable classes no longer accept the GO THROUGH
command. In the past, these classes did accept GO THROUGH as
synonymous with travel, but this became undesirable when the
EntryPortal and ExitPortal classes were added. Enterable and Exitable
are meant for things like buildings, where ENTER or EXIT make sense
but not GO THROUGH; EntryPortal and ExitPortal are for things like
doors and passages, where you can equally well ENTER/EXIT them or GO
THROUGH them.
The AutoClosingDoor class has a new method, reportAutoClose(), that
generates the message that mentions that the door closed behind
whoever just went through it. The TravelVia action() handler calls
reportAutoClose() after it automatically closes the door; by default,
reportAutoClose() shows the standard library message
&doorClosesBehindMsg. In the past, the action() handler simply
showed the same library message itself, rather than calling out to a
separate method.
The purpose of this change is to make it easier to customize how
the auto-closing is reported, or whether it's reported at all. If
you want the door to close silently, without any mention that it
closed, simply override reportAutoClose() to do nothing.
By default, a FireSource object now removes itself from consideration
as a default indirect object for the BurnWith action if it's not
already lit. This ensures that the library won't (for example)
attempt to use an unlit candle as a default source of fire to light
another candle. In the past, under certain circumstances, the library
could cause a stack overflow by trying to light one candle with
another, which it tried to light with the first, and so on. This
change ensures that a fire source will only be chosen as the default
indirect object for BurnWith if it's already lit.
(Matchstick objects, of course, aren't affected by this change,
since they can light themselves on fire independently. Matchsticks
thus remain valid as default choices for BurnWith even when they're
unlit.)
The Wearable class now sets an elevated likelihood (via logicalRank())
for a REMOVE (or TAKE OFF or DOFF) command when the object is being
worn. This helps avoid disambiguation prompts for these commands.
When a Hidden object is discovered (via its 'discover()' method), the
object now marks itself and its contents as having been seen by the
player character, assuming they're within sight at the time the object
is discovered.
The new RoomPart methods moveIntoAdd(room) and moveOutOf(room) make it
easier to dynamically change a room's set of RoomPart objects. In the
past, there was no straightforward way to change a room's RoomParts
dynamically; these new methods make it easy by making the necessary
updates to the affected room's properties.
Room.getExtraScopeItems() now adds the room's floor to scope only if
the actor is directly in the room. In the past, this routine added
the floor to scope unconditionally. The rationale of adding the floor
to scope is that an actor standing in a room is aware that there's a
floor there simply by virtue of standing on it - but that rationale
only holds when the actor is actually standing on the floor (or
sitting on it, or whatever), and that's only the case when the actor
is actually directly in the room. If the actor is in a connected
room, or inside a nested room within the outer room, then in most
cases the actor isn't actually in contact with the floor.
Similarly, NestedRoom.getExtraScopeItems() now adds the nested room
to scope only when the actor is directly in the nested room. The
rationale is the same as for the floor in an outer room.
The TALK TO command now treats talking to oneself as illogical; in
the past, it was merely considered improbable.
A side effect of this change is that the intransitive
conversational commands (HELLO, GOODBYE, YES, NO) won't try to make
the PC talk to itself by default. In version 3.0.8, these commands
were enhanced to choose the same default interlocutor as TALK TO would
when the most recent interlocutor is no longer present. This had the
undesirable effect of choosing the PC as the default target when no
other actors were present. Now that TALK TO won't choose the PC as
the default actor, HELLO and the rest won't do so, either.
In past versions, the InConversationState's "boredom counter" was
handled inconsistently in certain cases. In particular, when an NPC
initiated a conversation, it took one more turn for the NPC to become
bored than it took when the PC initiated the conversation. This has
been corrected.
The library now uses the "identity" object when setting a pronoun
antecedent in Actor.setPronounObj(). This ensures that when an
internal program object is being manipulated, and that object
generates a pronoun antecedent, any subsequent command reference to
the antecedent will properly refer to the main simulation object
associated with the internal implementation object. For example, this
ensures that when a ComplexComponent is manipulated, subsequent
references to "it" refer back to its ComplexContainer parent.
A library bug introduced in version 3.0.8 caused a run-time error when
the player attempted to address a command to another actor and used a
pronoun to identify the target actor (as in HIM, GO NORTH or ASK HIM
TO GO NORTH - the first phrasing is awkward enough that most players
wouldn't think to use it, but the ASK phrasing is natural enough that
players might try it). This has been corrected.
A bug in ThingOrTopicMatchTopic.isMatchPossible caused a run-time
error under certain conditions. This has been fixed.
The <.roomname> style tag now adds a newline after the room name
text; this is in lieu of the <.roomdesc> style tag showing a
newline before the room description, as was done in the past. The
newline is really part of the room name formatting, to ensure that the
room name goes on a line by itself. This change won't affect most
existing games, since the usual LOOK AROUND format shows the room name
followed by the long description; but the change does ensure that a
room description won't have an extra leading newline in cases where
the room name isn't displayed.
In Intangible, Fixture, Decoration, Room, and RoomPart,
hideFromDefault() now simply returns nil. (In the past, these classes
defined hideFromDefault() as returning the inherited value, which
didn't have the desired effect of bypassing the inherited call (from
Thing.hideFromDefault()) to self.hideFromAll(). These classes
override hideFromAll(), but they want to use separate handling for
hideFromDefault(). The old override didn't correctly accomplish this
because of the way the base Thing implementation of hideFromDefault()
is written.)
The library now responds to commands like THROW ME, THROW ME AT
something, and THROW ME TO something with an appropriate
failure message. (In the past, these commands were actually allowed,
and used the standard THROW handling, which obviously isn't
desirable.)
The library now accepts SIT and LIE (and, equivalently, SIT DOWN and
LIE DOWN) as intransitive verbs. In the past, these were defined as
missing-direct-object forms of the transitive commands SIT ON and LIE
ON, which meant that the library tried to find a default direct
object. This had the undesirable effect of making an actor who was
already sitting/lying automatically stand up and sit/lie on some other
suitable object in the room, which as a last resort was the default
floor. The new intransitive verbs check to see if the actor is
already sitting/lying, and if so, simply generate an error message to
this effect. If the actor isn't already sitting/lying, the new
commands proceed as before: they look for a suitable default object,
and sit/lie on that.
As part of this change, the intransitive SIT DOWN and LIE DOWN
phrasings no longer have [badness], so the parser will match them
as full-fledged intransitive phrasings.
There are two small changes to the action message processor.
First, it's now possible to define a message method on a Thing that
takes no arguments, even if the message normally does require
arguments. This makes it more syntactically convenient to define
simple message string or property-redirect methods that require some
small chunk of code, but which don't need any of the argument values
that are normally sent to the message method. For example, we can now
write this:
notImportantMsg = (myGenericMsg)
instead of this (which was formerly required):
notImportantMsg(obj) { return myGenericMsg; }
Second, these sorts of message methods can now return nil to
indicate that they don't want to provide the message after all. This
is useful when an object only wants to override a library message when
it's in a certain state. It's also useful when an object wants to
provide the message only when that object is in a particular role in a
two-object command (i.e., a command with both a direct and indirect
object). Recall that when a command involves both a direct and an
indirect object, the message processor asks both of the objects for an
override; this new capability lets the objects be selective about
providing an override according to their actual roles in the command.
For example, suppose you want your 'vase' object to override the
"not a container" message for PUT IN, but only when the vase is the
indirect object of the PUT IN, not when it's being put somewhere
itself. To do this, you could write vase.notAContainer like so:
notAContainerMsg = (gIobj == self
? 'The vase\'s opening is too small.' : nil)
The library provides a pair of new macros that simplifies the
syntax for that kind of conditional message override: dobjMsg()
and iobjMsg(). These macros simply test to see if gDobj or gIobj
(respectively) equal 'self'; if so, they return the argument, and
if not they return nil.
The statusLine object has a new method, setColorScheme(), that sets up
the status line's colors. This change won't affect any existing code,
since it's just a minor internal reorganization, and the behavior is
the same as it used to be. The point of the change is that it's now
easier to customize the status line color scheme, since you only have
to override this one method, and the only thing the override has to
do is to write out a <BODY> tag with the desired color scheme.
The English tokenizer now recognizes upper-case "'S" (apostrophe-S)
suffixes as equivalent to the lower-case version. In the past, the
tokenizer only recognized this token in lower-case.
In the English version, the SpecialTopic matcher now specifically
rejects matches for several "weak" topic command inputs: I, L, and
LOOK. The matcher only rejects the matches when these are the
only thing the player typed. If the player types just "I", for
example, they probably intend to enter an INVENTORY command rather
than to select a special topic that happens to have "I" among its
tokens (as in "tell him I don't know").
In the English grammar, plurals are now accepted as possessive
qualifier phrases (as in "the women's books"). When a plural
possessive is used to qualify a plural phrase ("the women's books"),
the parser resolves the overall phrase to all of the in-scope objects
matching the base noun phrase that are owned by anyone matching the
qualifier phrase. When a plural possessive qualifies a singular noun
("the women's book"), the parser makes the same initial selection, but
then prompts for more information if the selection includes more than
one object.
When a possessive phrase is used to qualify a noun phrase, and the
result is ambiguous, the parser now filters the list of possible
matches using the filterResolveList() mechanism. This ensures that
special objects - especially Unthings and Collectives - are treated
the same in this case as they are in other situations. In the past,
for example, the parser included Unthings in a disambiguation list if
the Unthings matched the vocabulary of a possessive qualifier; this
was undesirable because Unthings aren't meant to be treated as actual,
present objects from the player character's perspective.
In the past, if the player responded to a disambiguation prompt with a
possessive phrase, and the specified possessor didn't own any of the
objects in the original disambiguation list, the parser responded with
a message like "Bob does not appear to have any such thing." This was
inconsistent with the way disambiguation responses are treated for
other types of responses. Now, an inappropriate possessive response
receives the same message as would a random adjective that doesn't
apply to any of the original possibilities: "That was not one of the
choices."
The English version of the library now lets the Action determine which
message to use when there's nothing in scope matching a noun phrase in
a command. In the past, the parser always used the "noMatch" message,
the default wording of which was "You see no
noun phrase here."
This was sometimes jarring for commands that generally refer to
non-visual objects, such as LISTEN TO or SMELL. With this change, the
library now calls the Action (via its new method noMatch()) to
determine which message to show. Most actions simply invoke the new
message method noMatchCannotSee(), which shows the same message as
before. The LISTEN and SMELL actions override noMatch() to call the
new message method noMatchNowAware(), which shows the message "You are
not aware of any
noun phrase here." The new wording should be
more natural when the noun phrase is something inherently non-visual.
(It would be even better if the library could decide based on the
noun phrase itself whether the noun phrase refers to something visual
or not, since the player could just as well try to EXAMINE THE
SULFUROUS ODOR or TAKE THE BEEPING NOISE. Unfortunately, it's
effectively impossible to do this, since the true meaning of the error
message is that the parser can't determine what the noun phrase
actually refers to. We don't want to say that outright, of course,
since that would detract from the player's sense of immersion in the
game world by calling attention to the parser and its limitations, so
the best we can do is to find a heuristic that works most of the time.
The addition of the per-action customization improves on the old
scheme and should make incongruous cases relatively rare.)
The parser's algorithm for choosing grammar matches has been
fine-tuned to handle cases with truncated words and adjective-ending
phrases a little better, particularly in commands involving multiple
objects. In some cases, when presented with a two-object command
(e.g., PUT X IN Y) in which one of the object phrases failed to match
any in-scope objects, for the other phrase, the parser formerly
chose a noun-ending phrase over an adjective-ending phrase, even if
the adjective-ending version matched an object in scope and the
noun-ending version didn't. This sometimes yielded odd "you don't see
that" messages that named the object that actually was in
scope, from the player's perspective. This change should improve
the behavior in situations like this.
In the English library, the code that tries to figure out whether to
use "a" and "an" for an object's aName now ignores any leading HTML
tags in the object's base name. This is useful for objects with
aName strings such as "<i>Apple County Times</i>".
In addition, the code now specially recognizes names that start
with one-letter words (such as "X-ray" or "<q>L</q>
button"), and assumes they're pronounced by saying the name of the
letter. So, for example, "X-ray" turns into "an X-ray," since the "X"
is pronounced as "ex."
The English library now accepts SET as a synonym for PUT in the
commands PUT DOWN, PUT ON, PUT IN, PUT UNDER, and PUT BEHIND.
SET is also now accepted as a single-object verb. When applied to
a Thing, the library assumes the intention is SET thing ON
something, and prompts for the other object. When applied to a
Settable, the library assumes the intention is SET settable TO
setting, and prompts for the setting.
Note that the single-object form (simply SET dobj) could be
useful all by itself in some cases: SET ALARM, for example. For such
cases, you can simply define a custom dobjFor(Set) for the object.
In the English library, L is now accepted as a synonym for LOOK in the
various forms of the LOOK UP and LOOK FOR commands: L UP topic IN
object, L topic UP IN object, L FOR topic, etc.
In the English library, THROW
iobj dobj is now accepted
as a synonym for THROW
dobj TO
iobj, allowing phrasing
like THROW BOB THE BALL.
In addition, THROW obj direction (as in THROW BOOK
EAST) is now accepted, via the new action ThrowDir. The default
handling for ThrowDir (in Thing) is simply to display a message
essentially explaining that the proper command is THROW AT: "You need
to be more specific about what you want to throw the book at." If the
phrasing is THROW obj DOWN or THROW DOWN obj, though, we
use the same response we use for THROW AT FLOOR ("You should just put
it down instead").
The parser's grammar ranking mechanism has a new ranking criterion:
"phrasing weakness." This is an arbitrary numeric value that the
language module can use to rank particular phrasings as relatively
weak, which means that they're less likely to be valid matches.
The English grammar uses this new criterion to mark phrasings as
weak when they involve two object phrases with no prepositional marker
and no qualifier word - things like GIVE BOB BOOK. These phrasings
are weak because they could be interpreted as a single object with a
missing phrase instead of two objects - if the wording were GIVE GREEN
BOOK, for example, there's probably a GREEN BOOK and a missing object
rather than two objects GREEN and BOOK. A phrasing such as GIVE BOOK
TO BOB isn't weak because the prespositional marker makes the grammar
fairly unambiguous; likewise, GIVE BOB THE BOOK is relatively clear
because the article "the" can't normally go in the middle of a noun
phrase, so most likely introduces a second noun phrase.
In the English library, CLIMB UP and CLIMB DOWN without a direct
object are now explicitly recognized in the grammar as missing-object
versions of CLIMB UP dobj and CLIMB DOWN dobj. This
prevents the CLIMB dobj grammar from treating the UP or DOWN as
a direct object, which usually yielded an ungainly response along the
lines of "you see no up here." (The missing-object CLIMB UP and CLIMB
DOWN are marked with "badness" in the grammar, which means that if
there actually is an in-scope object with vocabulary words "up" or
"down", the CLIMB dobj form will still be matched - so CLIMB UP
as short-hand for CLIMB THE UP STAIRWAY will continue to work as it
did before.)
In the English library, the message substitution parameter "{a/he}"
was incorrectly not marked as a sentence subject. It's now so marked.
In the English library, the methods askMissingObject(),
missingObject(), askMissingLiteral(), and missingLiteral() have been
moved from class messageHelper (in en_us.t) to playerMessages (in
msg_neu.t). These methods are just message generators, so they belong
in the message object with all of the other message generators.
In the English library, the messages for noMatchForAll have been
changed to variations of "You see nothing suitable." In the past, the
message was "You see nothing to use for 'all' here," which was
somewhat misleading: it suggested that there's nothing present at all,
when the actual point is merely that there's nothing present that can
be used in the manner specified.
In 3.0.8, OOPS X (for any literal X) was changed so that it didn't
consume a turn, but OOPS (without a literal) was inadvertantly left
unchanged, so it still counted as a turn. This has been corrected: a
bare OOPS command no longer consumes a turn.
In the past, if the player typed NOTE without any text for the note,
the parser prompted for the missing text ("What do you want to
note?"). It no longer does this. Instead, the first time an empty
NOTE is entered, the parser displays a message explaining that NOTE is
usually used to enter a message, in case the player wasn't aware of
that; and after that, the parser simply accepts an empty NOTE the same
as any other.
In the past, typing the secret command XSPCLTOPIC by itself on a
command line caused a run-time error. (XSPCLTOPIC is a verb that the
library uses internally to handle SpecialTopic entries. It's not
something the player ever needs to type, or indeed is ever allowed to
type, but at certain time, the library internally runs XSPCLTOPIC
commands through the normal command processing as though the player
had typed.) This has been fixed.
In the past, if the parser asked for a missing literal phrase (such as
for TYPE), and the player responded with input that included a
character that the game's tokenizer didn't accept, the game displayed
an "unknown exception" message. The problem was that the parser
didn't properly catch the tokenizer's bad-token exception in this
case. The parser now catches this exception and reports it as normal.
In the English library, when the parser is announcing the name of each
object in a multi-object command, paragraph breaks are now suppressed
after the object name. This slightly improves formatting in cases
where the action response for an object happens to start with a
paragraph break.
In the English library, the verb-phrase builder method (getVerbPhrase())
for TopicAction, TopicTAction, LiteralAction, and LiteralTAction didn't
work correctly. This has been corrected.
The new output control pseudo-tag <./P0> cancels the effect
of an immediately preceding <.P0> tag. This is useful in
certain cases where you want to ensure that a newline or paragraph
break is displayed, even if a <.P0> was just displayed.
All of the various spellInt-related functions are now implemented in
en_us.t. Some of these functions were formerly in numbers.t, but that
wasn't the right place for them because some language modules might
need to change these functions' interfaces (for example, to specify
the grammatical case or gender of the result). This change should
have no effect on any existing game code; it's a purely internal
change.
The message property 'flashlightOnButDark' has been renamed to
'flashlightOnButDarkMsg' (that is, the suffix 'Msg' has been added)
for consistency with the other message property names.
The default library message for TASTE has been changed to "It tastes
as you expected" (from "You taste nothing out of the ordinary"). This
emphasizes that the physical action was completed but that nothing
unusual was detected; the old message could have been read as
suggesting that the object in question had no flavor at all, which
obviously isn't true of most objects.
In addition, UntakeableActor and Person now use messages similar to
those used for TAKE ("The cat won't let you," "Bob probably wouldn't
like that"), since these sorts of actors are meant to exhibit
volition, and in the real world would generally not allow themselves
to be arbitrarily tasted.
A couple of the default library messages for POUR incorrectly referred
to the direct object where they meant to use the indirect object.
These have been fixed.
Released 9/12/2004
Compatibility-breaking change: The class formerly named
ShipboardRoom has been renamed to simply Shipboard. This name change
is for consistency; most of the other similar mix-in classes are named
as simple adjectives. You should search your game code for any
mentions of ShipboardRoom and change each to Shipboard.
Note that the new class ShipboardRoom is provided for convenience
as a pre-defined combination of Shipboard and Room. This means that
any rooms you previously defined with the superclass list
ShipboardRoom, Room (and just changed, following the
prescription above, to Shipboard, Room) can now be written
simply as ShipboardRoom. This is purely for convenience; you
can leave the class list as Shipboard, Room and it will
behave the same way.
Compatibility-breaking change: The class formerly named
OccludingConnector is now called Occluder. This change emphasizes
that the class is a mix-in that isn't limited to use with sense
connectors.
Possibly compatibility-breaking change: The parameters to the
enterConversation() method of the ConversationReadyState class have
changed. The old second parameter, 'explicit', has been deleted and
replaced with a new parameter, 'entry', which gives the TopicEntry (if
any) that triggered the conversation entry. Any game code that calls
or overrides enterConversation() must be changed to accomodate the
parameter change.
The new parameter gives enterConversation() more information about
what triggered the conversation. This extra information is necessary
because, without it, the conversation system wasn't able to properly
handle topic entries that were marked as conversational but
non-greeting (that is, the isConversational property is true, but
impliesGreeting is nil). In the past, such entries were incorrectly
treated as though they were entirely non-conversational, so they
didn't enter an in-conversation state when used in a response.
Note that the same information conveyed by the old 'explicit'
parameter can be inferred from the new 'entry' parameter. When
'entry' is nil, it means that the conversation entry is explicit,
because it was triggered by a direct action (HELLO, etc.) rather than
a TopicEntry. When 'entry' is non-nil, it means that the conversation
entry is implied by the TopicEntry.
Possibly compatibility-breaking change: The ConvNode method
processSpecialCmd() has an additional parameter, as does the
SpecialTopic method matchPreParse(). It's unlikely that any game code
will be affected by this, because games don't normally override these
methods - usually, games just define SpecialTopic keyword lists.
The new parameter in both cases gives the "processed" version of
the player's input. The default SpecialTopic implementation now
matches the processed version of the input rather than the original
text the player entered.
In the English library, the processed input is the player's
original input with any periods, commas, question marks, exclamation
marks, colons, and semicolons removed, and with any leading "A" or "T"
keyword removed.
The purpose of the change to the parameters of these methods is
that it makes it simpler and more efficient to do this filtering.
Note that the punctuation filtering is new. We filter out the
punctuation because it's not usually meaningful for matching special
topics. For example, if the player's input contains the correct
keywords to match a special topic, we usually want to match the
special topic even if the player (for example) put a period or
question mark at the end of the command.
Possibly compatibility-breaking change: The format of the
message generated by DropType.getReportPrefix() has been changed so
that the message is expected to be a complete sentence. This affects
the library messages floorlessDropMsg, droppingObjMsg, throwHitMsg,
and throwShortMsg (this last one is new in this update). These
methods and messages are mostly for internal use, but if you override
or call DropType.getReportPrefix() in your code, or if you've
customized any of these messages, you'll need to adjust your own
messages for this change.
The old sentence fragment format had a couple of problems. First,
it was likely to be problematic for some non-English translations.
Second, it was less flexible than the new full-sentence format, even
in English, for the purposes of adding new "drop type" objects and
of adding new messages composed using the prefix.
The parser's pronoun antecedent rules have changed slightly. In the
past, when the player entered a command with multiple noun slots (such
as UNLOCK DOOR WITH KEY), the parser always chose the last-resolved
object as the antecedent for any future pronoun. This meant that the
parser remembered either the direct object or the indirect object,
depending on the verb. This rule didn't always produce the results
that you'd expect in natural English conversation, and had the further
drawback that the remembered object varied from verb to verb.
The parser now remembers both objects for two-object verbs,
and waits to make a decision until the player actually enters a
command with a pronoun. When the player types a singular pronoun (it,
he, she), and the most recent command had multiple noun slots, the
parser performs the standard "verify" evaluation on each potential
antecedent and picks the most logical one. If more than one
antecedent is equally logical, the parser simply picks one arbitrarily,
and announces the choice in the usual manner. When the choice is
completely arbitrary, it will always be the direct object of the
two-noun command.
Some examples:
>LOCK DOOR WITH KEY
Locked.
>DROP IT
Dropped. (the key, because it's illogical to drop the door)
>UNLOCK DOOR WITH KEY
(first taking the key)
Unlocked.
>OPEN IT
Opened. (the door, because it's illogical to open the key)
>LOCK DOOR WITH KEY
(first closing the door)
Locked.
>EXAMINE IT
(the door)
It's a heavy wooden door.
In the last command - EXAMINE IT - the choice of the two potential
antecedents is arbitrary, because it's equally logical to examine the
key or the door. The parser thus chooses the direct object of the
prior command, and announces the choice to alert the player (in case
she was thinking "it" would refer to the key).
The new Thing method notifyMoveInto(newContainer) complements the
existing moveInto notification routines notifyRemove() and
notifyInsert(). The new notifyMoveInto() method is called last in the
sequence, after notifyRemove() has been called on the old container
tree and notifyInsert() on the new container tree. notifyInsert()
gives the object being moved a chance to perform any special
additional processing before the move, or to veto the move (which it
can do by displaying an explanatory message and calling 'exit', just
as notifyRemove() and notifyInsert() are allowed to do).
The new routine has several benefits. First, it provides the
object being moved with the same sort of notifications that the old
and new containers already receive. Since it runs along with these
other notifications, it allows the same distinction as those other
notifiers between moves that trigger these side effects (via calls to
moveInto) and those that don't (via direct calls to baseMoveInto)
Second, since the new method runs last, after the notifyRemove() and
notifyInsert() notifications, it has the last chance to veto the move;
this means that if it doesn't cancel the move, then the move will
definitely proceed (at least to the extent that baseMoveInto() will be
called). Any side effects that must be carried out only when the move
will succeed can thus be safely coded here.
The parser now provides more helpful error feedback when the player
tries to enter a SpecialTopic command that's not active.
First, the library automatically enters all of the keywords defined
by special topics into the game's dictionary. This ensures that the
parser won't claim that special topic keywords are unknown if they're
used out of context, as it did in the past. It was confusing when the
parser claimed that special topic keywords were unknown, because the
parser most certainly did know them some of the time.
Second, when the player enters a command that looks syntactically
invalid, but which doesn't contain any unknown words, the parser scans
recent special topics for a match. If it finds a match, the parser
will show a new message: "That command can't be used right now." This
old response - "The story doesn't understand that command" - was
potentially confusing, since the story can recognize the exact
same command at other times.
The parser scans the list of special topics most recently listed in
topic inventories, using the new specialTopicHistory object. This
object keeps track of a configurable number of the most recent
SpecialTopic suggestions listed; set its maxEntries property, which
defaults to 20, to the maximum number of recent entries to scan. The
purpose of the limit is to ensure that a scan won't take too long, by
limiting the scan to the specified maximum number of SpecialTopic
items. The trade-off is that the parser will forget older
suggestions, so they'll be met with the old "story doesn't understand"
message if the player tries them. However, it seems safe to assume
that the player will also forget about older suggestions, and so won't
think to try them.
If you set maxEntries to nil, it essentially removes the limit to
the number of entries to scan. The library won't maintain the history
list at all in this case. Instead, to check an unrecognized command,
the parser will simply scan all SpecialTopic objects in the
entire game, whether or not they've ever been suggested in topic
inventory displays. This option might be preferable for a game where
the total number of items is small enough that scanning them all
doesn't take an appreciable amount of time. (Or not. Some authors
might not want the parser to recognize previously-unseen special
topics at all - even to the extent of saying "that command can't be
used right now" - because doing so could conceivably give away
upcoming plot developments. That is, if the player accidentally types
a command that happens to match a special topic that hasn't ever been
suggested, the player could infer from the special error message that
the command will do something later in the game. The chances of
actually giving away anything important this way seem rather remote,
but this sort of thing rubs some authors the wrong way. If you don't
like the idea of using the different error message for special topics
until after they've been suggested at least once, but you also don't
want a limit on the number of past topics to remember, just set
maxEntries to a very high number.)
The new property pluralOrder lets you control the order in which a set
of objects is processed when the objects match a plural phrase in a
command. This property applies to all VocabObjects (which is the base
class for any object with vocabulary words); the arbitrary default
value is 100.
In most cases, the order of processing for the matches for a plural
phrase is arbitrary, so you won't need to worry about this new
property. Once in a while, though, it's convenient to be able to
control the order of matching. This is especially useful when objects
have ordinal names (first door, second door, etc), or when objects are
named by relative spatial position (top drawer, middle drawer, bottom
drawer).
Note that the new property only controls the order of processing
within an individual plural phrase. For example, in the command TAKE
BOOKS AND MAGAZINES, the books will be sorted by their relative
pluralOrder values, then the magazines will be sorted by theirs - but
when the command is processed, all of the books will be taken before
any of the magazines, regardless of the relative pluralOrder values.
This is important because it ensures that commands are processed in
the order specified by the player.
Because pluralOrder and the existing disambigPromptOrder will
probably be used in the same way most of the time, disambigPromptOrder
now returns the pluralOrder value by default. So, for the common case
that the two properties should have the same value, you can simply set
pluralOrder.
The new function cls() clears the screen, and takes care of a couple
of chores that should always be done at the same time. First, the
function clears any text captured in the transcript manager, ensuring
that text that was meant to be displayed before the screen was cleared
won't show up on the new, blank screen. Second, the function calls
the new gameMain.setAboutBox() method after clearing the screen,
ensuring that any <ABOUTBOX> tag is re-displayed; this is
necessary because HTML TADS discards any old <ABOUTBOX>
information whenever the screen is cleared.
The new method setAboutBox() has been added to the gameMain() object.
This new method is designed as the place to put your game's
<ABOUTBOX> tag, if you provide one. The method is invoked
during start-up, and is also called from the new cls() function,
ensuring that the about-box information will be re-displayed each time
the screen is cleared.
The default implementation in GameMainDef does nothing. The new
method is simply a placeholder for the game's (optional) use.
This change is designed to make it easier to manage your about-box
information. HTML TADS discards any previous <ABOUTBOX> tag
when the screen is cleared, so it's necessary to re-display the tag
whenever you clear the screen. If you write your <ABOUTBOX> tag
in the new gameMain.setAboutBox() method, and you always use the new
cls() function to clear the screen (rather than calling the low-level
clearScreen() function directly), the library will automatically keep
yout about-box active.
You can now control whether or not a given verb accepts the word ALL
in its noun phrases. By default, every verb accepts ALL. However, if
you set the new gameMain property allVerbsAllowAll to nil, only the
basic inventory-management verbs (TAKE, TAKE FROM, DROP, PUT IN, PUT
ON) will accept ALL, and every other verb will simply show an error
message ("'All' isn't allowed with that verb").
Some authors like to limit usage of ALL to the basic inventory
verbs, either because of tradition or because they think it's a sort
of "cheating" when a player tries commands like OPEN ALL or EXAMINE
ALL. This feature is probably of particular interest to writers of
puzzle-oriented games.
In addition to the all-or-(almost-)nothing option setting in
gameMain, you can control ALL acceptance on a verb-by-verb basis. The
new Action property actionAllowsAll controls whether a particular
Action accepts ALL for its noun phrases. By default, the base
definition in Action simply returns the value of
gameMain.allVerbsAllowAll. In addition, the basic inventory verbs
(Take, TakeFrom, Drop, PutIn, PutOn) override actionAllowsAll to
return true. If you want to enable or disable ALL for an individual
action, you can use "modify" to set the property for that action
class.
The library uses the new Style Tag <.announceObj> to set the
display style for multi-object announcements. (These are the little
announcements, of the form "blue book:", that precede the responses
when a command is applied to multiple objects.)
The default display style is to show object announcements in bold
text. When a command operates on several objects, the response can
get rather lengthy; the new bold-face style for the announcements is
intended to make these responses easier to read by making the
boundaries of the sub-parts more visually apparent.
The new StyleTag object is called announceObjStyleTag. You can use
"modify" with this object to customize the announcement style, if you
want.
Actor.lookAround(), Thing.lookAroundPov(), and Thing.lookAroundWithin()
now accept a set of bit flags for the 'verbose' parameter. 'true' and
'nil' can still be passed, and have the same meanings as before, so
existing code doesn't need to be changed. However, the new bit flags
give you more control over the format of the listing. The flags, which
can be combined with the "|" operator, are:
- LookRoomName: include the room name, shown on a line by itself at
the start of the description.
- LookRoomDesc: show the full description of the room.
- LookListSpecials: show the special descriptions (specialDesc) for
visible objects that have them.
- LookListPortables: list the portable items that are visible.
Note that lookAroundWithin() automatically adds LookRoomDesc
to the flags if the actor has never seen the room before.
If you pass 'nil' for the 'verbose' parameter, the effect is the
same as passing (LookRoomName | LookListSpecials | LookListPortables).
If you pass 'true', the effect is the same as passing (LookRoomName |
LookRoomDesc | LookListSpecials | LookListPortables).
The main effect of this change is that it lets you generate a room
description without including the room title, which wasn't possible in
the past. This can be useful for cases where the standard LOOK AROUND
format isn't desirable, such as when describing the view through a
sense connector to another room.
The new Thing method roomRemoteDesc(actor) lets you customize the way
a room is described when it's viewed by an actor from another room.
The library calls this from Thing.lookAroundWithin() when the actor
who's viewing the room is not within the room being viewed.
Note that none of the standard library commands ever call this new
method, because the basic library doesn't provide any command to view
a remote room. However, games might provide their own commands to do
so, such as LOOK THROUGH KEYHOLE, and you might want to call
lookAroundWithin() to generate the descriptions in these cases.
Doing so will invoke roomRemoteDesc instead of roomDesc to generate
the room description.
The default Thing.roomRemoteDesc implementation simply calls
roomDesc. You'll probably want to override this any time you actually
use it, to show a suitable description from the remote actor's point
of view.
A new Thing method, adjustLookAroundTable(), lets you customize the
set of objects that lookAroundWithin() can mention. By default, this
routine removes the POV and actor objects (which are usually the same
object) from the table, ensuring that these objects aren't mentioned.
In the past, lookAroundWithin() simply removed the actor object
directly, so the default behavior is the same as in the past.
Separating this out into a new method lets games customize which
objects are mentioned and which aren't for the particular room and
point of view.
The new Thing method isLookAroundCeiling() determines whether the LOOK
AROUND description for a given object is generated by that object
directly, or by its location. By default, this routine returns true
if either the object is a top-level room (that is, it has a nil
location),
or the object's enclosing location isn't visible.
In either of these cases, since it's not possible to see out to an
enclosing location, the current object has to provide its own interior
description.
This method is called from lookAroundPov(), which formerly
performed the same test directly, so the default behavior hasn't
changed. This has been separated into a new method because the
default condition isn't always ideal. For example, if the player's
inside a closed wooden booth with a small window that looks out to the
enclosing location, and the player types LOOK AROUND, we'd want to
describe the interior of the booth, not the outside location. For
this case, you could override isLookAroundCeiling() for the booth to
return true even though the location is visible through the window.
The new TAction method getDobjInfo() returns the full ResolveInfo
object associated with the current direct object. Similarly, the
new TIAction method getIobjInfo() returns the indirect object's
ResolveInfo.
The parser now keeps track of the noun phrase that was parsed for
each object involved in the command. This information is kept in
the ResolveInfo object, in the 'np_' property. This information can
be used, for example, to obtain the original text of the noun phrase
that was resolved to a given object. For example, to obtain the
original text that resolved to the current direct object being
processed in an action() method, you could write this:
local txt = gAction.getDobjInfo().np_.getOrigText();
Due to a bug introduced in the previous release, the method
PresentLater.makePresentByKeyIf() didn't work correctly if the
condition argument was a function pointer. In these cases, the
routine called the function on the first object matching the key, and
then used that same true/nil result for every matching object.
(Thus, if the first matching object was to be revealed, every
matching object was revealed; if the first was to be hidden, all were
hidden.) This has been corrected; the method once again calls the
given function individually for each object matching the key.
Component objects are now hidden from EXAMINE ALL by default. Since
components are simply detail features of larger objects, it's usually
enough to examine the larger objects; it usually doesn't make sense to
also include the components separately in EXAMINE ALL responses.
By default, FOLLOW now reports a more helpful message when there's not
enough information to carry out the command. In the past, the default
message was simply "You don't know where (the actor) went." This was
misleading when the real problem was that the actor's departure had
been previously observed, but from some other location. The new
message is of this form: "The last place you saw (the actor) was (the
room where we last saw him)." Note that the library now keeps track
of the last place we saw each actor, even when we didn't observe the
actor leaving, to ensure that this new message is always accurate.
The "badness" levels of some of the grammar rules in the English
parser have been re-ranked, to put the various error conditions in a
different order. In particular, the "miscellaneous noun phrase" rules
now have a lower badness than any of the "missing noun phrase" rules.
In the past, this relationship was reversed, which caused a subtle
problem when a two-noun verb was used with a noun phrase containing a
word not in the dictionary.
Some background: In the English parser, certain grammar rules are
defined specifically to match certain distinct patterns of erroneous
user input. The point of matching invalid input is to recognize it
and respond intelligently to it, rather than issue a blanket error
message. Most of these invalid constructs are marked with "badness"
levels, which tells the parser to match them only as a last resort, so
that we match a valid grammar rule over one of these special error
rules whenever possible.
In the English grammar, each of the verbs with two noun slots (PUT X
ON Y, ASK X ABOUT Y, etc.) has a corresponding special grammar rule
that includes only the first noun slot (PUT X, ASK X, etc.), for cases
where the player inadvertantly or intentionally types the command with
only one noun. In the past, these rules weren't marked with "badness"
levels. Now they are: these are treated as having "badness"
equivalent to that of an empty noun phrase. This ensures that they'll
be matched only as a last resort, when there isn't a more "correct"
interpretation of the phrase.
CollectiveGroup objects are now explicitly marked as unlisted in room
or object contents or in inventories (that is, a CollectiveGroup's
isListed, isListedInContents, and isListedInInventory properties now
return nil). In the past, collective group objects incorrectly showed
up in these listings as separate objects under certain circumstances,
such as when their individuals were self-illuminating (brightness = 1)
and the enclosing room was dark.
In the past, the description of a dark room listed every visible
self-illuminating object, even including those being carried by the
actor doing the looking. Now, the description excludes objects in the
actor's inventory, since these objects aren't part of the actor's
surroundings.
In the past, the INVENTORY command included in its listing any items
that were being directly held, even if they weren't visible, on the
assumption that the actor could identify these items by touch alone.
This has now been refined slightly: instead of unconditionally
including directly-held items in the listing, the library now includes
directly-held items that the actor already knows about, as indicated
by the actor's knowsAbout() method. The rationale is almost the same
as before, but this change means that we no longer assume that the
actor can identify an item by touch unless the actor already knows
about the item. Of course, this change doesn't affect the listing of
visible items - visible items are listed whether previously known or
not.
The "finish option" handler for RESTORE (finishOptionRestore) now
terminates any remaining processing on the command line that triggered
the finish options, when the RESTORE is successful. This ensures that
when the finish options are triggered from somewhere in the middle of
a command (such as in a beforeAction method), there will be no strange
side effects from carrying out the rest of the triggering command.
In the English grammar, LOOK OUT is now a synonym for LOOK THROUGH.
This allows constructs like LOOK OUT WINDOW, which is more natural
for some objects than LOOK THROUGH but has the same meaning in
virtually all cases.
In the English grammar, the acceptable phrasing for the SitOn command
no longer includes "sit object" or "sit down object."
These phrasings aren't grammatical, but they were included to match
intransitive inputs (SIT and SIT DOWN) and then ask for the missing
direct object phrase. Now, rather than matching the intransitive
forms using the main SitOn rule, they're matched using the SitOnWhat
rule. The same change applies to the LieOn rules.
UnlistedProxyConnector.primaryConn is now initialized to TadsObject by
default. In rare cases, game code might invoke a property of an
UnlistedProxyConnector before the object is constructed; this new
default (rather than nil) provides reasonable behavior in these cases,
without requiring an added run-time test for a nil primaryConn.
In the English messages, the AutoClosingDoor's announcement (of the
door having automatically closed) now has a leading paragraph break,
to set it off from the room description or other travel report that
usually precedes the announcement.
The English library's rules have changed slightly for substituting
reflexive pronouns in message strings. In the past, the library
always substituted a reflexive pronoun when an "objective-case"
parameter matched the subject of the sentence; for example, in the
message string "{You/he} can't eat {the dobj/him}," if the direct
object was the same as the actor, the message would be "You can't eat
yourself" (rather than simply "You can't eat you"). The new rule is
almost the same, but has a new exception: if the
parameter
name of an objective-case parameter is the same as that of the
subject of the sentence, the reflexive substitution is not used.
For example, the library now displays "{You/he} close{s} the door
behind {you/him}" as "You close the door behind you." The old rule
would have used "behind yourself" instead, but the new exception kicks
in because both the subject ("{You/he}") and the objective-case item
("{you/him}") refer to the 'actor' parameter object.
The reason the automatic reflexive substitution exists in the first
place is that it allows you to write a message string that refers to
multiple parameters that might end up being the same object
some of the time. In the first example ("{You/he} can't eat {the
dobj/him}"), the direct object and the actor might sometimes be the
same, but usually aren't; since the library automatically provides the
reflexive pronoun when the direct object happens to be the same as the
actor, the same message string works for any combination of objects
("You can't eat yourself" vs. "You can't eat the deck chair").
However, this rule is too aggressive without the exception. The point
of the exception is to give authors exact control in cases where they
know in advance that the subject and objective-case object will
be the same, by virtue of the fact that the message is referring to
the same parameter by name in both cases. In these cases, there's no
need for the library to provide reflexive pronouns conditionally,
because the author knows in advance that the objects will always be
the same - so the author will know whether or not the reflexive
pronoun is desired, and will write it that way, or not, as desired.
If an exception occurs during an NPC's turn (in ActorState.takeTurn,
for example), the scheduler will now count the NPC's turn as over, so
that other NPC's get a chance to run before the same NPC takes another
turn. In the past, an uncaught exception during an NPC's turn
bypassed the turn-counting for the NPC, so the same NPC got another
turn immediately; in some cases, this caused the NPC to try the same
erroneous operation again, which set up an infinite loop as the
erroneous NPC code was invoked over and over without giving anyone
else a chance to run. The same change applies to fuses, daemons, and
any other "schedulable" object.
In the English library, SCRIPT ON is now accepted as a synonym for
SCRIPT (for consistency with SCRIPT OFF), and RECORD ON as a synonym
for RECORD. In addition, the RECORD ON/OFF messages have been changed
to sound less geeky (they formerly referred to "RECORD mode," but now
talk about "command recording" instead).
In the English library, "inside" and "inside of" are now accepted in
locational qualifier phrases (as in "read the coin inside the box").
When the player looks behind or through a closed door, the library now
assumes that the player's intention is to open the door to see what's
on the other side. So, the library now applies an objOpen
precondition to these commands, which will implicitly open the door if
it's closed.
In addition, in the case of LOOK BEHIND, the library uses a special
message if the implicit Open was applied: "Opening the door reveals
nothing unusual." This message emphasizes that we're looking through
the passage, not at the space created between the door and the
adjacent wall (if such a thing is applicable).
When we look behind a door that was already open, the library
assumes that the player's intention is simply to look at the back side
of the door, or the space between the door and the adjacent wall. In
this case, the inherited Thing message ("you see nothing unusual
behind the door") is used.
LOOK THROUGH PASSAGE now has a custom response when the passage is
open: "You can't see much through (the passage) from here." In the
real world, we can typically look through a passage into the next
room, but the library world model doesn't actually allow us to see
anything in the next room; so we don't want to say that we can't see
through the passage at all, but at the same time we can't actually say
we see anything. The new default suggests that we can in principle
see some distance through the passage, but not far enough to see
anything in the connected location.
When a passage is closed, the inherited Thing message ("you can't
see anything through (the passage)") is used instead, since a closed
passage is assumed to be something opaque, such as a closed door.
Looking through, under, or behind a room part now yields a custom
response that says that you simply can't do that, rather than using
the inherited Thing defaults (which say that you can't see anything
there). For room parts, it doesn't generally make sense to perform
any of these actions, which the new custom messages try to convey.
If a player types simply GO or WALK, the library now displays a more
helpful message: "You'll have to say which way to go." In the past,
the parser didn't recognize that phrasing at all, so responded with
the standard unknown-verb message.
PathPassage now uses a custom message for STAND ON: "If you want to
follow the path, just say so." The inherited default ("That isn't
something you can stand on") doesn't make a lot of sense for paths,
but at the same time, we don't want to treat every path passage as a
separate nested room, so this message seeks to convey that the action
might be logical in the real-world but isn't required in the game.
Actor now handles a command of the form PUT ME IN
container by
replacing it with ENTER
container. This is done any time the
direct object is the target actor.
In addition, the actor is now excluded in all cases from being the
default direct object for PUT IN. In the past, the actor was selected
as the default if there was nothing better, which occasionally led to
odd results.
Actor now treats the commands PUT ON and PUT UNDER as illogical when
applied to oneself (as in PUT ME ON THE TABLE).
Thing now calls verifyMoveTo() to verify a DROP command only
when the object is being held by the actor performing the command.
When the object isn't being held, the DROP will be illogical on that
basis alone - the object definitely won't be moved, so there's no
reason to verify the move. This is important because some subclasses
use verifyMoveTo() to report other catch-all conditions ("that's too
heavy to move," etc), which in the past sometimes were reported
instead of the more basic problem ("you're not holding that"). By
avoiding the call to verifyMoveTo() when the object isn't being held,
we ensure that the more basic problem is always reported instead of
any applicable catch-all condition.
The Immovable class incorrectly handled certain two-object actions
(such as PUT IN, PUT ON, MOVE WITH) by failing in the action()
routine. This still allowed the other object involved in the
command to handle the action successfully. For these two-object
actions, the class needs to block the action in the check() routine
instead, which it now does.
When an AgendaItem throws an exception (including things like 'exit'
signals) out of its invokeItem() method, the library now automatically
marks the agenda item as finished (by setting its isDone property to
true). In most cases, if an agenda item throws an exception, it's
because something went wrong; since the same thing would probably go
wrong again if the item were tried again, allowing the agenda item to
remain in its "ready" state typically resulted in an infinite loop as
the actor kept trying the same item over and over. This change avoids
infinite loops in these cases by ensuring that an item that throws an
error isn't retried automatically.
When an object provides its own custom definition for a library
message (via one of the xxxMsg properties), it can now be defined as a
simple single-quoted string, even if the library method for the
message normally takes one or more arguments. This makes it more
convenient to define these custom messages. Of course, you can still
provide a full method definition that takes the same arguments as the
corresponding library message method.
Settable now applies a touchObj precondition to the SetTo action.
This ensures, for example, that a dial is reachable before it can be
turned to a new setting.
The Floor class now includes a touchObj precondition for StandOn,
SitOn, LieOn, and (as an indirect object for) PutOn. This ensures
that a floor in a connected but unreachable room (such as a room
connected by distance) will be ruled out as a potential target object
for these verbs, avoiding poor defaults and unnecessary disambiguation
prompts.
The touchObj precondition now considers a distant object illogical
during its 'verify' phase.
In the past, this precondition treated an object that was visible
but not reachable as still potentially logical, but at reduced
likelihood. The reasoning was that the obstruction might be easily
removable via an implied action. For example, an object inside an
openable glass case could be made touchable by opening the glass
case, which could be performed as an implied action. However, in
the case of distance, there's not usually any way to remove the
obstruction automatically, so there's no point in considering the
object to be logical, even at reduced likelihood. The precondition
still does exactly this for objects that aren't at a distance, but
now assumes that distance can't be resolved automatically.
In the past, if the player used a SEARCH command on an Openable, and
the object was closed, the implied OPEN action showed the
newly-revealed contents of the object. The main SEARCH command that
triggered the OPEN also showed the Openable's contents, so the
contents were listed twice. Openable now omits the redundant contents
listing for the implied OPEN when the main command is SEARCH, just as
it's long done when the main command is LOOK IN.
A "conversational" command (HELLO, GOODBYE, YES, NO) now consistently
counts as a turn for the issuing actor, even when a target actor is
specified (as in "BOB, HELLO"). This corrects a subtle inconsistency
that occurred when a conversational command was directed to an NPC
that had a pending agenda item. In the past, explicitly directing a
conversational command to such an actor ("BOB, HELLO") prevented the
agenda item from executing on the same turn, because specifying the
target actor caused the turn to count against the NPC's turn counter
rather than the PC's; but leaving out the target actor (as in saying
simply "HELLO") counted the action as a PC turn, allowing the NPC to
carry out its agenda item. Now, both forms of the command ("HELLO"
and "BOB, HELLO") have the same turn-counting treatment, so a pending
agenda item will be allowed to proceed in either case.
The HELLO, GOODBYE, YES, and NO commands now find a suitable default
actor to talk to if the target actor isn't specified (that is, if the
command entered is simply something like "HELLO" rather than "BOB,
HELLO"). If there's a current interlocutor, and it's still within
earshot, then it's the default. Otherwise, we look for a suitable
default direct object for a TALK TO command. This makes things more
convenient for the player, since it allows the player to omit the
target actor any time only one suitable actor is present.
In the past, saying BYE to an actor in a conversation-ready state
(that is, an actor ready for a stateful conversation, but not
currently in conversation) caused a run-time error. This has been
corrected.
Non-conversational topic entries (those with the isConversational
property set to nil) now leave the actor's ConvNode unchanged.
Conceptually, a response that doesn't involve any conversation
shouldn't affect the thread of an ongoing conversation, so it
makes more sense for such topics to leave the ConvNode unchanged.
In the past, Thing.lookAroundWithin() generated a slightly strange
description when the point of view was a remote room, and the room
containing the actor doing the looking was visible from the remote
room. In these cases, the actors were described as being "here" ( as
in "Bob is standing here"), even though the "here" in this kind of
description is more properly the remote point-of-view room. The
actors are now described as though they're in a remote room ("Bob is
standing in the cave"), which is consistent with the perspective of
the rest of the description.
In the past, no turn was counted for giving an actor an order (BOB, GO
NORTH) that was refused in the actor's obeyCommand() method. This
didn't entirely make sense, since merely attempting to give an order
to an actor is an in-game action, from the player's perspective. This
has been changed so that giving an order that's refused counts as one
turn.
In the past, if an actor was in an accompanying-travel state, carrying
the actor and then traveling to a new room caused a run-time error.
The run-time error has been fixed.
In addition, the accompanying-travel state is now ignored when the
actor is being carried. When an NPC is being carried, it will
naturally move along with the actor it's accompanying, just as all of
the actor's other inventory items do by virtue of being carried. We
therefore don't want the NPC to separately travel to the new location,
because doing so makes the actor drop the NPC.
Thing now defines the methods canSee(obj), canHear(obj), and
canSmell(obj). In the past, these were defined only by Actor, not by
Thing, since only actors have senses that allow perception of the
physical surroundings. In some cases, though, an actor uses an
inanimate device as a point of view (a television monitor hooked up to
a remote camera, for example), and in these cases it's necessary to
check visibility (or other senses) from one inanimate object to
another. The new methods provide this capability. They can be
overridden as needed; for example, a camera that operates in the
infrared might want to override canSee() to calculate visibility in a
custom "infraredSight" sense rather than in the basic "sight" sense.
The NOTE command is now ignored for the purposes of the AGAIN command.
For example, if you type INVENTORY, then type NOTE SOMETHING, then
type AGAIN, the game will repeat the INVENTORY command. There's no
point in repeating a NOTE command, and NOTE is a sort of side
conversation between player and author anyway, so it makes sense for
AGAIN to act like the NOTE never happened.
The SAVE command is now ignored for the purposes of AGAIN. There's no
point in repeating a SAVE command immediately, as nothing will have
changed in the game state between two consecutive SAVE commands, and
hence no re-saving is warranted.
After a game is successfully restored, the memory of the previous
command is forgotten, so the AGAIN command cannot be used. This
ensures that there's no confusion about whether AGAIN refers back to
the RESTORE command (which would be pointless, as it would just
re-restore the same state), the previous command in the same session,
or the last command before SAVE from the restored game.
When a "finish options" menu is presented (with finishGame() or
finishGameMsg()), and the player selects the UNDO option, the library
now treats UNDO as the last command for the purposes of AGAIN. That
is, if the player answers a finish-options prompt with UNDO, and then
types AGAIN, the library performs another UNDO. (In the past, the
game repeated the command that led to the finish-options prompt, since
that was the last actual command. However, most players wouldn't
think to make this distinction, and wouldn't want to repeat whatever
command led to the game-ending prompt anyway, so the most reasonable
interpretation of an immediate AGAIN is to perform another UNDO.)
The RESTORE command is now ignored for the purposes of the UNDO
command. When a RESTORE succeeds, there's no way to undo anything:
the restore itself can't be undone, and the incoming restored state
initially doesn't have any undo history. When the RESTORE fails, it
won't make any changes to the game state, so there's nothing about the
RESTORE itself to undo. The main effect of this change is that typing
UNDO after a failed RESTORE undoes the last undoable command
immediately before the RESTORE.
In the past, if the player typed an OOPS command at the wrong time
(when there wasn't any spelling error to correct), it was counted as a
turn. This was undesirable because OOPS is a meta command, not an
in-game action. The command no longer counts as a turn.
In the English library, a string pre-parser now wraps the comment text
of a NOTE command in quotation marks, if the text contains any
punctuation marks or the word THEN. This allows players to enter
arbitrary free-form text in NOTE remarks without any danger of
confusing the parser.
The formatting in the English instructions has been tweaked slightly
to make it more consistent. In addition, the parsing of the responses
to the INSTRUCTIONS prompt had a couple of small problems that have
now been corrected.
A bug in the English library caused message substitution strings of
form "{it's dobj/he's}" (that is, with an apostrophe-S on each example
word, and the parameter name written in the middle of the two example
words) to be expanded incorrectly. This has been corrected.
In the English library, the methods (theNamePossNoun, theNamePossAdj)
that build the default possessive name based on the base name of an
object now add an apostrophe-S to a plural name that doesn't end in
"s". In the past, a bare apostrophe was always added to a plural
name, which produced the wrong results for certain words (the men',
the children', the fish').
The English library's method that builds the default plural name of
an object (pluralNameFrom) now handles abbreviations more in keeping
with conventional English usage:
- For single letters, the method now adds an apostrophe-S to small
letters (a's, b's, c's) and to the capital letters A, E, I, M, U, and
V (A's, E's, etc). For other capital letters, and for non-letters,
the method simply adds an "s" (Bs, Cs, Ds). (The apostrophe-S
capitals are special because they could be confused with words or
common abbreviations if we just added an "s".)
- For words that end in a capital letters or a digit, the method now
just adds "s" (CPAs, PCs, MPs, 1970s, 20s).
- For words that end in a period, the method adds apostrophe-S
(Ph.D.'s, M.A.'s).
The new Thing property objInPrep can be used to specify the
preposition to use when describing another object as being within a
given object. In the English library, the default for Thing is 'in'.
Note that the existing property actorInPrep is now defined by default
as returning the same value as the new objInPrep, since for most
purposes they have the same meaning.
A new message is displayed when a Throw command fails because a
DistanceConnector is in the way. In the past, the library used the
default Thing message, which describes the projectile as bouncing
off the object that's in the way. In the case of a distance connector,
this doesn't make any sense. The library now uses a custom message
that describes the projectile as falling short of the target.
In the past, if an object was thrown through a MultiLoc connector to a
separate top-level room, the object sometimes incorrectly landed on
the originating side of the connector rather than on the target side.
This only happened when the MultiLoc was a peer of the target. The
thrown object now correctly lands within the target's top-level
location rather than within the origin's top-level room.
The ComplexContainer class now refers the methods isOpen, isLocked,
makeOpen, and makeLocked to the subContainer, if it's non-nil. A
complex container appears in the game world as effectively enclosing
the contents of its sub-container, because the sub-container doesn't
usually appear as a distinct object in the game world, so the apparent
open and locked status of the complex container are almost always the
same as for its sub-container. These new referrals make it a little
easier to write code by removing the need to refer explicitly to the
sub-container when checking or changing the open or locked status of a
complex container.
An error in BasicOpenable.makeOpen() caused a run-time error ("wrong
number of arguments") in certain cases when a BasicOpenable was used
with multiple inheritance. In particular, the error ocurred when an
object derived from BasicOpenable also had at least one additional
superclass which itself defined makeOpen(), and the additional
superclass was placed after the BasicOpenable subclass in the object's
superclass list. This has been fixed.
Released 6/12/2004
The default rules in Thing and NonPortable for contentsListed and
contentsListedInExamine weren't quite right, so they've been changed
slightly. The new default rules are that an object's contents are
always listed when the object is directly examined, and they're
listed in "indirect" examinations if (1) the object is itself
mentioned somehow in the indirect description, and (2) its contents
would be listed in a direct examination.
(A couple of definitions might help. A "direct examination" is
what happens when the player explicitly EXAMINEs the object; an
"indirect examination" occurs when looking at the room, showing an
inventory listing, or directly examining an enclosing object. An
object is "mentioned somehow" in an indirect description if either (1)
it's included in the basic generated list of portable items within the
enclosing object that's being examined, as determined by the isListed
property, or (2) it shows as special description, as determined by the
useSpecialDesc method.)
The reasoning behind the new rules is as follows. First, on
directly examining an object, its contents should almost always be
mentioned; we'd want to conceal the contents only in unusual cases, so
the default for contentsListedInExamine is simply 'true'. Second,
when indirectly examining an object, we'd normally want its contents
to be included in the description, since they're also, indirectly, the
contents of the enclosing object being examined; however, when the
object itself isn't mentioned in the indirect examination, then we
probably don't want to mention its contents, either. Furthermore,
if for some reason we've chosen to conceal the contents even when
the object is directly examined, then we almost certainly want to
conceal the contents in indirect examinations as well.
The rule is slightly different, and simpler, in NonPortable:
contentsListed simply returns the value of contentsListedInExamine.
The reason for this different default is that a NonPortable is almost
always a fixed feature of its enclosing objects, and as such is
almost always mentioned, to the extent it merits mentioning, as part
of the custom description of the room or containing object. This is
sometimes accomplished with a specialDesc, but not always, so the
absence of a specialDesc isn't sufficient reason to think that the
object isn't mentioned. Since a NonPortable almost always gets some
kind of mention in indirect examinations, then, the default is that
its contents are listed in these cases, too, as long as they'd be
listed in a direct examination.
The Consultable class now requires that the object be both touchable
and visible for the Consult action (in the past, it only had to be
touchable). Consultables are typically things like books or maps
whose contents are read visually, so in most cases it only makes sense
to consult a consultable when it's visible. This change prevents
lookup up information in a consultable while in the dark, for example.
The new property disambigPromptOrder can be given to any VocabObject
(any simulation object with vocabulary words) to control its ordering
in an interactive disambiguation prompt ("Which book do you mean, the
red book, or the blue book?"). When a disambiguation prompt is shown,
the objects in the prompt are listed in ascending order of this
property value. By default, disambigPromptOrder is 100; since all
objects have this same default, if you don't override it for any
objects, the ordering of disambiguation lists will be arbitrary.
Arbitrary ordering is usually fine. However, it's nice to be able
to control the order when objects have ordinals in their names ("first
book," "third button," etc), to make the list order match the nominal
order. For example, if you have a group of books named "first book,"
"second book," and "third book," you could give these objects
disambigPromptOrder values of 101, 102, and 103, respectively.
When the parser processes a response to an interactive disambiguation
question ("Which book do you mean..."), it now takes a vocabulary
match to an ordinal name as superseding the alternative interpretation
as a list position. For example:
Which book do you mean, the red book, the first black book, or the
second black book?
>second
The parser will now interpret "second" as referring to "second black
book." In the past, it took this to mean the second item in the list,
which in this case is "first black book"; this is hardly ever what
the player means in these cases, though.
(This change is actually a bug fix. The parser already had logic
to apply this precedence order, but it didn't work properly. In
particular, the "adjective ending" preference - the preference for
avoiding a phrase that ends in an adjective - superseded the ordinal
preference. Adjective endings are obviously not a problem for
disambiguation responses, though, so this has been changed.)
When the parser asks the player for disambiguation help to choose
among several items that can be distinguished only by their location,
and some of the items are in different top-level locations from the
player character, the parser now describes the remote locations using
the remote room names (rather than with the default "floor" or
"ground" container, as was formerly the case). This helps ensure
that objects are distinguished properly when several top-level
rooms are connected by sense connectors.
Which coin do you mean, the one on the floor, or the one on the
balcony?
In a related change, the parser now chooses a "local"
interpretation of a locational qualifier in cases of ambiguity. For
example, if the player were to answer the question above with "the one
on the floor," the parser would now assume that the player is talking
about the coin in the local location (i.e., within the player
character's top-level enclosing room). In the past, if the balcony
also had a floor, the locational qualifier would have been ambiguous.
The new function dataTypeXlat(val), defined in the system library file
_main.t, returns the "translated" datatype for the given value. This
is almost identical to dataType(val), except that when 'val' is an
anonymous function, the new dataTypeXlat() returns TypeFuncPtr rather
than TypeObject. In most cases, it's convenient to treat an anonymous
function pointer value as though it were an ordinary static function
pointer, because the two types are almost interchangeable in their
usage; this function makes it easier to code such cases, because you
don't have to worry about these two different ways that a
function-pointer-like value might appear.
The new class AskTellShowTopic lets you combine responses for an
object for ASK ABOUT, TELL ABOUT, and SHOW TO in a single topic
response object. Players sometimes find SHOW TO to be more natural
than ASK ABOUT when an object is present and visible; this new class
makes it convenient when you want to handle either command the same
way.
A bug in Thing.lookAroundWithinContents caused an infinite loop when
attempting to describe a location in which three or more top-level
rooms were linked by sense connectors. This is now fixed.
In 3.0.6q, BulkLimiter defined lookInDesc as doing nothing. This was
problematic because BulkLimiter is inherited by some special types of
containers, such as Underside and RearSurface, that don't have
interiors for the purposes of LOOK IN. The problem showed up in
Underside and RearSurface objects: attempting to LOOK IN these
objects showed no reply at all, hence yielded the library's default
"Nothing obvious happens" message.
BulkLimiter no longer defines this property at all. Instead, this
functionality has been moved to Container and Surface. Furthermore,
BulkLimiter.examineInterior no longer shows the lookInDesc; instead
Container and Surface now show the lookInDesc as part of their
action() handler for the LOOK IN command.
The new Thing method hideFromDefault() is similar to hideFromAll(),
but indicates that the object should be hidden from being used as a
default for the given action (that is, it won't be considered as a
possible default when the player types a command that omits one or
more required noun phrases). By default, this simply returns what
hideFromAll() returns for the same action, since in general an object
that's hidden from ALL should also be ignored for defaulting.
This is a coarser-grained mechanism than using verify() routines
for individual actions, but it's more convenient when you want to
exclude an object from being used as a default for a large number of
actions.
gActionIs(a) now returns true if 'a' is one of the specific-direction
travel subclasses (NorthAction, SouthAction, etc), and the current
action is a base Travel action that's going the same direction. This
makes it possible to test for a NORTH command simply by testing
gActionIs(North), which is what you would have expected even in past
versions if you hadn't known better.
(In the past, it was necessary to write a test like this instead:
(gActionIs(Travel) && gAction.getDirection ==
northDirection). This two-part test was required because the grammar
rules for NORTH, SOUTH, etc. generate the base Travel action instead
of the specific-direction subclasses. The grammar rules still do
this, but gActionIs() now calls a new Action method, actionOfKind(),
which TravelAction overrides to encapsulate the two-part test; since
TravelAction does the test for you, there's no longer any need to
write it out by hand. You can still write the two-part test if you
prefer, and it still works the same as it did before, but it's easier
and clearer to just write gActionIs(North) and the like.)
The algorithm in Actor.tryMakingRoomToHold has been improved slightly.
(This is the routine that automatically frees up space in an actor's
hands, when the space is needed for the current explicit action, by
moving things being held into "bags of holding.") The change is a bit
heuristic, but it should work well in most cases. The new rule: when
there are at least four items that can be moved into bags of holding,
the routine will now find the two that were picked up most recently
and move them to the end of the list of possible items. In practice,
it's rare to have to put away more than one or two items for a single
action, so this change effectively ensures that the items picked up
most recently won't be put away automatically.
The point of this change is to avoid annoying situations where the
game automatically puts away an object that you just picked up in
order to do something with it. These situations were especially
annoying when they were entirely automatic (for example, the game
picked up an object automatically, then put it away automatically in
order to make room to pick up another object automatically, then had
to go get the first object out again in order to use it). It was
rare for this to happen, but it looked pretty silly when it did.
In a related change, the library now arranges the order of putting
things away to ensure that if object A goes inside bag of holding B,
and B goes inside another bag of holding C, then A will be put in B
before B is put in C. This was almost always the result anyway due
to affinity ordering, but proper nesting order is now explicitly
assured.
There are two new "illogical" status types that "verify" routines can
use to indicate the logicalness of an action with greater precision.
The library uses these new types when appropriate in its action handlers.
The new types are:
- illogicalAlready() - this indicates that the action is illogical
because the condition the command seeks to create is already in
effect. For example, we're trying to TAKE an object that the actor is
already holding, or we're trying to OPEN an object that's already
open. This new type is almost the same as illogicalNow(), but
provides more detail about the condition.
- illogicalSelf() - this indicates that the action is illogical
because it's attempting to use an object on itself in some invalid
way. For example, we're trying to PUT something inside itself. This
type is almost the same as illogical(), with the added detail that
it's the self-application that makes the command impossible to carry
out.
The Passage class now considers a closed passage to be invisible in a
dark room. The reasoning is that, when a passage is open, enough
light from the adjoining room (if the adjoining room is lit) comes
through the passage to make the passage itself visible, although not
enough comes through to light the current room; but when a passage is
closed, not even this small amount of light comes through the passage.
In the past, the parser matched a plural phrase (such as EXAMINE
BOXES) to the most likely subset of the matching in-scope objects, as
determined by the "verify" logicalness ranking. This was too
restrictive; it wasn't at all the common-sense interpretation that
most users would expect from a plural usage, which is simply to match
everything that matches the vocabulary.
This has been changed. There are two possible behaviors, which you
can control via a global configuration property,
gameMain.filterPluralMatches:
- If gameMain.filterPluralMatches is true, the parser excludes
objects from matching the plural phrase if their "verify" routines
include an "illogical-already" or an "illogical-self" result. This
means, for example, that TAKE BOOKS will exclude any books that are
already being held, DROP BOOKS will exclude any books that aren't in
the player's inventory. This is the default.
- If gameMain.filterPluralMatches is nil, the parser simply uses
all vocabulary matches for a plural.
The English parser now handles pronouns better in the recently-added
command phrasing TELL
actor TO
do something. In
particular:
- A reflexive pronoun that agrees in number and gender with the
actor will be treated as referring to the actor, as long as there's
not a valid interpretation internal to the predicate. For example,
TELL BOB TO HIT HIMSELF will treat HIMSELF as referring to Bob.
However, TELL BOB TO ASK BILL ABOUT HIMSELF will treat HIMSELF as
referring to Bill, since that interpretation is entirely
self-contained within the predicate.
- A regular pronoun that agrees in number and gender with the
actor will be treated as referring to the actor. This is especially
important with possessives: TELL BOB TO DROP HIS BOOK will treat
HIS as referring to Bob.
- A second-person pronoun is now considered to refer to the
player character, rather than to the target actor. For example,
TELL BOB TO HIT YOU is taken to mean that Bob should hit the player
character.
- The target actor is set as a pronoun antecedent for subsequent
commands from the issuing actor. For example, after TELL BOB TO GO
NORTH, the command FOLLOW HIM will mean to follow Bob.
The English parser now rejects the phrasing TELL first actor TO
TELL second actor TO do something. (It formerly
accepted this, but it didn't handle it as stated: in effect, the first
TELL TO was ignored, so the command was treated as simply TELL
second actor TO do something.)
The English parser now accepts the commands SIT DOWN and LIE DOWN.
The parser accepts these stated intransitively (i.e., without a
direct object), but treats them as synonyms for SIT ON (something
unspecified) and LIE ON (something unspecified), respectively. That
is, the parser tries to find a suitable default object to sit on, and
prompts the player for the missing object it no default can be
assumed.
In the English parser, when the command phrasing involves what looks
like an indirect object phrase that doesn't match anything in the
grammar, a "miscellaneous preposition phrase" production is matched.
This type of production is now more consistent about reporting that
"the story doesn't understand that command," to indicate that the
structure of the verb phrase was unrecognized. In the past, if the
the direct or indirect object phrases were themselves unresolvable,
the parser showed that error instead; this was often less useful,
because the root problem in these cases is really that the overall
verb phrasing can't be parsed.
The NOTE command now shows a warning if the transcript isn't
currently being saved (with the SCRIPT command). This command's main
purpose is to let the player embed a comment in the session
transcript, as a means of sending feedback to the author. There's
not a lot of point in using the command when the transcript isn't
being saved, so there's a good chance that a player using the command
thinks that a SCRIPT command is already in effect. The warning calls
the player's attention to this. The warning is only issued the first
time that NOTE is used without logging in effect, but this is reset
each time logging is started and then stopped.
In the past, real-time events created during pre-initialization
weren't set up properly, because the real-time manager's internal
clock wasn't initialized until run-time start-up. The clock now
has a static initializer, ensuring that events created during preinit
will have a valid time base.
Released 5/9/2004
Possible compatibility-breaking change: The standard parser
dictionary has been renamed from G_dict to cmdDict (for "command
dictionary"). The dictionary is simply an object, and the new name is
more consistent with other parser object names (in particular,
cmdTokenizer, the standard parser tokenizer).
It's relatively uncommon for game code to refer to the main
dictionary directly, but you should search your code for references
to G_dict and change the name to cmdDict.
Possible compatibility-breaking change: The methods that
describe an actor's location have been changed, in order to improve
consistency and to simplify the code structure. These changes are
mostly internal to the library, so they're unlikely to affect
existing code unless you've made some rather low-level customizations.
The changes involve a lot of renaming and rerouting of internal
method calls. The list of changes below is divided into groups of
related changes.
Group 1: The "nominal actor container" methods have been
consolidated into a single method.
- The methods getNominalStandingContainer,
getNominalSittingContainer, and getNominalLyingContainer have been
deleted. In their place is a single new method,
getNominalActorContainer, which takes the posture as a parameter.
Since it's almost never necessary to differentiate the nominal actor
container by posture, the single method approach is more convenient,
and it's also more readily extensible to new postures.
Group 2: The Posture object has been taken out of the loop,
as it were, for the methods that describe actors. The Posture
object's role in the past was to break out these various methods into
a separate version for each posture, and then further dispatch to the
actor's nominal location: for example, the okayPostureChange method
was split up into okayStand, okaySit, and okayLie.
The original idea was that locations might want to customize
extensively by posture, but in practice the multiplicity of methods
turned out to be more trouble than it was worth. With the present
change, each set of Stand/Sit/Lie variations is now consolidated back
into a single method, which makes the Posture object's role
unnecessary, allowing it to be removed from the dispatch chain.
It's still used in generating the final message, but in most cases
this is now a simple matter of getting the posture's participle
("sitting", etc) from the Posture object and using it to build the
message string.
The following list of changes will look daunting, but it's
unlikely that existing game code will be much affected, as these
methods were always meant mostly for internal use in the library.
- Actor.actorHereDesc changes:
- Posture.actorHereDesc and libMessages.actorHereDesc have been
deleted. Instead, the actor calls directly to roomActorHereDesc on
the nominal actor container.
- The BasicLocation, Floor, and libMessages methods
actorStandingHere, actorSittingHere, and actorLyingHere have been
deleted, and replaced by the new method roomActorHereDesc.
- The libMessages methods actorHereStandingOn, actorHereSittingOn,
and actorHereLyingOn have been deleted, and replaced by the new method
actorInRoom.
- Actor.actorThereDesc changes:
- BasicLocation, Floor, and libMessages provide definitions of the
new method roomActorThereDesc.
- NestedRoom.roomRemoteActorDesc has been renamed to
roomActorThereDesc, and the implementation has been moved from
NestedRoom to BasicLocation.
- libMessages.nestedRoomRemoteActorDesc has been renamed to
actorInRemoteNestedRoom, and libMessages.roomRemoteActorDesc has
been renamed to actorInRemoteRoom.
- Room status name changes - these affect the mechanism that
generates room title addenda such as "(sitting in the chair)":
- Posture.statusPosture has been deleted. Instead, the (newly
renamed) Actor.actorRoomNameStatus now handles this directly.
- Actor.statusPosture has been renamed to actorRoomNameStatus, to
clarify and reflect the broader potential of its purpose.
- The BasicLocation, Floor, and libMessages methods
statusStanding, statusSitting, and statusLying have been deleted,
and replaced by the new method roomActorStatus.
- The libMessages methods statusStandingOn, statusSittingOn,
and statusLyingOn have been deleted, and replaced by the new
method actorInRoomStatus.
- Actor posture description changes:
- Posture.actorPostureDesc has been deleted. Actor now handles
this directly.
- The BasicLocation, Floor, and libMessages methods actorStandingDesc,
actorSittingDesc, and actorLyingDesc have been deleted, and replaced
with the new method roomActorPostureDesc.
- The libMessages methods actorStandingOnDesc, actorSittingOnDesc,
and actorLyingOnDesc have been deleted, and replaced by the new
method roomActorPostureDesc.
- Posture change acknowledgment changes - these affect the mechanism
that generates the default responses to commands like SIT and STAND:
- Posture.okayPostureChange has been deleted. Actor now handles
this directly.
- The BasicLocation and Floor methods okayStand,
okaySit, and okayLie have been deleted, and replaced by the new
method roomOkayPostureChange.
- The libMessages methods okayStandMsg, okaySitMsg, and okayLieMsg
have been replaced by the new method okayPostureChangeMsg.
- The playerActionMessages and npcActionMessages methods
okayStandOnObjMsg, okaySitOnObjMsg, and okayLieOnObjMsg have
been deleted, and replaced by the new method roomOkayPostureChange.
- Changes to the mechanism that mentions that an actor is
sitting/lying/standing in a nested room, as part of the EXAMINE
description of the nested room:
- Posture.listActorPosture has been deleted. Actor.listActorPosture
now calls the nominal actor container directly instead.
- The BasicLocation and Floor methods listActorStanding,
listActorSitting, and listActorLying have been deleted, and replaced
by the new method roomListActorPosture.
- The libMessages methods listActorStandingOn, listActorSittingOn,
and listActorLyingOn have been deleted. Their role has been subsumed
by actorInRoom.
Group 3: Actor "grouping" is now handled in BasicLocation,
rather than only in NestedRoom.
- The implementation of listWithActorIn() that was formerly in
BasicChair has been moved to NestedRoom. This means that actors
without special descriptions are now grouped by default with other
actors without special descriptions who are present in the same
location and posture.
- NestedActorGrouper has been renamed to RoomActorGrouper, since
it's now used for any room with actors to be grouped, not just nested
rooms.
- In order to accommodate the wider range of cases where actor
group lists are now generated by default, RoomActorGrouper now
determines two additional bits of information: it gets the nominal
actor container for the location containing the actors being listed,
and it checks to see if the point-of-view actor is viewing the
listing from a "remote" location (a separate top-level room
connected by a sense connector). It uses this information to generate
the group prefix and suffix messages differently than before:
- If the nominal actor container is visible, the grouper calls the
new methods actorInGroupPrefix() and actorInGroupSuffix() on the
nominal actor container to generate the prefix and suffix messages.
BasicLocation and Floor provide suitable default definitions of these
new methods.
- If the nominal actor container isn't visible, but the actors are
in a remote location, the new libMessages methods
actorThereGroupPrefix() and actorThereGroupSuffix() are used.
- Otherwise, the new libMessages methods actorHereGroupPrefix()
and actorHereGroupSuffix() are used.
- Actor.actorListWith now returns an empty list if 'self' overrides
specialDesc. This ensures that a specialDesc defined on the actor
will take precedence over any list group applied by the location.
(This is more important now that BasicLocation applies actor grouping
by default.)
- Actor.actorHereListWith has been renamed to actorListWith. This
method is used whether the actor is local, distant, or remote, but
the old name incorrectly suggested the existence of a separate
actorThereListWith in parallel with the actorHereDesc/actorThereDesc
pair.
Group 4: Miscellaneous minor changes to the generation
of remote room actor descriptions.
- NestedRoom.roomRemoteActorDesc now generates a more elaborate
message by default, describing not only the actor's immediate
location, but its outermost visible location as well. If the actor's
outermost visible location isn't the same as 'self', the method now
invokes libMessages.nestedRoomRemoteActorDesc, which generates a
message of this form: "Bob is in the dining room, sitting in the
chair."
- The method inRoomName that was formerly defined in Room is now
defined in Thing instead. This ensures that a suitable default
remote room name will be generated for nested rooms as well as
top-level rooms. In addition, the method now simply returns
actorInName by default, which ensures that the preposition used in
the default remote room name will match the one customized via
actorInPrep, if necessary.
The new TravelConnector method connectorGetConnectorTo() gives a
travel connector a chance to scan any "secondary" connectors it knows
about for a connection to a given destination. In some cases, a
travel connector encapsulates other travel connectors; these are
secondary connectors in the sense that they're linked to the room only
from the first connector, and thus the room can't find them on its
own. This new method provides a way for a room to query a connector
for any secondary connectors it knows about, in order to find a
connector to a given destination. The base TravelConnector class
provides a default implementation suitable for most subclasses,
and AskConnector overrides the method to search its list of
secondary connectors. Thing.getConnectorTo calls the new method
when searching for a connector to a given destination.
The immediate practical benefit of the new method is that it makes
it possible for scripted NPC travel to traverse AskConnectors. In the
past, an NPC encountering an AskConnector was typically unable to
proceed, since the AskConnector didn't directly link to the
destinations of its underlying connectors, and the underlying
connectors typically weren't linked directly as directions to the
enclosing room. This made it impossible for the scripted NPC to find
a suitable connector, and thus blocked the scripted travel.
The properties for choosing whether or not to list the contents of an
object to list in EXAMINE and LOOK AROUND descriptions have been
expanded slightly to provide more control and better consistency.
These changes are all in the Thing class.
The isListed property now returns, by default, the value of
isListedInContents. The meanings of these properties haven't
changed: isListedInContents controls whether or not the object is
listed when examining the object's direct container, whereas
isListedInContents controls listability for examining indirect
containers, including the enclosing room. The reason for this change
is that it's rare that we'd want an object to be unlisted when its
direct container is examined, but still want it to be listed when
enclosing containers are examined (whereas the reverse isn't entirely
uncommon, since we sometimes want to mention an object only as a
detail of its direct parent, but not of the broader room context).
The new property contentsListedInExamine lets you control whether
or not an object's direct contents are listed when the object is
examined directly. In the past, you could do this only by overriding
examineListContents, but the new property makes it easier to control
this behavior. The new property returns the same thing that
contentsListed used to return.
The contentsListed property now returns the value of
contentsListedInExamine by default. This is parallel to the change
to isListed.
The new method getListedContentsInExamine() returns the list of
direct contents that should be listed when the object is examined.
This new method is the direct-examination equivalent of the existing
getListedContents() method, and returns the same value that
getListedContents() formerly returned: the subset of direct contents
that appear in the sense info table passed to the method.
The getListedContents() method now simply returns the result
of calling getListedContentsInExamine().
The method examineListContentsWith() now calls
getListedContentsInExamine() to obtain the list of direct contents to
list when examining an object. In the past, this used the 'contents'
property. The method also checks the value of
contentsListedInExamine, and skips the entire listing if this
property is nil.
These changes are all designed to be compatible with existing
code. They should simply add more flexibility and make certain
behaviors easier to customize, but existing code shouldn't be
affected.
In 3.0.6p, the library started listing the visible, portable contents
of "remote" rooms as part of each room description. The
implementation had a problem when MultiLoc objects were involved,
though: if a MultiLoc object appeared in more than one of the
connected top-level rooms
and had portable contents, those
contents were listed several times - once for each connected
top-level room in which the MultiLoc appeared.
This problem has been corrected: the room description generator
now ensures that a MultiLoc's contents appear only once. To do this,
the room description code now excludes each object that is also
located in the "local" top-level room or in any connected
remote room whose contents listing has already been generated.
NestedRoom now overrides roomRemoteActorDesc() to describe the actor
as being in the nested room. In the past, if the nested room's
location was visible, the nested room deferred to the location to
provide the description. This produced misleading messages, because
it described the actor as being in the outermost visible room
containing the actor - which was technically true, but left out the
important information about the intermediate nested room.
Noise and Odor objects no longer include themselves in LISTEN TO ALL
and SMELL ALL, respectively. These objects formerly did include
themselves in ALL for these commands, since the commands are
obviously highly applicable. However, because these objects are
intangible, it usually doesn't make sense from the player's
perspective to consider them in ALL, which a player usually thinks of
as applying to the set of discrete, visible objects. In practice,
including these in ALL was often problematic because these intangible
objects are usually not given names or vocabulary; they usually exist
only to model a feature of their source object, and aren't meant to
look like separate objects to the player.
Along the same lines, Vaporous objects are now included in
EXAMINE ALL, SMELL ALL, and LISTEN TO ALL. These objects do have a
visible presence, so it makes sense to include them in commands that
examine everything in sight with any sense.
For particular Noise and Odor objects that you do want to
participate in LISTEN TO ALL and SMELL ALL, just override
hideFromAll() to return true for the desired action or actions.
NonPortable now overrides the inherited Thing verify() handling for
EXAMINE to make an un-held NonPortable as likely as a held Thing for
disambiguation purposes.
Recall that, by default, Thing downgrades the disambiguation
likelihood that EXAMINE refers to an object not being held. The
reasoning is that an object being held is physically closer to the
actor, so in a real-world situation, it would be more convenient to
closely inspect an object being held than one sitting nearby. Since
NonPortable objects can't be held, though, this reasoning breaks down
somewhat. The change removes the disambiguation disadvantage for
NonPortable objects.
The objHeld precondition uses a new Thing method,
meetsObjHeld(actor), to determine if the object meets the condition.
This new method is called instead of isHeldBy(actor) on the given
object. This change allows an object to indicate that it's not
actually being held by an actor, but that it's as good as held
for the purposes of the objHeld condition. It's rare for
meetsObjHeld() to return anything other than isHeldBy(), but does
happen occasionally. The most obvious case is body parts: these
can't be considered to be held in the actor's hands (unless the actor
is specifically doing so for some reason), but at the same time,
there's no reason the actor should have to hold a body part in order
to carry out actions with objHeld conditions.
The Wearable class now handles SHOW TO specially: if the object is
being worn by the actor doing the showing, it doesn't require that it
be held. In the past, the objHeld condition was applied, as it is by
default to any portable object, which meant that the character had to
take off a wearable in order to show it to someone; this doesn't usually
make a lot of sense, thus the change.
In Thing, remoteSpecialDesc(pov) now calls distantSpecialDesc by
default; distantSpecialDesc in turn calls specialDesc by default. In
the past, remoteSpecialDesc called specialDesc directly by default.
This change leaves the default behavior the same as before, but it
simplifies the common case where the "distant" and "remote"
descriptions are the same by letting a single override -
distantSpecialDesc - handle both descriptions.
Similarly, remoteInitSpecialDesc(pov) now calls
distantInitSpecialDesc by default (rather than initSpecialDesc, as it
did in the past).
Room now treats the GetOutOf action as equivalent to Out. (This
means that if the room has vocabulary, the player can now say "get
out of (room)" as a synonym for "out".)
The English verb grammar now accepts LEAVE as a synonym for EXIT.
The English parser now accepts "A" and "T" as abbreviations for ASK
and TELL, respectively, in the full phrasing with ABOUT. That is,
you can now say, for example, A BOB ABOUT BOOK. In the past, the A
and T abbreviations could only be used in the super-short forms of
the commands, where only the topic could be specified (as in T BOOK).
The English parser now accepts the formats ASK actor TO
command and TELL actor TO command as equivalent
to the traditional actor, command format for telling an
actor to do something. The new ASK TO and TELL TO formats are simply
syntactic variations; they're otherwise handled exactly the same way
as the traditional actor, command format.
In the English library, SpecialTopic now allows a variation on the
input format to accommodate players who are thinking in terms of
ASK/TELL. Now, when a SpecialTopic is active, and the player's input
doesn't match the special topic's input pattern,
and
the user's input starts with "A" or "T" as a separate word, the
SpecialTopic checks for a match to the rest of the player's input
(i.e., the part following the "A" or "T"). For example, if the
special topic is "apologize for forgetting the anniversary," and the
player types T ANNIVERSARY, the special topic will try dropping the
"T" and matching just the "ANNIVERSARY" part.
This change is designed to accommodate players who are accustomed
to the standard ASK/TELL format, and who might not realize that the
special topic list shows commands meant to be entered literally, or
who are just so in the habit of using ASK/TELL commands that it
doesn't occur to them to leave out the "A" or "T" part. Since an "A"
or "T" command pretty clearly indicates that the player's intent is
to communicate with the NPC, it makes sense to check the topic the
player is trying against any active special topics.
Note that the special topic parser only accepts the
super-abbreviated formats - the "A" and "T" commands. This is
because it seems much less likely that it would even occur to a
player to try a special command with the full ASK ABOUT or TELL ABOUT
phrasing. Once the command is expanded to the full phrasing, the
detailed syntax of the special command suggestions should make it
fairly obvious to the player that the ASK ABOUT or TELL ABOUT part
isn't needed. In contrast, since the abbreviated "A" and "T" formats
are ungrammatical to start with, it would seem perfectly natural to
use them with arbitrary special command suggestions.
The RoomPart class now downgrades its logicalness for Examine
commands, in the same way that Decoration does. Room parts are
generally so much background noise, included only in case a player
explicitly refers to them. Downgrading the logicalness helps prevent
the room parts from causing nuisance disambiguation questions.
Due to a parser bug, the phrase ALL IN container (which can
also be written ALL FROM, ALL THAT'S IN, etc) incorrectly ignored the
hideFromAll() status of the objects in the container when determining
which objects to match to the phrase. This has been corrected; the
phrase now respects the hideFromAll() settings of the objects
involved.
The English parser now matches words it has parsed as "literal
adjectives" to ordinary adjectives in the dictionary when resolving a
phrase in "global scope." This change is the literal-adjective
equivalent of the
similar changes
involving noun phrases in global scope introduced in 3.0.6p.
Noun phrases that consist entirely of a "literal adjective" word are
now marked as "adjective-ending" phrases for the purposes of ranking
grammatical pattern matches during parsing. (The omission of this
flagging in the past was an oversight.)
Due to a bug, the removeEvent() method of the RealTimeEvent class
(the common base class for real-time fuses and daemons) didn't work
properly. This has been corrected.
A bug in the Lister class caused in-line contents listings to use the
"long list" format (i.e., with semicolons as the separator between
items) if the enclosing list did. An in-line contents list is set off
by parentheses, so it stands as an independent list and thus should a
long-list format only if it would need to on its own, irrespective of
the needs of the enclosing main list. (At least, the parentheses are
used in the English library; other languages might use different
notation, but in any case should use a notation that makes the
contents list similarly independent of the enclosing list.)
A couple of minor changes to the default LOOK IN handling make it
a little easier to customize the behavior, and reduce the need to do
so.
First, Thing has a new property, lookInDesc, that gives a message
that's displayed by default in response to LOOK IN and SEARCH. This
shows the standard default ("there's nothing unusual in it") if you
don't provide a custom message. It happens frequently that a game
wants to depict an object as having some interior features, but
doesn't want to actually model those interior features as separate
objects. The new lookInDesc makes this more convenient, since you
only need to provide the custom message in this property, rather than
overriding the entire dobjFor(LookIn) handling, as was necessary in
the past.
Second, Decoration now treats LOOK IN as logical by default (in the
past, this was handled by the catch-all routine that makes most
actions respond that the object is "not important"), and applies the
same logical-rank downgrade applied to EXAMINE. Correspondingly,
the default lookInDesc for Decoration displays the object's "not
important" message. This means that Decoration objects act by
default as they did before, displaying "that isn't important" in
response to LOOK IN, but can be given a custom LOOK IN response
simply by setting lookInDesc, just as with any Thing.
Third, the English phrasing for finding nothing in an object
specifically searched with LOOK IN or SEARCH has changed slightly, to
report that there's nothing "unusual" in the object. In many cases,
an object is described as having what would in the real world be
interior features, but those interior features aren't modeled as
actual game objects and hence don't show up by default in response to
LOOK IN or SEARCH. The slight change in phrasing helps make the
response at least technically defensible. It's probably still better
in most cases to customize the LOOK IN response anyway, but at least
the default response is a little less jarring in these cases.
Decoration now treats READ as logical by default, the same as it
does EXAMINE. (As mentioned above, LOOK IN now has the same treatment
as well.)
In the Thing class, PUT X IN X and PUT X ON X now respond with custom
messages ("you can't put the X in/on itself"). In the past, the
catch-all message indicating that X isn't a container/surface was
used, which is a less conspicuous problem than attempting to put
something in or on itself.
In the English library, the
plural-to-noun match expansion
introduced in version 3.0.6p is now limited to "global scope," just as the
noun-ending-to-adjective-ending
match expansion already was. English plurals are usually formed
by adding "s" or "es" to the end of a word, so when a noun is six
letters or longer, it usually looks like a truncated form of its plural;
this interacted with the plural-to-noun match expansion to be overly
aggressive in upgrading phrases to a plural interpretation. The
match expansion is only really important in global scope anyway (for
the reasons why, see the explanation of the
noun-to-adjective expansion),
so this change maintains the primary benefits of the match expansion
while avoiding the weird cases that can come up in local scope.
The parser now considers an indefinite noun phrase to be less
desirable, for grammar selection purposes, than a definite
interpretation of the same phrase. Object names can occasionally look
like indefinite phrases: an elevator button labeled "1" could be
called a "one button," for example, and a subway might have an "A
train." When there's an interpretation that exactly matches the
phrase to the name of an in-scope object, the parser will now prefer
that interpretation over one that treats a word like "a" or "one" as
signifying that an arbitrary match to the other words should be
chosen.
A library bug that caused a run-time error in
Actor.findVisualObstructor() has been corrected.
The library now executes "prompt daemons" (the special type of daemon
that runs before each command prompt, rather than when the turn
counter changes) before every prompt, rather than just before
a main command prompt. This ensures that various miscellaneous tasks
are performed whenever the game pauses for user input, even when
input is requested in the course of processing a command.
Released 4/25/2004
Compatibility-breaking change: The property initDesc, and the
related properties, have been
renamed. Games that define or
use these properties will have to change to use the new names. You
should search all of your existing source for the old names, and
replace each one with the corresponding new name.
Important: you must search for "initDesc" and
replace it with "initSpecialDesc" first. This is required
because the former "initExamineDesc" has been renamed to "initDesc" -
so you have to make sure you change all the old "initDesc" instances
before you rename "initSpecialDesc" to create new "initDesc"
instances. Please do your search-and-replace operations in the order
shown below:
- change "initDesc" to "initSpecialDesc"
- change "useInitDesc" to "useInitSpecialDesc"
- change "obscuredInitDesc" to "obscuredInitSpecialDesc"
- change "distantInitDesc" to "distantInitSpecialDesc"
- change "initExamineDesc" to "initDesc"
- change "useInitExamineDesc" to "useInitDesc"
The purpose of this change is to make the naming more consistent.
The old usage - "initDesc" for a special description and
"initExamineDesc" for the EXAMINE description - was inconsistent with
the respective non-initial properties, "specialDesc" and "desc". The
qualification was reversed: "specialDesc" was qualified as special,
leaving the EXAMINE description unqualified as simply "desc", while
the initial descriptions qualified the EXAMINE desription rather than
the special one.
Minor compatibility-breaking change: The Attachable class's
scheme for explaining why the object can't be attached to a given
other object has been changed. In the past, the method
cannotAttachMsg(obj) returned a message string or
actor-action-messages property, but this created a conflict with
another message property of the same name, which is used by Thing. To
resolve the conflict, the Attachable method has now been renamed to
explainCannotAttachTo(obj), and rather than returning a message to
display, the new method simply displays the message itself.
If you have any Attachable objects that define cannotAttachMsg,
you must make two changes to each. First, rename cannotAttachMsg
to explainCannotAttachTo. Second, rather than returning a string,
display the string directly.
Possible compatibility-breaking change: The conversation
mechanism has some internal changes to support deferral across
hierarchy levels (
see below).
- In TopicDatabase.findTopicResponse(), the former first
parameter, 'topicList', has been dropped, and two new parameters have
been added: 'convType', giving the conversation action type; and
'path', giving a list of the inferior topic databases that will be
searched for the topic if it's not found in the current database
(i.e., 'self').
- In TopicDatabase.handleTopic(), the former first parameter,
'topicList', has been dropped; and two new parameters have been added:
'convType' and 'path'. These have the same meanings as above.
- In ConvNode.handleConversation(), the new parameter 'path'
has been added. This only affects the ConvNode instance of
this method - the method of the same name in Actor and ActorState is
unchanged.
Note that the TopicDatabase methods findTopicResponse() and
handleTopic() no longer need the topic list parameter because this
information can be obtained from the new convType parameter.
These methods are mostly for internal use by the library, so the
likelihood that your existing game code will be affected is small.
Even so, you should scan through your code for these methods and make
the appropriate adjustments.
Important: note that there are two related but independent
methods called handleTopic(). There's the TopicDatabase method,
which is affected by this change, and there's the separate
TopicEntry method, which is not affected. When you're
scanning your code, you only need to change the TopicDatabase
version. It's easy to tell which is which at a glance: the
TopicDatabase version takes three parameters, while the
TopicEntry version only takes two. When you're scanning your
code, only change the three-parameter instances.
Additional technical notes: you'll only need to read this
part if you're making some rather advanced overrides to the library.
Please skip this part unless you find something that you're not sure
how to handle in the search-and-replace step above.
If your code contains calls to any of the affected methods, and
you're wondering what to pass for the convType parameter, you should
first check to see if you're receiving that value as a parameter to
the calling code; if so, just pass that parameter down. This is
usually easy to spot at a glance. If you have code like this:
handleTopic(self.(convType.topicListProp), actor, topic);
then you can simply change it to this:
handleTopic(actor, topic, convType, nil);
The important thing to note here is that you're already using the
convType parameter to look up the topic list in the old-style call;
you can simply drop the topic list lookup and pass the convType
parameter itself instead (but note that it moves to the last
position).
If you don't have a convType parameter passed in from your caller,
you'll need to choose which one to use. Look at which topic list
you're passing, and think about what conversational action is
triggering your code. Then scan through the list of xxxConvType
objects (askAboutConvType, tellAboutConvType, yesConvType,
noConvType, etc) defined in actor.t, and choose one that (1) has the
same topic list property that you're already using, defined in its
topicListProp property, and (2) conceptually matches the action
you're performing. If you can't find anything appropriate, you might
simply need to define your own ConvType object for whatever extended
action you're defining; just create one following the same pattern
as the existing ones in actor.t.
You can usually just pass nil for the new fourth parameter ('path')
to handleTopic(), findTopicResponse(), and
ConvNode.handleConversation(). You only have to pass a non-nil value
when you're implementing a search through a hierarchy of topic
databases, in which case you'll have to pass a list of the inferior
databases in the hierarchy. The purpose of the new parameter is to
allow a higher-level database to defer to a match in a lower-level
database, if one exists, so at each level you must pass the remainder
of the "search path" of databases.
The above changes to the conversation methods are designed to
facilitate a new mechanism that allows a conversation TopicEntry in
one topic database to defer to a TopicEntry from an inferior
database. This is done through the new TopicEntry method
deferToEntry(entry): this method returns true if 'self' should defer
to 'entry', nil if not. 'entry' is always a non-nil TopicEntry
object from an inferior topic database.
Recall that topic entry databases for conversation are arranged
into a three-level hierarchy for each actor: at the top is the
ConvNode's list of topics, at the middle level is the ActorState
list, and at the bottom level is the Actor list. We find a matching
topic entry by scanning each level of this hierarchy in turn, and
taking the first match we find. So, if there's a ConvNode match for
a topic we're looking for, we use it, even if there's a match for the
same topic in the ActorState or Actor. The matchScore is irrelevant:
the three-level hierarchy always trumps the matchScore ranking. The
matchScore ranking is only used to choose among different matching
entries at the same hierarchy level.
The new deferToEntry() mechanism allows topic entries to defer
handling across hierarchy levels. Here's how the new scheme works:
before we start searching the hierarchy, we start by noting the
matching entry at each level. Then, we search each level from the
top down as before. At each level, though, we look at the winning
match, and ask it via deferToEntry() if it wants to defer to the
winning match from the next level down the hierarchy. If so, then
we ignore the match at that level. This means that we skip the
higher-level match and go straight to the lower-level handling.
The main purpose of this addition is to allow a DefaultTopic
entry to be a catch-all only for topics that aren't handled
explicitly at a lower hierarchy level. For example, to create a
DefaultTopic that defers to any non-default match at a lower
hierarchy level, add this to the DefaultTopic object:
deferToEntry(other) { return !other.ofKind(DefaultTopic); }
This tells the topic finder that if there's any match that's
not itself a DefaultTopic at a lower hierarchy level, then this
default should be ignored.
The English library template for Room has been extended, and the
default relationships that derive one type of Room naming property
from another have changed slightly. These changes should be fully
compatible with existing game code, and should make it easier
to customize the various kinds of room names.
The template for Room now takes the first string to be the
'roomName' property, the second to be 'destName', and the third to be
the 'name' property. The 'desc' property is still in the last
position.
Room template 'roomName' 'destName'? 'name'? "desc"?;
In the past, the 'name' was the first entry, and 'destName' was
the second. The library took the 'roomName' to be the same as the
'name'. However, it makes more sense to do this the other way
around: the 'roomName' is now the one that's explicitly defined by
the game, and the 'name' setting is optional. If the 'name' setting
is omitted, it's derived by default by converting the 'roomName' to
lower case.
The 'roomName' is what's shown as the title of the room, in the
room description and on the status line. This is usually given in
"title case," meaning that each word has an initial capital letter,
with the exception of minor function words: "Hall of the Ancient
Kings". This is the format that games have defined all along, but
in the past, they defined it using the 'name' property. The change
improves matters, because it allows the room's ordinary 'name'
property to be entered separately, in a format suitable for use
when the name is needed in library messages: "You can't have the
ice cave."
The default 'name' derivation - converting the 'roomName' string
to lower case - gives decent results a lot of the time. However, in
many cases, you'll want to customize the ordinary name separately.
The template makes this easy by adding a slot for the 'name' property
separately from the 'roomName'.
There's one additional change to the default derivations. In the
past, the 'destName' didn't have a default at all. Now, the default
is the room's 'theName', which is itself derived, by default, by
prepending the ordinary name with 'the'. As before, the template
lets you supply a separate, custom 'destName' value.
The MultiLoc object now transmits sense information to and from
its contents in a more consistent fashion. In the past, putting
things inside a MultiLoc had somewhat inconsistent results; the
behavior should be more predictable now.
The primary design principle of a MultiLoc hasn't changed: a
MultiLoc is a single object that appears in multiple locations, but
which doesn't provide any sense connection among the multiple
containers. A MultiLoc is a single object in the sense that
the same object appears, in its physical entirety, in each of its
containers.
In the past, a MultiLoc was essentially a "sense ceiling": sense
information propagated from outside the MultiLoc to its interior, but
didn't propagate from within the MultiLoc to its containers. This was
intended to prevent the MultiLoc from propagating sense information
among its containers, but it was too restrictive an implementation.
For example, if a MultiLoc contained a flashlight, the flashlight's
light didn't shine into the MultiLoc's locations. Similarly, if an
NPC was inside a MultiLoc, the NPC couldn't see or hear anything from
any of the MultiLoc's locations, and hence couldn't take part in a
conversation with the PC while the PC was outside the MultiLoc.
The change is that a MultiLoc is no longer a sense ceiling; it's
now more like a wall. Now, from a point of view inside the MultiLoc,
all of the MultiLoc's locations (and their contents, to the extent
that's appropriate) are visible. From the point of view of any of
the MultiLoc's containers, the MultiLoc and its contents (as
appropriate) are visible, but none of the MultiLoc's other containers
are visible. Light is similarly transmitted from inside the MultiLoc
to all of its containers, but not from one container to another. So,
for example, if a flashlight is inside a MultiLoc, it provides light
to every container of the MultiLoc; but if a flashlight is in one of
a MultiLoc's containers, the light doesn't make it through the
MultiLoc to its other containers.
The main effect of this change is that things located inside a
MultiLoc now behave the way one would expect them to. In the past,
the asymmetrical "sense ceiling" design led to some odd behavior when
putting objects inside a MultiLoc: an actor within a MultiLoc
couldn't carry on a conversation with someone outside, for example,
because the actor inside couldn't hear out beyond the MultiLoc.
With the change, MultiLoc should behave much more intuitively.
The ContainerDoor class now defines the isOpen method to return the
same thing as its associated container's isOpen method; and it now
defines examineStatus to display its associated container's
open/closed status.
A ComplexContainer can now be used as a bag of holding. Operations
involving the bag-of-holding features of a complex container are
simply passed redirected to the complex container's subContainer
object.
Certain object contents listings were grammatically incorrect when
showing a single item that had a plural name. For example, if an item
had the name "some coins," and it was the only item on a table, the
table description said "On the table is some coins."
The listers now take into account the "cardinality" of each item
listed, to ensure grammatical agreement in these cases. For English,
there are only two degrees of cardinality that matter: one, and many.
The English module therefore assigns a grammatical cardinality of 2 to
items whose isPlural property is set to true, 1 for all others; this
is done with the new Thing method listCardinality(lister). A lister
calls this new method to determine the cardinality of the list, so
that it can provide the correct information to the methods that
generate the messages.
The LISTEN TO command now lists not only the sound the object being
examined is making, but the audible sounds of its contents as well.
This ensures that sounds made by components of an object are properly
displayed when the enclosing object is examined, and also ensures that
a container that has a sound source inside will have its sound listed.
The SMELL command has been similarly enhanced.
As part of this change, the thingListenDesc and thingSmellDesc
messages have been moved from libMessages to playerActionMessages, and
renamed thingListenDescMsg and thingSmellDescMsg, respectively. These
messages are now shown as default description messages, necessitating
the move to playerActionMessages.
The Distant class now explicitly allows LISTEN TO, the same way
it allows EXAMINE.
It's now possible to create a SenseConnector that occludes some
objects from view, depending on the point of view. This is useful
for situations where a sense connector provides only a partial view
of another room. For example, consider two rooms with a window
between them, providing a view of one room from the other. Now,
suppose a bookcase is positioned with its back to the window. When
viewing the room with the bookcase from the other side of the window,
it's not possible to see the contents of the bookcase.
The new class, OccludingConnector, is a mix-in that you can
combine with SenseConnector to create a partially occluded sense
path. Put OccludingConnector before SenseConnector in the class
list. You specify which objects to occlude from view by defining the
method occludeObj(obj, sense, pov). 'obj' is an object to test for
occlusion; 'sense' is the sense being calculated; and 'pov' is the
point of view, which is usually the actor performing the command.
Your method must return true if the connector occludes the object,
nil if not.
To implement the window in our example above, we'd do something
like this:
OccludingConnector, SenseConnector, Fixture 'window' 'window'
"It's a small window. "
connectorMaterial = glass
locationList = [roomA, roomB]
occludeObj(obj, sense, pov)
{
/* from roomA, we can't see what's in the bookcase in roomB */
return (pov.isIn(roomA) && obj.isIn(bookcase));
}
;
OccludingConnector uses a new mechanism that can be used for more
extensive customization of the sense path calculation than
OccludingConnector itself enables. During each sense path
calculation, the library first builds a table of all of the objects
connected by containment to the point of view. (This table contains
all of the objects that can possibly be sensed, because sense paths
are based entirely on containment relationships.) The library then
calls clearSenseInfo() on each item to initialize it for the path
calculation.
In clearSenseInfo(), an object can register itself for an
additional notification at the end of the sense calculation. To
register for the notification, override clearSenseInfo(), inherit the
default code, and append 'self' to the notification vector:
clearSenseInfo()
{
inherited();
senseTmp.notifyList.append(self);
}
Now that the object is registered, it will be notified at the
end of the calculation, after the sense path to each object is fully
calculated, but before the final table of sense paths is constructed.
The notification method is called finishSensePath(objs, sense). 'objs'
is a LookupTable whose keys are the objects connected by containment
to the point of view; 'sense' is the Sense object of the calculation.
You can do anything you want to the sense table in
finishSensePath(). The sense path information for each object is
stored in the object's tmpXxx_ properties - tmpTrans_, tmpAmbient_,
tmpObstructor_, etc. To change an object's sense path, simply change
those properties. (OccludingConnector simply sets each occluded
object's path transparency, in tmpTrans_ and tmpTransWithin_, to nil
to indicate that the object cannot be seen.) You can make additional
objects visible by adding them to the lookup table and setting their
tmpXxx_ properties to the settings you want them to have.
Note that the notification step is required for performance
reasons. The library could call the notification method on
every object, which would simplify things by eliminating the need for
the extra line of code to register. However, very few objects are
likely to require this extra notification, and since sense path
calculations are performed very frequently, the library doesn't want
to call the notification method blindly on all objects. So, to avoid
adding the notification overhead to every object, the library
requires the rare objects that require the notification to register
themselves.
The logic that decides on the descriptions generated for distant and
obscured objects has changed slightly. The purpose of these changes
is to make it easier to customize the various ways an object is
described, and to make the selection of description more intuitive.
First, a new type of description has been added: the "remote"
description. There are three new remote description methods:
remoteDesc, for the EXAMINE description; remoteSpecialDesc, for the
special description; and remoteInitSpecialDesc, for the remote
initial special description. These new methods are used to provide
the examination and special descriptions when the object is viewed
from a separate top-level location. For example, if two top-level
rooms are connected by a window, so that an actor in one room can see
objects in the other room through the window, the remote description
is used to describe objects in the other room from the actor's
perspective.
Second, when choosing a special description (as part of a room or
contents listing), the logic is now as follows:
- If the object is in a separate top-level location from the actor
doing the looking, the remoteSpecialDesc is shown;
- otherwise, if the object is obscured, the obscuredSpecialDesc is shown;
- otherwise, if the object is distant, the distantSpecialDesc is shown;
- otherwise, the specialDesc is shown.
In the past, the sightSize was taken into account in selecting the
type of special description to show: the specialDesc was shown if the
object had a large visual size, regardless of the transparency of the
sense path. Now, the sightSize isn't considered at all. The reason
for the change is that the distantDesc and obscuredDesc are almost
always custom messages per object, so they will naturally take into
account the visual size of the object; considering the visual size
added an unnecessary extra dimension.
Note how the new remoteSpecialDesc fits into the selection rules:
the remote special description takes precedence over the obscured and
distant descriptions. That is, if the object being described is in a
separate top-level room from the actor doing the looking, the
remoteSpecialDesc is used, even if the object is also visually
obscured or at a distance.
Third, when choosing an examine description (when the object is
explicitly examined), the logic is now as follows:
- If the object is in a separate top-level room from the actor
doing the looking, and a remoteDesc method is defined for
the object, the remoteDesc method is used;
- otherwise, if the object is obscured, and an obscuredDesc
method is defined for the object, the obscuredDesc method is used;
- otherwise, if the object is distant, and a distantDesc method
is defined for the object, the distantDesc method is used;
- otherwise, if the object's details are visible (which will be true
even for a distant or obscured object with sightSize set to large), the
ordinary desc is used;
- otherwise, if the object is obscured, the defaultObscuredDesc
method will be used;
- otherwise, if the object is distant, the defaultDistantDesc
method will be used.
In the past, the 'desc' was used any time the visual details were
visible, even if the object was distant or obscured and even if the
object had a custom distantDesc or obscuredDesc. The change is that
the distantDesc or obscuredDesc will essentially "override" the
ordinary 'desc' when custom definitions are provided, regardless of
the visual size of the object. The reason for this change is to keep
the EXAMINE description selection rules consistent with the special
description rules; the rules are essentially parallel in the two
cases. The EXAMINE selection rules are slightly more complicated
because of the need to generate suitable defaults when a custom
obscured/distant description is not defined: by considering first
whether or not a custom description is available, we can decide to
show the custom distant/obscured description (if one is available),
the default ordinary description (if no custom description is
available and the details are visible), or a default
distant/obscured description (in any other case).
Note that the defaultObscuredDesc and defaultDistantDesc methods
are new. The default implementations simply display the same default
library messages that the default obscuredDesc and distantDesc
implementations displayed in past versions.
The logic for deciding whether or not to use the initial special
description and initial examine description for an object has been
refactored slightly. The refactoring yields the same default effects
as before, so existing game code won't be affected, but the changes
provide better control for overriding the default behavior.
First, the new Thing method isInInitState determines whether or
not to use the initial special and examine descriptions. By default,
this method returns true if the object has never been moved, nil if
the object has been moved. That is, the object is considered to be
in its initial state, for the purposes of descriptions, until the
first time the object is moved by an actor in the game.
Second, the new Thing method useInitExamineDesc() indicates
whether or not the initExamineDesc should be used when examining the
object. By default, this returns true if the object is in its
initial state, as indicated by isInInitState, and the object
defines a non-nil initExamineDesc. Since isInInitState by default
returns true if the object has never been moved, and nil otherwise,
the default behavior is the same as before, so existing games will
not be affected by this change.
Third, the existing Thing method useInitDesc() looks at
isInInitState to make its determination. Since isInInitState by
default returns true if the object has never been moved, this has the
same effect as in the past.
These changes make it easier to change the "never moved" logic
that determines whether or not to use initial descriptions, and also
allows you to control the use of the initial special and examine
descriptions independently of one another. For example, if you want
the initial examine description to be used one time only, rather than
to be based on whether the object has been moved, you could override
useInitExamineDesc() to base its determination on 'described' rather
than on 'moved'.
In the English library, Room has a new method that provides a
prepositional phrase that can be used to describe objects as being in
the room. The new method is inRoomName(pov), and it returns a string
that can be used to construct a sentence describing objects as being
in the room. 'pov' is the point of view; this might not be in the
same top-level room, as we could be viewing objects in this room from
the point of view of a separate room that's connected to our room by
a window or another sense connector.
Room descriptions now include listings of the portable objects
visible in separate top-level locations. In the past, only the
contents of the point-of-view actor's own enclosing room were
described; now, the contents of each connected top-level room are
described as well.
To generate the new remote room contents listings, the room
describer first determines the set of top-level rooms with items that
are visible from the point of view of the actor doing the looking.
The room describer shows the contents of the local room (the room
containing the point of view) first, using the same procedure it
always has. Next, the describer runs through the set of other
top-level rooms with visible contents; for each one, it generates a
separate remote contents listing.
Each remote room's contents listing is displayed using essentially
the same procedure used to generate the local room's contents
listing. However, instead of using the lister defined by the local
room's roomContentsLister property (which is set to the roomLister
object by default), each remote listing is generated using a lister
returned by the new method remoteRoomContentsLister(other). This
method is called on the point-of-view room, and 'other' is the remote
room whose contents are to be listed. By default, this returns a
lister that uses the remote room's inRoomName to generate the
description.
You can customize the listing of portable items visible in remote
locations by returning a custom lister from remoteRoomContentsLister().
Note that the new class CustomRoomLister makes this easy, if all you
want to do is to customize the prefix and suffix messages displayed
in the listing:
remoteRoomContentsLister(other)
{
return new CustomRoomLister('Through the window, {you/he} see{s}', '.');
}
Note that remoteRoomContentsLister(other) is called on the
local room - the room containing the actor doing the looking -
and 'other' is the remote room. This arrangement lets you
customize the listing in the point-of-view room, which makes sense in
that the overall description is of the point-of-view room.
When a room is described (as when entering the room, or examining it
with a LOOK AROUND command), everything visible from the room is
marked as having been seen. In the past, only visible objects that
were located within the actor's top-level enclosing location were
marked as visible; now, everything that's visible is marked as seen,
including objects in other top-level rooms that are visible from the
actor's location.
The library has an enhanced way of listing an actor's presence in a
room (as part of the room description) when the actor is visible in a
remote location (i.e., a separate top-level room) or at a distance.
In the past, there was no distinction between the normal case and the
distant and remote cases. Now, when the actor is in a remote room,
or at a distance, a different path is used to generate the message.
To accomplish this change, Actor now overrides remoteSpecialDesc
and distantSpecialDesc. On LOOK AROUND, the room calls the
appropriate one of these to describe the actor when the actor is at a
distance. (The normal selection procedure is used: the
remoteSpecialDesc is used if the NPC is in a separate top-level
location from the point-of-view actor, otherwise distantSpecialDesc
is used if the NPC is at a distance.) The new Actor implementations
of remoteSpecialDesc and distantSpecialDesc invoke the corresponding
new ActorState methods of the same names. These new ActorState
methods in turn call back to the new Actor method actorThereDesc
(both the distant and remote methods call this one new method). The
new actorThereDesc calls the new method roomRemoteActorDesc on
the actor's location. Thing provides a default
implementation of this new methods, which simply calls the same
thing on its the location if there's a location that's visible from
the point of view, or shows a default library message if not. The
default library message method is also called roomRemoteActorDesc.
This uses the location's inRoomName to generate the description.
This sequence of calls lets you hook in and customize the actor's
distant special description at whatever level is most convenient -
at the actor itself, at the ActorState, or at the containing room.
In most cases, it will probably be most convenient to customize this
in the room, to display a message customized to the appearance of
the room from a distance. To do this, you'd put a method like this
on the room:
roomRemoteActorDesc(actor)
{
"\^<<actor.nameIs>> over at the north end of the courtyard. ";
}
Note that the actor to describe is provided as a parameter, and
that the method is responsible for displaying the complete message
describing the actor's presence.
In the past, an EXAMINE command applied to a room was always treated
as a LOOK AROUND command; this didn't produce good results when the
room being examined was a separate room that the actor doing the
looking wasn't in (i.e., a room connected to the actor's location via
a sense connector of some kind). This no longer happens; when the
actor doing the looking isn't inside the room, the standard EXAMINE
handling is used, as though the room were any ordinary object.
Thing.lookAroundPov() now actually sets a global point-of-view object.
This ensures that the POV information can be obtained (via the getPOV()
function) when room descriptions are being shown.
Thing.addToSenseInfoTable() no longer includes the 'src' parameter.
Instead, the same information can be obtained from
senseTmp.pointOfView. (The parameter has been removed for effiency:
the information is rarely needed, and is available from this other
source when it is needed.)
The Thing method canDetailsBeSensed() now takes an addition parameter
giving the point of view. This is usually the actor performing a
LOOK AROUND, EXAMINE, or other command that's causing a description
of the object to be generated.
Thing.lookAroundWithinSense now marks each item with a "presence" in
the sense as known to the actor who's doing the looking. This
ensures that any noises and odors that are actively listed become
known to the actor as soon as they're listed.
Objects are marked as known rather than seen, because it might be
possible to hear or smell objects that can't be seen. Note that only
objects with a "presence" in the sense are marked as known; an object
with a presence in a sense is one that is actively calling attention
to itself in the sense, via a noise or odor message in the room
description. Thus, an object that isn't actually making any noise
worth listing doesn't become known just because an actor could hear
it if it were making noise; it has to actually be making some noise
(and say so by setting its soundPresence to true) to become known in
this manner. Note also that a noise and its source are frequently
separate objects (likewise with odors), and it's the noise
that becomes known in this arrangement, not the source. This is
important because it's often possible to hear the sound a thing makes
without knowing what's making the noise: we can hear a beeping coming
from inside a closed box, so we now know about the beeping, but we
can't tell that it's coming from an alarm clock, so the alarm clock
remains unknown to us.
By default, Thing now applies the objHeld precondition to a Show
action; this means that SHOW X TO Y requires X to be held if X is an
ordinary Thing. In addition, NonPortable only requires that the
object be visible. This change assumes that portable objects need to
be held up for someone else to look at, as though offering the object
to the other person, while non-portable objects can be merely pointed
out but left in place.
The Vehicle class now treats an OUT command while an actor is inside
the vehicle as meaning to drive or ride the vehicle out of the
vehicle's enclosing location. In the past, OUT in a vehicle meant
the same thing as in a nested room: GET OUT of the vehicle. The old
behavior didn't seem to match most players' expectations, and it had
the drawback that it left no standard way of expressing the intention
of driving/riding the vehicle out of its location.
For certain vehicles, it might still be desirable for OUT to mean
GET OUT. Fortunately, it's easy to override the default behavior on
a case-by-case basis - just override a vehicle's 'out' property to
return nestedRoomOut:
car: Vehicle
out = nestedRoomOut
// ...other properties...
;
In the past, RoomConnector and OneWayRoomConnector didn't apply the
proper preconditions to travel; in particular, they didn't properly
determine the required initial location for the travel, so they didn't
move an actor out of a nested room before attempting the travel. This
has been corrected; RoomConnector now uses the travel preconditions
formerly defined in RoomAutoConnector, so all RoomConnector subclasses
(including OneWayRoomConnector and RoomAutoConnector) now use the
correct preconditions.
Enterable and Exitable objects now add a "touchable" pre-condition to
travel through the objects. In the past, only the pre-conditions for
the underlying connector were applied; since some types of travel
connectors are not themselves physical objects, this occasionally led
to odd results. In particular, if an Enterable or Exitable was
visible but not touchable (for example, if the object was at a
distance, or separated from the actor by a window), it was possible
to enter it despite its being unreachable. This type of anomaly should
no longer occur.
The departure and arrival messages for NPC's now yield more pleasing
results in cases where an NPC arrives or departs from a visible
"remote" location - that is, a location not part of the PC's
outermost room, but connected to the PC's location by a sense
connector (such as a window between two rooms, or a
DistanceConnector). There are two cases where the departure and
arrival messages are enhanced.
First, when an NPC departs from or arrives at a remote location,
and the other end of the NPC's travel is out of sight, the library
adds the name of the remote location to the arrival or departure
message. For example: "Bob leaves to the east from the alley," or
"Bob comes down the stairs to the north end of the courtyard." The
name of the location is taken from the destName property of the NPC's
location. The location name is only shown when the location is
remote; when an NPC enters or leaves the PC's outermost room, the
traditional messages ("Bob leaves to the east," etc.) are shown. The
addition of the location name to the message helps clarify that the
NPC is moving around nearby and within sight, but isn't directly at
the PC's location.
Second, when an NPC starts and finishes its journey within sight
of the PC, the library omits the departure message entirely, and
displays a new type of arrival message. When the entire journey is
within the PC's field of view, we don't wish to describe the
departure and arrival as two separate steps, since the whole journey
can be described as a single operation; this is what the new type of
arrival message does. The new message is given by the new
TravelConnector method describeLocalArrival(), which the traveler
calls from its describeNpcArrival method when it determines that the
origin is visible to the PC. TravelConnector.describeLocalArrival()
in turn calls the new Traveler method sayArrivingLocally(), and the
default implementation of this new method simply displays the library
message given by libMessages.sayArrivingLocally(). The library
message displays a message of the form "Bob enters the alley." Note
that it might sometimes be desirable to customize the travel
connector's describeLocalArrival() method: you could say things like
"Bob climbs the stairs up to the loft," or "Bob squeezes through the
window into the kitchen," for example.
BasicLocation.cannotGoThatWay now obtains the message to display from
a new property of 'self', cannotGoThatWayMsg. By default, this
returns &cannotGoThatWayMsg, which is the library message property
for the default message for this case. You can easily override a
room's message when travel is not possible in a given direction by
overriding cannotGoThatWayMsg to return a single-quoted string with
your custom message.
The class GuidedInTravelState formerly defined each of the
sayDepartingXxx() methods separately to show the departing-with-guide
library message. Now, the class instead defines only sayDeparting()
to display this message. This creates the identical effect by
default, since all of the sayDepartingXxx() methods inherited from
GuidedInTravelState's base class (AccompanyingInTravelState) simply
call sayDeparting(). The advantage of this change is that it allows
GuidedInTravelState's travel message for all of these different
variations to be customized by overriding sayDeparting(), rather than
overriding each sayDepartingXxx() individually.
SpaceOverlay now checks the "moved" status of its "identity" object,
not its own "moved" status, to determine if it should abandon its
contents on being moved for the first time. This makes the operation
work properly when the overlay is a component of a ComplexContainer,
which will usually be the case; since only the parent complex
container is actually marked as moved, checking the status of the
child overlay object wasn't enough. Checking the identity object
ensures that the status of the larger object of which the overlay is
a part is properly taken into account.
The TravelPushable method beforeMovePushable() now takes a new
third parameter, 'dest', giving the destination of the travel.
SpaceOverlay now overrides beforeMovePushable() to call
abandonContents(). This ensures that if the object is a
TravelPushable, and it's pushed to an new location, the contents are
abandoned
before the actor moves to the destination location.
In the past, the object waited for the normal moveInto() notification
to abandon its contents; but this notification didn't arrive until
after the actor had already moved to the destination location, so the
actor wasn't able to see the contents being abandoned, thus we weren't
able to list the contents properly.
ComplexContainer also overrides beforeMovePushable(), passing the
notification down to any SpaceOverlay components it has. This
corrects the same problem for a ComplexContainer that's also a
TravelPushable, and which has SpaceOverlay components.
SpaceOverlay is now more careful about the way it reveals the
contents of the object when it's moved. In the past, it simply did
an appropriate kind of nested command (LOOK UNDER for an Underside,
for example) to generate the listing. This generally showed up
before the main report for the action, so when the main report was a
simple default report (such as "Taken"), it created confusion by
suggesting that the default report somehow applied to the last object
listed in the revelation:
>take box
Under the box is a rusty can. Taken.
There are two changes to the way this listing is generated.
First, the listing is no longer just a generic LOOK UNDER (or
equivalent). Instead, it uses a new lister, given by the
abandonContentsLister property of the SpaceOverlay object. Underside
uses undersideAbandonContentsLister by default, and RearSurface and
RearContainer use rearAbandonContentsLister. These new listers use
more specific language to describe the revelation, of the form
"Moving the box reveals a rusty can underneath" (or "behind").
Second, the SpaceOverlay takes care to move the listing so that it
follows the main report from the command that triggered the listing.
This ensures that the main report remains close to the command line,
which is required if the terse phrasing of the default reports is
to make sense.
There's one exception, though: if the actor performing the command
ends up in a different location at the end of the command, the
listing is not moved to the end of the report. Instead, the
listing simply stays at the beginning of the command. This exception
is important because travel usually triggers a description of the new
location, and once we're in the new location, the revelation of the
SpaceOverlay's contents will seem out of place. It's better in these
cases to leave the contents listing at the start of the reports.
With these two changes, the old listing above is transformed
into this:
>take box
Taken. Moving the box reveals a rusty can underneath.
If all of the changes described above to SpaceOverlay's revelation
listing are still not adequate for some special case in your game,
you can now easily suppress the default listing and generate your own
explicit listing instead, thanks to a couple of new features.
First, the new SpaceOverlay property 'neverListOnMove' lets you
suppress the default listing; just set this property to true, and no
listing will ever be generated when the overlay object is moved. If
the contents are to be abandoned, the abanonment will still happen,
but no automatic mention will be made of it. Your code is
responsible in these cases for generating any desired listing.
Second, the new method listContentsForMove() generates the
revelation of the contents in the default format. SpaceOverlay calls
this method itself to generate the automatic message when
appropriate. If you set neverListOnMove to true in order to suppress
the default revelation listing, you can call listContentsForMove() at
the appropriate juncture in your own code to generate a substitute
listing. Of course, you could instead completely customize the
listing with your own method; but if you only want to change
where the listing appears, without changing its contents,
listContentsForMove() makes that easy.
Note also that you can customize the standard listing format used
by providing your own lister, and setting the SpaceOverlay object's
abandonContentsLister to refer to your custom lister.
In the default Floor class, there were a couple of problems with the
commands SIT ON FLOOR, STAND ON FLOOR, and LIE ON FLOOR that have now
been corrected. First, when standing in a nested room, STAND ON
FLOOR incorrectly failed with the message "You're already standing."
Second, when already sitting, lying, or standing on the floor,
another command to do the same thing was incorrectly accepted; it's
now rejected with an appropriate message (such as "You're already
sitting on the floor").
If the player character was in a nested room, and typed a command to
leave the nested room, and immediately followed with an AGAIN
command, the AGAIN incorrectly succeeded, making it look like the
character was getting out of the nested room repeatedly without
getting back in. This has been corrected; an appropriate message
("you're not in the booth") is now displayed on the errant repetition.
In ActorTopicDatabase.initiateTopic(), the conversation is "noted"
only if an InitiateTopic is found to handle the request. Noting the
conversation means that we set the actor's last interlocutor to the
player character, so this change means that initiateTopic() won't
affect the actor's interlocutor unless a conversation is actually
initiated. (In the past, the routine always noted the conversation.
This caused odd side effects when initiateTopic() was called
speculatively, without knowing for sure that an InitiateTopic was
actually available, since the interlocutor changed even though no
conversational action was visible to the player. This change ensures
that the interlocutor will only change when a conversation actually
takes place.)
When an InConversationState object becomes the active state for an
actor, it remembers the previously active state so that it can, by
default, transition back to that previous state when the conversation
ends. In the past, the InConversationState class remembered
any previous state and used it as the new state at the end of
the conversation. This has been changed. Now, the class remembers
the previous state only if the previous state is a
ConversationReadyState, or there's no other previous state already
remembered. This change makes it easier for an actor to interrupt a
conversation with another activity, and later resume the
conversation, because the interrupting state will no longer "take
over" at the end of the conversation. You can still explicitly set
the end-of-conversation state explicitly to anything you want; this
change only affects the default state transitions that occur in the
absence of any explicit settings.
The HELLO and TALK TO commands now defer the implied topic inventory
until the end of the turn, if the response to the HELLO or TALK TO
contains any non-default reports. This is important because a
non-default report could set the ConvNode for the responding actor,
but such a change doesn't take effect until the transcript is fully
processed at the end of the turn. In the past, HELLO and TALK TO
always showed the topic inventory right away, so the inventory didn't
necessarily match the new ConvNode's active topics. With this
change, a ConvNode change within a HELLO or TALK TO will be properly
reflected in the topic inventory.
The new ConvNode property 'isSticky' lets you create a "sticky"
conversation node. By default, nodes are non-stick: this yields the
traditional behavior, where the NPC leaves the node if a response
doesn't explicitly stay there. (If the node is active, and the NPC
shows a response that doesn't set any new node, the actor's current
node is set to nil.)
If you set 'isSticky' to true for a node, the node will stay
active if the NPC shows a response that doesn't set a new node. In
other words, the "default next node" for any response while the
sticky node is active is the sticky node itself, rather than nil.
Sticky nodes are useful in cases where you want an actor to drive
a conversation along a particular thread, but you still want to allow
the player to digress by talking about other topics. As long as a
digression doesn't explicitly set a new node, the sticky node will
remain active.
DefaultCommandTopic now has a matchScore of 3, which makes the
library choose it over a DefaultAnyTopic if both kinds of default
topics are active for a given response. (In the past,
DefaultCommandTopic had the same matchScore as a DefaultAnyTopic, so
in cases where both kinds were active the library chose one
arbitrarily. The DefaultCommandTopic should always take precedence,
as it now does, because it's the more specific kind of response.)
AltTopic now takes its setting for the 'impliesGreeting' property
from its enclosing topic. This ensures that an AltTopic used within
a HelloTopic or the like will behave the same as its enclosing main
topic by default.
ByeTopic and ImpByeTopic now work together the same way that
HelloTopic and ImpHelloTopic work together. Specifically, ByeTopic is
now a catch-all that matches both explicit GOODBYE commands and
implied conversation endings. (Implied goodbyes happen when the
player character just walks away from a conversation, or the NPC gets
"bored" from lack of attention). ImpByeTopic still matches only
implied goodbyes; since it's the more specific of the two, it now uses
an elevated matchScore. This means that if both a ByeTopic and an
ImpByeTopic are active at the same time, the ImpByeTopic will be
chosen for an implied goodbye, and the ByeTopic will be chosen for an
explicit GOODBYE command.
This change makes it easier to program catch-all goodbye messages,
while still allowing differentiation when desired. To create a
catch-all message that handles both explicit and implicit goodbyes,
just create a ByeTopic. If you want to differentiate, create one of
each: the ByeTopic will be chosen for the explicit GOODBYE commands,
since the ImpByeTopic won't match those; and the ImpByeTopic will be
chosen for implied goodbye, because both will match, but the
ImpByeTopic's higher matchScore will ensure that it's selected over
the ByeTopic.
Along the same lines, HelloGoodbyeTopicObj now matches implied
as well as explicit goodbyes. (In the past, it handled implied and
explicit greetings, but only explicit goodbyes.)
Some of the behavior of InConversationState.endConversation has been
moved to the base ActorState class. This ensures that the active
ConvNode will be notified if the player walks away from a
conversation.
The library messages for scoring (showScoreMessage,
showScoreNoMaxMessage, showScoreRankMessage, showFullScorePrefix) now
refer explicitly to "you", not to the player character. Since the
score is a meta-game feature, it should be attributed directly to the
player, not to the player character. This is especially important in
games that refer to the player character in the first or third
person, since the distinction between player and player character is
more evident in these cases than it is in second-person games.
The library message for trying to put an object inside an another
object that already contains the first object (i.e., the "circular
containment" message) now reports the objects as specified, rather
than possibly showing an intermediate container. In the past, an
intermediate container was shown in some circumstances, especially
when a ComplexContainer was involved; this produced the wrong
messages in some cases.
In the past, the multiple object announcement ("red book: Taken") was
missing in in some cases where the action was remapped (via remapTo).
This has been corrected.
The implied action message generator now allows for "silent" implied
actions - implied actions that behave as usual but generate no
announcement text (the normal announcement is the message of the form
"(first opening the door)" shown at the start of the response to the
command).
First, the new libMessages method silentImplicitAction() can be
used in place of the standard announceImplicitAction() to generate
the announcement. silentImplicitAction() simply shows no message.
To use it, perform the implied action with a call like this:
tryImplicitActionMsg(&silentImplicitAction, Open, self);
Second, the lister that aggregates a set of implicit actions into a
single announcement message now accepts the empty messages generated
by silentImplicitAction(). This ensures that silent implied actions
can be mixed with announced implied actions in a single command, and
a sensible aggregate announcement will be displayed.
Thing.setContentsSeenBy() incorrectly marked the object's contents as
having been seen by 'gActor' rather than by the actor passed as a
parameter to the method. This has been corrected.
In PresentLater, when the object is made present (via the makePresent
method, or any of the related methods), the object and its contents
are specifically marked as seen by the player character, to the
extent the player character can actually see them at the moment they
become present in the location. In most cases, when an object comes
into play dynamically via the PresentLater mechanism, the sudden
appearance is the result of some specific event that the game will
describe through custom handling - that is, the game will
specifically mention the new object to the player, so the player
character should remember having seen the object.
In a two-object command (such as PUT VASE ON TABLE), the parser now
retains the meaning of a pronoun used in the command, regardless of
which object slot the pronoun is used in. For example, after the
command PUT VASE ON IT, the pronoun "it" is set to refer to the
indirect object, even though "it" would normally refer to the direct
object after a PUT X ON Y command.
In the past, the parser unconditionally set the pronoun antecedent
after a two-object command to the second-resolved object, which in
many such verbs is the direct object. So, after PUT VASE ON IT, the
pronoun "it" previously referred to the vase. Due to this change,
though, the explicit use of the word "it" directly within the command
causes meaning of "it" to be left unchanged by the command. This
change doesn't affect the behavior when no pronoun is used in the
command, so PUT VASE ON TABLE still causes "it" to refer to the vase
on the subsequent command.
ConsultTopic and DefaultConsultTopic now refrain from setting any
pronoun antecedents for the topic. This means that after any
consultation command (LOOK UP, FIND IN, etc.), the pronoun antecedent
will be the consultable object, not the topic last sought.
TopicTAction (used for commands such as ASK ABOUT and CONSULT ABOUT)
didn't work properly if a reflexive pronoun was used as the direct
object, referring back to the topic phrase: LOOK UP BOOK IN ITSELF,
for example. This showed a couple of symptoms, including "Nothing
obvious happens" responses and run-time errors. The problem has been
corrected; the action now looks to the topic phrase to resolve the
direct object, producing consistent and appropriate results.
When a command is directed to an NPC (as in BOB, OPEN DOOR), and the
parser has to prompt interactively for disambiguation or for a
missing noun phrase, the parser now treats gender-matched
third-person pronouns in the reponse as referring to the target
actor. For example:
>look at bob
He's wearing a red sweater.
>bill, examine
What do you want Bill to look at?
>his book
The book looks old and dusty; the title on the cover is faded
to the point of being unreadable.
In the past, the parser would have taken the "his" in the
interactive response to refer to Bob, since Bob would have been the
pronoun antecedent up until that point (by virtue of having been
mentioned in the previous command). With this change, the parser
takes "his" to refer to Bill. This fits with the phrasing of the
prompt text, and also with the fact that Bill was mentioned more
recently in a command (specifically, in the incomplete "examine"
command).
Similarly, reflexive third-person pronouns (HIMSELF, HERSELF,
ITSELF, THEMSELVES) in interactive responses are taken as referring
to the target actor, when the gender matches.
The parser is now more consistent in its treatment of truncated
vocabulary matches. (Truncated matches are words in the player's
command that match longer object names defined in the game, by
truncating the longer object names to the dictionary's minimum length.
For example, the default English dictionary truncation length is 6
characters, meaning that the player can shorten the word FLASHLIGHT to
FLASHL, FLASHLI, etc., when entering commands.)
In the past, the parser treated exact matches as stronger than
truncated matches for singular definite noun phrases (the most common
kind of noun phrase: TAKE BOOK, OPEN THE BOX). Any time the parser
matched some names exactly and other names only after truncation for
the same user input, the parser kept only the exact matches, filtering
out the truncated matches. This rule hasn't been changed; what's
changed is that it has now been extended to every kind of noun phrase,
including indefinite phrases (TAKE A BOOK) and plural phrases (TAKE
BOOKS).
In the English parser, when the parser matches a plural phrase (i.e.,
a phrase with a vocabulary word defined under the 'plural' property),
the parser now includes both 'plural'
and 'noun' matches for
the word. In the past, only the 'plural' matches were included.
In some cases, it's convenient to define a word that's
grammatically a plural under the 'noun' property, because a single
game object encompasses what looks to the user like a set of
something: a bookcase might define "shelves" as a noun, for example,
since the individual shelves aren't modeled as separate objects. This
change ensures that if other objects are in scope that define the same
word under the 'plural' property, the bookcase will still match the
word "shelves" when the noun phrase is resolved.
This change doesn't actually make much difference most of the time,
but it is frequently significant in "topic" phrases (as in ASK ABOUT).
Topic phrases have very wide scope, so a particular word can match
numerous objects from all over the game. In these cases, the old
scheme that included only 'plural' matches was overly exclusive,
causing the parser to omit objects from the topic resolution that the
author would at first glance have expected to be included. The new
more inclusive matching should reduce the potential for confusing
match exclusions in these cases.
The English parser is now more inclusive in matching nouns and
adjectives at the ends of "topic" phrases. (Topic phrases are used in
commands like ASK ABOUT and TELL ABOUT, where the player can refer to
physical objects as well as to abstract Topic objects, and can even
use random text that doesn't correspond to any game objects.)
Normally, when the last word of a noun phrase can match one or more
objects in scope that define the word under the 'noun' property, the
parser will ignore any additional in-scope objects that match the same
word under the 'adjective' property. For example, if there's a desk
in scope that defines 'desk' as a noun, and there's also a desk drawer
that defines 'desk' as an adjective, the parser will interpreter LOOK
AT DESK as referring to the desk, not to the drawer. However, if
there's a red book present, but there's no object in scope that
defines 'red' as a noun, the parser takes READ RED as referring to the
red book, even though BOOK was left unstated. This combination of
rules gives the player the convenience of abbreviating the name of an
object to a unique adjective, while avoiding spurious ambiguity in
cases like the desk-and-drawer exmaple.
In cases of "global" scope, though, this strategy isn't as
beneficial. When resolving a topic phrase, objects from all over the
game have to be considered "in scope" even though they're not
physically present, as it's perfectly reasonable for the player to ASK
ABOUT something that was seen earlier in the game but isn't physically
present at the time of the question. As a result, the elimination of
adjective bindings for a word can cause subtle problems that are very
difficult to track down, and which sometimes can't be easily fixed
without creating other problems. Two objects could happen to share
the same vocabulary words, in one case as an adjective and in the
other case as a noun, even though the objects are completely
unrelated. Thus, the existence of one object can change the way
another object's vocabulary words are treated when matching a topic
phrase.
The English parser now treats topic resolution differently. When
resolving a topic phrase, the parser no longer makes the strict
noun/adjective distinction. Instead, the parser considers both kinds
of vocabulary to match. So, if the player types ASK BOB ABOUT DESK in
our example with the drawer, both the desk and the drawer are now
considered possible topic matches.
This change has the potential to create a different sort of
problem, in that it could include too many possible matches for
a topic phrase. However, this is a much easier problem to deal with
than the old one, and in most cases won't even be noticeable as a
problem. In the first place, topic phrases don't require
disambiguation in the normal sense; the parser never asks the player
which object was intended, so the more inclusive matching won't
degrade the user interface by generating more disambiguation queries.
Second, it's much easier to understand why an extra object is matching
a given vocabulary word than to understand why a given object is
failing to match. A failure to match indicates that some other object
is defining the same word under another part of speech, but it doesn't
tell you which object or where it's defined; when too many objects
match, in contrast, you can easily see (using the debugger, for
example) which extra objects are matching, and make any desired
vocabulary adjustments. Third, in the vast majority of cases, the
extra matches are completely harmless and not even noticeable. Topics
are generally handled in conversations, and conversations have to
explicitly include responses for all of the topics they want to match.
If an extra object or two show up in vocabulary matching for a given
conversational command, it won't usually matter, because those
unexpected objects won't have topic matches in the conversation anyway
and will thus simply be ignored. In cases where you want a
conversation to distinguish among objects with similar vocabulary, you
can add the necessary distinguishing vocabulary to the objects just as
you would in cases of ordinary ambiguity, and you can tweak the topic
list (using match scores, for example) to control which response is
used for which vocabulary. In the rare cases where it's not
straightforward to fine-tune the matching using object vocabulary, you
can always resort to using regular expressions as the topic match
criteria.
Consider a situation where a given object is entered in the dictionary
with two words for a given part of speech, and one of the words
happens to be a leading substring of the other word, and both words
are long enough to trigger "truncated" matching (that is, each word is
at least six characters long, in the default dictionary truncation
configuration). For example, suppose a given object is entered in the
dictionary with 'noun' words for WINDOW and WINDOWSILL. Now, if the
player enters a command using the shorter word (WINDOW), the raw
dictionary match list will contain two matches for this object: one
for the exact match to the shorter word, and one for the truncated
match to the longer word.
In the past, this situation sometimes had undesirable results. In
most cases, the two matches to the same object were collapsed into a
single match, but the "match flags" from the two matches were combined
- so both matches were marked as truncated. Depending on what other
objects were in scope at the time, this had the effect of eliminating
the object from consideration as a resolution of the noun phrase: even
though it had an exact match in the dictionary, the resolver saw it as
a truncated match because of the combination of the two sets of match
flags. This was especially likely to affect topic phrases (ASK ABOUT,
for example), because the global scope made it much more likely that
other objects would have exact matches for the same vocabulary.
The parser is now smarter about the way it combines these sorts of
redundant matches. Instead of simply OR'ing together the match flags,
the parser now specifically keeps only the "strongest" match in these
cases. In our example, the parser sees that there's a truncated
dictionary match (WINDOWSILL) and an exact match (WINDOW) for the same
object, so it now decides to consider the match to be exact.
The English parser now accepts IN object, INTO object,
and IN TO object as verb synonyms for ENTER object.
These aren't exactly valid English grammar, but they have a certain
linguistic consistency with other telegraphic IF-ese constructs, and
they seem to naturally occur to some players. These variations all
have an obvious meaning to an English speaker, and they're not
ambiguous with any other commands, so there should be no harm in
adding them as convenient abbreviations.
After a misspelling, if the player typed OOPS but left off the
corrected spelling (i.e., just typed OOPS by itself on the command
line), the parser responded with a message saying that OOPS isn't
allowed right now. This has been corrected; the parser now responds
with a new message explaining the correct syntax, and gives the user
another chance to enter the OOPS command.
When a single player command triggers more than one implied action
(for example, GO NORTH might trigger OPEN DOOR, which in turn might
trigger UNLOCK DOOR), the reporting mechanism had a couple of problems
when the doubly-nested action (UNLOCK DOOR in our example) itself
triggered a nested action, such as by remapTo or nestedAction. First,
the reported order of the implied actions was reversed ("first opening
the door, then unlocking it"); second, the doubly-nested one reported
the nested action rather than the original implied action. Both of
these have now been corrected.
When you synthesize an action programmatically (via a function such
as replaceAction or nestedAction), the library has to choose a
language-specific final subclass of the generic action you specify.
It does this by choosing a VerbRule that's subclassed from the base
action. In the past, the choice was entirely arbitrary. Now, the
library is a little pickier: it now chooses only a final subclass,
never an intermediate class. That is, it always chooses a VerbRule
which hasn't been further subclassed (or modified with 'modify').
The old, less selective algorithm occasionally caused oddities, such
as choosing a base VerbRule that was subclassed with 'modify', thus
picking up pre-'modify' values of properties such as the verb's
descriptive text.
The conversationManager object now provides a simple extensibility
mechanism that lets you add your own custom conversation tags. To
add custom tags, use 'modify' to extend the conversationManager
object, then add two things. First, define the customTags property,
giving a string with the new tag names you want to add. The tag
names must be in lower-case letters; do not include the angle
brackets or the leading period in the tag name. Separate multiple
tag names with "|" symbols. Second, define a doCustomTag(tag,arg)
method that processes your custom tag or tags. The 'tag' argument is
a string giving the custom tag that was matched in the output stream;
only the tag name will be included, not including the angle brackets
or leading period. The 'arg' method is a string giving the argument
to the tag, if any; this is simply the part inside the angle brackets
following any whitespace after the tag.
SensoryEmanation and its subclasses Noise and Odor will now let you
use Script objects for the hereWithSource and hereWithoutSource
properties. This is handy in cases where you want to vary the
object's description over time. If you define these properties as
Script objects, the SensoryEmanation object will invoke their
doScript() methods when an emanation message is to be displayed.
The Keyring class has a new method, getLooseKeys(actor), that
identifies the loose keys in an actor's possession that are eligible
for automatic attachment when the keyring is taken. By default, this
returns the actor's direct contents (i.e., the objects the actor is
directly holding).
This can be overridden to consider, for example, loose keys held in
the actor's pocket. To prevent the keyring from automatically
grabbing any keys when it's taken, simply override this method to
return an empty list. Note that you don't need to bother limiting the
returned list to include only valid keys, since the caller does this
by considering only objects for which isMyKey() returns true.
The function withParserGlobals() now takes an additional parameter
giving the actor who issued the command. (This is an internal
function that's probably not used at all by existing game code, so it
should have no impact on games. If you are calling this function,
though, note that you'll need to supply the extra parameter.)
The menu system now sets the main content banner window to use
scrollbars if possible, and automatically scroll to show new text
in any case. This ensures that hint lists that are too long to fit
in the window will scroll as needed to keep the last hint in view.
Released 3/14/2004
Possible compatibility-breaking change: The property
formerly named 'vocabWords_' has been renamed to 'vocabWords' (the
change is that the underscore formerly at the end of the name has been
removed). The underscore suffix is conventionally used in the library
to indicate an internal library property that isn't normally for use
by game code, which obviously isn't appropriate in this case, as this
is the property that games usually initialize to set an object's
vocabulary words.
You should search for instances of 'vocabWords_' in your existing
code, and replace them with 'vocabWords'. Most existing game code
will probably have few, if any, instances of this property name, since
most object definitions in existing game code probably use the
standard library templates to set this property's value without giving
its name. Even so, you should search through your code and update any
mentions to use the new name.
The method getOutermostRoom(), which was previously defined in
BasicLocation and NestedRoom, has been moved instead to Thing. This
ensures that the method will work properly even when a nested room is
located within a non-room object. The Thing definition simply returns
the enclosing location if there is one, or 'self' if not.
The new TopicEntry subclass AskTellGiveShowTopic makes it easy to
define a response that covers ASK, TELL, GIVE, and SHOW for a given
object or set of objects. It's sometimes convenient to be able to
treat all of these commands as equivalent in handling a response.
AskTellGiveShowTopic is based on the new class
TopicOrThingMatchTopic, which can be subclassed to create other
combinations of other subsets of these four verbs (or to add topic
entries for new custom game-defined verbs). This new class combines
the features of the "Thing" and "Topic" base classes for topic
entries.
ConvNode has a new method, autoShowTopics(), that returns true if the
node is to show a topic inventory automatically on activation. By
default, this method returns true if the node contains any active
SpecialTopic entries. You can override this if desired to generate a
topic inventory automatically under other conditions, or to suppress
the automatic inventory when an active special topic is present
(although the latter probably isn't a good idea).
The main reason you might want to generate a topic inventory
automatically on activating a node is that the node contains one or
more obscure topics that players will be unlikely to guess on their
own. If it's unlikely that the player would think to ask just the
right thing at just the right time, a topic inventory can help guide
them through the conversation by showing the possibilities. As
always, only the topics you specifically mark as suggested (with
SuggestedAskTopic, etc) will be listed in the inventory. When possible,
it's probably better from a game-design perspective to lead players
into asking the right questions more subtly, by putting the right
cues in the conversation itself; when this is impractical, the topic
inventory is a usable substitute.
The pronoun expansion for CollectiveGroup objects that was introduced
in 3.0.6n has been partially removed. In the past, when an individual
object was associated with a CollectiveGroup object, the parser
automatically added the group object into consideration for resolving
a subsequent pronoun referring to the individual. This was meant to
provide better fidelity for resolving pronouns in these cases, but it
led to new problems when the original noun phrase could only have
referred to the individual. To correct the problems, the parser no
longer puts the collective group into consideration when a pronoun
refers to one of the group's individuals.
The other direction still applies, though: when a pronoun refers to
a CollectiveGroup object, the parser puts the group's associated
individuals into consideration for resolving the pronoun.
A bug in the CommandTopic class (introduced in 3.0.6n) prevented it
from matching a single Action class as the matchObj correctly. (It
did work properly when a list of Action classes was given, but not
when a single object was given.) This has been corrected.
The HelloGoodbyeTopic class now has its impliesGreeting property set
to true, ensuring that this topic type won't trigger an implied
greeting. Since a HelloGoodbyeTopic is itself a greeting, an implied
greeting would be undesirable, not to mention that it caused a
run-time error when it occurred.
A bug in ComplexContainer caused a problem when an object was
initially located in one of the the complex container's sub-containers
via the 'subLocation' property. In the past, the ComplexContainer
didn't properly re-initialize the object's 'location' property to
reflect that it was actually in the sub-container. This has been
fixed.
The problem manifested as strange behavior when the contained
object was removed from the sub-container (via TAKE or the like). In
particular, because the object's 'location' was still set to the
ComplexContainer, the object wasn't properly removed from the
sub-container's contents list, so the object appeared to still be in
the sub-container as well as in its new location.
A couple of problems involving formatting and real-time events have
been fixed. In 3.0.6i, fuses and daemons - including the real-time
versions - started running with a transcript context, which means
that their text output is captured. This caused some interactions
with the input manager that weren't taken into account properly in
the library.
First, if a real-time event itself stopped and asked for input from
the user (by reading a new command line, for example), the transcript
capturing prevented the text displayed by the event from showing up at
all until after the user entered something on the new command line.
It was possible to work around this by deactivating the transcript
prior to showing any output in the real-time event, but this is no
longer necessary.
Second, even if a real-time event didn't display anything, its mere
silent firing caused an extra blank line to show up immediately after
the user next pressed the Enter key to finish a command line.
Both of these problems are now corrected, so there's no need for
real-time events to do anything special with respect to output
formatting or command-line input.
The new pre-defined message object parameter 'pc' can be used to
refer directly to the player character. This is similar to the 'actor'
parameter, but refers to the player character actor even if an NPC is
the current active character. An expression such as "{you pc/he}"
thus refers to the current gPlayerChar, not to the current gActor.
The library messages for scoring (showScoreMessage,
showScoreNoMaxMessage, showScoreRankMessage, showFullScorePrefix)
incorrectly referred to the current actor rather than the current
player character, which resulted in attributing the score to an NPC
rather than to the player if a score message was generated on another
actor's turn. These messages now explicitly refer to the player
character.
Released 3/6/2004
Major compatibility-breaking change: The library's main
entrypoints to the game have changed. Existing games
must
be modified to accommodate this change.
In the past, the game code was responsible for defining two
entrypoints: main() and mainRestore(). The template games generated
by Workbench's "New Project" command defined these two functions as
one-liners that called mainCommon(), where the real action took place.
In the new arrangement, the library now invokes methods of a
new object, gameMain, which each game must define. For your
convenience, the library defines a class, GameMainDef, that you
can use as the base class of your gameMain object. This base class
defines suitable defaults for the required methods, so you only have
to override what you need to customize. Overall, this change should
result in much less required startup code for each game.
If you use GameMainDef as the base class of your gameMain object,
you're only required to define one property in it:
- initialPlayerChar - set this property to the Actor object that
serves as the initial player character in the game. You must
set this; GameMainDef can't provide a default for you here.
In addition, there are a couple of optional optional methods that
you can use for customization:
- showIntro() - show the game's introduction. Most games will want
to override this to show the prologue text for the game.
- showGoodbye() - show the game's "goodbye" message. Override this
if you want to add a parting message as the game terminates.
Most games will find it sufficient to define only initialPlayerChar
and showIntro(), and simply inherit the default handling for everything
else from GameMainDef. However, there are several additional methods
that you can override if you want to do something special.
- newGame() - the library invokes this method to start a new game.
The default version calls showIntro() to show the introductory text,
then runs the main command loop until the player quits, then calls
showGoodbye() to show the goodbye message. You can override this
if you want to do something extra, such as showing a pre-game
options screen.
- restoreAndRunGame(filename) - the library invokes this method to
restore and start running a saved game that the user selected when
launching the interpreter. It should rarely be necessary to override
this.
- setGameTitle() - the library invokes this method at start-up to
set the interpreter's window title to show the game's title, as
defined in versionInfo.name. It should rarely be necessary to
override this.
If your existing game code uses the Workbench-generated template,
or you follow the same pattern, then rewriting your code to fit the
new design is relatively straightforward. Just follow these steps.
That's it - if you were using the standard Workbench template
for mainCommon(), you're now finished.
If you made certain kinds of changes to the template main(),
mainRestore(), or mainCommon() that Workbench produced when you
originally created your game, you might have to do some additional
work. These extra changes are uncommon, but check this list to make
sure they don't apply to you:
- Do you have anything in main() or mainRestore() beyond a simple
call to mainCommon()? If so, you'll probably have to move that code
into the showIntro() method, or you might even have to override
newGame() (and possibly even restoreAndRunGame()). This is beyond the
scope of this simple recipe, so please refer to the definition of
GameMainDef in misc.t for full information on the new setup.
- If you customized the code in mainCommon() that restores the file
specified by 'restoreFile', you'll need to override
restoreAndRunGame(). You can probably just move your existing code
from your old mainCommon() into a new mainGame.restoreAndRunGame()
method. Refer to the definition of GameMainDef in misc.t.
- If you player character isn't named 'me', you'll need to change
the initialPlayerChar definition accordingly.
Minor Compatibility-breaking change: A few properties that
were previously in libGlobal and other library objects have been
moved into the new gameMain object instead. The relocated properties
all select optional library behavior; they've been moved to gameMain
to make it easier for game authors to select non-default option
settings. In the past, you had to use 'modify' to change these
settings, or explicitly assign them from your startup code; now, you
simply have to include any non-default option settings as property
values in your gameMain object definition.
The properties moved are:
- The former libGlobal.verboseMode is now gameMain.verboseMode.
- The former exitLister.enableStatusline is now
gameMain.showExitsInStatusline.
- The former libGlobal.allowYouMeMixing is now
gameMain.allowYouMeMixing.
- The former libMessages.scoreRankTable is now
gameMain.scoreRankTable.
- The former libScore.maxScore is now gameMain.maxScore. (This value
is also now nil by default, since there's no reason for the library to
assume a particular maximum score.)
Compatibility-breaking change: The format of the verbPhrase
string has changed slightly for the English version of the library.
Game-defined verbs might need to be adjusted to the new format.
The change is that prepositions in a verb phrase are now grouped
inside the parentheses of the "(what)" placeholders for the objects,
if the prepositions go with the noun phrases. For example, the
LookIn action's verbPhrase string has changed from 'look/looking in
(what)' to 'look/looking (in what)'. The difference is subtle: by
moving the preposition 'in' so that it's inside the parentheses with
the direct object's "(what)" placeholder, we're telling the library
that the 'in' is part of the direct object noun phrase. Contrast
this with the verbPhrase for Doff, which is now 'take/taking off
(what)': this tells us that the preposition 'off' is part of the verb
structure, not part of the direct object.
How can you tell which way it goes? There are two rules you
should use to determine this.
First, if the preposition separates the direct and indirect
objects, such as the IN in PUT X IN Y, then it always goes
inside the indirect object phrase, hence 'put (what) (in what)'.
Second, for any other preposition, use the "it test": try writing
the verb using 'it' in place of 'what', and ask whether it sounds
right for the preposition to go before or after the 'it'. For
example, for the Doff action, TAKE IT OFF sounds right, and TAKE OFF
IT is obviously wrong. This means that the preposition 'off' belongs
in the verb phrase, hence 'take off (what)'. In contrast, for the
LookIn action, LOOK IN IT sounds right, not LOOK IT IN, so the
preposition goes with the direct object, hence 'look (in what)'. If
the "it test" indicates that the preposition goes before the
'it', then the preposition is part of the direct object and thus goes
inside the parentheses of the "(what)"; if the preposition
goes after the 'it', then the preposition is part of the verb
and goes outside the parentheses.
Prepositions that go with the verb are uncommon in two-object
verbs, but they do occur. An example from the library is
ConsultAbout, one form of which uses the verbPhrase 'look/looking up
(what) (in what)'. We'd write LOOK IT UP IN IT, so the preposition
'up' goes after the direct object pronoun and is thus part of the
verb, while the preposition 'in' goes before the indirect object
pronoun and is thus part of the indirect object. In case there's any
ambiguity about whether the 'up' goes with the verb or with the
indirect object, try the un-it test: we'd write LOOK UP TOPIC IN
BOOK, so clearly the 'up' is not part of the indirect object, but is
just part of the verb.
Compatibility-breaking change: The TextList classes have been
removed, leaving only the more general EventList classes. The
TextList classes have been gradually converging with the EventList
classes anyway, to the point where the TextList classes have become
nothing more than renamed versions of the corresponding EventList
classes. Retaining the parallel set of names for otherwise equivalent
classes is bad because it steepens the learning curve for new users
and makes for more to remember for experienced users; so, the
redundant names have now been dropped.
Existing code will need to be scanned for occurrences of TextList,
StopTextList, RandomTextList, ShuffledTextList, and SyncTextList.
Replace these names with CyclicEventList, StopEventList,
RandomEventList, ShuffledEventList, and SyncEventList, respectively.
(Note that you can simply do a global search-and-replace to change to
suffix TextList to EventList, except that you must change occurrences
the TextList when it occurs as an entire word to CyclicEventList.)
In addition, the following property names must be changed:
- textStrings to eventList
- firstStrings to firstEvents
- messagePercent to eventPercent
- messageReduceAfter to eventReduceAfter
- messageReduceTo to eventReduceTo
If you really don't want to change your existing code, you can
always use #define to create macros with the substitutions. You
really should change your code if possible, though, since using the
old names is likely to create confusion as the old names recede from
your memory and from the documentation.
Compatibility-breaking change: In ConversationReadyState,
the greeting and goodbye messages have been changed.
First, the greetingList property is no longer used; instead, the
state now looks for a HelloTopic entry and uses its response. In
addition, you can differentiate between explicit HELLO commands and
implied greetings generated by other conversational commands (ASK TO,
for example) by creating separate HelloTopic and ImpHelloTopic
objects. In most cases, it's not necessary to distinguish the two
cases, so you can simply use a single HelloTopic to cover both cases.
Second, the enterFromByeMsg and enterFromConvMsg methods have been
removed, along with the enterFromByeList and enterFromConvList
properties. Instead, the state now looks for a ByeTopic or
ImpByeTopic entry, depending on whether the conversation is ending
due to an explicit GOODBYE command or due to an automatic ending (due
to the other actor walking away, or due to an inactivity timeout) and
uses its response.
You should scan your existing game code for any greetingList,
enterFromByeMsg, enterFromByeList, enterFromConvMsg, and
enterFromConvList properties, and change them to HelloTopic,
ByeTopic, and ImpByeTopic as appropriate.
Note that both the HELLO and GOODBYE topic entries go in the
ConversationReadyState, not in the InConversationState. This is
because these handlers typically describe the transition to and from
the "ready" state. A single in-conversation state could work with
several "ready" states, so putting the hello/goodbye topic entries in
the in-conversation state wouldn't allow any differentiation among
the several "ready" state transitions. Keeping the hello/goodbye
entries in the "ready" states themselves makes this differentiation
easy. For example, this allows a "goodbye" message to say something
like "Bob goes back to sweeping the porch."
Possibly compatibility-breaking change: The internal methods
that handle conversational commands in Actor have changed. The changes
are isolated to the internal methods that route the commands within
and between the Actor and ActorState objects, not to the main public
interfaces that game code usually uses. Even so, since it's occasionally
useful to override these internal methods, some existing game code
might be affected.
In general terms, these changes are designed to concentrate the
handling of all conversational actions along a single path. In the
past, each type of conversational command was handled by its own
cluster of methods, so the Actor and ActorState had a set of parallel
methods for HELLO, GOODBYE, YES, NO, ASK ABOUT, ASK FOR, TELL ABOUT,
SHOW TO, and GIVE TO. This old design was intended to make it easy to
override these handlers in isolation, but experience has since shown
that it's much more useful to be able to override all of the handlers
in concert instead. The new design therefore consolidates all of
these different conversational actions into a single method that's
parameterized with the type of action. There are still several
methods because of the sequence of processing points through the Actor
and ActorState, but there are no longer several clusters of methods:
each of the old clusters is now a single method.
An additional benefit of these changes is that HELLO and GOODBYE
are now handled through the topic database like everything else. This
has two useful effects. First, it means that a DefaultAnyTopic entry
will now respond to a HELLO or GOODBYE along with everything else.
This is highly desirable in most cases, because these defaults are
usually meant to convey a blanket response to all conversational
overtures. Second, it means that it's now a bit easier to customize
HELLO and GOODBYE responses: just use HelloTopic, ByeTopic, and
HelloByeTopic objects as desired.
Advice: Because there are a lot of details to these
changes, we suggest you scan your code for mentions of the affected
methods, to see if your code is affected at all. If your code
doesn't define or call any of these methods, you shouldn't be
affected. The methods are:
- sayToActor
- handleConversation
- yesNoFrom
- answerQuestion
- hearAbout
- beShown
- beGiven
- answerRequestFor
Now to the specific changes.
Actor.sayToActor() has some changes to its parameters. The
'prop', 'propArgs', and 'topicListProp' parameters have been dropped,
and the new 'topic' and 'convType' parameters have been added.
'topic' is a special object representing the topic; the library
defines the singletons helloTopicObj, byeTopicObj, yesTopicObj,
and noTopicObj for the corresponding library actions. 'convType'
is an object of type ConvType describing the type of action being
performed.
The new helloTopicObj and byeTopicObj singletons have
been added, as mentioned above. These are used as special topic
placeholders for the HELLO and GOODBYE commands, respectively.
The new ConvType class has been added, and singleton
instances for all of the standard library conversation actions (HELLO,
GOODBYE, YES, NO, ASK ABOUT, ASK FOR, etc.) have been defined.
The 'topicListProp' and 'handler' parameters of
ActorState.handleConversation() has been dropped, and the new
'convType' parameter has been added. 'convType' is a ConvType
object representing the conversation type.
The 'topicListProp' parameter of
ConvNode.handleConversation() has been replaced with a new 'convType'
parameter, which is a ConvType object describing the conversation
type.
Actor now provides a handleConversation() method. The
ActorState will invoke this method by default when the ActorState's
own handleConversation() method doesn't handle the action. This new
Actor method is in lieu of the former cluster of per-command handlers
in Actor: yesnoFrom, answerQuestion, hearAbout, beShown, beGiven,
answerRequestFor.
All of the per-command handlers in Actor and ActorState
have been removed: yesNoFrom, answerQuestion, hearAbout, beShown,
beGiven, answerRequestFor. These are replaced by the
handleConversation() method in Actor and ActorState, as mentioned
above.
The new Actor method defaultConvResponse() is a general
handler for showing the default response when a conversational action
isn't otherwise handled (that is, it's not handled by any topic
database entry, and the ActorState doesn't want to handle it
specially). This new method provides an easy way to show the same
response for every conversational action - just override this one
method, and you can show a common response for all conversation
commands. By default, this method routes looks at the convType
parameter to determine the conversation type, and invokes the Actor
method that provides the default response for that particular
conversation type, using the same default response methods used in the
past (defaultGreetingResponse, defaultGoodbyeResponse, etc).
The ActorState object can now optionally define any
of the default response handlers that Actor can define
(defaultGreetingResponse, defaultAskResponse, etc). When these are
defined, they'll be called when the state object doesn't provide a
suitable TopicEntry in its topic database. Note that these
effectively override the Actor's topic database and default
response handlers for a given type of action. The order of handling
for a conversational action is now ConvNode, ActorState TopicEntry
list, ActorState default response method, Actor TopicEntry list, and
finally Actor default response method.
Possibly compatibility-breaking change: A change to the
library's message system makes it easier to customize the default
response messages for individual objects. In the past, if you wanted
to customize the message for OPEN COCONUT, say, you'd have to
override the 'verify' method for the 'coconut' object's
dobjFor(Open). With this change, you can create a custom per-object
message more easily: you simply define coconut.cannotOpenMsg with the
custom message string.
You can use the new message customization scheme any time the
library generates a standard response message using code like this:
dobjFor(Open)
{
verify { illogical(&cannotOpenMsg); }
}
Any time you see library code like that, you can override the
default response message for that action on an individual object
simply by defining the message property in your object:
coconut: Thing
cannotOpenMsg = '{You/he} would need something sharp to do that. '
;
In addition to illogical(), this also works with illogicalNow(),
inaccessible(), defaultReport(), defaultDescReport(), extraReport(),
mainReport() reportBefore(), reportAfter(), reportFailure(), and any
other verify or action reporting routines.
Here's how this works. In the past, when a verify, check, or
action routine generated a message using a message property, the
library looked up the message in the current actor's "action message
object," which is usually playerActionMessages when the actor is the
player character, and npcActionMessages otherwise. Now, the library
still looks there, but only after checking the individual
objects involved in the command to see if any of them define the
message property.
The library looks first at the direct object, then at the indirect
object. If you were to create your own custom verbs with three or
more object slots, such as PUT COIN IN SLOT WITH TWEEZERS, the
library would automatically continue on to those extra objects as
well. If the library finds the message property in any of these
objects, it stops and uses that object as the source of the message;
if it can't find the message property among these objects, the
library simply falls back on the standard message object
(playerActionMessage or whatever).
Important: As part of this change, all of the library
message properties have been renamed to end in "Msg". This affects
every message property, so if you've created your own "action message
object," or you've used 'modify' to change playerActionMessages
and/or npcActionMessages, you'll have to do some extensive searching
and replacing to add the "Msg" suffix to every message property name.
Sorry about this; the proposed change was put to the TADS 3 mailing
list, and no one objected. The naming change isn't gratuitous. The
reason for the name change is that it should greatly reduce the
chances of collisions between message properties and properties
defined for internal use by an object. The library itself formerly
had a number of these collisions, so it was necessary to rename at
least those properties; using the naming convention consistently for
all of the message properties will help ensure that games don't
inadvertantly introduce their own name collisions.
There's one last detail to mention. An object can override a
message property with another message property. For example,
the Vaporous object in the library uses this feature:
notWithIntangibleMsg = ¬WithVaporousMsg
When a message property in an object points directly to another
property, the library takes this as an indirection to another library
message from the action message object. This feature is mostly for
the library's benefit, since library objects are required to get all
of their messages from the action message object (to ensure that the
library can be translated without rewriting entire object
definitions).
Minor compatibility-breaking change: The Script method
getState() has been renamed to getScriptState(), and the Script
property curState has been renamed to curScriptState. This change
allows Script and its subclasses to be combined (with multiple
inheritance) with Thing (which defines its own meaning for getState)
and with Actor (which defines its own curState). It's sometimes
desirable to combine Thing or Actor with a Script subclass, because
that's an easy way to attach simple scripting behavior to these
objects.
Existing game code that overrides or calls getState or curState
for a Script object (including any EventList subclass) will need to
be changed to use the new name. You should scan your source code for
occurrences of these names, and rename them as needed. Note that
you should not rename curState properties that pertain to
Actor objects, since Actor.curState has not been renamed.
In most cases, game code won't have any reason to override or
access these script properties at all, so most game code should be
unaffected by this change. Game code usually just defines instances
of the library EventList subclasses using templates, and such code
won't be affected by this change.
Minor compatibility-breaking change: The library objects
redirectTravelIn, redirectTravelOut, and redirectTravelDown have been
renamed to askTravelIn, askTravelOut, and askTravelDown,
respectively. The names changes are for consistency with the naming
of the new class AskConnector.
The PresentLater class has a new property, initiallyPresent, that lets
you override the standard behavior of the class, which is to make the
object initially absent from the game map. This new property is set
to nil by default; if you override it to true, the object will be
initially present in the game, like any ordinary object. This new
property would seem to defeat the purpose of the class, but it
actually extends the class to situations where an object comes and
goes during the game, but starts out present. Using PresentLater with
initiallyPresent set to true, you can still use all of the
PresentLater showing and hiding mechanisms, such as the key-based and
conditional methods; this is often more convenient than moving objects
in and out of the game map individually and manually.
The Attachable class now automatically notifies itself and each of its
attachments whenever the Attachable is carried by a traveler, by
calling the new method travelWhileAttached(). The method does nothing
by default, but games can override it as needed to enforce conditions
or carry out side effects when an attached object is moved indirectly
via travel.
The Attachable class now has a way of specifying the "direction" of
an attachment relationship, for the purposes of descriptive messages.
Formerly, attachments were always described symmetrically: if A was
attached to B, then examining A generated a status message along the
lines of "A is attached to B," while examining B generated a message
like "B is attached to A." This didn't always work; "the note is
attached to wall" is fine, but "the wall is attached to the note"
isn't quite right. The new feature lets you specify that the note is
always said to be attached to the wall, never vice versa.
The direction of a relationship is specified by the new Attachable
method isMajorItemFor(obj). By default, this method always simply
returns nil, which means that there are no "major" items by default,
which makes all attachment relationships symmetrical by default. If
you wish, you can override isMajorItemFor() so that it returns true
in some cases. When A.isMajorItemFor(B) returns true, the
relationship will always be described such that B is said to be
attached to A: examining A will yield "a B is attached to the A,"
while examining B will show "the B is attached to an A."
A few new methods have been added to support the new feature;
these are mostly for internal use, but could potentially be used by a
game to fine-tune the way attachments are listed. The new method
isListedAsAttachedTo(obj) lets the object indicate whether or not
'obj' is listed among the things 'self' is attached to; by default,
this returns true if 'obj' isn't permanently attached and
'self' isn't the "major" item for 'obj'. The new method
isListedAsMajorFor(obj) is essentially the major-list counterpart: it
indicates whether or not 'obj' is listed among the things attached to
'self' when 'self' is described. By default, this method returns
true if 'self' is the "major" item for 'obj', and
obj.isListedAsAttachedTo(self) returns true (that is, 'obj' thinks it
should be listed as attached to 'self'). Finally,
majorAttachmentLister returns the lister to use for the items
attached to 'self' for which 'self' is the "major" item in the
relationship; by default, this uses a MajorAttachmentLister instance.
The new classes Underside, RearContainer, and RearSurface make it
easier to model situations where one object is behind or under
another. Underside can be used to model the space under an object, or
for the bottom surface of an object. RearContainer models the space
behind an object, and RearSurface models its back surface.
In addition to letting you set up "under" and "behind"
relationships among objects initially, these new classes support the
PUT UNDER and PUT BEHIND commands to let actors add new contents under
and behind the objects. (The PUT BEHIND command is also new in this
release.) An Underside can have new objects added under it with PUT
UNDER, and a RearContainer or RearSurface can have new contents added
with PUT BEHIND. These commands can optionally be disallowed for a
given Underside or RearContainer/RearSurface: override the properties
allowPutUnder and allowPutBehind, respectively. The new classes
derive from BulkLimiter, so you can use the usual BulkLimiter
properties to control the individual and total bulk allowed under and
behind the objects.
ComplexContainer has been extended to support these new classes.
The new ComplexContainer property subUnderside can be set to an
Underside object representing the space under or bottom surface of the
complex container; the new property subRear can be set to a
RearContainer or RearSurface representing the space behind or back
surface of the complex container. PUT BEHIND and LOOK BEHIND commands
on the complex container are routed to the subUnderside; PUT UNDER and
LOOK UNDER are routed to the subRear; and both subcomponents are
included in the regular LOOK AT display.
These additions were adapted from work originally done by Eric Eve.
It's now easier to set up objects so that they're initially inside the
component sub-containers of a ComplexContainer. In the past, if you
wanted to create an object in your source code so that it was
initially inside a ComplexContainer's internal container, for example,
you had to explicitly set the object's 'location' property, rather
than using the "+" syntax. Now, you can use the "+" syntax as long as
you add a little extra information: give each contained object a new
property, 'subLocation', and set it to the property of the component
sub-container you want to use as the initial location. For example,
here's how you'd create a washing machine as a complex container,
with a blanket inside and a laundry basket on top:
+ washingMachine: ComplexContainer 'washing machine' 'washing machine'
subContainer: ComplexComponent, Container { /* etc */ }
subSurface: ComplexComponent, Surface { /* etc */ }
;
++ Thing 'blanket' 'blanket'
subLocation = &subContainer
;
++ Container 'laundry basket' 'laundry basket'
subLocation = &subSurface
;
The blanket and the laundry basket are nominally directly inside
the washing machine itself, according to the "+" syntax, but their
'subLocation' settings ensure that they end up in the desired
component sub-containers during initialization.
Note that 'subLocation' is only intended for initialization, so the
library automatically sets subLocation to nil for each object right
after the initial setting is used. This helps avoid any unpleasant
surprises should the object be moved into a different ComplexContainer
later on. When you're moving objects around on the fly in your
program code, there's no reason to use subLocation at all; instead,
just specify the appropriate component as the explicit destination of
the moveInto: towel.moveInto(washingMachine.subSurface), for
example.
The Openable class has a new property, openingLister, that specifies
the lister to use to display the list of items revealed when the
object is opened. By default, this is set to the
openableOpeningLister object. Individual objects can override this
if they want to customize the message listing the revealed items.
In addition, the object formerly called openingLister has been
renamed to openableOpeningLister.
The new CommandTopic makes it easier to generate a response when an
NPC receives a particular command. Just create a CommandTopic, with a
list of the Action classes you wish to match. Actions are matched on
class alone; if you want to match something more specific, such as
matching a particular direct object of a particular action, you'll
have to create a custom matchTopic() method for your CommandTopic
object. If you want to create a default response that matches any
action, use a DefaultCommandTopic.
CommandTopic works like any other TopicEntry object, so you can put
these in ConvNode, ActorState, and Actor topic databases to provide
responses under the specific conditions you need to match.
As part of this change, ActorState.obeyCommand() now invokes
handleConversation() to look for a response to the command, specifying
the Action object as the topic and 'commandConvType' as the
conversation type object. This looks as usual in the ConvNode,
ActorState, and Actor conversation topic databases for a CommandTopic
to handle the response, or for a DefaultAnyTopic if there's no
CommandTopic.
The parser now gives more weight to explicit owners when resolving
possessives. If a noun phrase is qualified by a possessive ("my
book"), and the noun phrase is ambiguous even with the possessive
qualifier (because the possessor is carrying more than one matching
object), the parser will now choose an object with an explicit
"owner" property over one without an explicit owner. When the parser
resolves this kind of ambiguity by choosing an explicitly owned
object over one that's merely being carried, it will mark the chosen
object with the UnclearDisambig flag to indicate that it's a best
guess.
The library no longer treats "brightness" (the sensory intensity of
light, sound, etc.) as diminishing over a "distant" containment
boundary. In the past, distance diminished brightness by one level.
While this was arguably physically realistic over large distances, in
practice it proved to be undesirable for the typical scales in IF
settings, where "distant" is usually used to indicate that something's
tens or hundreds of feet away, not miles.
The sense mechanism has a new feature that allows an object to take on
its own special sensory status. An object can use this feature to
override the normal sense path mechanism and decide for itself how it
appears to the senses. The change is comprised of two new methods.
First, when building the sense information table, the library now
calls a new method, addToSenseInfoTable, on each object connected by
containment to the source object. This method by default does what
the library has always done: it evaluates the temporary sense
properties set up by the containment path traversal, and adds a
SenseInfo object to the table based on the sense properties. An
objects can now override this method to add a table entry based on a
different sensory status.
Second, when finding the containment paths from one object to
another, the library calls the new method specialPathFrom on the
target object if (and only if) no ordinary containment path can
be found. This method can supply its own custom containment path.
This allows an object to exist outside of the normal containment
hierarchy but still get a chance to represent its connection to
the normal containment hierarchy as it sees fit.
When a CollectiveGroup object has no location, it now takes on the
sensory status of its individuals. It will take on the "best" sense
status of any individual in scope, which is the one that's most
transparent and (transparencies being equal) has the highest ambient
level.
This change makes it much easier to work with CollectiveGroup
objects, because it makes a CollectiveGroup as visible, audible, etc.
as any of its individuals.
Note that this change doesn't affect CollectiveGroup objects that
have a normal location. These objects already participated in the
sensory model in the normal manner, and will continue to do so. This
change only affects group objects with no location.
The handling of the "catch-all" Default handlers has changed
slightly. The sequence of processing is now as follows:
- The iobjFor(All) and dobjFor(All) handlers run first, if defined.
- The iobjFor(Default) and dobjFor(Default) handlers run next, if
they "override" the verb-specific handlers.
- The iobjFor() and dobjFor() handlers for the specific verb run next,
unless the corresponding Default handlers override them
In the past, the 'verify', 'check', and 'action' methods for the
Default handlers were called in addition to any verb-specific
versions of the methods. For example, if an object defined a
dobjFor(Default) with an action() method, and the player entered an
OPEN command on the object, the Default action handler ran, and then
the dobjFor(Open) action() method also ran. In most cases,
this made no difference, because the base Thing class doesn't even
define action() or check() handlers for most verbs, since it
disallows most verbs in the verify() stage.
With this change, the Default handlers now run instead of the
verb-specific handlers, when the Default handlers run at all. The
rules about when Default handlers override verb-specific handlers,
and vice versa, haven't changed. A Default handler still overrides
any verb-specific handler inherited from a base class, and any
verb-specific handler defined in the same class as the Default
handler or in any subclass still overrides that Default handler.
Note that the relative order of the All and Default handlers has
been reversed: the All handler now always runs first. This makes the
sequence of methods proceed from most general to most specific.
If an 'exit' occurs within a PreCondition object's
checkPreCondition() method, it's now treated as a failure of the
enclosing action that invoked the precondition. The normal pattern
that most preconditions use is as follows: first, evaluate if the
condition is met; second, if the condition isn't met, try an
appropriate implied action to try to bring it into effect; third,
re-test the condition to see if the implied action did what it was
meant to; and fourth, if the condition still doesn't apply, use
'exit' to terminate the triggering action. It's this use of 'exit'
that's now interpreted as a failure of the enclosing action.
Note that this doesn't apply if 'exit' is used within a nested
implied action; this only applies if 'exit' is used within the
precondition's checkPreCondition() method itself.
The library now defines a template for the Achievement class, taking
a double-quoted string for the achievment's description text.
The new function finishGameMsg() makes it easier to show one of the
conventional end-of-game messages, such as "*** YOU HAVE DIED ***" or
"*** YOU HAVE WON ***". This function takes two parameters: a
message specifier, and a list of extra finishing options. The extra
options are the same as for the existing function finishGame().
The message specifier can be one of several standard, pre-defined
objects, or it can simply be a string to display. The pre-defined
object ftDeath, ftVictory, ftFailure, and ftGameOver display messages
for death of the player character ("YOU HAVE DIED"), victory ("YOU
HAVE WON"), failure ("YOU HAVE FAILED"), and simply "game over",
respectively. Alternatively, you can make up your own messages for
unconventional cases ('YOU HAVE SUFFERED A FATE WORSE THAN DEATH',
say) simply by specifying a string. Your string will be displayed
surrounded by "***" sequences to yield the conventional formatting,
or with other sequences that might vary by language. You can also
pass nil as the message specifier, in which case no message at all
is displayed.
The value of gameMain.maxScore (formerly libScore.maxScore) is now
allowed to be nil. In addition, the default value in GameMainDef is
now nil. When this value is nil, the SCORE and FULL SCORE commands
simply won't mention a maximum score for the game.
If your game has complex scoring that makes it difficult or
impossible to state a maximum possible score, or if you simply don't
want to give this information to the player, simply set maxScore to
nil in your gameMain object. The library uses nil as the default
because there's no reason for the library to assume that a game will
have a particular maximum score (the library formerly used a default
value of 100, but this was overly presumptuous).
The library's scoring system has a new, optional usage style that
provides two benefits. First, it lets you define the number of points
an Achievement is worth as part of the Achievement object, better
encapsulating the information about the Achievement. Second, the
library can use this new information to automatically compute the
maximum possible score in the game, saving you the trouble of figuring
this out manually (and of keeping the stated maximum in sync with the
game as you make changes).
If you explicitly set the maxScore property in your gameMain object,
the library will not automatically compute the maximum score.
Your explicit maxScore setting always overrides the computed value.
To take advantage of the new capabilities, simply define the new
'points' property for each Achievement object you create. Then,
rather than calling addToScore() or addToScoreOnce(), both of which
take a parameter specifying the number of points to award, call the
new Achievement methods awardPoints() or awardPointsOnce() instead.
For example, suppose you have some existing code that looks like
this:
vase: Thing
handleBreakage()
{
// ... do the real work here...
scoreMarker.addToScoreOnce(10);
}
scoreMarker: Achievement { "breaking the base" }
;
This code defines a nested object called 'scoreMarker' to describe
the scoring item, and the 'handleBreakage' method awards the Achievement
object, assigning it 10 points in the score. Using the new style,
you'd change the code above to look like this instead:
vase: Thing
handleBreakage()
{
// ... do the real work here...
scoreMarker.awardPoints();
}
scoreMarker: Achievement { +10 "breaking the base" }
;
The "+10" in the nested Achievement definition assigns the object a
value of 10 in its 'points' property (using a template defined in
adv3.h), indicating that the item is worth 10 points in the score.
Rather than specifying this in the code that awards the Achievement,
we define it as part of the Achievement object itself. The code that
awards the points doesn't need to specify the number of points; it
just calls the awardPoints() method of the Achievement object, which
awards the points defined in the object. This makes the code easier
to read, since you can see the description of the scoring item and the
number of points it's worth in one place.
To take advantage of the library's automatic computation of the
maximum possible score, you have to follow a few rules:
- Use only Achievement objects to award points. Never
call addToScore() with a string value to award an ad hoc scoring item.
- Set the 'points' property of each of your Achievement objects
to the number of points the item is worth.
- If an Achievement can be scored more than once, also set
the 'maxPoints' property to the maximum number of points the item will
contribute to the score. This is usually just 'points' times the number
of times the item can be awarded.
- Only define Achievement objects statically. Never use 'new
Achievement' to create an Achievement dynamically. (If you create new
Achievements dynamically, the library won't know about them at
start-up time, obviously, so it can't count their contributions to the
maximum score.)
- Always award Achievements through their awardPoints() or
awardPointsOnce() methods. (This ensures that each Achievement is
scored with the number of points specified in its 'points' property.)
- Achievements can't be mutually exclusive. That is, there must
exist at least one solution of the game in which every Achievement
object is awarded. (If you have Achievement objects that represent
different solutions to the same puzzle, so that only one of these
achievements can actually be awarded in any given traversal of the
game, the library would incorrectly count the score contributions of
all of the alternatives in the maximum score. The correct maximum
would count only the single highest score among the alternatives. The
library has no way of expressing mutual exclusion among Achievements,
so if your game has such a situation, you'll need to set
libScore.maxScore manually.)
If you follow these rules, the library will accurately compute the
maximum score value, so you don't need to bother figuring out how many
points are in the game yourself, and you don't need to worry about
initializing libScore.maxScore in your game's start-up code.
If your game can't follow the rules above (because you have
alternative solutions to puzzles that assign different scores, for
example, or because your game has alternative paths with different
maximum scores), you'll need to figure the maximum score manually,
and set the property 'maxScore' to this value in your 'gameMain'
object. Explicitly setting gameMain.maxScore in your code will
override the library's automatic computation.
The finishGame() and finishGameMsg() functions now display the
current score (using the same message that the SCORE command normally
displays) if the finishOptionFullScore option is in the extra options
list. In fact, these functions will announce the score if any extra
option has the property showScoreInFinish set to true;
finishOptionFullScore has this option set to true, so including it in
the extra options list causes the score to be announced. If you want
the end-of-game announcement to include the score, but you don't want
to offer the FULL SCORE option, you can include finishOptionScore in
the extra options list; this is an "unlisted" option, so it doesn't
add anything to the list of offered option list, but it does cause
the end-of-game announcement to include the score.
Actor.holdingDesc (which provides the default description of what an
actor is holding as part of processing an Examine action on the
actor) now uses pretty much the same handling that Thing uses when it
lists the contents of an object. The only difference is that
Actor.holdingDesc still uses the special holdingDescInventoryLister
as the contents lister. One important consequence of this change is
that examining an actor marks the actor's contents as having been
seen by the player character, just as examining an ordinary object
marks the object's contents as seen. Another is that the scope of
objects included in the listing is limited to objects the
point-of-view actor can actually see. In the past, the examined
actor listed everything in "inventory scope," which includes objects
that are directly held even if they're not currently visible; this is
correct for an inventory listing from the point of the view of the
actor taking the inventory (because the actor can presumably identify
directly held items by feel, even in the dark), but the new
sight-only behavior is more appropriate for examining another actor.
This change also adds a new Thing method examineListContentsWith().
This is simply a service method that performs the bulk of what
Thing.examineListContents() did before, but is parameterized by
a lister to use to generate the listing.
The new class AskConnector makes it easy to define a directional
connection when the direction is ambiguous. For example, suppose
that you have two doors leading north, and you don't want to use the
traditional trick of distinguishing the doors' respective directions
as northeast and northwest. (If you had ten doors leading north
instead of two, the traditional northeast/northwest trick wouldn't
help anyway.) If the player types NORTH, you would like the parser
to ask which door to use.
AskConnector makes this easy. Simply define the room's 'north' as
an AskConnector object, and set the AskConnector instance's property
'travelAction' to 'GoThroughAction'. The connector, when invoked for
travel, will try running the GoThroughAction, but with a missing
direct object; this will make the parser ask the player which door to
use with the usual prompt for a missing object: "What do you want to
go through?"
If the direction has a specific set of possible objects, as in
our example of two doors leading north, you can further refine the
parser's question by specifying the 'travelObjs' property. Set this
to a list of the possible direct objects for the action. If you set
this property, you should also set 'travelObjsPhrase' to a string
giving the noun phrase to use in disambiguous questions: in our
example, we might set this to 'door', so that the question becomes
"Which door do you mean...?".
The English library now provides a default verbPhrase for the
TravelVia action ('use/using (what)', which is pretty vague, but
TravelVia is fairly vague itself). This lets the parser generate
usable messages in cases where a TravelVia action elicits a parser
prompt, such as when using TravelVia in askForDobj().
TravelPushable has several small changes.
First, TravelPushable.movePushable() now uses moveIntoForTravel()
to move the object, rather than moveInto(). This corrects a problem
that occurred when the starting and destination locations were
connected by a sense connection (such as a distance connector).
Second, movePushable() and describeMovePushable() each now take an
additional argument giving the connector being traversed.
Third, the new method beforeMovePushable() gives the pushable
object a chance to do any necessary work just before the travel takes
place. This is especially useful for adding a message describing
anything special that happens when pushing the object out of its
current location. By default, this routine does nothing.
The new Traveler methods canTravelVia() and explainNoTravelVia() allow
a traveler to disallow travel via a particular connector and/or to a
particular destination. These new methods essentially provide a
complement of the "travel barrier" mechanism, which allows a connector
to disallow travel by a particular traveler. The new methods are
useful becuase it's often more convenient to tie a travel condition to
the traveler than to the connector. For example, you might want to
implement a condition that prohibits a particular actor from going
outside; you could do this by defining a canTravelVia() method on the
actor that returns nil if the destination is an OutdoorRoom.
The new BasicLocation methods enteringRoom(traveler) and
leavingRoom(traveler) make it more convenient to write code that
responds to an actor's arrival into or departure from a room. These
methods are called from travelerArriving() and travelerLeaving(),
respectively; they differ only in that (1) they have simpler
parameter lists, leaving out information that frequently isn't needed
to write an arrival/departure event handler, and (2) the base class
implementations don't do anything, so there's no need to use
'inherited' to inherit up the base class behavior. These new methods
are purely for convenience, to make the very common task of writing
arrival/departure event handlers a little less work.
The new class ContainerDoor makes it easy to create a door for a
container, if you want the door to act like a separate object in its
own right. This works with the ComplexContainer class: create a
parent as a ComplexContainer, and inside it create a ContainerDoor
and the normal secret actual Container. The door will redirect
commands like open, close, lock, and unlock to the secret inner
container, but will still appear as a separate object.
In the past, the base TravelConnector methods describeArrival() and
describeDeparture() simply showed the most generic travel messages
("Bob is leaving the area"). This meant that replacing a simple room
connection with a TravelMessage connector meant giving up the
directional message that a normal room connection generated ("Bob is
leaving to the east").
Now, the directional message is the default in the base
TravelConnector class. If there's a direction property linked to the
connector from the origin (for a departure) or destination (for an
arrival), the base TravelConnector messages will now show the
directional version of the message. The generic, non-directional
message will only be shown when no directional link can be found.
Note that this doesn't affect the more specific types of connectors
and their custom messages, so stairs will still say "Bob goes up the
stairs," doors will still say "Bob leaves through the wooden door,"
and so on.
The new TravelConnector method isConnectorPassable() indicates whether
or not a traveler can pass through the connector. This can be used in
game code that probes the map to determine if a connector can be
traversed in its current state.
In the past, the noTravel object, and the NoTravelMessage and
FakeConnector classes, incorrectly displayed their special messages on
attempted travel even when it was dark in the room. In the dark, the
"dark travel" message should take precedence. This has been
corrected. (The problem was that these classes overrode the
dobjFor(TravelVia) check() method to bypass the normal darkness check.
The unnecessary override has been removed, so the standard darkness
check is now made, so the dark-travel message is now shown instead of
any special message.)
The BasicLocation methods getTraveler() and getPushTraveler() have
been renamed and moved into Thing. These methods are now called
getLocTraveler() ("get location traveler") and getLocPushTraveler()
("get location push traveler"), respectively.
This change is necessary to allow actors to hold other actors, and
to allow intermediate containers within rooms (that is, to allow a
Room to hold an arbitrary Thing subclass, which in turn holds a
NestedRoom). Actor has methods with the names getTraveler() and
getPushTraveler(), but these methods have a different purpose than the
old BasicLocation methods: the Actor methods get the traveler when the
actor is initiating the travel, and the old BasicLocation methods get
the actual traveler when a traveler within the location initiates
travel. The use of the same name created a conflict between these
different purposes, so one or the other set had to be renamed. In
addition, limiting these methods to BasicLocation prevented ordinary
Things from holding actors who could travel; adding them to Thing
corrects this.
The logic in Vehicle.getTraveler() that determines whether to move
the vehicle or the traveler within the vehicle has changed slightly.
Rather than basing the decision on travel barriers, the routine now
moves the traveler rather than the vehicle only if the connector is
contained within the vehicle - that is, the connector leads somewhere
from inside the vehicle. The connector is considered to be inside
the vehicle if (1) it's a physical object (a Thing) that's inside the
vehicle, or (2) one of the direction properties of the vehicle is set
to the connector. (The old logic had the undesirable side effect of
implicitly removing the traveler from the vehicle if the vehicle
couldn't cross a barrier, which was really too automatic.)
In the past, the way push-travel actions was handled caused a problem
in certain cases involving remapping of actions. The problem showed
up most readily with StairwayUp and StairwayDown objects;
specifically, PUSH x DOWN acted differently than PUSH x DOWN y, where
y was a StairwayUp or StairwayDown and the room's 'up' or 'down'
pointed to y. These commands obviously should have been equivalent.
Stairways were the only library objects that exposed the problem,
but it was possible to define your own objects with the same bad
behavior. In general, the problem occurred whenever a two-object
push-travel action (PUSH x UP y, PUSH x THROUGH y, PUSH x INTO y,
etc.) was attempted, and the indirect object's handler for the
corresponding travel action (ClimbUp, GoThrough, Enter, etc.) was
defined using asDobjFor(), and the handler for the target
action of the asDobjFor() was itself defined using a remapTo().
The problem has been fixed, so any two-object push-travel action
should now be handled equivalently to the corresponding one-object
(directional) push-travel action on the same travel connector.
The Chair, Bed, and Platform classes can now
all allow
standing, sitting, and lying on the object. In the past, Chair only
allowed sitting, Bed allowed lying and sitting, and Platform allowed
all three. The set of postures allowed can now be customized for
each individual object.
The new property allowedPostures contains a list of the postures
that the object allows. By default, Chair allows sitting and
standing, and Bed and Platform allow sitting, standing, and lying.
You can override allowedPostures for an individual Chair, Bed, or
Platform object to add or remove allowed postures. When a posture
isn't in the allowed list, it will be ruled as "illogical" in the
verification stage.
In addition, the new property obviousPostures contains a list of
the postures that are "obvious" for the object; these are the
postures that are most natural for the object. The obvious postures
are enumerated separately from the allowed postures to control
defaulting: if a posture is allowed but not obvious for an object,
then the corresponding command will be ruled as "nonObvious" in the
verify stage, ensuring that the command will only be accepted if
stated explicitly (that is, the player will be able to perform the
command, but the parser won't automatically guess the command based
on incomplete information from the player). By default, it's obvious
to sit on a chair, to sit or lie on a bed, and to sit, lie, or stand
on a platform.
The three separate classes are still three separate classes for a
couple of reasons. First, each one has a primary, "natural" posture
associated with it: sitting on a chair, lying on a bed, standing on a
platform. This primary posture is the one that the library will
assume for implied actions and incomplete commands. Second, Platform
differs from the other two in that objects dropped while standing on
a platform land on the platform, whereas objects dropped while on a
chair or bed land in the enclosing room.
Note that a posture that isn't allowed by allowedPostures will
be treated as illogical. If you want to create an object for which
a given posture makes physical sense, but isn't allowed for some
other reason (character motivation constraints, for example), you
should include the posture in allowedPostures and then disallow the
corresponding command using the check() routine.
NestedRoom now defines 'out' to use the special 'noTravelOut'
connector, which will automatically treat an OUT command as a GET OUT
OF command while in the nested room. Without this, the 'noTravelOut'
would typically be "inherited" from the enclosing room anyway, but
not always: if the enclosing room defined its own OUT, the nested
room would pick up that one instead, which isn't usually appropriate
when within a nested room. This change ensures that nested rooms
behave consistently even when nested within rooms with their own
explicit 'out' connections.
The HighNestedRoom class now prohibits exiting the room, by default.
It does this by setting the exitDestination property to nil; this
indicates that the room has no natural exit location, so it's not
possible to leave it. HighNestedRoom generates an appropriate
message ("It's too long a drop to do that from here") when an
attempt to exit the location fails.
The base NestedRoom now checks, in the GetOutOf action's check()
handler, that there is a non-nil exitDestination. If there's not a
valid exit destination, the check() handler calls the new method
cannotMoveActorOutOf() to display an appropriate message, then
terminates the command with 'exit'.
By default, NestedRoom now uses the new travel connector nestedRoomOut
for its "out" link, rather than noTravelOut as it did in the past.
The nestedRoomOut connector works just like noTravelOut, except that
nestedRoomOut is "apparent" to characters, so it shows up in the EXITS
list. Since OUT is usually a valid travel command while in a nested
room (the command exits the nested room), it makes sense to list OUT
explicitly as a possible direction while in a nested room. To
override this for a particular nested room, just set 'out' to
noTravelOut; to change this behavior for all nested rooms, do the same
thing with 'modify NestedRoom'.
The new Floorless class is a mix-in that can be combined with any
other type of Room class to create a floorless version of the room.
Mix Floorless in ahead of the Room base class in the superclass list
of the room you're creating; Floorless will, among other things,
subtract any default floor/ground object from the base room's
roomParts list.
The existing FloorlessRoom class has been retained, but is now
simply a convenience class that combines Floorless and Room. This
provides the same behavior as the old FloorlessRoom class, so existing
code should not be affected.
Some fine-tuning has been applied to announcements for cascading
implied actions in certain cases. When a series of nested implied
actions fails, the parser now announces only the first of the series.
For example, in the past, if a travel action initiated an OPEN DOOR
which initiated an UNLOCK DOOR which failed, the parser showed
something like this: (first trying to unlock the door and then open
it). The parser now shows only this: (first trying to unlock the
door).
The reason for dropping the extra announcements is that they don't
really tell the player anything useful, so they're just unnecessary
verbosity. Because of the recursive relationship of the implied
action in these cases, the first action is nested in the second,
which is nested in the third, and so on; so the second and subsequent
actions all failed as a direct result of the first one failing (in
our example, the OPEN fails because it requires the UNLOCK to
succeed, so when the UNLOCK fails the OPEN must also fail). This
means that we never actually get around to trying the second and
subsequent actions; when the first action fails, we give up on
trying any of the others. The only thing they tell the player is
why the failed action was attempted at all (in our example, the
implied OPEN explains why we're attempting the implied UNLOCK).
But the rationale in these cases is almost always obvious to the
player, so even this isn't a very good reason to keep the extra
announcements.
This behavior can be controlled via
implicitAnnouncementGrouper.keepAllFailures. The property is nil
by default; to list the entire stack of failures for these cases,
change it to true.
The extra "testing" step for opening Lockable objects has been
removed. This extra step was introduced in 3.0.6m to make the cascade
of implied actions more plausible for these cases, but the extra
verbosity was really too much, so it's been removed in the interest
of simplicity.
Instead, a Lockable now tests a new method, autoUnlockOnOpen(); if
this returns true, then OPEN implies UNLOCK, otherwise it does not.
This means that, if autoUnlockOnOpen returns nil, and the object is
locked, then an OPEN command will simply fail with an error message
("the door seems to be locked"), and the player will have to enter an
explicit UNLOCK command.
The new autoUnlockOnOpen() method is defined as follows:
- For Lockable, the method returns the value of the lockStatusObvious
property. This means that OPEN implies UNLOCK if the lock status is
readily observable, and requires a separate, explicit UNLOCK if not.
- For KeyedLockable, the method returns true if the inherited value
is true, OR if the actor is carrying a known key for the object. By
default, a KeyedLockable automatically remembers keys as known once
they're successfully used, so once an object is successfully unlocked
with a key, an OPEN command will automatically imply UNLOCK on
subsequent attempts, as long as the actor is carrying the same key.
This is a concession to playability, in that it eliminates the need
to spell out every step of operating a door or other keyed lockable
after the first time through, but it's also arguably more realistic:
the actor knows from experience how to operate the door, so we can
see the automatic UNLOCK as merely leaving some boring details out of
the narration.
Some authors might always prefer the automatic UNLOCK to forcing a
player to type a separate UNLOCK command for objects with non-obvious
locking status. Which way you prefer is a matter of taste. On the
one hand, the extra UNLOCK command is a little annoying to players,
and is exactly the sort of thing the precondition mechanism was
created to avoid. On the other hand, the automatic UNLOCK is weird
when the lock status isn't apparent, because until the OPEN fails, we
have no way of knowing that the object was locked in the first place
and thus no reason to try the implied UNLOCK; it's an instance of the
parser revealing game-state information that the player character
isn't supposed to know. It also could be seen as actually detracting
from playability by making the game do too much automatically, taking
away a certain amount of control from the player (the player could
think: "I didn't even know that door was locked; if I had, I wouldn't
have tried to unlock it right now.")
By default, the library implements the more realistic but also
more tedious behavior, requiring a separate UNLOCK to OPEN a locked
object whose lock status isn't obvious. If you prefer to make UNLOCK
automatic on OPEN for a given object, simply override
autoOpenOnUnlock() to return true unconditionally for that object; if
you prefer to make UNLOCK automatic for all objects, modify Lockable
to make autoOpenOnUnlock() return true in all cases.
The implied command behavior of keyed-lockable objects has been
changed slightly. In the past, attempting to OPEN a keyed-lockable
triggered an implied UNLOCK, which asked for a key. Thus, a command
like OPEN DOOR, or even GO NORTH, could be answered with a question
("What do you want to unlock it with?"). This was a little awkward
because the player never actually said in so many words that they
wanted to unlock the door. Now, keyed-lockable objects simply
point out that a key is required to open the lock:
>go north
(first trying to unlock the iron door)
You need a key to unlock the door.
Note, however, that if the actor knows which key to use, and one
of the known keys is in sight, then the command will be allowed to
proceed implicitly. In these cases, it won't be necessary to ask for
the key, since the actor's knowledge of the key allows it to be
supplied automatically as a default.
The Keyring class now follows the normal Thing conventions for
mentioning the contents of the keyring. Formerly, Keyring.desc
generated the list of contents, using a lister defined in the property
'examineLister'. Now, Keyring has no separate 'desc' method;
instead, the listing is generated by the standard Examine code
inherited from Thing, and the 'examineLister' property has been
renamed to 'descContentsLister' to accommodate the inherited
Thing methods.
The new class CustomImmovable makes it easy to create an Immovable that
uses a single customized message to respond to all attempts to move
the object. Just override cannotTakeMsg, and the "move" and "put in"
messages will use the same message. The new class CustomFixture does
the same thing for Fixture.
The new class FueledLightSource abstracts some of the functionality
that was formerly in Candle, to create a base class for objects that
provide light using a limited fuel supply. The new base class
doesn't assume that it's something that burns, as Candle does, so you
can use the new class to create things like limited-use flashlights.
Candle is now based on the new class. This change shouldn't affect
any existing game code; the implementation of Candle has merely been
rearranged internally to the library, so no changes should be
required to existing code.
An additional improvement in the new FueledLightSource class is
that the fuel source is no longer assumed to be the object itself.
This lets you model the fuel source as a separate object; for example,
you could use a separate battery object as the fuel source for a
flashlight, allowing a character in the game to swap out a dead
battery for a new one. Simply override the 'fuelSource' property
of your FueledLightSource object to specify the object that provides
the fuel. By default, the fuel source is still 'self', so Candle
objects in existing source code won't need any changes.
The tryMakingRoomToHold method in Actor now checks each object it's
about to move into a "bag of holding" to make sure the object that's
to be held isn't inside the object to be moved. This ensures that the
attempt to make more room to hold things doesn't inadvertantly move
the object to be held out of reach, defeating the purpose of making
room to hold it. This change ensures that the object to be held
stays where it is, since all of its containers stay where they are.
UnlistedProxyConnector.ofKind now returns true if the caller is
asking about UnlistedProxyConnector, and also returns true if
the caller is asking about the class of the underlying connector.
This makes a proxy look like its underlying connector and also like a
proxy, which is consistent with the rest of its behavior.
The new DefaultTopic property 'excludeMatch' can be set to a list of
topics (Thing or Topic objects) to exclude from the catch-all match.
By default, this is just an empty list, so nothing is excluded.
Individual DefaultTopic objects can provide a list of objects to
exclude from the catch-all. This is useful when you're creating a
DefaultTopic for a ConvNode or ActorState, but you want one or more
specific topics to be referred to the enclosing topic database for
handling. By including the topics in the excludeMatch list, you
ensure that they won't be caught in the catch-all DefaultTopic, which
will let them propagate to the enclosing topic database.
ConvNode.npcContinueConversation() now returns true if anything was
displayed in the course of the method, nil if not. This lets a caller
determine if the NPC actually had anything to say.
ActorState.takeTurn() now uses this information to consider the
NPC to have finished its turn if the ConvNode added a conversational
message. In the past, merely having a non-nil ConvNode was enough to
make takeTurn() consider the turn to be taken entirely by the
ConvNode; now, the turn is only considered finished if the ConvNode
actually wanted to say something. This allows ordinary actor-state
background scripts to continue running even when a ConvNode is
active, as long as the ConvNode doesn't generate any NPC conversation
messages.
The nestedActorAction() function now sets a visual sense context for
the new actor. (In the past, the nested action incorrectly executed
within the same sense context as the enclosing action, which allowed
actions that shouldn't have been visible to be reported, and vice
versa.)
If an implied action announcement comes immediately after other
report messages for an action in which the implied action is nested,
the command transcript now automatically inserts a paragraph start
just before the implied action announcement. It's often desirable to
invoke a nested action in the course of handling a main action, and
in some cases the nested action can trigger implied actions of its
own. This new transcript feature makes the spacing look better in
these cases when the main action reports its own results before
invoking the nested action.
Similarly, if an implied action announcement occurs after a result
message from a previous implied action, a paragraph break is inserted
before the second implied announcement. This improves readability
in these cases.
The CommandTranscript method summarizeAction() now interacts properly
with conversational responses. A change in 3.0.6m caused a problem
when attempting to summarize a series of conversational messages
(generated by TopicEntry activation); specifically, the summarizer
didn't recognize the new "in-band" conversational boundary markers
that the conversation manager started generating in 3.0.6m. The
summarizer now handles the conversation markers properly.
The AgendaItem class has a new property, initiallyActive, that
indicates that an agenda item is active at the start of the game.
During initialization, the library will automatically add each
initially active agenda item to its actor's agenda (by calling
addToAgenda() on the actor).
The new AgendaItem property agendaOrder makes it easy to control the
priority order of agenda items. The agenda list is now sorted in
ascending order of the agendaOrder values; this means that the lower
the number, the earlier the item will appear in the agenda list.
Actors choose the first item in the list that's ready to run, so
ready-to-run agenda items will always be executed in order of their
agendaOrder values. By default, agendaOrder is set to 100; set a
lower value if you want the item to go earlier than other items, a
higher number if you want it to go later. If you don't care about the
ordering, you can leave the default value unchanged.
Note that readiness is more important than agendaOrder. Suppose
that AgendaItem A has a lower agendaItem value, and thus goes earlier
in the list, than AgendaItem B. Despite this list order, if B is
ready to run and A is not, B will be executed.
The new class SuggestedAskForTopic can be mixed into an AskForTopic
object's superclass list to add the "ask for" to the topic inventory.
This works in parallel with the other SuggestedTopic subclasses.
In the past, AltTopic didn't work properly with SpecialTopic. This
has been corrected; you can now create AltTopic children of
SpecialTopic entries, and they'll work as you'd expect.
In a related change, SpecialTopic now inherits from
SuggestedTopicTree, rather than SuggestedTopic as it did in the past.
This ensures that a special topic will be suggested in the topic
inventory when any of its AltTopic children are active.
The library now defines template variations that allow defining
firstEvents lists with various TopicEntry/ShuffledTextList
combinations: TopicEntry, MiscTopic, SpecialTopic, DefaultTopic,
AltTopic. Now, if you define an instance of one of these classes,
your definition can include two lists: the first will be the
firstEvents list, and the second will be the eventList list.
The new DefaultAskForTopic class lets you define a default response
to an ASK FOR command. This new class is parallel to the other
DefaultTopic subclasses.
The various DefaultTopic subclasses are now arranged in a hierarchy
of low "match score" values. In the past, all DefaultTopics used a
match score of 1, which generally made them rank lower than any
non-default topic, but didn't differentiate among different default
topics that matched the same input topic. Now, the single-type
defaults (DefaultAskTopic, DefaultTellTopic, etc.) have a match score
of 3, the multi-type defaults (DefaultAskTellTopic, etc.) have a
match score of 2, and DefaultAnyTopic has a score of 1. This means
that a single-type default will take precedence over a multi-type
default, which will in turn take precedence over an ANY default.
This lets you define a DefaultAskTopic that will handle any ASK
command, for example, and a DefaultAnyTopic to handle everything else.
The initiateTopic() method (in Actor, ActorState, and ConvNode) now
remembers that the player character is talking to the NPC. This
ensures that commands that check the current interlocutor, such as
TOPICS, properly recognize that the characters are talking to one
another.
A problem in the library generated a spurious "you're not talking to
anyone" message under certain obscure circumstances involving an
automatic "topic inventory" scheduled at the start of a conversation.
This is now fixed.
In some cases, if an actor was in a ConvNode but didn't say anything
on a given turn, the actor was incorrectly set as the most recent
pronoun. The library was being overly aggressive about setting the
pronoun as though the actor had said something, as a convenience to
the player to respond to the actor. The library no longer sets the
pronoun unless the actor actually says something (i.e., some text is
generated in the course of the actor's turn while in the ConvNode).
In the English language module, the vocabulary initializer now
ignores extra spaces after delimiters (such as '/' or '*'). This
makes it easier to break up long vocabulary initializers across
multiple lines of code. For example:
+ Thing 'big large huge gigantic giant book/tome/volume/
text/manuscript' 'huge tome'
// etc
;
Note the line break in the middle of the list of nouns. This
introduces an extra space into the string, which is now perfectly
okay.
In the English parser, the literal adjective wildcard string has been
changed. In the past, "*" (an asterisk within double quotes) was the
wildcard character. This made it difficult to match literally an
asterisk in the input, so the wildcard is now the character \u0001
(i.e., Unicode code point 1), which is highly unlikely to be used in
input.
In the English parser, 'miscVocab' was used as a token property
instead of 'miscWord' in a few places. The correct token type
should have been 'miscWord'; this has been corrected.
The menu system now "wraps" up-arrow and down-arrow keys at the top
and bottom of a list of menu items; that is, if the first menu item
is selected and user presses the up-arrow, the selection moves to the
last menu item, and a down-arrow at the last item selects the first
item.
The library now provides a template for MenuTopicItem, with slots for
the title string, an optional heading string, and the menu contents
list.
When a locked door with lockStatusObvious was examined, a message
indicating its status (locked or unlocked) was meant to be displayed,
but wasn't. This has been corrected.
A recent change to OOPS processing (in 3.0.6j) introduced a bug:
typing OOPS after a misspelling in a response to an interactive
parser query didn't work. For example, if your answer to a
disambiguation question ("which book do you mean...") contained a
typo, you couldn't use OOPS to correct that typo. This now works
properly again.
Typing AGAIN after a SpecialTopic command didn't work in the past.
This has been corrected. AGAIN can now be used to repeat a special
topic, as long as the same comamnd is still valid as a special topic;
if the command isn't a valid special topic any longer, the parser
will simply say that the command cannot be repeated.
The new Thing method expandPronounList() allows an object to
effectively reverse the effects of filterResolveList() when a pronoun
refers to the object in a subsequent command. The parser calls this
new method on each of the objects in the "raw" binding for a pronoun
(that is, the set of program objects that were resolved in the
previous command that established the antecedent of the pronoun).
The Thing and CollectiveGroup objects use this new method to
ensure that a collective group's filtering is applied consistently
for each command, whether the command uses a full noun phrase or a
pronoun to refer to the groupable objects.
When a noun phrase in a player command refers to an object that has
"facets" (as indicated by the object's getFacets() method), and more
than one facet of the object is in scope, the parser will now
automatically choose only one facet of the object. The parser chooses
the facet that has the best transparency in the actor's sight-like
senses; if the visibilities are equivalent, then the parser chooses
the facet with the best touch transparency; if visibilities and
touchabilities are equivalent, the parser chooses one arbitrarily.
This change ensures that different facets of the same object will
never create ambiguity in the parser, which is important because a set
of facets is always meant to implement what the player sees as a
single game world object.
In a related change, the parser's method of choosing a facet as the
antecedent of a pronoun has changed slightly. First, the parser now
considers all in-scope facets when an antecedent has facets, even if
the base antecedent object is itself in scope. Second, the parser
chooses which in-scope facet to use based on the same criteria as
above: visibility, then touchability. This ensures that when multiple
facets are in scope (because of sense connectors, for example), the
most readily visible and reachable one will be selected.
The parser now selects a default antecedant for a pronoun whenever
possible, based on the objects in scope. For example, if you type
LOOK AT HIM, and you haven't established a prior meaning for HIM (by
referring to a male actor with a previous command), or the
prior meaning of HIM is no longer valid (because that actor is no
longer present), then the parser will assume that HIM refers to a
male actor who is present, assuming there's exactly one. If there
are no in-scope objects matching the gender of the pronoun, or
multiple matching objects are in scope, the parser has no way of
guessing what the pronoun means. When a pronoun could only refer to
one object, though, the parser will assume that that's the object you
meant.
In 3.0.6l, the parser started accepting HIM and HER to select a
unique gendered object from a disambiguation query ("which one do you
mean..."). This handling has been improved slightly: the parser now
treats HIM and HER as narrowing the choices, rather than
requiring a unique match. If more than one of the choices matches
the pronoun, the parser narrows the choices to the ones that match
and asks for disambiguation help again, this time offering the
reduced list of choices.
A bug in turn timing showed up when an implied command used
replaceAction(); due to the bug, such a command didn't consume a
turn. This showed up, for example, when a travel command (GO NORTH)
triggered an OPEN DOOR command, which in turn triggered an UNLOCK
DOOR command, which replaced itself via askForIobj() with an UNLOCK
DOOR WITH WHAT? command. This has now been corrected.
In the various places where nested actions are created (nestedAction,
nestedActorAction, remapTo, etc), it's now easier to specify literal
and topic objects. In the past, it was necessary for the caller to
wrap these types of objects in the appropriate internal parser
structures, such as a ResolvedTopic. This is no longer necessary:
actions that use special wrappers internally will now automatically
create the appropriate wrappers for you.
In practice, this means that you can simply use a string in any
nested action context where a literal is required, and you can use
a simple Thing or Topic object in any context where a topic is
required. Examples that formerly required special coding, but now
work in the obvious fashion:
- remapTo(TypeLiteralOn, typewriter, 'hello')
- nestedAction(AskFor, bob, goldKey);
A remapTo() object argument can now be a string, when the new verb
accepts a literal in that position. For example, you can use
remapTo(TypeLiteralOn, typewriter, 'hello') to remap to the command
TYPE "HELLO" ON TYPEWRITER.
The parser failed to announce a defaulted object under certain obscure
circumstances. In particular, if a remapTo() on a two-object action
remapped a command, and the source of the remapping was itself chosen
as a defaulted object, the announcement was missing. The announcement
will now be generated.
In libMessages, obscuredReadDesc() and dimReadDesc() incorrectly
returned a string rather than displaying a message, resulting in no
message being displayed when reading a Readable under dim or obscured
visual conditions. This has been corrected; these libMessages methods
now display their messages directly.
In the default English messages, the various messages indicating that
an actor is in the room and situated in or on a nested room (such as
sitting in a chair, or standing on a platform) have been changed
slightly. In the past, these said something like "Bob is here,
sitting on the chair." Now, the essentially pointless "here" part
has been dropped, so the messages read more simply: "Bob is sitting
on the chair."
The English library message for attempting to take an UntakeableActor
referred to the wrong object (the actor instead of the direct object)
in one of its clauses. The message has been corrected.
The English library message listing the newly revealed objects after
opening an Openable now uses specialized phrasing when an NPC performs
the action: "Bob opens the box, revealing..." The PC version hasn't
changed: "Opening the box reveals..."
The English library's default message for attempting to ask oneself
for something (ASK ME FOR...) incorrectly referred to the
substitution parameter "{iobj}" (which isn't available with an ASK
FOR command, because the second noun phrase is actually a topic
phrase). This has been corrected.
The English version of the INSTRUCTIONS command now automatically
senses the presence of certain optional conversation features and
mentions the corresponding commands only if they're used in the
game. In particular, the TOPICS command will be mentioned only
if there are SuggestedTopic instances in the game; the existence
of special topics will be mentioned on if there are SpecialTopic
instances; and the TALK TO command will be mentioned only if
there are InConversationState instances. This reduces the manual
work needed to customize the conversation system instructions;
if your game uses these features, they'll be mentioned, otherwise
they won't.
A style tag name can now contain digits, as long it starts with an
alphabetic character. (In the past, style tag names were only
allowed to contain alphabetic characters. This makes style tag
naming more consistent with the HTML tag naming rules.)
The new function overrides(obj, base, prop) makes it easy to test
whether or not the given object 'obj' overrides the property 'prop'
that 'obj' inherits from base class 'base'. This function returns
true if 'obj' derives from 'base' (that is, obj.ofKind(base) returns
true),
and 'obj' and 'base' get their definitions of 'prop'
from different places.
In the past, the library tested in a few places to see if a given
property or method was inherited from a given library base class.
This was almost the same test that overrides() performs, but
didn't work properly in cases where a library base class had been
modified by a game or library extension; when 'modify' is used to
extend a class, the defining class for methods defined in the original
(pre-modification) version of the class is the original version, not
the new version created by 'modify', which takes on the original name.
To allow for 'modify' usage, all of the tests the library used to make
along these lines have now been replaced with calls to overrides()
instead.
The SENSE_CACHE conditional compilation has been removed, so the sense
caching code is now permanently enabled. This shouldn't affect any
existing code, since I'm not aware of anyone who's wanted to use the
option to disable the sense cache.
(This is simply an internal change to eliminate some unnecessary
extra source code that's been disabled for a long time anyway. When
the sense-cache mechanism was first introduced, it was made optional,
so that any games that had problems with the caching could turn it
off. The code has been active by default for a long time now, and I'm
not aware of anyone who has found it necessary or desirable to disable
it, so there no longer seems to be any value in the added complexity
of having both options. Removing the conditional option simplifies
the source code by removing the effectively dead code for the option
of disabling the sense cache.)
Due to a bug in the parser, a run-time error occurred in certain cases
when the player entered a two-object command that was missing its
indirect object, and then answered the parser's prompt for the missing
object. This has been corrected.
The standard library tokenizer now accepts function pointers and
anonymous function pointers in the fourth slot (the converter
callback) in a token rule. The function is invoked using the same
arguments that are used when calling a method of 'self' to convert
the token value.
Released November 15, 2003
Important compatibility-breaking change #1: The 'explicit'
parameter has been removed from Thing.lookAround() and
Actor.lookAround(), as well as from the internal Thing service
methods lookAroundPov(), lookAroundWithin(), and
lookAroundWithinSense(). This extra parameter was an historical
relic that's no longer used, so it's being removed to simplify the
interfaces to these routines. Game code is likely to call
lookAround() in a few places, and might even override it, so authors
should check their existing game code and remove this parameter from
any calls or overrides.
Important compatibility-breaking change #2: The AltTopic
mechanism has been changed slightly. In the past, AltTopics were
nested one within another to form a group of alternatives. Now,
AltTopics are still nested within their parent TopicEntry, but
they're no longer nested within one another; instead, they're
simply siblings. So, for old game code like this:
// OLD WAY!!!
+ TopicEntry @lighthouse "It's very tall...";
++ AltTopic "Not really..." isActive=(...);
+++ AltTopic "Well, maybe..." isActive=(...);
++++ AltTopic "Okay, it is..." isActive=(...);
...you'd now instead write it like this:
// the new way
+ TopicEntry @lighthouse "It's very tall...";
++ AltTopic "Not really..." isActive=(...);
++ AltTopic "Well, maybe..." isActive=(...);
++ AltTopic "Okay, it is..." isActive=(...);
The only change you need to make is to remove the additional
nesting from the second and subsequent AltTopic in each group - simply
put all of the AltTopics in a group at the same '+' nesting level.
The old nesting scheme had a tendency to get unwieldy whenever a
single topic had more than one or two alternatives. The new scheme
makes game code a little cleaner when defining large AltTopic lists.
Possible compatibility-breaking change #3: The getTraveler()
methods, in both Actor and BasicLocation, now have an additional
parameter giving the connector being traversed. This allows the
routine to take into account both the actor and the connector
involved in the travel, which can be useful in some cases.
For example, you might want to set up a travel connector that
leads out of a vehicle, in which case you'd want the actor within the
vehicle rather than the vehicle to be the traveler when the connector
is traversed. In these cases, you'd override the vehicle's
getTraveler() method to check the connector, returning the actor
rather than the vehicle when the outbound connector is the one being
traversed.
In addition, for greater generality, the "actor" argument to
BasicLocation.getTraveler() (and subclass overrides) has been renamed
to indicate that it now takes a traveler rather than merely an
actor. Vehicle.getTraveler() takes advantage of this: it now
recursively asks its container for the traveler, passing itself
(rather than the traveler within itself) as the proposed traveler.
These changes allows for situations where an actor is inside a
vehicle that's inside another vehicle, for example.
Finally, Vehicle.getTraveler() now makes an additional check
before returning itself as the traveler: if the connector allows the
traveler within the vehicle to pass (as indicated by the connector's
canTravelerPass() method), but the connector doesn't allow the
vehicle itself to pass, then getTraveler() simply returns the
original traveler rather than trying to make the vehicle travel.
This makes it easy to set up an outbound connector from within a
vehicle - simply set up the connector's canTravelerPass() to exclude
the vehicle itself, and a traveler within the vehicle will
automatically leave the vehicle behind on traversing the connector.
There are a few minor interface changes in the travel mechanism, all
of which should be transparent to existing game code.
The TravelConnector method connectorBack() now takes a Traveler
object rather than an Actor as its first argument.
The TravelConnector method fixedSource() now takes a Traveler instead
of an Actor as its second argument.
The Thing method getTravelConnector() now accepts a nil 'actor'
argument. When the actor is nil, it means that the caller is
interested in the structure of the map independently of any actor's
present ability to perceive that structure. This is useful in some
cases, such as auto-mapping, where we want to know what the map is
actually like rather than merely what it looks like to a given actor
at a given time.
The internal handling of the various PushTravel action subclasses
now uses only the 'verify' stage of the action handling for the
indirect object, when there's an indirect object at all. For
example, PUSH BOX THROUGH DOOR only calls the door's
dobjFor(GoThrough) 'verify' handler, not any of its other handlers.
This change reflects the fact that the action of a push-travel is
always carried out by a nested TravelVia with the special
PushTraveler object, hence the only need we have for the indirect
object handlers at all during the initial PushTravel handling is for
disambiguation via the 'verify' routine. The old implementation was
problematic when the indirect object remapped decomposed verb, because
it incorrectly remapped the entire action before the proper nested
action could be invoked. This change corrects the problem, allowing
the PushTravel varieties to work correctly for any combination of
objects.
In the Traveler class, describeDeparture() and describeArrival()
now show their messages in a visual sense context for the traveler, if
the player character isn't involved in the travel. This ensures that
the travel is properly described (or properly not described) in
situations where the traveler is visible to the player character but
the motivating NPC isn't, or vice versa, such as when an NPC is inside
an opaque vehicle.
The Fixture and Immovable classes are now based on a new common base
class, NonPortable. This change is entirely a matter of the
library's internal organization, and is entirely transparent to game
code - Fixture and Immovable behave exactly like they did before.
The new class was introduced for two reasons. First, it
consolidates the behavior common to all non-portable objects,
eliminating a small amount of redundancy in the former arrangement.
Second, and much more importantly, it roots all of library's
non-portable object classes in a single base class. This means that
code can reliably test an object for unportability by checking
obj.ofKind(NonPortable). In the past, it was necessary to write
(obj.ofKind(Immovable) || obj.ofKind(Fixture)); not only is that test
more cumbersome, but its very form is suggestive of the potential for
additional "||" clauses in the future. The new arrangement
formalizes in the library a single root class for all unportable
objects, ensuring a clean way for the library and library extensions
to add any future specialized unportables without breaking existing
game code.
Note that this change does not break existing game code
that uses the "||" test; other than the new common base class, the
inheritance structure for Fixture and Immovable and their subclasses
has not changed. Even so, it wouldn't be a bad idea to replace any
"||" tests in your existing code with the simpler ofKind(NonPortable)
test, because this will ensure that your code takes advantage of the
insulation from future changes that the new class affords.
The Fixture class (and by inheritance, its subclass Component) now
considers an instance to be owned by a given object if the object is
the Fixture's location, or the location is owned by the object.
Since a Fixture/Component is a permanent part of its location,
it usually is appropriate to consider the component to be owned
by anything that owns the location, as well as to consider the
component owned by the location itself.
The new mix-in class Attachable (in extras.t) makes it easier to
define objects that can be attached to one another, such as a hose
and a faucet, a plug and an electrical outlet, or a rope and a
railing. Attachable is meant to be combined (using multiple
inheritance) with Thing or a Thing subclass. The class provides a
number of methods that you can override to control which objects can
be attached to one another, and the special effect of enacting out
those attachments.
The Attachable class provides default handlers for AttachTo,
Detach, DetachFrom, and TakeFrom, but it's relatively easy to define
additional, customized action handlers in terms of these default
actions using the standard 'remapTo' mechanism. For example, if
you were defining a rope, you could define a TieTo action, and then
map TieTo to AttachTo in your rope object.
The new classes PermanentAttachment and PermanentAttachmentChild
are subclasses of Attachable for cases where you describe objects as
attached in the story text, but you don't want to allow the objects
to be detached from one another. Describing objects as attached
tends to invite a player to try to detach them; these classes lets
the objects provide customized responses to the player's attempts,
without actually allowing the objects to come apart. (These classes
aren't intended for mere components; the Component class is adequate
for that. PermanentAttachment is for situations such as a ball
attached with a string to a paddle, where essentially separate
objects are conspicuously attached to one another.)
The new mix-in class PresentLater makes it easy to set up objects that
aren't in the game world initially, but will appear later in response
to some event. For example, you might want to create a hidden door
that isn't part of the game world until the player casts the DETECT
HIDDEN DOORS spell, or you might want to create a pile-of-leaves
object that shows up at the foot of a tree after the player shakes the
tree.
The traditional way of programming these sorts of objects was to
create the object with a 'nil' initial location, then use moveInto()
to move the object into the game upon the triggering event.
PresentLater provides an alternative. To use PresentLater, you set up
the object in its eventual location, as though it were going to be
there from the start, but you add PresentLater at the start of the
object's superclass list. During pre-initialization, the library will
remember the object's starting location, and then move the object to
'nil', effectively removing it from the game world. Later, when you
want to the object to appear, simply call makePresent() on the object,
and it will automatically move to the eventual location that was noted
during start-up.
The advantage of using PresentLater is that you define the object's
eventual location as part of the object itself, as though it were an
ordinary object; this means that you don't have to specify its
eventual location as part of some method (in a moveInto call)
elsewhere in the source code. Another advantage is that you can use
the makePresentByKey() method to bring a whole set of related objects
into the game world at once: you can give each PresentLater object a
"key" property, which is just an arbitrary value you choose to
identify a group of objects, and then bring all objects that have that
key into the game world at once.
The new class Unthing represents the absence of an object. This
class is useful for the occasional situation where a player is likely to
assume that an object is present even though it's not. For example,
sometimes an object becomes hidden, but a player might not notice that;
rather than letting the regular parser error handle it, we can create
an Unthing that explains that the object can no longer be seen.
Unthing is a simple subclass of Decoration, with a customized default
handling message.
The travelerDirectlyInRoom precondition object has been replaced with
a new TravelerDirectlyInRoom class. This change allows the caller to
create the precondition object with the full set of information it
needs via the class's constructor. This change is probably
transparent to existing game code, as game could would have little
reason to use this precondition.
The class Thing now defines the method checkTravelerDirectlyInRoom();
the default implementation simply defers to the container. This
ensures that connectors that are defined inside ordinary objects (for
example, a hole in a bookcase) will properly ensure that a traveler is
in the Thing's containing Room or NestedRoom before travel. In the
past, the absence of this method meant that there were no conditions
at all on a traveler's initial location in such cases.
The dropDestinationIsOutRoom precondition now moves the actor,
rather than the traveler (to the extent that they differ), to the
outer room when needed. (Since the actor is attempting to reach the
outer room in these cases, it makes more sense to move the actor,
rather than any vehicle the actor is inside.)
Similarly, the Floor object's SitOn and LieOn handlers now apply a
precondition requiring the actor, rather than the traveler, to be in
the room directly containing the Floor object.
The new VocabObject method getFacets() returns a list of "facets"
of an object. These are other objects that share the same parsing
identity; for example, the two sides of a door are facets of the
same physical object, from a character's perspective, so each side
would return a list containing the other side from getFacets().
In fact, the Door class overrides getFacets() to do just this.
The parser uses getFacets() to resolve an out-of-scope pronoun
antecedent, if possible. If the player uses a pronoun, and the
antecedent is out of scope, the parser calls the antecedent's
getFacets() method, and looks for a facet that's in scope; if it
finds an in-scope facet, the parser uses that facet as the
replacement antecedent. For example, if you type OPEN DOOR, GO
THROUGH IT, THEN CLOSE IT, the parser will now correctly resolve the
second "it" to the door. In the past, the second "it" wasn't
resolvable, because it referred to the side of the door that's in the
other room and thus out of scope after the travel. Using the new
facet list capability, the parser can now resolve the second "it"
to the side of the door that is in scope.
(If more than one new facet of the object is in scope in these
cases, the pronoun resolver will take the one that's most readily
visible - that is, the one that has the most "transparent" sense path
in the actor's visual senses. If all of the in-scope facets are
equally visible, the resolver just picks one arbitrarily.)
By default, VocabObject.getFacets() simply returns an empty list.
The Door and Passage classes override the method to return a list
containing the linked object representing the other side of the door
or passage, if there is one. The new MultiFaceted object returns the
list of facet objects that it synthesizes; each facet instance of
a MultiFaceted does the same thing.
You can customize getFacets() whenever you use multiple objects
with a shared parser identity. One common case where you might want
to do this is "object transmutation," where you substitute one object
for another in order to effect a radical change in the setting. For
example, you might have one object representing a wooden dining
table, and another representing the pile of splintered wood that an
enraged chef turns it into after the guests insult his cooking. In
this case, you could override getFacets() in the table so that it
returns the pile-of-splintered-wood object.
The class AutoMultiLoc has been removed, and its capabilities have
been rolled directly into the MultiLoc class itself. All of the
initialization properties and methods (initialLocationClass,
isInitiallyIn, and buildLocationList) have been moved into MultiLoc,
and they work the same as they used to. There really wasn't any need
for a separate AutoMultiLoc class, since the two styles of
initialization (enumerated and rule-based) can coexist quite easily in
the single class.
Along the same lines, the DynamicMultiLoc class has been removed,
and its functionality has been rolled into MultiLoc. In particular,
the reInitializeLocation() method is now part of the base MultiLoc
class.
If you have any AutoMultiLoc or DynamicMultiLoc objects in your
existing game code, you should simply change the AutoMultiLoc or
DynamicMultiLoc superclass to MultiLoc. The objects should then
behave the same as they did before.
MultiLoc.moveOutOf() caused a run-time error due to a mismatched
call to notifyRemove(). This is now fixed.
The new classes MultiInstance and MultiFaceted are useful for cases
where you want to create an object in multiple locations, but
MultiLoc isn't suitable. A MultiLoc is suitable when a single object
is contained
entirely and
simultaneously in more than
one location; when a single large object spans several locations, or
when you simply want to duplicate a ubiquitous background object in
several places, the new classes are better.
MultiInstance is good when you want to duplicate a ubiquitous
background object in multiple locations: trees in a forest, the sun
in outdoor locations, crowds in the street. MultiFaceted is for
large objects that span many locations, such as rivers or long ropes.
MultiLoc isn't ideal for these sorts of cases because the sense
system treats a MultiLoc as a single object that's entirely in all of
its locations at once; this means, for example, that if it's lit in
one place, it's lit everyplace.
MultiInstance and MultiFaceted work mostly like MultiLoc from the
programming perspective. Internally, though, rather than appearing
itself in each of its locations, a MultiInstance or MultiFaceted
creates a separate "instance" object for each of its locations. Each
instance is an ordinary singly-located object, so the library creates
one per location. The instance objects are instances of a template
that you define as part of the MultiInstance or MultiFaceted object.
The new classes present the MultiLoc multi-location interface, so
you can add and subtract locations as needed, using the usual
routines: moveInto(), moveIntoAdd(), moveOutOf(). You can also set
the initial set of locations as you would for a MultiLoc: you can set
locationList to the list of initial locations, or you can define
initialLocationClass, isInitiallyIn, and/or buildLocationList.
The instances of a MultiInstance or MultiFaceted are all
essentially identical, so these classes are only good for relatively
homogeneous objects. These classes aren't appropriate for cases
where you want to present distinctive aspects of an object, such as
the different sides of a large building, or distinctive individual
objects, such as different people in a crowd. When the individual
parts are distinct, you're best off just creating separate objects
for the different instances. The new classes are mainly for
convenience in cases where you'd otherwise have to repeat an
identical object definition in several locations.
The new pre-conditions dobjTouchObj and iobjTouchObj can be applied
to the indirect or direct object (respectively) of a two-object
action, to require that the one object can touch the other. This is
useful for actions where the actor manipulates one of the objects
directly, but manipulates the other object only indirectly using
the first object.
For example, PUSH COIN WITH STICK would be a good
place to use iobjTouchObj as a direct object precondition: the
actor is manipulating the stick directly, so the actor has to be
able to touch the stick, but the coin only needs to be reachable
indirectly with the stick. Another example: PLUG CORD INTO OUTLET
requires direct manipulation of the cord, but only the cord needs
to touch the outlet, so this would be a good case for dobjTouchObj
as a precondition on the indirect object.
In the class Thing, the default direct object handlers for the
actions MoveWith, TurnWith, ScrewWith, and UnscrewWith now use the
iobjTouchObj condition rather than touchObj condition. These verbs
all generally model interactions where the direct object only needs
to be reachable through the indirect object. The new class Attachable
uses dobjTouchObj as a precondition on the indirect object.
To enable the new iobjTouchObj condition, the TouchObjCondition
class can now handle a yet-unresolved source object during the
verification stage. When the source object is nil, the pre-condition
simply skips its verification stage. This allows a TouchObjCondition
to be applied across objects for a two-object action: for example, it
allows you to apply a condition to the direct object that the indirect
object can touch it.
The new preconditions sameLocationAsIobj and sameLocationAsDobj let
you require that a given object is in the same immediate location as
the other object of a two-object command. You use sameLocationAsIobj
as a precondition on a direct object, and sameLocationAsDobj as a
precondition on an indirect object. Both of these condition objects
are based on the class SameLocationCondition, which you can construct
dynamically to require the target object to be in the same location as
an arbitrary second object, which need not be directly involved in the
command.
These conditions use the new Thing method tryMovingObjInto(obj),
which tries to move the given object 'obj' into 'self'. This method
is customized in Room, Container, Surface, and Actor to generate
appropriate implied commands for those classes, and you can override
it in your own objects as needed.
The new Actor method endConversation() effectively lets an NPC say
GOODBYE of its own volition, ending a conversation without waiting
for the player to leave or say GOODBYE. This is the complement of
Actor.initiateConversation: it lets an NPC initiate the end of a
conversation.
The new conversation-ending code endConvActor indicates that we're
ending the conversation of the actor's volition.
The new ConvNode method noteLeaving() is called when the conversation
node is about to become inactive. This is the complement of
noteActive(). This method doesn't do anything by default, but
instances can use it to trigger side effects when leaving the node.
The conversation manager's mechanism that keeps track of conversation
responses has been changed to make it better able to handle multiple
conversations on a single turn. In particular, the conversation manager
now flags the start and end of each response text in the text stream.
Because the "transcript" subsystem captures displayed text
and defers its actual output until later, difficult synchronization
problems arose if a game tried to trigger multiple responses from
different actors on a single turn. This change should be transparent
to existing game code.
TopicEntry has a new method, setTopicPronoun(), that tries to set a
pronoun antecedent when the topic is matched. The default
handleTopic() method automatically calls the new method.
It's not always possible for setTopicPronoun() to guess about an
antecedent for a topic phrase match, because TopicEntry instances can
match more than one game object, and topic phrases by their nature can
refer to more objects than are present visually. The new method will
set a pronoun antecedent for the topic if the topic phrase in the
player's input yields one object when "intersected" with the
TopicEntry's 'matchObj' list. The method first looks only at in-scope
objects, then looks to the "likely" list, but only if there are no
in-scope matches. If the intersection yields more than one object,
the library doesn't set a pronoun antecedent at all, since the match
is ambiguous.
Since this heuristic procedure can't always decide on an
antecedent, you might occasionally want to set the antecedent
explicitly in a particular TopicEntry. You can do this by overriding
the TopicEntry instance's setTopicPronoun(fromActor,topic), in which
you'd call fromActor.setPronounObj(obj), where 'obj' is the game
object you want to set as the antecedent.
In the past, when an "again" command was used to repeat a command
directed to a non-player character ("bob, go east"), the turn counter
incorrectly advanced by two turns. This was due to an error in the
mechanism that allows one actor to wait for another to finish a
command. This is now fixed.
A separate problem prevented an undo savepoint from being created
for an "again" command repeated a command directed to another
character. Since no savepoint was created, typing "undo" after such
an "again" command took back not only the "again" turn but the turn
before it as well. The savepoint is now created properly.
Due to a bug, the conversation mechanism didn't enter a conversation
(i.e., didn't show the "greeting protocol") if a DefaultTopicEntry was
used for a response. This has been fixed.
As part of this change, the ActorTopicEntry class, which was
introduced in the refactoring of the TopicEntry class introduced in
3.0.6l, has been eliminated. On further consideration, the
bifurcation of TopicEntry into actor-associated and
non-actor-associated subtypes wasn't very clean, since it implied a
similar split for some of the other classes that work with TopicEntry,
notably AltTopic and DefaultTopic. This was too unwieldy.
So, ActorTopicEntry has been removed. In its place, the "topic
owner" abstraction, which was introduced in 3.0.6l, has been used
throughout the base TopicEntry class to replace the need for an
associated actor. This means that the base TopicEntry can do
everything that it did before (and everything that ActorTopicEntry did
before), but without any assumption that there's an associated actor.
The getActor() method is still present, but it's now strictly for the
convenience of game code; the library no longer assumes that topic
entries to be associated with actors. The TopicEntry.getActor()
method simply returns the topic owner if it's of class Actor,
otherwise nil.
These changes should have no impact on existing game code, since
the ActorTopicEntry was intended as an internal class structure only.
A bug in the TopicResolver class caused a run-time error for
a command like ASK someone ABOUT ALL. This is now fixed.
When the ConsultAbout action picks a default consultable object based
on the actor's last ConsultAbout command, the action now marks the
defaulted object as such, which triggers the usual announcement of
the default object.
The typographicalOutputFilter object (in en_us.t) is now a little
smarter about what it considers sentence-ending punctuation. If a
lower-case letter or a hyphen of some kind follows what the filter
would otherwise take for sentence-ending punctuation, it doesn't treat
it as a sentence ending. This gives better results in the common case
of an exclamation point or question mark within a quoted passage:
"'Who are you?' he asked." It also helps in some less common
cases, such as when an exclamation point is embedded in a sentence,
such as in an interjection set off by dashes ("And then -- oh no! --
I dropped it").
The PendingCommandInfo class has been refactored into a couple of
subclasses for a cleaner class structure. PendingCommandInfo is now
an abstract base class; the new concrete subclasses
PendingCommandToks, PendingCommandAction, and PendingCommandMarker now
implement the specialized behavior that was formerly embedded in the
single class and handled conditionally. These classes are mostly for
internal use in the library, so this should have no impact on any
existing game code.
Some messages in BasicContainer have been modified slightly to remove
the assumption from the base class that it's an object that can be
opened. First, the new properties cannotTouchThroughMsg and
cannotMoveThroughMsg give property pointers, referring to
playerActionMessages properties, that specify the messages to use
when an object cannot be reached or moved through the container's
containment boundary. By default, these now refer to the new messages
cannotTouchThroughContainer and cannotMoveThroughContainer, which don't
say anything about the container being closed. Second, BasicOpenable
class overrides these two new properties to refer to the original
cannotTouchThroughClosed and cannotMoveThroughClosed messages.
Third, the tryImplicitRemoveObstructor() method that was formerly
in BasicContainer has been moved to BasicOpenable instead, eliminating
the assumption in BasicContainer that the obstruction can be removed
by opening the container.
Note that these changes are designed in such a way that objects
based on both Openable and Container (either by mixing the two
superclasses, or by using the library class OpenableContainer) should
be unaffected. Objects based on Container or BasicContainer, without
any Openable mix-in, should now behave more logically, in that they
won't assume anything about being openable.
The touchObj precondition incorrectly reported two failure messages
if an implied command failed trying to remove an obstruction to touch.
The failure message of the implied command itself was shown, and a
separate failure message explaining that the target object isn't
reachable was also shown; in such cases, only the implied command
failure should have been reported. The precondition now omits the
redundant second message.
KeyedLockable is now more flexible about the "known key list" and who
owns it. In the past, the "master object" of a linked lockable (the
master object of a two-sided door, for example) always owned the known
key list; this made it impossible to have separate keys operate the
two sides of a door, and also created a dependency on which side of
the door was the master. Now, KeyedLockable instead uses the local
side's known key list if it has one, and only uses the master side's
list if either the local side has an empty list or the master side has
no list; only if the local side has an empty list, and the master side
has a list, is the master side used. This makes initialization of
two-sided doors much more flexible, because it no longer matters which
side is the master for the purposes of the known key list.
In 3.0.6j, we introduced the notion of whether or not the status of a
Lockable is known. The idea was that actors shouldn't just
automatically unlock a door on trying to open it unless they have
some reason to know the door is locked in the first place.
Unfortunately, this mechanism took away the important playability
convenience feature that automatically unlocks locked doors when it's
obvious how to do so.
This mechanism is now replaced with something a little more
subtle, which solves the same problem without any loss of convenience
for the player. Instead of letting the original OPEN command fail on
encountering a door that's locked but not previously known to be
locked, the library now inserts a "testing" action into the sequence
ahead of the implied UNLOCK. The "testing" action represents the
actor's first attempt to open the lockable, as in attempting to turn
a locked doorknob. This first phase fails, but instead of letting
the rest of the command fail, we consider the actor to have
immediately learned from the first phase that the object is
locked, so we proceed directly to an implied UNLOCK action. The
result looks like this:
>go north
(first trying the door and finding it locked, unlocking it, then
opening it)
Note that the lockStatusObvious property of the former scheme
still applies. If lockStatusObvious is true, then the extra
"testing" phase is omitted, since we can tell just looking at the
object that it's locked. Note also that the "testing" phase is
only used when the object is actually locked; when it's not locked,
the OPEN command succeeds, so there's no need to split the OPEN
action into the two phases.
The Door class now uses a custom cannotTravel() message, rather than
using the default message for the location. The message explains that
the travel is not possible because the door is closed; the generic
cannot-travel message for a room simply claims that the travel is
impossible, which isn't as specific as we can be about it.
The Openable class has a new method, openStatus, that makes it easier
to override the open/closed status addendum that's shown as part of
the object's description. The openableContentsLister now calls this
method to show the status; the default implementation just says "it's
open" (or equivalent, adjusted for gender and number). An Openable
can override this to customize the message as needed.
The Passage class's initialization of a two-sided relationship has
been refined slightly. In the past, the non-master side of a
two-sided passage explicitly set its master object's otherSide
property to point to the non-master side. Now, instead of setting
the property directly, the non-master side calls the master object's
initMasterObject() method with the non-master side as the parameter;
by default, initMasterObject() simply sets 'otherSide' to the other
object. This makes it easier to customize the initialization for
cases where more than two objects are involved in a passage
relationship, or where the passage relationship is dynamic, by
allowing the master object to directly control its own initialization.
In the Candle class, when the object runs out of fuel, it now displays
its "burned out" message before actually cutting off its light
source (by marking itself as unlit). In the past, the order of these
operations was reversed, which caused the library to suppress the
"burned out" message when the candle was the only light source. The
message was suppressed because these operations are performed in a
daemon that runs in the "sight" sense context of the candle itself;
once the candle is marked as unlit, it becomes invisible when there's
no other light source, and so any message displayed in its sight
context is suppressed. By showing the message while the candle is
still marked as lit, we ensure that the message will be seen as long
as the candle is in view of the player character.
The Readable class now generates its "obscured" and "dim" default
descriptions properly. A bug formerly caused a run-time error when
attempting to read a Readable under these sense conditions.
The menu system now includes an automatic sorting mechanism,
which sorts a menu's entries into a game-defined order each time the
menu is displayed. The default initializeContents() method in the
MenuObject class does the sorting, based on the new 'menuOrder'
properties of the items in the menu. The 'menuOrder' property is
an integer value giving the relative order of an item among its
siblings. By default, 'menuOrder' returns the 'sourceTextOrder'
value for the menu item, so the default ordering is simply the
original ordering of the items in the source file.
In the English module, the new property isQualifiedName generalizes
the function of the existing isProperName property. isProperName
indicates that a 'name' property is "proper," meaning it's the name
of a person or place - the kind of noun that's usually capitalized in
English. Proper names don't require "qualification" when they appear
in sentences, which basically means that they don't need articles
like "the" or "a", because they're inherently definite. The new
isQualifiedName property can be set to true for an object when the
name is fully qualified, but isn't proper. By default, a proper name
is considered qualified, but the two properties can be set
independently as needed.
An example of a non-proper but qualified name is "your book."
This isn't the proper name of the book, but the possessive pronoun
makes it fully qualified, making it incorrect to add an article when
using the name in a sentence.
In practice, at the moment, "qualified" and "definite" have the
same effect, which is to omit any articles when the name is used in a
sentence. The new property is intended to separate the two concepts
in case there's a need to distinguish them in the game or in future
library changes.
The English parser has a new bit of fine-tuning in its verb phrase
chooser for a particular type of ambiguous phrasing. In certain
sentences in English, a preposition can be interpreted either as part
of a noun phrase or as a structural part of the verb; for example,
DETACH STRING FROM BOX could be interpreted either as a verb with two
objects (STRING as the direct object, BOX as the indirect object), or
as a verb with a single object (STRING FROM BOX as the direct
object). English speakers will almost always assume the two-object
interpretation automatically in cases like this; English is such a
strongly positional language that the verb phrase structure tends to
dominate everything else in a sentence. In the past, the parser
didn't care one way or the other, so when both interpretations were
valid, the parser sometimes arbitrarily chose the much less probable
single-object version. The parser now takes the verb structure
dominance into account by preferring the intepretation with more
"noun slots" in the verb phrase, whenever there's ambiguity.
The English parser's tokenizer now treats ampersands essentially as
though they were alphabetic characters for the various kinds of
"word" tokens. Ampersands have no other meaning to the standard
parser, and show up every so often in names of things, so it makes
sense for the parser to accept them as parts of words.
Note that an ampersand is treated as alphabetic, not as a
punctuation mark. This has two implications that might be
counterintuitive, at least to the extent that ampersands look to most
people like punctuation marks. (In fact, they're not punctuation
marks at all; they're actually ligatures of "et," Latin for "and,"
although the glyph has become so stylized in modern typefaces that
it's hard to see that unless you know what to look for.) First, an
ampersand is not a token separator, so a sequence like
"T&V" will be read as a single token. Second, when an ampersand
is surrounded by whitespace (or other token separators), so that it
does count as a separate token, it'll be parsed as a "word" token,
not as punctuation. For example, "Bob & Sons" is read as three
"word" tokens. This means that you can use a lone "&" as an
adjective or noun in defining an object's vocabulary.
In the English module, Thing.conjugateRegularVerb() incorrectly
applied an "-ies" ending to verbs ending in a consonant plus a "y",
such as "say". This affected routines like itVerb() and nameVerb().
The routine now uses a regular "-s" ending when a vowel precedes
a terminal "y".
The parser now allows possessive pronoun adjectives ("her book") to
refer back to the previous noun phrase in the same action. For
example, ASK BOB ABOUT HIS HAT will treat "his" as referring to Bob.
This is accomplished by first trying to find an earlier noun phrase
in the same action that matches the pronoun in number and gender. If
we find a match, we use it, otherwise we use the ordinary pronoun
antecedent from a previous command. Note that the number/gender
match ensures that we don't get overzealous in applying this: if we
type ASK BOB ABOUT HER BOOK, we find that BOB doesn't match HER, so
we look to the meaning of HER from a previous command.
As part of this change, the Action routine that was formerly
called getReflexiveBinding has been renamed to getAnaphoricBinding.
The new name better reflects the more generalized function, since it
can be used for other kinds of anaphoric bindings besides reflexives.
This is an internal method that is highly unlikely to be
used in any game code, so this change should be transparent.
("Anaphor" is the linguistic term for any sort of reference to an
earlier noun phrase in a sentence; a reflexive is a particular kind
of anaphor that re-uses a noun phrase from an earlier "slot" in a
predicate to fill another slot in the same predicate, such as HIMSELF
in ASK BOB ABOUT HIMSELF. The reason reflexives exist in English and
other languages is that they're unambiguously anaphoric: in ASK BOB
ABOUT HIMSELF, HIMSELF can only refer to Bob, whereas the HIM in ASK
BOB ABOUT HIM almost certainly refers to someone other than Bob from
a previous sentence or clause. The name change in the method
reflects the fact that there are other kinds of anaphoric constructs
besides reflexives; in ASK BOB ABOUT HIS BOOK, the HIS refers
anaphorically to BOB, but it's not reflexive.)
In Actor.executeActorTurn(), when a pending response is delivered,
the actor now clears its memory of the pending response. (In the
past, the pending response wasn't cleared, so an NPC with a pending
response would keep delivering the pending response over and over.)
When AskFor changed to a TopicTAction in 3.0.6l, the Actor handler for
GiveTo was affected but wasn't updated properly. The GiveTo handler
changes a GiveTo command directed to another actor into an AskFor
command (BOB, GIVE ME THE CROWBAR becomes ASK BOB FOR CROWBAR), so the
AskFor change required this rephrasing to be cast in the new
TopicTAction terms. This change wasn't made, so the GiveTo handler
didn't work properly. This has now been fixed.
Actor.desc no longer shows the postureDesc as part of the description
of an NPC. Instead, this is shown in examineStatus(). This change
shouldn't affect any existing code; it merely moves around where the
calls are made, but still makes all the same calls in the same
sequence. The change makes it easier to override an Actor's 'desc'
without changing the overall display format.
A bug in the sense system prevented a self-illuminating object (an
object with a brightness of 1) from applying its self-illumination to
its own interior surface; the illumination was considered external
only. This was inconsistent with the normal sense-transmission
rules, and it's now been corrected. A self-illuminating object is
now considered to have an ambience level of 1 on both its inner and
outer surface.
A subtle bug in the sense system caused ambient sense energy levels
to be transmitted incorrectly into the interior of objects with
non-transparent fill media. In particular, the sense system
incorrectly applied the fill medium to the ambience level at the
inner surface of an object as transmitted from the outside of the
object. This was incorrect because it meant that a viewer on the
inside of an object saw the ambient level to the object adjusted
twice for the fill medium: once for the incorrect adjustment
at the inner surface, and once more for the path from the viewer,
through the fill medium, to the inner surface. This has been
corrected: the ambient level arriving at the inner surface of an
object from outside the object is now calculated without any
adjustment for the fill medium, and any further transmission inward
is then adjusted for the fill medium.
This problem showed up by making an object invisible from its own
interior when the object had a non-transparent fill medium, and the
light coming in from outside wasn't bright enough to penetrate the
fill medium twice. For example, a transparent booth filled with
attenuating fog, with external normal illumination (brightness 3),
was not visible from its interior because of the double traversal
of the fog. Such a booth is now visible from its interior.
When looking for an object's default associated Noise or Odor, the
Thing methods getNoise() and getOdor() now only consider objects with
an active "presence" in their sense. That is, getNoise() will only
return a Noise object with a non-nil soundPresence property value,
and getOdor() will only return an Odor with a non-nil smellPresence.
This makes it easier to turn an object's associated sense data on and
off, since you can simply use the presence properties of the Noise
and Odor objects.
In 3.0.6j, CollectiveGroup stopped matching singular phrases and
phrases with quantities specified (as in "five coins"). This behavior
hasn't changed, but it's now easier to control, with the new method
isCollectiveQuant(). This method returns true if the given "required
quantity" allows using the collective, nil if not. By default, this
returns true if and only if there's no specific quantity needed (in
which case the quantity parameter is nil).
RandomTextList and ShuffledTextList can now handle arbitrary event
entries in their lists, using the same rules as the base EventList
class. (In the past, only strings and nil values were allowed in
these specialized subclasses, but they now use the base class handling
to carry out each event, so they can now handle any sort of event the
base class can.)
Released October 4, 2003
The actor knowledge and "sight memory" systems have been revamped to
make it easier to keep track of individual knowledge for different
actors.
A few key interfaces have changed, so existing games will
have to make corresponding changes.
First, the main interface changes:
- obj.seenBy(actor) is now actor.hasSeen(obj)
- obj.setSeenBy(actor, stat) is now actor.setHasSeen(obj)
- obj.isKnownBy(actor) is now actor.knowsAbout(obj)
- obj.setKnownBy(actor, stat) is now actor.setKnowsAbout(obj)
Note that the "stat" parameter has been removed from the two "set"
methods. This parameter was essentially superfluous, since it's rare
to the point of non-existence for a game to want to mark something as
un-seen or unknown after it's been previously seen or known; the new
methods elide this parameter and act like the old methods acted with
the parameter set to true.
You have a choice of how to update your existing game code to the
new interfaces. The first option is to do the search-and-replace
operations in the list above throughout your game's source code; this
is the recommended option, because it'll make your code consistent
with future documentation. The second option is to modify the
VocabObject class to reinstate the old methods, defining them in
terms of the new ones:
modify VocabObject
seenBy(actor) { return actor.hasSeen(self); }
setSeenBy(actor, flag) { actor.setHasSeen(self); }
isKnownBy(actor) { return actor.knowsAbout(self); }
setKnownBy(actor, flag) { actor.setKnowsAbout(self); }
;
Second, how do the changes facilitate separate NPC knowledge
tracking? The change here is that the Thing and Topic 'seen' and
'isKnown' properties that underlay the old system are still used, but
are now only defaults for tracking the seen/known information.
The actual properties used to track the information are determined on
an actor-by-actor basis, using the new Actor.seenProp and
Actor.knownProp properties.
By default, Actor defines seenProp as &seen, and knownProp as
&isKnown. This means that, by default, there's only one
"global" set of knowledge. To track an NPC's knowledge individually,
simply set seenProp and/or knownProp to new properties unique to that
actor. For example:
bob: Person
// ... other definitions...
seenProp = &bobSeen
knownProp = &bobKnown
;
This specifies that 'bob' will individually track its knowledge,
separately from any other actors. When you call bob.setKnowsAbout(obj),
the library will examine obj.bobKnown and obj.bobSeen, because those are
the properties that track bob's knowledge. Note that this individual
tracking is completely automatic once you define seenProp and knownProp,
since the Actor methods hasSeen, setHasSeen, knowsAbout, and setKnowsAbout
all use seenProp and/or knownProp to do their work.
Note that because the default values in Actor for seenProp and
knownProp are (respectively) &seen and &isKnown, everything
works roughly the same way it did before if you don't override these
properties. In particular, if you don't care about tracking
individual NPC knowledge, which typical games probably won't, you
only have to worry about 'seen' and 'isKnown' as before. So, if you
want to initialize an object as pre-known, before the start of the
game, simply set isKnown=true for that object as in the past.
Third, a minor enhancement: the GIVE and SHOW iobjFor() handlers
in Actor now automatically mark the object being shown, and its
visible contents, as having been seen by the actor being shown the
object. For games that track NPC knowledge separately, this will
ensure that the target of a GIVE or SHOW takes note of having seen
the object in question.
A new class, Consultable, can be used to implement things like books
and file cabinets: inanimate objects in which an actor can look up a
topic, to find some information. This class works almost exactly
like the Actor conversation system. Another new class, ConsultTopic,
serves as the TopicEntry subclass for Consultables. Simply create
one or more ConsultTopic objects, and locate them within the
Consultable, to populate the consultable object's database of
responses.
In support of the new Consultable class, the TopicDatabase and
TopicEntry classes have been refactored a bit. In particular, these
two classes no longer assume that they're associated with an actor,
and no longer assume that they're involved in conversation. The
parts of the former implementations that made assumptions about actor
or conversation associations have been moved into a pair of new
subclasses, ActorTopicDatabase and ActorTopicEntry. The existing
classes that were based directly on TopicDatabase and TopicEntry are
now based on the new ActorXxx subclasses instead. For almost all
existing game code, this change should be entirely transparent,
because the final subclasses that most games use still have the same
names and work the same way they did before; the only changes are
some internal reshuffling to abstract out most of the functionality
into the new base classes.
In addition, the TopicResolver class has a new subclass,
ConvTopicResolver, that's used for the conversational actions (ASK
ABOUT, TELL ABOUT). The base TopicResolver doesn't differentiate
at all among topics; it simply considers everything to be an
equally good match. The ConvTopicResolver differentiates topics
into three sublists, as it did in the past: objects that are in
physical scope or are known to the actor performing the command;
objects that are "likely" topics, as indicated by the performing
actor's isLikelyTopic(); and everything else.
The ConsultAbout action in the English module now adds grammar for
some incomplete forms, such as "look up (topic)". If the player
enters one of these incomplete forms, the parser will prompt for
the missing object in the usual fashion.
Similarly, when the direct object is missing from a CONSULT ABOUT
command, the default is the last object consulted by the same actor,
as long as that object is still visible. The new Actor property
lastConsulted keeps track of the last object consulted; the new Actor
method noteConsultation() sets this property. The Consultable class
calls gActor.noteConsultation(self) in its ConsultAbout action()
handler. This saves the player some typing, since they can leave out
the object to be consulted when they're looking up a series of topics
in the same consultable (they can type simply LOOK UP BOB, for
example).
The TopicEntry scoring system now uses nil to represent a non-match.
This means the valid scoring range now includes zero and negative
values, which in turn means that it's no longer a problem if
matchScoreAdjustment values reducing scores to zero or below. This
makes it easier to use score adjustments, since there's no longer any
need to worry about interactions between score values and adjustment
values taking a final score out of range.
The AskFor action is now a TopicTAction instead of a TIAction. That
is, the indirect object is now a topic rather than an ordinary
resolved object. The TIAction implementation was never really
appropriate, since ASK FOR conceptually needs topic-oriented rules
for the indirect object: one needs to be able to ask for things that
aren't in scope, as well as for abstract topics (ASK FOR HELP, ASK
FOR DIRECTIONS).
This change should have little or no impact on existing games.
The new TopicEntry subclass AskAboutForTopic can be used to make a
single topic entry respond to either ASK ABOUT or ASK FOR for a given
game object or objects (read the name as "ask about/for topic").
Similarly, AskTellAboutForTopic can respond to ASK ABOUT, TELL ABOUT,
or ASK FOR (read the name as "ask/tell about/for topic," although the
TELL FOR combination implied in that reading doesn't make any sense,
obviously).
The limitSuggestions property now applies to ActorState objects
as well as to ConvNode objects. Suggestions are effectively arranged
into a hierarchy: the top level of the hierarchy is the ConvNode, the
next level is the ActorState, and the bottom level is the Actor.
The full list of suggestions consists of the ConvNode's suggestions,
plus the ActorState's suggestions, plus the Actor's suggestions.
However, if limitSuggestions is true at any level of the hierarchy,
then suggestions below that point are not included in the full list.
So, if the ConvNode's limitSuggestions property is true, only the
ConvNode suggestions are included. If the ConvNode's limitSuggestions
property is nil, but the ActorState's limitSuggestions property is
true, then the ConvNode and ActorState suggestions are both included.
If limitSuggestions is nil for both the ConvNode and ActorState, then
the suggestions at all three levels (ConvNode, ActorState, and Actor)
are included in the full list.
The new class SuggestedTopicTree makes it easy to create a tree of
AltTopic alternatives that acts as a single suggested topic.
Normally, when you create a tree of AltTopic alternatives,
you can make each AltTopic a separate suggestion. Each alternative
is effectively an independent topic for suggestion purposes, so
asking about one doesn't satisfy the PC's curiosity about any of
the other alternatives. This means that the game might make the
same suggestion several times, as the different alternatives become
active.
In many cases, it's better to treat the whole group of alternatives
as a single suggestion, so that the suggestion is only made once (or
only as many times as desired) for the whole set. This is what
SuggestedTopicTree is for. To use this class, simply add SuggestedTopicTree
to the superclass list for the main TopicEntry (that is, the
TopicEntry at the root of the tree: the one containing all of the
AltTopic alternatives).
Note that the new TopicEntry property altTalkCount keeps track of
the number of invocations for an entire alternative group. This
property is kept in the outermost TopicEntry for an AltTopic group,
and is incremented each time the outermost TopicEntry or any of its
AltTopic objects is invoked (via ASK ABOUT, TELL ABOUT, etc).
SuggestedTopicTree uses this new counter to determine if the PC's
curiosity has been satisfied for the set of alternatives as a group.
The former ConversationManager method setRespondingActor() has been
renamed to beginResponse(), and a new method finishResponse() has been
added. Each call to beginResponse() should have a corresponding call
to finishResponse().
This change allows the conversation manager to defer setting the
new default ConvNode in the responding actor until after the response
has been finished. This ensures that a ConvNode's noteActive()
method will not be invoked in response to a <.convstay> tag.
In the past, the conversation manager immediately transitioned the
actor to the default next ConvNode at the start of showing the
response, so if the response contained a <.convstay> tag, this
caused a transition back to the starting ConvNode, and thus a call to
noteActive() on the ConvNode object. The new scheme ensures that the
actor's ConvNode won't change at all when processing a
<.convstay> tag.
The AgendaItem class has a new method, resetItem(), that is invoked
each time the item is added to an actor's agenda (via
Actor.addToAgenda()). By default, this method sets the item's isDone
property to nil, as long as the property isn't a method. This makes
it easier to re-use agenda items, since you don't have to worry about
resetting isDone explicitly.
The Surface class now uses a custom Lister,
surfaceInlineContentsLister, to show its in-line contents listing.
The new lister shows the in-line contents using the format "(on which
is...)". In the past, Surface simply inherited Thing's in-line
contents lister, which phrases things in terms of containment
("(which contains...)"), which isn't quite right for surfaces.
For naming consistency, the Lister formerly named
keyringListingContentsLister has been renamed to
keyringInlineContentsLister. The old name was a bit odd and didn't
properly call attention to the "in-line" aspect.
Whenever an object's contents are listed via EXAMINE, LOOK IN, or
OPEN, the library now automatically marks all of the object's visible
contents as having been seen. Only contents that are actually
visible to the character performing the command will be marked as
seen, but it doesn't matter whether or not the contents are mentioned
at all in the description of the object.
The new Thing method useSpecialDescInRoom(room) lets an object specify
whether or not its special description should be shown in a LOOK AROUND
description. By default, this simply returns useSpecialDesc(). In
some cases, it might be desirable for an object to show its special
description only when a container is examined, but not in a room
description; this methods lets the object control this.
The new Thing property suppressAutoSeen can be used to suppress
the library's automatic "seen" marking. By default, the library
automatically marks every visible object as having been seen whenever
a LOOK AROUND command is performed. The library also automatically
marks as seen every visible object within a container when the
container is explicitly examined (with EXAMINE, LOOK IN, or OPEN, for
example).
suppressAutoSeen is nil by default. If you set it to true for an
object, then the library will not mark the object as having been seen
in any of the standard cases, even when the object is visible. The
game must mark the object as having been seen by explicitly calling
setSeenBy(), if and when desired.
DistanceConnector now derives from Intangible as well as
SenseConnector. This makes it easier to use DistanceConnector, since
it's no longer necessary to mix it with a Thing subclass. Each
distance-connector object needs to derive from Thing so that it fits
into the normal sense model, but these objects will almost never have
any physical presence in the game; Intangible fills both of these
needs.
The DistanceConnector template in adv3.h, which sets the locationList
property, has been changed into a generic MultiLoc template. This
lets you initialize any MultiLoc object's location list by specifying
the list of locations after the class name when defining the object.
The Room template in en_us/en_us.h now accepts a room that defines
only a 'name' property; the 'desc' property is now optional. This
lets you use the template to define the room name even if you want
to define a complex method for the 'desc'.
A bug in the Sense class incorrectly considered objects to be out of
range of the 'smell' and 'sound' senses when the objects had distant,
obscured, or attenuated sense paths. This has been corrected.
A Door object's getDoorOpenPreCond() can now return nil, if it's
not desirable to use a precondition for opening the door.
The BasicChair, BasicBed, and BasicPlatform classes now include
a touchObj precondition for the SIT ON, LIE ON, and STAND ON commands.
Where it makes sense, the preconditions that report failures with
messages along the lines of "You must open (something) first" now set
the pronoun antecedent to the object mentioned. This makes exchanges
like this work as one would expect:
>north
You must open the door first.
>open it
In the hint system's Goal class, the new properties openWhenKnown and
closeWhenKnown let you tie a hint topic to the player character's
knowledge of a Topic or Thing. If you set a Goal object's
openWhenKnown to refer a Topic or Thing, then the Goal will become
"open" as soon as the Topic or Thing becomes known to the PC.
Likewise, if you set closeWhenKnown to a Topic or Thing, the goal
will become "closed" when the Topic or Thing becomes known.
The new Goal properites openWhenRevealed and closeWhenRevealed
let you tie a hint to a <.reveal> tag. You can set these
properties to (single-quoted) strings giving <.reveal> tags
to test.
The Goal class has two new methods, openWhen and closeWhen, that
compute the conditions for opening and closing the hint topic,
respectively. These methods isolate the conditions that were
formerly included in-line in the updateContents() method.
Isolating the conditions in separate methods makes it easier to
use 'modify Goal' to add new custom open/close sub-conditions. For
example, suppose you wanted to add a pair of new custom Goal
properties that open and close hint topics based on a Thing being
moved for the first time. You could do this like so:
modify Goal
openWhenMoved = nil
closeWhenMoved = nil
openWhen = (inherited || (openWhenMoved != nil && openWhenMoved.moved))
closeWhen = (inherited || (closeWhenMoved != nil && closeWhenMoved.moved))
;
The new library function intOrdinal(n) returns a "numeric ordinal"
representation of the number: '1st', '2nd', '3rd', and so on.
(Thanks to Søren J. Løvborg for suggesting this and providing a
sample implementation.)
Along the same lines, the new library functions spellIntOrdinal(n)
and spellIntOrdinalExt(n, flags) return fully spelled-out ordinals
('first', 'second', 'third', etc). The 'Ext' version takes the same
bit-flag values as spellIntExt().
The library no longer considers the player character to be an
antecedent to a third-person pronoun when the game itself refers to
the PC in the first or second person (as determined by the PC Actor's
referralPerson property). In the past, typing X ME and then X IT
examine the player character twice. This will no longer happen,
except in third-person games, since the first X ME won't set ME as an
antecedent for HIM, HER, IT, or THEM. This isn't especially important
for IT and THEM, but it can be for HIM and HER if the PC has a specified
gender.
The library now treats HIM and HER a little differently in disambiguation
responses. If HIM or HER is given as the answer to a disambiguation
question ("which one do you mean..."), the parser first looks to see
if there's a prior meaning of the pronoun that applies; it always did
this much. This is the new part: if there is no prior meaning for
the pronoun, or the prior meaning isn't one of the choices in the
query, then the parser looks to see if any of the objects in the query
match the pronoun. If there's exactly one match, the parser takes that
as the result.
The English library module defines a few new methods related to
pronouns and gender. The new Thing methods canMatchHim, canMatchHer,
canMatchIt, and canMatchThem determine if an object can match these
individual pronouns. By default, these simply return true if the
corresponding isHim/isHer/isIt flags are true. Actor overrides these
so that they return true if the inherited version returns true
and the actor can be referred to in the third person, which is
determined by testing the additional new Actor method
canMatch3rdPerson. The new canMatch3rdPerson method returns true if
the actor isn't the player character, or the game refers to
the PC in the third person.
The former Actor methods canTouch(obj) and findTouchObstructor(obj)
have been moved to Thing. There wasn't any need for these methods to
be special to Actor, and it's sometimes useful to establish
reachability between arbitrary non-Actor objects, so these are more
appropriate for Thing than Actor.
The new class TouchObjCondition implements a generic object-to-object
reachability condition. This is similar to the existing touchObj
pre-condition, but allows for arbitrary source objects, whereas
touchObj can only test to see if the current gActor can touch an
object. touchObj is now a subclass of TouchObjCondition.
In the past, throwing an object at a MultiLoc object, or at a component
within a MultiLoc object, didn't work properly. This has been corrected.
In order to allow throwing at MultiLoc objects, the interface to a
Thing method, getDropDestination, has been altered slightly. This
method now takes an additional parameter giving the "sense path" that
was used in the operation that's seeking the drop destination. The
new sense path parameter is a list in the same format returned by
Thing.selectPathTo. This path information allows getDropDestination
to determine whence the MultiLoc object was approached - that is, it
allows a MutliLoc object to find out which of its containers or
containment peers was last traversed to reach the object. This path
can be nil, but when it's supplied, it tells us how we're approaching
the drop destination.
Note that this change slightly affects the interfaces to several
Thing methods: getHitFallDestination, throwTargetHitWith,
stopThrowViaPath, throwViaPath, and throwTargetCatch. In all of these
routines, the former 'prvCont' parameter is now replaced with the
'path' parameter. Similarly, the processThrow method passes the path
rather than the previous container.
In the English grammar, abbreviated words that include periods in the
abbreviation (as in "Main St." or "J. Pretensions") are now treated a
little differently, to ensure that abbreviated words don't have any
bad interactions with unabbreviated equivalents. This change should
be completely transparent to existing game code.
First, the tokenizer no longer treats an abbreviation as a single
token that includes the period, but rather as two separate tokens:
one for the word itself, and a separate token for the period. The
period is entered not with the ordinary punctuation token class, but
with the new 'tokAbbrPeriod' class. Second, wherever ordinary noun
tokens were allowed in the grammar, the grammar now instead accepts
the new subproduction 'nounWord'. This new production matches an
ordinary noun token, or a noun token followed by a
tokAbbrPeriod token. Third, and similarly, the existing 'adjWord'
production now accepts an adjective token followed by a tokAbbrPeriod
token. Fourth, the vocabulary initialization
(VocabObject.initializeVocabWith, which parses the vocabWords_
string) now enters each token that ends in a period with the period,
as it did, but also without the period. So, for example, the
vocabWords_ string 'main st.' will create an adjective 'main', a noun
'st.', and a second noun 'st' (in other words, 'st' is entered in the
dictionary both with and without a trailing period).
This corrects a problem that could have occurred with the old
scheme if the same word was used abbreviated and unabbreviated. For
example, if 's.' was used as an abbreviated word (for a string like
's. main st.', for example), and 's' was also entered in the
dictionary as a verb (which it is by default, for the South action)
the command "s." was not properly handled. The problem was that the
tokenizer would see that "s." was entered in the dictionary with the
period, so it tokenized it with the period. This precluded
interpreting it as two tokens, 's' and '.', so the 's' was not
matched to the verb, hence the command was rejected as not understood.
A useful side effect of this change is that each abbreviation is
now automatically entered in the dictionary without its period. This
should be helpful to players who choose to avoid the extra typing of
entering the periods in abbreviations.
The new Hidden class can be used to implement objects that are present
but not seen. This is useful for objects that are too inconspicuous
to be noticed, or are positioned in such a way that they can't be
readily seen (but in such a way that the ordinary sense mechanism
would think the object was visible).
The BulkLimiter class now parameterizes all of the messages that
were previously coded directly as failure reports. The new BulkLimiter
properties tooLargeMsg, becomingTooLargeMsg, and becomingTooFullMsg
join the existing tooFullMsg in specifying the various failure reports;
these properties in turn contain property pointers that refer to
messages defined in the playerActionMessages object. This makes it
easier to create "cosmetic" subclasses of BulkLimiter that define
customized types of containment relationships.
The new macro askForTopic(action) allows a single-object TAction to
ask for a topic phrase and retry the command as a TopicTAction. This
allows, for example, a CONSULT action with no topic specified to ask
for a topic and turn itself into a CONSULT ABOUT action.
To enable this change, the parser includes a new grammar
production, EmptyTopicPhrase. This new production represents a topic
phrase that requires an interactive response from the player.
In the past, it wasn't possible to use askForDobj() to convert a
LiteralAction into a LiteralTAction, or a TopicAction into a
TopicTAction, by requesting a missing direct object from the player.
This was due to an oversight in the library, which has now been
corrected.
Another similar, though more subtle, oversight made it impossible
to use askForDobj() to convert a TAction into a TIAction. This is an
unusual scenario, because the usual way to perform this conversion
would be through askForIobj(): it's almost always the indirect object
that's missing in these cases. However, there are cases where a
missing direct object is possible; for example, we might want a
full-fledged SPRAY verb that takes only one object, as in SPRAY AIR
FRESHENER, but also have a two-object form for things like SPRAY SINK
WITH DISINFECTANT, in which it's the direct object that's missing
from the two-object form when we type SPRAY DISINFECTANT. (This
particular example is a bit contrived, since we could just as well
have defined the verb as SPRAY DISINFECTANT ON SINK, but even so, we
don't want to be forced to find such a rephrasing.) In these cases,
askForDobj() can now be used. When a TAction is retried as a
TIAction with askForDobj(), the direct object of the TAction now
becomes the indirect object of the TIAction; this is plainly
the only way it can be, since we know the direct object is missing by
virtue of the fact that we're asking for it.
The interface of the method Actor.trackFollowInfo() has been changed
slightly to improve its flexibility. In the past, this method assumed
that the current action was actually performing the travel; now, the
method instead takes an additional argument to eliminate this
dependency. The new third argument gives the starting location of the
travel; this is normally simply the location of the actor traveling,
but could be something else; for example, when the actor is inside
a vehicle, then the starting location is the vehicle's location.
The new class NestedRoomFloor can be used for convenience in creating
an object that serves as the floor of a nested room.
The Floor class now accepts STAND ON commands. STAND ON FLOOR is
simply remapped to STAND UP.
The BurnWith action now resolves its direct object first. This is
the better resolution order for this action, since it allows the
direct object to set the scope for choosing the indirect object,
which is likely to be omitted and thus require a suitable default
to be chosen.
In addition, the FireSource class now declares it illogical to
burn an object using itself as the fire source. It's common for a
Candle to also be a FireSource; this change prevents such an object
from being chosen as its own default fire source in a command like
LIGHT CANDLE, which would cause an infinite loop as we tried to light
the candle with itself, which would require first lighting the
candle, which would default to lighting it with itself again, and so
on.
The FireSource and Matchstick classes have also been fine-tuned to
work together a little better. The FireSource class now specifies a
logicalRank of 150 when used as the indirect object of BurnWith. The
Matchstick class uses a logical rank of 160 when lit and 140 when not
lit. This makes a matchstick an especially logical choice for
starting a fire under all conditions, but makes a non-matchstick an
even better choice when it's already burning. A lit match trumps
everything (160), next best is an already-lit non-matchstick
FireSource (150), and when there's nothing else around that's
burning, a matchstick will still be a good choice (140). This
ensures that we don't light a new match when there's another fire
source readily at hand, but at the same time ensures that we'll
default to using a match when no better choice is available.
When an implicit action is interrupted with an interactive prompt,
the announcement of the implicit action is now phrased as "trying"
the action. This is along the same lines as the change in 3.0.6k
that phrased failed implicit actions as "trying"; since an interactive
prompt interrupts an implied action, it makes more sense to refer to
the implied action as being merely attempted at this stage.
Internally, the announcement generator distinguishes between
failed actions and actions interrupted for interactive input, so it's
a fairly easy matter to customize the types of messages separately.
To customize the "asking" case separately from the "trying" case,
customize the askingImpCtx object; in particular, override its
buildImplicitAnnouncement() method and its useInfPhrase property as
needed. The default in the library is to use the same message
style for both cases.
Due to a bug in the library, using "exit" to abort an action from
within a check() handler did not properly mark the action as failed
within the transcript, which meant that the "trying" form of an
implicit action announcement was not generated. This has been
corrected.
In the past, when an implied action was remapped to another action
(via remapTo), the normal announcement of the implied action wasn't
generated. This has been corrected: the announcement will now be
shown as usual.
The parser is now a little smarter about excluding items with ALL
EXCEPT in cases of simple remappings. In many cases, remapTo is used
to create simple object-to-object synonyms for certain verbs; for
example, OPEN DESK might be remapped to OPEN DRAWER when it's obvious
that the two commands mean the same thing. In these cases, when an
object is excluded from the ALL list with EXCEPT, the parser will now
look for these simple synonym remappings, and it will treat them as
equivalent. So, if OPEN DESK is remapped to OPEN DRAWER, and the
player types OPEN ALL BUT DESK or OPEN ALL BUT DRAWER, then the desk
and drawer will be excluded from the ALL list.
Note that the parser can only exclude synonym remappings when
they're remapped using the remapTo (or maybeRemapTo) mechanism.
Remappings that are done using replaceAction() or the like can't
be detected as synonyms, so they won't be excluded. Remappings
that aren't simple synonyms won't be excluded, since remapping
to different verbs or word orders changes the meaning of the
command enough that the EXCEPT list shouldn't exclude the new
objects. For example, if OPEN DOOR remaps to PUSH BUTTON, because
a button is used to operate a mechanical door, OPEN ALL BUT BUTTON
would not exclude the remapping to PUSH BUTTON, since we
didn't say not to PUSH the button, only not to OPEN the button.
The standard "weak vocabulary" checking in Thing now takes into account
truncatable vocabulary words, as well as any other special dictionary
comparison rules, by using the main dictionary's string comparator
to check for weak tokens. Note that this routine uses the global
property languageGlobals.dictComparator, so if the game ever changes
the main dictionary's string comparator object, it should also change
this global property at the same time.
The Topic class now specifically disallows sensing a topic object
with any physical senses. This ensures that a topic object will
never be in any physical scope, even if the object is part of a
containment hierarchy. (It's sometimes convenient to include a topic
as part of a containment hierarchy in order to associate the topic
with a physical game world object.)
The possessive qualifier resolver (PossessiveResolver) now considers
an object to be in scope in a possessive phrase only if the object
has the property canResolvePossessive set to true. This property is
true by default for all Things and nil by default for all Topics. In
other words, by default, possessive phrase qualifiers can only be
resolved to physical game world objects, never to abstract topics.
Released August 17, 2003
The new TopicEntry method isMatchPossible() tries to guess whether or
not the TopicEntry can be matched by any input currently. This
question is unanswerable in general, because it's asking if there
exists any input, out of the infinite set of possible inputs, that
will match the topic entry. However, the TopicEntry subclasses use
heuristics to provide an answer that's right in most cases. In
particular, the ASK and TELL entries consider themselves matchable if
any of their matchObj objects are either known to the player character
or are simply in scope; the GIVE and SHOW entries are considered
matchable if any of their matchObj objects are in scope; and YES, NO,
and special topic entries are always considered matchable. If a game
creates a custom matchTopic() method, it might want to customize
isMatchPossible() to match the custom match condition.
The SuggestedTopic method isSuggestionActive() uses this new
method to hide suggestions that aren't currently matchable. This
provides an important benefit for the most common cases: topics won't
be suggested until the player character knows about them. This
avoids showing suggestions for things the player shouldn't even
have heard of yet.
To facilitate this change, isSuggestionActive() now takes an
additional argument, giving the list of in-scope objects. This
avoids the need to repeatedly compute the scope list when scanning
a large list of suggested topic objects, which is important because
the scope calculation is complex and can be time-consuming.
Some important terminology for TopicEntry objects has been changed:
where we formerly referred to a TopicEntry as "known," we now refer to
it as "active" instead. The old terminology implied that TopicEntry
objects model NPC knowledge; this was misleading, though, because the
availability of a TopicEntry often depends on PC knowledge as well as
NPC knowledge, or what the NPC
wishes to reveal rather than
what the NPC actually knows, or various other things. The new
terminology removes the implication that NPC knowledge in particular
is being modeled.
This change requires new names for several TopicEntry properties:
isKnown is now isActive; checkIsKnown is now checkIsActive; and
topicGroupKnown is now topicGroupActive.
Game code that uses TopicEntry objects will have to rename isKnown
to isActive in the object definitions. The other renamed properties
might have to be changed as well, of course, but these are less likely
to appear in game code.
The travel precondition mechanism has been tweaked a little to make it
more flexible for handling nested-room situations. In the past, each
travel connector was responsible for providing a precondition applied
whenever an actor traversed the connector, via the travel connector's
actorTravelPreCond(actor) method. This formerly returned the
actorStanding precondition. While this was appropriate in most cases,
since it ensured that the actor wasn't sitting in a chair or anything
like that, it didn't make for easy customization for specialized nested
rooms or other unusual situations.
The change is that TravelConnector.actorTravelPreCond(actor) now
returns a new precondition, actorTravelReady, instead of
actorStanding. The actorTravelReady precondition abstracts the travel
condition by letting the actor's immediate location handle it. In
particular, it calls three new BasicLocation methods to carry out the
precondition: isActorTravelReady(conn), tryMakingTravelReady(conn),
and notTravelReadyMsg. By default, these methods do exactly what
actorStanding does: they require the actor to be standing up before
travel. However, a location can override any or all of these. For
example, a particular nested room might want to use a command other
than "stand up" to get the actor out of the nested room in preparation
for travel; the nested room could override tryMakingActorTravelReady()
in this case to carry out the desired implied command.
Note that the travel connector to be traversed is an argument to
the first two of the new methods. This allows the actor's current
location to impose different requirements on travel via different
connectors. For example, it might be necessary to climb down from a
ladder before leaving via any connector except for a window high up on
the wall, which can only be reached when on the ladder.
The new Action method cancelIteration() lets you tell cancel further
iteration of the current action, if multiple objects are involved in
the action. You can call this method on the current gAction object
during the 'check' or 'action' phases of execution. Once the method
is called, the action will complete the execution cycle for the
current object as normal, but it will then act as though the current
object were the last object of the command.
Note that this new method does not have any effect on the
execution of the command for the current object; in particular, it
doesn't interrupt the execution flow the way 'exit' and the like do.
Instead, this method simply sets a flag in the current Action object
telling it to ignore any additional objects it would otherwise
iterate over. If you want to jump out of the execution cycle in
addition to canceling further iteration, simply use 'exit' (or one of
the similar signals) after calling this method. Note also that this
method obviously can't cancel the iteration for any prior objects in
the iteration.
In the status-line and menu modules, there were some unnecessary
"flush" operations (on banners and on the main window). Most of these
were placed before "sizeToContents" calls on banners, which is
unnecessary because that operation automatically flushes the
underlying output stream anyway. These extra calls were not only
unnecessary but were also undesirable, because the explicit flush
operations always update the display immediately in addition to
flushing internal text buffers. This made the display flicker
annoyingly at times. The extra flushes have been removed, which
makes the display updating faster and eliminates the flicker in
the common cases covered by the changes.
When a MenuLongTopicItem is part of a series of "chapters" (as
indicated by the isChapterMenu property), and the user navigates
directly from one chapter to the next, the menu system keeps the
banner configuration for the chapter display throughout the change
to the new chapter. In the past, the menu system removed the banner
and then immediately restored it, which caused some visual flicker.
This change eliminates the flicker during the chapter change.
The English implicitAnnouncementGrouper implementation now uses
pronouns, where appropriate, to refer to repeated objects when it
groups a series of announcements. It frequently happens that the same
object appears more than once when a series of implied actions is
performed; this new phrasing makes the announcement text shorter and
makes it read more naturally. For example, rather than saying "first
unlocking the door, then opening the door", we'll now say "first
unlocking the door, then opening it".
The implicit action announcement mechanism now phrases the
announcement for a failed action a little differently than for a
successful action. When an action fails, the message now reads
"(first trying to ...)". The old phrasing was a little awkward at
times, because the announcement describes the action as being
performed, and then the (usually) very next message says just the
opposite, that the action wasn't performed at all because it failed.
Depending on the wording of the failure message, the combination could
even be misleading on occasion, since the suggestion in the
announcement that the action was performed could lead to a player
assuming that the failure message must refer to something else.
To enable this change, the interface to the libMessages method
announceImplicitAction() has changed slightly. The method now takes a
"context" parameter, which is an object of class
ImplicitAnnouncementContext The context contains information on the
format of the message to be generated.
Also, the implicitGroupTransform internally scans for failure
messages that apply to implied action announcements, and rewrites the
implied action announcements into the new format when failures are
noted.
Finally, the implicitAnnouncementGrouper object implementation in
the English module does some additional grouping, to render a series
of "trying" messages as readably as possible. When a series of
consecutive "trying" messages appears, the grouper combines them under
a single "trying" phrase, as in "first trying to unlock the door and
then open it."
Several small internal changes have been made in the English library
module.
First, the new method getInfPhrase() returns a string giving the
full action description in infinitive form. This returns a phrase
such as "open the box" or "unlock the door with the key". The verb is
always in the infinitive form, but note that the English infinitive
complementizer, "to", is not part of the returned phrase; this is
because an infinitive is used without a complementizer in certain
contexts in English, such as with auxiliary verbs (such as "might" or
"should").
Second, the new method getVerbPhrase(inf, ctx) returns the full
verb phrase in either the infinitive form ("open the box") or the
present participle form ("opening the box"). In English, the two
forms are equivalent except for the verb ending, so the code to
generate both forms can be easily consolidated into a single routine.
The subclasses of Action override getVerbPhrase() as needed to provide
appropriate phrasings. The context object 'ctx' is optional; if it's
not nil, it should be an instance of the new class
GetVerbPhraseContext. If the context object is provided, the routine
uses it to keep track of pronoun antecedents for the verb phrase; the
returned verb phrase will use a pronoun if one of its objects matches
the current antecedent, and the routine will set the antecedent in the
context for the next verb phrase that uses the same context. The
context is useful if you're generating a series of verb phrases that
you're going to string together into a single sentence; by using
pronouns to refer to repeated objects, the sentence will be shorter
and will read more naturally.
Third, the implementation of getParticiplePhrase() now simply
calls getVerbPhrase() in all cases.
Fourth, the routine formerly called verbInf() has been renamed to
getQuestionInf(), to better describe its purpose. This routine
doesn't return the full infinitive form of the verb, but rather
returns an infinitive form specifically for an interrogative in which
one of the verb's objects is the interrogative's unknown. The old
name was always misleading, but the addition of getInfPhrase() would
have made the name even more confusing because of the similarity of
the names.
In the routine that tries to free up space in an actor's hands by
moving objects into a "bag of holding," objects with no encumbering
bulk are now never moved. In the past, objects were moved in
descending order of encumbering bulk, so objects with zero bulk were
typically ignored; however, if there weren't enough bulky objects
present, it was possible for the routine to try moving bulkless items.
Since worn items have no encumbering bulk by default, this meant the
routine sometimes removed worn items and moved them to the bag, which
was pointless.
The new class FloorlessRoom, a subclass of Room, makes it easier to
build rooms that represent locations without conventional floors.
Anything dropped in the room is described as falling and vanishing
below, since there's no floor for it to land on. You can control
where dropped objects land using the bottomRoom property; by default,
this is nil, which means that dropped objects simply disappear
from the game.
The Room class no longer requires the room's floor to be represented
by the first element of the roomParts list for the room. Instead, the
new method roomFloor returns the room's floor object; this method
finds an object of class Floor in the roomParts list and returns it,
or nil if there no object of class Floor in the list. This change
eliminates the fragility caused by the former convention.
A new mechanism provides more consistent handling when objects
are dropped into a room. The new mechanism is used in the library
to handle the DROP and THROW commands, both of which can result in
an object being dropped into the enclosing room. The mechanism is
extensible, so library extensions and games can add their own actions
that drop objects into the room.
Whenever an object is to be dropped into the enclosing room, the
DROP and THROW commands first find the drop destination using the
existing getDropDestination() method. Then, they invoke the new
receiveDrop(obj, desc) method on the drop destination object. In most
cases, the drop location is the enclosing room, so the enclosing
room's receiveDrop() method is usually the one that will be invoked.
The receiveDrop() method is responsible for carrying out the
effects of dropping the object, including any game-state changes (such
as moving the dropped to its new location) and reporting the
results of the action.
In most cases, the effects won't vary according to which command
was used to discard the object. This is the reason that there's only
one receiveDrop() method, rather than separate methods for DROP,
THROW, and any library extension or game-specific verbs. By using a
common routine to handle all means of discarding an object, the game
only has to implement any special handling once.
However, the message will almost always vary according to
the command, for the simple reason that the message needs to
acknowledge what the actor did to discard the item in addition to the
effects of dropping it. This is the main reason the 'desc' argument
is provided: it's a special "drop descriptor" object that lets you
find out what command was used. You could also look at gAction to
find the Action being performed, but the descriptor object makes your
job a lot easier by providing some convenience methods for building
report messages.
The descriptor object is of class DropType. This class has two
important methods. First, standardReport() displays the "normal"
report for the action. For DROP, this is simply "Dropped." For
THROW, this is a message of the form "The ball hits the desk, and
falls to the floor." You can use the standard report when your
receiveDrop() method doesn't do anything out of the ordinary and thus
doesn't require a special report. Second, getReportPrefix() returns a
string that gives the start of a sentence describing the
action. The returned string is a complete main clause for a sentence
-- but it has no ending punctuation, so you can add more to the
sentence if you like. The main clause will always be written so that
the object being dropped will be the logical antecedent for any
pronouns in subsequent text, which lets you tack on a string like
this: ", and it falls to the floor."
Rather than building a message from the report prefix provided by
the descriptor, you are free to build a completely custom message, if
you prefer. To do this, you could use 'modify' to add your own method
to each DropType subclass in the library, and then call this method
from your receiveDrop(). Alternatively, you could use ofKind() to
check what kind of DropType descriptor you got, and show custom
messages for the ones you recognize.
Thing.stopThrowViaPath() now simply calls throwTargetHitWith() by default.
When an object interrupts a projectile's course, the result is exactly as
though the projectile had been successfully thrown at the interrupting
object, so the separate implementations were redundant.
finishGame() now resets the sense context. This ensures that if the
event that triggered the end of the game happened while a blocking
sense context was in effect, the finish options are still listed.
(Since the finish options involve direct interaction with the player,
they obviously shouldn't be displayed in any NPC sense context.)
Thing now has a constructor, which calls initializeThing(). This ensures
that any Thing objects that are created dynamically (with 'new') are
initialized in the same manner as objects defined statically.
The library's handling of RESTART has been changed slightly to correct
a problem that cropped up under certain unusual circumstances. The
old handling reset the VM within the Restart action execution method,
and then threw a RestartSignal to tell the main loop to re-enter the
game from the beginning. This approach had a subtle flaw: if any
dynamically-created objects were referenced from an intermediate stack
frame in the code that initiated the restart, those objects were
retained through the VM reset because they were still accessible via
the stack. These objects then had a chance to remain visible to an
object loop, such as those that run during the library
pre-initialization. The Restart action handler now simply throws the
RestartSignal to the main loop, and the main loop now performs the VM
reset. This ensures that the stack is reset to initial conditions
before the VM reset occurs, which in turn ensures that only those
dynamic objects that should survive the reset do survive the reset.
Released August 2, 2003
The new class DistanceConnector is a specialized subclass of
SenseConnector that allows two (or more) locations to be connected,
but at a distance. This is handy for situations such as divided rooms
(where we use two or more Room objects to model a single large
physical location), and for cases where two rooms are open to one
another (such as a balcony overlooking a courtyard).
The new class Vaporous is a subclass of Intangible designed for
insubstantial but visible objects, such as fire, smoke, and fog.
This class works a lot like Intangible, but it has a visual presence,
and it specifically allows the commands Examine, Smell, and Listen To;
in addition, it allows the commands Look In, Look Under, Look Behind, Look
Through, and Search, and responds to these with "You just see (the
object)."
The Lister class no longer sets the 'seen' attribute of objects it
lists, because this attribute is now more generally handled in the
main room description routines (per the change in meaning of 'seen'
in 3.0.6i). As a result, the Lister.markAsSeen method has been
removed.
A new macro, gSetKnown(obj), marks an object (usually a Thing or a
Topic) as known by the player character. This is simply short-hand
for obj.setKnownBy(gPlayerChar, true), for convenience.
The EventList object has been enhanced slightly to accept property
pointer entries. A property pointer entry is interpreted as a
property to invoke on the EventList itself, with no argument. This
allows writing complex steps as separate properties of the EventList
object, and then referring to them from the script by property
pointer.
In addition, the ScriptEvent class has been eliminated. Instead,
simply use Script objects in an event list. So, when an object
appears in an event list, the EventList class now invokes the
object's doScript() method. This makes it easy to build event lists
recursively.
The new EventList subclasses CyclicEventList and StopEventList
provide new pre-defined options for the behavior of an event list
after visiting all elements. The new subclass SyncEventList provides
a list that synchronizes its state with another list.
The TextList, StopTextList, and SyncTextList classes are now
simple subclasses of CyclicEventList, StopEventList, and
SyncEventList, respectively.
A new EventList method, scriptDone(), is invoked by doScript()
after it processes the script's current step. By default, this
method simply invokes advanceState() to advance the event list to
its next state. Subclasses can override scriptDone() so that it
does not advance the script's state, or does something else in
addition.
The new EventList subclass ExternalEventList overrides scriptDone()
so that the method does nothing. This makes it easy to create an
event list that is driven externally; that is, the event list doesn't
advance its state when doScript() is invoked, but only advances its
state in response to some external processing that calls
advanceState() directly on the event list object.
The Room class now provides a dobjFor(Examine) that replaces the
Examine action with a Look action. This means that if a Room object
has ordinary vocabulary words defined, and the player types EXAMINE
(room), the command will automatically be treated as though the
player had typed LOOK AROUND.
The new class IndirectLockable is a subclass of Lockable that
can be used for situations where a lockable object cannot have
its locked status directly manipulated by LOCK and UNLOCK commands.
The reply to LOCK and UNLOCK can be customized via the object's
cannotLockMsg and cannotUnlockMsg properties.
The Lockable class now handles the 'objUnlocked' precondition a little
more subtly. The class now keeps track of whether or not the player
knows the object to be locked; the status is assumed to be unknown
initially. When the player doesn't know the status, the Open action
does
not apply an objUnlocked precondition, but instead disallows
the action in the check() routine. When the status is known, the
objUnlocked precondition is applied as before. Whenever the Open
action fails in the check() due to the object being locked, the
status is taken to be known for subsequent attempts.
This change makes the precondition handling a little more
intelligent. When the player doesn't know the status beforehand, an
Open command will simply fail with the discovery that the object is
locked. This usually makes more sense than attempting to unlock the
object automatically with an implied Unlock command, because if the
object isn't already known to be locked, there's no reason to try to
unlock it. When the status is known, however, it makes sense to
perform the implied Unlock.
Objects whose lock status is visibly apparent can be marked as such
with the lockStatusObvious property. This property is nil by default;
an object whose lock status can be visibly observed should set this to
true. When this property is set, the lock status is always
known, since we'll assume that actors will simply look at the lock and
observe the status. In addition, when this property is set, we'll add
the locked/unlocked status at the end of the object's 'Examine'
description (we'll add "It's currently locked" or "It's currently
unlocked").
A new class, TravelWithMessage, can be mixed (using multiple inheritance)
with TravelConnector or a subclass of TravelConnector to create a connector
that shows a message as it's traversed. This is slightly more flexible
than using TravelMessage, since TravelMessage can't usually be mixed with
other TravelConnector-derived classes.
A new TravelConnector property, stagingLocation, gives the location
in which a traveler must be located prior to traversing the connector.
In the past, the connector simply assumed the traveler needed
to be in the connector's direct location; this new property allows
this condition to be customized. At times, the connector might be
inside an intermediate container, for example, in which case the
staging location might be a container of the container. By default,
the staging location is the connector's location, so connectors that
don't override stagingLocation will have the same behavior as before.
A new Thing method, roomLocation, returns the 'self' if the object
is capable of serving as a location for actors, or the nearest
container that is a roomLocation otherwise.
The Passage travel connector class now uses the other side's
container's roomLocation as the default destination of the passage,
rather than the other side's immediate container, as it did in the
past. This makes it much easier to create passages that model
non-trivial containment relationships, such as holes in walls, since
an intermediate container (between the Passage and the enclosing Room
or NestedRoom) will no longer create any confusion in performing the
travel.
The NoTravelMessage class can now multiply inherit from Script, for
convenience in defining message lists. If you want to provide several
different messages to choose from when the connector is traversed, you
can make your connector object inherit from TextList as well as
NoTravelMessage, and the travelDesc() will automatically invoke the
script to show a message.
If the travel connector has a non-nil location, the TravelConnector
class's connectorTravelPreCond method adds a precondition that the
connector must be touchable. This ensures that characters won't be
allowed to traverse connectors that are visible but on the other side
of a window, for example, or connectors that are out of reach.
The travel notification protocol has been changed very slightly. The
travel connector's noteTraversal() method is now invoked from within
the Traveler.travelerTravelTo() method rather than the TravelVia
handler in the connector. This change means that noteTraversal()
is called just before the traveler is actually moved, and in particular
that it occurs after all of the other travel notifications
(beforeTravel, actorTravel, and travelerLeaving). This is generally
the more desirable ordering for the notifications, because it puts
the connector's reaction closest to the actual traveler movement.
If you want the connector to do something special before any of the
other notifications, you can override the action() handler
in the connector's dobjFor(TravelVia) so that it performs the special
code and then inherits the base class handling.
When an actor travels through a connector that leads to a location
with a posture other than the actor's original posture, the new
location now sets the actor's posture directly, rather than via a
nested action. For example, if a travel connector leads to a chair,
then traversing the connector will move the actor into the chair and
set the actor's posture to 'sitting', without activating a nested Sit
action. In the past, the nested Sit action would have been used
instead. This change is in BasicLocation.travelerArriving(), which
sets the new actor's posture via a call to Actor.makePosture().
This change is generally desirable because we usually want arrival
by travel connector to be self-contained; we don't want it to appear
as though the character walked to the new location and only then
performed a SIT ON THE CHAIR command (say). It's worth noting,
however, that any side effects of the usual way of getting into the
new location's posture will be skipped. Therefore, when linking a
travel connector into a nested room with a non-standing posture, you'll
need to consider any side effects you'd want to trigger, and trigger
them explicitly on the travel connector itself.
The new Actor method scriptedTravelTo() simplifies coding of scripted
travel for an actor. This routine is suitable for cases where an NPC
is to perform a series of scripted travel actions, where each room
along the way is scripted in advance. This routine is not
suitable for goal-seeking NPC's, because it can only perform travel to
a location adjacent to the actor's current location, and because it's
"omniscient" (it doesn't take into account the actor's knowledge of
the the map, but simply considers the actual map). For cases where
an NPC is to visit a scripted series of locations, this is a convenient
way to accomplish the travel, since it requires specifying only the
destination of each step of the travel.
The class SecretDoor is now based on BasicOpenable as well as
ThroughPassage. The class previously didn't have BasicOpenable
anywhere among its superclasses, which prevented its open/closed
mechanisms (initiallyOpen, isOpen, makeOpen) from working
consistently with other types of doors, which are all based
(indirectly) on BasicOpenable.
The new ThroughPassage subclass PathPassage is a specialization for
cases such as outdoor walkways, paths, and streets. The key
difference between the standard ThroughPassage and the more
specialized PathPassage is that path passages are not considered
enclosed, so the descriptions for arrival and departure via a path
take this into account. In the English messages, we describe travel
on a path as being "via" the path rather than "through" it. In
addition, the English parser accepts "take" as a synonym for "enter"
or "go through."
To handle this new class, TravelMessageHandler and its subclasses
have a new pair of methods, sayArrivingViaPath() and
sayDepartingViaPath(), for generating travel messages related to
paths.
The new method Actor.actorTravel(traveler, connector) complements
beforeTravel() and afterTravel(). This new method is called on the
actor who initiated the travel (that is, the actor performing
the command, if any); it's called after the beforeTravel()
notifications are sent to nearby objects. This method is provided for
symmetry with the beforeAction/actorAction/afterAction set.
The AccompanyingState.accompanyTravel() method has been changed slightly.
The first parameter is now a Traveler object, which might be an Actor but
could also be another kind of Traveler, such as a Vehicle. This allows
the game to determine whether the NPC is to accompany actors when they
travel on vehicles or in other indirect ways.
In addition, the class's beforeAction() method, which responded to
the initiating actor's travel, has been changed to a beforeTravel()
method. This takes advantage of the more precise kind of notification
offered by beforeTravel(). In particular, using this method avoids
unnecessary attempts to accompany an actor who attempts a travel command
that doesn't actually result in travel (because the connector doesn't
go anywhere, for example).
If a travel connector is located inside a nested room, the
preconditions for traversing the connector now correctly move the
character to the nested room prior to the travel. In the previous
library version, the enclosing room tried to add its own precondition
requiring the traveler to be in the outer room, which was incorrect.
The Follow command now provides better feedback to the player in
certain cases. When the player has previously observed the target
actor departing, but from a different location than the player's
current location, the Follow command will now respond with the message
"You didn't see which we he went" (rather than "You can't do that from
here" as it did in the past).
An Actor's executeTurn() method, and all of the methods it invokes
(the Actor's idleTurn() and the ActorState's takeTurn(), for
example), now run within a valid "action environment," just like
fuses and daemons started doing in 3.0.6i. In addition, the
executeTurn() method sets up a visual sense context for the actor,
ensuring that any output displayed in the course of the actor's turn
is displayed only when the actor is visible to the player character.
(It is presumed that the effects of any action that an actor performs
are visible if and only if the actor is visible. This is just the
default case, though: the game is always free to set up a separate
sense context, using callWithSenseContext(), whenever necessary.)
To facilitate this change and make it easier to override an actor's
per-turn processing, the main handling for an actor's turn is now in
a new method, executeActorTurn(). The executeTurn() method simply sets
up the action environment and then invokes executeActorTurn(). In most
cases, subclasses that need to specialize the per-turn processing should
override executeActorTurn() rather than executeTurn(), since this will
let the override run in the action environment.
The optional initial "look around" that the runGame() function
performs is now executed within an "action environment," just like
fuses and daemons started doing in 3.0.6i. This allows the initial
room description to refer to gActor and gAction, which some library
methods do implicitly.
callWithSenseContext() now uses "lazy evaluation": rather than
immediately calculating the effect of the new sense context, the
routine now simply notes that it has a new context, and defers
calculating the effects of the new context until the effects actually
need to be known. This makes it very inexpensive to set up a new
sense context in cases where the context won't actually be needed for
anything, which is frequently the case in daemons, fuses, and the
standard per-turn processing for NPC's. This can save a substantial
amount of time when a game has numerous background events if, as is
typical, most of the background events don't generate any output on a
given turn.
The new macros actorStateDobjFor(action) and actorStateIobjFor(action)
make it convenient to delegate an actor's processing for an action to
the actor's current ActorState object. These are defined in adv3.h,
and work analogously to asDobjFor(action) and asIobjFor(action). Using
these macros involves two steps. First, in the Actor, write a short
handler for the action that uses the macro to delegate to the state
object:
bob: Person
// ... other definitions ...
dobjFor(AttackWith) actorStateDobjFor(AttackWith)
;
This sets up the "bob" Actor object so that the handling for
AttackWith, with bob as the direct object, will be delegated to bob's
current ActorState object. Second, just write a normal dobjFor() handler
in each of the ActorState objects associated with bob:
+ bobFighting: ActorState
dobjFor(AttackWith)
{
action() { "Bob puts up a fight..."; }
}
;
+ bobCowering: ActorState
dobjFor(AttackWith)
{
action() { "Bob just hides in the corner..."; }
}
;
The withActionEnv() function now takes an addition parameter giving
the Actor object to use as the gActor value while running the
callback. In the past, this function always used the player character
Actor (given by gPlayerChar); the new parameter allows other actors to
be used instead.
The withCommandTranscript() function now returns the result of the
callback function, rather than the transcript object created to run
the callback. Callers who still need the transcript object as the
result can simply return gTranscript from the callback function
itself, as this value will then be passed up as the return value from
withCommandTranscript().
The attentionSpan property of an InConversationState object can now be
set to nil to indicate that there is no attention span limit for the
NPC. Setting attentionSpan to nil will prevent the NPC from ever
ending the conversation by virtue of being ignored for too long.
The standard SpecialTopic template (in adv3.h) now allows defining a
list of strings (for the textStrings property) in place of a single
response. The standard TopicEntry, SpecialTopic, and YesNoTopic
templates now make the response/response list entry optional, allowing
the rest of the templates to be used even if a custom method is
needed to handle the response.
A new DefaultTopic subclass, DefaultAnyTopic, matches all of the
different sorts of topic interactions: ASK, TELL, GIVE, and SHOW.
This is especially useful for default handling in conversation nodes
(ConvNodes) where you want catch all topics not otherwise handled.
The new TopicEntry property isConversational lets a TopicEntry
specify whether or not the response is "conversational." A
conversational response is one that involves some kind of interaction
with the NPC. This property is true by default, but some response
objects will want to set it to nil to indicate that the response
doesn't involve any conversation with the NPC. For example, a
response like "You don't think he wants to discuss that right now" is
non-conversational, because it doesn't involve any exchange with the
NPC.
When isConversational is nil for the response object that's found
for a conversation command, a ConversationReadyState will not
enter its in-conversation, but will simply show the response and
remain in the conversation-ready state. This means that there will
be no "greeting" exchange for these responses.
The new ConvNode method canEndConversation(actor, reason) lets a node
prevent a conversation from ending. 'reason' is an enum indicating
what is triggering the attempted termination: endConvBye if the player
typed BYE; endConvTravel if the other actor is leaving (i.e., the player
typed a travel command); or endConvBoredom if we're terminating the
conversation on our own because our attentionSpan has been exceeded.
This method can cancel the operation that attempted the termination
simply by returning nil; the method should display an explanation
when returning nil, to let the player know why the command is being
canceled.
In related changes, ConvNode.endConversation() and
ActorState.endConversation() now take 'reason' codes rather than
'explicit' flags. This gives the methods full details on why the
termination is occurring.
The new AgendaItem class provides a simple new mechanism for handling
cases where you want to model an NPC in terms of motivation. Each
actor has an "agenda," which is a list of AgendaItem objects. On each
turn, the actor's ActorState object (in the takeTurn method) will look
at the agenda list, to see if any items are ready to execute. If
there's an item that's ready to execute, the ActorState will execute
the first one that's ready.
Each AgendaItem object has a method called isReady, which indicates
whether or not the item is ready to execute. An actor will only execute
an agenda item once it's ready. The invokeItem method contains the code
that actually carries out the agenda item's action.
By default, an agenda item is removed from the actor's agenda list
when it's executed. However, if the item's stayInList property is true,
then the item will not be removed on execution. This property lets you
handle agenda items which the actor will try repeatedly, until some
other code determines that the goal has been achieved and removes the
item from the actor's agenda.
You must nest AgendaItem objects within their actor, using the "+"
notation. This associates the AgendaItem objects with the actor, but
it doesn't add them to the actor's agenda list. You must explicitly
add agenda items to the actor's agenda list by calling the actor's
addToAgenda() method. Agenda items must be explicitly added because
an actor's motivation typically will change dynamically as the game's
events unfold.
Autonomous NPC conversation continuation processing has been moved
from InConversationState to the base ActorState. This allows actors
that don't use greeting protocols to nonetheless use stateful
conversations.
To accommodate this change, the default takeTurn() method in
ActorState now preforms more processing. If the actor has an active
ConvNode, then this takes precedence over any other default processing
for the actor's takeTurn() method. If the actor hasn't engaged in
conversation on the same turn, we invoke the ConvNode to continue the
conversation under NPC control; otherwise, we do nothing more. If
there's no ConvNode, we process the actor's "agenda." If there's no
ConvNode, and the actor has no agenda items that are ready to execute,
and the ActorState inherits from Script, then we invoke the script.
This structure means that a stateful conversation takes precedence
over everything, and an agenda item takes precedence over state-level
background activity.
In the Actor class, the TellAbout and AskAbout handlers now include
the "canTalkToObj" precondition by default. This ensures that the NPC
being addressed can actually hear the other actor, to disallow
conversations in cases such as when the two actors are too far apart
to hear one another. The HELLO, GOODBYE, YES, and NO commands now all
make the same check as well, in the sayToActor() method.
Similarly, ConversationReadyState now makes the same check before
going through a greeting. If the initating actor can't talk to the
target actor, the enterConversation() method says so and uses 'exit'
to terminate the command. This prevents strange situations from
arising commands that don't require the canTalkToObj precondition.
(SHOW TO works this way, because the actors don't necessarily have to
be able to talk to each other to use SHOW TO; we could show a note to
an NPC on the other side of a sound-proof window, for example.)
The new TopicEntry subclass InitiateTopic provides an easy way of
making an NPC initiate conversation based on simulation objects in the
environment. To make an NPC trigger a conversation, call the actor's
initiateTopic(obj) method, where 'obj' is the simulation object you
want to use to key the conversation. This will find an InitiateTopic
object in the actor's topic database matching the given object
('obj'), and show its response text. One easy way to use this is to
initiate conversation based on the NPC's current location: just create
InitiateTopic objects keyed to those locations where you want the NPC
to say something special, and add a line like this to the ActorState's
takeTurn() method:
getActor().initiateTopic(getActor().location);
The "topic inventory" list shown by the TOPICS command is no longer
enclosed in parentheses by default. The parentheses are still included
when the topic inventory is shown implicitly (by a TALK TO command or
a <.topics> tag, for example).
The new ConvNode property limitSuggestions allows a ConvNode to
indicate that suggested topics from the broader conversation context
should not be included in a topic inventory listing. This is useful
for times when the ConvNode won't allow the player to stray from the
subject, such a a ConvNode that only allows a YES or NO answer to a
question. In these cases, set limitSuggestions to true. The property
is nil by default, which causes topic inventory listings to include
not only any suggestions defined within the ConvNode, but also any
defined in the active ActorState or in the Actor itself.
The new Actor.scheduleInitiateConversation(state, node, turns) method
lets the game set up an NPC to start a converation at the next opportunity
after the given number of turns has elapsed. If 'turns' is zero, then
the conversation can start on the next turn on which the NPC hasn't
been targeted for conversation by the player; if 'turns' is one, the
player will get at least one more turn before the conversation can start,
and so on for higher 'turns' values. The important thing is that the
conversation can only start on a turn on which the NPC isn't targeted
with a conversational command (ASK, TELL, etc). The Actor checks its
list of pending conversations on each turn in its takeTurn() method,
before invoking the current state's takeTurn() method.
In the previous version, if Actor.initiateConversation() was called
from within the player character's turn (in an NPC's beforeAction()
handler responding to a player character action, for example), and
the ConvNode had a "continuation" message (defined in
npcContinuationMsg or npcContinuationList), the first continuation
message was incorrectly shown on the same turn. This no longer
occurs; the continuation message won't be shown until the next turn
at the earliest. ("At the earliest," because a continuation message
won't be shown on a given turn if the player character addresses a
conversational command to the NPC on that turn.)
When an actor continues a conversation of its own volition (using
npcContinueMsg or npcContinueList) in a ConvNode, if the continuation
routine actually displays any text, then the ConvNode will
automatically set the player character and the initiating NPC to be in
conversation with one another. This ensures that any subsequent
conversational command from the player will have the initiating NPC as
the default interlocutor.
In the past, the REPLAY QUIET command didn't display any acknowledgment,
and improperly left all subsequent text in bold-face. This has been
corrected.
ActorState.greetingsFrom now delegates to a method of the same name
in Actor; this makes it easier to change the default greeting for a
simple actor without a state object.
Along the same lines, ActorState.showGreetingMsg has been removed,
and replaced with Actor.defaultGreetingResponse. The method has been
moved to Actor because of the change to greetingsFrom, and the method
has been renamed for consistency with the similar pattern for the
other conversation command default message methods (for ASK, TELL,
etc) in Actor.
Along the same lines, ActorState.goodbyeFrom now delegates to a
method of the same name in Actor by default. A new Actor method,
defaultGoodbyeResponse, displays the default message, for consistency
with the naming of the similar methods for other conversation
commands.
Two new ConversationReadyState methods have been added:
enterFromByeMsg and enterFromConvMsg. These are provided for
convenience as single-message complements for enterFromByeList and
enterFromConvList, respectively. By default, these simply invoke
their respective list scripts.
VocabObject has a new feature that allows an object's vocabulary to
be divided into "strong" and "weak" tokens. Weak and strong tokens are
the same as far as the parser is concerned, and are entered into the
dictionary as usual. The difference is that a weak token can only
be used to refer to an object in combination with one or more strong
tokens. That is, if the player enters a noun phrase, and the noun
phrase matches all of the vocabulary for a given in-scope object,
the object will match the noun phrase only if one or more of the
words in the noun phrase is a strong token for the object.
The purpose of this new feature is to make it easy to define extra
vocabulary for an object that the parser accepts for an object
without creating any new ambiguity. This comes up in situations
where you simply want to create additional synonyms for an object, as
well as in cases where an object is most strongly identified in terms
of its relationship to something else. For example, you might have a
location that contains a couple of doors, one to a house and the
other to a shed. You could use adjectives ("house door" and "shed
door") to differentiate the doors, but it might be more natural to
use a prepositional phrasing, such as "the door of the house" and
"the door of the shed." If you also have a house and a shed object,
though, this phrasing would create unwanted ambiguity with those
objects. This is where weak tokens come in. If you define "house"
and "shed" as weak tokens for the respective door objects, the parser
will never take those words alone to refer to the doors, so there
will be no ambiguity with the house and shed objects themselves; but
the parser will still allow "house" and "shed" to refer to the doors,
as long as they're used in combination with the strong token "door".
Weak tokens are defined per-object (they're not global). Each
object that has weak tokens keeps a list of its weak tokens in its
'weakTokens' property. In the basic VocabObject.matchName()
implementation, the object checks the noun phrase to see if it
consists entirely of weak tokens, and rejects the match if so.
When you define an object, you can define weak tokens in one
of two ways. If you define 'noun' and 'adjective' (etc.) properties
directly, simply define a 'weakTokens' property containing a list of
strings giving the object's weak tokens. If you use the
vocabulary initialization string, simply enclose each weak token
in parentheses. For example:
+ Door 'front door/(house)' 'front door of house'
"The door is badly weathered. "
;
VocabObject.matchName() and matchNameDisambig() now invoke a new
method, matchNameCommon(), to carry out their common handling.
This change facilitates the weak-token feature mentioned above.
In most cases, when a game needs to override an object's name
matching test, it should override matchNameCommon(), since this
method provides the common handling for normal matching and
disambiguation matching. Games can still override matchName()
and/or matchNameDisambig() individually, but it's only necessary
to do so when you want to use different rules for normal matching
and disambiguation matching.
The English parser's handling of quoted string literals as adjectives
has been improved slightly, but the changes will require some small
adjustments to any games currently using literal adjectives.
Instead of allowing any adjective to be quoted, the parser now
only matches quoted strings that are specifically designated as
"literal adjectives" in an object's vocabulary. To designate an
adjective as a literal adjective, you can either enclose the
adjective in double-quotes in the vocabulary initializer string, or
you can explicitly define the word using the 'literalAdjective'
part-of-speech property instead of the normal 'adjective'
part-of-speech. For example, you could define an elevator button
for the lobby level like so:
+ Button '"L" button' 'L button' "It's a button labeled <q>L.</q> ";
This change makes the parser stricter about which words it accepts
as quoted adjectives, but it also allows the parser to be more
flexible about requiring the quotes. In particular, the parser now
accepts all of the following forms for the button defined above: "L"
button, button "L", L button, and button L. In the past, the parser
was unable to accept the last of these, because without the quotes,
it wasn't able to tell that the unquoted "L" was meant as a literal
adjective. Now that the "L" is defined in the dictionary as a
literal adjective, it's no longer necessary for the player to quote
the word for the parser to recognize it as a literal adjective, so
the parser is able to accept the form with the literal adjective
following the noun.
Note that this change also involves a subtle change to the special "*"
vocabulary wildcard token. In the past, if the string '"*"' (that is,
an asterisk enclosed in double-quotes) appeared as an 'adjective'
dictionary entry for an object, then that object matched any quoted
string used as an adjective in player input. Now, this effect is
obtained by using the string '*' as a 'literalAdjective' dictionary
entry for an object. This change has no effect at all on the way you
write vocabulary initializer strings, since you still enclose the
asterisk in quotes in that string; however, if you're defining a
'literalAdjective' list directly for an object, you now must drop
the quotes and add a word entry consisting of simply the asterisk.
The English parser now accepts pronouns as responses to
disambiguation questions. (The disambigListItem(noun) rule now
matches completeNounPhraseWithoutAll rather than qualifiedNounPhrase,
as it did in the past; the new sub-production matches pronouns, which
qualifiedNounPhrase does not.)
In the English parser rules, the command "T topic" is now
accepted as a short-cut for "TELL ABOUT topic." Together with
the "A topic" shortcut for ASK ABOUT, this can greatly reduce
the amount of typing a player has to do in a game that has a lot of
ASK/TELL character interaction.
In the English parser rules, the Hello action now accepts "hallo"
as equivalent to "hello" or "hi", and also accepts "say" with any
of these variations.
A new Action method, callAfterActionMain(obj), allows a game to register
an object for invocation at the end of the current action. This is
only meanginful when used on the current gAction object, since it
registers for notification of completion of the current action.
When the current action is finished - including the iteration over
all of the objects involved in the action - the action invokes
the afterActionMain() method of each registered object.
This new method is especially useful for adding a "summary" of an
action that involves multiple objects. Each individual object's
action handler (an action() method in a dobjFor() block, for example)
can register a handler object to receive notification when the
overall command is finished. A given object can be registered only
once - redundant registrations are simply ignored - so each
individual iterated object doesn't have to worry about whether or not
other iterated objects will register the same handler. Then, at the
end of the action, the handler will be invoked; it can determine what
happened in the action, and do something based on the end result.
For example, the handler could scan the transcript (gTranscript) for
certain types of reports, and add a summary message based on the
reports, or could even replace some of the individual reports with a
summary. The handler could also take addition action based on the
overall end results; for example, in a GIVE TO command, a handler
could look at the full set of objects successfully given, and decide
that the combination is sufficient to allow the recipient to give the
donor something in return.
A new CommandTranscript method, summarizeAction(), can be used with
the new callAfterActionMain() system to generate a summary of an
action that involves multiple objects. summarizeAction() scans
through the transcript, looking for runs of two or more reports for
the current action that match criteria specified by the caller (via a
callback function). For each run of two or more consecutive
qualifying reports, the method removes those reports, along with their
corresponding multi-object announcements, and replaces the last of the
reports with a single "summary" report generated by the caller (via
another callback function). This method lets you turn a transcript
like this:
gold coin: Bob accepts the gold coin.
gold coin: Bob accepts the gold coin.
gold coin: Bob accepts the gold coin.
into something like this:
Bob accepts the three gold coins.
This sort of summary isn't straightforward to generate in general,
and the library makes no attempt to apply it generically. In specific
cases where you can control the range of possible results, though,
this can be a powerful way to improve the game's output by describing
an iterated action as though it were a single logical unit.
Phrases involving quantities ("five coins") and "all" with a plural
("all coins") are now resolved a little more consistently and
intuitively, from a player's perspective. First, phrases involving
quantities are now always disambiguated in the same manner as
singular phrases, and then the required number of objects is chosen;
in the past, disambiguation filtering was not as consistent. This
change ensures in particular that "collectives" are properly filtered
in or out; the old mechanism sometimes missed collectives because it
sometimes skipped the normal disambiguation filtering. Second,
"all"-plus-plural phrases are now resolved to all matching objects,
rather than to only the most logical subset of matching objects, as
was the case in the past. This means that a command applied to "all
coins" really is applied to all coins that are present, even those
for which the action isn't currently valid.
To implement these changes, NounPhraseProd has a new method,
getVerifyKeepers(), that the disambiguation filtering methods call to
reduce the list. The default NounPhraseProd definition of the method
does the traditional filtering that the disambiguation filter did,
which keeps just the most logical subset of the results.
AllPluralProd overrides this method to keep everything in the list,
and QuantifiedPluralProd overrides the method to select a subset with
the desired number of entries.
The CollectiveGroup object by default no longer matches a noun
phrase that specifies a quantity ("take five coins" or "take both
coins"; or any singular phrase, since a singular phrase specifies a
quantity of one). Collective groups are designed to stand in for
a completely collection of in-scope individuals, not for arbitrary
subsets of the individuals, so when a quantity is specified we must
fall back on iterating over a selected subset of the individuals.
To implement this change, the filterResolveList() method now takes
two additional parameters: one giving the quantity specified in the
noun phrase, if any, and another giving the role played by the object
(DirectObject, IndirectObject, etc). When the noun phrase specifies
a quantity, the new parameter will be set to an integer giving the
quantity specified. If the noun phrase is unspecific about the
quantity (as in "take coins" or "get all coins"), then the quantity
parameter will be nil.
Note that the isCollectiveAction() method now takes an additional
parameter as well, giving the role in the action played by the object
being resolved (DirectObject, IndirectObject, etc). This allows
differentiating the handling based on both the action and the role
played in the action.
Custom CollectiveGroup objects can represent specific
quantities of objects, from the player's perspective, if desired. For
example, a game might want to create a CollectiveGroup that represents
a quantity of money, rather than dealing with the individual coins
making up the quantity. To do this, the CollectiveGroup object must
override the filterResolveList() method, and must set the "quant_"
element of its own ResolveInfo entry to an integer giving the number
of grammatical objects it represents. The CollectiveGroup must
keep track of what it's representing somehow; the best way to do this
is to create a new instance of the CollectiveGroup itself, and store a
property in the new instance giving the quantity represented by the
collective. Note that using CollectiveGroup objects in this manner
is tricky; you'll need to code action handlers for the custom
CollectiveGroup object so that they correctly take into the account
the quantity represented by the group object.
The ownershipAndLocationDistinguisher has been refactored into two
separate distinguishers: ownershipDistinguisher and locationDistinguisher.
Each of these new distinguishers is very similar to the old combined
distinguisher, but the ownershipDistinguisher gives priority to
ownership, regardless of the containment relationship. The library
classes that define distinguishers now use both of these, with
the ownership distinguisher applied first.
The purpose of this change is to ensure that ownership will be
used as a distinguishing feature whenever possible, before the parser
falls back on location. The old combined distinguisher could only
use ownership in the limited case where the owner and immediate
location were the same, because it couldn't otherwise be sure that it
would be able to distinguish multiple objects with the same owner but
different locations. By separating the ownership and location
distinguishers, we first try to identify objects purely by ownership;
when this fails, we fall back on the old approach of identifying by
immediate location in preference to owner.
To support this change, the English methods for owner-or-location
names (aNameOwnerLoc, theNameOwnerLoc, countNameOwnerLoc) now take
a parameter indicating whether to use ownership or location as the
first priority in generating the name. When ownership takes priority,
these methods will show the name with a possessive form of the owner
regardless of the containment relationship between the object and its
owner.
Possessive qualifiers (such as "bob's" in "bob's chair") are now
resolved in a special resolution context that allows referring to
owners that aren't in scope for the purposes of the phrase being
qualified (such as the "chair" in "bob's chair"). A possessive
phrase is now considered in scope if it's in scope for the phrase
being qualified or it's known to the actor performing the
command. This allows possessive phrases to be used to refer to
objects that are present, and which are known to belong to an
actor, even if the actor itself isn't present.
When the parser generates a disambiguation prompt, and the prompt
distinguishes objects by possessives ("which match: bob's match or
your match?"), the parser automatically sets the pronoun antecedents
for "him" and/or "her" to the people mentioned in the prompt. This
allows the player to answer with "his" or "hers" (or "his match"),
referring back to the person or people mentioned in the message.
The parser error messages that display literal text entered by the
player (such as the message for unrecognized punctuation marks, and
the message for unknown words) now HTML-ify the user's text. This
ensures that any markup-significant characters (such as "<" and
"&") are converted into HTML sequences that display the
corresponding characters.
A bug in the English parser's rule for phrases like "anything in the box"
caused run-time errors when the word "anything" alone was used as a noun
phrase. This has been fixed.
In the hint system, the new Goal methods openWhenTrue and closeWhenTrue
can be used to define arbitrary conditions that open and close the goal.
These new general-purpose conditions supplement the more specific
conditions (openWhenSeen, openWhenAchieved, etc.); the goal will be
opened if
any of the openWhenXxx conditions are met, and will
be closed when any of the closeWhenXxx conditions are met.
The new Goal property openWhenDescribed lets you specify that the goal
should be opened when the referenced object is described (usually with
EXAMINE). This is good for goals where the existence of a puzzle isn't
made apparent until the full description of an object is viewed. The
new property closeWhenDescribed correspondingly closes the goal when
the referenced object is described.
The RESTART command no longer resets the commandSequencer to the
"no-command" state, as it did in the past. This change corresponds to
the change in commandSequencer start-up state effected in 3.0.6i.
The English grammar now accepts '1' as a synonym for 'one' or 'any' in
singular indefinite noun phrases, such as "take 1 coin." (In the
past, the grammar omitted this alternative.)
The new class SecretFixture is designed for objects that are needed in
the internal implementation but are not meant to be manipulated
directly by characters. This is a simple subclass of Fixture; the
main difference is that a SecretFixture is hidden from "all," to help
prevent direct references to it in commands.
In the English grammar, a new production named singleNounOnly can be used
where a single noun (rather than a list of nouns) is
structurally
required. The singleNoun production itself will structurally match a
list of nouns, but considers such matches semantically invalid. In
contrast, singleNounOnly won't even structurally match a noun list.
The firstCommandPhrase(withActor) rule now uses the new singleNounOnly
production to match the actor phrase. This eliminates the structural
ambiguity of certain types of invalid input that were able to cause
unbounded memory consumption with the old rule.
The menu system no longer shows a border in full-screen menu windows.
(When a menu takes up the whole game window, a border is superfluous,
since there's no other window requiring separation. Removing the
border slightly but noticeably improves the appearance of a full-screen
menu.)
The Decoration class now gives Examine commands a reduced "logical
rank" (of 70) for disambiguation purposes. This means that if the
player enters an Examine command, and the vocabulary for the direct
object matches a Decoration object and a non-Decoration object, the
non-Decoration object will be chosen ahead of the Decoration. It's
usually desirable to treat Decoration objects as second-class citizens
for disambiguation purposes, because they're usually meant to stay in
the background as much as possible.
The standard Actor handlers for the Give and Show actions now produce
more sensible messages for giving or showing something to oneself.
(The old message was the generic "you do not respond"; the new message
is "Giving [or showing] that to yourself won't accomplish anything.")
The base definition of the method filterResolveList() has been moved
from Thing to VocabObject. The parser calls this method on objects
that match vocabulary in player input, so it more properly pertains to
VocabObject than to Thing. (In practice, this is important in some
cases when Topic objects match player input, because Topic objects
descend from VocabObject but not from Thing.)
The cached scope list in Resolver and its subclasses is now better
encapsulated, to make it easier to subclass Resolver. In particular,
the "scope_" member is referenced only in the method cacheScopeList(),
objInScope(), and the new method getScopeList(). This means that a
subclass can dispense entirely with the cached scope list (and the
"scope_" member), as long as the subclass implements objInScope() and
getScopeList() to return mutually consistent results. A subclass
can alternatively override cacheScopeList() to use a different set
of rules to obtain and cache the scope list.
Along similar lines, TAction.initResolver() now calls
cacheScopeList() to initialize its cached scope list, rather than
doing so directly. This allows TAction subclasses to customize the
scope rules in exactly the same way that subclasses of Resolver can
customize scope. Likewise, TopicResolver.filterAmbiguousNounPhrase()
now calls objInScope() rather than accessing the "scope_" list
directly.
The new Achievement method addToScoreOnce() makes it a little easier
to do score book-keeping in the common case of achievements that are
scorable only once. This method adds the achievement to the score,
with a given number of points, but only if the achievement has never
been scored before; if the achievement has been scored before, the
method does nothing at all. This makes it unnecessary to keep track
of any separate status to avoid scoring the same action more than
once. (Some achievements are meant to be repeatable; this method
wouldn't be useful in such cases, obviously.)
The complexMultiTransform report transformer is now a little more
liberal in determining when to add visual separation between reports
when an action is iterated over multiple objects. In the past, visual
separation (i.e., a paragraph break) was added only when implicit
command announcements were present. Now, visual separation will be
added any time the report group for an individual object in the
iteration consists of more than one report, or a report has a single
message with text over 60 characters in length. This new, more
liberal policy is designed to add visual separation essentially any
time an individual object's result message is non-trivial, since
multi-object messages become hard to read when they're all jammed
together, unless each individual message is very short. The new
policy errs on the side of adding too much visual separation, which on
balance seems to do considerably less harm to readability than too
little separation.
The handling of OOPS responses has been improved slightly. In
particular, when the player uses an OOPS command to correct an error,
the new text that results from applying the OOPS substitution is now
run through the normal pre-parsing sequence.
In the past, an OOPS command itself was run through the normal
pre-parsing steps, but the new command resulting from applying the
OOPS replacement text was not pre-parsed. This resulted in
incorrect behavior in certain certain rare cases. One noticeable
example was when a SpecialTopic was active. Because SpecialTopics
rely on pre-parsing to match their special command templates, and
because the corrected text after the OOPS wasn't pre-parsed, it was
effectively impossible to use OOPS to correct a typo in a command
intended to match a SpecialTopic. The new OOPS handling ensures that
pre-parsing is run as normal on the new command resulting from
applying an OOPS correction.
When the player enters a command that matches an active SpecialTopic,
the library now treats this as a conversational action directed to the
interlocutor, as though the player had typed an ASK or TELL command.
This is important in cases where the actor has background actions that
it performs based on the absence of an explicit conversational
interaction from the player on any given turn, such as showing
ConvNode continuation messages.
ASK FOR commands are now handled uniformly with other conversational
commands (ASK ABOUT, TELL ABOUT, GIVE TO, etc). In particular, these
commands are now routed to the ActorState for handling, and can be
handled using the new AskForTopic class, which works along the same
lines as GiveToTopic and ShowToTopic.
The library now automatically sets the antecedent for the appropriate
pronouns to refer to a responding actor, any time a TopicEntry is used
to generate response text. This ensures that the player can use a
pronoun on the next command to refer to the last actor who generated a
conversational message.
The new method Actor.setPronounObj(obj) makes it easier to set a
simulation object as the pronoun antecedent for commands targeting the
given actor. (In the past, only the setPronoun() method was
available, which took a list of ResolveInfo objects. When the game or
the library needs to set the pronoun directly, it's often easier to
set it directly in terms of simulation objects, without creating a
ResolveInfo list.)
The new classes SimpleNoise and SimpleOdor make it easier to define
Noise and Odor objects for the common case that a noise/odor is just
a background part of the room description that (1) doesn't require
ongoing "daemon" announcements, and (2) doesn't need any
differentiation among the different types of descriptive messages.
These classes simply use the "desc" property as the default for all
of the descriptive messages, and are marked as "ambient," to avoid
automatic inclusion in the room description.
The Throw command now works properly when throwing something at
oneself. (In the past, throwing something at oneself incorrectly
tried to treat the actor as the drop location of the throw; it now
correctly treats the actor's location as the drop location.)
The new NOTE verb accepts an arbitrary literal as its object; the
command doesn't do anything except acknowledge the input. The purpose
of the command is to allow the player to enter arbitrary notes into
the session transcript as she plays the game. Players might want to
make such notes for their own later reference, but this command is
especially useful for play-testing, because it gives play-testers
a very easy way of pointing out bugs or making comments directly in
the session transcript where they apply, and at the moment they occur
to the player.
A library bug caused a run-time error if an attempt was made to remap
from a direct object handler (a dobjFor) to an intransitive action
(an action with no objects). Remapping to intransitive actions will
now work properly.
The RealTimeDaemon class had a couple of problems that prevented it
from working properly. For one thing, it wasn't based on RealTimeEvent
as it should have been; for another, it didn't reset its next firing
time when fired. These problems have been corrected.
Released June 15, 2003
The commandSequencer now starts in "before-command" mode, rather than
in "no-command" mode. This allows any introductory text displayed
before the first command to use the normal command sequencing tags.
The "seen" property of Thing has been changed slightly in meaning. In
the past, this property was set to true only when an object was
specifically listed as a portable item in a room description. Now,
the property is set to true whenever a room description is displayed
from the player character's perspective, and the object is visible to
the player character. The important difference is that
unlisted items, such as 'Fixture' and 'Heavy' items, or items
with special descriptions, will be marked as seen as soon as their
containing rooms are described.
Due to a bug in the previous version, the REPLAY command only worked
when one of its qualifiers (REPLAY QUIET or REPLAY NONSTOP) was used;
just plain REPLAY didn't work. The plain REPLAY command now works
correctly.
The gameinfo.t module has been removed from the base system library.
In its place, the GameID class has been extended to write the GameInfo
data file automatically during pre-initialization. If the game defines
a GameID object, then the library automatically writes a gameinfo.txt
file, using values from the GameID object. Refer to the comments
in modid.t for the GameID class.
Actor.setConvNode() now accepts a string giving the name of a
ConvNode, as an alternative to a reference to a ConvNode object. In
the past, only the object reference was accepted. Passing in a
string naming a ConvNode now has the same effect as passing a
reference to the ConvNode itself.
The new Actor method initiateConversation() makes it easier to code
an NPC initiating a conversation. This method takes an ActorState
object to use as the NPC's new state, and a ConvNode (which can be
specified by the string name of the ConvNode) to use as the initial
conversation node.
In addition, the new ConvNode method npcGreetingMsg is defined to
display the initial conversational exchange in an NPC-initiated
conversation. Any ConvNode used in an initiateConversation() call
must either override npcGreetingMsg to define a greeting message, or
define an npcGreetingList property as a TextList containing a list
of greeting messages.
The new ConvNode method npcContinueMsg is defined to display a
conversational "continuation" message from the actor. This method
lets the NPC continue a conversation of its own volition if the player
character doesn't do so. This method is invoked on each turn when the
ConvNode is active,
and the player didn't enter a
conversational command on that turn. The method is invoked during the
actor's takeTurn() daemon processing. To define a continuation
message, you can either define npcContinueMsg as a double-quoted
string with the message, or you can define npcContinueList to a
TextList subclass containing a list of continuation messages.
If you don't override npcContinueMsg or npcContinueList, there will
be no NPC-initiated continuation message. If you do provide a
continuation message, then the ConvNode stays active by default. You
can use the <.convnode> tag within the continuation message's
text to switch to a new ConvNode, just as in a topic response message.
The commandSequencer now starts in "before-command" mode, rather than
in "no-command" mode. This allows any introductory text displayed
before the first command to use the normal command sequencing tags.
The Yes and No topic classes (YesTopic, NoTopic, YesNoTopic) had a
number of bugs that prevented them from working properly. These
classes should now work correctly.
A new template for DefaultTopic makes it easier to define these
objects. The DefaultTopic template accepts simply a response string
(double-quoted), or a list of response strings (single-quoted).
A bug in the AltTopic mechanism prevented intermediate AltTopics in
nestings more than one level deep from being properly selected. This
is now fixed.
In the English-language module, the miscWord production now accepts
'#'-prefaced numbers and quoted strings. This provides more
flexibility for inputs in things like topic phrases.
The AccompanyingState class didn't use the correct test for a travel
action in its beforeAction() method, which prevented the class from
carrying out its accompanying travel role at all. This has been
corrected.
ActorState.obeyCommand() generated the wrong default message. This
is now fixed.
ConvNode had a couple of problems that caused run-time errors or other
strange behavior when activating a ConvNode that had no associated
SpecialTopics. These have been fixed.
Fuses and daemons (including real-time events and prompt daemons) now
run automatically in a standard Action environment. This means that
you can call essentially any library code without having to worry
about whether or not it will need to access gAction or gTranscript.
In the past, fuse and daemon code was called with no Action
environment in effect, which made it necessary to either limit the
library calls made within the fuse or daemon, or to explicitly set up
an action environment (using withActionEnv(), for example) before
invoking library code. This added a lot of unnecessary complexity
to fuse/daemon code. Now that these routines are always invoked with
a valid Action environment, these restrictions have been lifted.
The withActionEnv() method now takes an additional argument, giving the
specific Action class to use for the dummy action object in effect during
the callback.
The senseContext object's pushSenseContext() and popSenseContext() have
been removed; they've been replaced with a new withSenseContext() method.
In addition, the callWithSenseContext() function has been changed
to eliminate the argument list parameter; instead, callers should use
anonymous functions when the code to be invoked requires arguments.
(This is the way the rest of the library already used the function in
most cases anyway, so this change simply cleans up some old code
that's no longer needed. Most games won't need to call this function
directly, so this is unlikely to affect any existing game code.)
The INSTRUCTIONS command now operates either in menu or in-line mode,
not both. In the past, the menu format supplemented the in-line format,
but this was redundant and complicated the user interface.
Released June 7, 2003
Several major enhancements to the Actor class have been added.
These changes are described in more detail in the comments in the
library sources files, and in a new series of technical articles on
the
tads.org web site,
Creating Dynamic
Characters in TADS 3. We'll summarize the changes here, but you
should refer to the articles for full details.
WARNING! Due to the extensive scope of these new features,
there's a good chance that they'll undergo some changes as we gain
experience with them. Be aware that game code that uses these features
might have to be changed to accommodate library changes in future updates.
A new class, ActorState, makes it easier to create characters that
exhibit multiple behaviors. The Actor class has been changed to work
with ActorState; most of the methods involved in describing an actor's
physical state and in interacting with an actor are now delegated from
the Actor class to the ActorState object. The idea is that most of
the parts of an actor that need to vary according to the actor's
behavior have been moved to the new ActorState object; this means that
adding a new behavior to an actor is a matter of defining a new
ActorState object for the actor.
Several subclasses of ActorState have also been created. These
include ConversationReadyState and InConversationState, which are used
to make an actor explicitly enter, maintain, and exit a conversation;
HermitActorState, for times when an actor is unresponsive; and
AccompanyingState and AccompanyingInTravelState, which allow an NPC to
travel with another character as part of the character's own turns.
A new "topic database" system makes it easier to manage
conversational interactions, by creating individual objects that
represent responses to ASK, TELL, GIVE, SHOW, and other interactions.
The TopicDatabase class is used to store an actor's responses; Actor
and ActorState inherit from TopicDatabase, so you don't normally
create instances of this class directly. The TopicEntry class
represents a response; several subclasses, including AltTopic,
AskTopic, TellTopic, AskTellTopic, GiveTopic, ShowTopic,
GiveShowTopic, YesTopic, NoTopic, DefaultTopic, and SpecialTopic are
used to create the individual responses.
The conversationManager object and the ConvNode class are used
to manage "threaded" conversations. A threaded conversation is one
where an actor keeps track of what has been said before, and responds
according to the context of the conversation.
The SuggestedTopic class and its subclasses let you maintain a
"topic inventory," which is a mechanism that automatically suggests
topics to the player. This is an optional mechanism that you can
use in various ways; you can use it as a sort of hint system, and
you can use it to guide threaded conversations.
Two library modules have been renamed: 'hints.t' is now 'hintsys.t',
and 'menus.t' is now 'menusys.t'. Since these modules are included in
most projects using the library file (adv3.tl), this shouldn't require
any changes to any game code or project makefiles. The new names
better reflect that these modules define the hint and menu systems,
respectively, rather than actual hints or menus.
A new function, withActionEnv(func), makes it possible for daemon
code to create a simulated Action environment. Some code relies upon
there being a current Action object and an active command transcript
object; these objects are normally only available while executing a
command, which made it difficult to call code that depends on this
command environment from within a daemon. This new function makes it
easy to call such code from within daemons and other non-command
contexts. The function sets up a dummy Action object and a default
command transcript, then invokes the given function; the function can
do anything that can be done during normal Action processing.
Openable now shows any special descriptions of the contents of an
object that are revealed when the object is opened (via an OPEN
command).
The English module now includes some needed customizations to the
TopicAction class. These were missing in the past, which caused
incorrect messages to be generated in some cases involving this
type of action.
The new class RoomAutoConnector is a subclass of RoomConnector that
can be mixed in to any BasicLocation subclass to make the room usable
directly as a connector to itself. Room inherits from this class (it
formerly inherited from RoomConnector and overrode part of the
interface; the overrides are no longer necessary because they're
contained in RoomAutoConnector itself now). In particular,
RoomAutoConnector can be mixed in to any NestedRoom object's
superclasses to make the nested room usable as a connector to itself.
The new notification methods beforeTravel(traveler, connector) and
afterTravel(traveler, connector) are invoked just before and after a
traveler travels via travelerTravelTo(). These notifications are sent
to each object connected by containment to the traveler;
beforeTravel() is called on each object connected by containment to
the traveler in its old location, and afterTravel() is called on each
object connected by containment in the new location.
These notifications are more precise than using beforeAction() and
afterAction() with the TravelVia pseudo-action, because these actions
are only called when travel is actually occurring. TravelVia will
fire notifications even when travel isn't actually possible.
A beforeTravel() method can veto the travel action using "exit".
The notification is invoked before the travel is actually performed,
and even before a description of the departure is produced.
The method Actor.trackFollowInfo() is now invoked from
Actor.beforeTravel() rather than directly from
traveler.travelerTravelTo(), as it was in the past. This change
makes the follow-tracking a lot less "special," in that it uses the
general before/after travel notification system rather than a
separate, purpose-built notification system. Note that this change
means that any actor object that overrides beforeTravel() must
inherit the base class implementation if the actor wants to be able
to follow other actors.
The default travel precondition calculations have been changed
slightly to allow connections between top-level rooms to be placed
inside nested rooms. This allows, for example, a window that can
only be reached when standing on a desk, or a secret passage from
inside a wardrobe. The main changes are that TravelVia doesn't
automatically require the traveler to be in the outermost room, but
in the room containing the outbound connector; and travel between
top-level rooms that leaves the traveler inside a nested room (as
would happen when climbing through the window from outside and ending
up on the shelf) now provides a normal arrival message.
First, BasicLocation.roomTravelPreCond() no longer does anything;
the method is still present so that rooms can override it as desired,
but it doesn't do anything by default. In the past, this method
unconditionally added a precondition requiring that the traveler be
in the outermost room; we don't want to apply that condition, since
the required starting location of the traveler depends on the
outbound connector, not the room.
Second, TravelConnector.connectorTravelPreCond() now adds a
requirement that the traveler be in the connector's location, if the
connector has a location. This ends up having the same effect as
BasicLocation.roomTravelPreCond() in cases where explicit connectors,
such as doors and passages, are situated in the top-level room.
Third, RoomAutoConnector.connectorTravelPreCond() overrides the
inherited version, and instead requires that the traveler be in the
"appropriate" starting location. This provides the same effect as
the old BasicLocation.roomTravelPreCond(), but with an enhancement.
The "appropriate" location is defined as the nearest enclosing
location (enclosing the traveler) that has a directional connection
("north = xxx", etc) leading to the room. In most cases, rooms are
connected directly to one another only at the top level; in such
cases, the directional connection will be found in the traveler's
outermost room, so the effect will be exactly as it was with the old
system. But here's the enhancement: in cases where the directional
connector is defined leading out of a nested room, this will
correctly find the nested room as the starting location.
Note that NestedRoom objects cannot by default be used as
direct targets of directional connectors, because they're not based
on RoomConnector. It seems highly unlikely that it would ever be
useful to connect a nested room directly to a directional connector
(i.e., "east = wardrobe") - some kind of explicit connector (a door,
or a passage, or something) seems desirable in almost every case,
just from the perspective of game geography. In the event that a
direct directional connector is required, though, simply include
RoomAutoConnector in the superclass list for your specific nested
room.
Fourth, the methods travelerArriving() and travelerLeaving() have
been moved from Room to BasicLocation. This produces the proper
travel messages for any travel between top-level locations, even when
the travel starts or ends in a nested location.
BasicLocation.travelerArriving() now enforces the defaultPosture
setting for the location. The default posture is enforced via a
nested command before the arrival message is displayed. This makes
it easier to create locations with restrictions on posture, such as a
low crawl where you can only sit or lie down.
Note that if all goes well, there won't be any mention of the
action for the player character (because the successful nested action
will result in a default report, which will be suppressed due to the
non-default message for the room description); so the new posture
will simply be reflected in the description of the new location.
Generally, all should always go well in these cases, since the actor
will already be located within the location and merely has to change
posture. If you want to explain why the posture change is necessary,
you can override travelerArriving() for the location, display an
explanation, then inherit the default. An appropriate message would
be something like "The ceiling is so low you have to lie down."
The semantics of TravelConnector.getDestination() have been changed
slightly. First, the method now takes a traveler rather than an
actor, since connectors are always traversed by travelers. (A
traveler might in fact turn out to be an actor, of course, but it
could also be something else, such as a vehicle.) Second, the method
is now required to return the destination, and the destination must be
stable for the duration of the turn up to the point where the travel
actually occurs.
To allow for connectors that are affected by traversal (for
example, a connector that randomizes its destination each time it's
used), the new method noteTraversal() must be called whenever the
connector is actually traversed. This method can change the connector
state as needed for the traversal. Note that game code should
generally never need to call noteTraversal(), as the library
dobjFor(TravelVia) action implementations do this automatically as
needed. This method is provided for games to override as needed.
The base TAction and TIAction classes no longer require that any of
the target objects of the command define an action() method for the
verb. Instead, the classes now merely require that at least one of
the target objects defines any of action(), check(), or verify(). If
none of the target objects defines any of these methods for the verb,
then the verb will automatically add an "illogical" status during the
verify phase.
The purpose of this check is to ensure that the command has
some sort of handling. If the game adds a new verb, but never
defines any handling at all for the new verb for some object, then
without this check, there would be no handling at all if the player
attempted to apply the verb to that object. The old check tested for
the existence of an action() handler, but this was overly
conservative, since an object could have fully handled the verb in its
verify() or check() handler. The new test allows for this by allowing
the action to proceed if any sort of handler (check, verify, or
action) is defined for an object involved in the command.
A new property, globalParamName, has been added to Thing. This lets
you define a substitution parameter string (for "{xxx}" sequences)
that can be used at any time to refer to the object in message text.
Once you define an object as having a global parameter name, you can
use that name to refer to the object in {xxx} sequences, even when no
command is active, and even when the object isn't involved in any
command. This can be especially useful for writing messages that
refer to objects whose names change in the course of the game, such as
actors who are known by one name until introduced, then are known by
another name ("the white-haired man" might become "Bob" after he tells
us his name, for example).
A new Script subclass, EventList, provides a convenient way of
defining scripts using procedural code in steps without writing a big
'switch' statement in the doScript() method. The eventList property
contains a list of script step elements. Each element gives one step
of the script. An element can be a single-quoted string, in which
case the string is simply displayed; a function pointer, in which case
the function is invoked with no arguments; a ScriptEvent object, in
which case the object's doEvent() method is invoked; or nil, in which
case nothing happens on the step.
The Actor method getDefaultInterlocutor() has been renamed to
getCurrentInterlocutor(), to better reflect its purpose. The
conversation model keeps track of the actor we're currently talking
to; conversational commands that aren't directed to a specific actor
(such as ASK ABOUT BOOK) are assumed to be directed to the current
conversational partner.
A couple of the abstract base classes in the area of Container have
been renamed to better reflect their purposes. The class formerly
known as BasicContainer is now called BulkLimiter, and the class
formerly known as Encloser is now called BasicContainer. So, Container
is a BasicContainer, which is a BulkLimiter; Surface is also a
BulkLimiter. The new name BulkLimiter is more consistent with its
main purpose, which is to constrain the aggregate bulk of its
contents. The old name "Encloser" was confusingly similar to the
real word "Enclosure," and was vague in conveying how the class
differs from Container; the new name BasicContainer makes it clearer
that this class contains some of the basic abstract functionality
of Container but
The actor inventory listing mechanism has been reworked to make it a
smarter about the listing format. When the inventory list is short,
the listing will appear in a single sentence showing both the items
being carried and the items being worn. When the listing is long,
it's broken up into two sentences, as in the past. The threshhold for
"long" is set with a property of the new DividedInventoryLister class,
singleSentenceMaxNouns; the default is 7, which means that the listing
will be shown in one sentence if it involves seven items of fewer, two
sentences otherwise. For example:
>inventory
You are carrying a music box, a gold coin, and thirty silver coins,
and you're wearing a watch and a helmet.
>inventory
You are carrying a cardboard box (which contains several LP's, a
bunch of photographs, and some letters), a rubber chicken, two
pieces of the Magic Goblet of Forblotsk, and a bowl of chowder.
You're wearing a pair of flippers, a wool jacket, and a red wig.
The list length is determined by capturing the output and counting
the phrase separators. The English library counts commas, semicolons,
the word "and", and right parentheses to determine the phrase count.
This change slightly affects the Actor class (showInventoryWith now
takes only one lister object), and extensively affects the
InventoryLister class and the WearingLister class and their subclasses
(the English-specific implementations in msg_neu.t). Games shouldn't
be affected unless they modified these classes.
If you want the old behavior, where the items being worn and the
items being carried were always shown as separate sentences, simply
set DividedInventoryLister.singleSentenceMaxNouns to zero. If you
want to always use a single sentence no matter what, set the property
to some absurdly high number - 30000, say.
If you'd prefer the more traditional behavior that shows the items
being worn marked "(being worn)" and mixed in with the rest of the
inventory in a single listing, simply change Actor.inventoryLister
to refer to actorSingleInventoryLister.
The match list for an ALL phrase in player input is now filtered
through the filterResolveList() method of each object in the list,
just as ambiguous noun phrases are. This allows objects that
substitute for others in resolution lists (such as Collective and
CollectiveGroup objects) to perform the same substitutions in ALL
lists that they would in normal matches.
The miscWord grammar rules are now defined in en_us.t, rather than
in the language-independent parser.t as they were previously. This
change is necessary because different languages might have different
token types that are valid in miscellaneous word lists. (In point of
fact, the English parser defines a special language-specific token,
tokApostropheS, that is now allowed in miscellaenous word lists.)
The menu system now uses the regular game color scheme as the
defaults; the top "instructions" bar is shown using the status line
color scheme, and the main menu area uses the normal game window color
scheme. This scheme is the safest default, since it uses only colors
the user has selected (on interpreters that allow the user to select
the color scheme); this ensures in particular that things like
hyperlink colors work well with the menu colors, since presumably the
user will have chosen settings that work well together. (If not, at
least it's not the game's fault.) This change means that menus won't
stand out as well from the main game window, since they use the same
color scheme, so the default mode for menus has been changed to the
"full screen" mode. This new default appearance - full screen, using
normal game and status-line colors - makes menus very unobtrusive,
since they just look like an ordinary game screen.
The menu system takes advantage of the new MORE-mode banner style
option, by using a separate banner to show "long topic" items. Since
long text can now safely be displayed in a banner, with the interpreter
providing pagination via MORE prompts as needed, long topics can avoid
taking over the main game window. This is nice because it leaves the
original game window intact (without any need to clear the screen) after
the user exits from the menu system.
The MenuItem class has a new property, heading, that specifies the
text to display as the heading of the menu while the menu is active.
By default, the heading is the same as the title, which is the string
displayed in the parent menu's list of items. The separate property
allows the caption shown while the menu is active to differ from its
title, if this is desired.
A couple of the English messages for the npcMessagesDirect group have
been recast as quoted statements from the NPC, for consistency with
other messages in the group. The affected messages are
noMatchDisambig and disambigOrdinalOutOfRange. In addition,
askDisambig (in the same group) has been tweaked slightly to
accommodate this change.
The CollectiveGroup class has been split into two classes. The full
behavior of the old class is now contained in the class
ItemizingCollectiveGroup, which is a subclass of CollectiveGroup. On
Examine, the CollectiveGroup class no longer shows the itemized list
of collected items, but instead simply shows its own description using
the normal Examine handling. The itemizing behavior is desirable in
some cases of collective groups, but certainly not all; this change
makes it easy for the game to select whether or not the itemizing
behavior is used.
If a check() routine (in a dobjFor() or iobjFor() group) terminates
the command with 'exit', the parser now automatically marks the action
in the transcript as having failed. This is the same thing that the
reportFailure() macro does, so this means that it's never necessary
to use reportFailure() from within a check() handler. Since the purpose
of check() is to enforce conditions on the action, exiting from the
check() routine necessarily means that the command has failed; this
change takes advantage of that to save games a bit of work.
A new set of grammar productions provide for third-person reflexive
pronouns in verbs with two noun-phrase slots: "put klein bottle in
itself," for example. These are mostly intended for conversation,
specifically things like "ask bob about himself," but will work in
general for the rare cases where they might be useful.
The parser resolves a third-person reflexive pronoun by referring
back to the resolved object list for the other noun phrase in the
action. Verbs taking only one noun phrase don't accept these, as they
make no sense: "open itself" isn't meaningful. In the basic TIAction,
the order of noun phrase resolution can vary by verb, and the order
of resolution isn't required to match the order of the nouns in the
phrase; in English, at least, a reflexive pronoun in this type of
construction is always anaphoric (i.e., it always refers to a phrase
earlier in the sentence). This means that the TIAction resolver could
find itself trying to resolve the reflexive phrase first, before it
knows the resolution of the phrase to which the reflexive refers.
To cope with this situation, the resolver notes when this occurs,
provides an empty list for the initial resolution of the reflexive,
and then goes back and re-resolves the reflexive after resolving
the other noun phrase.
The new Thing property isKnown lets you specify that an object is
known in advance to the actors in the game. The method isKnownBy(actor)
can be used to test actor knowledge of a Thing: isKnownBy(actor) returns
true if seenBy(actor) returns true, or the isKnown property is set to
true for the object. The Actor methods knowsTopic() and isLikelyTopic()
now use isKnownBy() rather than seenBy() to determine actor knowledge.
A few new conversation-related verbs have been added: Goodbye,
Yes, and No. These are handled similarly to Hello.
A new Action method, isConversational(issuingActor), determines if an
action is "conversational." A conversational action is one that
involves the issuing actor saying something, within the game context,
to the target actor, as opposed to an order to the target actor to do
something. Of the system-defined verbs, Hello, Goodbye, Yes, No, and
TellAbout (with the issuing actor as the direct object) are defined
as conversational.
The interface of the Actor.obeyCommand() has changed, as has its meaning.
The method now takes two parameters: the actor who issued the command, and
the Action object. In the past, this method was called with only the
issuing actor as a parameter, because the action was unresolved when the
method was called. Now, the parser calls this method
after
resolving the action and its objects. This change gives the method
full access to the details of the action, so it can decide to accept
or reject commands based on the actual action being performed.
The parser does not call this method when the action is
"conversational," as indicated by the isConversational() method no the
action. Conversational methods are not considered to involve an order
to the target actor. The actual physical action of a conversational
action simply consists of the issuing actor saying something to the
target actor, so even though these actions are phrased as though
they're orders to the target actor, they're really carried out by the
issuing actor, and thus don't require acceptance by the target actor.
The default implementation of this method on Actor calls the
corresponding method on the atcor's current state object. The
basic state object implementation simply refuses the command, so the
default behavior is the same as it was in the past.
The TextList subclass formerly known as SlaveTextList has been
renamed to SyncTextList, to better reflect that this list is always
kept synchronized with its associated master list. The old name
suggested that the connection was one-way, that state changes flowed
only from the master to the slave, when in fact the master and slave
are fully synchronized. The new name is more suggestive of this
two-way connection.
A new TextList subclass, StopTextList, provides a minor variation on
the standard text list script. Once StopTextList reaches its last
message, it will simply stay at the last message forever, repeating
the last message each time the script is invoked. This is useful for
cases such as a conversation topic where an actor has several things
to say, but once the actor has said each bit, the actor will from
that point on just repeat the last message; the last message would
usually be something like "I've already told you all I know about
it," or could be a summary of what the actor revealed.
ShuffledTextList features a few enhancements.
First, a new property, firstStrings, can be set to a list of
strings to show sequentially before starting the shuffled strings.
This can be useful in cases where you have some meaningful
information to convey initially, but once those messages have been
displayed, you want to fall back on randomized messages for
atmosphere and variety. The firstStrings list is shown only once, in
sequential order. Once the firstStrings list is exhausted, these
strings are never shown again.
Second, the main shuffled list in textStrings can now be shown
sequentially the first time through, if desired. Set the property
shuffleFirst to nil (it's true by default) if you don't want the list
shuffled the first time through. Since the strings in the textStrings
list are intended to be shown in random order, in most cases it won't
matter to the author what order is used, and in this sense the order
in which the strings are actually defined is as random as any other
order. In some cases, it might actually be desirable to have the
strings come out in a certain order the first time through; this lets
you refer back to an earlier message in a later message, for example,
with assurance that the player will have always seen the earlier
message first. After the first time through the list, the list is
always shuffled and shown again in random order.
Third, the class now takes care to ensure that a message is never
repeated consecutively. Repeats are normally avoided naturally by
the shuffling: every item is shown once before anything is repeated.
But a consecutive repeat was still possible in one special case,
which is immediately after a shuffle. Because the order of one
shuffle is independent of the order of the next shuffle, it was
possible for the last element of the previous shuffle to be the same
as the first element of the next shuffle. The class now suppresses
this case by checking each new shuffle to make sure its first element
doesn't match the last element of the previous shuffle, and choosing
again when necessary. This change is intended to increase the
apparent randomness by ensuring that the same string is never shown
twice in a row.
The special description list order has been modified slightly. If two
objects have the same specialDescOrder value, but one of the two
objects is inside the other, we'll list the outer object first. So,
specialDescOrder still dominates, but when there's no specialDescOrder
preference, we'll list containers before their children. In almost
all cases, this produces a more pleasing list order; objects within
other objects will frequently mention their placement in their special
description text, so it's usually better to have seen the containing
item's own special description before we see the containing item
mentioned in a child item's special description.
A set of new commands makes it easier to record and play back command
scripts, which can be especially useful while writing and testing a
game. The command RECORD (RecordAction) starts recording a command
script, which saves command input text to a file. RECORD OFF
(RecordOffAction) turns off the recording starting with RECORD (it has
no effect if no recording is in progress). REPLAY (ReplayAction)
plays back a command script previously recorded. RECORD and REPLAY
both accept a filename in quotes on the command line, but this is
optional; if you just type RECORD or REPLAY, the commands will ask you
to select a file using a standard file dialog.
REPLAY has two mutually exclusive options. REPLAY QUIET plays
back the script without showing any output while the script is running.
REPLAY NONSTOP plays back the script without pausing for MORE prompts.
Because REPLAY is fully redundant with the old "@" syntax, but much
friendlier, the "@" syntax has been removed.
The new class LocateInParent makes it easy to define a nested object
that's to be located within the enclosing object. This is a mix-in
superclass, so simply add it to the object's superclass list;
LocateInParent should go ahead of Thing or any Thing subclass in the
superclass list.
Released April 12, 2003
The default object picker has been improved to better handle cases
where commands are remapped. Remapping from one object to another is
often used as a convenience to the player, so that the same command
can be applied to any of several related objects with the same
effect. For example, you might want to set up a jar with a lid so
that OPEN JAR and OPEN LID have the same effect; this could be done
by remapping OPEN LID to OPEN JAR. Similarly, a house with a door
might remap ENTER HOUSE so that it's handled as ENTER DOOR. In the
past, cases like these prevented the parser from choosing a default
object, because the parser can only apply a default when there's only a
single object that could make sense - the remappings made several
different objects look equally good superficially, even though the
apparent different possibilities were all going to turn into the same
thing in the end thanks to the remapping.
The parser now keeps track, during the verification process, of
any remapped objects. The default picker looks at this remapping
information before deciding on a default. If there are any objects
among the possible defaults that are to be remapped, the default
picker will discard any that are redundant due to the remappings.
For the OPEN JAR/OPEN LID example, the parser would see that OPEN LID
turns into OPEN JAR, and it would thus discard the lid from the list
of possible defaults, since the jar is already in the list. This
would leave us with just one possibility, so the parser would be able
to apply use it as the default.
The parser will only eliminate a remapped object as redundant
when the remapping matches the object, action, and role of another
object in the list of possible defaults. If a remapping changes the
verb, the remapped object will only match another object if it's also
remapped to that same new verb. The verb has to match because the
command could otherwise have a different effect, and thus the two
actions on the same object wouldn't be redundant with one another.
The asExit() macro has been changed slightly. You should no longer
uses the ":" syntax to define the direction; instead, simply put
the asExit macro directly after the direction name:
north asExit(down)
This change is intended to make asExit more consistent with
the similarly-named asDobjFor() and related macros.
The menu system has a few minor changes to make the user interface
more easily customizable.
- In HTML interpreters, the top title/instructions bar now shows
a hyperlink that returns to the parent menu. The text in this
hyperlink is controlled by the 'prevMenuLink' property, which
by default uses the text 'Previous'.
- In HTML interpreters, a hyperlink is shown in topic lists to
advance to the next topic item. The text of the hyperlink is given
by the 'nextMenuTopicLink' property.
- The 'fgcolor' and 'bgcolor' properties now look to the parent
menu, if there is a parent menu, for their default values. For
the top-level menu, the defaults are still the status line colors.
In most cases, you'll want all of the menus in an entire menu tree
to have the same appearance, and this change makes it easy to
accomplish this: simply specify the appearance in the top-level
menu, and all of the child menus will use the parent menu settings.
Of course, individual menus can sever this parental dependence simply
by overriding these properties.
- Two new properties, 'topbarfg' and 'topbarbg', allow each menu to
specify the foreground (text) and background colors of the top
title and instructions bar. By default, these look to the parent
menu, if there is one; the top-level menu defaults to using the
inverse of the color scheme of the menu itself.
- The 'indent' property uses the parent's value by default.
- A new property, 'fullScreenMode', lets you indicate that you want
the menu to take over the entire interpreter window. By default,
menus are given just enough space at the top of the interpreter
window to display their contents. If 'fullScreenMode' is true,
though, menus will cover the entire interpreter window. Full-screen
mode has the advantage that it's less jumpy than the default
partial-screen mode, since the partial mode resizes the menu windows
on each navigation operation to accommodate the new contents.
A new hint menu type has been added: HintLongTopicItem. This is
simply a MenuLongTopicItem subclass designed for use in hint menus.
(Regular HintLongTopicItem objects don't have the necessary logic for
calculating visibility in a hint menu, so the specialized subclass
should be used for long-topic menus within hint menu trees rather
than the base MenuLongTopicItem type.)
The library's turn-counter incorrectly counted a command as two turns
if the command's action() handler invoked a nested action which was
then remapped (via remapTo). Nested actions aren't supposed to count
as separate turns; it was erroneous for the remapping to affect this
one way or the other. This has been corrected.
moveInto(nil) now works properly for a MultiLoc object. (In the past,
this incorrectly caused a run-time error.)
SensoryEmanation has a new property, isAmbient. This is nil by default;
if set to true, it indicates that the noise/odor/etc is purely in
the background, so it's not especially noticeable. Ambient emanations
won't be mentioned when they first become sensed; normally, an emanation
is automatically mentioned whenever conditions change so that it was
not sensed previously but is now. Ambient emanations will still be
mentioned in explicit intransitive LISTEN/SMELL commands; they simply
won't be mentioned on their own.
Part of the Container class has been separated into a new lower-level
base class, Encloser. An Encloser is an object that can enclose its
contents, so that all senses must pass through the encloser's
material when passing in and out of the object. Encloser has all
of the basic handling for enclosing contents, but not the action
handling. Encloser is meant for cases where the object's contents
are not to be directly manipulable by the player, via "put in"
commands and the like. Container is now a subclass of Encloser;
Container defines the suitable action handlers to allow a player
to manipulate the container's contents.
A new mechanism in the Actor class makes it relatively easy to set it
up so one or more non-player characters accompany the player character
on travel. This kind of group travel is especially good for things
like a sidekick character who goes everywhere the player does, and for
tour guides or other escorts who are showing the player character
where to go.
This mechanism is similar to the "follow" mechanism, which makes
one actor attempt to follow another on each turn, but it improves
considerably on regular following by customizing the messages. Normal
following is a little awkward for explicit group travel of the sort
that one wants with sidekicks and escorts, because the messages are so
generic; the NPC almost seems to be wandering around on its own and
just coincidentally showing up where the PC is. This new
"accompanying" mechanism accomplishes much the same thing, but smooths
out the messages a bit. First, rather than having the NPC trailing
along after the fact, the new system sends accompanying NPC's on
ahead; this means that the NPC's don't just wander in later as they do
with normal following. Second, the message for the NPC's
pre-departure is customized to make it clear that the NPC isn't
departing, but is coming along with you. Third, on arriving in the
new location, there's a customization hook for describing the NPC's
presence in the new location specially for the group travel; this is
important because it gives us a place to mention that the actor starts
doing whatever it is the actor normally does in the new location. The
overall effect is that each group travel action is made to look like a
single, integrated action, rather than two generic and unrelated actor
travel actions.
NPC's can also be set to accompany NPC's with the same mechanism,
but it's much less interesting for that, since the main value of the
new mechanism is that it improves the messages for PC-plus-NPC group
travel. For NPC-plus-NPC group travel, this new mechanism has no
particular advantage over the the simpler "follow" mechanism.
Setting up accompanying travel is relatively straightforward.
You have to specify the conditions under which the group travel occurs,
and you should customize two messages related to the travel. You
set this all up by overriding methods on the NPC who's going to follow
the lead actor (usually the PC). The methods to override are as
follows.
First, override the accompanyTravel() method so that it returns
true under the conditions where you want the accompanying travel to
occur. This method is called each time your NPC is present and sees
another actor attempt a travel action (it's called during your NPC's
beforeAction when the action is a TravelVia). If you want your actor
to accompany the PC everywhere, simply return true when the action
actor is the PC. The method also has access to the TravelConnector
involved in the travel, so you selectively accompany an actor for
some destinations but not others, if you wish.
Warning: the next two parts are likely to be modified soon.
I'd recommend against writing any code that makes customizations using
these features right now.
Second, define an accompanyDesc method for your actor. This method
is used instead of the usual actorHereDesc method to describe your
actor in the new location immediately after the group travel is
finished. It's usually desirable to use a special message to describe
the actor right after the accompanying travel, because you usually
want to convey that the actor is just arriving - not that the actor is
walking in separately from the lead actor, but that the actor is
arriving at the same time as the lead actor. If you do override
accompanyingDesc, you should override accompanyingListWith to return
an empty list ([]).
Third, you can optionally provide a special TravelMessageHandler
for your actor by overriding getAccompanyingTravelMessageHandler. By
default, this routine provides a message handler that uses messages
like "Bob goes with you" instead of the usual "Bob leaves to the east"
to describe the departure of your actor. The normal departure message
isn't appropriate, because the actor isn't just leaving, it's leaving
*with* the lead actor. The default "Bob goes with you" is better, but
you might still want to override this handler to provide even more
customized messages: "Bob escorts you to the east," or "You drag Bob
with you," or whatever makes sense for the specific situation.
Finally, note that you can use accompanying travel and the regular
following mechanism together. Regular following can be a useful
fallback for cases you don't want to customize. Regular following
occurs after the fact, because it occurs on an NPC's turn when the
NPC sees that the actor it's tasked to follow is no longer present.
Accompanying travel, in contrast, happens on the same turn as the
lead actor's travel. This means that regular following is essentially
overridden by accompanying travel, since it happens first.
The English parser now accepts quoted strings as adjectives. Quoted
strings can be used as adjectives in exactly the same way that numbers
can be used, so phrases such as "QU" TILE and BUTTON "G" are accepted.
For the purposes of matching vocabulary in the dictionary, quoted
adjectives are simply treated as though the quotes weren't present.
So, "QU" TILE is treated exactly the same as QU TILE. This means you
don't have to worry about the quotes when defining your vocabulary
words.
There is an additional bit of special treatment for quoted strings.
The special vocabulary word '"*"' (that is, an asterisk within
double-quote characters) serves as a wildcard for any quoted string.
If an object defines this special wildcard as an adjective among its
vocabulary words, then that object will match any quoted string
as an adjective. This is the equivalent of the '#' wildcard string
for numeric adjectives.
String-as-adjective phrasing is implemented using the new
production literalAdjPhrase. This new production is now used
wherever numberPhrase was formerly used in the role of an adjective
in noun phrase rules. literalAdjPhrase matches numbers, '#' number
phrases, and quoted strings.
In travel.t, there were formerly a couple of TravelConnector
subclasses that handled action synonyms for TravelVia using
asDobjFor(TravelVia). These have been changed to use
remapTo(TravelVia) instead.
The asDobjFor() approach had the drawback that the action
synonyms didn't ever generate TravelVia actions proper, but
simply called the TravelVia handlers internally; so, for example,
beforeAction() routines wouldn't ever see a TravelVia action
being performed. The new remapping approach is better because
it means that every possible action on these objects that
involves travel will go through an actual TravelVia action.
This allows beforeAction() and similar routines to be assured
that they can catch all travel actions by looking for TravelVia
alone, eliminating the need to worry about synonym actions that
could cause travel.
A couple of new classes simplify a few tasks involving entry
and exit portals. Exitable is a new class that's similar to
Enterable, but provides Exit instead of Enter as its main
action. EntryPortal and ExitPortal are just like Enterable and
Exitable, respectively, but add support for GoThrough actions.
Added finishOptionFullScore, to make it easy for the game to
offer the player the option of seeing the full score at completion.
Released March 23, 2003
It's now less dire for gPlayer.location to be set to nil. In the
past, setting gPlayer.location to nil caused an infinite error loop,
because the status line code dereferenced gPlayer.location without
checking for nil, which caused an error, which aborted the current
turn, which started a new turn, which tried to update the status
line, and around we went. It's still not completely valid for
gPlayer.location to be nil; this change just removes the infinite
error loop, which makes it a little easier to deal with this problem
arising during programming and testing.
Changed Thing.dobjFor(Remove) to remap to RemoveFrom (asking for an
iobj). Wearable now overrides Remove to map it to Doff. It makes
more sense in general for "remove foo" to mean "remove foo (from
something)" rather than "doff foo"; only when "foo" is an article of
clothing does the "doff" interpretation usually apply.
Added the new TravelConnector property isConnectorListed. If this
property is nil, the connector is suppressed from generated exit
lists (such as the status line exit list, and "you can't go that way"
messages).
Added a new class, UnlistedProxyConnector, that acts as a proxy for
an underlying connector, but is unlisted. The idea is to make it
easy to add an exit that acts exactly like another connector, but is
suppressed from exit lists.
Added a macro, asExit(), that can be used to define an
UnlistedProxyConnector that links to another direction exit.
Integrated Stephen Granade's menu system (menus.t) with the main
library.
Added an on-line adaptive hint system (hints.t).
Added the new class RestrictedContainer. This is a Container
specialization that makes it easy to define containers that only
accept certain objects as contents. In the basic implementation, the
allowable objects are simply enumerated as a list of objects; but
this can be easily overridden for other methods of determining which
objects are allowed.
Added the new class SingleContainer, and the associated new
precondition objEmpty. SingleContainer is a specialization of
Container that only allows a single object to be in the container at
a time; this is suitable for things like sockets and receptacles.
SingleContainer places the objEmpty precondition on the PutIn action;
the objEmpty precondition simply requires that the subject object is
empty, and tries implicit TakeFrom actions on the contents to
accomplish this.
In en_us.t, changed typographicalOutputFilter.eosPattern to ignore
any run of HTML tags between sentence-ending punctuation and a
following space.
The main implicit action processor now uses the more abstract
"allowed in implicit" test on the verify results, rather than merely
checking for "dangerous" results. This will allow results flagged as
"non-obvious" to cancel implied actions, as they should. (A
non-obvious command should never be done implicitly, because implicit
commands are specifically for actions that are obvious intermediate
steps toward the explicitly stated command.)
Openable and Lockable have been cleaned up slightly. A new class,
Linkable, has been added for objects that can be paired in
master/slave relationships; Openable and Lockable are now based on
Linkable. This removes some duplicated code for managing the
masterObject relationship. In initialization, Linkable checks for a
master object loop (where each object points to the other as the
master) and break such loops by arbitrarily selecting one as the
master. Also, we have reduced the number of times we have to refer
to masterObject by relying on isOpen/isLocked/makeOpen/makeLocked to
defer to the master. Also, the initiallyOpen/initiallyLocked
initializations are now handled in initializeThing() rather than in
isOpen/isLocked.
Another new class, BasicOpenable, provides the basic state
management for openable objects (which can optionally be linked
in pairs to maintain the same status across the pair) but doesn't
provide any of the verb handling of Openable. This can be used
for objects that want to maintain open/closed state using the
usual method names, but which don't respond to direct player
open/close commands.
IMPORTANT: Objects based on Openable and Lockable must
not initialize their open status with isOpen or their locked
status with isLocked. Instead, initialize the status with
initiallyOpen and initiallyLocked, respectively. Also, be sure
you never set isOpen or isLocked directly; instead, call makeOpen()
and makeLocked() to effect these status changes.
Changed Passage slightly to rely on isOpen/etc more to manage the
masterObject relationship.
Added a parameter to Direction.defaultConnector() giving the location
from which travel is being attempted. This allows the direction to
customize the default connector according to the type of location.
Added a new property, isShipboard, to Thing. By default, if we have
a location, we use the location's isShipboard setting, otherwise we
return nil. Rooms aboard ships can set this to true to indicate that
shipboard directions make sense here.
In ShipboardDirection.defaultConnector, if the source location or
any container has 'isShipboard' set to true, then we now use noTravel
as the default connector rather than noShipTravel, as the latter is
meant to convey that shipboard directions make no sense in
non-shipboard locations.
Added a ShipboardRoom mix-in class that defines isShipboard to true.
Changed the way that VerifyResultList determines if an action is
allowed at all or allowed as an implicit command. In the past, this
calculation did what the "most disapproving" result in the list said
to do; this wasn't quite right, because approval is essentially a
separate axis from the "disapproval" order, despite the name. In
reality, the "disapproval" order is the message priority order, and
the actual determination of approval depends on there being
no disapprovers. So, to determine approval, we now scan the full
result list and require that every result approves; a single
disapproval, no matter where it appears in message priority order,
constitutes disapproval.
In the English module, added a new token type for numbers specified
with a leading pound sign (as in "#2 pencil" or "room #101"). These
are treated essentially the same as numeric adjectives. These match
vocabulary for just the underlying numbers, not including the pound
signs, so vocabulary should be specified simply as '2 pencil' and
'101 room'. (In other words, do not include a pound sign in
vocabulary words; just use the number as with ordinary numeric
adjectives, and the parser will match the number with or without the
pound sign).
The TravelVia check() condition in Passage no longer uses isOpen as
its test. Instead, it calls the new abstract condition method,
canActorTravel(), passing the current actor as a parameter. This
makes it easy to allow some actors to pass and not others, and to
test for conditions other than isOpen. By default, canActorTravel()
simply returns isOpen, so this doesn't change the default behavior.
Along the same lines, Door.connectorTravelPreCond() now calls
a separate method, getDoorOpenPreCond(), to obtain the door-is-open
precondition object. By default, this returns a doorOpen precondition
(wrapped with an ObjectPreCondition for 'self'), so the default behavior
hasn't changed.
In the past, when an NPC was following another actor, and the actor
being followed moved into a nested room within the current room, we
generated a redundant message:
>stand on platform
Okay, you are now standing on the platform.
Bill stands on the platform.
Bill follows you onto the platform.
The redundant "Bill follows you" has been eliminated.
The reporting mechanism now processes sets of implicit action announcements
to make them more readable and more easily understood. In the past,
each implicit action was shown separately; when one implicit action
triggered another, this could make for somewhat confusing displays.
For example, in the Platform Room in sample.t, if the PC is sitting on
the blue chair and wants to get up and sit on the red chair, we formerly
generated a transcript like this:
>sit on red chair
(first standing on the red platform)
(first getting off of the blue platform)
(first standing)
Okay, you're now sitting on the red chair.
To the uninitiated, this looks backwards: shouldn't we stand up,
then get off the blue platform, then get on the red platform, and then
sit on the red chair? Of course, that's what's actually happening,
and in its own way the transcript above reflects this: the second
"first" applies to the first "first": it's saying "before you can
stand on the red platform, you first have to get off the blue
platform." Likewise, the third "first" applies to the second "first,"
to say "before you can stand on the red platform, you have to stand up
first." In other words, the implied reports are nested recursively.
The recursive structure isn't represented visually, though, so it's
not evident whether one of the later "firsts" refers to the preceding
"first" or back to the original command line. It would have been
possible to represent the recursive structure visually, using
indentation or something like that, but this probably wouldn't have
made most people very happy; most non-programmers aren't accustomed to
thinking in terms of recursion and stacks and tree views, and even
programmers might well have found this kind of presentation to be too
obviously mechanistic.
To improve the situation, the transcript processor now features a
new transform, called implicitGroupTransform, that rearranges these
recursive listings into an order that should be entirely
straightforward to anyone, programmer or not. The new transform also
consolidates these lists into a much more concise and readable format.
The transformer does two things. First, it "unstacks" recursive lists
of implied action announcements to put them into the chronological
order in which the commands were actually performed; the transcript
has always kept track internally of which announcement is tied to
which action, so the new transformer can merely inspect these records
to determine the action relationships and use this information to
unwind the stack. Second, the transformer combines each run of
adjacent implied action announcements into a single announcement,
showing a list of the actions performed. The result is that the
example above now looks like this:
>sit on red chair
(first standing, getting off of the blue platform, then standing on
the red platform)
Okay, you're now sitting on the red chair.
This new format should be easier for players to understand,
since it shows implied actions in the order in which they're actually
carried out, eliminating any need to be aware of the recursive
goal-seeking work that the system is doing internally. Hopefully,
players will also find it more readable and less obviously mechanistic
than the old format.
3.0.6e was released March 16, 2003
For a restore-on-startup operation, if the restore fails, don't simply
start the game from the beginning. Instead, offer options to START
from the beginning, RESTORE another game, or QUIT, using the same type
of prompt that we use for presenting "finishing" options.
Add a verb for OOPS typed at arbitrary times, which simply explains
that OOPS can only be used after the parser has pointed out an unknown
word.
Fix problem using possessive qualifiers in topic phrases. (The
problem is that we're using the topic resolver to resolve qualifiers
in the topic phrase; we should be using an ordinary object resolver.
Change the topic resolver's qualifier resolver to use the topic
action's direct object resolver by default.)
Move the pronoun-setting routines (setPronoun, setIt, setHim, setHer,
setThem) into Actor - these shouldn't be global functions since they
need to operate on the current actor.
In TopicAction, set the pronoun antecedent based on the direct object
phrase immediately during resolution, to allow things like "ask bob
about his book."
Add a DefineIAction macro, for defining IAction classes.
Base LiteralAction on IAction, not directly on Action.
Because of this change, rewrite OopsAction to implement its processing
in execAction() rather than doActionMain().
Refactor the Instructions action into an InstructionsAction base class
and a separate grammar rule, to allow the base class to be referenced
by name from other code.
Add a banner manager, along the lines of the input and output
managers. A new BannerWindow class would represent a banner; this
would encapsulate the system banner handle and provide methods that
operate on the banner. The game should use the BannerWindow methods
to perform all operations on banners, rather than calling the
system-level banner API directly.
The banner manager should provide persistence support, so that the
on-screen banner layout is restored on RESTORE, UNDO, or RESTART. To
this end, BannerWindow should be an ordinary persistent object, and a
separate set of transient objects should track the current UI state.
On RESTORE, UNDO, or RESTART, the transient tracking list and the
persistent BannerWindow states should be compared, and the actual
on-screen UI state, as represented in the transient objects, should be
brought into line with the saved state.
Wait to mark an object as 'seen' in Thing.lookAroundWithin() until
just before the method returns, so that any routines called from the
method can check the old 'seen' status.
Fix gAction reference in Actor.travelWithin() so that it's conditional
on gAction being non-nil.
Fix takeFromNotInActor message (iobj/dobj are reversed).
Fix TAction.retryWithMissingDobj() and
TIAction.retryWithMissingIobj() so that they cancel the game time
contribution of their enclosing (replaced) actions.
Fix touchObj precondition logic so that it realizes when an attempt
to remove an obstruction with a precondition fails. To do this, keep
track of which obstructions we've tried to remove with implicit
commands, and give up if we encounter the same obstruction more than
once.
Add <.parser> to msg_neu.t responses for some system verbs (NOTIFY
ON/OFF, EXITS ON/OFF, a few others).
Assign the IndirectObject role to the literal in a LiteralTAction by
overriding getRoleFromIndex, getObjectForRole, and getMatchForRole.
These are needed to allow remapTo to be used with a LiteralTAction.
Note that the literal is always in the IndirectObject role,
regardless of whichLiteral, which only specifies the grammatical role
for message generation purposes.
Likewise, assign the IndirectObject role to the topic in TopicAction.
This will allow remapTo to be used with a TopicAction.
Rename TopicAction to TopicTAction, parallel to LiteralTAction.
Add a new TopicAction that takes only a topic as its object, parallel
to LiteralAction.
Rename whichObject, whichLiteral, and whichTopic to
whichMessageObject, whichMessageLiteral, and whichMessageTopic,
respectively, to make it clearer that these apply only to the roles
played in generated messages, and not to any other roles.
In particular, the message role only affects the way the object is
used in generating messages based on the verb, such as "what do you
want to open it with?".
It would be better to interpret "type on typewriter" as having a
missing literal than as having a missing ON phrase (in other words,
we want to interpreter this as TYPE <empty literal> ON TYPEWRITER
rather than as TYPE "ON TYPEWRITER" <on missing object>).
To do this, add a verb rule for TYPE ON <object>, without any literal
phrase. For most objects, fail in verification; but when
verification passes, in the action, ask for a missing literal phrase.
(This has the additional benefit that it makes it easy to create
objects that allow generic typing on them, as in TYPE ON COMPUTER,
for situations where the game doesn't want to make the player type
anything specific but still wants to allow the generic act of typing
on the object.)
>ASK BOB ABOUT
what do you want to ask him about?
>BILL
"Ah, yes, , very interesting..."
(The problem is that we're not propagating getOrigText() from the
empty topic phrase match down to the underlying replacement match.)
>ASK BOB
what do you want to ask him about?
>ABOUT BILL
"Ah, yes, about bill, very interesting..."
(The problem is that we need an aboutTopicPhrase production to parse
responses to ABOUT WHAT questions.)
Add a generic Settable class for things (such as dials) that can be
set to various settings. Base Dial on Settable.
Do not override lookAroundWithinDesc() in BasicLocation; instead,
provide an implementation of BasicLocation.roomDesc that simply uses
the 'desc' property to display the room's interior description.
(This allows nested rooms to differentiate more easily between
interior and exterior descriptions, if they want to.)
Add a new method to Thing, filterResolveList(), to allow "global"
filtering of a noun phrase resolution list. Call this method from
the parser where we use 'verify' to filter a resolution list. Unlike
'verify', this new method has access to the entire resolution list;
this allows the method to take action based on which other objects
are in the resolve list.
Add a new mix-in class, Collective, to serve the purpose of the
former isCollectiveFor(obj) method in Thing. To create an object
like the matchbook in the sample game, use Collective as an
additional base class.
Remove the special-cased plural filtering in AllPluralProd and
DefinitePluralProd. Move this logic instead into Collective's
filterResolveList() implementation.
Add a new class, CollectiveGroup, to allow creating "abstract"
collective objects. These differ from regular Collective objects in
that a Collective is actually a simulation object (such as a
matchbook that can hold several matches), whereas a CollectiveGroup
is not a separate object from the player's perspective (so it's never
listed as a separate object in a room's contents listing, for
example).
Add a new Thing property, collectiveGroup, that allows an ordinary
object to be associated with a CollectiveGroup object. In
CollectiveGroup's filterResolveList() method, choose to keep either
the individuals (the ordinary Thing objects associated via their
collectiveGroup properties with a CollectiveGroup object) or the
CollectiveGroup in the resolution list, but not both. By default,
make the selection based on action; subclasses can override as
desired to use other criteria.
CollectiveGroup can be used to create things like a "money" object
to represent a set of coins and bills, so that a command like "look
at money" can respond with a single description of all of the money
present, rather than iterating over all of the coins and bills
individually.
Eliminate the actorHereLister. Instead, handle actor descriptions
using the special description mechanism. By default, give actors
a higher specialDescOrder, to keep actor special descriptions listed
after other special descriptions.
Add a new special description control property to Thing,
specialDescBeforeContents; set it to true by default in Thing, but
override it to nil in Actor. In verbose room descriptions, show
special descriptions in two phases: where we currently show special
descriptions, just before the room's list of portable contents, show
special descriptions only for items with specialDescBeforeContents
set to true. Then, after we've shown the portable contents list
and any other sensory messages (listen/smell), show the special
descriptions for objects with specialDescBeforeContents set to nil.
This will allow the traditional ordering, with actor "I am here"
messages placed after the rest of the room description, while keeping
the rest of the special descriptions grouped with the room's main
description. Most special descriptions belong with the main room
description because they're meant to be extensions of the room
description. Some special descriptions are not; they're meant to
describe more ephemeral status information, so work better when
grouped with the other status-like messages. Actors in particular
fall into the latter category, since actors are meant to seem
autonomous, not parts of the rooms they occupy.
When showing special descriptions of the contents of an object as part
of the object's description, use a new form of the special description
method, showSpecialDescInContentsWithInfo(), and corresponding
specific methods for viewing conditions (showSpecialDescInContents,
showObscuredSpecialDescInContents, showDistantSpecialDescInContents).
Override showSpecialDescInContents in Actor, so that we can show
the type of description we previously showed using nestedActorLister.
Remove nestedActorLister and NestedRoom.examineNestedRoomActors().
Add a default output filter, typographicalOutputFilter, to the main
output stream. In this filter, convert '--' and '---' sequences to
typographical dashes, and insert an en space at the end of each
sentence.
The conversion of sentence-ending punctuation compensates for the
elimination of the double-space insertion in the VM-level formatter.
It also improves matters over the old way by making the treatment of
sentence-ending punctuation customizable: the game can replace the
filter method with its own custom conversions. This allows
customization not only for stylistic variation but for
language-specific conventions as well.
Add the special {subj} messageBuilder token. This token simply notes
its target object as the subject of the sentence, for internal
book-keeping purposes, but doesn't actually display anything.
Add a "log file console" type. This would act like the main console
or a banner console, in that it would apply the full set of VM-level
output formatting (including text-only HTML interpretation) to the
text sent through the stream, but it would write the output only to a
file, rather than displaying it anywhere. This would be useful for
capturing output to a file without showing the output at all on the
display; for example, this would make it easy to generate an About.txt
file by capturing the output of the ABOUT command to a file during
preinit, without having the ABOUT output also show up on the screen.
This would require a new set of VM-level functions in the tads-io set:
- logConsoleCreate(filename, charmap, width):
returns a handle to the new console
- logConsoleSay(handle, ...): writes the arguments to the
log console; works like say() and bannerSay()
- logConsoleClose(handle): closes the console
In addition, add a LogConsole library class to simplify operations
with the log console. This class can be quite simple; it's just an
OutputStream subclass that implements the writeFromStream method to
call logConsoleSay().
Add a TopicQualifierResolver subclass of Resolver, specifically for
resolving qualifier phrases in topics. Use a new instance of this
class instead of the direct object resolver in TopicActionBase. (The
direct object resolver isn't really appropriate, and doesn't work at
all with plain TopicActions, because they don't have direct object
resolvers at all.)
Add a new mapping macro, iobjAsDobjFor(), that makes it safe to map
an indirect object handler to a direct object handler in the same
object. Use this for the mapPushTravelHandlers() macro.
(The mapPushTravelHandlers() macro essentially wants to decompose the
two-object action into two single-object actions, which isn't safe
with the regular asDobjFor() mapping. The difference with
iobjAsDobjFor() is that this new routine temporarily makes the
indirect object take on the direct object role, so that the target
dobj handler sees the proper object in the direct object slot.)
Add a LabeledDial subclass of Dial. This class accepts arbitrary text
labels as dial stops; the property validSettings contains a list of
strings giving the valid dial stop labels.
Remove any .t and .tl extensions from adv3.tl and en_us.tl. (The
extensions are implied by the file types; it makes the library file
more portable to omit the extensions, since the compiler will
automatically apply the default extensions using the appropriate local
conventions when the extensions aren't explicitly included in the
names as they appear in the library files.)
Get rid of Thing.articleIndef; games should simply override aName
instead when they want to override the default indefinite article
determination.
Change the way theDisambigName, aDisambigName, and countDisambigName
work. If name == disambigName, then return the corresponding xxxName
from xxxDisambigName; otherwise, apply the same algorithm to
disambigName that the corresponding xxxName applies to name. For
example, in aDisambigName, if name == disambigName, simply return
aName; otherwise, apply the indefinite article algorithm to
disambigName and return the result.
This change will allow disambigName to be overridden without
requiring all of the xxxDisambigName's to be overridden at the same
time, because the xxxDisambigName's will by default apply the
standard algorithms to the modified disambigName. At the same time,
though, this has the virtue of the original implementation that, in
the common case where disambigName is not overridden, any overrides
to theName, aName, and countName will be used for the corresponding
xxxDisambigName's. Furthermore, since these are all still separate
methods, objects can still separately override each xxxDisambigName
as needed for cases where the disambigName is customized and the
customized name requires overriding the normal article algorithms.
While we're at it, add pluralDisambigName, using the same logic.
Remove the dictionary properties possessiveAdj, possessiveNoun, and
their corresponding grammar. (These are no longer needed, since we
now have explicit grammar for all of the possessive pronouns in
adjective and noun usages. At one time, these were used for adding
vocabulary for the player character, but it worked better to use the
grammar and resolver mechanism more explicitly.)
Rework "'s" handling for literal vocabulary:
First, remove the special-case tokenizer handling for "'s" words that
appear in the dictionary, so that we handle all "'s" words
uniformly in the tokenizer: all "'s" suffixes are treated as separate
tokens.
Second, never add "'s" words to the dictionary in initializeVocab().
Instead, define a new dictionary property, adjApostS; when we see a
"'s" word in a vocabulary list, remove the "'s" suffix and add only
the prefix to the dictionary, but add it as an adjApostS instead of
as an adjective. (For simplicity, don't bother with nounApostS at
all; allow only adjectives as apostrophe-s words.)
Third, add a new grammar production, adjWord, that accepts either a
single adjective or an adjApostS followed by an apostrophe-s token.
Fourth, where appropriate, change 'adjective->' grammar rules to use
'adjWord->' instead.
These changes correct the problem that adding a literal "'s" word to
an object's vocabulary prevented that word from being used as a true
possessive phrase in the grammar. The old tokenizer rule was that a
word that appeared with an explicit "'s" suffix in the dictionary was
kept intact, rather than split into separate tokens for the root word
and the "'s" suffix. Since the word was kept intact as a single
token, it couldn't match the grammar rules for possessive phrases,
which only match the separated form. These changes allow the same
word to be used in either context, since everything is treated the
same way in the tokenizer; even if a word is used as a literal
vocabulary word with a "'s", it'll still be split up during
tokenization. Even with this change, we can still match explicit
"'s" vocabulary words, thanks to the adjWord grammar.
Note this has one small potential impact on existing games: if an
object explicitly defines its own 'adjective' property (rather than
using the automatic library initialization), and a word defined in the
adjective list ends in apostrophe-s, then that word must be stripped
of the apostrophe-s suffix, removed from the 'adjective' list, and
added to a new 'adjApostS' list instead. Similarly, if any vocabulary
words that end in apostrophe-s are dynamically added with
G_dict.addWord(), the apostrophe-s words should be stripped of the
suffix and added under the '&adjApostS' property instead of the
'&adjective' property.
Add noteLiteral(lst_.getOrigText()) to getVocabMatchList() in
simpleNounPhrase(misc)? This would make the length of text in a
miscellaneous word list a match selection criterion (less important
than the presence of a misc word list). This won't change anything in
cases where we want to choose between a match with a misc word list
and one without, as the presence or absence of a misc word list is
more important than literal length, but it will help distinguish
between two matches that both have misc word lists. Since we prefer a
shorter literal match, this will have the effect of preferring to
match the more structured interpretation, since a shorter misc word
match means a longer grammar match.
Fix run-time error with "a
" (the 'inherited' argument list
is wrong in AskAboutAction.getDefaultDobj; the same problem is in
TellAboutAction).
Rename class Fixed to Fixture.
Add new class Immovable: this is for objects that aren't fixed in
place obviously or by their very nature (as are Fixtures), but rather
are fixed for some other reason: great weight, hidden fasteners, etc.
This class differs from Fixture in that it disallows actions like
take, move, push, and pull not in the verify() but in the action().
Add a subclass, Heavy, for objects that are immovable because they're
very heavy. This is suitable for things like large furniture and big
boulders.
Remove moveInto() override on Immovable. (This attempted to be a last
resort to disallow actions that involved trying to move the object,
but it prevented programmatic relocation as well, creating more
problems than it solved.)
Fix problem with applying AGAIN to a command issued to another actor:
the target actor becomes "busy" and won't accept another command until
a turn passes. The problem is that we're not accounting for
synchronous actors in the AGAIN processing: we need to tell the issuer
to waitForIssuedCommand on the target actor.
Fix problem in GiveTo and ShowTo actions: getDefaultIobj calls
inherited() with wrong argument list.
Fix the parameter list for npcActionMessages.okayTurnTo.
Make RandomTextList customizable with the percentage of the time a
message is generated at all. Random message lists are often used for
atmospheric messages, and it's usually better if these messages don't
appear on every single turn. Add a 'messagePercent' property that
determines the percentage of turns where a message is generated; the
default is 100, which leaves the default behavior as it was (i.e., a
message is generated on every turn).
In addition, especially with random atmospheric messages, it can get
tedious to see the same messages over and over if you spend a lot of
time in the same area. It's therefore often the case that we want
frequent atmospheric messages when the player first gets to a
location, but then we want the frequency to drop dramatically after
the player has spent more than a few turns there. To make this easy
to handle, add a couple of new properties: 'messageReduceAfter' is the
number of times that we want to see messages at the initial frequency,
and 'messageReduceTo' is a new value for 'messagePercent' that we'll
apply after we've generated the messages 'messageReduceAfter' times.
Make these nil by default, which means that there is never a reduction
in the frequency. Authors can use these properties to generate random
atmosphere messages at high frequency at first, but then drop the
frequency to just an occasional message after the player has been in
the location long enough to get the idea.
Add a new class, NominalPlatform. This is a "secret" object: it's not
visible to the player as a separate simulation object, and won't
normally have any vocabulary or appear in any room listings. The
purpose of this object is to make it easier to describe NPC's as
standing somewhere specific within a room; the author locates an NPC
within the NominalPlatform, and the library will automatically
describe the NPC as standing on the platform: "Bob is here, standing
in the corner." The author can also customize the display methods for
the nominal platform so that the description is something like
"leaning against the lamppost."
Add a new property to Thing, specialContentsLister, that lets
individual objects and rooms customize the lister they use to generate
special descriptions. Set the property by default to
specialDescLister.
Add a new topicPhrase grammar rule that matches a miscWordList without
badness. (The normal topicPhrase grammar matches a singleNoun, which
matches a miscWordList, but it does so with badness; we want to add an
explicit rule that matches a miscWordList without badness.) It's in
the nature of topic phrases to go outside of what's implemented in the
simulation model; we don't want to treat resolvable and irresolvable
topic phrases any differently at the grammatical level, because doing
so can lead to inconsistencies when parsing a verb containing both a
topic and another kind of noun phrase.
Add gLiteral to adv3.h, analogous to gTopic.
Change Thing.dobjFor(Attack) so that the default Attack handling is
simply to display the message "it's useless to attack that." Remove
the askIobjFor(AttackWith). Specifying a weapon shouldn't be
required; especially for attacks like "hit" and "kick," asking for an
indirect object will seem wrong to a player. This makes a tiny bit of
extra work for authors when AttackWith is overridden, since Attack
will have to be overridden as well in such cases, but this is probably
worthwhile anyway because it'll encourage authors to make the same
distinction between weapon and weaponless attacks that players are
likely to make in their own minds.
Move the code from Contents.examineContainerContents into
Thing.examineStatus (actually, into a new method called from
Thing.examineStatus, call it examineListContents). Get rid of
Surface.examineSurfaceContents. Ordinary Thing objects should
traverse into their contents during Examine; this shouldn't be limited
to Container and Surface objects. We need to do this listing in
Thing, because a Thing could contain a component that's a container,
and we want to list its contents when examining the Thing.
Fix bug in Action.getObjPreconditions: if pre is nil before appending
the catch-all list, we'll get an error. Check that pre is nil before
appending, and just use the catch-all list by itself if so.
Change the TravelConnector.describeDeparture() and describeArrival()
interfaces to add a new parameter for the traveler. Use the traveler
parameter instead of the global gActor. This makes the travel
routines more flexible, and in particular allows them to be called
from outside of commands (in daemons, for example). It also will
improve the handling for non-actor travelers (vehicles in particular).
In RoomConnector.describeDeparture/Arrival, use gPlayerChar rather
than gActor as the point-of-view object in directionForConnector().
The description is being generated for the benefit of the player, so
it should be from the PC's perspective.
Split off a new method from each of Traveler.describeDeparture and
describeArrival: describeNpcDeparture/Arrival, which is called when
an NPC (or a traveler not involving the PC) is doing the travel.
This will simplify overriding the messages for vehicles and other
special travelers when desired, since the overriding method won't
have to make all of the checks to see if it's the PC doing the travel.
Run all of the libMessages.sayDeparting/Arriving connector-specific
messages through an intermediate method call in the Traveler; these
new Traveler methods by default are simply be covers for calls to the
libMessages methods. The purpose of the extra layer of calls through
Traveler is to make it easier for individual Traveler subclasses to
customize the messages on a per-connector-subclass basis.
'@script.txt' at the command prompt should start reading from the
named script file. (This should work essentially the same way it did
in tads 2, except that we want to handle this in the library instead
of in the interpreter.) Also, '@@script.txt' should run the script in
"quiet mode," which is to say that input and output generated while
reading the script are to be suppressed.
Add some new classes to misc.t for "shuffled" random selections. A
shuffled selection is a random selection taken from a set of values
that doesn't repeat a selection until we've gone through all of the
values once, like dealing from a shuffled deck of cards.
ShuffledList: the basic shuffled selection class. Keeps a list of
values, and returns a randomly selected element on demand.
ShuffledIntegerList: a specialization of ShuffledList that returns
a randomly selected integer from a given range.
ShuffledTextList: a subclass of RandomTextList that makes its
selection using shuffling rather than independent random selection.
Add a point-of-view parameter to Thing.showSpecialDescWithInfo().
Add a parameter to the following library messages to indicate which
object is being described (the object is needed for things like gender
and number agreement in some languages):
openMsg, closedMsg, lockedMsg, unlockedMsg, onMsg, offMsg
Add a parameter to the following library messages to indicate which
actor is being described (the actor is needed for things like gender
and number agreement in some languages):
statusStanding, statusSitting, statusLying,
statusStandingOn, statusSittingOn, statusLyingOn
Add a parameter to the various name methods in ThingState (listName,
inventoryName, wornName) providing a list of the objects being shown
in this state. This is needed for some languages so that the state
description can agree (in gender and number, for example) with the
objects being desribed as being in the state.
Change the count parameter to the full list in
actorStandingGroupPrefix and the related methods in libMessages.
This will allow the prefix/suffix messages to check any attributes of
the individual objects in the list, such as gender mix, that might
affect the message.
The INSTRUCTIONS command should not take any turns (make it a
"system" command). Also, it should not consume any game time on the
real-time clock (which, given the size of the instructions, could be
noticeable).
Add an extended form of inputManager.getInputLine(), which takes a
new InputDef object to define the input parameters. Use this new
parameter definitions object to determine what to display to set and
remove the input text style; do this instead of unconditionally using
the <.inputline> style tag. The InputDef class should use
<.inputline> as the default, of course. Keep the existing
getInputLine() method, but make it a cover for a call to the new
extended version, setting up an InputDef object representing the
parameters.
Make "remove" (with a direct object but no indirect object) a fully
separate verb from "doff". In English, "remove foo" usually means
"doff foo", but it can also have the substantially separate sense of
removing a component of an object. In the English library module,
provide a default mapping of "remove" to "doff" in Thing, to keep the
default meaning the same. Making a separate Remove action will allow
this action to be independently overridden per object when it's
desirable to do so.
By default, don't list anything in the credits for a library module.
We should leave the formatting of the credits fully up to the author.
Add an optional mainRestore() function, to be provided by the game
and called from the run-time startup code when a saved game is
explicitly selected for restoration at startup (such as by
double-clicking on a saved position file from a GUI desktop, or using
the "-r" option with the command-line interpreter).
Change runGame() so that it no longer takes the initial player
character parameter. Instead, callers should always set gPlayerChar
explicitly prior to calling runGame(). (This change makes runGame()
more sensible for cases where a game is restored: since the restore
operation will restore gPlayerChar as part of restoring the rest of
the game's state, it would be redundant to pass gPlayerChar as a
parameter to runGame() only to have runGame() set gPlayerChar to that
value.)
Fix the bug in EventManager.removeMatchingEvents ('eventMatches' should
be 'cur.eventMatches')
Add a new output stream method, addOutputFilterBelow(), which adds an
output filter at a given point in the filter stack.
Remove the "\( \)" escape sequences (for highlighted text). Authors
should use HTML markups and instead.
Combine open/closed and contents status listings into a single
message, to make the openable container default description less
choppy.
Rename maxBulk to bulkCapacity, to make the meaning clearer.
Similarly, rename maxWeight to weightCapacity.
>put trike in bag. drop bag. ride trike.
(produces a run-time error)
The problem is that we're failing to treat the bag as an invalid
"staging location" on the way to boarding the trike. Add a routine,
checkStagingLocation, to Thing, and call when moving an actor into a
chosen staging location; this routine should generate an error
explaining why the travel is impossible and terminate the command
with 'exit'. BasicLocation should override this to allow being used
as a staging location.
add default Thing handling for 'feel' and 'taste' commands
>close bag; x all in bag
red ball: You cannot see that. [etc]
We're incorrectly resolving the contents of an object for an "all in
x" phrase, even when the contents can't be seen. We must filter the
resolved objects to include only the visible contents.
"all in x" doesn't filter for objects hidden from "all" phrases; it
should.
Standing should be a precondition of "jump".
The noMatchDisambig and disambigOrdinalOutOfRange messages should use
<.parser> open tags, but not close tags: instead, the <.parser> mode
should be left open, to be closed by the disambiguation re-prompt that
always follows.
askDisambig should provide an open <.parser> tag if and only if askAgain
is false: this will let the mode flow in from the preceding re-prompt
message from noMatchDisambig or disambigOrdinalOutOfRange. askDisambig
should always close the <.parser> tag.
Get rid of parserMessage(), and just use <.parser> tags instead.
We shouldn't be able to read at brightness 2. For Readable,
differentiate readability according to brightness and transparency,
as long as a readDesc is defined. (When no readDesc is defined for
the object, use the default "examine" behavior as usual.)
Disambiguation: when indistinguishables are involved, answering with
an ordinal applies the ordinal to the full list, rather than the
reduced list offered:
>take coin
Which coin do you mean, a gold coin, a silver coin, or a copper coin?
>second
Taken. [actually takes a gold coin, since more than one was present]
Add a new option flag, libGlobal.allowYouMeMixing, that controls
whether or not the parser accepts "you" and "me" in commands as
equivalent. Set this to true by default, since most games will not
have both first- and second-person narrative characters at the same
time, and hence it seems unlikely that it will ever create confusion
if the player can use "you" and "me" interchangeably. (Having the
option to make the parser strict about this will take care of any
cases where a game actually does have separate first- and
second-person characters present simultaneously.)
The forwarding scheme isn't quite right. Replace it.
First, get rid of dobjForwardTo, iobjForwardTo, remapTIAction,
dobjRemapTI, iobjRemapTI, dobjRemapTIReverse, and iobjRemapTIReverse.
Second, add a new mechanism that combines the old forwarding and
remapping schemes into a single new system. Use syntax like so:
desk: Fixed
dobjFor(Open) remapTo(Open, drawer)
dobjFor(Close) remapTo(Close, drawer)
;
The first definition above maps the Open action, when applied to the
desk, to a replacement action of Open applied to the drawer instead.
The second does the same thing for Close. This replaces the old
dobjForwardTo/iobjForwardTo scheme, so we no longer need forwarding at
all. The difference with the new remapTo scheme is that we use a
replacement action for the remapping - the remap for Open above is
essentially like using replaceAction(Open, drawer) for the action
definition of Open on the desk.
Further, consider this syntax:
lid: Fixed
dobjFor(Push) remapTo(Open, jar)
dobjFor(Pull) remapTo(Close, jar)
;
Here we've remapped not only the object (as we formerly did with
forwarding), but also the action. The first definition above says
to replace "push lid" with the new action "open jar."
Next, consider this:
ninjaThrowingStar: Weapon
iobjFor(AttackWith) remapTo(ThrowAt, self, DirectObject)
;
In this case, we're doing what the old dobjRemapTIReverse did, but
with considerably clearer syntax. We're saying that when we resolve
the indirect object of "attack with" and find that it's the throwing
star, we should remap the action to "throw at
", where is the original direct object
of the AttackWith action. The important thing about using the
"DirectObject" argument is that it tells us to use the pre-resolved
match tree for the direct object if we haven't yet resolved the direct
object noun phrase - this means that we'll be able to resolve that
noun phrase in the context of the new action (ThrowAt) rather than in
the original action (AttackWith) context.
Compiler: for each nested object definition, set a property of the
nested object, 'lexicalParent', to point to the lexically enclosing
object.
Change the default message for talking/asking/etc a random object to
indicate more specifically that it's illogical to talk to the object.
Add a class for a complex container, for objects that contain objects
in multiple ways. Examples: a crate that can contain things within
and also have objects placed on top of it; a container with
components.
The class should treat all of its immediate contents as components,
so it should be based on Thing rather than Container or Surface. It
should have two additional properties: one for a secret object that
serves as the internal container, and another for a secret object
that serves as the internal surface. Commands like "put in" and
"look in" should be redirected to the internal container, if one is
defined; "put on" and the like to the internal surface, if present.
The contents lister must specifically show the contents of the secret
internal container and surface as though they were contained directly
by the outer object.
Change default Thing handling for Push, Pull, Move, MoveWith (dobj),
and MoveTo (dobj), so that verification succeeds with a logicalRank
of 50, and report that there is no effect in the action. (These
actions all involve physical manipulations that a player might want
to try with any object, so it's better to allow these to pass
verification, so that preconditions can be allowed to proceed rather
than halting before even getting to preconditions.)
OutOfReach seems to put the object itself (not just its contents) out
of reach. Add a separate test for reaching 'self'.
In Actor.canReach, don't make touching our immediate location a special
case - just traverse the sense path to the container as normal.
>look in stove
(First opening the stove)
Opening the stove reveals a loaf of bread. The stove contains a loaf of bread.
It would be good to remove this redundancy: if we're opening
something implicitly for a look-in command, we shouldn't bother
showing what the opening reveals. (We can handle this in Openable's
Open action handler: if the action is implied, and the parent action
is LookIn with the same direct object, we can suppress the revelation
message.)
Suppress default "you see nothing special about it" or "it's an
ordinary x" descriptions when there's a special status message,
such as contents to be listed.
>look in small alcove
You see nothing in it. A trophy is visible...
(Perhaps if we special description items, and nothing listed, we should
display no message at all.)
Use tags for line-input modes in commandSequencer, eliminating method
calls. This should remove the fragile order-of-call issues that we
currently have.
Should we reformat some messages involving implied commands? For example:
>put coin in bag
(first opening the bag)
Opening the bag reveals a keyring. Done.
Perhaps we should put that "done" on a new line - no paragraph separator,
just a new line. (In general, perhaps when there's a non-default response
from an implied command, we should add an "implied command terminator
separator," which by default would simply be a newline.)
Rename the English-specific part of the library to make it easier to add
new languages in the future. In particular:
- Create a new US English subdirectory of the
adv3 library directory; use ISO 639/3166 codes for the naming
convention, so the US English directory is called en_us.
- Move us_eng.t to en_us/en_us.t
- Move us_eng.h to en_us/en_us.h
- Move msg_neu.t to en_us/msg_neu.t
- Add a new library, en_us/en_us.tl. Move the
English-specific source file inclusions out of adv3.tl and into
en_us.tl.
- Include the new en_us.tl library in the
auto-generated makefile for the new-project wizard in Workbench.
(We'll have to add a user preference at some point that lets the user
select the language library to use, rather than using en_us.tl
unconditionally.)
Add a general IF instructions module (like instruct.t from tads 2)
Fix nil dereference in transcript processing for ambiguous noun
phrase errors.
Fix command state management for suffixes so that suffixes are always
generated before the prompt. (We simply need to display <.commandbegin>
in intput.t before displaying the prompt.)
Add a new command state, stateNoCommand, for interactive input. In
this state, don't add any prefixes/separators/suffixes when reading
input. This should be used for yes/no answers, answers to
end-of-game prompts, and so forth - for anything where we need to
read input interactively in the course of a command.
Move en_us.t Thing vocabulary initialization code to VocabObject.
For commands like "put coin with jar," we're asking "what do you want
to put," which is a weird question. (This happens because we
interpret this as "put coin with jar in missing-np" and then resolve
the iobj first; this asks the usual question, which comes out the way
it does because the dobj tentatively resolves to an empty list since
it is a non-matching phrase. To solve this, we could use a dummy
entry in the tentative resolution list, to signal that we actually
have a dobj phrase, even if it's not resolvable to anything.)
It might be nice to catch constructs like this:
>open door with key
You see no door with key here.
In particular, it might be nice to recognize "<nounPhrase> <prep>
<nounPhrase>" constructs specifically, and flag them as "command not
understood" errors instead of "object not seen" errors. This could be
handled by adding a "badness" production for np-prep-np structures for
the prepositions commonly used in command phrases; by making this a
badness match, we'll match it as a last resort when we can't find a
valid verb phrase structure.
Add an extra category of reports, "extraReport", that's used to add
extra information that doesn't replace any default report. Use this
for the key-found-on-keyring report, since this report is supplemental
to any default Locked/Unlocked report and shouldn't suppress it.
Move the call to initializeVocab out of Thing.initializeThing, and
instead call initializeVocab directly from adv3LibPreinit.execute()
for each VocabObject. (This will initialize vocabulary for non-Thing
VocabObjects, such as Topic objects.)
Add a new inputManager method, promptForMore(), that shows the MORE
prompt, taking care of flushing the transcript and dealing with the
real-time clock. The method should take an argument specifying
whether or not to freeze the real-time clock; but even if the clock
keeps running, real-time events shouldn't be allowed to occur until
after the user acknowledges the MORE prompt, because otherwise we'd
defeat the purpose of the MORE pause, which is to wait for user
acknowledgment before showing any new output.
Change the Decoration class so that its generic not-important message
can be overridden simply by changing a property, rather than having
to override all of the Default handlers individually.
Likewise for Distant and Intangible.
Add a new 'isTopLevel' property, which determines if an object is a
top-level container within the game world. Set this to nil by
default for everything except Room objects, where we set it to true.
Use this property in evaluating isIn(nil): for an object with a nil
location, isIn(nil) returns true if and only if the object is NOT
top-level. This allows isIn(nil) to be used to sense whether or
not the object is part of the game world, recursively considering
the status of the object's containers.
Remove selfInReach from OutOfReach.
Fix problem with TravelConnector.verifyTravel(): when this routine is
called, gVerifyResults is not reliably set to a valid list. In
particular, gVerifyResults must be set to a valid result list from
TravelAction.verifyAction().
Turn on the sense cache while generating the status line. Displaying
the status line can involve some substantial sense calculations, so
enabling caching while generating it can improve overall response
time noticeably.
Fix 'follow wall' (generates nil pointer error)
Fix 'throw x at wall' (or floor or other room parts - nil pointer error)
Fix nil pointer error when addressing an ambiguous target actor.
>sit on darker chair
>sit on chair
Which one?
>darker
This is allowed, but shouldn't be - the reply should be "you're
already sitting on the darker chair." (The problem is that we verify
the lighter chair as okay, and we remember that we verified something
as okay but don't bother remembering what; we need to track which
objects we verify as okay individually.)
Make searching a surface (or looking in a surface) show the surface's
contents.
Check bulk when adding a new item to a surface. (This will fix the
weird inconsistency with chairs that allows you to put an object
on a chair you're sitting on, even if there's not room for you to
sit on the chair when the object is already on it.)
Reduce the verification logical rank of non-Food objects for >TASTE.
>sit on chair
>look
... You contain a keyring ...
(We shouldn't mention your contents in that manner. The problem is
that Actor inherits contentsListed from Fixed; Actor should override
contentsListed and set it to nil, since an actor's contents shouldn't
be listed in the ordinary fashion in a room description.)
Show object contents for EXAMINE and LOOK IN to full depth.
Searching (or looking in) a room part shows contents as being "in"
it, which isn't appropriate for walls, ceilings, or floors.
For THROW AT FLOOR, perhaps we should have a message other than
"Dropped" - something indicating the action is more violent than
just setting the object down.
Make contents listings go to arbitrary depth. Rather than showing a
separate list for the contents of top-level list items, show the
contents of each item parenthetically, and then recursively add these
parenthetical lists to show all contents. (Only do this when the
ListRecurse flag is set.)
Add a new Thing property, contentsListedSeparately, that allows an
object to control whether its contents are listed in the new in-line
style, or as the traditional separate list. Make this nil by default.
For "tall" listings, show contents of unlisted top-level items in
separate lists after the main list, just as we do for "wide" listings.
Remove the need to call showContentsList() separately after showList();
instead, call this automatically from showList() if the ListRecurse
flag is set.
Throwing destinations aren't working quite right. Refactor things a
bit: rename getFallDestination() to getHitFallDestination(), and give
it the previous container in the path as a parameter, so that it can
decide whether the object is being thrown from inside or outside (or,
in the case of a multi-location item, the source location). Make
things drop to the floor in most cases; games can override to make
things fall into intermediate containers when desired.
Change Actor.standUp to use reportFailure() when already standing.
>out
Out of what?
>e
You can't get out of that (the east wall).
It would be nice to treat such responses as new commands. We could
probably adopt the heuristic that we give the new command
interpretation priority over the noun phrase interpretation of a reply
to this prompt. Anything that looks like both a syntactically valid
noun phrase and a syntactically valid command probably matches the
noun phrase syntax only because it's extremely abbreviated; the
chances of the verb phrase match being an accident are somewhat less.
In addition, it should be less confusing to the user to treat the
response as a new command when a noun phrase was intended than vice
versa, and it should be fairly obvious how to force a noun phrase
interpretation (adding "the" would do the trick, for example).
Can we go to arbitrary depth for contentsListedSeparately? In particular:
>i
You are carrying a duffel bag (which contains a pen cup). The
pen cup contains four pens.
Right now, we're not adding that bit about the pens, because we think
we're done with the top-level list, as we've already listed
everything: we scan the duffel, and decide that we don't need to
recurse into it, since it's listed and has in-line listed contents.
What we'd need to do is add another recursive descent to look for
everything that we listed at the second (or deeper) level that has
contentsListedSeparately.
Tweak the parser's command match-tree ranking mechanism a little, so
that differences in the number of occurrences of a problem are counted
in a second pass, after we've exhausted the possibility of any
differences in the presence or absence of problems. This will make
slightly better choices in certain cases, such as when we have two
separate noun phrases in the action, and one interpretation treats
only one as ending in an adjective while the other has an adjective
ending in both phrases.
Add a profiling facility, to gather statistics for performance
optimizations in the library.
Rename senseInfoList to senseInfoTable, and make it return a
LookupTable rather than a list. Sense information lists are almost
always used as random-access tables, so we can speed up some
operations by representing these as lookup tables keyed on object.
Rename connectionList to connectionTable, and make it return a
LookupTable rather than a list. Since we want only one copy of each
object in the result, it's faster to build this as a lookup table,
where we can determine whether or not an object is already present
much more quickly than we can with a vector.
>e
>put cup in desk
>undo
(Takes back turns through "e". The problem is that we're marking a
remapped action as a nested action; it's not really nested, because
the original action is completely replaced and will never reach the
execution phase. Do not mark remapped actions as nested.)
Add Thing.specialDescOrder to control specialDesc the relative order
of specialDesc listings.
Change the format of the first-score-notification supplemental
message, which explains how to turn off score notifications: rather
than showing it as part of the same paragraph with the score, start a
new line and show it as a separate notification message.
Rename UnqualifiedPluralProd to DefinitePluralProd. In the English
parser, base implicitDetPluralOnlyNounPhrase(main) and
explicitDetPluralNounPhrase(definite) on DefinitePluralProd, since
"books" and "the books" should act the same way in English.
Simplify the Key and Keyring Attach and Detach handling by using
remapTo() in the keyring: remap "attach x to keyring" to "put x on
keyring" in Keyring.iobjFor(AttachTo), and remap "detach x from
keyring" to "take x from keyring" in Keyring.iobjFor(DetachFrom).
This removes a bunch of code from Key to handle the remapping at its
end - that code all predated the remap mechanism, and can now be
greatly simplified by using the new mechanism.
Fix a problem in vocabulary initialization: if a hyphen is used as a
placeholder in a vocabWords_ setting, the hyphen is added to the
object's part-of-speech property (it's not added to the dictionary,
but it does show up in the list for the part-of-speech property). It
should be omitted from the list, since it's not really a vocabulary
word.
Fix finishOptionUndo so that it can be used in daemons. This requires
a couple of changes. First, remove the transcript flush - this is a
relic of the pre-'transient' transcript mechanism and is no longer
needed. Second, daemons and fuses need to be able to use
TerminateCommandException and the like to exit, so we should catch
command-ending exceptions in the event manager.
Fix examining sightSize=large objects at a distance. (The problem is
that Thing.canDetailsBeSensed() is attenuating the ambient light level
for the sense path, which is unnecessary as the SenseInfo already does
this. canDetailsBeSensed doesn't need to go to so much trouble - any
SenseInfo with trans != opaque indicates the object can be sensed,
since trans will always be set to opaque for objects that cannot be
sensed.)
Add tok.t to system.tl. (This file isn't actually necessary to
compile a simple program, which is why it hasn't been in system.tl all
along; but it is necessary for any game based on the library, so on
balance it's probably better to include it in the default library.
Separate some action-time checks into check() routines:
Container.dobjFor(LookIn); Dial.dobjFor(TurnTo) (this could perhaps
benefit from some 'verify' checking of the literal value as well).
Add a specialDescLister, and use it for listing special descriptions.
Add a new property of Thing, specialDescListWith, which returns a list
group to use when showing special descriptions; use this new list
group generator in specialDescLister. This will allow special
descriptions to be grouped when desired, simply by using the standard
list grouping mechanism.
Add an "isCircularPassage" property to TravelConnector: when this
property of the connector is true, fully describe travel that winds
up in the origin. (We normally don't bother describing such circular
trips, but sometimes it's desirable to be able to do so.)
In en_us.t, add askDobjResponseProd=singleNoun for each action that
takes a singleDobj as its direct object.
In en_us.t, remove the redundant syntax for "walk in/into" and "go
in/into" from VerbRule(GoThrough), as these are already handled by
VerbRule(Enter).
In en_us.t, use tags wherever literal quotes are used, rather than
ASCII-34 quotes.
Convert the parser to use the new Dictionary API. In the English
parser, install a StringComparator to perform case folding and
truncation matching. Remove case conversions from the English
tokenizer, as they're not needed with the StringComparator.
Refactor the travel connector operations so that everything turns into
a TravelVia on its connector. Handle direction commands ("go north")
by using replaceAction(TravelVia, connector). This simplifies the
travel verbs, and makes the task of defining travel connectors more
consistent with defining other action handlers.
Move the travel connector checks for dark-to-dark travel and for
travel barriers into the TravelVia check() handler.
Rearrange the room description code (lookAroundPov, etc) so that
lookAround can be called on any object. To do this, move all of the
room description mechanism from BasicLocation into Thing, and work the
NestedRoom code into the Thing code for descriptions.
Add a return value to basicEventManager.removeMatchingEvents,
indicating whether or not a matching event was actually found.
Fix getTenativeLiteralText argument mismatches in parser.t when
calling getLiteralText.
In MessageBuilder.generateMessage(), for the case where we're
substituting '{actor}' but there's no current action, use the current
gActor value if it's not nil, or gPlayerChar if gActor is nil (rather
than always using gPlayerChar - using gActor instead allows a daemon
or other non-action message producer to set gActor to indicate the
actor doing whatever it is that's generating the message).
In Actor.addPendingAction() and addFirstPendingAction(), take
a resolved object list as the last varargs-list argument, and pass
it to the PendingCommandInfo constructor.
Hide Fixed objects from "drop all".
Add some more default verbs: CutWith, Kiss.
Add RoomPart.isIn(). Consider a RoomPart to be within a given location
if its room part location is equal to or within the given location, or
the inherited isIn returns true.
Add some more string-quoting styles to the English tokenizer: `like
this', and with typographical ("curly") single and double quotes
(\u2018 and \u2019 for single quotes, \u201c and \u201d for double).
Add a quotedStringPhrase production type, for rules that explicitly
call for quoted strings (but not unquoted literal text). Use this
production to build the quoted string rule for literalPhrase.
Add SAVE, RESTORE, and SCRIPT command variations that take quoted
strings giving the filenames to use.
Traveler.travelTo in a daemon dereferences gAction - check to see if
gAction is nil, and skip that step if so.
Change all uses of 'seen' to use the seenBy() and setSeenBy() methods.
Move these from BasicLocation into Thing so that all objects uses them
uniformly.
Bug: TERSE mode doesn't show full descriptions on first entry.
Some of the objects shown in the introductory room description aren't
getting marked as seen. (The problem is that Thing.lookAroundWithin
is using gActor in a few places where it should be using the 'actor'
parameter.)
Bug: NoTravelMessage is commented as being an apparent connector, but
is implemented as a non-apparent connector. Leave the implementation
the way it is, but fix the comments, and add a new class that acts the
same as NoTravelMessage but has an apparent connector (call it
FakeConnector, perhaps).
Rename LiteralAction to LiteralTAction. Add a new LiteralAction
class that takes only a literal phrase as its object (i.e., no other
direct object), for commands like "say random stuff".
Note that DefineLiteralAction is likewise renamed to
DefineLiteralTAction, and we add a new DefineLiteralAction for
defining an action with only a literal phrase for its object.
Add a PostUndoObject class, analogous to PostRestoreObject.
Reduce parse rankings for pronoun phrases vs noun phrases: if a word
is explicitly defined as noun, it probably should be a stronger match
than a pronoun interpretation of the same word.
Consider this command:
>put ball in asdf
Which ball do you mean, the red ball, or the green ball?
>red
The story doesn't know the word 'asdf'.
>oops box
Which ball do you mean, the red ball, or the green ball?
The issue is that OOPS retries the entire command with an updated
token list, and as a result, the disambiguation work from the first
attempt at resolving the objects is lost for the second iteration.
The reparsing is necessary, since the corrected typo could change
the entire meaning of the command.
One possible solution: add a resolution pass specifically to catch
unknown words. Don't do any other resolution on this pass - just
deal with unknown words. This would ensure that we process OOPS
before we ask any disambiguation questions.
Another possibility: keep a set of answers to questions, and the
conditions under which the questions were asked. Clear the list
each time we start a new parsing from scratch, but let the list
survive if we reparse a token list. Whenever we're about to ask
a question, look to see if the same question has been asked with
the same conditions, and if so, reuse the same response.
All
Missing noun phrases: prompt for input when PC gave command
Missing noun phrases: use a default when possible
Object announcements: include a preposition when appropriate (announcing
indirect object, announcing direct object with verb taking a preposition:
">dig \n (in the dirt)")
Pronouns
Again
Again: should we use disambiguation information from past commands?
For example:
>take book
Which book do you mean, the red one, the blue one, or the green one?
>blue
Taken.
>g
Should this 'take blue book' or should it ask...
Which book do you mean, the blue one, or the green one?
Right now we use the literal tokens from the original command, so we
ask the disambiguation question again. This is exactly what we want
in cases with indistinguishables:
You see five silver coins here.
>take coin
Taken.
>g
Taken.
But in cases where we have distinguishable objects, we probably want
the resolved objects, not the words.
We probably want to distinguish this way: if we had indefinites or
indistinguishables, resolve again from the original text. Otherwise,
use the original resolution.
disambiguation: we're not keeping the full list properly in cases of
indistinguishables:
>take coin
Which coin, a gold coin, or a silver coin?
>coin
Which coin, the silver coin, or the gold coin?
The second time around, the cardinality of the full list should not change,
so we should be asking the same question as the first time.
Preconditions
We need 'touchDobj' and 'touchIobj' preconditions - use these for all
objects that require that the object be physically manipulated by the
actor but not necessarily held. These are required to deal with things
like the contents of a closed transparent container, where an object
can be in scope but cannot be manipulated.
As part of this, we need to find a way to say what object blocks a
sense path opaquely, so that we can say why we can't touch something
that we can see. There are two main cases: containers are in the way,
and connectors are in the way.
Bag of holding: when an actor's hands are full and the actor is trying
to take something, pick a held object and try to find a bag of holding
for it. First, check the object's preferredHolder property - this
gives an object or class that the object prefers as its bag of holding.
If the actor is holding such a holder, move the object to the holder.
If we can't find any affinity, look for any object the actor is holding
of the generic BagOfHolding class.
Examples of specialized bags of holding: wallet, key ring, purse, duffel
bag.
Formatting: where does the blank line come from before an implicit
message for a single command?
use disambiguation filter even for indefinites, so we pick the best
possibility if there are objects of different likelihoods
"The red one" should work for exclusion lists. It should probably also
work in general - although 'one' should bind more tightly to an actual
name, in the absence of a name it should serve as a generic noun.
"drop coins" - should we consider only the ones we're carrying? In other
words, should we run verification and include only the logical ones?
Probably - if there are some logical and some not logical, apply the
command only to the logical ones; otherwise, apply the command to all
of them.
"take both coins" - really wacky
"take two gold" should work (i.e., quantified ending in adjective)
consider:
>take both coins
Which two (of five gold coins or three silver coins) do you mean?
>two gold
You don't see that many coins here.
The problem is that we disambiguate with the reduced match list.
>take coin
Which coin, gold, silver, or copper?
>all but copper
no worky - might even be a gram prod bug, since we seem to have a weird
firstToken/lastToken value in these cases
"take books except red" - disambiguation of the exclusion list should be
limited in scope to the resolved plural list.
"take books except silver" - "You see no silver here" - this should
ignore the missing object.
"take coins except copper" - the 'copper' should implicitly be plural
rather than indefinite so that we skip all of the copper coins if there
are several
>take gold coin, gold coin
gold coin: Taken.
gold coin: You're already carrying the gold coin.
When we have multiple equivalent items in scope, and we mention several
items in a list, we should pick separate equivalent instances for each
mention.
This should apply to disambiguation responses, too:
>take coin
Which coin do you mean, a gold coin or a silver coin?
>gold, gold
"take any ball except green" - takes the green if there's nothing else
left
add verification for pre-conditions - objHeld, for example, would boost
the likelihood for a held object
add the new pre-execution check as a separate phase (checkXxx?)
output capturers - one at a time
add object affinities for bag of holding
key ring - not just a bag of holding, but automatically adds items
as they're picked up when the key ring is in inventory
Key ring messages: we should check for a non-default report, and if there
is no non-default report we should include more details: "You take the
key and put it on the keyring."
Probably should have an objDirectlyHeld precondition. This one would
be used for commands that physically move an object; objHeld would
continue to be used for commands that merely need to do something
with an object. So, "put x in y" would require that x is directly
held, while "unlock x with y" would only require that y be nominally
held. For objDirectlyHeld, we'll make the implicit command silent
if the object is being indirectly carried, so that we don't get weird
things like this:
>i
You are carrying a paper bag. The paper bag contains a key.
>drop key
(First taking the key)
Dropped.
If there's no doXxx, fail verification with a generic Illogical('you
can\'t do that').
For travel: shouldn't we have a verify phase for moving the actor?
need to export propNotDefined in a system file
take key: if it started on the keyring, we should take it off the
keyring
Travel connectors
Fix 'i tall' listings to show contents with a better format
Move senses to a separate module.
>get coin
Which coin, a gold coin, a copper coin, or a silver coin?
>coin
Which coin, a copper coin, a silver coin, or a gold coin?
The re-ordering on the second round is weird - we should keep the order
stable if we can.
>get ball
Which ball, red or green?
>blue
You don't see that here.
The error shouldn't be "you don't see that here" - it should be more
like "that's not one of the choices you were offered." However, it
would be better to widen the scope again to the real scope, rather
than failing.
Add maxWeight and weight enforcement
Move pre-conditions to the objects. Keep the verb preconditions as
well, but verb-specific pre-conditions should never mention the
objects.
Mark objects for which we've displayed descriptions, for topic scoping
Add "module ID" objects, for library extensions. The "credits" command
should run through the module ID's and show a credit for each one.
Make styles into objects
Fuses and daemons
Need to apply logical tests again after running preconditions. (Try
this: "put rusty key on keyring" - it'll pick up the key for the
'held' precondition, which will put the key on the keyring, but then
the explicit put-on-keyring command will succeed, which it shouldn't.)
Generalize the showList mechanism to allow for other types of
hierarchies besides contents to be shown. Use the lister interface
to virtualize the hierarchy traversal.
Use the generic showList mechanism for the full score lister, rather
than using a specialized lister that does essentially the same thing.
Score change notifications: add daemon that senses score changes and
notifies between turns.
Do not count the time taken for implicit commands.
Add a "message generation context" that determines what is generating
messages. This object must be sensible to the player character for
messages to be generated. For convenience, define a callWithMessageContext
function that establishes the context, invokes a callback, then restores
the enclosing context.
When establishing a new context, we check to see if the generator is in
scope in the given sense. If not, we hide messages; if so, we show
messages.
A nested context should actually be considered a context switch,
because we don't want an enclosing hiding context to hide an enclosed
showing context. For example, if we are generating messages with
sight scope for Lloyd, and we have a nested context where Lloyd is
saying something and so we now are in hearing scope for Lloyd, we
want the nested context to show its messages if in fact Lloyd is in
hearing scope even if Lloyd is not in sight scope (maybe the player
character and Lloyd are in contact over a walkie-talkie).
NPC messages: only show if NPC is in sight of PC (using message generation
context)
Use message generation context for fuses and daemons. Allow events to
be created with a context object and sense object; whenever an event
is executed, establish the context for the duration of the event's
execution.
try this: bob, n. z. s. / n. z
We should see bob leaving on his third queued move, but we don't for
some reason.
Do we really want to remove the objects directly involved in a command
from the beforeAction and afterAction processing? It seems like it's
an unnecessary inconsistency.
Should consider directing report messages to the pc/npc message
objects. This would more readily allow separate messages for
reporting the results of pc and npc commands (for example, "Taken" vs
"Bob takes the book"). This is probably trivial, since we already
have the pc/npc message generator division anyway; we probably just
need to use it rather than verbMessages for generating report
messages. (If there's really a good reason to have a separate
verbMessage object, maybe we should have a per-actor verbMessages
object, the way we do with the default library messages generator.)
Missing blank line between commands on parser failures:
>bob, n. get silver coin.
>n
In room descriptions, list actors separately.
Don't let the room lister include actor inventory listings with the
main room contents listing.
Footnote system
Add an initExamineDesc, to parallel initDesc when an object is
in its initial position and is explicitly examined.
Check changes in object bulks for effects on containers. If a
container doesn't allow a change in child bulk, fail the command
that attempted the change. (For example, in a stretchy bag,
putting a new object into the bag should be disallowed if doing
so would make expand the bag so much that the bag would be too
large for its own container.)
Stretchy bags, that conform to their contents (in the sense that
the bulk changes according to its contents)
Use normal command result reports (especially ReportFailure) in
precondition checks.
For target of "put in", elevate likelihood for an item being held.
implement save/restore
implement restart
implement undo
For undoing commands to NPC's, show the NPC as part of the command
being undone: "undoing one command: bob, go north".
Add '\{' and '\}' (push/pop formatter state) output sequences.
Implicit commands should not set antecedents.
"Take All" should include the non-fixed contents of fixed containers
and surfaces within the room.
Status line
Footnotes: add FOOTNOTE MEDIUM mode, which shows new footnote
references but hides references to footnotes that have already
been read. This helps cut down on unnecessary clutter, since
a footnote reference is unlikely to be of much interest once
it's been read, assuming that footnotes are always extra
information that isn't required to finish the game.
Weird:
>get large red ball, small red ball and drop them
[works]
>get asdf red ball, small red ball and drop them
Don't know the word 'asdf'
>oops large
...you see no drop them here
Why does it pick out the right interpretation normally but can't
on a token reparse?
Add an "unobvious" verification mode that allows the command but will
not be accepted as a default.
Decorations
Add dobjCheck, iobjCheck, dobjGen, iobjGen equivalents
Add a default mechanism for listing the inventory of an actor as
part of the actor's description. ("Bob is carrying...wearing...").
Add a sorter method to the list group object, for simple control over
the sorting order
Add input font switch when reading a command
When defaulting a direct object of a two-object command, include the
verb's participle form in the default message:
>ask about watch
(asking Bob)
Most default messages are meant to be tagged onto the end of the
command line, but when we're adding a direct object to a two-object
verb, the added object goes in the middle of the command and hence
the default phrase doesn't sound right when tagged onto the end of
the command line. By adding the participle form of the verb, we
make it clear that the default message stands alone as a new sentence
(or sentence fragment, anyway) and is not meant as a missing
continuation of the original command text.
"show iron key" - since the missing phrase form of this command has
badness, this ends up being interpreted as "show key to iron", which
is silly. For command forms like this with two objects and no
preposition, we should probably add a non-badness single-object
grammar that asks for the missing prepositional phrase. How we
select the misisng-object phrasing over the two-object interpretation
is unclear, though.
Parameterize the amount of time it takes for an actor to give an
order to another actor.
try 'inflate raft and put it in bottle' - we don't get a blank line
before the 'put it in bottle' response, presumably because it has
an implicit command first
Floor/ceiling/walls for indoor rooms, ground/sky for outdoor rooms
Traverse the sense path for throwing an object at another object, and
make the projectile land in the appropriate place when an intervening
object prevents the throw from finishing.
Keep track of multiple logical results, and make comparisons based on
the whole list. Distinguish different reasons (using 'key' values)
for different logical results with the same ranking.
The idea is that if we have two objects that are both ranked the same
way on one axis but are distinguishable on another axis, we want to
consider the axis on which they're distinguishable. For example, if
we type "put silver coin in jar," and one jar is open and the other
is closed, we want to pick the one that's open. At the same time,
they'll both be ranked as equivalently logical on the basis that
neither is held by the actor. We don't want the being-held status to
override the open status, but we likewise don't want the open status
to override the being-held status if both are open but only one is
being held. The solution is to ignore the equivalent being-held
status when both objects have this same value, and to ignore the
equivalent open status when both have the same value, but still
consider these attributes when they differ.
Provide suitable default implementations for about 1.0e+6 verbs
"re-route" syntax - see, for example, keyring.iobjFor(AttachTo).
Use asDobjFor and asIobjFor:
iobjFor(AttachTo) asIobjFor(PutOn)
For travel: actorStanding pre-condition that automatically extricates
the actor from chairs and the like. We must catch dangerous conditions
in the verifier.
For missing iobj's, allow preposition in response:
>dig in dirt
What do you want to dig in it with?
>with shovel
Type/enter string/number on object
Type/turn to unquoted string?
In a two-object action, when assuming one object or the other, and
then asking for the other, we should show the defaulted one before
prompting:
>ask
(Bob)
What do you want to ask him about?
For disambiguation, never pick a 'not obvious' over an illogical, because
a 'not obvious' essentially counts as an illogical for the purposes of
picking objects.
It might be good to eliminate the redundant default announcement in
cases like this:
>ask
(asking Bob)
What do you want to ask him about?
>watch
(asking Bob)
It might be good to eliminate the participle when asking for a direct
object of a two-object verb and the indirect object is not yet known:
>ask
(asking Bob)
It would be better as simply
>ask
(Bob)
Turn x to string/number
Dials (turn, turn to)
Buttons (push)
Switches (turn on, turn off)
Levers (pull, push, move)
Doors
In the dark: should be able to list items you're holding by touch.
In the dark, 'look at' should indicate that it's the darkness that
prevents an in-scope item from being inspected.
In the dark, inventory should do something better than say "you're
empty-handed".
Climbables (such as stairs). These should probably just be travel
connectors that go up and down.
Check scope for preconditions - make sure we don't apply a command
to an object not in scope by way of a precondition implicit command.
For example, if you type "go north" in the dark, and there's a closed
door that you can't see, you shouldn't be able to open the door by
way of the travel precondition.
>go through passage
>again
This somehow keeps the old passage in scope. Must need to check
scope again for 'again'.
(The problem is that the last action is hanging on to its cached
resolver scope lists. We simply need to drop the old resolvers
so that we start the new command with new resolvers.)
climb up/climb down verbs
make sure we have makeOpen, makeClosed, makeLocked, makeUnlocked, etc. -
in other words, encapsulate these types of state changes the same way
moveInto encapsulates containment changes
Add an "inaccessible" verification failure - this would be more
disapproving than any "illogical" level and would be used for
"it's too dark" and "you can't reach that" types of failures.
The pre-condition execution sequence should probably be like this:
for (pass = 1 ; pass <= 2 ; ++pass)
run all verifiers
for each precondition
run this precondition, allowing implicit commands on pass 1 ONLY
if no implicit commands were executed
break
This would ensure that side effects of an implicit command are re-tested
through previous preconditions. Each precondition would only be allowed
to run its implicit command on pass 1 because this would prevent
infinite loops from conflicting preconditions - on pass 2, if the
condition isn't met by now, we'll assume it's not going to be, so
we'll simply fail the command.
"actor, give x to me" should be handled as though it were "ask actor
for x", via a replacement command.
actors: shouldn't allow taking possessions in general
Dark rooms: limit travel to adjacent lit locations?
Explicitly inherit base class vocabWords_ in initializeVocab() in
us_eng.t.
touch/feel verbs
Add verb "listen" (both transitive and intransitive)
Add verb "smell" (transitive and intransitive)
Enterables
keys and doors: add options for boosting likelihood for keys:
-
on the first successful use of a key, automatically boost the
likelihood for future disambiguation using the key if an option is
set (this might be the default option)
-
if the author wants the player character to know from the outset
which key goes with a door, the author can add the key to the
lockable's knownKeyList - this can be useful for games with obvious
key associations or key associations known from the outset to the
player character (for example, if the PC has a key to their own
house, the ought to know what the key opens)
locks and keys
lockable doors
Automatic key rings for unlocking doors - an "unlock" command with no
indirect object automatically searches for a keyring in the actor's
inventory, and automatically searches for a key on the keyring.
keyring: need to implicitly take keyring if not held when trying it
keyrings: message on success should be something like:
(trying each key on the keyring, you find that the iron key unlocks the door)
keyring search: when we already know the correct key, we should just
default it rather than search the keyring for it
implicit commands that use 'replace' should inherit original implicit
status (try 's' from the stair bottom - we get an "unlocked" report
for the replaced 'unlock door' command)
For Fixed, indirect the illogical messages through properties, to
allow easier customization.
Whenever any message appears before a room description in a command
sequence, show a blank line before the room description. This applies
especially to travel, but is generally desirable for any command that
shows a room description.
Put iron key in duffel; unlock iron door - won't default the iron
key, because the keyring takes precedence by virtue of being held.
The problem is that the objHeld precondition is applying a likelihood
of 80 because the iron key isn't held, and the held keys are getting
100's. Maybe we need to reduce the likelihood of any other keys or
keyrings when the known key is present, but this seems like a poor
special-case hack for a problem that is likely to arise in other
contexts.
To solve this, add a 'priority' to logical results. Use low priority
for precondition rankings, because these are inherently of lower
importance than unique attributes of the objects themselves.
Add an 'exit only passage' to be used as the receiving end of things
like trap doors, chutes, and so on.
Message travel connectors: simple connector that shows a message as the
connector is traversed.
preserve the case of tokens in literal phrases
Light/dark changes should be noted at the end of each turn.
Light/dark changes should be noted before/after each daemon is
dispatched.
>move me to
What do you want to move it to?
Could we substitute "yourself" for "it" somehow?
Trap doors - in particular, the arrival message should indicate that the
trap door automatically slams shut after the actor arrives.
Possessives - "take his box", "take bob's box"
possessives - problem with disambig:
>get key's jar
Which key's do you mean...
That should just be 'which key do you mean', not 'which KEY'S'
possessives - plurals
possessives - five of bob's keys
possessives - bob's keys except the green one
get all of bob's keys except the rusty and iron ones - even when this
is just one key, we should announce it as though it were a multiple
object (so we need to set some flag)
possessives:
>bob's
unknown word ''s'
possessives - problem with disambig:
>get key
Which key do you mean...
>get bob's key
The word ''s' is not necessary...
The problem would seem to be that we don't handle 's in disambig
responses and treat it as an unknown word.
>look under me
You see nothing unusual under you.
We should make that second 'you' into 'yourself' instead. In general,
can we change an objective case use of an object already used in the
nominative should to a reflexive instead?
possessives - disambig response should allow 'bob's' as a choice,
even if it's not offered
possessives - "get keys except bob's" - need special grammar for
just a possessive in this context
>bob, x key
Which key?
>quit
This command cannot be directed to another character...
Distant items
Finish topic-verb implementation
Consider:
>lock door
(first closing the iron door)
(with the iron key)
Locked.
The defaulted indirect object is misleading. This should instead be:
>lock door
(with the iron key)
(first closing the iron door)
Locked.
The problem comes from the fact that "Lock" has an objClosed
precondition for keyed lockable; "Lock" doesn't need this
precondition, because "LockWith" has it. Removing the precondition
will get the ordering right.
truncated adjectives seem worse than unknowns in ranking:
>type hello on typewri
do not filter the 'all' list with hideFromAll when looking for default
objects
>get iron key
You take the key and attach it to the keyring
>get keys
brass key: You take and attach...
rusty key: You take and attach...
iron key: Taken.
It's irritating that the last one is taken off the keyring. Should
we leave items out of plurals when we have multiple logical items of
different logicalness? (This isn't a completely academic problem,
since you could in practice have a couple of new keys in a room that
you want to pick up, without detaching keys you already have on the
keyring. One easy way to solve this would be to use a different
command for detaching from the keyring - require 'take key off
keyring', and say Illogical('if you want to take the key off the
keyring, say so'); but this could be irritating when you just want to
take the key off the keyring.
follow: should replace command with actual command used for last
observed travel; alternatively, reproduce the preconditions and
verification for the travel via the connector
Player character vocabulary - either add switchPlayer to fiddle with
the dictionary, or have some other way to route 'me' and 'my' to the
current player character (using matchName, for example).
Move travel preconditions into the connectors after all. Just create
an openDoor precondition type that is instantiated with a specific
door to open, rather than attaching it to a command object.
send bob into dark. follow bob into dark, then:
>follow bob
Bob is right here.
>bob, u
You see no bob here.
>bob, n
>g
We're not checking to see if the actor is still in scope.
>n. open door.
>close door and sit down
--> endless loop - weird problem with token indices for 'sit down' part
of grammar tree. The token indices for the second predicate are for
the entire command, strangely, so we think we need to parse the entire
phrase again and again.
This has something to do with the empty dobj noun phrase. Same thing
happens with 'close door and open' (which also has an empty dobj),
but not with 'close door and look' (which takes no dobj) or 'close
door and sit down on floor' (which has a non-empty dobj).
when changing sensory contexts, don't flush output; instead, consider
sensory context when queueing reports, so that we simply don't queue
a report unless it's visible in the sense context
when a door closes from the other side, show a message about it
Status line messages for nested rooms, sitting, lying
Standing needs to be able to specify what you're standing on in some
cases, such as platforms. Is it only the floor that won't?
when standing, make sure we move actor to room
when sitting/standing, make sure we do all the necessary work of
travelTo
Chairs, beds (make Room not Fixed? or make a deeper base class for
BaseRoom, with Room: Fixed, Room?)
sit/lie on floor: actor doesn't actually need to stand first, if the
actor is already sitting/lying in the room. In other words, if we're
just changing from sitting to lying or vice versa, and not changing
containers, we don't need an intermediate standing state. We need
something like standing-or-in-obj instead.
sit/lie/stand on - when checking that we can enter a nested room, make
sure we're in the immediate container of the nested room
sit/lie: need to configure prep for 'on' vs 'in' (since we 'sit on'
some things but 'sit in' other things, and likewise for 'lie on'
vs 'lie in')
sit on floor, then sit on chair -> no intermediate 'stand'. This might
not be important, since it won't involve actual movement, but it's a
little weird, and it would be nice to make it consistent with the real
nested rooms.
'sit on floor' doesn't work if run from a doubly-nested room (or deeper)
sit on armchair, then stand on dais: "already standing on the dais".
Need to deal with an implied command that effects the change required
of the main command.
Might want a precondition for travel more general than 'standing',
to allow for standing in vehicles and on platforms
list actors in a nested room when describing the room
maximum seating capacity for a chair
in box: try 'e': "you can't do that from the main platform".
in box: in the dark, should probably have the enclosing room in scope
in box: when closed and we have light, definitely should have the
enclosing room in scope
in box: should not be able to try to get out of box when we can't
sense beyond the box - it should serve as top-level location for
the purposes of the implied actions in this case
When in box, and box is closed, list contents of box as top-level
contents of room.
Listing room contents should show contents of fixed items to any
depth. The first-level contents of anything not listed should by
default be listed.
Vehicles
traveling - don't use actorStanding, but use travelerReady instead.
If the traveler is an actor this turns into actorStanding (or whatever
is appropriate for the actor); for a vehicle it's probably nothing, but
could be used to check for things like the doors being closed on a car
or seatbelts being fastened.
Vehicles: when PC is riding, we get no description of the new room
Vehicles: put tricycle on platform; get on tricycle; ride tricycle:
we don't seem to move the tricycle off the platform first.
Vehicles: departing should use definite article
Vehicles: departing doesn't list riders (presumably they're out of
scope by the time the departure message is generated; need to save
sense info or something)
sample game: implement 'ride tricycle'
sample game: when getting on tricycle, mention how to ride it
Put x on floor: if we're in a nested room with a drop location that
isn't the main room, we'll perform a "drop" in the nested room. Should
actually move to outermost room if possible. (Maybe we need to leave
the nested room as a precondition.)
Vehicles: need a convenient way to put up vehicle barriers, so that
you can walk through a door but not ride through, and vice versa
'follow me' mode for an actor
recognize (in input) reflexive pronouns referring to target actor
(bob, examine yourself)
Allow things to be placed out of reach from within nested rooms
(canReachFromRoom).
change from LangThing to 'modify Thing', etc.
change "bob, tell me about x" into "me, ask bob about x"
'push/move dobj to iobj' - for things like building staircases ('push
crate to window'). Synonyms might include 'push/move/put dobj under iobj'.
Fix follow problem: "bill cannot stand in closed box" is reported
even though we're out of range of the sense.
Allow some types of nested rooms (platforms, booths) to be their own
follow locations, so we can follow an actor into such a room
Might want to keep pronoun antecedents per actor. When an issuer
gives a command to a target, the target should copy all of the
antecedents from the issuer.
for 'in' and 'out', by default ask for a direct object if there's
no evident travel connection for the direction
remove all preconditions from noTravel, as we know travel will not occur
and hence don't need to meet any conditions
'push obj north' (and other directions)
for "follow", track actors in vehicles as well as the vehicles
themselves
'draggable' class, implementing a general framework for pushing
objects from one room to another
add a push-travel barrier that works like the vehicle barrier, but
for traveling while pushing objects
Change the barrier mechanism so that barriers are listed on the connector,
rather than being part of the connector. This is needed because the
connector is in many cases directly addressable - putting the barrier
in front of the connector as a proxy connector doesn't work when you
can reach the connector directly, making an end run around the barrier.
Change the listing mechanism so that showList is a method of the
ShowListInterface class, to simplify argument lists.
Handle nested grouping. Use this for equivalents in groups.
For group listings, don't add a group when there's only one group due
to equivalence. We currently say this, which is awkward: "the jar
contains two coins (two copper coins)."
Equivalent listings - eliminate as a separate special case and simply
list equivalents using a listing group. Add an Equivalent Group
which implements a simple listing group for equivalents; by default,
the group simply shows the count.
For our silver/gold/copper coins in the sample game, make sure the
grouping mechanism can list like this: "eight coins (three silver and
five gold)". Note that the names in the parens leave off "coins" and
just list the number and adjective.
Move all of the lister objects into the messages module, and eliminate
the secondary indirection through the libMessages object. Simply make
the lister itself part of the messages module, and put the messages
directly in the lister.
Get rid of the unnecessary list flags LIST_WORN and LIST_INVENTORY,
replacing them with parameterization at the listing method level
instead.
Use the generic list mechanism to list actors in a room. By default,
each actor simply lists separately, but it should be possible to
group actors using the normal listing group mechanism ("Bob and Bill
are at their desks", for example).
Can we make the disambig list use the normal listing mechanism?
Add a "specialDesc", similar to initDesc but for more general purposes.
specialDesc should turn into initDesc when initDesc is applicable.
add "the former" and "the latter" as disambiguation responses
For nested rooms, break out the "staging location" test from
checkActorReadyToEnterNestedRoom into a separate routine. Use a
list of possible staging areas given by a separate property for
easier overriding.
For nested rooms, use a separate "exit destination" property for the
destination of a GetOutOf action, rather than assuming that we always
move to the enclosing location.
Add a "high nested room" that can't be entered except from particular
staging areas due to height. These is a fairly trivial
specialization: first, don't solve the problem using implicit
commands unless the actor has already solved it before; second, the
default failure message is something like "you can't enter that from
here; it's too high up."
Rename the '*predicate*' production classes to '*command*'
Base all command (formerly predicate) classes on a common CommandProd
base class
Add a resolveAction method to the command classes. This method
retrieves the action from the command. In English, this simply
retrieves the 'predicate' match tree object, which is always based on
action; other languages, especially those that use case markers
rather than word order to encode phrase-role information, can
customize this behavior appropriately for their grammatical systems.
When scanning command parse matches, we can immediately eliminate
any match for which resolveAction fails to find a valid action.
Use separate listers for room/object contents lists and inventory lists.
Use separate show-list-item methods for inventory items.
For keyrings, show contents of keyring in room/object/inventory contents
as a sublist: a keyring (attached to which are an iron key and a bronze
key)...
Allow queuing pending actions for an actor, not just token lists.
Make it possible to insert a pending action at the beginning of the
queue (for continuation actions).
Use a separate listing flag (isListedInInventory) for inventory
list inclusion. For actors, make this true by default, even though
actors are not listed in room descriptions.
Dispenser and Dispensable, for matchbooks and the like
Collective objects for cases like "book of matches" and "matches" -
when we type "take matches," we should interpret this as the book of
matches (i.e., "matches" == singular) rather than as the individual
matches ("matches" == plural).
Matches - flammable objects that are self-igniting
Matchbooks
Include a checksum in saved state files, and check at start of load.
This will help avoid attempting to restore a file that was corrupted.
Add a tentative pre-resolution pass, for the later-resolved object of
a two-object action, that runs before the resolution of the
earlier-resolved object. Don't perform full resolution on this pass;
specifically, don't count any problems against the ranking results,
and don't ask for help interactively.
Make use of this information in 'verify' routines for the
earlier-resolved objects when applicable. For example, for "take
from ", we resolve the indirect object first, so we
don't know the final dobj resolution when resolving the iobj; so, use
the tentative information so that we can rule an iobj illogical if
there's no tentative dobj inside the proposed iobj.
Might want to clean up the other-object business in askMissingObject
and related routines. This scheme doesn't feel properly generalized.
For that matter, we might want to rethink the whole scheme so that
it's subclassed by and handled in the action classes, rather than
parameterized, since the parameterization scheme seems hokey and
brittle.
Remove the otherObjectList and otherObjectWhich stuff when we've
cleaned up the corresponding code. This information should now be
available via the tentative resolution lists instead.
When lighting a match as part of an implied command, reduce the burn
time by one turn - this ensures that lighting the match as part of
another action won't artificially extend the match's burn time.
candles, torches, oil lanterns - flammable objects that must be lit with
another item, such as a match
defaults: when there are two or more equivalent items that could be
used as defaults, pick one arbitrarily
for implied transfers into a bag of holding, put the indirect object
of "take from" last in the affinity list
for implied transfers into a bag of holding, other things being equal,
transfer the least recently acquired object first
food items
Change the logic of checkActorInStagingLocation so that it doesn't
assume that we can reach a staging location just because we're
indirectly in the location. Instead, add a "choose staging location"
method so that rooms can override the staging location chooser with
appropriate special-case code when needed.
Add issuing actor and target actor parameters to resolveAction(), so
that the resolver can tell what reflexive pronoun phrases mean. (In
many languages, reflexive constructions are structurally part of some
predicates, and affect the meaning of the predicate, so the identity
of the speaker and of the subject must sometimes be known to
correctly determine the interpretation of the predicate structure.)
sample game: reject just plain "match" in the matchbook's matchName
touch scope: include contents of matchbooks and keyrings, so that
these can be used even in the dark
Add an associated odor and sound property to each Thing. This points
to an object that encapsulates the object's sensory emanations. Always
add these objects to scope when the associated objects are in scope,
with the same properties.
Make "listen to x" and "smell x" defer to the associated objects.
For scoping, a sound or smell doesn't have to place the main object
in scope, but rather can just put the intangible sensory emanation in
scope. For example, if an alarm clock is buzzing, the 'buzzing
sound' object would be in scope but not the alarm clock itself. (On
the other hand, some sounds, like a ringing phone, might be
sufficiently distinctive as to place the main object in scope. But
for those cases we can simply associate the sound/smell with the main
object.)
We need different odor/sound descriptions for different situations:
>listen to phone
It's ringing.
==> phone.soundDesc
>listen to ring [phone visible]
It's coming from the phone.
==> sound.soundWithSource
>listen to ring [phone not visible]
It sounds like a phone.
==> sound.soundWithoutSource
>look [phone visible]
...
The phone is ringing.
==> sound.soundHereWithSource
>look [phone not visible]
...
You can hear what sounds like a phone ringing.
==> sound.soundHereWithoutSource
Don't open a footnote when the PC isn't seeing the text with the
footnote reference.
Add sense emanations to inventory displays.
For sound/smell objects, add options to control ongoing announcements
of sensory emanations:
- always show on room description, or only show on
explicit "look" descriptions (and corresponding sense commands -
"smell", "listen")
- show every n clock ticks
- show every n clock ticks for m iterations, then go
to a secondary message every p clock ticks (for something like "the
phone is still ringing").
Room descriptions should be differentiated according to whether we're
explicitly looking or merely entering a new room.
When a noise's or odor's source is not visible, and the object is
examined (via "examine", or via "listen to" or "smell" or whatever),
it might be desirable in many cases to show the
apparent source,
which is to say the visually opaque obstructor, and describe the
sound/odor as coming from inside/outside/behind/whatever the
obstructor.
The obstructor can be easily found with
gActor.findOpaqueObstructor(sight, obj) (where 'obj' is the noise or
odor object). Once the obstructor is found, we'll have to generate
an appropriate message, which will require a new method parallel to
obs.cannotReachObject(obj) - perhaps we could call it
obs.describeSoundObstructor(obj) etc - which would display "The
ringing seems to be coming from inside the box" and the like.
This entire mechanism would not be used when the obstructor itself
cannot be seen, such as in the dark.
Consider:
>look
...
You can hear what sounds like a phone ringing.
>listen to ring
It sounds like a phone.
>x phone
You don't see any phone.
It would be better if that last line were:
>x phone
You can hear a phone ringing, but you can't see it.
To deal with this, adjust the touchObj and objVisible preconditions
so that it provides better feedback for an object that can be heard
but not seen.
Write a common main() that most games will use, with appropriate
hooks for showing the introductory text and so on. Or, perhaps
better, write a standard initialization routine that the game's
main() can call.
Provide an author-configurable option to process multiple orders to
NPC's synchronously - so, if you say "bob, go north, get all, go
south", the PC doesn't get a command line prompt again until Bob
finishes the whole set of commands.
It might be worth considering making the disambiguation responses for
&noMatchDisambig and &disambigOrdinalOutOfRange more interactive.
Currently, if you give an invalid response to a disambig question,
you get an error and no chance to retry. Something like this might
be more intuitive:
>take ball
Which ball, the red ball, or the green ball?
>third
There weren't that many choices - did you mean the red ball, or the
green ball?
>orange
That wasn't one of the choices - did you mean the red ball, or the
green ball?
However, if the response looks like a valid response but doesn't give
us a match, and it also looks syntactically like a valid new command,
treat it as a new command.
Generic script objects: an object for which we call a method automatically
each turn it's active. We'd keep a counter that we'd automatically advance
on each call. We should probably use this generic object to implement
text lists.
Generic text lists: an object that encapsulates a list of messages
to display, one per turn.
Text lists should be linkable to a common counter object, so that the
lists are synchronized on the same counter. This lets you have separate
lists for each room, but keep the timeline in each list the same.
It should be possible to associate a text list object with a room instead
of coding the room's text list in-line in the room.
Room text lists: a list of atmosphere messages we cycle through while
the player is in the room. This should be totally automatic, so you
just program the list and the library automatically sets up a daemon
to run through the messages.
When describing an object, show the special descriptions for any contents
of the object, just as we would list them in room descriptions. This
lets us see the special descriptions for contents, without having to
write any additional descriptive code in the container.
consider:
>take coin
Which coin, silver or gold?
We should probably keep the copper one in the list. In particular,
we probably shouldn't remove an item from consideration for
disambiguation just because it's less likely - once we've determined
that the noun phrase is ambiguous, we should offer all logical
matches, even when they're less likely.
consider:
>take coinc
Which coin...?
>take tricyc
The word 'tricyc' is not necessary in this story.
>take tricyc
Taken.
For some reason, truncated words are treated as misspellings when they
appear in disambiguation responses.
Can we cache sense information to speed up resolution a bit? In
particular, we should be able to cache sense information during the
noun phrase resolution and verification steps, since these do not
normally involve any game state changes; once execution begins,
though, caching should be turned off. (This is probably the optimal
set of trade-offs: on the one hand, the game and library should
never have to worry about cache invalidation - the library only needs
to turn caching on before starting the parsing phase and then turn
caching off before beginning the execution phase of a command; on
the other hand, we should derive substantial efficiency from caching
during the parsing phase, because this phase in particular tends to
evaluate a lot of sense information.
(Experimental code has been added, conditionally compiled based on
the SENSE_CACHE macro. We'll leave it on for the time being to get
some experience with it to see how well it works.)
Preparsing
Just-out-of-reach containers. Create a container type that puts its
contents in the distance for 'touch' purposes. This can be used for
things like items on the ceiling. We'll need a way to make this vary
depending on the source object, so that, for example, something could
be out of reach for an actor standing in the room but in reach if the
actor stands on a desk.
When using 'again' with a command that had an interactive
disambiguation response, the response 'that wasn't one of the
choices' is not appropriate since the player doesn't get to respond.
cache touch paths (and canTouch information) the same way we cache
sense paths
For OutOfReach containers, we shouldn't be able to touch the
container itself, since not only the contents but the container
itself is meant to be out of reach. To do this, add a PathTo
traversal for the last item in a path (and while we're at it, add the
symmetrical PathFrom for the first item in the path), and in
OutOfReach's checkTouchViaPath, treat PathTo the same as PathIn.
'down' from a platform should be 'get off platform' by default
add 'debug' verb (to break into the debugger)
Real-time events: on save/restore, adjust system clock basis to keep
everything in sync
Real-time events: design a class for the events
Real-time events: integrate with the scheduler and command reader
Real-time events: when an interruption occurs, catch any output and
automatically cancel the interrupted input
Fix problem with quoted strings in literal phrases (as in 'type
"hello" on typewriter')
Fix problem with 'throw': it's illogical to throw something at an
object within the object being thrown
Change all of the exclusion list phrases to terminalNounPhrase rules -
otherwise, when they exclude everything, they get misinterpreted as
((all but x and y) and z) which turns into (z and z). Everything with
a 'but' should turn into a terminalNounPhrase, just like the plain
'all but <list>' rule.
Make 'me' symmetric with 'yourself' in the grammar. In particular,
'me' should refer to the issuing actor, which is not necessarily the
player character.
fix problem with "yourself, jump"
For chairs and the like, we might want to consider an object owned
by an actor if the actor is occupying the object (so, "bob's chair"
is the chair bob is sitting on).
When taking an item, reduce the likelihood that an item being carried
by another actor is the one being taken.
Provide a way of distinguishing lit and unlit equivalents in a list.
Likewise for worn and unworn objects. Should probably add another
list group object after the equivalent grouper, and in this object
we should provide one group for lit objects and another for unlit.
The desired output is something like this:
You are carrying three matches (one lit), two flashlights (one
providing light).
Move the inheritNext into the behavior as the default native 'inherit'
instruction. Remove inheritNext from library source files.
the dagger shouldn't show up in the 'x desk' description when the
dagger's initial message is still being shown
Add macros to make grammar rules for predicates easier to read.
Improve the module names (adv3g -> parser.t, adv3v -> verbs.t, etc)
provide .t3m, .tdc for sample game with proper directory set-up
accept 'all <adj>' and the like
build the #include file list in Workbench automatically when creating
a project, and when explicitly asked via a new command "scan source
files for #include"
compiler: warn on finding a backslash outside quoted text in front of
anything but a newline
Add a simpler, more structured way to add a single paragraph break.
Use a new pseudo-tag, "<.p>", to indicate a paragraph break; process
this tag just before writing output to the console.
All library input functions should coordinate with command reports to
turn off output capturing.
Add a finishGame() function offering restart/restore/undo/quit options
Build a proper set of Workbench sample games
Freeze the real-time clock while we're waiting for inputFile results,
while we're saving/restoring a file, and while we're waiting for an
inputLine when real-time events are not allowed. All of these should
be considered to be outside the normal real-time flow of the game,
so none of them should consume any game real-time.
In showList: mix groups and singles in the original order of lst,
rather than displaying groups and then singles separately. This is
important because we're not preserving the original ordering when
the list is sorted and it has subgroups. We can't guarantee
that we'll preserve the sorting order, of course, since grouping
could put together items that weren't consecutive in the sorting
order, so let grouping prevail when there's a conflict; but when
possible, keep the order the same. To do this, we should have
a single loop that traverses the original lst; for each item, if
it's a single, show the single, otherwise show its group. Only
show the group for the first item in the group; once we match a
group, mark the group as used so we don't show it again.
'+' should consistently concatenate individual items from a
collection on the right-hand side, whether the right-hand side is a
list, vector, or array
'+' and '-' should work with an Array as the left-hand side
Delete class Array. We'll have to change the function object stuff
to use Vector instead of Array.
Make Vector+val and Vector-val return new objects - do not modify the
original vector in place
add Vector.appendAll() - appends list elements in-place; maybe
Vector.removeValue() and Vector.removeAllValues() to do '-' in-place
LookupTable.forEach: remove the 'key' argument to make its interface
the same as everything else's.
Everything with a forEach: add a forEachAssoc(key, val) method alongside
forEach.
for Openable, if the object isn't transparent looking in, implicitly
open the object for "look in"
Iterators: add getCurVal and getCurKey methods.
Fix bug: 'listen' or 'smell' shows no response when there are things
that nominally have Noise/Odor associations, but none of the Noise/Odor
objects actually has anything to say at the moment.
Dictionary: add a way to iterate over the entries in the dictionary.
For inputManager.inputLineBegin and inputLineEnd, use a new style tag
"<.inputline>" rather than coding "" directly.
This makes it easier for a game to customize the input font setting.
Add access to the "restore code" (the second argument passed to
restoreGame()) for PostRestoreObject instances by adding the value as
a class property of PostRestoreObject.
Change the Thing template that ends with "desc" @location to end
instead with @location "desc" (i.e., reverse the desc and location
property order). Since "desc" can be lengthy, putting the location
first will make the template more readable.
Add before/after command separation messages in libMessages, to allow
for finer-grained control over the formatting of command responses.
Check dark travel before checking any barriers - move the call to
gActor.checkDarkTravel into the TravelConnector methods that call
gActor.travelTo, since this must be called as a separate phase before
the travel connectors do any of their other work.
Make Illogical() the most illogical ranking, and make IllogicalNow()
slightly less illogical (i.e., invert the old ordering of Illogical
and IllogicalNow).
Add Action.setMessageParam(), to allow message processors to set
their own special message parameters that they can use in expansion
text. This would be better than constructing messages partially with
the "{it obj/him}" mechanism and brute-force string concatenation,
because it would allow the concatenated bits to participate in the
same processing steps, such as reflexive pronoun conversions.
Add a macro (gMessageParams) to simplify the syntax for adding new
message parameters.
"x me" doesn't work in dark (target actor of a command should always
be in scope)
Remove 'actorResolved' check from execCommandTokens - this check is
vestigial (it stopped being necessary when we started pulling out the
actor phrase after resolution and re-parsing the rest of the command),
and makes it impossible to have multiple levels of indirection, as in
"tell bob to tell bill to go north".
Refactor FirstCommandProdWithActor (parser.t) to move the actor resolution
stuff into a separate CommandProdWithActor that FCPWA inherits from.
>bob, get ball
Which ball?
>z
Bob waits...
The "z" should have a default target of the player character, not Bob.
Allow things like "tell bill to tell bob to go north". In particular,
fix execCommandTokens so that it doesn't assume that only one actor can
be resolved per command.
Keep first-on-line information in the PendingCommandInfo. (This requires
changing the interfaces to all of the Actor routines that create pending
command info objects: addPendingCommand, addFirstPendingCommand,
addPendingAction, addFirstPendingAction.)
Add a noun-list grammar rule for singleNoun, so that if we match a
noun list in a place where a single noun is grammatically required,
we can generate an appropriate message rather than failing to match
grammar.
Symptoms fixed: "ask bob and bill about box" -> "you see no bob and
bill here"; "give coins to bob and bill" -> "you see no coins to here".
Add a point-of-view argument to BasicLocation.lookAround, so that a
room can be described from a point of view other than the actor doing
the looking.
Fix: paragraph break in response to getting out of white box while
the box is closed (in the platform room). (The last report -
"Okay..." - is displayed on the same line as the beeper noise.)
Can we make every kind of noun phrase production derive from a common
NounPhraseProd base class?
Allow multiple actors per command line, tads2-style, as in "bob, go
north. look. bill, go south." (In tads 2, a new actor can be
specified after a period.) However, only allow this in "synchronous"
mode, where the issuing actor waits for all commands to NPC's to
complete before taking another turn; in asynchronous mode, do not
allow mid-command target actor changes, because it's too confusing.
Fix problem in touchObj precondition: we assume that we can find a
valid touch path, so we traverse the path without checking to see if
we got a nil path. In some cases (such as when the target object is
in a location not sharing any common container with the actor), there
is no touch path at all, so we need to deal properly with this
possibility.
Fix visual command separation for 'undo' after a multi-command line,
and for a multi-undo command line.
Modify/replace/delete grammar rules. Add new syntax:
modify grammar production(tag): :
;
replace grammar production(tag): :
;
To make this workable, the "production(tag)" names of grammar match
objects must be unique; change the compiler to enforce uniqueness of
these names.
Also, change VerbRule to include a tag: VerbRule(tag). This will
allow modify/replace to be used with VerbRule(tag) definitions to
replace library verb grammar rules.
Add a mechanism for remapping a two-noun-phrase action to a different
action after resolving first object. For example, allow mapping
ATTACK x WITH THROWING STAR to THROW THROWING STAR AT x.
To do this, add a new pair of properties to TIAction: remapDobjProp
and remapIobjProp; define these properties using the same template
style as the rest of the properties in DefineTIAction. Add a new
remapTIAction() macro that can be used from within a remap() method
to remap a command.
Note that only the first-resolved object can be remapped, because the
whole point is to effect the remapping before resolving the
second-resolved object, so that the second-resolved object can be
resolved using the rules of the new action rather than of the
original action.
In BasicLocation.lookAround, remove the point-of-view object from
the list of objects to be described, rather than removing the actor.
Move the grammar convenience macros (VerbRule, SingleDobj, etc) out
of adv3.h and into us_eng.h - these are all specific to the English
verb grammar, so they should be in the English-specific definitions
rather than the language-independent definitions.
If a Daemon is created with an interval of zero or less, it'll put
the scheduler into an infinite loop invoking the daemon, because the
daemon will be permanently schedulable. Force the interval to at
least 1 on creation.
Add a lookAround() method to Thing, to generate a description from
the point of view of the Thing. This is necessary to allow things
like remote cameras. This method should call the container's
lookAroundPov() method (which must also be added to Thing) with the
point of view set self; this algorithm will eventually reach the
innermost room-like object, which will display the appropriate
room-like description.
Add a PAUSE command, to stop the real-time clock for games that
use real-time events.
Add a grammar notation that specifies a match for one of several
vocabulary properties. With the '<prod>' grammar that was being
kicked around for a while, the notation '
' was
appealing, but without the angle brackets, it's not obvious what the
equivalent syntax would be.
Use angle-bracket notation, even though we don't use it for
individual tokens: <nounM nounN nounF>
Add an explicit 'grammar' declaration with no matchable rules, so that
production names can be declared without actually creating rules for
them.
make 'inherited' after 'delegated' more consistent with the regular
inheritance behavior (in particular, the relatively new dynamic
inheritance search algorithm will fail to find a class related to
'self' in the delegatee's superclass tree, so there will be nothing
to inherit; ideally, we'd have an 'inheritance target' in the stack
frame in addition to 'self')
NestedRoom.getTravelConnector refers to gActor. The reason the method
looks at gActor is that it wants to check if the actor can see the
containing room, so that it can get the connector from the containing
room. Add an 'actor' parameter to getTravelConnector(), so that we
can specify an actor explicitly and thus ask for travel connectors
outside of turn execution contexts.
SouthwestAction is missing from action.t
noTravelIn, noTravelOut, noTravelDown - these need to override
isConnectorApparent to return nil.
Add redirectTravelIn, etc, which do show isConnectorApparent = true.
Add newActionObj alongside _newAction: the new function takes a
pre-created action instance, rather than creating a new instance
itself.
Add a getBestMatch() method to ResolvedTopic to retrieve the
top-ranking object. By default, provide an implementation that simply
returns an arbitrary object from the strongest match list. Games and
library extensions could customize this to use a different topic
picker mechanism as desired, but providing a decent default
implementation would help authors get started without having to worry
about coding their own topic framework if they didn't want anything
specific here.
Add a PreRestartObject, analogous to PreSaveObject, to allow any special
actions to be performed before a restart.
execCommandTokens should probably switch the sense context upon
discovering a target actor different from the issuing actor, before
actually starting to execute the command.
Move all of the output management into discrete OutputStream objects.
Associate filters, notifiers, capturers with individual streams. By
default, create one stream for the main text and another for the
status line.
NOTE: This change affects all of the notifier, filter, monitor, and
capturer function interfaces. All of these formerly global functions
are now methods of the OutputStream class.
Regarding touch path calculation (see e.g. touchObj precondition):
should we be finding a touch path to an object in a separate location
not connected by containment, but connected by a SenseConnector?
Fix basicScoreChange reporting for changes of 1 (or -1) points: use
singular "point".
change the T3 Workbench .tdc format to use .t3m files
vmrun: respond to Ctrl+Break to break into debugger
"parse-debug" command: accept the command without the "on" or "off"
specifier, and simply invert the current mode.
Add a nounMultiList production alongside nounList. Use this
production in matching singleNoun, rather than using the regular
nounList, so that we don't create spurious additional matches for the
degenerate single noun phrase form of nounList.
For bags of holding, check (in Container.tryPuttingObjInBag) to make
sure that the object being moved into the bag actually fits, and
don't even try if it won't. This will avoid spurious attempts and
failures to move things in to a bag of holding implicitly after the
bag becomes full.
Refactor the CommandProd-based productions in parser.t to move the
methods into new classes, and base the defined grammars on the
classes. This will facilitate adding new commandPhrase grammar rules
by allowing the existing behavior to be re-used via inheritance.
Add a new Actor property (revertTargetActorAtEndOfSentence) that, when
set, tells the parser to consider target actor designations ("bob, go
north...") in effect only until the end of a sentence. When the
property is set to true, switch the target actor back to the original
issuing actor at the end of each sentence.
As a convenience, add a new StyleTag subclass, HtmlStyleTag, for tags
with different renderings in HTML and plain text modes.
Add some new style tags:
- <.a> - for <a> text (and use it for
all links we generate)
- <.statusroom> - for the room name portion of
the status line
- <.statusscore> - for the score/turn count
portion of the status line
Add inputManager methods getKey() and getEvent() that provide covers
for inputKey() and inputEvent(), respectively, the integrate with the
real-time manager and the reports gatherer in the same manner as
inputLine.
Don't use [badness] to reduce the priority of ordinal lists in
disambiguation responses; instead, use the ranking mechanism to give
ordinals a lower ranking than noun/adjective interpretations.
Fix looping problem with answering 'g' to disambiguation queries.
(Don't store the response to a disambiguation query until after we
actually know that the input is an answer to the query, as opposed to
a new command.)
Do not notify sense path in moveInto() when traveling. (Travel uses
the separate TravelConnector mechanism, which doesn't necessarily map
directly onto sense connections; we don't want to attempt to notify
sense connections of travel because we don't actually use them for
travel.)
When disambiguating interactively, if the response isn't in the full
list, check to see if it's in the full scope list. This allows for
cases where the player really wants to try a command on something
that we decide isn't logical - it's better to let the player try than
to deny that the object is among the possible choices.
We should test the scope list as a second pass rather than allowing
the full scope on the first pass. So, we should try first as we do
now, limiting scope to the narrowed ("logical") list; then, if that
fails, we should try again with the full scope list.
Add an equivalent of the tads 2 "->" syntax: this syntax allowed
routing messages for one object to another object. For example, if
we wanted to route "open desk" to "open drawer", we would put "doOpen
-> drawer" in the desk. Define a new macro:
dobjForwardTo(Open, drawer)
This is used in place of a dobjFor().
(This was from Phil Lewis, who suggested "reroute" as the name and
also suggested a slightly more general format: dobjReroute(drawer,
Open). It might also be useful to be able to specify the verb to
forward separately, so that you could handle a verb with both a
different verb and different target object, but the action is
probably the same in most cases, so it seems more convenient to be
able to omit it.)
In roomSmellLister (and roomListenLister), add a custom isListed
method that checks a new property of the target object,
isSmellListedInRoom (isSoundListedInRoom in the case of Noise
objects). Set these properties to true by default. Add an isListed
override to smellActionLister (and listenActionLister) that simply
returns true. This change allows an Odor/Noise to list in response
to a >SMELL or >LISTEN without also showing in the normal room
description, as it would by default.
Propagate the results of an action up to callers by returning the
CommandReportList. Actually, we'll have to return a list of
CommandReportList objects, since one CommandReportList is generated
per iteration for an iterated action. The aggregate list could be
stored in a CommandResults object, which could provide high-level
analysis methods (e.g., isFailure()) to interpret the action results.
To do this, return a CommandResults object describing the command
reports list from newAction(), nestedAction(), and so on.
Provide a way of specifying a subclass of CommandReportList to use
when executing a new or nested command. To do this, callers use
a new function that takes a particular CommandReportList subclass
to use while calling a callback:
result = withCommandReportsClass(
MyCommandReportList, {: nestedActorAction(bob, SitOn, chair) });
In the tokenizer, if a word with a "'s" suffix explicitly appears in
the dictionary with the "'s", do NOT make the "'s" into a separate
token - keep the whole string with the "'s" as a single token. This
allows for cases where the grammar has a literal token with a "'s"
suffix, and where vocabulary words (such as adjectives) are
explicitly defined with "'s" suffixes.
In TravelConnector, allow for a single barrier rather than a list
of barriers. If the travelBarrier property is not a Collection of
some kind, treat it as a single TravelBarrier.
Add a OneWayRoomConnector to make it easier to define a connector
that connects one room to another but not vice versa.f
Add checkTravelConditions() to TravelConnector. Call this from
checkTravelBarriers(). By default, this does nothing; instances
can override it to apply special conditions to the travel in lieu
of creating a separate TravelBarrier object when the conditions
don't need to be re-used in other connectors.
Fix indentation problem for grouped items in INVENTORY TALL lists.
Hide intangibles from 'all' by default. Noises and odors should
probably be included in 'all' for LISTEN/SMELL.
When trying to put objects into a bag of holding to make room to take
a new object, don't even try moving an object that contains the bag
of holding (because doing so will just fail with a message "the is already in that").
Need a better way to say "the one in the room" than showing the room
name, because that's not useful in input. Should say something
like "the match on the floor".
Once everything in a disambiguation list is a basic equivalent, we
could use the 'name' of the objects to display the prompt, rather
than using the input text.
In Thing.initializeEquivalent, deal with the possibility that we have
a base class with a separate equivalent grouper. Create a separate
grouper for the subclass by testing to see if equivalentGrouper is
defined directly by the class object. If there is an inherited
grouper, we must create our own separate grouper for the subclass,
AND we must take the superclass grouper out of the listWith for the
subclass (since it will inherit the base class listWith by default).
In action.t around line 1532, when we're adding the UnclearDisambig
flag, we might want to suppress the flag if the object we selected
and the objects we rejected are basic equivalents of one another.
This avoids weird situations such as
>light match
(the match)
when we have one match we're holding and another in the matchbook:
we choose the one we're holding over the one in the matchbook because
of the must-be-holding precondition to lighting a match, but mentioning
which one we're choosing is weird because it doesn't tell us anything.
>drop match
Which match, one of your matches, or the match in the matchbook?
>my match
Which match, one of your matches, or the match in the matchbook?
...etc. The problem is that we're taking "my match" to mean any
match in my possession for parsing purposes, whereas we differentiate
holding from indirectly owned. Ideally, we'd prefer to treat "my
match" as "the match I'm holding directly" when it's ambiguous.
Add a disambigName property, and use it instead of the ordinary name
when generating disambiguation prompts. (We'll need aDisambigName,
theDisambigName, and countDisambigName as well to round out the set.)
Add "x in y" grammar.
Add a mechanism for distinguishing equivalents with different states:
Which candle to you mean, the lit candle or the unlit candle?
-but not-
Which wax do you mean, the unlit candle or the seal?
-which should just be
Which wax do you mean, the candle or the seal?
Use this mechanism to create a base class for light sources. We
can use this for things like matches and candles. The disambig
name for an object is "lit x" or "unlit x", so we add the adjective
"lit" or "unlit" according to state. Check the adjective in parseName.
Use this same mechanism to specify by owner when owner is a
distinguishing factor - "which gas mask do you mean, yours, or
teeterwaller's?"
To do this, associate with each object a list of "distinguisher"
objects. A candle might include the "lit" distinguisher object, and
probably all objects would include the "owner" distinguisher. We'd
make a list of all of the distinguishables in common to the set of
equivalents, then run through the common set. For each one, we'd ask
the distinguisher if the list of equivalents we have is
distinguishable via this distinguisher. The "lit" distinguisher
would return true if the objects were all in different "lit" states,
and the "owner" distinguisher would return true if all had different
owners. We'd stop at the first distinguisher that can tell all of
the objects apart. The distinguisher would give us the method to
call in each of the objects to list its distinguishing name - the
"lit" distinguisher would call the litName property, for example,
which would display "the lit candle" or "the unlit candle"; the
"owner" distinguisher would display "yours" or "teeterwaller's".
The response would have to be handled by the normal disambiguation
response mechanism, so the objects would have to conspire with the
distinguisher to have the proper adjectives. This is easy, though,
because the objects are the ones displaying the adjectives in the
first place when they show the disambiguation list.
If we find no distinguisher that can tell all of the items apart,
we'd look for one that can tell at least some of the items apart, and
phrase it like this: "the lit candle, or one of the unlit candles".
If they select one of the ones indistinguishable with this
distinguisher, we'd iterate, and maybe pick it up with a separate
distinguisher next time.
If none of the distinguishers can tell any of the objects apart, we'd
simply choose one arbitrarily like we do now.
>take candle
Which candle do you mean, the lit candle, or one of the unlit candles?
>unlit
Which do you mean, your candle, or one of Bob's?
>bob's
(arbitrarily picking one of bob's several unlit candles)
Taken.
Note that for ownership distinctions, need to offer a way to refer to
unowned objects:
>get candle
Which candle do you mean, Bob's, or the other one?
-or- Which candle do you mean, Bob's, or another one?
[responses would include...]
>another / another one / another gold coin / one of the others
/ one of the other gold coins / other / the other / the other one
/ the other gold coin / other gold coin
Hide actors from 'all' for all verbs by default.
For "put in" and "put on", use roughly the same strategy as "take"
for generating the "all" list: include only objects that are directly
in the actor's location, or in fixed objects in the actor's location,
as "take" does, but also include objects that the actor is directly
holding. Keep the same iobj/in-iobj exclusions.
Break up TravelConnector.checkTravelConditions() into two separate
methods (canTravelerPass(traveler) and explainTravelBarrier(traveler)),
just like TravelBarrier.
Fix Thing.normalizePath() so that it makes two separate passes over
the list. It should apply the PathThrough tranformation first, then
the PathPeer transformation as a second pass. (Performing both
transformations both on a single pass can cause PathThrough
transformations to be missed, because the PathPeer changes the list in
such a way that the PathThrough transformation doesn't recognize a
needed change. The PathThrough condition could alternatively be
modified to recognize PathPeer operations, but it's easier to do
things in two passes. Note that doing PathThrough first is safe,
because it only adds to the list - it never drops any operations that
the PathPeer transformation looks at.)
Add distance differentiation for 'examine' descriptions (and for
'listen') and 'smell' descriptions as well). Specifically, add
distantDesc and obscuredDesc, and call them from basicExamine
according to the current point of view. Add corresponding methods for
'listen' and 'smell' descriptions.
Add automatic state listing to the equivalent grouper. Use a new
Thing method, getState, that returns a ThingState object. Use this
object to group equivalents by state and to show the state of each
item.
Use the ThingState object to provide the ordinary status description
of an object.
Use ThingState to provide automatic matchName() filtering by state.
Add a property to ThingState, stateTokens, that lists tokens that
can only be used in noun phrases referring to an object in the state.
Integrate Steve's exit lister package into the library.
It would be nice to have a sorting order for the directions, so we
list in a more canonical order: north, south, east, west, northeast,
northwest, southeast, southwest, up, down, in, out.
When two exits have the same destination, list them together: "south
(or out) to the front yard".
Don't show exits to dark rooms in the dark.
In listing exits from a dark room, maybe we should show exits leading
to rooms with light, since travel would be allowed to those locations.
Change the way we figure out if travel from a dark room to an
adjoining lit room is possible. Add a separate TravelConnector
method that specifically determines if the connector itself is
visible to a given actor in a given origin location when the origin
location is dark. By default, make a connector visible from a dark
location when the destination room is lit. This will provide the
customary behavior (i.e., dark-to-dark travel is prohibited, but
dark-to-light is allowed on the theory that some of the light from
the destination leaks through to the origin, making the connection
itself self-illuminating and thus visible even in the dark origin
room), while more clearly articulating the rule by making the
visibility of the connector the explicit factor in determining
whether or not the travel is allowed, and also providing a clean way
to override the default heuristic for visibility of the connector for
particular connections that should use a different rule.
Add an option to show available exits in the status line (using
a terse display that just lists the direction names).
Make Surface.PutOn behave like Container.PutIn with respect resolving
the indirect object using the tentative direct object list: if
everything in the tentative direct object match list is already on
the indirect object, flag the indirect object as illogical.
Sense Event Model: Create a new class, SensoryEvent, that can be used
to trigger transient sensory events that actively notify interested
observers of their occurrence.
Add convenience macros for remapTIAction that define the full remapping
for a dobj or iobj to another action, with the same or reversed noun
phrase order:
dobjRemapTI(OpenWith, UnlockWith)
dobjRemapTIReverse(FillWith, PutIn)
dobjRemapTIReverse(ThrowAt, AttackWith)
examineSpecialContents should probably differentiate according to POV
and sight viewing conditions.
Change CommandReportList to a SimpleOutputCapturer. We don't actually
want to process any notifiers or monitors when capturing report results,
because we turn them into a report list that can be reordered. We want
to wait until we actually display the reports to notify anything of the
output.
>i
You are carrying three matches (one lit), and a matchbook (which
contains two matches).
>x match
Which match do you mean, an unlit match, or the lit match?
>an unlit match
(the match)
The match is an ordinary match.
It would be better not to make an arbitrary choice here, unless they
explicitly said "any unlit match". We should have another go at
asking for detail in this case.
Add TITLEs to <A HREF>'s in system messages.
Exit lister: if we have multiple exits from a location with nil
destination names, we are incorrectly grouping them as though they
had the same destination name. The result is that we only see one
such exit in an EXITS list. Destinations with nil destination names
should be treated as unknown destinations.
Sort adv3.tl alphabetically (except for 'modify' order dependencies).
Add Dan Schmidt's hyphen-converting output filter to output.c
Add Dan's Thing.canBeSeenBy, Thing.canBeSeen, etc. (These are
convenience methods, especially for canBeSeen and its like, which
operate on the player character, saving a little typing by making
'gPlayerChar' implicit.)
Add Dan's Event.delayEvent, removeEvent methods
Add an event manager method that cancels an event based on the
obj/prop combination, to save work for cases where this uniquely
identifies the event (this way, the author doesn't have to go to the
extra trouble of saving the Event object reference if the event can
be uniquely identified without it).
Add a "mass" or "collective" noun property, for objects that refer to
collections of large numbers of smaller parts, or continuously-measured
quantities: "some popcorn", "some water". (Is there anything that this
changes besides changing aName to "some "? Should think about it.)
Add a lower-level base class for Switch, OnOffControl, that keeps
track of the on/off state and accepts "turn on" and "turn off".
Define the additional switch-specific verbs ("switch" and "flip")
only in the Switch subclass - this allows for adding on/off behavior
without making something look exactly like a light switch.
Add a GO BACK command to return to the most recent location (if the
connector in was reversible).
Keep track of the last "interlocutor" for each actor; this is actor
last addressed in a targeted command ("bob, go north"), or the last
actor in a conversational command - ASK ABOUT, TELL ABOUT, SHOW TO,
GIVE TO. For the conversational commands, if the actor is left out
(ASK ABOUT GOLD, TELL ABOUT STORM), use the performing actor's most
recent interlocutor as the default, if the interlocutor is still
within talking range.
Create a subclass of Actor, UntakeableActor, for an actor that can't
be taken or moved. Define an additional subclass, Person, to
represent human characters, and provide some custom "fixed" messages
for it.
Ensure that the SCORE and FOOTNOTE modules are completely modular,
so that they can simply be omitted from the build if not required.
Get rid of libMessages.openBracket and closeBracket; instead, add
a style tag, <.parser>...<./parser>.
Also, add <.notification>...<./notification> for notifications
(score changes, FOOTNOTE instructions, etc.).
Change withBrackets() to parserMessage(), for better documentary
effect.
Simplify the menagerie of output filters, notifiers, monitors,
and capturers, reducing everything to a single type (filter).
Enforce stacking of filters.
Get rid of the paragraph suppression immediately following input.
Instead, use the command sequencer to display the appropriate kind
of separation.
Rename CommandReportList to CommandTranscript, and rename
gCommandReports to gTranscript.
Add resolved object arguments to Actor.addPendingAction,
Actor.addFirstPendingAction.
Rearrange arguments to Actor methods addPendingCommand,
addPendingAction, addFirstPendingCommand, addFirstPendingAction to
move the firstInSentence flag first. This will keep everything
consistent with the rearrangement necessitated by adding the varargs
resolved object list to addPendingAction and addFirstPendingAction.
When a command is applied to several objects, and processing the
command for some of the objects involves implied subcommands, set
off the results for the objects with implied subcommands visually,
by putting an extra paragraph break before and after each result
for an object with implied subcommands:
PO Box: Taken.
red ball: Taken.
iron key:
(First putting the red ball in the duffel bag to make room)
Taken.
blue test booklet: Taken.
brass key:
(First putting the red book in the duffel bag to make room)
Taken.
gold coin: Taken.
gold coin: Taken.
Use a new style tag, <.commandsep>, for command separation,
eliminating the method call commandSequencer.startNewCommand().
Using a tag instead of a method call will work better with text that
doesn't reach the output stream immediately (for example, text that
is queued into a command transcript during command results display).
In Thing.construct(), initialize the new object's vocabulary words and
add them to the global dictionary.
Add information on the connector back, if known, to the EXITS display:
Obvious exits lead north, back to the east, and south to the den.
...north; east, back to the living room; and south, to the den.
For NPC's, the announcement/result format for implied commands should
probably be changed. In particular, we should probably just treat NPC
implied actions as full separate commands:
>bob, north
Bob opens the door.
Bob departs through the door.
For NPC's, don't allow interactive defaulting for an implied command.
For NPC's, if an implied command fails, we should fail with an indication
that the NPC needs to perform the implied command:
>bob s
Bob must open the door before he can do that.
>bob, open door
Bob must unlock the door before he can do that.
Get rid of gCommandReportClass and withCommandReportsClass. Instead,
add a parameter to _newAction() to specify the transcript class to
use.
Add a new BasicLocation method, getRoomPartLocation(part), which
returns the immediate container of the given room part, as perceived
in that location. Top-level rooms would generally just return 'self'
if they have the room part, nil if not. Nested rooms would generally
pass this up to their containers, because they don't have room parts
themselves in most cases. Nested rooms with their own room parts
would handle it themselves.
"x floor" - should list objects that are on the floor; likewise for
other room parts (walls, ceiling). Use a separate listing test
(isListedInRoomPart(part)) to allow objects to opt out of being
listed specifically in such cases.
For special descriptions, add a "room part location" property - this
is a purely advisory property used only for finding contents of the
floor and so on. When we "x floor", show special descriptions only
for objects whose roomPartLocation is the floor of interest.
Re-enable the transcript after prompting for interactive resolution
(reading an OOPS response, a disambiguation response, etc).
Add a method to Action to swap the roles of the tentative objects,
for use with the 'verify' forwarding for TIAction remapping. Add a
call to this method to the dobjRemapTIReverse/iobjRemapTIReverse
macros before they forward their 'verify' calls.
When an action is remapped, and we need to announce an object (such
as a defaulted object), announce the
entire action, not just the
object. For example:
>fill bucket
(dipping the bucket into the river)
>fill bucket
(pouring the coffee into the bucket)
This is important because the normal announcements are worded with the
intention of being tacked onto the end of what the player typed; when
the action is remapped, the action we're actually executing is different
from what the player typed, so phrase fragments that are intended to be
added to what the player typed are no long applicable.