NOTE: This article has been depreciated along with all of the original ORLib Manual efforts. The newer manual can be found at http://www.onyxring.com/ORLibDocToc.aspx
Part D1: Advanced Library Entries
From conversations to actions, NPCs represent, potentially, the most complex objects in any game. The standard library begins the trek of NPC implementation by defining the attribute "animate." It continues by creating a handful of verbs that can only be used in concert with "animate objects." The journey toward enabling NPCs in Inform programs is completed with the standard library's creation of the "Life" and "Orders" properties.
At first glance, this support for NPCs may seem inadequate. Indeed, none of the pre-defined verbs seem to lend support for NPCs performing the actions. Nevertheless, it provides the necessary foundation for any NPC to be built. In truth, many exceptional NPCs have been built upon this foundation, and the NPC modules of the ORLibrary leverage this foundation as well. Although not named as such, this part essentially covers NPC related modules, or modules derived from NPC related modules.
Since the design of the NPC classes is substantially affected by it, now seems as good a time as any to clarify a seldom-discussed nuance in Inform's behavior with regard to multiple-inheritance. When dealing with objects derived from more than one parent, conflicts are always resolved by a first-one-defined-wins, rule. That is, when class A and B both define a property named "button_count" and object C is derived from both A and B (in that order) then C.button_count will always refer to class A's definition.
Let's exemplify this with the following multiple inheritance object:
The follwing code...
will always result in the following result.
This may seem strange to those that mistakenly think of class B overriding class A. Indeed, class B just "fills in the gaps" so to speak, adding only the properties that have not already been defined.
To reinforce this, let us consider additive properties. Additive properties, such as "before" and "life", are also run in the order that they are defined. This seems doubly strange since additive properties are, by their nature, run in reverse order of definition. In the above example, if A and B both define a collection of before rules, class A's life rules would be run first (in reverse order) then B's.
To make class B's definition be the default implementation of button_count, and to make B's additive properties run first, the order in object C's "class" property must be rearranged:
For this reason, when assembling an NPC out of componant classes (such as ORNPC_movement) it is necessary to add behaviors to the front of the class list and to declare the NPC as an object or a previously defined class rather than directly from the ORNPC class.
The ORKnowledgeTopic class is an advanced implementation of a topic-based conversation/information system. It aids in separating information from conversation so that two NPCs could potentially present the same information in two separate ways. It implements a topic memory, so that knowledge can be "learned" and "taught" to others. It further implements and unifies the entire ASK/TELL/ANSWER/CONSULT paradigm.
Before we get into the ORKnowledgeTopic examples, let us resume our tutorial by creating the list mentioned in the introduction:
And we should include the investigators_list in the player's initial inventory. This can be done in the Initialise() routine:
As the read_value property states, the list object will be consultable and the player will be able to read about each of the suspects. We can accomplish this with minimal code:
1.1 Basic ORKnowledgeTopic usage
As we create the suspects of this game, we can add information about them to our list, but in the mean time, lets add a topic about the victim:
It should be noted that the knowledge topics do not physically exist anywhere in the game world, so the description name is never displayed. For this reason we could have left it off entirely; however, since we are using the ORRecogName module, it is easy to cram all words into the description name. Alternatively, we could have used the conventional method of the "name" property.
At this point we have completely implemented a basic knowledge topic which we can successfully consult the list about:
Note that it is the knowledge topics themselves that keep track of who or what "knows" them. In this example, the list can be consulted because the topic has it listed in its "knownby" property list. Alternatively, we could have left the knownby property undefined (or rather, not redefined from its inherited definition) and caused the list to "learn" about the victim when the game first began with the following line:
This would also be one way to handle someone writing a new topic into the list.
In the example given thus far, the extra zeros in the "knownby" property serve no purpose. However, it is always a good idea to provide this extra space in case the topic is to be shared with others via the MemorizeFor() routine. This is especially appropriate if the topic is to be learnable.
It should be mentioned here that knowledge topics can also have parents which are knowledge topics. When this is the case, a knowledge topic is also known by the same characters that know its parent. This enables "knowledge groupings" and offers a more flexible way to manage knowledge bases.
1.2 Making a Topic Learnable
Making a topic learnable is as simple as giving it the "learnable" attribute:
The topic now has the ability to be "learned". Note that to begin with, the player character does not "know" the details about the victim. The list must first be consulted before that information is learned. We'll demonstrate this by first trying to tell the maid about something that the player does not know:
Note that as you run this example, additional statements regarding the maid and the butler may also speckle your output. This is the result of the each_turn property of the foyer that we created in Part B section 5.4.
When playing with the tutorial thus far, it may be tempting to try and verify that the maid has learned the information by asking her:
Those who attempt this will be disappointed to see the generic response:
It may please you to know, that despite her refusal to answer, the maid does indeed now know about the victim. She is, however, merely an NPC by virtue of the animate attribute. She is a mere shadow of an NPC. As we will cover in sections following, the ORNPC related classes expose much more substantial characters.
One particularly intriging use of learnable ORKnowlegeTopics is the creation of a magic spell system. Leveraging this class it is embarrassingly easy to create a spell book which contains a number of spells which must be learned before being cast.
1.3 Adding Presentation to Topics
In the above example, the value of the "topicinformation" property is simply a statement. It is raw information and is displayed as such. When you consult the list or tell the maid of this topic there is no accompanying narrative to present the information. Although it is possible to format the topicinformation with a more narrative-oriented text, such as "The list says…" by doing so, we would have tied this topic to the list object. This is fine for non-learnable knowledge topics that always originate from the same source, but in this example it would be inappropriate since the character would be unable to tell anyone about the victim without the narrative mentioning the list.
By default, an ORKnowledge topic analyses its "TopicInformation" property. If only a single string of text is supplied (as was the case above) then it presumes the text is already appropriately formatted and outputs it. It behaves differently if a list of strings have been supplied, however. Note how adding a second element (in this case, a zero) to the TopicInformation list turns this behavior on. Note also that the ending period following the name "Billman" has also been removed:
This results in a more pleasing narrative form:
If you were paying close attention, you may have noticed the period was still attached to the end of the output. Why?
In the above example, the narrative begins the output, so the quoted topicinformation ends in a period. But it is also perfectly valid to have the narrative follow the quoted topicinformation. Consider the punctuation in the following:
In this case, a comma would be used for punctuation instead of a period. This is almost always the case for periods that end a quote, but do not end a sentence. This is also almost never the case for other punctuation in the same circumstance. A question mark, for instance, would be used regardless or narrative text following the quote. Replacing the question mark with a comma is unacceptable.
The even elements in the TopicInformation list are called "forced punctuation" elements. If set to zero (0), then the routines that integrate the narrative with the topicinformation text will choose between a period or a comma depending on whether or not text follows the quote. If the "forced punctuation" element is not zero, but instead holds a value, then that value is always printed regardless of text following the quote. In the following example, note the use of the exclamation point:
As you see done above, it is fine to list the topic information in two or more clusters of text followed by punctuation. In fact, customized presentation rules can leverage this to accomplish an even more pleasing formatting, as we will see…
1.4 Customizing the Narrative Presentation
It is often useful to change the way the narrative is presented depending upon who is relating it. An angry troll might relate the information in a totally different manner than, say, a book. The ORKnowledgeTopic class gives the telling object the opportunity to format the text by checking to see if it provides the ProcessDialog property.
The ProcessDialog routine may choose to format the text how it sees fit. If it does so, then it should return true. Returning false signifies that the default formatting for the KnowledgeTopic should take place.
The ProcessDialog routine takes the following parameters:
To – who we are talking to
The PersonalizeTell and PersonalizeAsk values are actually properties passed as values. Developers of reusable classes may or may not find it useful to define these properties and simply call them from within ProcessDialog. This is the manner in which it is done in the ORNPC_converse class.
To give a basic example of customizing the presentation of dialog for an object, we'll add a ProcessDialog routine to the paper list:
Notice the call to the ORKnowledgeTopic class's FlushDialog method. As shown above, this routine simply concatenates all text contained in the given property at the given position and beyond. This text is output and wrapped in quotes.
Below we can see the result of our customized ProcessDialog routine as it compares to the generic version:
1.5 Asking for Information
As was stated previously, the maid is just a shell of an NPC. Only the animate attribute sets her apart from pocket fluff, and she cannot answer your questions. Still, the player asking a question, and the NPC answering a question are two separate things. In order to formulate a question, the ORKnowledgeTopic provides a property named "query" which takes the same form as the TopicInformation property. Additionally, the same rules of forced punctuation apply. Review the complete knowledge topic with the query property defined:
Which enables the following transcript snippet:
Unlike the telling of topic information, for which the customization of text is done in the ProcessDialog routine of the telling object, the asking of topic information is done in the ProcessDialog routine of the asking object. To simplify: The object speaking is the object formatting the text. In the above example, the player object, that is, the selfobj, is checked for a ProcessDialog routine. Since none is provided, the ORKnowledgeTopic's formatting is used. It is possible to define the selfobj with a ProcessDialog routine and take advantage of Custom formatting, however it is generally a better idea to create an object which defines this routine and make the object the player via a call to ChangePlayer() at the start of the game. This has additional advantages too, such as negating the need to move objects into the players inventory or set the location variable in the Initialise() routine.
1.6 The HasBeenSpokenOfBy Property
ORKnowledgeTopics remember who has told them. The "HasBeenSpokenOfBy" property-routine takes an NPC as a parameter and returns true or false, depending upon whether or not that NPC has spoken about the topic before. This enables us, should we choose, to modify the text when an NPC is repeating a topic.
1.7 Modifying Repeated Information
In the world of Asking and Telling topic information, it is possible to ask about the same information more than once. When this occurs in real life, the second answer to the same question is usually modified. Irritation may be conveyed by a person being asked the same question twice, or the information may be abbreviated in some way.
ORKnowledgeTopic provides a property named "AskedAgain" which takes the same form and follows the same formatting rules as the TopicInformation and Query properties. If this property is defined and the Topic has already been spoken about by the NPC being asked, then it is used instead.
Because the NPCs in our tutorial cannot yet answer questions, we will postpone the transcript produced by our example of this property until section 4 (ORNPC_AskTellLearn).
1.8 Common Knowledge and Context There is an object defined with the name "CommonKnowledge." Any KnowledgeTopics placed as children of the CommonKnowledge object are automatically known by everyone in the game. This has the potential to create some difficulties with regard to general topics being spoken by inappropriate people. To address some of these difficulties, a property has been defined in ORKnowledgeTopic called "IsInContext". "IsInContext" takes two parameters, "to" and "from" which indicate who is being told of this topic and by whom. This routine provides a place for specialized code to verify that topics are not spoken out of context. The male NPCs may speak openly about there wives via some generic Knowledge Topic, but the maid, for instance, would not. The "IsInContext" for this topic would need to validate that the person telling it was of the appropriate gender. The "IsInContext" routine serves no purpose at this stage in the tutorial, but become indispensable with NPCs begin to initiate conversations on their own. By default, this routine will return true.
1.9 Additional Notes
There are a couple of additional features included with the ORTopicKnowledge module which should be mentioned.
The first is the implementation of two abbreviations for ASK and TELL. These are A and T respectively. These were first seen (by this author at least) in Emily Short's game "Galatea" and have been commented upon by the IF community with varying degrees of favor.
The second is the implementation of a mechanism to determine who the player is currently talking to, so that additional ask/tell commands can assume that this is a continuation of a conversation already in progress.
These two features make it possible to issue the command:
…and have it translate into:
…provided the maid is who we are already speaking to, of course.
The ORInsultCompli_KT module is a module that defines the verbs "insult" and "compliment" and creates two objects derived from the ORKnowledgeTopic class which these verbs leverage. The insult_t and compliment_t knowledge topics will generate randomized insulting names and randomized complimentary names. Additionally, if the NPC is inherited from the NPC_moods then he or she will become more or less irritated accordingly.
For our tutorial, simply include the module. The following transcript demonstrates the new verbs, but is very unlikely to resemble the actual output:
As was mentioned previously, the standard library implements only minimal NPC support. Nevertheless, this support is the required foundation upon which more significant NPCs can be based. ORNPC builds upon this foundation to provide a more versatile and sophisticated base from which to construct NPCs.
At first glance, the minimal ORNPC does not appear to act any differently from a bare-bones object with the "animate" attribute. We'll make the butler in our tutorial derive from ORNPC to show how little difference their is. But first, a base class:
A little experimentation will show that the butler acts no differently that the maid. So what is the use of the ORNPC class? The power behind ORNPC is not what it makes NPCs do, but rather the hooks it provides to enable NPCs to act on their own...
The core of NPC activity is powered by the ORNPCControl object. This object is simply a wrapper for the daemon which imbues life into an NPC. Each turn, the daemon calls a routine called "heartbeat" which is defined in every ORNPC object.
Because all ORNPC objects are raised to life in this fashion, by a single daemon, it is an easy matter to halt the actions of all NPCs at once. A property in the ORNPCControl object named "pause" can be set to true to accomplish this. Likewise, it can be set to false to restart all NPC activity. It is worth mentioning that this daemon is started automatically, and the call to StartDaemon() never actually needs to be implemented by the developer. For this reason, although starting and stopping all NPCs is not commonly done, it is perhaps the only reference to the ORNPCControl object that will occur in a game. More often than not, the object is not referenced at all.
It should be noted that the daemon does more than simply call the "heartbeat" routine. It also takes some steps to ensure that the world is as it should be for the NPC, just as it is for the player. This is accomplished by taking steps such as making the NPC the current actor and calling a replaced version of MoveFloatingObjects().
As described above, "heartbeat" is the entry point for all NPC activity. Leveraging this property gives us a great deal of versatility. Many authors have found that the addition of even very small statements to remind the player of our NPCs presence can have a profound effect and go a long way toward making it appear more lifelike. Dan Schmidt does this with the tool man character in "For a Change" and leaves small, lasting reminders of his presence. Emily Short, also does this in her game "Metamorphosis" despite the fact that the only NPC in the game serves little more purpose than scenery. Sentences such as "The toolman jingles in the breeze" or "The gondolier lifts his head and sighs softly" fire at arbitrary times and add to the illusion of life. We can accomplish this easily with the heartbeat property:
As shown above, it is a relatively straightforward matter to extend an NPC using this property, but it is worth mentioning that "heartbeat", like "life" and "before", is an additive property. Provided newer versions do not interrupt it (by returning true), the default implementation of "heartbeat" will select a registered action for the NPC, and perform it. We'll cover registered actions in a moment, but before we do lets talk briefly about a routine called above that we've not yet discussed, the PlayerCanWitness() routine.
Unlike the each_turn property, the "heartbeat" routine is fired for an NPC regardless of the player's current location. This enables NPCs to act in ways that meet their own agendas even with the player is no where to be seen. It is necessary for this reason to verify that the player is indeed able to see or hear the NPC before text is output. The PlayerCanWitness() routine can be called for this. It returns true or false if the player can witness the actor's actions. Additionally, another object can be passed in if it is not the actor that we are checking (such as a CB radio that the actor is speaking through).
In the above example the "heartbeat" routine simply exits if the player cannot observe the actions of the NPC, but care should be taken to ensure that this routine only filters the text output and does not filter out changes the NPC may make to the game world. In short, the PlayerCanWitness() routine should determine if the player is notified when an NPC picks up an object, but not whether the NPC actually does pick up the object.
3.4 Registered NPC Actions
It was mentioned previously that the default implementation of the additive "heartbeat" property will search for "registered actions" to perform. For the purposes of this discussion, "actions" are essentially member properties that have been registered with a call to the register_action() routine.
An action property takes the single parameter "can_perform". When "can_perform" is passed in as true, then the NPC is attempting to make a decision about what action to perform. At this point, the action property should check and make sure that the action is possible (such as making sure there is an empty chair nearby if the action is to sit down) and return true if it is. When the "can_perform" parameter is set to false, then the action is actually being attempted and the appropriate code should be run.
Since some actions take more than one turn to complete, ORNPC implements a property called "continued_action" which an action routine can set to ensure it will be called again next turn. When continued_action is set to point at an action property, the normal steps in determining which action to take are circumvented. The action being referenced will continued to be called from the "heartbeat" routine until the "can_perform" test returns false.
Alone, the ORNPC class does not implement any actions, but this framework is the basis for many of the NPC component classes. The "ORNPC_movement" and "ORNPC_converse" modules are examples of NPC actions.
3.5 The DoNothing_msg Property
When the NPC has considered all potential actions, it is entirely possible that there is nothing to do. At this point the "DoNothing_msg" property will be run or printed if it is defined.
The ORNPC_AstTellLearn class is a component class. That is, it is a class which encapsulates a specific behavior that can be used in conjunction with other classes to piece together an effective NPC. As we discussed in the section on learnable topics (1.2), basic NPCs do not come with the ability to relate knowledge topics, even if they have already "learned" the information. Adding the ORNPC_AskTellLearn class to an NPC's class list rectifies this and makes speaking to the NPC a little more natural. In particular, the NPC will now be able to answer questions when asked and respond when told something.
The following four properties are provided by an NPC that inherits form this class:
UnlearnedResponse_msg - printed when an NPC has been told a topic that is not learnable.
Getting back to our tutorial, lets make our NPCs able to answer our questions. This seems particularly necessary in a murder mystery investigation. Recall our need to insert the new class at the front of the class list.
And that is all that was necessary to enable our NPCs to answer questions. Notice in the following transcript the change in the response when the question is asked a second time. This is the result of the "AskedAgain" property which we discussed in section 1.6:
The ORNPCVerb module redefines the majority of the standard library's verbs and enables them to be performed by NPCs as well as the player. Like the OREnglish "language definition file, " this module exhibits no apparent difference when simply included in the compilation process. In truth, the ORNPCVerb module is part of a triad of modules that work together to bring the NPCs of the game world under the same umbrella of rules that the player is subjected to.
To put the components of this simulation in to a nutshell: ORNPCVerb makes the standard library's verbs NPC compatible. OREnglish makes the default messages NPC compatible. ORNPC_doverb empowers NPC with the ability to issue verb-defined actions.
Since this module is best exemplified when used with the ORNPC_doverb module, further covering will continue in that section.
Table of Contents
Part C2: Miscellaneous ORLibrary Modules (part two)
Part D2: Advanced Library Entries (part 2)