Chatter: A TADS NPC Interaction Library
Version 1.0
Designed for use with TADS 2.5.1 or higher
Chatter webpage: http://www.igs.net/~tril/if/chatter/
Suzanne Britton, 1999, Public Domain
tril@igs.net


Welcome to Chatter, a feature-rich NPC Interaction Library for TADS. Chatter
boasts the following capabilities:

  * Topic-based ask/tell conversation, with disambiguation
  * "Conversation mode": just enter a topic to ask the selected character
    about it
  * Better implementation for the SAY verb, including SAY <x> to <y>
  * Topic-based information sources (e.g. dictionaries)
  * A system for answering yes/no questions posed by npc's to the player
  * Implementations for thanking, apologizing, greeting, and saying goodbye
    to npc's
  * Alternate syntaxes for giving commands to npc's
  * Various ways of asking npc's for help

All of these features are flexible and customizable to the utmost, especially
in syntax--there are often 4-5 different ways of phrasing the same thing. For
instance:

  * "ask john about the mysterious box"
  * "john, tell me about the mysterious box"
  * "john, what is the mysterious box?"
  * "show mysterious box to john"
  * "john, look at the mysterious box" (synonymous with "show")

By default, these will all point to the same response, although they can be
specialized.

Interested? Read on.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

USAGE

Chatter is an addendum to adv.t, rather than a complete replacement. It builds
upon and supersedes the example code asktell.t on GMD. You should #include
chatter.t after including adv.t. It sets #pragma C+ (C-style operators), but
reverts to #pragma C- at the end.

This library is suitable for both TADS newbies and experienced TADS programmers
It can be used "out of the box" with minimum fuss, or, for more experienced
programmers, customized to your heart's content. Of course, I'd appreciate a
blurb in your game's credits :-)

You must have a version of TADS 2.5.1 or higher for Chatter to function
properly.

The enclosed silly test/demo program, johndoe.t, and its accompanying script
johndoe.scr, are designed to put Chatter through its paces. If everything is
working, the output should be identical to the contents of johndoe.out.
If you find any problems (either via johndoe.t or another route) or have
any suggestions for improvement, please let me know.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

TOPICS

Topics are at the heart of Chatter. They are designed to run parallel to normal
game objects, so that any object/person/concept that you want the player to be
able to talk about should have a corresponding topic object with appropriate
sdesc, thedesc, isHim/isHer/isThem, and vocabulary (which should not
necessarily be identical to the game object's vocabulary). Also, the game
object should point to its topic via the property thing.mytopic. Topics have
two special properties of note. The first is "known", which indicates whether
the player has encountered that topic yet. Topics are unknown to begin with,
except for those which inherit from knownTopic instead of topic. The second
is "whatis" which is described later.

Topics are used as the target of "ask about", "ask for", "tell about",
"what/who is", "look up", and other verbs (such verbs are all derived from
ioTopicVerb [takes a topic as its indirect object] or doTopicVerb [takes a
topic as its direct object]). When the player types a command such as "ask jack
about the giant beanstalk", the parser proceeds as follows:

  * If "beanstalk" is not a recognized word, the parser will print the usual "I
    don't know that word" error message. I decided on this since it's often
    confusing to get a disinterested response to an important topic which the
    player misspelled.

  * If the words "giant" and "beanstalk" are both defined somewhere, but *never
    on the same object*, the special object badNounPhraseObj will be returned
    (the same thing happens in this circumstance for all verbs, in fact, not
    just ask/tell). This will generally cause the appropriate character's "I
    don't know about that" response to be printed.

  * If the noun phrase does match objects, but no *topic* objects, then the
    special object catchallNonTopic will be returned. This will generally cause
    the appropriate character's "I don't know about that" response to be
    printed. The player will never be asked to disambiguate in this case.

  * If the only topics that match are unknown topics, the parser prints out
    global.unknownMsg ("You don't know about that.") and aborts
    processing. Again, the player will not be asked to disambiguate.

  * If one or more known topics match, they are selected. The user will be
    prompted to choose one, if necessary.

What will happen here is that unknown topics and non-topics are automatically
removed from consideration. The player will never be asked to disambiguate
between a topic and a non-topic, or a known topic and an unknown topic. This is
very useful in a large game which might have 15 different "river" objects, for
example:

  >ask merlin about the river
  Which river do you mean, the river, the river, the river, the ...
  [arg!]

Topics are subtle and often tricky to use, and you may not get the hang of it
right away. A lot of the trickiness comes from the fact that an adjective or a
general noun can be enough to refer to a very specific topic, if no others
match the vocabulary. This is fine for objects clearly visible in the same room
as oneself, but with topics, it can cause confusion. Be careful of the
vocabulary you use, and be careful of unknown topics. For instance, this is
probably a bad idea:

  magicTree: topic
    sdesc = "magic tree"
    noun = 'tree'
    adjective = 'magic'
  ;

  [no more topics with the noun 'tree']

What this means is, if the player types "ask merlin about the tree", and
magicTree is still unknown, they will get the message "You don't know about
that." This isn't very reasonable if the player has been walking around in a
forest, for instance. Instead, make sure to include a general, known "forest"
topic:

  forestTopic: knownTopic
    sdesc = "forest"
    noun = 'forest' 'tree'
    plural = 'trees'
  ;

Now, if the player types "ask merlin about tree" before magicTree is known, the
game will assume they are referring to forestTopic. Otherwise, they will see.

  Which tree do you mean, the magic tree, or the forest?

Not ideal, perhaps, but better than the alternative. Another option you may
sometimes find suitable is to give the general topic an sdesc like "foobars (in
\(general\))" ("general" will be hilited), then put 'general' on the topic as
an adjective.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ASK/TELL CONVERSATION

