Behaviors and Reactions
As discussed earlier in NPC Scripting, any NPC Types are really simply a collection of behaviors and reactions to events. At the most meta level, all of us fit this definition. How we act and how we respond to stimuli determines everything about our activities over time. Thus it seems sufficient and general to model npc behavior this way also.
Each behavior gets a script made of Behavior_Operations as documented in the previous sections. For now we will neglect those, however and just talk about the behaviors themselves.
- 1 Example
- 2 Behavior
- 3 Reaction
- 4 Perceptions
- 4.1 Attack Perceptions
- 4.2 Collision Perceptions
- 4.3 Damage Perceptions
- 4.4 Death Perceptions
- 4.5 Faction Perceptions
- 4.6 In bounds Perception
- 4.7 Item Perceptions
- 4.8 Location Perception
- 4.9 NPC Cmd Perception
- 4.10 Out of bounds Perception
- 4.11 Spell Perception
- 4.12 Spoken To Perception
- 4.13 Time Perception
Here is an example of an NPC Type’s behavior/reaction list:
<npctype name="Wanderer"> <behavior name="walk" decay="0" growth="0" initial="50"> <behavior name="turn" completion_decay="100"> <behavior name="fight"> <behavior name="chase" completion_decay="100">
<react event="collision" behavior="turn" delta="100" /> <react event="out of bounds" behavior="turn" delta="100" /> <react event="attack" behavior="fight" delta="150" /> <react event="damage" behavior="fight" delta="20" weight="1" /> <react event="target out of range" behavior="chase" delta="0" /> <react event="death" behavior="fight" delta="-1" /> </npctype>
This collection of behaviors and reactions is sufficient to have a basic outdoor wandering monster who will avoid collisions, stay in his boundary region, fight back if attacked and stop attacking if he dies.
List of priorities for NPCs
Similar to the way the hate list maintains a prioritized list of enemies for each NPC, each NPC also maintains a set of priorities for the list of behaviors in its npctype. The priority list starts out with 0 as the default score, unless an “initial” attribute is specified. (One behavior should have this attribute so the npc has a place to start.) Thus, any “Wanderer” NPC will start with the following priority list:
P-List: walk-50, turn-0, fight-0, chase-0
Thus on startup the NPC will begin by walking and using the operations inside that behavior tag to do something until an event occurs which preempts the behavior by making something else higher priority. For example, if he goes out of bounds, he will get an “out of bounds” perception, which he has a <react> tag for. It says to add 100 points to the “turn” behavior priority, which results in:
P-List: walk-50, turn-100, fight-0, chase-0
So his “walk” behavior is interrupted and the “turn” behavior and script is activated. This script starts running and the npc starts turning. Turning to point back into the boundary region should only take a couple of seconds. When the script complets for this behavior, the “completion_decay” of the behavior kicks in and affects the P-List again:
P-List: walk-50, turn-0, fight-0, chase-0
There are many real-world examples of how “completion_decay” exists in humans. Many actions are the top priority until they are completed, and then one simply doesn’t need to do them anymore. “completion_decay” is modeling that change in priority when an action is performed.
Now that the priority for turn has gone back to 0, “walk” again is the highest priority behavior and the npc starts walking again.
So now the npc is attacked by a player in the forest. “attack” is a perception sent by the server, as discussed in a previous section, and one for which this npctype has a <react> tag, telling him to add 150 to his “fight” priority, resulting in:
P-List: walk-50, turn-0, fight-150, chase-0
So now the npc stops walking and starts his fight behavior, which probably has the <melee> operation in it, using the hate list to fight back against one or more attackers. As the attack progresses, damage perceptions come in. A damage perception of 10HP has a <react> script also, which adds 10 points to the fight priority and also has a “weight” attribute which affects the hate list instead of the behavior list:
P-List: walk-50, turn-0, fight-160, chase-0
Since the fight behavior is already the highest, it might be redundant to keep increasing it like this, but personally I view it as the npc getting more enraged, which means it will take him longer to calm down when he runs out of enemies. The weight=”1” attribute means that the attacker’s hate score will go up by 1*HP.
Now imagine that the attacking player is losing the fight, and decides to run away. Very quickly, the “target out of range” perception will fire, created by the melee operation when the most hated enemy is farther away than the melee_range but within the seek_range. This NPC is scripted to chase after hated enemies if they run away with his <react event=”target out of range”> tag. The “delta” specified of 0 is a special delta which means “Make the named behavior the highest no matter what.” When reactions are coded with 0 deltas, NPCs are really acting like state machines again.
P-List: walk-50, turn-0, fight-160, chase-185 (Highest means at least 25 higher than the next highest to cause a preemption and switch.)
So now the NPC stops attacking and begins chasing the most hated enemy. The <chase> tag has a range specified for the chase to be completed (probably 2m again), so when the npc has caught the player, the chase behavior completion decay hits. The chase behavior has a specified completion decay of 100, but since chase was escalated artificially high by the 0 react, it is restored intead back to its original value.
P-List: walk-50, turn-0, fight-160, chase-0
So the npc stops chasing and resumes fighting the player immediately when he catches him. Now the npc finally kills the player and the fight is over. The “fight” behavior is still the highest one, so the npc keeps looking around for other enemies to fight, such as group members. If none are found, his fight behavior keeps decreasing by 10 points per iteration. P-List: walk-50, turn-0, fight-160, chase-0 P-List: walk-50, turn-0, fight-150, chase-0 P-List: walk-50, turn-0, fight-140, chase-0 P-List: walk-50, turn-0, fight-130, chase-0 And so on…
Eventually, “walk” behavior becomes the most active again and the npc resumes his wandering of the forest.
The key concepts here are that the NPC constantly maintains a priority list of behaviors and a priority of list of enemies to attack. <react> tags can affect behavior priority, hate list priority or both.
The interactions of events, behaviors and reactions can become very complex. The npc can be interrupted by an attack while moving, turning or even chasing another player. This priority structure was designed to allow for rich behavior interruption and resumption while having realistic reactions to world events and activities. Hopefully, with the right scripts we can create very life-like npcs that are also efficient on networking and CPU, with emergent behaviors that stay challenging and interesting to players for a long time to come.
A behavior is a collection of Behavior_Operations. A behavior can be configured to behave in very different ways. The following table show the parameters that can be set for each behavior.
|name||string||Mandatory||The name of this behavior|
|loop||boolean||false||Set to true if this behavior should loop|
|decay||float||0.0||The decay rate of this behavior to apply when the behavior is the active one.|
|completion_decay||float||0.0||The decay to be applied when this behavior is done. -1 will be all of the current need.|
|failure||string||The FailurePerception to fire if a operation fail without having own failure perception.|
|growth||float||0.0||The growth rate of this behavior to apply when the behavior isn't the active one.|
|initial||float||0.0||The initial need for this behavior|
|interrupt||string||A perception to fire when this behavior is interrupted.|
|when_dead||boolean||false||Set this to true if this behavior should be possible while dead. Resurrect Operation will need this.|
|resume||boolean||false||Set to true if this behavior should resume after an interrupt.|
|min||float||none||Set this attribute if a min need limit should be applied to this behavior.|
|max||float||none||Set this attribute if a max need limit should be applied to this behavior.|
Ex: <behavior name="move" decay="0" growth="0" initial="80" loop="yes" resume="yes"> ... </behavior> <behavior name="chase" decay="1" completion_decay="-1" growth="0" initial="0" > ... </behavior>
|event||string||The event to react to.|
|behavior||string[,...,string]||The set of behavior to influence if this reaction is triggered.|
|delta||float||0.0||Apply a delta value to the need of the affected behavior.|
|absolute||float||0.0||Apply a absolute value to the need of the affected behavior.|
|weight||float||0.0||Passed to the perception when the reaction is accepted. Used to alter the hate list of the NPC.|
|faction_diff||int||0||Used to match faction perceptions.|
|oper||string||The operator to use when comparing faction perceptions.|
|condition||string||The condition NPCClient math script has to return a none zero value in order for the reaction to react.|
|value||string[,...,string]||Used by some perceptions like time.|
|random||integer[,...,integer]||Used to add some random value to the value array.|
|type||string||The type is different for each perception. A "location sensed" will have the location type as the type. NPC Variables will be replaced.|
|active_only||boolean||false||Only influence active behaviors|
|inactive_only||boolean||false||Only influence inactive behaviors|
|when_dead||boolean||false||Influence behaviors even if the npc is dead.|
|when_invisible||boolean||false||Set to true if an invisible NPC should accept this reaction.|
|when_invincible||boolean||false||Set to true if an invincible NPC should accept this reaction.|
|only_interrupt||string,[...,string]||If set this event will only be accepted if it can interrupt one and active behavior from this list.|
|do_not_interrupt||string,[...,string]||Set this if this reaction should not interrupt some active behaviors.|
The reaction can influence on a behaviors need either as GUARANTIED to execute(no delta or absolute attribute set), DELTA (delta attribute set), or ABSOLUTE ( absolute attribute set).
Ex: <react event="damage" behavior="Fight" delta="20" weight="1" />
Ex: <react event="time" value="16,0,,," random=",5,,," behavior="GoHome" />
This example show a reaction to a time event that will occur between 1600 and 1605. This is archived due to the random property. Each of the values in value represents "Hour,Minute,Year,Month,Day". Any parameter not set will match every possible combination.
Ex: <react event="location sensed" behavior="ExploreLoc" type="mine" only_interrupt="Explore" />
This example show a reaction to the location sensed perception where location of type mine will interrupt explore operation with the ExploreLoc behavior.
Perceptions it the invisible mechanism that makes reactions act. There are several situations where perceptions are fired. Some are hard coded into the NPCClient. Some are fired as responses to other script elements and the name might be configurable. An example on this is the Chase Operation where the perception name is configured through the operation. A hard coded perception is the Death Perceptions event.
Some perceptions might only be documented through the senders like the Behavior_Operations.
This is perception is fired when someone is attacking a NPC.
|event||attack||The perception name|
|weight||float||0.0||The weight to apply to the hate list. The default is 0.0 so without setting a weight no adjustment to hate list will be done.|
Ex: <react event="attack" weight="10.0" ... />
This example will apply 10.0 to hate list for the NPC when reacting to the attack perception.
This perception is fired when someone collide due to movements.
|event||collision||The perception name|
Ex: <react event="collision" ... />
This perception is fired when someone do damage to an NPC.
|event||damage||The perception name|
|weight||float||0.0||The weight to apply to the damage regarding hate list. The default is 0.0 so without setting a weight no adjustment to hate list will be done.|
Ex: <react event="damage" weight="2.0" ... />
This example will apply double damage to hate list for the NPC when reacting to the damage perception.
This perception is fired when an NPC/Player dies and is broadcasted.
|event||death||The perception name|
|range||float||0.0||The range of the perception|
Ex: <react event="death" ... /> <react event="death" range="10" ... />
Upon execution the dead entity is removed from the receivers hate list.
This is perception can be filtered on the faction standing between the NPC and the sender. By defining the oper in the reaction the faction will be compared. Without the factions will not be compared.
|talk||Talk preception fired when talked to|
|event||owner anyrange, owner sensed, owner nearby, owner adjacent||Owner perceptions fired when owner is at different ranges(Will not get player perception if you are the owner).|
|player anyrange, player sensed, player nearby, player adjacent||Player perceptions fired when player is at different ranges.|
|faction_diff||integer||The faction value to compare|
|oper||>,<||React if grater than or react when less than. If no oper is defined, faction will not be compared.|
Ex: <react event="talk" faction_diff="10" oper=">" ... />
This example will only react to talk if the faction difference is more than +10 to the originator of the talk perception.
Ex: <react event="player nearby" ... />
This will allways react when a player is nearby.
In bounds Perception
This perception is fired whenever a NPC moves out of bounds.
|event||in bounds||Checks if the NPC is in bounds of a region defined in sc_npc_definitions and set up in sc_locations.|
Ex: <react event="in bounds" ... />
This perception is fired whenever a NPC moves into the nearby, adjacent, or sensed range from an item. In the nearby range the item is so close that it can be picked up.
|event||item nearby, item adjacent, item sensed||The different perception fired that nearby, adjacent, or sensed range.|
Ex: <react event="item adjacent" behavior="pickup item" /> <react event="inventory:added" behavior="equip item" />
This perception is fired whenever a NPC moves into the sensed range of a location.
|type||string||The location type name of the location sensed.|
Ex: <react event="location sensed" type="mine" ... /> <react event="location sensed" ... />
NPC Cmd Perception
This perception is fired whenever a NPC receives a command from the server. If prefixed with npccmd:self only self will react. If prefixed with npccmd:global: all npcs will be able to react to this command. The command can be generated from the NPC Cmd Response Operation <npccmd cmd="npccmd:self:open_cage" /> used in NPC Dialogues scripting at the server.
|event||npccmod:global:xx||The command sent from the server. See description above.|
Ex: <react event="npccmd:global:start_riot" ... /> <react event="npccmd:self:open_cage" ... />
Out of bounds Perception
This perception is fired whenever a NPC moves out of bounds.
|event||out of bounds||Checks if the NPC is out of bounds of a region defined in sc_npc_definitions and set up in sc_locations.|
Ex: <react event="out of bounds" ... />
This perception is fired whenever a spell is casted.
|type||string||The spell type.|
Ex: <react event="spell:self" type="direct damage" ... /> <react event="spell:target" type="direct healing" ... />
Spoken To Perception
This perception is fired whenever someone speak to a NPC. This can be used to suspend other activities while players interact with the NPC.
|type||string||true,false||True if spoken to, or false if no longer spoken to.|
Ex: <react event="spoken_to" type="true" ... />
This perception is fired every game minute. The time is in Game Time. It allows react to trigger on any combination of game hours, minutes, years, month, days.
|value||hours[0-23],minutes[0-59],years,month[1-10],days[1-32]||In the Yliakum Calendar there are 10 month in a year and 32 days per month. Match 1200 at the 2nd month like this "12,00,,2,"|
Ex: <react event="time" value="12,00,,2," ... />
Random times can be archived by using the random. Any value larger than the max hour, max minute, max month, max days will cause the event to be normalized and the next bigger value will be increased if valid. Eg. in the following example the hour will be increased if the selected random value of the minute is larger than 60.
Ex: <react event="time" value="10,0,,," random=",120,,," ... />