Multiplayer Interactive-Fiction Game-Design Blog



Download 8.87 Mb.
Page123/151
Date02.02.2017
Size8.87 Mb.
#15199
1   ...   119   120   121   122   123   124   125   126   ...   151

07 Combat narration


Describes how players are informed about the results of an attack.


Combat narration

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."


cCombatTranscript

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.







CombatNarrate()

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.



cCombatNarrator

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

Sound effects for combat are also generated using the transcript

object:





  1. CombatSoundEffectPlay() is passed a transcript

    object.




  2. It calls the CombatSoundEffect() method for the

    attacker, defender, the attacking weapon, parrying weapon,

    armor, and the body part attacked.





  3. 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.







  4. 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.







  5. 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.


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:





  1. Set the room's pRoomCanAttack to +1.





  2. 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.


Resurrection

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.


Object identification

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:



  1. 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.



  2. 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.)





  3. 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.




  4. 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.


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:






  1. 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.





  2. 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.





  3. 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:







  1. Create your room or container as usual.



  2. 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.)




  3. 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.

  4. 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.


Hidden objects

Making a hidden object is easy:





  1. Set the pHidden property of the object to TRUE.





  2. 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.



  3. 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.


  4. You may wish to set pSearchDifficultyNoPerception if

    the character's oSkillPerception skill won't have any

    effect on searching.





  5. If you wish to change the string displayed when the hidden

    object is found the set pSearchFindString.



  6. 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.





  7. 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.






Spawned resources

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.



  1. Set the room's pSpawnResourceClass to the number and

    type of resources that are spawned in the room.





  2. pSpawnResourceLocation is optionally set to the locations

    over which the resource is spawned.







  3. 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.





  4. 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.





  5. 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.


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.


Making your own currency

If you wish to make your own currency:





  1. Create a class, cCurrencyMyOwn, and

    base it on cCurrency.



  2. 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.



  3. Set pVisual to control what the currency

    looks like. Again, cCurrencyGold has a pVisual you may wish

    to look at.





  4. Set pCollectionType to a unique lower-case

    string for the currency, such as "myown".



  5. 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.


  6. 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.




Currency functions

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.


Repairable items

Items get damaged, stored in pDamaged. If

you wish to make items that can be repaired by player characters or

NPCs:




  1. Set pRepairSkill to indicate what skill is needed to

    repair the item, along with the skill level.



  2. 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.





  3. 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 (cQuest)

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.




Creating a quest

To create a quest, such as one in which the player must slay ten orcs:





  1. Create a new class, such as "cQuestKillOrcs".





  2. Derive cQuestKillOrcs from cQuest.





  3. Fill in pNLPNounName and pNLPParseName for

    the name of the quest, "Bob wants you to slay 10 orcs".



  4. 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.







  5. 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.





  6. pQuestTasks must be filled in with a list of tasks for

    the player to complete.




  7. A final task of "Return to END-NPC." is automatically included.

    You can disable this task, or change the string, by

    changing pQuestTasksReturnToEnder.

  8. 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.


  9. If any of your rewards are "special" rewards, that don't involve money

    or objects, then you'll need to write

    a QuestRewardSpecial() method.

  10. 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:





  1. You can call cCharacter.QuestsAdd() to add the quest to the player's

    list of quests.


  2. While you're looking at QuestsAdd(), you might also wish to look

    at QuestsEnum(), QuestsFind(), QuestsRemove(),

    pQuestsCancelled and pQuestsCompleted.

  3. 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:





  1. 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:







  1. 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.







  2. AIQuestHandOut() gives you more control over the quests

    that a NPC hands out than pAIQuestHandOut.







  3. 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.





  4. AIQuestQuestionAdd() provides even more control than

    pAIQuestQuestionAdd.








Miscellaneous



  • 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.








The rumor mill

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:





  1. 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!"





  2. 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:





  1. 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.





  2. 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.




  3. 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.


  4. In the quest object, set pQuestRumorMillConvScriptsNotStarted to the conversation

    script object that you just created.



  5. 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.





  6. 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:





  1. Fill in pRumorMillSource in the NPC with a list of rumors.





  2. Optionally, fill in pRumorMillSourceMap with the map where the rumor

    will be spoken.



  3. Or, write your own RumorMillSource() method for the NPC.





  4. 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:







  • gRumorMillLastTime and gRumorMillSpokenTime affect how often rumors

    are spoken, ensuring that players don't hear them too often.








16 Shape chaging


Magically changing a player character's shape... transforming them into frogs and whatnot.


Shape changing

A fairly common fantasy device is to change a character's shape,

such as turning them into forgs, mice, or bowls of petunias.



Shape changing layer

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:







  1. 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.



  2. Base the class off of cShapeChange.





  3. In cShapeChangeFrog, write code

    to override some important methods, such

    as NameRace(). See below for more information.





  4. 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:





  1. Add the pHeight property as normal.





  2. Leave the pHeight edit field blank. It won't make any difference.





  3. Check the "Get/Set" checkbox.



  4. 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;".



  5. 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:



  1. You can call ShapeChangeQuery() to get the character's

    current shape.



  2. 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.


  3. 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?

  4. Call ShapeChange() to change the shape. The function

    takes parameters specifying what's to happen with the character's equipment.



  5. You should use SpeakNarratorAll() to inform everyone in the

    room that the shape change has occurred.







Miscellaneous



  • 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.


Stealth

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.





How it works



  1. 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.





  2. 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.





  3. 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.





  4. When a character is listed in pStealthHiddenTo, calls

    to IsHidden() will return TRUE for that character.



  5. Whenever the hidden character performs an

    action, ActionVoidStealth() is called, potentially

    exposing the hiding character. This, in turn,

    calls StealthIsHiddenPerceived().



  6. NPCs that perceive a character sneaking around,

    with PerceiveActorEnter(), PerceiveActorLeave(),

    and PerceiveInNewRoom() will mistrust the character.







Miscellaneous



  • If you want specific characters not to be noticed by

    other characters, write a StealthAutoHide() method

    for the character.







Download 8.87 Mb.

Share with your friends:
1   ...   119   120   121   122   123   124   125   126   ...   151




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

    Main page