Describes how players are informed about the results of an attack.
Most MUDs display the results of combat in a predictable and
fairly boring manner: "X hits Y for N damage." "Y dies."
Unforuntately, this system does not work well for Circumreality because
a) it's uninteresting, and b) Circumreality's combat is more complex
than a typical MUD's.
If Circumreality were to take the same apporach then a typical attack
would produce a string like, "X attacks Y. Y defends high. X attacks
middle. Y's parry fails. Y's dodge fails. X hits Y's legs.
Y's leg breaks." This is a) uninteresting, and b) too longwinded.
To combat this problem (pun intended), Circumreality first stores
all of the combat information into a combat transcription (a
cCombatTranscript) object, without speaking it out.
It then passes the transcript object to CombatNarrate(), which
investigages the transcript and finds a good way of wrapping
up all the information into an interesting sentence, such
as "X swings at Y's feet, avoiding Y's parry, hits Y's
leg with a bone-crunching sound."
For the most part, you don't need to worry about the specifics
of the implimentation, unless you wish to add your own descriptions.
For example: If you invented a special magical sword, you could
have any attacks with it produce special descriptions. You could
also write custom descriptions for monster attacks... "The octopuses
tentacles grab X and squeeze."
If you wish to write your own descriptions, the first thing
you need to understand is how cCombatTranscript works.
When an attack is initiated, a cCombatTranscript object is
created. This object has no methods or properties, and is just
a means of storing information. The object is passed through
to all the combat functions and methods. If they have anything
to "say", they will add a property to cCombatTranscript with
the information instead of calling SpeakNarratorAll().
Below are some of the basic properties that will be added.
For a complete list, look at the cCombatTranscript defintion
or help, or search through the code for "Transcript." references.
-
pCombatLastAttacked - The character that was attacked.
-
pCombatLastAttacker - The character doing the attacker.
-
pCombatTranscriptType - This is "parry" if the
defender parries, "dodge" if the defender dodges, or "hit"
it the attack gets through.
-
pCombatDodge - If the defender dodges, this
is the how well the dodge went (in standard deviations). Thus,
0.1 is barely dodged, while 2.0 is easily dodged.
-
pCombatParry - If the defender parries, this
is the how well the parry went (in standard deviations). Thus,
0.1 is barely parries, while 2.0 is easily parried.
-
pCombatTranscriptArmorAbsorb - If the defender's
armor absorbs all the damage, this is the piece of armor that
does it.
-
pCombatTranscriptBoneBreak - If the defender's
bone breaks as a result of the attack, this is the body
part that breaks.
-
pCombatTranscriptSevered - Like bone break,
except this indicates that the limb was severed.
-
pDamaged - The severity of the wounds inflicted on the
defender. 4 for very large wounds, 3 for large, 2 for medium, 1 for minor,
and left NULL/Undefined/0 for mere flesh wounds.
-
pDamageDazed - Set to a number of the attack dazed the defender.
-
pDamageDeath - Set to a number of the attack killed the defender.
-
pDamageKnockDown - Set to a number of the defender was knocked down.
-
pDamageUnconscious - Set to a number of the defender was
knocked unconscious.
-
pEquip - A list of equipment dropped by the defender,
usually as a result of blows to arms.
When an attack is finished and all its information is wrapped
into a cCombatTranscript object, a call is made to
CombatNarrate().
CombatNarrate() first finds all the player characters in the
room and determines what level of detail they wish to
hear about. Usually, players will request a high level of detail
for attacks they make and enemies attacking them, while a lower
level of detail for combat ocurring with their friends.
The levels are 0 for brief, 1 for short, 2 for normal, and 3
for detailed.
Next, CombatNarrate() finds all the narration objects
in the system. These are based on cCombatNarrator, whose
constructor automatically adds the object to gCombatNarrators.
Each narrator object is passed in the transcript object and asked
how relevent the narrator is to what just happened. (The method
call is CombatNarratorWeight(). Most narrators
will return a value of 1.0. However, if the narrator is designed
for a special purpose, like attacks from giant octopi, then
it tests the properies in cCombatTranscript. If the attack matches
what the narrator is designed to describe, it will return a high
value, like 10. If the narrator isn't designed for the type
of attack, such as the octopus-narrator not handle sword attacks,
then it will return 0.
CombatNarrate() selects a narrator at random, taking into account
the narrator's weight. It calls the
the narrator's CombatNarratorNarrate() method.
If the narrator can produce a decent narration for the event then
it generates a string and sends it to SpeakNarratorList().
If the narrator is unsuccessful, it returns FALSE and the
CombatNarrate() function tries another narrator. Eventually,
a narrator will be found that works, and the process stops.
If you wish to write your own narrations,
you need to create an object based on
cCombatNarrator. I don't need to describe this in detail.
Make sure that the narrator object is created as an object (as
opposed to a class.)
Each narrator object will have 10 to 1000 possible ways of
"phrasing" the combat transcript. For example: "X attacks Y"
could be phrased as "Y is attacked by X", "X hits Y",
"X attacks Y with a sword", etc.
Some phrasings will only be applicable to parries, dodges,
or different attack types though.
To create one of the phrases, add a private
method to your new narrator object. The private method
must accept a transcript object, list of actors who are to
receive the narration, and a detail level. It should return
a score, which indicates how accurately it was able to
narrate the transcript. The easiest way to do this
is return the value from CombatNarratorTieLooseEnds(),
which calculates the score based on the properites used,
as well as penalties for missed events. You might add a few
extra points for an exceptionally good narration. If the phrasing
code doesn't have any appropriate transcript it should return NULL.
To see an example of narration code, look in oCombatNarratorBasic.
You'll notice quite a few private methods, prefixed with "Short_",
"Normal_", or "Detailed_". The "Short_" ones are used for
a detail level of 0 or 1. The "Normal_" ones are used for
a detail level of 2, while "Detailed_" is used for level 3.
Some things to notice about the phrasing code:
-
The method first checks to make sure that it can describe
the information in the transcription.
A method designed to describe succesful parries would return
NULL if it were passed a "dodge" or "hit" value
in pCombatTranscriptType.
-
Detail level 0 (brief) and 1 (short) often use the same
methods. They are virtually identical, except
that level 0 won't display anything if the attack is parried
or dodged.
-
CombatNarratorTieLooseEnds() is called at the
end of every phrasing method. This will speak out important
information that was "missed" by the narrator code. For example:
An attack might wound a defender's arm so badly that the defender
drops his weapon. Since this is an unusual event, the narrator
code may not pick up on it. The call to CombatNarratorTieLooseEnds()
will speak the inforamtion though.
To prevent CombatNarratorTieLooseEnds() from reiterating what
the method just spoke, make sure to keep a record of the
properties that were narrated and pass them to
CombatNarratorTieLooseEnds().
Thus, if the phrasing method told the players "Fred's mighty
blow slays the orc.", ["pdamagedeath", "pcombattranscriptweapon"]
would be passed. If you don't,
CombatNarratorTieLooseEnds() will speak of "The orc dies."
-
If the phrasing method is just being queried for a score,
the ActorsList is NULL.
You need to write a CombatNarratorPhrasings() method
that returns a list of private methods that are appropriate for
each detail level. oCombatNarratorBasic returns [Short_Parry,
Short_Dodge, Short_Hit] for detail level 0 or 1. Higher levels of
detail have many more ways to phrase a transcript.
You may also wish to provide
a CombatNarratorWeight() method so your narrator
object is only used for certain types of attacks, or with
specific weapons or monsters.
That's it... Of course, you'll probably write a few hundred
different phrasings, which can add up to a fair amount of
work.
Sound effects for combat are also generated using the transcript
object:
-
CombatSoundEffectPlay() is passed a transcript
object.
-
It calls the CombatSoundEffect() method for the
attacker, defender, the attacking weapon, parrying weapon,
armor, and the body part attacked.
-
Each CombatSoundEffect() object fills in a list of
the sound effects it thinks should be used and returns that.
For the most part, the methods just
call CombatSoundEffectImpact() to determine the
impact sound of weapon against armor or body part.
-
CombatSoundEffectImpact() calls into the weapon
and whatever object it hit. It uses
the CombatSoundEffectMaterial() method
to determine if the weapon is metalic, wood, etc., and also
what the armor (or object that is hit) is like. Both
of these value are used to pick a wave file to play, such
as a sword clanking against steel armor, or sword against
chainmail.
-
CombatSoundEffectPlay() wraps up all the
returned sounds and plays them on all the nearby-players computers.
08 Player vs. player combat
Enabling player vs. player combat.
Whenever a player character (or NPC) attacks or thinks about
attacking, the function, CanAttackFunc() is called.
This returns TRUE if the character can attack another,
or FALSE if combat isn't allowed.
CanAttackFunc() works by calling Attacker.CanAttack(),
Defender.CanAttack(), and Room.CanAttack(). All the values
are summed up (with the room's score being doubled). If
the sum is zero or more then an attack is allowed.
Otherwise, it isn't.
PCs use the following rules in their CanAttack() method:
-
If a PC tries to attack another member of his party,
-100 is added, ensuring that party members can't be attacked.
-
If the enemy is an NPC then +10 are added, allowing
NPCs to be attacked by default.
-
If the enemy (or a member of his party) has recently attacked the PC
or the PC's party, then +10 points are added. The recently-attacked
information is stored in pCombatLastAttackerList.
NPCs use the following rules:
-
If the NPC has pDamageCanBeAttacked set to FALSE, then
-100 is added to the score, ensuring the NPC can't be attacked.
-
If the enemy (or a member of his party)
has recently attacked the NPC, then +10 points are added.
Rooms use the following rules:
-
If the room is a safe room, pRoomRessurectionLoc then
the room returns -100, ensuring that combat won't take place.
-
Otherwise, the room's pRoomCanAttack value is returned.
To enable player vs. player combat, you can do one or more of the following:
-
Set the room's pRoomCanAttack to +1.
-
Create your own CanAttack() method for PCs that returns
a high score (such as 10) when attacking each other. For example: You might allow
orc PCs to attack elf PCs, but orc PCs cannot attack other orc PCs.
Once one person has been attacked, they will be able to return the
attack because their pCombatLastAttackerList will
remember the attacker.
09 Resurrection
How to make a room that characters will be resurrected into.
When a player character is killed, the character will be resurrected
in about minute. To ensure that they don't get resurrected into the
hands of their enemies, they are resurrected into the
last "safe room" they had entered. This
is stored in the character's pRoomRessurectionLoc property.
To create a safe room, set the
room's pRoomRessurectionLoc to TRUE. Whenever a PC enters
the room, their safe room will be set to the room. The player will
be notified of the new safe room too, assuming that their character's
safe room has actually changed.
10 Object identification
Some objects can only be identified with the necessary skill.
Australia has an ancient family of plants known as "cycads".
Cycads grow nuts on them that are poisonous. Most tourists don't
know this, but the locals do. How is this implimented in Circumreality?
Implimenting objects whose names change depending upon the
character's skills is easy:
-
Instead of making
the pNLPNounName and pNLPParseName entries
strings, make them lists. The first element of the list is the
unskilled-name, such as "nut". The second entry is the name
that the object would be known by to a skilled viewer,
such as "poisonous nut". You can have more than two entry; in the
case of cycad nuts, the third entry might be "a poisonous cycad nut".
A fourth entry could contain the scientific name even.
-
You can also modify pNLPNounAnimate, pNLPNounCount,
pNLPNounGender, pNLPNounNoAutoArticle, pNLPNounNoAutoPlural,
pNLPNounNoAutoPossessive,
pNLPNounQuantity, and pNLPParseNameNoArticles to
be lists too, although they don't have to be. (Being lazy,
you'll usually leave the single values, and Circumreality will automatically
use the same value for every level of identification.)
-
Add a pNLPIdentifySkills property to the object. It
is a list that contains information about what skills can be
used to identify the object. There are multiple skills since,
for example, a botanist would know what a cycad nut was, as
well as someone with bush survival skills.
Each skill has a sub-list. The first element of the sub-list
is the skill object, followed by a number of (increasing) skill
levels that correspond to the levels of detail in the object's
name.
For example: A cCycadNut object might have a pNLPIdentifySkills
of [[oSkillBotany, 2, 4], [oSkillBushSurvival, 1, 2]].
Characters with a skill level of 2 in botany, or 1 in bush
survival would see the cycad nut described as "poisonous nut"
instead of just "nut". A skill of 4 in botany or 2 in bush
survival would see "poisonous cycad nut".
11 Loot
How to create spawned NPCs with equipment and loot.
What good is a monster that isn't carrying some treasure?
What good is an evil knight that tries to kick you to death?
The Basic IF tutorial described how to have rooms automatically
spawn NPCs using pSpawnClass. Unforuntately, these
spawned creatures won't be carrying any treasure or weapons.
To make a spawned creature carry treasure or weapons:
-
Add pLootEquip to the NPC's object. This
property allows you to control what weapons, armor, or other
standard equipment the NPC will be created with.
You can even randomly select the weapon or armor from a list,
so not all NPCs of the class carry the same weapon.
(The NPC's AI will automatically equip the weapon or armor
as soon as it's created.) For details about the syntax
for pLootEquip, see the property's help topic.
-
pLootCreate lets you specify what treasure
will be created on the NPC. Of course, the treasure selection
is random. It is controlled by a value you set so that
NPCs won't have too much treasure on them.
-
If you wish treasure to appear when the NPC dies, such
as a unicorn's horn being part of a unicorn's
loot, you can provide a pLootCreateOnDeath.
This acts just like pLootCreate except that the treasure is
only created when the creature dies.
(All NPCs support this, even those that aren't spawned
by rooms.)
Multiplayer loot appearing in rooms and containers
There are cases where you might want the player to find an object when they enter a room,
or when they open a container. For example: When a book is opened, a scrap of paper is inside
the book that provides a clue.
This is easy in a single-player game, since you just add an "oScrapOfPaper" and have it contained
within the book.
This won't work in a multiplayer game because only one player would ever get a chance to
take the paper. All the others would find an empty book.
There is a way to do (almost) the same thing in a multiplayer game:
-
Create your room or container as usual.
-
If you create a container, you must set pImmobile to TRUE. (You have to do this
anyway in a multiplayer game since all take-able objects will either be taken by other
players, or they'll be automatically cleaned up by the invisible room cleaner.)
The container should have pOpen set to FALSE so it's closed. It should also
be set to automatically close itself after being opened, with pOpenAutoClose so
that if one player opens it, the object will automatically close and reset for the next player.
-
Set pLootMultiplayer in the room or container to "cScrapOfPaper", or whatever
class you have defined. You can also uses classes based on cMonster or cCharacter.
That's it! The first time a player opens the book, a scrap of paper specifically for the
player will be created. It won't be created subsequent times because a note is made
in the character's pLootMultiplayerHistory.
12 Hidden objects
How to make hidden objects, like secret doors.
Making a hidden object is easy:
-
Set the pHidden property of the object to TRUE.
-
To control how difficult the object is to find,
set the pSearchDifficulty property. This allows
you to set the base difficulty, which can then be affected
by skills (such as "Find hidden traps") that you might
specify.
Additonally, you can provide a bonus if the
players search in the right spot; "Search in the bottom of the chest"
might provide a bonus in finding a hidden panel
at the bottom, while "Search the lid of the chest"
would reduce the chance of finding the hidden panel.
More on this later.
-
You may wish to set pSearchDifficultyNoPerception if
the character's oSkillPerception skill won't have any
effect on searching.
-
If you wish to change the string displayed when the hidden
object is found the set pSearchFindString.
-
You may wish secret doors to re-hide themselves a few
minutes after they've been found.
Set pSearchAutoHide to do this,
as well as pSearchAutoHideString to change
the description.
-
You can change the string displayed while the player
searches the object from "%1 searches %2." to
"%1 rummages around the chest looking for something.", but
changing pSearchString in the chest object.
Since having resources, like plants and metal ores, spawn is common,
the IF libraries contain special code for hidden resources. If
you use this, all the spawned resources will
be hidden by default.
-
Set the room's pSpawnResourceClass to the number and
type of resources that are spawned in the room.
-
pSpawnResourceLocation is optionally set to the locations
over which the resource is spawned.
-
pSpawnResourceTime is optionally set to number of minutes
between resource spawning. This defaults to an average of 10 minutes.
See also pSpawnResourceTimeRange to limit the times.
-
pSpawnResourceSearchDifficulty controls how difficult
it is to find the resource, as well as what skills are needed.
If not defined, then the difficult defaults to 0.
-
pSpawnResourceSearchFindString describes the object being found.
This can be left blank.
Advanced hidden objects - Specific locations
When the player types in the search command, they can (optionally)
specify a location within the object to search. If a player
is specific about the location, you should increase their chance
of success if they state the right location, and decrease their
chances if they state the wrong one.
When the player types in their command, like "search the bottom
of the chest", this is parsed down to "`search the bottom of
CHESTOBJECT". The parser rules in rParserRPG also include
some rules for parsing "`search [*1] [the] bottom [*2]" to
"`search *1 `bottom *2". Thus, "search the bottom of
the chest" becomes "`search `bottom of CHESTOBJECT".
The Parse_Search() method in then discards the "of" word,
and assumes that "`bottom" describes where the player is searching.
The "`bottom" string is then passed into the search
functions, and eventually into SearchForThis(), where
the pSearchDifficulty property in the object
indicates how searching the "`bottom" affects the success rate.
See the docmentation of pSearchDifficulty for more information.
You will probably need to add new parse rules for "the lid",
"the sides", etc. of a chest, or whatever object you wish to
have searched. To do this, provide
a NLPRuleSetTempVerbs() property, or hardcode
the rules for all your objects into a global rule set.
Advanced hidden objects - Replacing methods
You may also wish to replace some methods:
-
SearchInsideThis() - If you add to this layered
function you can ensure that searching around a forest will
find random leaves and branches, searching around seashore
will produce seashells, etc. Adding a SearchInsideThis()
layer for trapped chests or doors will allow players to
find the traps before springing them.
-
SearchLookFor() - This is called when a specific
object is searched for. If you rewrite this code, you can
make objects that are hidden to some players while visible
to others. You'll also need to
replace IsHidden() and
maybe SearchAutoHide().
13 Currency
Gold pieces, silver pieces, copper pieces, and other currency.
The default fantasy library comes with three
currencies: cCurrencyGold, cCurrencySilver,
and cCurrencyCopper. 1 gold is worth 16 silver.
1 silver is worth 24 copper.
If you wish to make your own currency:
-
Create a class, cCurrencyMyOwn, and
base it on cCurrency.
-
Set pNLPNounName, pNLPParseName, and
pExamineGeneral to describe the currency. You may wish
to take a look at cCurrencyGold's pNLPNounName and pNLPParseName
to ensure you handle all the parse/noun forms.
-
Set pVisual to control what the currency
looks like. Again, cCurrencyGold has a pVisual you may wish
to look at.
-
Set pCollectionType to a unique lower-case
string for the currency, such as "myown".
-
Modify gCurrencyList and add an entry for
your currency, which might look like ["myown", "cCurrencyMyOwn", 1010].
The 1010 is the value of the currency in terms inflation adjusted
units, before gCurrencyInflation is taken into account.
Make sure to place your currency entry in sorted order. Several
functions assume that gCurrencyList is sorted from the highest
value currencies to the lowest.
If you don't wish to support gold pieces, for example, then make sure
to remove that entry from gCurrencyList.
A number of handy currency functions are provided:
-
CurrencyChargeActor() - This removes a given amount of
money from a character and provides change. Or, it gives money to
a character. It can also be used to provide change for a gold piece.
-
CurrencyCoinToString() - Creates a string that
can be displayed to the user, such as "2 gp, 3 sp" as in,
"That mace will cost you 2 gp, 3 sp."
-
CurrencyIAToCoin() - Converts from an inflation adjusted
value to a list of coins. It automatically handles rounding.
-
CurrencyOnActor() - Returns a list of all the currency
objects held by the actor, or in the actor's unlocked containers.
-
CurrencyOnActorToAI() - Accepts the
results from CurrencyOnActor(), and calculates how much money the actor
has in inflation adjusted units.
-
CurrencyValue() - Given a lower-case currency name, like "gold", this
returns the value in inflation adjusted units.
14 Repairable items
Making items that can be repaired.
Items get damaged, stored in pDamaged. If
you wish to make items that can be repaired by player characters or
NPCs:
-
Set pRepairSkill to indicate what skill is needed to
repair the item, along with the skill level.
-
Set pRepairTools to indicate the tools that must
be handy for the item to be repaired. For example: The object
might require a forge to be repaired.
-
Set pRepairMaterials to include a list of materials
that will be used up each time the item is repaired. The materials
should be collections, since the amount of material depends upon
how badly the item is damaged.
Whenever a player or NPC repairs an item, it won't be 100% repaired.
Characters with low skills will do a shoddy job repairing the item,
while those with better skills will get closer to the 100% repaired
mark.
To account for this fact, pRepairBestCase will
be modified higher each time, so that items can only be repaired
so often before they're unrepairable.
15 Quests (cQuest)
How to create quests and quest objects.
Quests are a ubiquitous CRPG feature that is supported by Circumreality.
However, quests are handled slightly differently (and more powerfully)
in Circumreality than most CRPGs.
In Circumreality, a quest is really a hidden object that tags along in the
character's inventory. It receives all the PerceiveXXX() messages that
the character does, so it can monitor how many monsters were killed,
how many blueberries were picked, etc.
Unlike most CRPGs, Circumreality quests have a mind of their own. They are
their own object, and can do what they wish, including but not
limited to manipulating AIs. You can think of quests as "surrogate
dungeon masters" whose job is to tag along with the player and make
sure the game stays fun.
To create a quest, such as one in which the player must slay ten orcs:
-
Create a new class, such as "cQuestKillOrcs".
-
Derive cQuestKillOrcs from cQuest.
-
Fill in pNLPNounName and pNLPParseName for
the name of the quest, "Bob wants you to slay 10 orcs".
-
Fill in pExamineGeneral with a description of the quest.
This description will be displayed when the player is offered the quest,
as well as when the player checks out his quest's status.
-
pQuestPlayers should be set the the recommended number
of players to complete the quest. If you don't set this, it
will default to 1, indicating that the quest is good for solo play.
-
pQuestTasks must be filled in with a list of tasks for
the player to complete.
A final task of "Return to END-NPC." is automatically included.
You can disable this task, or change the string, by
changing pQuestTasksReturnToEnder.
-
Set pQuestRewardEnum to the list of rewards offered to the
player when the complete the quest. They will be shown the rewards when
they accept the quest too. The default value for the
property is no reward except gratitude.
If any of your rewards are "special" rewards, that don't involve money
or objects, then you'll need to write
a QuestRewardSpecial() method.
-
You might wish to write your own QuestCanShare() method
to control whether players can share quests amongst themselves. The
default behavior allows sharing in most cases.
You might also want to do the following:
-
pQuestGiverSpeak controls what the guest giver says when
it hands out the quest.
-
pQuestGiverGive will cause the quest giver to hand
out items to the player, such as a letter that must be
delivered. pQuestGiverSpeakAfterGive has the NPC speak
a phrase after it has given the object(s) to the player.
-
You may wish to set pQuestEnder to the NPC that the player
needs to talk to in order to complete the quest. If you leave this
as Undefined, then the player will need to return to the quest giver.
If this is NULL, the player doesn't need to talk to anyone, and can
complete the quest anywhere.
-
pQuestEnderContextMenu is a string that's displayed in
the pQuestEnder NPC's context menu that allows the player to finish
the quest. The defaults to something like, "I'm done with the quest".
The NPC's reply is controlled by pQuestEnderSpeakOnCompleted.
It has a default.
-
If the quest ender requies a set of objects from the player in order
to complete the quests, then fill the objects
in pQuestEnderTake. You may also wish to
fill pQuestEnderSpeakBeforeTake with a phrase that the NPC
speaks before it takes the objects, like "I'll be taking your foozle skins now."
-
If the player completes the quest, starts talking to the pQuestEnder NPC,
but doesn't mention pQuestEnderContextMenu, then the NPC will eventually
bring up the fact that the player has completed
the quest. Set pQuestEnterQuestionOnCompleted to the string.
It defaults to something like, "So you completed the quest! What reward would
you like?"
-
When the player finally selects a reward, the quest is completed.
The NPC will then speak pQuestSpeakOnCompletion with
something like, "Thank you for completing the quest."
-
To have the quest ender automatically offer another quest,
set pQuestEnderOfferMoreQuests to TRUE, which is the
default.
-
If you want your quest to be hidden from the player,
set pIsInvisible to TRUE. You wouldn't normally have an
invisible quest since then players couldn't cancel it. However, if
the quest is acting like an invisible dungeon master, this might
be a good idea.
-
Unlike most CRPGs, there are consequences to a player cancelling
a quest. The most common one is that the quest giver will dislike
and/or distrust the player for cancelling the quest.
Adjust pQuestCancelledLike to control how much the
quest giver's opinion of the player character is affected if the
player cancels the quest.
-
Change pQuestCancelCoolDown to control how long
the player must wait before they can re-accept a cancelled quest.
-
pQuestCompletedSpeak is spoken by the narrator when the
quest is finished. If left blank, a default phrase is spoken.
-
Conversely, pQuestCompletedLike increases the quest
giver's like and trust when the quest is completed.
-
pQuestCompletedReputation affects the PC's reputation
when the quest is completed.
-
By default, all quests will identify all "involved" NPCs to the
player's characters so that when the player sees the NPC, he
will have a name associated with it. This makes life easier for
the player since they don't have to wander around the world
asking NPCs their names. The NPCs are gotten from pQuestGiver,
pQuestEnder, and any "MapToObject" listed in QuestTasks(). If
you don't wish to automatically identify NPCs then
set pQuestAutoIdentify to FALSE.
-
If you provide pQuestKnowledgeAnecdote then the player
can brag about his exploits on the quest to any NPC he may meet.
This bit of knowledge can be used by the player when trading stories
and rumors with NPCs.
-
Setting pQuestFracture will cause
PlayerCharacter.FractureAdd (pQuestFracture) to be called if
the player completes the quest. Thus, a quest to slay the orcs
will result in a happy village without any fires burning.
-
pQuestAutoComplete lets the quest automatically
end without having to see a NPC.
-
If your quest object has properties that it needs to initialize
when it's first created and assigned to a player, then
write your own QuestInit().
-
If you quest needs special clean-up, then write your
own QuestEnd().
-
You might write your own QuestTasks() method. This returns
a list of all the tasks that the player must complete in the quest,
as well as a percent-done for each task. The tasks are displayed
in the quest information page. The default code looks at
pQuestTasks and derives a list.
-
If you use pQuestTasks and provide your own callbacks, you
may need to call QuestTasksBoundary() from time to time
to ensure that the player isn't allowed to "work ahead" on a task.
-
Write your own QuestAIConversation() method to
allow NPCs that are involved with the quest to interpret sentences
spoken to them by the player. For example: If one task in your
quest is to talk to Fred and get some information from him, you
would provide a QuestAIConversation() method to return the
string's that player could speak to Fred, such as "do you have [any]
(information|info) for me".
-
Write your own QuestAIQuestionAdd() method so
that NPCs that are involved in the quest can bring up quest
information in their conversations with the player. As in the
example above, if the player doesn't mention that they are
looking for information, Fred might eventually bring it up.
-
If you use either QuestAIConversation() or QuestAIQuestionAdd() you
will need to provide QuestAICallback() to have the
NPC speak the phrase, and/or take actions.
-
QuestAIShowOffer() and pQuestAIShow and pQuestAIOffer can
be modified to control how pQuestEnder responds when shown or given one
of the objects needed to complete the quest.
-
You may wish to override QuestCanBeCompleted(), but
you probably won't need to.
-
QuestPercent() can be overridden, but as long as your
QuestTasks() list is accurate, QuestPercent() will work fairly well.
-
The default implimentation of QuestRewardEnum() returns
pQuestRewardEnum. If you need more control than this then override
QuestRewardEnum().
-
pQuestCanCancel can be set to FALSE to prevent the quest from
being cancelled, or you can write your own QuestCanBeCancelled() method.
-
If you want to keep statistics about how long a quest took to complete
or when players started/stopped the quest, then create statistics objects
based on cStatisticsQuestDuration and/or cStatisticsQuestStartStop and
reference the object(s) in pQuestStatistics.
Quest objects also contain some useful properties that you don't need
to initialize, but which might be handy during your coding:
-
pIsQuest is set to TRUE, identifying the object
as a quest.
-
pQuestClass is the class name of the quest, "cQuestKillOrcs".
-
pQuestGiver is the NPC that gave the player the quest.
-
pQuestStartedPlayTime is the number of hours the player had
played his characters when the quest was begun. You can use this
for timed quests, or penalties for taking too long on a quest.
-
pQuestStartedTime is the time from TimeGet() when the quest
was begun.
-
pQuestStoryline is the storyline object (based on cStoryline)
that this quest is part of.
Giving a quest to a player character
There are two ways to give a quest to a player character:
-
You can call cCharacter.QuestsAdd() to add the quest to the player's
list of quests.
While you're looking at QuestsAdd(), you might also wish to look
at QuestsEnum(), QuestsFind(), QuestsRemove(),
pQuestsCancelled and pQuestsCompleted.
-
You figure out which NPC hands out the quest and modify some
properties of the NPC so it will hand out the quest. See below.
To easily have a NPC hand out a quest:
-
Set pAIQuestHandout to a list of all the quests that the
AI hands out, as well as information about requirements like previous
quests that must have been completed.
You can get a bit more control by modifying:
-
pAIQuestQuestionAdd controls questions/statements the NPC
might make based upon whether a quest has been completed or not. For
example: If the player hadn't yet accepted the "kill 10 orcs" quest, the
NPC (as well as every NPC in the village) might mention that orcs
were troubling the village. When the player accepts the quest, a different
line might be used. And finall, after the player finishes the quest,
NPCs might occasionally thank the player for defeating the orcs. Likewise,
if the player cancels the quest, the player might get some verbal abuse.
-
AIQuestHandOut() gives you more control over the quests
that a NPC hands out than pAIQuestHandOut.
-
AIQuestHandOutUI() brings up a dialog that allows players
to accept a quest that the NPC is providing. You probably won't need
to override this, although you may wish to call it at some point.
-
AIQuestQuestionAdd() provides even more control than
pAIQuestQuestionAdd.
-
The QuestUI() function brings up a dialog for displaying
the player's quests, or displaying information about a specific quest.
-
cCharacter.QuestsCompletedOrCancelled() can be used to
determine if a player character has completed (or cancelled) a quest.
Quests can take advantage of a sub-system called "the rumor mill".
The rumor mill causes NPCs to talk to one another about the quest, either
spurring the player's interest in the quest (if they haven't accepted it),
or talking about the quest's completion (assuming that the player has
completed it).
The rumor mill is a useful technique because:
-
It make the quest feel important before players have heard about
the quest or accepted it. For example: NPCs might say to one another,
"I just saw another rat this morning!", "Yes, I know what you mean; They're everywhere!"
-
Once players have completed the quest, NPCs will comment about
their accomplishments. For example: "Thank god all the rats are gone!",
"<PC-Name> certainly did a good job."
To modify a quest to use the rumor mill:
-
Create a conversation script based on cConvScriptRumorMill which
described the "There's a plague of rats" conversation between NPCs. For more information,
see the cConvScript tutorial.
-
You may want to set pConvScriptAutoNPCs and pConvScriptAutoListenersExclude so
that only certain NPCs will discuss the rumor, or so that NPCs won't talk about another NPC
if that NPC is present.
For example: If the quest is about spying on the strange activities of NPC X, then NPCs won't discuss
the rumor while NPC X is in the room.
-
In the quest object, set pQuestRumorMillConvScriptsNotStarted to the conversation
script object that you just created.
-
You might also wish to create several other conversation scripts and assign them
to pQuestRumorMillConvScriptsActive, pQuestRumorMillConvScriptsCompleted,
and pQuestRumorMillConvScriptsCancelled. These control what NPCs will say to one another
once the quest has been accepted (but not completed), after it has been completed,
or after it has been cancelled.
-
If you want the quest's rumors to spread beyond the map where the quest is handed out,
then set pQuestRumorMillMap to a list of maps where the rumors will be discussed.
If you leave it blank, then the map where the NPC hands out the quest will be used.
That's all you need to do.
If you want to include rumors outside of the quest, then:
-
Fill in pRumorMillSource in the NPC with a list of rumors.
-
Optionally, fill in pRumorMillSourceMap with the map where the rumor
will be spoken.
-
Or, write your own RumorMillSource() method for the NPC.
-
Or, write your own RumorMillConvScripts() method layer, assigned to the player
characters. You can test for whatever conditions you like (such as different fractures) and
add rumor-mill conversation scripts.
You might also wish to change the following:
16 Shape chaging
Magically changing a player character's shape... transforming them into frogs and whatnot.
A fairly common fantasy device is to change a character's shape,
such as turning them into forgs, mice, or bowls of petunias.
Before you even think about changing a character into a frog (or whatever),
you need to create a special class, such as cShapeChangeFrog, that
contains code and properties describing the essense of what makes a frog
different from a man (or elf, dwarf, etc.)
To create a shape changing class:
-
Create a new class in the usual way. Make sure that
it is not instantiated as an object. Give it a name,
such as cShapeChangeFrog.
-
Base the class off of cShapeChange.
-
In cShapeChangeFrog, write code
to override some important methods, such
as NameRace(). See below for more information.
-
Add some properties that you need to override,
such as pBodyParts or pHeight. This is a tricky situation.
See below for more details.
When a character is changed into a frog, the cShapeChangeFrog class
is layered on top of their character's existing class.
(They layering is done using a call to LayerAdd().) This layer
intercepts all method and property calls made to the character's object.
If the layer happens to have the given method, that's called instead
of the main object's method.
Likewise, if the layer has a specific property, then that property
is used instead of the character's property (kind of... see below).
You may wish override one or more of the following methods in
your cShapeChangeFrog class:
-
EmoteQuery() - Use this to limit or change the emotes
that the changed character can do. A frog may not be able to smile,
but it can "ribbit". (You may also need to add an extra emote command, "ribbit",
to allow the character to type the emote.)
-
HeightEye() - Override this if the character can fly.
-
NameRace() - Returns the race's name, such as "a frog".
If you don't replace this, the character may still be known as "an elf" or
"a dward".
-
StealthAutoHide() - Causes the transformed character not
to be noticed right away.
-
VisualEmoteEnum() - Override this to control what visual
emotes a character can make, perhaps eliminating "smile" and adding
"throat sack full".
-
VisualEmoteQuery() - Change this along with VisualEmoteEnum().
-
VisualModifierEnum() - Change this along with VisualEmoteEnum().
This method converts from an emote into morph shapes used in the character's
3d model.
You will also need to add several properties to cShapeChange frog.
However, because this is a layered class added after the main object
has been created, any values you type into the properties' edit
fields will be ignored. The only way to actually change
the value is to check the "Get/Set" checkbox next to the property and
write your own Get() method.
For example: You want your frog to be 1/10th of a meter tall.
Normally, you'd set "pHeight" to 0.1. This won't work. Instead:
-
Add the pHeight property as normal.
-
Leave the pHeight edit field blank. It won't make any difference.
-
Check the "Get/Set" checkbox.
-
Edit the "Get" code by pressing the "Get" button next
to the property edit field. The
default code will be "return pHeight". Change this
to "return 0.1;".
-
You can leave the "Set" code alone. There's no point changing
it.
You will need to write code for a number of properties. You may wish
to change once or more of the following:
-
pBodyParts - Controls what limbs your character has.
Be aware that you may need to create special body parts
for the shape change; frogs legs are different than human legs.
-
pContainerItemsMax - The maximum number of items the character
can hold.
-
pContainerVolumeMax - The maximum volume of items the character
can hold.
-
pContainerWeightMax - The maximum weight of items the character
can hold. This is affected by the character's strength attribute.
-
pExamineGeneral - What other players hear if they examine
the shape-changed character.
-
pGender - If you want to do gender bending.
-
pHeight - The character's height.
-
pSkillsFemaleInnate and pSkillsMaleInnate - How male/female
versions of the shape differ.
-
pSkillsRacialInnate - Skills that the player automatically acquires
when they're turned into this shape. This might, for example, include improvements
to jumping as well as the ability to speak to other frogs.
Important: Skills from pSkillsRacialLeanred are kept when a
character is shape changed. That means that if oSkillElvish is part
of pSkillsRacialLearned, then when the character is transformed into
a frog, they will be able to speak Elvish. If you don't want this
to happen, you could either add a negative Elvish skill to
cShapeChangeFrog.pSkillRacialInnate, or you could make Elvish an
innate skill instead of a learned one.
-
pVoice - The character's voice; frogs might have a croaky voice.
-
pVolumeSelf - How much volume the character uses.
-
pWeightSelf - How much the character weighs.
-
pDamageScreamBig, pDamageScreamHuge,
pDamageScreamSmall, and pDamageScreamWince - What sounds the character
makes when it's hit, probably a croak.
-
pFOVRange360 - The character's field of view.
-
pHandedness - Force the character to be left/right handed.
-
pThreeDSound - How the player hears sound when playing
the shape-changed character.
-
pVisualEffectAutoExposure - How well the shape-change sees at night.
-
pVisualEffectColor - How colorful the world looks.
-
pVisualEffectColorBlind - Make the shape-changed
character color blind.
-
pVisualEffectNearSighted - Make the shape-changed
character near-sighted.
-
pVisual - A 3D model of what the shape-changed character
looks like.
Changing a character's shape
Once you've done all that, actually changing the character's shape is
pretty easy:
-
You can call ShapeChangeQuery() to get the character's
current shape.
-
Before changing the shape, find out what room the character is
in and call vRoom.RoomShapeChange(). This is a safety
check to ensure that characters won't be changed into shapes that
aren't suitable for the room.
For example: Part of the game might involve changing player characters
into mice and letting them run through the walls of a building. While
a player is in the wall, you don't (usually) want them to change back into a human.
For one, how would you draw the inside of a wall from the perspective
of a human?
-
Call ShapeChange() to change the shape. The function
takes parameters specifying what's to happen with the character's equipment.
-
You should use SpeakNarratorAll() to inform everyone in the
room that the shape change has occurred.
-
As alluded to above, if you want rooms, maps, regions, or zones where
the player is forced into a specifc shape (or forced out of a shape),
then write your own RoomShapeChange() for the room, map,
region, or zone object.
17 Stealth
How sneaking about works.
Circumreality allows PCs and NPCs to sneak around through various commands
like "Sneak DIRECTION", "Hide", and "Hide behind OBJECT". Sneaking
allows characters to avoid enemies as well as listen in on conversations.
For the most part, sneaking is auomatically handled by:
-
oSkillStealth helps make characters more stealthy.
-
oSkillPerception improves a character's ability to find
hidden characters.
-
IsThereLight() affects how dark it is, and how
well characters can hide.
-
If an object's pVolumeSelf is high enough (a few times
higher than the character's volume), then the object can be hidden
behind (or in).
Objects/rooms that can hide in
If you want to ensure that characters can hide behind an object, or hide in
a room:
-
Set pVolumeSelf for objects such as crates, dressers, or
anything that a character might wish to hide behind or in.
-
If pVolumeSelf isn't good enough, you can write your
own StealthHidingPlaceQuality() method for the object. For example:
If you wanted to allow players to hide under a pile of leaves,
you'd need to write this.
-
If you have a room that you want to make it easy to hide in, without
having to hide behind an object, write your
own StealthHidingPlaceQuality() for the room. For example: If the
room is very smokey then hiding would be easy.
-
Whenever a character hides, their pStealthHidden property
is set to indicate the object they're hiding in/behind, as well
as how they're hiding.
-
If any characters come into the room where a character is hiding, or
if they search the room, StealthIsHiddenPerceived() is called.
ActorMove() automatically handles this.
-
If one character is hidden to another, then the
hiding character's pStealthHiddenTo is expanded to
include the character that can't see the hiding character.
-
When a character is listed in pStealthHiddenTo, calls
to IsHidden() will return TRUE for that character.
-
Whenever the hidden character performs an
action, ActionVoidStealth() is called, potentially
exposing the hiding character. This, in turn,
calls StealthIsHiddenPerceived().
-
NPCs that perceive a character sneaking around,
with PerceiveActorEnter(), PerceiveActorLeave(),
and PerceiveInNewRoom() will mistrust the character.
-
If you want specific characters not to be noticed by
other characters, write a StealthAutoHide() method
for the character.
Share with your friends: |