Part A (§1): The BasicsKnowledge and Conversation
§2 Advanced Techniques
(Advanced NPC Implementation, Part 2b)
n the previous section we discussed various methods for developing NPC conversation and reviewed techniques for implementing the most widely used of these. In this section we will expand upon those methods with much more powerful techniques and begin to develop reusable classes for NPCs in the process.
Uniting Knowledge and Information
In the previous example, a character's knowledge of something and his ability to talk about it are not as tightly related, as they ought to be. Coded into the guard's life routine is the same information about Snow White that the mirror has. The guard may not recognize her name, but he was coded, wastefully, to talk about her anyway. The reverse of this is a more probable circumstance, considering the existence of common knowledge. In the above example, a new NPC would also need to be coded with responses to the 'beauty' topic. Uniting what is said with what is known is the second major benefit to using conversation objects.
To start off with, let's modify our KnowledgeTopic base class a little to default a property, which we will arbitrarily call "TopicInformation:"
Now we can tie the text to the actual knowledge objects:
And remove the text from the life property (of both characters) entirely. Instead, we can print out the TopicInformation value:
Note that the example runs exactly as it did previously (i.e.: The guard will respond when asked about 'beauty', but not 'snowwhite.' The magic mirror will talk about both.)
An NPC'a ability to converse about a topic is now tied directly to the scope of the topic itself. If the character "knows" the information, he can talk about it. No changes to his life property need to be made to add this knowledge. This technique benefits us in several ways:
So far we've developed our scope rule to allow a knowledge object to be known by one character or all characters. This doesn't cover all cases, though. Rarely do we have an all-or-nothing circumstance; often we have a group of people that know a specific topic of conversation, and another group of people that do not. What is needed is a way to keep track of who knows what.
The most intuitive way to accomplish this would be to add a property to each NPC that records what he knows. This isn't really the best solution, however, since knowledge does not have to be limited to NPCs. It could also be found in a book. Instead we can attach a property list to the knowledge object that indicates who or what "knows" it:
Having done that, it is now not such a complicated task to check this list for a specific object. We'll call this routine IsKnownBy() and attach it as a property to the KnowledgeTopic class as shown below:
And, of course, our scope rule can leverage this as well:
Note that our scope rule no longer looks for the parent of the topic anymore. This is actually a preferable arrangement since we can now avoid placing the knowledge topics in the game world at all. This keeps us from having to deal with sticky commands like:
To demonstrate our new capacity for selectively shared knowledge, we will need to create a third NPC. This one, like the mirror will also know the 'snowwhite' topic, making this knowledge shared by two characters, but still unknown by the third (the guard.)
Since we are beginning to duplicate code, this is a good opportunity to implement an NPC base class:
The two existing NPC's, by inheriting from this class, are now reduced to:
And a third can be created just as easily:
The snowwhite topic can now be shared between the MagicMirror NPC and the Dwarf NPC by filling in values in the KnownBy property (the beauty topic, being a child of the CommonKnowledge object, does not need the KnownBy property):
Now we can verify that the same KnowledgeTopic is shared by two NPCs, but not all NPCs:
Now we're are ready to give our NPCs (and PCs, too) the ability to learn...
Learning and Teaching Knowledge
Learning and teaching are the same action described from two perspectives. Since implementing the 'shared knowledge' functionality, the act of learning a new topic translates exactly into manipulating the topic's KnownBy list. The following routine can be added to the KnowledgeTopic class to do just this:
Now, letting the guard in on the 'snowwhite' secret can be accomplished with a simple:
Thus far, the 'ask' portion of the 'life' routine has called the TopicInformation property to print out the topic. Often, as with the case of topics being taught and learned, there is more to be done than simply printing text. Morphing topics is an example of this that will be discussed in a moment, as well as adding character personality by separating information from conversation. For now though, we can just recognize that the teaching/learning of topics should be tied to the printing of dialog and a driving function should be added to call both, without blurring the two into the same routine. A new property, we'll call it 'TellAbout,' will be added to the KnowledgeTopic class to do both:
The TellAbout routine is where the logic behind the conversation occurs. The 'learnable' attribute has been introduced to govern whether or not to "teach" this topic. Some topics are simply small talk and don't need to be shared between characters. 'Learnable' allows the developer to designate which topics can be "learned."
Astute readers may catch that the 'from' variable is never used and so will generate a compiler warning. This is okay. The 'from' variable is actually a parameter that will become useful to us later. We've just introduced it early.
We can now change the life routine of our NPC class to call the TellAbout property:
That's all that's needed for the player character to learn from an NPC. Of course, learning a topic is nothing if you cannot speak about that topic. In order to do that, we need to extend the 'tell' verb. First, a new scope rule to check and see what the actor (usually the player) knows:
Next we will extend the 'tell' verb appropriately:
And that's it. All the functionality for teaching and learning topics is in place. For the sake of example, let's give the 'snowwhite' topic the 'learnable' attribute:
and see our new functionality in action:
Separating Information and Conversation
A drawback to uniting information and knowledge is that we lose the ability to tailor a response to an NPC's personality. Separating the text that qualifies as topic information from the text that is character specific addresses this and creates dialog that is much more fluid and believable. Consider the ways in which each of the NPCs may differ in their answers regarding 'snowwhite,' as well as the differing ways that the topic could be broached:
The narrative that accompanies dialog occurs in many different forms, but breaking the topic's information into, for example, two pieces can facilitate the use of most of these forms. The above examples can be accomplished by doing this. Text for asking about a topic also needs to be introduced:
Notice that the ending punctuation has been separated from the rest of the text. In the English language (and shown in the above examples), periods are often substituted with commas when joining a piece of dialog with a piece of narrative. This separation gives us the option to replace the punctuation programmatically. It would be nice if the substitution always occurred, but with some punctuation, such as the question mark, the punctuation remains and is not replaced by a comma. To signify this, we can choose a zero value for the punctuation list element. Additionally, you can see that there are no quotation marks included in the information text. These will be included or not, by the narrative text.
Adding all of this new diversity to the information objects greatly increases the complexity. Since we may not always want the complexity we should be able to opt out of it if we so desire. A fairly good indicator that we don't want to personalize the text to the NPC is having only one value in the given property. That is, if the ending punctuation wasn't provided then just print the text and move on. The following KnowledgeTopic TellAbout routine, together with a new property, does a basic form of this conversation "blending" provided the telling NPC doesn't provide a means to do this:
Note that the ProcessDialog routine is called if the NPC provides it. This routine will parse the topic information and print it through a property we've specified, in this case PersonalizeTell. We've made a presumption that if the object provides ProcessDialog, then it also provides PersonalizeTell. We could check for both, but if we add them to our NPC base class, then it is really not such a large leap of faith to presume that the existence of one implies the existence of the other.
The routine for wrapping all remaining text in quotes and printing it is best allocated globally since multiple classes will use it.
So why did we pass in PersonalizeTell rather than just calling it from the ProcessDialog? Doing so enables us to reuse the code in ProcessDialog and specify other routines for the text customization. We'll use this in a moment when our characters begin to "ask" as well as "tell."
Now we have the entire framework in place. Alone, we have far better than just quoted text.
But we can also personalize the telling of topics with regard to the speaking NPC:
Of course, the PersonalizedTell routine works better if stocked with several variations of text, but this is a good starting point. To completely implement our examples, we still need to implement a PersonalizedAsk routine. Since the act of asking is done by the player rather than the NPC, it needs to be implemented somewhere other than the NPC's life routine. Replacing the standard library's AskSub routine is the obvious choice:
Now we can implement the AskAbout routine just as we did TellAbout:
And add the default PersonalizeAsk routine to the NPC base class:
At this point, we've accomplished everything we need to print out the query text when the player asks about a topic. We've also laid the framework for our NPCs to ask questions. Personalizing the narrative is accomplished for "ask" in the same way that it was for "tell." For example, let's add the following personalization to the guard:
The following code will now cause the guard to ask the dwarf about the snowwhite topic:
Upon execution, the generated text reads:
To top it all off, when all of this narrative diversity is said and done, the guard has now learned something that he can talk about if asked.
As you look over the guard's PersonalizeAsk routine, you might be wondering about the "You hesitate" text and the corresponding check to see if the guard and the player are one and the same. Of course, this is only necessary if you plan of letting the player "body jump" into the guard, and in a real game, we would want to make similar changes to the PersonalizeTell routine. As it happens, we can utilize the standard library's ChangePlayer() routine to demonstrate giving the player the same conversation abilities that we have given our NPCs:
Notice that when the player character becomes the NPC, he also inherits the personalization routines:
A useful technique, even for games where the player does not change, is to create an NPC that will be the player. This way, questions asked by the player will have the same customized formatting as questions asked by NPCs.
It should be noted that the pronoun print rules discussed in the previous article "Pronouns On Steroids" really shine in this area and make coding dialog for these sorts of dual-purpose NPCs much simpler.
On to More Advanced Techniques
In this section we have covered several advanced topics, that can be used to customize text to a specific NPC. There's more to come in section 3...
§3c: More Advanced Techniques
§1: The Basics