Multiplayer Interactive-Fiction Game-Design Blog



Download 8.87 Mb.
Page129/151
Date02.02.2017
Size8.87 Mb.
#15199
1   ...   125   126   127   128   129   130   131   132   ...   151

10 Emotions – cAI


AIs can have emotions for happy/sad and anger/fear.


Emotions - cAI

AI's store two emotions: Happy/sad and Angry/afraid. Each

emotion ranges from -10 (very sad or very afraid) to +10 (very happy

or very angry).


You can get the AI's emotions using AIEmotionsGet().


The emotions can be adjusted or set

with AIEmotionsSet(). This either sets the emotions

directly, or it adjusts the emotions, making the NPC happier, sadder,

angrier, or more afraid. The amount that a NPC's emotions

are changed is affected by pAIEmotionsChangable,

allowing for some NPCs to be more excitable than others.

Emotions gradually return to 0 over time. (Actually, they

eventually return to the AI's pAIEmotionsDefault over

time.) The speed of this return is controlled

by pAIEmotionsCalmDown.

You may have the AI's emotions changed based on conversations

the PCs are having with the AI, or good/bad news that the AI

hears, etc.


An AI's emotions are automatically adjusted during combat.

When the AI determines what its odds are for winning, this

is expressed on the AI's face as either anger (confident of

winning) or fear (almost ready to run).

If an AI sees an enemy killed or knocked unconscious it becomes

happier, and vice versa for friends.

11 NPC conversations – cAI


This tutorial provides the details about how conversations with NPCs work.


NPC conversations - cAI

In Circumreality, players converse with NPCs using the chat window, the

same way that players talk with other players. The NPCs use

simple natural language processing (NLP) to understand what

the player is asking for.


Programatically starting up a conversation

When an AI hears speech (PerceiveSpeak()), or sees an emote

(PerceiveEmote()), or the PC identifies himself (PerceiveIdentifySelf()),

it's first task is to determine if the PC is talking to the AI

or another PC or NPC in the room. To do this, the AI

calls AIConversationIsTalkingToMe(). This method

uses various tricks to determine if the PC is talking with the AI.

If it is, a conversation is started up (see below) and the

method returns TRUE. If not, the method returns FALSE.

You will probably not need to call AIConversationIsTalkingToMe()

though.

If AIConversationIsTalkingToMe() starts up a conversation,

it calls AIConversationStart(). This method adds

an entry to pAIConversations so the AI knows

what characters it's talking to. It also causes

AILikeEmoteFirstMeet() to be called so that players can tell

how much the AI likes them. Finally, it adds

the oAIGoalConversation goal to the AI at

priority level 10. This causes the AI to stop doing what it

was doing until the conversation is finished (unless there

is a higher priority goal). The conversation will be finished

when the PC says "Goodbye" (or the equivalent), or leaves the

room.

You may wish to call AIConversationStart() if you wish your AI

to start up a conversation with a PC on its own. (See below.)

When the AI walks into a room, or when the AI sees the player

character walk into its room, AIConversationWantToStart() is

called. This lets the AI start up a conversation with PCs.

For example: An inn keeper might ask the player if they'd

like a room tonight without the player's ever approaching

the inn keeper. The default method will

have AI's who see well-liked PCs stop and say hello.


You may wish to write your own AIConversationWantToStart() for

chatty NPCs like inn keepers, friendly NPCs who know the PC,

or beggars. You will need to call AIConversationStart() to

start up the conversation though.



Conversing

When a PC speaks, emotes, or introduces itself to an AI, the

speech (or emote or introduction) is passed

onto PerceiveConversation(). The method isn't

called immediately, but has a 1 second delay. This makes for

more realistic response times, as well as ensuring two AI's

in conversation don't get into an infinite loop.

The PerceiveConversation() method does this following:





  1. Sets up all the rule-sets to be used by the NLPParse() function.

    For more information on parsing and the NLPParseXXX() functions,

    see the tutorials on command-line parsing.





  2. Calls NLPParse().





  3. Based on the parse results, it calls

    into ParseC_XXX() to determine if the parse makes sense.

    Command-line parsing calls into Parse_XXX() methods.





  4. The ParseC_XXX() method with the highest score is chosen,

    and its ParseActC_XXX() method is called to have

    the AI react to what the player said.





  5. The AI may respond to the player's question, and in turn

    ask one of it's own by calling AIConversationAskQuestion(). For

    example, the AI may ask the PC his name.






