15 Conversation trees – cConvTree
How to create pre-scripted conversations between two or more NPCs.
Conversation trees are a tride-and-true method for talking to NPCs,
and the standard NPC conversation system for most CRPGs. For those
of you not familiar with the term, you'll be familiar with the
scenario: The NPC says something like "What's your favorite color?", and
the player is given a menu of choices, such as "Red", "Green", or "Blue".
Once the player selects the color, the NPC makes a statement and (often)
a new question is asked by the NPC.
While Circumreality doesn't rely on conversation trees (most CRPGs do), it does
allow for them.
Creating a conversation-tree object
To create a conversation tree (combination of question, player answers,
and NPC's responses to the answers), you need to create a conversation-tree
object. To create a conversation-tree object:
-
Create an object, as normal, but base it off
of cConvTree. For this example, call
it oConvTreeFavoriteColor.
-
The object should not be contained in anything.
-
Fill pConvTreeQuestion with the question that the NPC
asks, such as "What's your favorite color?"
-
pConvTreeAnswers should be filled in with a list of
answers to the NPC's question, such as ["Red", "Green", "Blue"].
-
pConvTreeResponses is a list containing the responses
to each of the player's answers, such as ["Red? I hate red.",
"Green is nice.", "Blue is my favorite color."]
-
If you wish one or more answers to lead to another conversation
tree, then set pConvTreeResponsesNext to indicate
subsequent conversation trees.
-
Since the player's answers may change the NPC or other parts of
the game, you'll probably need to write your
own ConvTreeResponse() to handle and coding.
For example: If the player likes red, the NPC might give them
a red riding cloak; this needs to be handled by code.
You may also wish to set the following properties and methods:
-
Set pConvTreeMustAnswer to TRUE if the NPC requires
an answer from the player and won't be distracted by attempts
to change the subject.
-
pConvTreeQuestionRepeat is spoken if the player
doesn't answer the NPC's question properly and the NPC has to
repeat the question.
-
If you wish to customize pConvTreeQuestion or
pConvTreeQuestionRepeat based upon the current situation,
you can write your own ConvTreeQuestion() method.
-
You can allow for alternate phrasings of the answers, such as
"I like red best." by proving
a pConvTreeAnswersParse property.
-
Answers won't always be valid every time a conversation tree
pops up. For example: If the NPC asks the player, "What inn are
you staying at?", you could include answers for all the inns in
town. However, if the player character didn't know about some of
the inns, you might want to invalidate and hide some of the
answers. To do this, write your
own ConvTreeAnswerIsValid() method.
-
pConvTreeResponseNotAnswer controls what the NPC says
if the player tries to change the subject and avoid answering
the question.
-
pConvTreeResponseNotAnswerLike controls how much
the NPC's like/trust of the PC changes when the player tries
to change the subject.
-
pConvTreeImportantDecision will cause all the context-menu
options to be flagged so the player knows they're making an important decision.
-
If you invoke the converation tree using AIConversationConvTreeAdd()
then you may wish to write a ConvTreeIsValid() method
so that the conversation tree won't be brought up in the conversation
if it's no longer valid.
-
pConvElementConversationState is used when you call
AIConversationConvTreeAdd() to determine the conversation state after
the conversation tree finishes.
Activating a conversation tree
There are several ways that you can activate a conversation tree:
-
Call oNPC.AIConversationConvTreeSet() to start the conversation
tree immediately.
-
oNPC.AIConversationConvTreeAdd() will add the conversation tree
to the NPC's list of "questions" that the NPC might choose to bring up, depending
upon where the conversation takes it. Unless you set the priority to a very
large value (greater than 1.0), there's no guarantee that the conversation
tree will be run immediately.
If you're providing a oNPC.AIConversationQuestionFill() method
for your NPC, which controls what questions the NPC asks, you can
also call AIConversationConvTreeAdd().
-
You can have a cFactoid invoke a conversation tree by
setting oFactoid.pFactoidConvTree.
Often, you'll want NPCs to ask a series of questions to players as they get
to know them. The questions will start out easy, like "Who do you want to win the
world series?" and gradually work up to something more contentious, like
"Do you want the elves or the orcs to win the war?"
Creating this question ladder is easy, just fill in oNPC.pAIConvTrees with
the series of questions and acceptable responses.
16 Conversation stories – cConvStory
Code that allows NPCs to tell stories to player.
Conversation stories - cConvStory
An important part of Circumreality gameplay is for non-player characters to
tell stories to the players. The stories not only provide knowledge
(cKnowledge) to the player characters, but they provide clues
about the NPC's personality. These clues help the player determine
what needs to be done to get on the NPC's good side.
Getting NPCs to speak stories is pretty easy, and involves the creation
of an object for each story:
-
Create an object, as normal, but base it off
of cConvStory. For this example, call
it oConvStoryFishingTrip.
-
The object should not be contained in anything.
-
Fill pConvStorySpeak with the <CutScene> resource
for the story. If you just wish the NPC to speak the story, you
can also use a <SpeakScript> resource, or even a string.
If pConvStorySpeak is a list, then the story will
be multi-part, and require the player to speak,
"Please continue" after each part.
-
pConvStoryAskSpeak should be filled with a unique string
that the player can speak to the NPC and re-hear the story.
For example: "Could you tell me about your fishing trip to the Catskills?"
-
pConvStoryIsRumor should be set to TRUE if the story
is actually a rumor.
-
pConvElementConversationState is a list of lower-case
strings that described what the story is about. For example:
["fishing", "catskills", "holiday"]. Every story needs this
property since it allows the NPC to determine which stories to use
given the conversation context.
If your code access pConvElementConversationState, it should
call ConvElementConversationState() instead, since this
method appends "story" or "rumor" to the list based on
pConvStoryIsRumor.
-
You either need to sub-class your story object from cKnowledge
as well as cConvStory or,
set pConvStoryKnowledge to the associated knowledge
object. This means that every time a player hears a story/rumor, they
will be able to access it from their "What do I know?" list, as well
as tell it to other NPCs in story/rumor swaps.
You may also wish to fill in the following properties or methods:
-
ConvStorySpeak() and ConvStoryNumParts() can
be overridden so that the story changes depending upon the player.
-
pConvStoryWontSpeak is the text that the NPC speaks
if it isn't willing to tell the player the story just yet, perhaps
because the NPC doesn't like the player enough.
-
You can write your own ConvStoryWontSpeak() to decide
when the NPC will speak the story to the player. The
default method tests the properties: pConvStoryWontSpeakLike,
pConvStoryWontSpeakFaction, pConvStoryWontSpeakFracture,
and pConvStoryWontSpeakQuest.
-
pConvStoryAskSpeakParse provides for other ways to
ask the pConvStoryAskSpeak question.
-
pConvStoryAskSpeakAlwaysShow causes the pConvStoryAskSpeak
question to always be listed in the NPC's context menu, even if the
player hasn't heard the story.
-
pConvElementPriorty affects how badly the NPC wishes
to tell the story. Values range from 1.0 (very badly) to 0.0 (doesn't
wish to tell).
-
If pConvStoryExpectPCToReciprocate then after the NPC
finishes with the story/rumor, it will expect the player to follow
with their own story/rumor. Players will be shown their, "What do I know?"
list and be able to select a story to speak back.
-
Some NPCs just like to tell stories to NPCs. If
you set pConvStorySpeakLike then every time the NPC
gets to tell a story/rumor to a character, it will like/trust the
character more. Use this to create NPCs that just want a shoulder
to cry on, or someone to brag to.
There are several ways that you can have a NPC speak a story:
-
Call oNPC.AIConversationConvStorySet() to speak the story immediately.
-
Call oNPC.AIConversationConvStoryAdd() will add the story
to the NPC's list of "questions" that the NPC might choose to bring up, depending
upon where the conversation takes it. Unless you set the priority to a very
large value (greater than 1.0), there's no guarantee that the story
will be spoken immediately.
If you're providing a oNPC.AIConversationQuestionFill() method
for your NPC, which controls what questions the NPC asks, you can
also call AIConversationConvTreeAdd(). You probably won't need to; see below.
-
For each cCharacter and cFaction, you can
set pAIConvStories to a list of stories that the character (or
faction) knows. Characters will automatically know stories assigned to
their factions.
Using pAIConvStories will automatically add stories to the list of "questions"
when oNPC.AIConversationQuestionFill() is called, as well as provide
context menu options for the stories.
-
A call to ConvStorySelect() will select a story that seems
most appropriate to the NPC, given the current conversation state and
stories that the NPC has already spoken to the player.
Properties associates with NPCs
You may wish to set the following properites for every NPC:
-
pAIConvStories, as previously stated, controls the stories
that the NPC knows. (It also knows the cFaction.pAIConvStories of any
factions to which it belongs.)
-
pAIConvStoryPreferred is a list of the topics (aka: a conversation
state) in stories that the NPC prefers to speak, as well as hear.
For example: If a NPC's pAIConvStoryPreferred is ["story", "fishing", "cars",
"holiday"], then the NPC will prefer to tell stories about fishing, cars,
and/or holidays. Likewise, they will prefer to hear such stories.
This property is very important because it defines much of
the NPC's personality.
A more flexible alternative to pAIConvStoryPreferred, it to
override the method, AIConvStoryPreferred(). The default
behavior just refers to pAIConversationStoryPreferred. However, if
you write your own code, you can specify that all dwarves like to hear
about "mining" and "gems", or that NPC's who are sad wish to hear "sad" stories.
-
pAIConvStoryStickToPreferred controls how much the NPC
sticks to its pAIConvStoryPreferred list during conversations. If this
is 1.0, the NPC will keep returning to fishing, cars, and holidays. If
this is 0.0, the NPC will easily follow the player's lead.
-
By default, if the NPC knows any stories or rumors, it will include
the context menu of "Do you know any good rumors/stories?" to save
players some typing. However, this context menu reveals to the player
the fact that the NPC knows some stories/rumors. If you wish
to hide the menu, then
set pAIConvStoryHideRumorMenu to TRUE.
As stated, the pConvElementConversationState property is very important for
stories, since it controls when a NPC will speak a story, and what
the NPC thinks about stories told to it by the player. The property
is a list of lower-case strings that describe the story, such as ["story",
"fishing", "holiday", "catskills"].
The NPC's AI determines which stories follow one another best by
calling ConvStoryConversationIntersect(), comparing
the existing conversation state to the story's conversation state.
Some converation state topics are opposites of one another. For example:
"rumor" is the opposite of story, and "work" is the opposite of "holiday".
NPCs take extra precaution to not speak a follow-on story with opposites
to the current conversation state. For example: A story about a holiday
followed by a story about work (holiday's opposite) is a social mistake
that's to be avoided. Players need to work within the "avoid opposites"
framework too.
To determine a conversation state's
opposites, ConversationStateOpposites() is called.
17 Conversation messages
Easy way for NPCs to relay messages to players.
Often times, you'll want NPCs to relay messages to PCs, such as
"Your mother called me and wants you to go home and eat dinner now,"
or "[Other PC] has been spreading bad rumors about you."
The hard way to provide this functionality is to write your
own AIConversationQuestionFill() that calls AIConversationQuestionAdd().
The easy way is to:
-
Call AIConversationMessageAdd() when the NPC learns of
a message that it wants to tell the player.
-
That's it.
If you want to remove a message from a NPC,
then call AIConversationMessageRemove(). For example:
When the player returns home from, then all the mothers in the neighborhood
that had been called with AIConversationMessageAdd() should
receive an AIConversationMessageRemove() call with the same
message string.
18 Commands, statements, and questions
How to easily allow players to give NPC commands, ask them questions, or state facts to them.
Commands, statements, and questions
There is yet another way to make NPCs more conversive; this way is
designed so that you'll find it easy to create commands (that players can
give to the NPCs), questions (that players can ask), and
statements (that players can speak to NPCs). In most cases, you may
find this easier than using factoids (cFactoid), conversation
trees (cConvTree), or conversation stories (cConvStory).
The easiest way for a NPC to answer questions is to:
-
Fill in pAIQuestionsAndAnswers with the questions and
answers that the NPC knows.
Optionally, write your own AIQuestionsAndAnswers() method.
Unfortuntely, this method is somewhat limit, so you'll
need something more flexible.
A more flexible technique
To get a NPC to respond to a custom command, answer a specific question, or
understand a statement:
-
Invent a lower-case alphabetic-only string that described
the phrase, such as "singsong" if you want players to ask NPCs
to sing a song.
-
Add a prefix to the string. Use "cmd" if it's a command,
"quest" for a question, or "state" for a statement. For the singing
example, this would create a name of "cmdsingsong".
-
Add AIListenForEnum() to the NPC. You'll need to
write this code so this appends the appropriate phrases (commands, questions,
or statements) to the list. For the singing example, this might
append, ["cmdsingsong", "Sing a song", "sing [a] song [to %1]"].
"cmdsingsong" is the name. "Sing a song" will appear on the NPC's context
menu so that players know they can ask the NPC to sing. "sing [a] song [to %1]"
is an additional parse that instructs the NPC to sing their song to
a specific character.
If you don't include the second element in the list, "Sing a song", then
the command won't appear on the NPC's context menu, requiring
users to type it in.
-
You'll need to add AIListenForIsValid() to validate
that the pameters for the %1 wildcard is correct. In this case,
if the (PhraseID === "cmdsingsong") then the method should verify that
there are no parameters, in the case of "Sing a song", or that there is
one parameter and it is another character, for "Sing a song to %1".
-
Finally, write a AIListenForAct() to actually
have the NPC sing a song.
-
If you only want the NPC to speak a phrase in reply, such as to the
question, "What is the meaning of life?", then you can get away with
only an AIListenForEnum() that fills the list in with ["questmeaningoflife",
"What is the meaning of life?", "[*4] meaning [of] life [*5]", "The meaning
of life is 42."]. The fourth parameter will be spoken if the question
is asked. The third parameter, "[*4] meaning [of] life [*5]", accepts
any sentence as long as it has "meaning of life" or "meaning life" in it.
Using this approach you won't need an AIListenForIsValid() or
AIListenForAct().
-
You can also add AIListenForEnum(), AIListenForIsValid(),
and AIListenForAct() to the NPC's cFaction, allowing
all members of the faction to respond to the phrase.
-
Alternatively, adding AIListenForEnum(), AIListenForIsValid(),
and AIListenForAct() to different NPC classes, such as cNPCSinger
or cNPCPhilisopher,
will enable all members of the class to react to the command, question,
or statement. The methods are all layerable, so a NPC derived
from cNPCSinger and cNPCPhilosopher will be able to
both sing and answer the meaning of life.
Give/offer (default command)
If a player gives an object to a NPC, this is translated into a "cmdoffer"
ParseID that's passed into AIListenForIsValid() and AIListenForAct().
Only one parameter is used, the object that's being given.
The default implimentation checks to see the real value of the gift, along with
the NPC's personal valuation (since some NPCs like specific types of objects,
such as chocolates or collectables). If the real valuation is too high for
the NPC, or the player have given the NPC too many gifts recently, then the
NPC will decline the gift. Otherwise, the gift will be accepted, and the
NPC's opinion of the player will be improved based upon the NPC's valuation
of the gift (not the real value).
Some properties that you might wish to modify are:
-
pAIOfferCanBribe - If this is TRUE then the player can give
money to the NPC without offending the NPC. By default, this is FALSE.
-
pAIOfferEffectiveness - The amount that a NPC's like/trust
go up when it's given a gift is based upon the NPC's personal valuation of
the gift combined with the values in pAIOfferEffectiveness. Higher numbers
will make the NPC more susceptible to gift-giving and bribery.
-
pAIOfferForget - See below for details, but this controls
how frequently players are able to give NPCs gifts.
-
pAIOfferMaxAmount - This is the maximum (real) value of gifts
that the player can give the NPC per "week". (pAIOfferForget controls how
long a "week" is.) The value is tempered by how much the NPC likes the
PC; with smaller amounts being used by NPCs that like the player less.
If the player tries to give a gift that's more expensive
than this then the NPC will reject the gift.
Because this behaviour is customized so frequently, there's a special
method that you may wish to modify, AIListenForCmdOffer().
If a player shows an object to a NPC, this is translated into a "cmdshow"
ParseID that's passed into AIListenForIsValid() and AIListenForAct().
Only one parameter is used, the object that's being given.
The default implimentation checks to see
the NPC's personal valuation of the object (since some NPCs like specific types of objects,
such as chocolates or collectables).
The NPC then responds based on its personal valuation.
Some properties that you might wish to modify are:
Because this behaviour is customized so frequently, there's a special
method that you may wish to modify, AIListenForCmdShow().
Can I have your OBJECT? (default command/question)
Players can ask NPCs to give them objects that the NPCs are carrying, by typing,
"Can I have your OBJECT".
To specify what objects the NPCs are willing to give:
-
Set pAIGivable to the list of objects that the NPC will give. To ensure
that the NPC will have the objects for all players that come through a multiplayer
game, objects in pAIGivable will (by default) be automatically created in the NPC's
possession.
NPCs understand dozens of default questions, such as "What race are you?",
"What guilds are you a member of?", etc.
You can fine-tune how readily the NPC answers these questions by
setting:
-
pAIInfoReveal controls how tight-lipped the NPC is about
information. If you don't set this, the value will be randomly generated.
-
AIInfoRevealValue() is a layered method to see how tight lipped the AI
is about a specific type of information, such as the languages that it
speaks or its hobbies. This is called by AIInfoReveal() (see below) to
see whether a NPC will reveal specific information.
-
While you won't modify this, AIInfoReveal() is
a method to see how tight lipped the AI
is about a specific type of information, such as the languages that it
speaks or its hobbies. Call this to see if the AI would be willing
to reveal some information to the player.
You may also wish to override some of the default responses:
You can add a layer to AIListenForAct(), as described above.
This will cause, for example, a specific question to a specific NPC
to be trapped. If you ask Fred, "Is Mike a member of any guilds?", Fred
will have a unique answer.
If you wish to have all NPCs that know Mike to provide
a custom response to, "Is Mike a member of any guilds?", then...
-
Write you own AIInfoSpeak() associated with the object about
which the question is being asked. For this example, you would
write oMike.AIInfoSpeak().
-
And/or, set oMike.pAIInfoSpeakFuturePlansNPC to provide a default
response to the question, "What are Mike's future plans?"
-
And/or, set oMike.pAIInfoSpeakHowDoingNPC to provide a default
response to the question, "How is Mike doing?" or "How are you doing?"
-
And/or, set oMike.pAIInfoSpeakKnowAboutNPC to provide a default
response to the question, "What do you know about Mike?"
-
And/or, set oMike.pAIInfoSpeakOccupationNPC to provide a default
response to the question, "What is Mike's occupation?" or "What is your occupation?"
-
And/or, set oMike.pAIInfoSpeakWhereFromNPC to provide a default
response to the question, "Where is Mike from?"
-
And/or, set oMike.pAIInfoSpeakWhereLiveNPC to provide a default
response to the question, "Where does Mike live?" or "Where do you live?"
-
And/or, set oMike.pAIInfoSpeakWhyHereNPC to provide a default
response to the question, "Where is Mike here?"
The default location for where the character lives is
generated from pAIScheduleLocationLive.
-
And/or, set oMike.pAIInfoSpeakWhereWorkNPC do provide a default
response to the question, "Where does Mike work?" or "Where do you work?"
The default location for where the character works is
generated from pAIScheduleLocationWork.
-
Fill pAIInfoSpeakAdvice with the NPC's answer to "Do you
have any advice for me?"
-
Fill pAIInfoSpeakHappening with the NPC's answer to "What's
going on here?"
19 Chatter-bot functionality
How to easily incorporate chatter-bot functionality into your NPCs.
So far, all the NPC conversation tools that you've been shown are designed
to (a) create procedural responses to questions, like "Where is fred?", and
(b) create NPC-specific responses, such as "Why are you here?"
Sometimes, though, you may just need basic chatter-bot functionality.
The player types in a statement or question, and the NPC responds with
a statement (or a statement that ends in a question mark). In general,
the same response is provided by all NPCs, or at least, all NPCs
of a specific class (such as all elves).
To create a chatterbot:
-
For a class of NPC, such as cElf, write a cElf.AIConvChatter() method.
This will fill in a list with [ResourceName, Resource]. In the case
of elves, this might be ["elf", rNLPRuleSetElfChatter].
Alternatively, you could fill in pAIConvChatter, but this isn't
recommended for classes of NPCs, because one class might overwrite another
class's pAIConvChatter if a NPC based on two classes, each with their own
chatter-bot rules.
-
Create a <NLPRuleSet> resource named rNLPRuleSetElfChatter (or
whatever you called it above.
-
In the rule set, create rules that convert sentences the player may speak
into parsed sentences of the format, "`speak WHATTOSPEAK".
For example: If you want players to ask, "What is the meaning of life?",
then create a rule that converts "what is the meaning of life" to
"`speak The meaning of life? 42, of course.".
That's it. From now on, players will be able to ask all elves, "What is the
meaning of life?", and all elves will reply with the exact same answer,
"The meaning of life? 42, of course."
This simple method has several limitations: (a) You can't assign transplanted
prosody to the response, (b) You can't assign a <SpeakScript> to the response.
And (c) you can't customize it according to the NPC, PC, and situation.
A more sophisticated approach is:
-
Create a rule that converts the english sentence to a parsed
sentence with the form, "`chatter `RESOURCENAME `SENTENCENAME". RESOURCENAME
is the name you used for the resource, above, "elfchatter". SENTENCENAME
uniquely identified the response for "What is the meaning of life?"
For example: Your rule set would convert "what is the meaning of life" to
"`chatter `elf `whatmeaningoflife".
-
Write a cElf.AIConvChatterAct() method. In the method, first
test that (ResourceName == "elf"), returning if it isn't. Then, test
that (SentenceName == "whatmeaningoflife"), returning "The meaning of life?
42, of course." if there's a match.
-
If the method returns the resource sAIConvChatterWhatMeaningOfLife,
then you can record transplanted prosody.
-
Or, the method could return a <SpeakScript> resource.
-
Or, the method could run any code it wishes, and have the elf break into
song, and then return TRUE to indicate that the AIConvChatterAct() method
handled all the speaking.
20 Merchants – cMerchant
The cMerchant class makes it easy to add a merchant to your world.
Merchants are a common fixture in most worlds.
The cMerchant class was created to make implimenting
merchants easy.
Merchants can perform the following functionality:
-
Sell equipment to player characters.
-
Buy equipment from player characters.
-
Change currency from one form to another (gold to copper).
-
Repair equipment.
-
Identify equipment.
-
Teach player characters skills.
-
Heal charaters.
-
Other actions; whatever the author thinks up.
To create a merchant:
-
Create your NPC as usual.
-
Add an extra "Super-class", cMerchant, on top
of (and with higher priority than) the NPC's basic class.
-
Specific properties are required depending upon exactly what
the NPC will sell. See below.
If you wish your merchant to sell equipment:
-
Fill in pMerchantForSale with a list of items that
the merchant is selling. For each item, an object class, the
number of items in stock, the restock rate, and the maximum
stock are stored. You can also customize the price for an item,
making it more or less expensive for a particular merchant.
You can leave this blank if you fill in pMerchantForSaleInit; see
below.
-
Since many merchants sell the same basic goods, such as bandages,
you can fill in pMerchantForSaleInit with a series
of callback functions that indicate "classes" of objects that
are sold. The fantasy library containes some
default callback functions, MerchantForSaleInitArmor(),
MerchantForSaleInitWeapons(), MerchantForSaleInitFirstAid(),
MerchantForSaleInitLighting(),
and MerchantForSaleInitContainers() which include default
armor, weapons, firt-aid equipment, lighting, and containers.
For each of these callbacks, you can scale the number of goods
a merchant stocks, how quickly the stock arrives, and how much
the merchant charges for the stock.
You can write your own callbacks if you wish.
You might wish to change some other properties of the merchant:
-
pAIValueObject will cause the merchant to value some
items more or less than others. This is another way of increasing
or decreasing the amount the merchant will charge.
-
gMerchantCurrencyError affects the amount that the
merchant will round a price to. The default is 2 (percent).
See CurrencyIAToCoin()
-
gMerchantCurrencyMax controls how many currencies the
merchant will include in a price. The default is 2, allowing
for prices like "2 gp, 1 sp", not "2 gp, 1 sp, 3 cp".
See CurrencyIAToCoin()
-
pMerchantLikeScale affects how much the merchant will
adjust his price based on how much the merchant likes (or dislikes) the
character he is selling to.
-
pMerchantLikeThreshhold will cause merchants to refuse
to sell (or do any transaction with) characters the merchant doesn't
like.
If you wish the merchant to buy equipment from players:
-
Set pMerchantBuyBack to TRUE so the merchant is allowed
to purchase used equipment.
You may also wish to set the following values:
-
pMerchantBuyBackNotNormallyStocked affects the price
the merchant is willing to pay for items that the merchant doesn't
normally stocked. If not specified, the number is randomly
generated.
-
pMerchantCash specified how much money the merchant
has, and ultimately how expensive of items the merchant can buy.
-
gMerchantBuyBackScale controls the price
that all merchants are willing to pay for used equipment.
This global will help you control your world's economy, affecting
how much money is introduced into the world by merchants.
-
When the merchant purchases an item that isn't normally
stocked, gMerchantBuyBackSellDown determines how long
the item will remain on the merchant's list before being "sold off"
and removed from the list.
-
pMerchantForSale, pMerchantLikeScale, pAIValueObject,
and pMerchantLikeThreshhold will also affect the merchant's
purchasing of used equipment.
By default, merchants are willing to exchange coins for larger
or smaller denominations with players. If you wish to
disable this:
-
Set pMerchantChange to FALSE to prevent a merchant from
making change.
You may also wish to set the following values:
To allow a merchant to repair objects:
-
Set pMerchantRepair to TRUE.
-
Modify the NPC's pSkills property so it includes the
appropriate repair skills. For example: A blacksmith would
have oSkillBlacksmith, while a leather worker would have oSkillLeatherWorking.
-
Make sure all the tools that the merchant needs to
repair are either held by the NPC or located in the same room. Merchants
can't repair without tools. They can get buy without
materials though.
You may also wish to set the following values:
-
pMerchantLikeThreshhold will affect whether the merchant
will make change for the player.
-
pMerchantCash affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character better.
To allow a merchant to identify objects:
-
Set pMerchantIdentify to TRUE.
-
Modify the NPC's pSkills property so it includes the
appropriate identification skills. For example: A botonist might
have oSkillBotony, while a magical scholar might have oSkillMagicHistory.
You may also wish to set the following values:
-
pMerchantLikeThreshhold will affect whether the merchant
will make change for the player.
-
pMerchantCash affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character better.
To allow a merchant to be a banker:
-
Set pMerchantBanker to 1. If you use a higher value then
the merchant will charge more for deposits than normal. Lower values (more
than 0) will resulting in cheaper banking.
You may also wish to set the following values:
-
gBankerPricePerVolume is the "average" that a banker
charges to store 1 liter of object volume.
-
gBankerMaxDeposit is the maximum number of items a
character can store in their bank accounts. This ensures that characters
don't hoard too much equipment.
-
pMerchantLikeThreshhold will affect whether the merchant
will take deposits from the character..
-
pMerchantCash affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
To allow a merchant to teach skills to a player:
-
Set pMerchantSkills to a list of skills that the merchant
teaches, along with the cost to learn the skill.
You may also wish to set the following values:
-
pMerchantLikeThreshhold will affect whether the merchant
will teach the character.
-
pMerchantCash affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
To allow a merchant to heal the player's wounds:
-
Use the cMerchantHealer class instead of the
cMerchant class.
-
You may want to adjust pMerchantHealer to control
how expensive/cheap the merchant is.
-
pSkills can be changed so the merchant has
a higher or lower oSkillFirstAid, which affects what kind of
healing is possible as well as the speed at which the healing
occurs.
You may also wish to set the following values:
-
gHealerPriceBandage, gHealerPriceSplint,
and gHealerPriceRegrow affect the typical cost of
getting an injury healed.
-
pMerchantLikeThreshhold will affect whether the merchant
will heal the character.
-
pMerchantCash affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
Healers are really based on the merchant's "Miscellaneous" services
option. If you wanted to create a barber, for example, you'd write
some code to allow for miscellaneous services:
-
Write a MerchantMiscEnum() method for the merchant. This
method enumerates all the miscellaneous services the merchant offers.
-
Write a MerchantMiscDo() method, causing the merchant
to act on a purchased service.
-
Optionally, set pMerchantMiscCommand to the phrase
displayed in the merchant's context menu. The default is,
"Do you provide any services?". You could change this to,
"I want my hair cut."
For this to work, you must include the parser rules in the merchant
for "I want my hair cut" to either "`merchantforsale `misc" or
"`merchantforsale `hair". (See below.)
-
If you use the "`merchantforsale `hair" option (or another custom
tag), you'll need
to set pMerchantMiscCommandTag to "`hair".
You may also wish to set the following values:
-
pMerchantLikeThreshhold will affect whether the merchant
will provide a service for the character.
-
pMerchantCash affects how much cash the merchant has
on hand, and (more importantly) how much patronage will affect
how much the merchant likes the player's character. Spending lots
of money with a merchant make the merchant like the player character more.
You may also wish to modify the following:
21 Relationships – cRelationship
Creating relationships between your NPCs.
Relationships - cRelationship
In Circumreality, NPCs can have relationships with other NPCs. These might
include being a spouse, child, coworker, friend, etc. The relationshps
a NPC has affects how much one NPC knows about another, as well as how
much the NPC is willing to say about the other NPC.
Before specifying what relationships a NPC has, you must first specify
the types of relationships: spouses, parents, children, enemies, friends, etc.
The following relationships are already built into Circumreality, and handled by
the listed object:
Major relationship |
Minor relationship |
Object |
Inverse |
"auntuncle" |
None |
oRelationshipAuntUncle |
"niecenephew" |
"child" |
None |
oRelationshipChild |
"parent" |
"coworker" |
None |
oRelationshipCoWorker |
"coworker" |
"employee" |
None |
oRelationshipEmployee |
"employer" |
"employer" |
None |
oRelationshipEmployer |
"employee" |
"enemy" |
None |
oRelationshipEnemy |
"enemy" |
"friend" |
None |
oRelationshipFriend |
"friend" |
"friend" |
"good" |
oRelationshipFriendGood |
"friend" |
"girlboyfriend" |
None |
oRelationshipGirlBoyFriend |
"girlboyfriend" |
"girlboyfriend" |
"hidden" |
oRelationshipGirlBoyFriendHidden |
"girlboyfriend", "hidden" |
"grandchild" |
None |
oRelationshipGrandChild |
"grandparent" |
"grandparent" |
None |
oRelationshipGrandParent |
"grandchild" |
"niecenephew" |
None |
oRelationshipNieceNephew |
"auntuncle" |
"parent" |
None |
oRelationshipParent |
"child" |
"rival" |
None |
oRelationshipRival |
"rival" |
"sibling" |
None |
oRelationshipSibling |
"sibline" |
"spouse" |
None |
oRelationshipSpouse |
"spouse" |
Every relationship object (associated with a relationship) has a list
of properties that describe the "typical" relationship:
-
pNLPNounName and pNLPParseName are used to
name the relationship, such as "grandmother".
-
pRelationshipInfluence simulates the NPC telling its
relations about what a good/bad guy the player is. If this is a large number
then any like/trust impression that the player character makes on the
NPC also has a large impact on the NPC's relations. Thus, a "spouse" relationship
would have a much larger impact than a "coworker" relationship. This
number can be negative for an "enemy" relationship where making friends with
one NPC causes the NPC's enemies to become unfriendly towards the player.
-
pRelationshipInverse indicates the opposite relationship.
Thus, if NPC A is a "employer" to NPC B, then NPC B must be an
"employee" to NPC A.
-
pRelationshipLike indicates how much the NPC likes/trusts his
relations. "friend" might have a [1, 1] indicating theat if NPC A is a friend
of NPC B then, in general, NPC A's like/trust of NPC B is +1.0 higher than
one would normally expect. Conversely, "enemy" might be [-6, -6], indicating
a default like/trust of 6-points below.
-
pRelationshipString" is the lower-case string for the relationship,
such as "spouse".
-
pRelationshipTalkAbout controls how willing the NPC is to divulge
personal information about the relation. A husband may be wary about divulging
personal information about his wife, and have high values, like [4, 4]. However,
NPCs are willing to say anything (bad) they know about enemies,
with a [-4, -4], score.
-
Setting pHidden to TRUE will cause the relationship to
be secret. It won't be spoken of. The only way the player can learn it is
by observation.
So far, I've just been discussing "major" relationships. Although no
"minor" relationships are built in, you may wish to add some. Minor
relationships are variations on the major ones. For example: You could
have a "loving" (minor relationship) "spouse" (major relationship). The loving
spouse might have a higher pRelationshipLike.
As you may have noticed, there is an important naming schemes for
relationship objects. All relationship objects are named with a prefix
of "oRelationship", followed by the major relationship, and then followed
by the minior relationship, if one exists. Thus, the spouse relationship
object is "oRelationshipSpouse", while the loving-spouse object
is "oRelationshipSpouseLoving".
This naming scheme is critical since it's used
by RelationshipToObject() to convert a major (and minor)
relationship string into a relationship object.
Creating your own relationship object
To create your own relationship object, such as that of a "priest" to
his "follower"(s):
-
Create an object based off cRelationship.
-
The object should not be contained in any other object.
-
Make sure to name it "oRelationshipXXXYYY", where XXX is the
major relationship, and YYY is the minor relationship, if there is one.
Thus, your new relationship object would be "oRelationshipPriest".
-
Make sure to fill in all the relationship properties, as listed
above.
-
If the relationship has an inverse, create
the inverse object too. Thus, if you created
oRelationshipPriest, you'd also need to create oRelationshipFollower.
-
If you have created a minor relationship, such as oRelationshipFollowerFanatic,
then make sure you also have an object that will handle
the major relationship, such as oRelationshipFollower.
Assigning relationships to a NPC
When you want to assign one or more relationships to a NPC, do the following:
-
Set the NPC's pAIRelationships to indicate what relationships
the NPC has. For example: [ [oNPCWife, "spouse"], [oNPCChild, "child"] ] causes
the NPC to be married and have one child.
Make sure that the NPC's inverse relationship is included in
the pAIRelationships of the NPC's relations. For example: You would need
to set oNPCChild.pAIRelationships to [[oNPCFather, "parent"]].
-
Additionally, you could write your
own AIRelationships() method. This might be useful to ensure
that all members of a town have a relationship with the town mayor, etc.
-
You can also create relationship by adding a pAIRelationships
to cFaction objects.
Inquiring about NPC relationships
If you're writing code and wish to find out what kinds of relationships
a NPC has with other NPCs, you can do the following:
-
Call the NPC's AIRelationships() and search through the list
yourself.
-
Call the NPC's AIRelationshipExists() to see if the NPC
has a relationship with a specific NPC. If the NPC does, then a list of the
relationship objects will be returned. Alternatively, the NPC may merely
know the other NPC because they're from the same town, causing a number (in
this case 2.0) to be returned.
The NPC's "home town" is automatically determined based on the NPC's
creation room. If you wish to specify a specific map (or maps) then
set pAIHomeMap, or write your
own AIHomeMap() property.
Player database NPC relationships
As playes meet NPCs, a database about the NPC is automatically saved, allowing
the player to then view relationship graphs of the NPC. Most of the
relationship information is automatically stored, so you won't need to
worry about it. However, in the event that you do need to modify the
relationshipd database:
-
The database is stored in the player
character's pKnowledgeRelationships property. You shouldn't
need to access this directly.
-
You can find remembered relationship information specific to a NPC
by calling the PC's KnowledgeRelationshipsGet().
-
To have the player's character remember relationship information,
call KnowledgeRelationshipsSet(). You can store information
about the fact that a relationship exists between two NPCs, the
NPC's last like/trust for the player, and the NPC's last known location.
Automatic conversations between relationships
You can have automatic conversations happen between two NPCs given a specific
relationship. This is handy to have friends say hello, enemies sneer at one
another, etc.
To do this, your relationship object should have:
-
pAIConvScripts filled in with one or more conversation scripts
that might occur.
Each cConvScript specific to the relationship needs to have:
-
pConvScriptAutoRelationship set to the relationship object.
-
pConvScriptNPCs set to 2, since relationships only exist
between two NPCs.
-
pConvScriptAutoPriority should be lowered (below 50) so that
other non-autmatic conversation scripts will take priority.
-
Other cConvScript properties as appropriate.
-
You may wish to modify the pConvScriptResource resource so
that the relationship between the two NPCs is automatically exposed.
That way, players that see the conversation happen will learn of
the relationship.
22 Favors
Favors, the conversational-game equivalent of "loot".
Once players have gotten successfully friendly with NPCs (by giving them
gifts, doing quests for them, etc.), the PCs can ask the NPCs
for favors.
"Favors" are the equivalent to "loot" in the "kill 10 rats" game. It acts
as an incentive/reward, and also enables new gameplay. NPCs can grant
a variety of favors:
-
goodword - The NPC puts in a good word to one of its
relations, providing the player with a "leg up" when interacting with
the relation (NPC).
-
knowledge - The NPC will tell the player a tasty rumor,
which the player can then use to get procede elsewhere in the game.
-
money - This is a bit mundane, but players can ask
for a bit of extra cash.
-
object - The NPC gives the player an object, like a key (that
provides access to another part of the world), and a letter of reference
to another NPC.
-
reputation - The PC's reputation (such as how heroic the PC is)
can be enhanced, affecting how other NPCs react to the PC.
-
skill - The NPC trains the PC in a new skill, such as a long-forgotten
language that's needed to translate an important document.
-
speak - The NPC speaks some words of wisdom (such as hints) to
the player.
-
story - The NPC tells the PC a special story.
-
custom - Whatever you wish to code.
To ask for a favor, the player types "Can you do me a favor?"
This automatically appears on the NPC's context menu.
After asking for a favor, the player will be presented with a list of favors,
and will be able to choose one.
After the favor is granted, the player won't be able to ask
for another favor for aroud half a day (of real time), and until the player
has rebuilt some of the like/trust lost by asking for the favor.
To get a NPC to provide favors, you need to do the following:
-
Nothing! Some defaults already exist for:
"goodword" favors are automatically created for all of the NPC's positive (non-enemy)
relations so long as the player knows about these relations. (See KnowledgeRelationshipsGet()).
A "money" favor is automatically created that defaults to giving pAIFavorMoney inflation-adjusted
units.
Some objects, based on pAIGivable, are automatically added by AIFavor().
-
Having said that, you'll probably want to modify pAIFavor to provide some
custom favors.
You may also wish to modify:
-
pAIFavorAsk is the question that the player asks the NPC to
be granted a favor. This defaults to "Could you do me a favor?"
-
pAIFavorLike controls how many favors a NPC will grant, as well as how
much the NPC must like/trust the player to grant them.
-
pAIFavorLikeCost affects how much the NPC's like/trust goes down
after a favor has been asked.
-
pAIFavorQuestion is what the NPC speaks after the player
has asked for a favor.
-
pAIFavorTime specified the number of real days that the player must
wait before being able to ask the NPC for another favor.
-
AIFavorDoCustom() can be used to write your own custom favor
behaviors, such as, "Can you quack like a duck for the next two hours?"
If you wish a NPC to grant a favor outside of the default context menu,
then you may wish to call AIFavorDo() and AIFavor().
Share with your friends: |