WelcomeWhat's NewsORLibraryDownloadsLinksWebMaster@OnyxRing.com

This site
produced
656719
pageviews since
8/19/2004

Today's date:
11/20/2017






Author: Jim Fisher
Title: Advanced NPCs, Part 2c: Conversation and Learning

Creation Date: 8/9/2004 6:09:53 PM
Last Updated: 8/9/2004 6:09:53 PM


Introduction to Section 2c
Morphing Topics
Conversation Script Topics
Inanimate Knowledge Transfer
In Closing
About the Examples
For Review
Note: Because of the length, the "Knowledge and Conversation" article was written in three sections (of which this is the third) listed here for convenience:
Part A (§1): The Basics
Part B (§2): Advanced Techniques
Part C (§3): More Advanced Techniques
Knowledge and Conversation
§3: More Advanced Techniques
(Advanced NPC Implementation, Part 2c)


n the previous section we discussed various methods of sharing knowledge between characters and customizing the dialog to match a character. This section will cover additional techniques, such as topics that change content.

Morphing Topics

A common practice when developing NPCs is to have the character say random things at various points in the game. Embedded somewhere deep in whatever daemon controls the NPC can be a couple of lines of code that cause the character to speak to the player:

   
 actor=dwarf;
 <tell player smalltalk>;   

Although it is certainly possible to create a dozen or so KnowledgeTopics, pick one at random each turn and have the NPC talk about it, a better way is to create a morphing topic. That is, a topic that prints something different each time it is used. Since this technique is best implemented with non-learnable topics, separating conversation from information need not be done:


 KnowledgeTopic smalltalk 
   with name 'smalltalk'
   , KnownBy dwarf 
   , TopicInformation[;
      switch(random(4)){
         1: "~There don't appear to be any other dwarves here,~ 
            says the dwarf.  ~I'll have to notify the 
            labor board.~";
         2: "~I'm trying to keep my shoes clean,~ says the dwarf. ~My   
            brothers are all envious of them.~";
         3: "~Hi-ho!~ spouts the dwarf.";
         4: "The dwarf scratches his bearded chin and says, 
            ~Hmmm, I wonder where my children hid my hat.~ ";
      }
   ]
 ;
Additionally, using a variation of this technique we can customize conversations to follow a specific script…

Conversation Script Topics

One of the more difficult challenges that must be faced when writing believable NPC dialog via the ASK/Tell method is getting the player to ask the right questions. In many cases there is information that needs to be conveyed to the player to further the game, yet there is no viable method of forcing him/her to make the appropriate inquiries. It is true that the occasional hint can be dropped here and there, but there are often times that hints just don't seem appropriate to the game.

Having an NPC initiate conversation and thereby volunteer this information is a time-honored method of dealing with situation. In truth, this technique can be useful even if no pertinent information needs to be conveyed. People initiate conversation and volunteer trivial information as a matter of common practice. A realistic NPC should do this as well. Having an NPC follow a "conversation script" that evolves over a series of turns can help to simulate a conversation and provide pertinent information if needed.

There is a caveat or two associated with this approach, though. Many developers, myself included, have implemented this technique and rendered the player helpless for a time. That is, a situation has been created where the player can do nothing except wait until the NPC is finished talking. No amount of ingenious dialog can make up for a lack of interactivity. If the only action available to the player is to type "z", the game's "player experience" will suffer.

Another thing to consider when designing a conversation script is that people generally do not repeat the same information unless asked to. In a conversation where the player actually participates, it is possible that an NPC will be asked a question earlier than he was scripted to volunteer it. Repeating the information after it has been already been told is repetitive. What is needed is a way for the NPC to remember what has already been said and to move on to the next item in the script.

The "conversation script topic," a variation of morphing topics, is one of the more useful techniques in creating realistic, non-repeating dialog. It is particularly powerful because it can utilize other KnowledgeTopic objects so that the information can be both asked for and volunteered.

A couple of minor modifications should be implemented to facilitate knowledge scripts. The first is a method of recording whether or not a topic has been communicated before. We can accomplish this by defining and setting an attribute. Arbitrarily, we'll call this 'hasbeentold':


 attribute hasbeentold;   