Now for some more details on this process:



Rule sets

For the natural language processor to work, it must have

a number of "rule sets" loaded into a "parser". In the case

of conversations, the parser name used is gConversationParser.

The rule sets can be customized for each NPC, but the

default behaviour is to include the following rule sets:







  • rParserCommon - These are rules that are common

    to both the command-line parser and the conversations. They

    do parse converstions like "colour" to "color", and

    "we're" to "we are".







  • rParserConversation - Parsing rules that all

    AI's understand. These include "What time is it?" and "Do

    you know where NAME lives?".





  • Pronouns - Rules that convert pronouns, such

    as "it" and "him", to object IDs. This allows conversations

    to use the pronouns. For more information on

    pronouns in a conversation

    see AIConversationPronounSet() and AIConversationPronounSetString().





  • NPC and speaker possessions - The names of all

    objects held by the NPC and the speaker (but not those in

    containers). This allows conversations to reference objects

    held by the NPC or the player.







  • Names that the NPC knows - A list of all the

    PC names that the AI has learned when the characters

    identify themselves. The rules convert the names into

    the character's object ID. These are the names from CustomNameGet()

    and CustomNameSet().





  • Names of all NPCs on the map - A list of all

    NPCs in the NPC's original map, so that their names are

    converted to object IDs. It's useful for villages and cities (which

    are all one one map), so that NPCs will know about other

    NPCs in the city. See pAnonymous for information

    on making a NPC unknown to other NPCs.







  • Names of all rooms on the map - A list of all

    rooms in the NPC's original map, so that their names are

    converted to object IDs. It's useful for villages and cities (which

    are all one one map), so that NPCs will know where the "inn" is,

    and where the "church" is. See pAnonymous for information

    on making a NPC unknown to other NPCs.







  • Names of all zones, regions, and mapps - Lets the NPC answer

    questions about specific zones, regions, and maps.







  • Names of all factions - Lets the NPC answer

    questions about specific factions.







  • Names of all skills - Lets the NPC answer

    questions about specific skills.







  • pAIConversationParser - This property may

    provide additional parsers for the NPC's AI. The extra

    parsers can, in turn, add new rule sets. (See below.)





  • Factions - If the NPC is a member of any factions,

    the NPC's factions' AIConversationParser() method will be

    called. This can add rule sets of its own, which are specific

    to a faction. For example: Members of the "Leatherworkers guild" might

    include rule sets that allow players to ask about leatherworking.





  • AIConversationParser() - NPCs can provide their

    own AIConversationParser() methods, which can add new rule

    sets. (See below.) For example: AI's from the cMerchant class

    might include rule sets for "How much does XXX cost?" and "I

    want to buy XXX."



cAI.PerceiveConversation() adds these parser's by calling

into the AI's AIConversationParser(). This method

adds many of the above rules into the gConversationParser parser.

It also calls oParserConversation.AIConversationParser(),

which adds the rest. This object supplies most of the conversation

code and methods that are common to all AIs, so that each AI

doesn't have to keep track of the hundreds of conversation methods,

such as ParseC_XXX() and ParseActC_XXX(). It provides a similar

role to oParserVerb for commands. More rule sets are also

added by calling AIConversationParser() for each of the factions

that the AI is a member of, allowing the AI to "know" information

specific to the faction. Since AIConversationParser() is

layerable, sub-classes of the AI, such as cMerchant, can

also provide extra rule sets.

When AIConversationParser() is called in an object, it does

the following:





  1. Identify the rule sets it wishes to activate,

    such as rParserCommon from above.







  2. See if the rule set is already loaded into the parser by

    calling NLPRuleSetExists().




  3. (In order to prevent

    resources from piling up, rule sets will be deleted from time to

    time, so it's always a good idea to make sure the rule set

    still exists, even if you're sure it was created for the last

    conversation.)

  4. If the rule set exists, the method should

    call NLPRuleSetEnableSet() to make sure the rule

    set is active.




  5. Before PerceiveConversation() calls the AIConversationParser()

    methods, it will have disabled all of the rule

    sets in gConversationParser.

  6. If the rule set does not exist, create it and

    call NLPRuleSetAdd(), followed

    by NLPRuleSetEnableSet().





  7. If the object provides any ParserC_XXX() methods, which

    it probably does, then add the object to the ParserObjects

    list that's passed into the method. (See below.)







