SayQuery | [Main] [Next]

Overview
The SayQuery extension provides two new TopicEnty types: SayTopic and QueryTopic. Between them these offer much of the flexibility of SpecialTopic, but unlike SpecialTopic, they are not restricted to use with ConvNodes. Basically, you can use SayTopic to allow the player to make statements to NPCs beyond the basic TELL BOB ABOUT X, and QueryTopics to allow the player to ask questions beyond the basic ASK BOB ABOUT Y. Between them SayTopic and QueryTopic vastly expand the range of questoions that can be asked or statements that can be made. With SayTopic and QueryTopic you can design conversation exchanges like:
>TALK TO BOB
"Hello, Bob," you say.
"Hi there," he replies
(You can say that you've been to the beach, or ask when the troubles started)
>SAY THAT YOU'VE BEEN TO THE BEACH
"I've been to the beach," you say.
"Then you saw the lighthouse," he shudders.
>ASK WHEN THE TROUBLES STARTED
"When did the troubles start?"
"About five years ago," he tells you.
The problem with allowing this is that at least some players will attempt commands like I'VE BEEN TO THE BEACH or WHEN DID THE TROUBLES START?, especially since such commands might well be allowed with SpecialTopics, and the player can hardly be expected to know that you've used a different kind of TopicEntry here! Well, the good news is that, with a little care, you can set up your SayTopics and QueryTopics so that the following exchange would also work:
>TALK TO BOB
"Hello, Bob," you say.
"Hi there," he replies
(You can say that you've been to the beach, or ask when the troubles started)
>I'VE BEEN TO THE BEACH
"I've been to the beach," you say.
"Then you saw the lighthouse," he shudders.
>WHEN DID THE TROUBLES START?
"When did the troubles start?" you ask.
"About five years ago," he tells you.

SayTopic
+ SayTopic '(i\'ve|you\'ve|i have|you have) been to (the ){0,1}beach'
"<q>I've been to the beach,</q> you say.\b
<q>Then you saw the lighthouse,</q>" he shudders.
;
As an alternative to specifying a regular expression, you can specify a series of keywords in the keywordList property. These can be specified just like the keyword list of a SpecialTopic, except that SayTopic (and QueryTopic) gives you more control over how a keyword list is interpreted. In particular:
· You can define certain keywords as weak keywords by putting them in brackets (like weak tokens); weak keywords are allowed to be present in player input for a match, but a SayTopic won't match on weak keywords alone.
· If the allowDuplicateKeywords property of a SayTopic is nil (as it is by default), then a match will fail if non-weak keywords are repeated in player input (so you can prevent nonsense like "SAY BEACH BEACH BEACH" matching).
· If a SayTopic's enforceKeywordOrder property is true (as it is by default) then the non-weak keywords must appear in the same order in player input as they are specified in the keywordList if a match is to occur.If allowDuplicateKeywords is true then this property has no effect.
· The keywordsReqd holds the minimum number of non-weak keywords that must appear in player input for a match to occur. By default this is set to the number of non-weak keywords in the keywordList property, but you may often want to override this.
Using a keyword list you might specify the above SayTopic as:
+ SayTopic ['(i)', '(you)', '(i\'ve)', '(you\'ve)', 'have', 'been', 'to', '(the)', 'beach' ]
"<q>I've been to the beach,</q> you say.\b
<q>Then you saw the lighthouse,</q>" he shudders.
;

As a further sophistication, you can define alternative keywords using a vertical bar symbol (|) to split the alternatives. For example, the player might type SAY I HAVE BEEN TO THE BEACH or SAY YOU HAVE BEEN TO THE BEACH, but s/he shouldn't need to use both I and YOU in this say command. To indicate that I and YOU are alternatives here, we can define a token as 'i|you' or, in its weak version, '(i|you)'. The above SayTopic then becomes:

+ SayTopic ['(i|you)', '(i\'ve|you\'ve)', 'have', 'been', 'to', '(the)', 'beach' ]
"<q>I've been to the beach,</q> you say.\b
<q>Then you saw the lighthouse,</q>" he shudders.
;

(This assumes that your source file has included the SayQuery.h header file so you can use the SayQuery template to specify the keywordList property).

Note that you are not limited to two alternatives in this kind of case; you can have as many alternatives as you like: for example 'i|you|we|he|she|they' would be a perfectly legal token in the keywords list.

Since a player can hardly be expected to guess the availability and wording of a SayTopic, in practice you'll need to make any SayTopic a SuggestedSayTopic:
+ SayTopic, SuggestedSayTopic
['(i)', '(you)', '(i\'ve)', '(you\'ve)', 'been', 'to', '(the)', 'beach' ]
"<q>I've been to the beach,</q> you say.\b
<q>Then you saw the lighthouse,</q>" he shudders.
name = 'you\'ve been to the beach'
;
Finally, if you wanted this topic also to match by TELL BOB ABOUT BEACH, you could make it a SayTellTopic:
+ SayTellTopic, SuggestedSayTopic @beach
['(i)', '(you)', '(i\'ve)', '(you\'ve)', 'been', 'to', '(the)', 'beach' ]
"<q>I've been to the beach,</q> you say.\b
<q>Then you saw the lighthouse,</q>" he shudders.
name = 'you\'ve been to the beach'
;
Matching odd input
The "say" in a SAY command can be abbreviated to "s" to allow commands like:
>S I'VE BEEN TO BEACH
But many players will sooner or later try something like:
>I'VE BEEN TO BEACH
This would be valid for a SpecialTopic, but a SayTopic is not a SpecialTopic, even though the player can hardly be expected to tell the difference. The SayQuery extension accordingly tries to trap this kind of input as well. It does this by adding a hook to the various routines in playerMessages that would normally report that a command or word wasn't recognized. If there's a current interlocutor, the extension then checks whether the input would match one of that interlocutor's SayTopics; if it would, then that SayTopic is invoked (if the game allows it). The extension also keeps a history of say commands that worked, and the player's input is next tested against that (if no matching SayTopic was found). If a match is found, or if the offending word is found in one of these previous topics, then an appropriate error message is displayed explaining that the command or word is not valid in the present context. If no match of any kind is found, then the standard library message is displayed.
Changing some of the properties of the sayTopicManager object can change the way this behaves. Firstly sayTopicManager.mode controls what happens when a player enters an unrecognized command. This can be one of:
· SayTopicStrict: the command simply fails - so all SAY commands must start with an explicit SAY or S (effectively the standard library behaviour).
· SayTopicNormal: if the player character is currently in conversation, sayTopicManager attempts to find a SayTopic that would match the input as its topic; if a match is found, it is invoked, unless it is a DefaultTopic. This is the default and probably gives the best results.
· SayTopicLax: as for SayTopicNormal except that a DefaultTopic match is also accepted. This has the effect that any unrecognized input is regarded as an attempt to say something to the current interlocutor. Although this avoids error messages in conversations, it probably doesn't provide particularly helpful feedback to the player, particular in the case of minor typos, so this option should be used with caution.
Since the behaviour may be a matter of player preference, the command SAY MODE STRICT|NORMAL|LAX is provided to allow players to alter this setting.
Secondly, sayTopicManager.maxValidCmds is a number representing the length of the valid say command history that is kept. The default value is 30, but you can increase this if you think a larger number would work better in your game. The trade-off is that sayTopicManager then has to search a longer list of valid commands in the event of an unrecognized input, which may impact performance. When the maximum command history length is reached, sayTopicManager discards the oldest command first.
The extension also defines DefaultSayTopic and DefaultSayTellTopic.

QueryTopic
QueryTopic is very like SayTopic, except that it is used to ask questions instead of make statements, and that you need to define a qType property on each QueryTopic to define what type of question it represents. A QueryTopic matches a range of questions such as ASK [actor] WHEN X, ASK [actor] WHO..., ASK WHAT, ASK WHEN, ASK HOW, ASK IF... The qType property is needed so that the game knows which type of question it is you want to match. For example, to match the question ASK WHEN THE TROUBLES STARTED you might define your QueryTopic thus:
+ QueryTopic, SuggestedQueryTopic
qType = 'when'
keywordList = ['(the)', 'troubles', 'started']
topicResponse = "<q>When did the troubles start?</q> you ask.\b
<q>About five years ago,</q> he replies. "
name = 'when the troubles started'
;
This may seem a little convoluted, but it's better than having to define AskWhenAction, ActionWhoAction, AskWhatAction and so on.
If you have included the SayQuery.h file in your source file, you can use the SayQueryTopic template to write this as:
+ QueryTopic, SuggestedQueryTopic 'when' ['(the)', 'troubles', 'started']
"<q>When did the troubles start?</q> you ask.\b
<q>About five years ago,</q> he replies. "
name = 'when the troubles started'
;
The thing to note here is that the type of question being asked, in this case 'when' , must occur on its own in the qType property and not in the keywordList (or matchPattern), but should be included in the name property of a SuggestedQueryTopic.
Since there is only a finite set of query words like 'when', 'why' and 'how', the QueryAction grammar won't mind if the initial ASK or A is omitted; a command starting with WHEN, WHY, HOW or whatever will still match the grammar of a QueryAction, so the above topic would be matched by the command:
>WHEN THE TROUBLES STARTED
But the player is much more likely to type:
>WHEN DID THE TROUBLES START?
So you'll need to make sure your keywordList or matchPattern will cope with this too; perhaps:
+ QueryTopic, SuggestedQueryTopic 'when'
'(did ){0,1}(the ){0,1}troubles (start|started)$'
"<q>When did the troubles start?</q> you ask.\b
<q>About five years ago,</q> he replies. "
name = 'when the troubles started'
;
Sometimes you'll need the qType property to be a list. Suppose, for example, you want the player to be able to ask Bob whether he likes coffee. The player might type ASK BOB WHETHER HE LIKES COFFEE, or A BOB IF HE LIKES COFFEE, or DO YOU LIKE COFFEE? To cope with this you must allow for qType being either 'whether', 'if' or 'do':
+ QueryTopic, SuggestedQueryTopic ['whether', 'if', 'do']
'(you|he) (like|likes) coffee$'
"<q>Do you like coffee?</q> you ask.\b
<q>I love it,</q> he replies. "
name = 'whether he likes coffee'
;
If you want this topic also to match the query ASK BOB ABOUT COFFEE you can use an AskQueryTopic:
+ AskQueryTopic, SuggestedQueryTopic @coffee ['whether', 'if', 'do']
'(you|he) (like|likes) coffee$'
"<q>Do you like coffee?</q> you ask.\b
<q>I love it,</q> he replies. "
name = 'whether he likes coffee'
;
The extension also defines DefaultQueryTopic and DefaultAskQueryTopic.
QueryTopic Types:
QueryTopics can match ASK <actor> <qType> <topic> where <qtype> is one of:
who, whose, what, why, where, when, which,
if, whether,
do, does, did,
have, has, had,
is, are, was, were,
will, shall, would, should.
If you need to change or extend this list, find and edit the line that starts #define query_word in SayQuery_en_us.t

A Note on Regular Expressions in matchPattern
In common with the standard library TopicEntry types, SayTopic and QueryTopic convert the player's input to lower case (unless matchExactCase is set to true) before attempting to match the regular expression in matchPattern, so there is no need to include a <NoCase> tag. On the other hand, you'll probably want to include a $ at the end to prevent the matching of input that starts with text that matches your regular expression but then continues with something else (e.g. ASK BOB IF HE LIKES COFFEE AFTER LUNCH).
Additionally, SayTopic and QueryTopic strip out quotation marks and surplus spaces from player input before attempting a match, so there's no need for regular expressions designed for use with these types of TopicEntry to worry that the player might have typed SAY "I LOVE YOU" TO KATE or might have typed more than one space between words.

SayQuery, SpecialTopics and Other Final Considerations
Between them, SayTopic and QueryTopic can do most of what SpecialTopic does, but you may still want to use SpecialTopics in ConvNodes where they're appropriate, particularly if you're giving the player options that don't readily start with SAY or ASK, such as LIE, REFUSE TO ANSWER or EQUIVOCATE, which SpecialTopics match perfectly well. Moreover, although SayTopics and QueryTopics can be used anywhere, you might want to be sparing in their use, since the player won't normally be able to guess their syntax, and it could become wearisome to have to keep typing TOPICS. Where ASK BOB ABOUT X or TELL BOB ABOUT Y will do the job perfectly well, it's best to stick with them, since it's generally much easier for the player to type A LIGHTHOUSE or T MYSELF than ASK WHAT HAPPENED AT THE LIGHTHOUSE or SAY YOU'RE NEW HERE, and the player is much more likely to hit on such commands without having to resort to a TOPICS command each turn.
One particular kind of case where SayTopics and QueryTopics might be useful is where something of this form is introduced in a ConvNode but could remain valid thereafter (at least in the player's mind). For example, if a ConvNode offers a set of choices like "(You could tell her that you love her, or ask her if she will marry you, or talk about the weather). " a player who chooses TALK ABOUT THE WEATHER at that point and then leaves the ConvNode might expect SAY I LOVE YOU or ASK HER IF SHE WILL MARRY ME to work on some subsequent turn when neither command is available; this could be fixed with the use of a SayTopic and a QueryTopic that become available at the appropriate time and which can continue to field such conversational commands thereafter, even outside a ConvNode.
There may be other cases where a SayTopic or QueryTopic is virtually essential. In a detective game, for example, there may be no good substitute for ASK BOB WHERE HE WAS LAST NIGHT or SAY THAT FRED IS THE MURDERER. Perhaps ASK ABOUT LAST NIGHT would often do for the first of these, but neither TELL BOB ABOUT FRED nor TELL BOB ABOUT MURDER is obviously equivalent to the second. It may be that there are many possible suspects, and that the player needs to be able to say that he thinks any one of them is the murderer.
The included sample game gives examples of how SayTopics and QueryTopics can be implemented. If you compile it and play through it (which shouldn't take long) you'll get some idea of what SayTopics and QueryTopics can do, but you'll probably also see why it's best to stick to AskTopics and TellTopics where they can do the job well enough.