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 to * 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 zarf, tell me about zarf, what is/are ? zarf, who is/are ? 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 zarf, give me The latter depends on the mytopic property to translate to its topic. Unfortunately, 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 [Note: None of the below applies to "zarf, tell me about "! This is only for cases where actor = Me. "zarf, tell me about " is instead synonymous with "ask zarf about ".] 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 to zarf zarf, look at 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 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 in book read about in book consult book on 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 ? who is/are ? 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, tell zarf to ask zarf to 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 ; 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.