Finding the best parse

Once AIConversationParser() has been called, and all the rule

sets have been added, PerceiveConversation()

calls NLPParse() to produce a list of possible

parses for what the player said.

The first word of each hypothesis is examined:


If it begins with a "`", then ParseC_XXX() is called for each

of the objects added to ParserObjects by AIConversationParse().

XXX is the string after the "`". Thus, if the character said,

"My name is Fred", one of the hypothesis would be "`identifyself Fred".

This would cause ParseC_IdentifySelf() to be called for

each of the objects in ParserObjects. (More about this later.)

If the first word has no "`", then the method(s) called are

determined by the current conversation state.

(See AIConversationStateSet()). The method

used will be "ParseC_State_XXX", where XXX is replaced with

the state name. Thus, if the NPC asked the player, "What is your

name?", it could set the converstion state to "AskedName". Then,

if the player replied, "My name is Fred", this would result in

a "`identifyself Fred" parse, that would call ParseC_IdentifySelf().

However, if the user replied with "Fred", the first word

of the parse wouldn't have a "`", so ParseC_State_AskedName() would

be called. (If the NPC had asked for the PC's favorite color,

it could use the "AskFavoriteColor" state, so as to not

accidentally think that the PC's name was "red" or that his

favorite color was "Fred".)

The ParseC_XXX() methods accept the following parameters:







  • Actor - The AI being spoken to.





  • Speaker - The character doing the speaking.





  • CommandTokens - A list of words spoken. Some of the

    words may be converted to objects. Thus, if the user said,

    "I like your lantern", this might be ["`ilike", oLantern],

    whereas "My name is Fred" would be ["`identifyself", "Fred"].







  • BestParse - This is the best parse for the CommandTokens

    so far. If the ParseC_XXX() method comes up with a higher score,

    it should overwrite the elements of BestParse(). See below.





  • Returns - None.



The ParseX_XXX() method does the following:





  1. It examines the CommandTokens to see if can

    make any sense of them.



  2. If it can't make any sense of the tokens then

    the method should just return without changing BestParse().



  3. If the tokens do make sense, then the method needs

    to calculate a score for how much they make sense.

    Use 1.0 for a statement that makes perfect sense,

    such as ["`identifyself", "Fred"]. If it makes less sense,

    such as ["`identifyself", "a", "new", "person", "here"] then

    return a lower value, like 0.5. If it makes very little

    sense then use an even lower number.





  4. If the score is more than BestParse[0], then you'll need to

    replace the contents of BestParse. If it isn't then

    just return. (BestParse[0] will be Undefined if there hasn't

    been a good parse yet.)



  5. Replace BestParse[0] with the score you calculated.







  6. BestParse[1] should contain the conversation state(s) that

    this response is valid for. This is either a single lower-case string,

    or a list of lower-case strings. They can contain

    wildcards. (See ConversationStateCompare().)




  7. If the conversation states in BestParse[1] do not match

    any of the current conversation state the the AI is listening for

    then the score will be automatically reduced. This way, if

    a player's response is ambiguous, the AI can use the expected

    conversation state to disambiguate. Example: If the AI asks the

    player a question and the player answers, "Yes, I do", resulting

    in in ["`answeryes"], the BestParse[1] state will allow the

    AI to determine if the "Yes" belonged to "Do you like me?"

    ("askiflikeme" conversation state) or "Do you like eating turkey?"

    ("askifliketurkey" conversation state).

    Furthermore, if the player responds with a question or answer

    that isn't in the AI's current conversation state, this will

    annoy the AI slightly and reduce the AI's like/trust for

    the PC. A change in topic is determined by comparing the

    BestParse[1] state with the AI's converastion state

    set in AIConversationStateSet().




  8. BestParse[2] should be filled with a callback

    that acts on the speech. Usually, this will

    be "this.ParseActC_XXX", where XXX is the first word

    in the parse, such as "this.ParseActC_IdentifySelf".







  9. BestParse[3] and higher are passed into

    the BestParse[2] callback. Their meaning is up to the

    BestParse[2] callback.




After all the ParseC_XXX() methods have been called, the

one that filled in the highest BestParse[0] will be kept.

NOTE: The values of BestParse[0] are adjusted down if

the conversation states in BestParse[1] do not match