Now that I've given some background on topics, I can go into ask/tell in more
detail. Chatter implements the following topic-based commands. Those which are
functionally identical in all but syntax are listed together.

  * ask zarf about <x>
    zarf, tell me about <x>
    zarf, what is/are <x>?
    zarf, who is/are <x>?
    zarf, who am I?  (same as "zarf, who is me?", but more grammatical)

    All three trigger a call to handleAskAbout on zarf, which will:

      1. Call zarf.helpResponse if io is helpTopic, otherwise --

      2. Call zarf.askTopics(topic, words), where topic is io and words is a
         list of the vocabulary words with which the player described the
         topic (avoid making use of words unless you absolutely must...they
         may not always be set correctly due to the eccentricities of TADS)
         This user-defined method should print out a message (and optionally
         do other stuff) and return true if Zarf has a response for that topic.
         Otherwise, it should print nothing and return nil, in which case --

      3. Call zarf.askDefault (the rough equivalent of the adv.t "disavow"
         property)

  * ask zarf for <x>
    zarf, give me <x>

    The latter depends on the mytopic property to translate <x> to its topic.
    Unfortunately, <x> must be in local-scope for this syntax to work (so
    no "zarf, give me help"). Both will trigger a call to handleAskFor on
    zarf, which will:

      1. Call zarf.helpResponse if io is helpTopic, otherwise --

      2. Call zarf.askForTopics(topic, words), where topic is io and words is
         a list of the vocabulary words with which the player described the
         topic.  This user-defined method should print out a message
         (and optionally do other stuff) and return true if Zarf has a
         response for that topic. Otherwise, it should print nothing and
         return nil, in which case --

      3. Check the user-defined property zarf.askForRedirect. If it is

           true   -- redirect the topic into an "ask about" query.
           nil    -- no topics will be redirected (the default)
           a list -- if the first element of the list is ALL_EXCEPT, all
                     topics except those in the list will be redirected.
                     Otherwise, only the topics in the list will be redirected

      4. If the topic was not caught by helpResponse, caught by askForTopics,
         or redirected, or if it was redirected but askTopics returned nil,
         then zarf.askForDefault is called to print out a "disavow"-type
         message.

  * tell zarf about <x>

    [Note: None of the below applies to "zarf, tell me about <x>"! This is
    only for cases where actor = Me. "zarf, tell me about <x>" is instead
    synonymous with "ask zarf about <x>".]

    This triggers a call to zarf.handleTell, which will:

      1. Call zarf.tellTopics(topic, words), where topic is io and words is a
         list of the vocabulary words with which the player described the
         topic.  This user-defined method should print out a message
         (and optionally do other stuff) and return true if Zarf has a
         response for that topic. Otherwise, it should print nothing and
         return nil, in which case --

      2. Check the user-defined property property zarf.tellRedirect. If it is

           true   -- redirect the topic into an "ask about" query.
           nil    -- no topics will be redirected (the default)
           a list -- if the first element of the list is ALL_EXCEPT, all
                     topics except those in the list will be redirected.
                     Otherwise, only the topics in the list will be redirected

      3. If tellTopics returned nil and no redirection occurred, or if
         redirection occurred but askTopics returned nil, zarf.tellDefault
         is called to print a "disavow"-type message.

  * show <x> to zarf
    zarf, look at <x>

    This is not strictly a topic-based command, but it may be redirected into
    a topic query. Both syntaxes trigger a call to ioShowTo, which will:

      1. Call the user-defined method customShow(obj) with the object shown.
         If your customShow handles the action, it should return true, and no
         more processing will occur. Otherwise it should return nil, in
         which case --

      2. If the shown object has a .mytopic property, ioShowTo will check the
         user-defined property self.showRedirect. If it is

           true   -- redirect the topic into an ask/tell query, checking first
                     tellTopics then askTopics for a conversational response
                     corresponding to the object's topic (this is the default)
           nil    -- no redirection will occur
           a list -- if the first element of the list is ALL_EXCEPT, all
                     objects except those whose topics are in the list will be
                     redirected. Otherwise, only objects whose topics are in
                     the list will be redirected

      3. If no special handling or redirection occurred, or if both tellTopics
         and askTopics returned nil, zarf.showDefault will be printed.

  * give <x> to zarf

    This is not strictly a topic-based command, but it may eventually be
    redirected into a topic query. It triggers a call to ioGiveTo, which
    will:

      1. Call the user-defined method customGive(obj) with the object given.
         If your customGive handles the action, it should return true, and no
         more processing will occur. Otherwise it should return nil, in
         which case --

      2. ioGiveTo will check the user-defined property self.giveRedirect. If
         it is

           true   -- Redirect the GIVE to a SHOW (call ioShowTo)
           nil    -- no redirection will occur (the default)
           a list -- if the first element of the list is ALL_EXCEPT, all
                     objects except those in the list will be redirected.
                     Otherwise, only objects in the list will be redirected.
                     Note that unlike other such redirection lists, this is
                     a list of game objects rather than topics.

      3. If no special handling or redirection occurred, giveDefault is
         printed out. If redirection occurred but ioShowTo had nothing
         interesting to do, showDefault is printed out.

  * look up <x> in book
    read about <x> in book
    consult book on <x>

    Unlike the preceding items, the target of these verbs is an information
    source (a book, a dictionary, etc.) rather than a character. The actual
    superclass is infoSource. All forms trigger a call to lookupTopics(topic,
    words) on the source. This user-defined method should print out a message
    and return true if there is an entry for that topic, otherwise return nil,
    in which case book.lookupDefault will be printed ("There's no entry for
    that topic.")

  * what is/are <x>?
    who is/are <x>?
    who am I?  (same as "who is me?")

    While a command like "zarf, who are you?" is automatically redirected into
    an "ask zarf about himself" query, the commands "who are you?" or "what is
    a tribble?", without any actor addressed, are considered to be directed at
    the parser itself. This allows you, the narrator, to answer the player's
    questions if you like. To do so, simply override the whatis property on the
    appropriate topic (pcTopic to answer "who am I?", youTopic to answer "who
    are you?"). The property should be a double-quoted string or a method
    outputting a double-quoted string. If the whatis property is not
    overridden, it will output a stock message ("You'll have to discover that
    for yourself", or, for youTopic, "You'll have to address that question to
    someone").

A final note: Some initial translation of the topic is done for all topic-based
verbs, usually by calling the utility function

    translateTopic(t, asked, translateSelfRefs, translateYou)

t is the original topic, asked is the character or information source for the
verb.

1. If t is a non-topic (this can usually only happen if the player used a
pronoun, e.g.  "ask boy about it") and it has a mytopic property, it is
replaced with its topic.

2. If translateSelfRefs is true, words like "himself" will be translated to the
character's mytopic, assuming gender and plurality match (e.g. "ask boy about
himself").

3. If translateYou is true, the words "you" and "yourself" will be translated
to the character's mytopic (e.g., "boy, who are you?" and "boy, tell me about
yourself").

Various verbs set these options as logical for their syntax.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

GIVING COMMANDS

Any of the following syntaxes may be used to give commands/requests to an
npc:

    zarf, <do something>
    tell zarf to <do something>
    ask zarf to <do something>

  When the player enters one of these forms, it causes a call to actorAction
  on zarf, which will

    1. Call the user-defined method zarf.customAction(v,d,p,i) to see if you
       want special handling for that command. customAction should return
       true if so, in which case no more handling will occur, otherwise it
       should return nil, in which case --

    2. actorAction will interpret and act upon various special commands
       and pseudo-commands (zarf, look at <x>; zarf, hello; zarf, thank you;
       etc.). These are all alternate syntaxes for methods of conversation
       described elsewhere in this document.

    3. If it doesn't find anything of interest, print out zarf.actionDefault.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

CONVERSATION MODE

As of version 0.05, Chatter implements a special "conversation mode". The
player can enter this mode by typing "talk to/with [npc]" at the
prompt. Thereafter, the player can ask that character about topics by typing
just "[topic]" or "ask [topic]", and tell the character about things by typing
"tell [topic]".  If the player types what does not appear to be a noun phrase,
parsing proceeds as usual. No-object verbs are given precedence over topics,
so, for instance, if the player types "east", and there is an "east passage"
topic, he will move east rather than asking about that topic. Of course, this
behavior can be overridden by explicitly typing "ask/tell east" or "east
passage".

Other verbs related to this mode are "talk off", to exit conversation mode,
and "talk help", to get help on using conversation mode.

If there are any instances in your game where you have more than one object for
the same character, you may want to override the "doubles" property on each
object. doubles is a list of objects which represent the same character as this
object. If the game finds that a conversation target is no longer present
(specifically, no longer reachable), it will check the doubles list, and if any
object in that list is currently present, it will quietly switch the
conversation target to the new object.

An extension of conversation mode is "consult mode". The player can type
"consult [information source]", without an indirect object, to enter into
consult mode with that source. Thereafter, a simple noun phrase entered on
the command line will be translated to "consult [source] on [noun]".
"consult help" gives a help message, and "consult off" exits.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

CONVERSATION RECORDING

Chatter keeps a record of all topics that have been asked about, asked for, and
told about on each npc. It will add a topic to the appropriate record (if it
isn't already there) whenever the user-defined method (askTopics, askForTopics,
or tellTopics) returns true for that topic (so asking and getting a stock
response doesn't count). You can retrieve this information via the following
methods:

* npc.askedAbout(topic)
  Returns true if the topic has been asked about, nil otherwise.
* npc.askedFor(topic)
  Returns true if the topic has been asked for, nil otherwise.
* npc.toldAbout(topic)
  Returns true if the topic has been told about, nil otherwise.

You can call these methods within askTopics and its kindred functions and
they will work properly, since the conversation record is not updated until
after askTopics/askForTopics/tellTopics has returned. One common use of this
feature might be to give a topic a different response when the player asks
about it a second time.

Note: If conversation redirection occurs, the topic will be recorded under
its final handler. For instance, if "tell john about the lantern" is
redirected to "ask john about the lantern", the lantern topic will be
recorded under askedAbout, not toldAbout.

An advanced feature allows you to assign a numeric status code when the player
can get various different responses when talking to an npc about the same
topic. In this case, you return the status code from
askTopics/tellTopics/askForTopics rather than "true" after printing the
response, and this information is stored and returned when you call
askedAbout/askedFor/toldAbout. This is not often necessary, but it's
available if you need it. If you never return status codes from your
conversation functions, you never need be concerned with them.

Here's a trivial example of how to use the feature:

#pragma C+

  askTopics(topic, words) = {
    if (topic == fooTopic) {
      local st = self.askedAbout(topic);
      if (st == 2) {
        "You've asked me about foo 3 or more times now.";
        return 2;
      }
      else if (st == 1) {
        "This is the second time you've asked me about foo.";
        return 2;
      }
      else {
        "This is the first time you've asked me about foo.";
        return 1;
      }
    }
    else
      return nil;
  }

Topics are recorded similarly on information sources. Call
[source].lookedUp(topic) to see whether a topic has been successfully
looked up by the player on that source, and/or to retrieve its status code.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

THE PREPARSE SEQUENCE

Chatter has a whole sequence of preparse manipulations it performs on the
user input string before it is passed to the TADS parser. The list of
manipulations is a list of function pointers kept in global.preparseSequence.
If you wish to modify the preparse sequence, simply modify that list. Remember
that the order of operations is important in some cases, so don't rearrange
the existing entries unless you know what you're doing.

Each function in the list must be of the following form:

   [function name]: function(tokens, len) {
     // blah blah
   }

Where tokens is the tokenized user input, and len is the number of tokens.
The function may return one of three types of values:

* true
  This function didn't make any changes. Continue with preparsing.
* nil
  This function found an error in the input and printed out an error message.
  Stop and return "nil" from preparse.
* a list
  This function made changes to the input and is returning the modified
  token list. Continue with preparsing.

preparse tokenizes the user input and passes it to each function in order.
If any function returns nil, it stops immediately and itself returns "nil".
If any function returns a list, preparse flags the input as changed and
passes the new token list to all subsequent functions. At the end, it
returns true if no changes were made, otherwise it de-tokenizes the final
list (using the Chatter utility function constructStringFromTokens) and returns
the resulting string.

Keep in mind that since all these functions are called from preparse (after
performing a parserTokenize() call) rather then preparseCmd, the token list may
contain multiple commands (separated by '.') and/or actor specifications (e.g.:
'john' ',' 'help' 'me'). Also keep in mind that your function must perform
all of its modifications in one go. If it is called a second time using the
modified token list that it returned, it should now return "true".

The current preparse sequence is as follows:

1. preparseValidateSay - returns nil if the user entered a SAY command that
   was not followed by a quoted string. This is much more effective than just
   putting a verDoSay on thing.

2. preparseRemoveComma - removes the comma from expressions like
   "hello, john" so that john will be interpretered as the direct object of
   helloVerb (also done for goodbye, thanks, and sorry).

3. preparseConvertAutoTopic - when in conversation mode, converts >[topic],
   >ask [topic], or >tell [topic] into a full conversational query. When in
   consult mode, converts >[topic] into "consult [source] on [topic]".

4. preparseConvertRequest - converts "ask/tell [person] to [do something]"
   to "[person], [do something]".

5. preparseConvertThem - converts "them/they/those" to "it" for cases look
   "look them up" and "john, what are they?". This circumvents a TADS glitch
   which would cause it to error out with "You can't use multiple objects
   with that verb" -- even if the player is only actually referring to a single
   object:

   >x tribbles
   There is a pile of chittering tribbles here.
   >john, what are they?

6. preparseConvertHer - If the word "her" is used as a possessive adjective,
   this function will convert it to "hers" so that it will match any
   object in scope that has "hers" as an adjective. However, it will skip
   this step if it doesn't find any objects in the game with "hers" as an
   adjective.

   Confusion can arise when the command is something like "give her money".
   Does this mean "give her money [to someone]", or "give money to her"?
   Chatter will assume the second unless it sees an indirect object clause
   (e.g. "give her money to john"). If you add new verbs that can behave this
   way (reversing do and io), you made need to change the preparseConvertHer
   function.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

NICETIES

It can add wonderfully to mimesis when characters have responses for things
like "merlin, hello" and "apologize to wounded troll". Thus the below
commands.

  * SAY

    SAY always takes a quoted string as a direct object (if the player
    does not provide a quoted string, preparseCmd will trap it and complain).
    When used without an indirect object, it generally just echoes back
    whatever the player wants to say:

    >SAY "echo"
    "echo"

    (This behavior can be altered by changing global.globalSayHandler(str))
    However, a few special strings, such as "thank you", are trapped and
    redirected, as explained below.

    If the player addresses their statement to an npc like this:

    >SAY "xyzzy" to dungeon master

    The npc's sayHandler(str) will usually be called (by default, sayHandler
    calls TellDefault), unless, again, the string is a special case like
    "thank you", in which case it will be redirected.

  * HELLO (or hi, greet, greetings), GOODBYE (or bye, good bye, farewell),
    THANKS (or thank, thank you), SORRY (or apologize)

    These are all handled in about the same way. Any of the below syntaxes
    are acceptable (using "thanks" as an example):

    >thank zaphod
    >thanks, zaphod
    >zaphod, thank you
    >say "thanks" to zaphod
    >thanks (the parser will prompt for an npc)
    >say "thanks" (ditto)

    The following response methods will be called on the npc in all cases:

      hello   -> helloResponse
      goodbye -> goodbyeResponse
      thanks  -> thankResponse
      sorry   -> sorryResponse

    By default, the parser will always prompt for an npc if none is specified.
    If you want the npc to be assumed under certain circumstances (for
    instance, if someone is expecting an apology from the player), you can
    override action(actor) on helloVerb, goodbyeVerb, thankVerb, and/or
    apologizeVerb, passing back to the original if no special handling is to be
    done after all.

  * HELP

    There is special handling for when the player asks an npc for help, by
    any of the below means:

    >ask npc about help
    >ask npc for help
    >npc, help
    >npc, help me
    >npc, help me out

    All of these will end up calling npc.helpResponse.

    Typing something like "help mom" will result in a stock response
    ("You'll have to be more specific about how to do that."). Typing
    just "help" should probably return instructions or hints for your game --
    to set this up, override helpVerb.action(actor) (making sure to call
    "abort" at the end, so no game time will pass).

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

YES/NO HANDLING

Chatter has a special system for handling player responses to npc yes/no
questions. I designed this system for my own game when the number of such
questions started to balloon (in response to playtester nagging :-), and
they started to overlap and interfere with one another. Though the library
code itself is a little complex, using it out of the box is dead simple.

The system is centered around the yesno superclass.  Any non-rhetorical yes/no
question (sometimes it's even fun to implement rhetorical ones) which may be
asked by an npc over the course of the game should have a corresponding yesno
object. The question itself generally occurs in the course of npc dialogue and
may be asked in multiple ways (and therefore is not defined inside the yesno
object), at which point the yesno object (which contains the npc's reactions to
"yes" and "no") is flagged as active.

Chatter keeps track of recently asked questions via global.lastYesnoAsked and
[npc].lastYesnoAsked. The former is overwritten anytime *any* new yes/no
question is posed; the latter is an npc-specific property which is set only
when that npc asks a question. When the player types "yes" or "no" at the
prompt (or 'say "yes"', 'say "no"'), Chatter will check
global.lastYesnoAsked. If, on the other hand, the player addresses themself to
a character ('npc, yes', 'yes, npc', or 'say "yes" to npc', lastYesnoAsked on
that npc will be checked.

Here's an overview of all yesno's special properties:

actor
  This is the npc who asked (or might ask) this question, the figurative
  "owner" of the yesno object. You must override this property.

yesText
  This is the text which will be printed out if the player answers
  "yes". It should be a double-quoted string, or a method which outputs
  a double-quoted string (it is perfectly acceptable for yesText to do
  other things besides printing a message). You should override this.

noText
  This is the text which will be printed out if the player answers
  "no". It should be a double-quoted string, or a method which outputs
  a double-quoted string (it is perfectly acceptable for noText to do
  other things besides printing a message). You should override this.

req
  This property/method will be called to determine whether this question
  can currently be answered. It is a default requirement that the npc be
  present (specifically, reachable--if this requirement is not met,
  global.globalYesnoDisavow will be printed), and that pc.canSpeak and
  npc.canInteract return true (see under "miscellaneous notes"). Any other
  requirements should be defined in req. If req fails (returns nil), the
  npc's yesnoDefault will be output, and it should probably explain why
  you can't currently answer the question.

  Default: true

expireTime
  This is the length of time, in ticks, before this question will
  automatically "expire"--i.e., the player will no longer be able to answer
  it. Set to nil for no time-based expiration.

  Default: 3

resetAfterAnswer
  If set to true, indicates that the question expires after it has been
  successfully answered, so that the player cannot answer it a second
  time (unless the npc poses it again).

  Default: true

The rest of the items are methods which you will rarely need to override.
Rather, they are called in user and/or library code:

flag
  When this method is called, it flags this question as the most
  recently asked, both globally and on the appropriate npc. If expireTime
  is non-nil, it then sets up a fuse to expire itself. You should call this
  on the appropriate yesno object whenever a non-rhetorical yes/no question is
  posed.

expire
  This method forces the question to expire. You may need to call it
  yourself if you have a question which neither time-expires, nor expires
  when answered.

process(val)
  This method is called when the player answers this yes/no question. val
  should be true if the answer was "yes", nil otherwise.  It checks
  requirements and makes sure the npc is still in the room. If all is well,
  it prints out the npc's response. If resetAfterAnswer is true, it then
  calls the expire method. You'll rarely need to call process(val) in your
  own code unless you implement a new way of answering questions.

All this may sound a little confusing, but in practice, it's quite simple.
I've handled all the inner workings for you. You'll usually only need to do
two things:

  1. Define a yesno object:

     trollQuestion: yesno
       // The troll asked if you've got the treasure he asked for.
       actor = troll
       yesText = "\"Hand it over, then!\" the troll roars."
       noText = "The troll roars and swings his axe at you."
     ;

  2. Flag the yes/no question when it is asked:

     theBridge: bridgeObject
       doCross(actor) = {
         if (troll.aintGotNoTreasure) {
           "\"Not so fast\", the troll growls. \"Did you bring me my
           treasure?\"";
           trollQuestion.flag;
         }
         else
           pass doCross;
       }
     [etc.]

Voila!

     >CROSS BRIDGE
     "Not so fast", the troll growls. "Did you bring me my treasure?"
     >NO
     The troll roars and swings his axe at you.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

EXTRA DOODADS

* This library defines parseAskObjIndirect in order to provide more
  intelligent io-prompting. As it is, in a large game, you will often see
  things like this:

  >attack mary
  What do you want to attack it[!] with?

  This is because, somewhere in your game, you have something that matches
  the vocabulary "mary" and is not flagged as female. Maybe it's "picture
  of Mary". Maybe it's "Mary's tennis shoes" (yes, truncated matches
  count). Because TADS does io-prompting *before* it disambiguations direct
  object, this will confuse it.

  parseAskObjIndirect puts the list of matching objects through several
  rounds of pruning to try to come up with a better guess. To help it out,
  you should

    + Apply isHim, isHer, and isThem as appropriate to ALL objects,
      including topics.
    + Put plural vocabulary under "plural" rather than "noun" wherever
      possible (when parseAskObjIndirect sees plural vocabulary, it knows
      beyond doubt that it should refer to the dobj as "them").

* Another enhancement to parseAskObjIndirect (as well as parseAskObjActor,
  for dobj-prompting) is that it will ask "who" rather than "what", where
  appropriate.

  >ASK
  Who do you want to ask?

  >DROP
  What do you want to drop?

  To help out, make sure to put "doNPC = true" on any verbs that usually take
  an npc as a direct object, and "ioNPC = true" on any verbs that usually take
  an npc as an indirect object.

* The properties basicMe.canSpeak and movableActor.canInteract(verbType) are
  designed to limit NPC interaction under certain circumstances. If canSpeak
  returns nil on the current Me, the player cannot speak at all, even to
  himself (however, he can give or show things to npc's). If
  canInteract(verbType) returns nil on an npc, the player's attempt to
  interact with that npc failed. The parameter verbType indicates whether
  the player is attempting to interact by speech (1), by showing something
  (2), or by giving something (3).

  If canSpeak or canInteract return nil, they should also print a message
  explaining why the speech or interaction failed ("Sheila doesn't even notice
  you with those headphones on.")

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

MISCELLANEOUS NOTES

* There is currently no facility for answering yes/no questions posed by the
  parser--but this is easily added if you want it. Simply create a global
  floating npc representing the narrator, and make all parser-questions
  owned by that npc (and now, if some snarky player tries something like
  "narrator, yes", it will actually work! ;-)

* As mentioned elsewhere, pcTopic is the player-character topic, with
  'me' 'myself' and 'self' as vocabulary. You may want to add vocabulary
  to pcTopic to correspond to your own game's pc.

* Chatter disallows questions like this:

  >ask john about hers
  >ask john about john's

  Chatter will respond with "please be more specific". This prevents the player
  from potentially getting a long list of loosely related topics that happen to
  have that possessive adjective -- i.e., from cheating. If for some reason you
  want to allow this, or you actually have words in your game that end in "'s"
  and aren't possessive adjectives, you will want to modify
  ioTopicVerb.disambigIobj.

* One of the keys to understanding the source code of chatter.t is that all
  actions with an actor other than Me (or, more accurately, parserGetMe())
  are handled in the npc's actorAction method. For instance, "joe, tell
  me about the bicycle". Such actions will be handled solely within
  actorAction (and any special methods that actorAction calls), since
  actorAction always exits. The parser sequence halts there. Therefore, the
  verDo/verIo and do/io methods in thing and movableActor only apply where
  actor = Me.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

HANDLERS, OPTIONS, AND DEFAULT RESPONSES

Below is a compact list of all npc-specific (that is, defined on movableActor
and inherited by all actors) conversation handlers, options, and default
responses which you may wish to override. They are in alphabetical order within
section.

Also note: if you wish to override any of these properties to set a new default
for *all actors*, you may do so by modifying movableActor itself (after
chatter.t has been #included):

modify movableActor
  askDefault = "\"I don't know much about that.\""
;

1. Handlers

These are methods which handle various statements and queries directed at
the npc.

* askTopics(topic, words)
    For: ASK ABOUT
    Params: topic - the topic which the player asked about
            words - list of vocabulary words used by the player for the topic
    Action: Print a response for the topic, if any
    Return: true if a response was printed, nil otherwise
    Default: Returns nil

* askForTopics(topic, words)
    For: ASK FOR
    Params: topic - the topic which the player asked for
            words - list of vocabulary words used by the player for the topic
    Action: Print a response for the topic, if any
    Return: true if a response was printed, nil otherwise
    Default: Returns nil

* customAction(v,d,p,i)
    For: actorAction commands
    Params: v,d,p,i - the command as passed to actorAction
    Action: Perform custom handling for the command, if any
    Return: true if custom handling was performed, nil otherwise
    Default: Returns nil

* customGive(obj)
    For: GIVE TO
    Params: obj - the object given
    Action: Perform custom handling of GIVE TO, if any
    Return: true if custom handling was performed, nil otherwise
    Default: Returns nil

* customShow(obj)
    For: SHOW TO
    Params: obj - the object shown
    Action: Perform custom handling of SHOW TO, if any
    Return: true if custom handling was performed, nil otherwise
    Default: Returns nil

* sayHandler(str)
    For: SAY TO
    Params: str - a single-quoted string representing a statement made to
                  the npc
    Action: Print a response for the statement
    Return: None
    Default: Calls tellDefault

* tellTopics(topic, words)
    For: TELL ABOUT
    Params: topic - the topic which the player told the npc about
            words - list of vocabulary words used by the player for the topic
    Action: Print a response for the topic, if any
    Return: true if a response was printed, nil otherwise
    Default: Returns nil

2. Options

These are various npc-specific options which customize the conversation
system. They can be either methods or static values.

* askForRedirect
    For: ASK FOR
    Controls: Whether otherwise unhandled topics are redirected from ASK FOR
              to ASK ABOUT
    Return: true   - to redirect all unhandled topics
            nil    - to redirect no topics
            a list - if the first element is ALL_EXCEPT, redirect all except
                     topics in the list. Otherwise, only topics in the list
    Default: nil

* canInteract(verbType)
    For: All NPC interactions handled in Chatter
    Controls: Whether the player can interact with this NPC at present,
              in the manner indicated by verbType
    Action: Print a failure message if returning nil
    Params: verbType - 1 for verbs involving speech, 2 for SHOW, 3 for GIVE
    Return: true if the player can interact with the NPC, nil otherwise
    Default: true

* giveRedirect
    For: GIVE TO
    Controls: Whether otherwise unhandled GIVE TO actions are redirected to
              SHOW TO
    Return: true   - to redirect all unhandled given objects
            nil    - to redirect no objects
            a list - if the first element is ALL_EXCEPT, redirect all except
                     objects in the list. Otherwise, only objects in the list
    Default: nil

* showRedirect
    For: SHOW TO
    Controls: Whether otherwise unhandled SHOW TO actions are redirected to
              ASK ABOUT / TELL ABOUT.
    Return: true   - to redirect all unhandled shown objects
            nil    - to redirect no objects
            a list - if the first element is ALL_EXCEPT, redirect all except
                     topics in the list. Otherwise, only topics in the list
    Default: true

* tellRedirect
    For: TELL ABOUT
    Controls: Whether otherwise unhandled topics are redirected from TELL ABOUT
              to ASK ABOUT
    Return: true   - to redirect all unhandled topics
            nil    - to redirect no topics
            a list - if the first element is ALL_EXCEPT, redirect all except
                     topics in the list. Otherwise, only topics in the list
    Default: nil

3. Default responses.

These include "I don't know about that"-type disavow responses, as well as
default responses to things like "thanks" and "goodbye". To get a truly
fleshed-out npc, you should usually override most or all of these. For some
less wordy characters, though, you may be able to rely on the redirections
and just define askDefault and a few others.

All of these should be either double-quoted strings, or methods outputting
double-quoted strings.

* actionDefault
    For: actorAction commands
    Default: tellDefault.

* askDefault
    For: ASK ABOUT (no response)
    Default: "There is no response."

* askForDefault
    For: ASK FOR (no response)
    Default: askDefault

* giveDefault
    For: GIVE TO (no reaction)
    Default: "He/she/it/they show/shows no interest in this."

* goodbyeResponse
    For: GOODBYE
    Default: tellDefault

* helloResponse
    For: HELLO
    Default: tellDefault

* helpResponse
    For: HELP
    Default: actionDefault

* showDefault
    For: SHOW TO (no reaction)
    Default: "He/she/it/they show/shows no interest in this."

* sorryResponse
    For: SORRY
    Default: tellDefault

* tellDefault
    For: TELL (no response)
    Default: askDefault

* thankResponse
    For: THANKS
    Default: tellDefault

* yesnoDefault
    For: YES or NO (no yes/no question was posed)
    Default: "He/she/they/it haven't/hasn't asked you a yes/no question."

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Well, that's about it. I have just one thing left to say: read the source
code! That's probably the only way you'll fully understand how Chatter works,
and it may give you ideas for your own code. My apologies for its somewhat
sparsely-commented state.
