A TopicEntry can go directly inside an Actor, in which case it's part of the actor's global set of topics; or, it can go inside an ActorState, in which case it's part of the state's database and is only active when the state is active; or, it can go inside a TopicGroup, which is a set of topics with a common controlling condition; or, it can go inside a ConvNode, in which case it's in effect only when the conversation node is active.
Each entry is a relationship between a topic, which is something that can come up in an ASK or TELL action, and a handling for the topic. In addition, each entry determines what kind or kinds of actions it responds to.
Note that TopicEntry objects are *not* simulation objects. Rather, these are abstract objects; they can be associated with simulation objects via the matching mechanism, but these are separate from the actual simulation objects. The reason for this separation is that a given simulation object might have many different response - the response could vary according to who's being asked the question, who's asking, and what else is happening in the game.
An entry decides for itself if it matches a topic. By default, an entry can match based on either a simulation object, which we'll match to anything in the topic's "in scope" or "likely" match lists, or based on a regular expression string, which we'll match to the actual topic text entered in the player's command.
An entry can decide how strongly it matches a topic. The database will choose the strongest match when multiple entries match the same topic. The strength of the match is given by a numeric score; the higher the score, the stronger the match. The match strength makes it easy to specify a hierarchy of topics from specific to general, so that we provide general responses to general topic areas, but can still respond to particular topics areas more specifically. For example, we might want to provide a specific match to the FROBNOZ SPELL object, talking about that particular magic spell, but provide a generic '.* spell' pattern to response to questions about any old spell. We'd give the generic pattern a lower score, so that the specific FROBNOZ SPELL response would win when it matches, but we'd fall back on the generic pattern in other cases.
TopicEntry : object
This should be set to nil when the topic entry's response is non-conversational, in which case a greeting would be undesirable. This is appropriate for responses like "You don't think he'd want to talk about that", where the response indicates that the player character didn't even ask a question (or whatever).
This method returns a TopicEntry object - one of the objects from the match list - if it has an opinion as to which one should take precedence. It returns nil if it doesn't know or doesn't care. Returning nil gives the other topics in the match list a chance to make the selection. If all of the objects in the list return nil, the topic database searcher simply picks one of the topic matches arbitrarily.
'matchList' is the list of tied TopicEntry objects. 'topic' is the ResolvedTopic object from the parser, representing the player's input phrase that we're matching. 'fromActor' is the actor performing the command. 'toks' is a list of strings giving the word tokens of the noun phrase.
The topic database searcher calls this method for each matching topic entry in the case of a tie, and simply accepts the opinion of the first one that expresses an opinion by returning a non-nil value. There's no voting; whoever happens to get *and use* the first say also gets the last word. We expect that this won't be a problem in practice: when this comes up at all, it's because there are a couple of closely related topic entries that are active in a particular context, and you need a special bit of tweaking to pick the right one for a given input phrase. Simply pick one of the involved entries and define this method there.
The database hierarchy, for most purposes, starts with the ConvNode at the highest level, then the ActorState, then the Actor. We search those databases, in that order, and we take the first match we find. By default, if there's another match in a lower-level database, it doesn't matter what its matchScore is: we always pick the one from the highest-level database where we find a match. You can override this method to change this behavior.
We don't actually define this method here, because the presence of the method is significant. If the method isn't defined at all, we won't bother looking for a possible deferral, saving the trouble of searching the other databases in the hierarchy.
By default, we'll do one of two things:
- If 'self' inherits from Script, then we'll simply invoke our doScript() method. This makes it especially easy to set up a topic entry that shows a series of responses: just add EventList or one of its subclasses to the base class list when defining the topic, and define the eventList property as a list of string responses. For example:
+ TopicEntry, StopEventList @blackBook
['<q>What makes you think I know anything about it?</q>
he says, his voice shaking. ',
'<q>No! You can\'t make me tell you!</q> he wails. ',
'<q>All right, fine! I\'ll tell you, but I warn you,
this is knowledge mortal men were never meant to know.</q> ',
// and so on
- Otherwise, we'll call our topicResponse property, which should simply be a double-quoted string to display. This is the simplest way to define a topic with just one response.
Note that 'topic' will vary by subclass, depending on the type of command used with the topic type. For example, for ASK and TELL commands, 'topic' is a ResolvedTopic object; for GIVE and SHOW, it's a simulation object (i.e., generally a Thing subclass).
'actor' is the actor to whom we're making the suggestion. 'scopeList' is the list of objects that are in scope for the actor.
The library only uses this to determine if a suggestion should be offered. So, specialized topic instances with non-standard match rules don't have to worry about this unless they're used as suggestions, or unless the game itself needs this information for some other reason.
This is abstract in the base class because the meaning of 'topic' varies by subclass, according to which type of command it's used with. For example, in ASK and TELL commands, 'topic' is a ResolvedTopic describing the topic in the player's command; for GIVE and SHOW commands, it's the resolved simulation object.