the AI's current conversation state,

from AIConversationStateGet(). The BestParse[0]

score is also multiplied by the score produced by

the rule-set parsing.


Responding to the parse

If no parse was successful, then AIConversationDontUnderstand() is

called. This will cause the AI to answer with,

"Sorry, I don't understand.", or something to the effect.


Otherwise, the callback from BestParse[2] will be called.

It should accept the following parameters:





  • Actor - The AI being spoken to.





  • Speaker - The character doing the speaker.





  • ParseList - The values from BestParse[3] and

    higher. The meaning depends upon how the method wishes

    to interpret them.





  • Returns - See below.



The ParseActC_XXX() method should do the following:







  1. Have the NPC speak or act based on how it

    interprets the speech.





  2. The NPC might wish to call AILikeSet() if

    what the player says causes the NPC to change his opinion

    of the player.




  3. Tip: If something that the player says positively

    impaces the AI's view of the PC, make sure to use AIMemoryGet() and

    AIMemorySet() to remember the event. Otherwise, players will discover

    that every time the compliment a NPC about their hair, the

    NPC's like/trust will go up. You'll find players repeated complimenting

    a NPC about its hair until the NPC is super-friendly.


  4. If the conversation sentence mentions an object that might

    be used as a pronoun later, make sure to

    call AIConversationPronounSet().





  5. If the AI's converation state won't change based on the sentence,

    then return NULL. This is particularly useful

    for emotes, since many times an emote will just be ignored

    by the AI, and it will sit waiting for some speech.



  6. If the AI always responds to this parse with

    a question, then code the question into this method and

    call AIConversationStateSet() to set the new state.

    Then, return TRUE.


  7. Example: If this method handles the player asking, "Where is

    the cave of Wigeywom?" and the NPC's reaction to this is

    always, "Why do you want to know?", then this response should

    be coded in, and the new conversation state should be set to

    "npc_asks_why_want_to_know_wigeywom".

  8. If the AI has no particular follow-up response then

    return the new conversation state. This can either be a single

    lower-case string, or list of lower-case strings. Normally,

    the like/trust penalty for the PC jumping to a different

    topic is [-0.5, 0]. If you wish a different pentaly, then

    return a list of states, but set the first element of the

    list to your [Like, Trust] change.




  9. For example: The NPC asks the PC, "What's your name?". This

    method interpret's the player's answer of "My name is Fred."

    Since nothing logically follows, returning a new state

    will allow the AI's intelligence to select a new topic

    of conversation by calling AIConversationQuestionAsk().

    See below for details.




Conversation questions

Conversations are not just an interrogation where the player

asks the NPC a series of questions and the NPC dutifully answers.

NPCs have their own agendas... When a conversation is first

begun, the NPC AI code initializes a list of "questions" that the

NPC wants to ask the PC.


These "questions" can also include statements. Some examples