The next modification we can make will ensure that the NPC doesn't "double talk" or say two things at once. This can happen when the NPC is following a script and the player asks a question. If not coded correctly, the NPC will answer the question and then continue to say whatever comes next on the topic script. Game conversation seems to more closely parallel reality if the NPC only "drives" the conversation when the player isn't talking to him. To accomplish this, we can add a property to the NPC class that records the last move he/she spoke in:


 ...add to NPC class   
   , LastMoveSpoken 0

These values can be updated when a topic is conveyed. The TellAbout routine, in the KnowledgeTopic class, is the obvious place:


 ...in the KnowledgeTopic class
   , TellAbout[to from;
      if(from provides LastMoveSpoken)
      from.LastMoveSpoken=turns+1;
      give self HasBeenTold;
      if((self.#topicinformation/2)>1){
         if(from provides ProcessDialog) 
            from.ProcessDialog(to, PersonalizeTell,   
               self, TopicInformation);
         else{
            if(from==player) print "You say, ";
            else print (The)from, " says, ";
            FlushDialog(self,TopicInformation,0);
         }
      }
      else PrintOrRun(self,TopicInformation);
      if(self has learnable) 
      Self.MemorizeFor(to);
   ]

The final new addition that we will make to the KnowledgeTopic object, which we have been developing in this article, is a routine called SayOrRecurse. The sole purpose of this routine is to facilitate a conversation script. This routine, which is called by the TellAbout routine, checks to see if the specified KnowledgeTopic has already been conveyed. If not, then it will print it and return. If so, then the TellAbout routine of the current object is called (recursively) to request the next scripted topic:


 ...from KnowledgeTopic class
   , SayOrRecurse [o to from;
      if(o hasnt hasbeentold) o.TellAbout(to, from);   
      else self.TellAbout(to, from);
   ]

Opinions vary concerning the use of recursion. Suffice it to say, misuse of this technique can cause run-time errors in the Z-Machine interpreter. Although it can be a useful addition to your conversation repertoire, use this method with care.

Before creating a conversation script, an author should have a clear idea of how the conversation should flow. A design technique used by many authors is to write the entire conversation the way they would prefer it to read first. Once this has been done, the conversation can be logically separated into pieces. Almost always, these pieces of text can be classified into two categories: dialog that is the answer to a question which the player will possibly ask, and dialog that will never be asked about, and serves no purpose other than to make the NPC seem more believable.

The former type, which can be inquired about, is best stored in a KnowledgeTopic of it's own. Generally, these types of dialog pieces will compromise the bulk of a conversation script. The latter type more often appears at the beginning and end of a script. Generic statements, such as variations of "hello," and "goodbye", are classified in this category.

What follows is an example script object for a conversation between a doctor and the player who has just woken up in a hospital. Notice that script steps 2, 3, and 4 utilize three other KnowledgeTopics and so will be brought up, even if not directly asked about by the player.


 KnowledgeTopic DocScript_t
   with     count     0, KnownBy doctor
   , TellAbout[towho fromwho;
      self.count=self.count+1;
      switch(self.count) {
         1: print "^~How are you feeling, Mr. Valentine?  I'm pleased to 
               see you've awakened,~ the man says to you. ";
         2: self.SayOrRecurse(doc_who_t,towho, fromwho);
         3: self.SayOrRecurse(doc_me_t, towho, fromwho);
         4: self.SayOrRecurse(hosp_where_t, towho,fromwho); 
         5: print "~Okay then,~ says the doctor as he opens the door.  ~If you   
               have anymore questions, feel free to buzz the nurse.~  He 
               leaves and closes the door behind him.";
            self.TellAbout=NULL;
      }
   ]
 ;
 KnowledgeTopic doc_who_t 
   with     name 'him' 'himself' 'man' 'doc' 'doctor' 'who'
   , KnownBy doctor
   , query  "^~So who are you?~ I asked the man. "
   , TopicInformation "^~My name is Doctor Harris.  I'm the resident 
      Nuerological specialist.~ ^^~Nuerological.~ You repeat, 
      then touch your head probingly. No bandage.^^The doctor 
      nods. "
 ;
 KnowledgeTopic doc_me_t 
   with name 'myself' 'self' 'me' 'head' 'damage' 'condition'
   , KnownBy doctor
   , query "^~So what's my condition, Doc?~ You ask."
   , TopicInformation "^^~You're the talk of the hospital right now,~ he replies. 
      ~You've been comatose for several weeks. CAT scans showed 
      extreme cerebral dysfunction. To be frank, the prognosis was 
      bleak.^^~This morning, for no apparent reason, monitors 
      registered a return to normal brain activity. Further examination 
      showed burned tissue and broken ribs healed. I've never seen 
      anything like it before.~^^Cerebral dysfunction... You think 
      about that for a moment when it suddenly dawns on you that
      you cannot remember anything about yourself, including your 
      own name!"
 ;
 KnowledgeTopic hosp_where_t
   with name 'where' 'hospital' 'location'
   , KnownBy doctor
   , query "^~So where am I?~ ,You ask the doctor. "
   , TopicInformation "^~You're at Saint Augustine Hospital.~^Saint Augustine. 
      You don't recognize the name. "
 ;

Additionally, a believable NPC will have a collection of additional topics that are not volunteered, but are clarifications of statements made in the script. This NPC will be able to answer questions the player may have in the course of the conversation but will not volunteer this information unless directly asked about it:


 KnowledgeTopic doc_amnesia_t 
   with name 'memory' 'amnesia'
   , KnownBy doctor
   , query "^~So could my memory be affected?~ You ask."
   , TopicInformation "^^~Certainly,~ he said. 
      ~Without a doubt, the explosion, and   
      events immediately preceding and following it would be   
       unclear to you. That's normal.  There may even be 
       more substantial memory loss.~"  
 ;
 KnowledgeTopic explosion_t 
   with name 'explosion'
   , KnownBy doctor
   , query "^~What happened? You said I was in an explosion? ~"
   , TopicInformation "^^A sad look comes across the doctor's face. 
      ~I'm afraid that I don't know many of the details behind what 
      happened to you. I only know that there was an explosion of some kind. 
      I have no information as to the condition of your family.~^^You watch 
      his eyes as he says this to you. For reasons you don't know, you 
      concentrate upon pupils as they dilate. In the back of your head, some    
      forgotten memory tells you that this means he is lying. "
 ;

Now that we have a workable script, we need an NPC capable of driving the conversation:


 NPC -> doctor "man" 
   with name 'doc' 'doctor' 'harris' 'man'   
   , description "The man is dressed in a white coat.  He 
      appears to be a doctor."
   , each_turn[; if(self.LastMoveSpoken==turns) return;
      if(DocScript_t provides TellAbout) 
      DocScript_t.TellAbout(player,self);]
 ;

This simple NPC will converse with the player each turn about our script object. The conversation can happen in several different ways now. One possible play out of the script follows:


 "How are you feeling, Mr. Valentine? I'm pleased to see you've    
 awakened," the man says to you. 
   
 >x man 
 The man is dressed in a white coat. He appears to be a doctor.
 "My name is Doctor Harris. I'm the resident Nuerological specialist."   

 "Nuerological." You repeat, and then touch your head probingly. No bandage.  
 The doctor nods. 

 >ask doc about head
 "So what's my condition, Doc?" You ask.
 "You're the talk of the hospital right now," he 
 replies. "You've been comatose for several weeks. CAT 
 scans showed extreme cerebral dysfunction. To be frank, the prognosis 
 was bleak.
   
 "This morning, for no apparent reason, monitors registered a return 
 to normal brain activity. Further examination showed burned tissue and 
 broken ribs healed. I've never seen anything like it before."

 Cerebral dysfunction... You think about that for a moment when it suddenly   
 dawns on you that you cannot remember anything about yourself, including   
 your own name!

 >ask doc about hospital
 "So where am I?", You ask the doctor. 
 "You're at Saint Augustine Hospital."

 Saint Augustine. You don't recognize the name. 

 >ask doc about memory
 "So could my memory be affected?" You ask.
 "Certainly," he said. "Without a doubt, the explosion, and events   
 immediately preceding and following it would be unclear to you. That's 
 normal. There may even be more substantial memory loss."

 >ask doc about explosion
 "What happened? You said I was in an explosion?"
 A sad look comes across the doctor's face. "I'm afraid that I 
 don't know many of the details behind what happened to you. I only know   
 that there was an explosion of some kind. I have no information as to the 
 condition of your family."

 You watch his eyes as he says this to you. For reasons you don't know,  
 you concentrate upon his pupils as they dilate. In the back of your head,  
 some forgotten memory tells you this means he is lying.

 >z
 Time passes.
 "Okay then," says the doctor as he opens the door. "If you have   
 anymore questions, feel free to buzz the nurse."  He leaves and closes   
 the door behind him.

Of course this is just a basic example, but notice that the script adapts and skips over topics already asked about by the player. This technique has the advantage of ensuring that the player gets information, even if he does not think to ask the appropriate question, but also adapts to a player's questions, ensuring a more realistic conversation.

Inanimate Knowledge Transfer

It was mentioned above that characters are not the only objects that can utilize the knowledge objects. Any book, such as "Waldecks's Mayan dictionary" from the Inform Designer's Manual, or the ever-popular spell book is an ideal candidate for an inanimate object housing KnowledgeTopics. The consult verb should be extended much as the tell verb was:

   
 [ConsultTopicSub; second.TellAbout(actor,noun); ];

 extend "consult" first * noun 'about' scope=TopicInTarget-> ConsultTopic   
   * noun 'on' scope=TopicInTarget -> ConsultTopic ;  

Now we can create the spell book with a specialized ProcessDialog routine, specifically tailored to a reference book:


 Object -> spellbook "dusty old spell book" 
   with name 'spell' 'book' 'dusty' 'old'
   , ProcessDialog [talkto procedure knobj info;  
      talkto=procedure; !warning supression  
      print "Flipping through the old book uncovers the following entry:^";  
      FlushDialog(knobj,info,0);
   ]  
 ;

And of course a spell for the spell book:


 KnowledgeTopic lightspell 
   has learnable
   with name 'lumos' 'light'
   , KnownBy spellbook 0 0 0 0 0 0 0 0 0
   , TopicInformation "The lumos spell provides light" "."  
 ;
Since the spell book is not a "creature," as defined by the standard library, we cannot tell the spell book anything (this is a good thing), and so the spell book cannot learn new KnowledgeTopics as NPCs do.

In a sense, a journal can "learn" about a topic when someone writes in it. A good exercise for the reader would be to implement a "write" verb in which knowledge can be recorded in non-animate objects.

In Closing

The reader may have noticed that, although Ask and Tell are thoroughly discussed in this article, Answer (or say) has been left relatively untouched. It has been my experience that it is often best to treat Answer and Tell as two forms of the same command. Essentially, I choose to treat:


 >Say snowwhite to dwarf 


in the same manner as I would treat:


 >tell dwarf about snowwhite 

As stated previously, my way is not the only way. Distinguishing between the two commands may be useful in a number of scenarios. The above "spell book" example, set in a world where characters cast spells, would be one such situation. For example,


 >say lumos   

would be better handled in the same manner as


 >cast lumos   
 A bright light appears and brightens the room.   

instead of


 >tell dwarf about lumos
 You give the dwarf a condescending gaze.  "The lumos    
 spell provides light,"  You say.

This distinction is best made by the developer.

Also, in the interest of avoiding dependencies, all references to specialized print rules have been left out of the samples above. Pronoun print rules, however, are especially useful when creating the sort of conversation topics that we have been developing in this article. Techniques discussed in the previous article "
Pronouns on Steroids" can be of significant aid.

About the Examples...

To aid the reader, the examples given in all three sections of this article have been bundled together in single
source file.

Additionally, the ORLibrary entries ORKnowledgeTopic.h and ORNPC.h contain a complete, and somewhat more progressive, implementation of the techniques discussed in this article.

For review

Emily Short's
page on NPC design, although geared for platform independence and not for Inform specifically, is nevertheless inspirational in its discussion.

Additionally, her game "Galatea", is an exquisite example of NPC conversation.

Also, although mentioned previously, the conversation section of Roger Firth's Infact page is especially recommended.

Next:
§3: NPC Actions

Previous:
§2b: Advanced Techniques



Copyright 2004 - 2017 : Jim Fisher
OnyxRing.com has been given the potentially non-exclusive right to display the content of this article. However the original author retains all rights. Permission to reproduce this article -- either in part or in whole -- is left strictly to the discretion of the original author.

Table of ContentsAuthorsSearchIndex
Would you
recommend this
article to
someone else?
You bet I would! Heck No!