of questions are:





  • Greeting - When a conversation first starts the

    NPC will say "Hello" (or similar) to the PC.







  • What is your name? - If the player has not

    given his name, the NPC will ask for it. (Depending upon the

    NPC's personality.)





  • My name is X. - The NPC may wish to identify itself

    to the player.







  • Rumors - If there's a hot rumor going around,

    or even just the latest sports trivia, the NPC may find a good

    opportunity to relate it.





  • Friendly questions - The NPC may ask some friendly

    questions, such as, "Where do you come from?", "Do you have

    any family nearby?", etc.





  • Interrogating questions - The NPC may wish to learn

    more about the player, and might have questions to ask

    like, "To what duke does your allegiance lie?".



To programatically add a question to the NPC's list,

call AIConversationQuestionAdd(). You'll need to

pass in a few parameters. Some parameters to note are:







  • Priority - Higher priority questions are more likely

    to be asked.







  • ConversationState - The NPC will try to bring

    up questions that are appropriate to the current conversation

    state. If the player brings up fishing, the AI is more likely

    to mention the latest rumor about the giant turtle in the lake.

    If the conversation states don't match then the effective

    priority will be about 1/4 of the priority value.







  • Callback or speak string - If the question is chosen

    to be spoken, then a callback will be called. (See below.) If

    you don't need a callback, then just pass in a string to be

    spoken.



  • The callback accepts the following parameters:




    • Actor - NPC/AI object.





    • Speaker - Character (PC) that the AI is speaking to.





    • Parameter - A parameter passed in

      when AIConversationQuestionAdd() is called.







    • Returns - The callback needs to

      return the new conversation state(s) to use after the

      question (or rumor) has been spoken. This is either a lower-case

      string, or a list of lower case strings. Normally, the non-sequiter

      penality for [Like, Trust] is [-0.5, 0]. If you wish to change this,

      then return a list of conversation states, with the first element

      being the [Like, Truest] change for a non-sequiter.







You may wish to call AIConversationQuestionAdd() in some of

the ParseActC_XXX() methods if the player speaks something

that trigger's the NPC's memory or curiosity. For example: If the

player mentions "The order of the rising baloon", the NPC may

become suspicious of the PC and wish to ask questions that

allay or verify the suspicions. One way to do this would be

to use AIConversationQuestionAdd() to add a number of questions

about "The order of the rising baloon".


You can also call AIConversationQuestionRemove() to

remove a question from the list.

Most questions are added in

the AIConversationQuestionFill() method. This method

(part of the cAI class) is called whenever a conversation is

started up. It is passed +1 is the AI initiates the conversation,

and -1 if the PC initiates the conversation. (It's also called

if all the AI's questions have been used up and it needs new ones.

A value if 0 is then passed in.)


The default implimenation of AIConversationQuestionFill() calls

the same method in all the AI's factions, as well as those

objects listed in pAIConversationParser. It also calls

oParserConversation.AIConversationQuestionFill(). The method

is layerable, so sub-classes can include their own questions.

For example: You could have a cMobileLikesToTellJokes that would

occasionally tell a joke using AIConversationQuestionFill().


oParserConveration's version of AIConversationQuestionFill() will

set up some default "questions" that are common to all NPCs.

This includes a "question" that causes the NPC to say "Hello" (or

similar) and ask the player's name (depending on the AI's

personality properties).


The added questions are used when a ParseActC_XXX() method

returns a conversation state or list of conversation states. (But

not if the callback returns NULL or TRUE.)

To select a question to ask, AIConversationQuestionAsk() is

called.

This method finds the question with the highest score,

given the current conversation state. If the score is less than

a value calculated from the

AI's pAIConversationTalkativeness personality property

then the AI won't say anything. (You might be able to make the

talkativeness property for some AIs vary depending upon how

drunk they are.) Otherwise, the AI will remove the question from

the list (so it's not spoken again) and then execute the callback,

or speak the string.

In some cases, automatically removing the question may cause

some problems for a conversation. For example: If the AI asks

the PC for his name, but the PC evades the answer, the AI won't

ask for the PC's name until the next conversation. You may want

some AIs to keep asking the player character's name until the AI gets

an answer. To do this, you can play the following trick:



  1. Have the question callback clear the list of questions

    by calling AIConversationQuestionRemove(Actor, NULL).



  2. The next time the AI has the opportunity to ask a question,

    it will realize that there are no questions left. This

    will cause it to call AIConversationQuestionFill().





  3. Have the AI's AIConversationQuestionFill() code test to make

    sure that the question was answered. If it wasn't, add it

    back in with a very high priority (such as 4.0).








Built-in parsers and questions

oParserConversation includes many built-in parsers and questions

that the AI will ask. Some of them include:





  • ParseC_AskAIName allows the player to ask the

    AI its name.







  • ParseC_Emote deals with emotes from the player.





  • ParseC_Goodbye lets the player say goodbye to the NPC.





  • ParseC_Hello allows the player to say hello.





  • ParseC_IdentifySelf is called when the player

    identifies himself, so says, "My name is XXX."







  • ParseC_State_NameOnly is activated when the AI

    asks the player his name. This allows a one or two word response.







  • QuestionC_AskPCName causes the AI to ask

    the player's name.







  • QuestionC_GoAway tells the player to go away

    and ends the conversation.







  • QuestionC_Goodbye causes the AI to say

    goodbye and ends the conversation.







  • QuestionC_Hello lets the AI say hello.





  • QuestionC_SayName causes the AI to say

    it's name.









Odds and ends



  • The "anything" conversation state is used when

    the AI is expecting any (normal) topic. The AI will expect this

    state after the AI and PC have said hellos.







Download 8.87 Mb.

Share with your friends:
1   ...   125   126   127   128   129   130   131   132   ...   151




The database is protected by copyright ©ininet.org 2024
send message

    Main page