This system allows the game code to automatically execute a reaction that fits the context when a character is hit (HitReaction) or dies (DeathReaction).
When a Hit or a Death is detected on an actor the system evaluates the list of reactions that actor holds and chooses the first one that matches the validation criteria. After that it executes it. Hit reactions and Death reactions are very similar, but the latter ones will always end up with the actor ragdollized.
The reactions are contained on a LUA table that is parsed from an XML data file whose filepath is contained within BasicActor's fileHitDeathReactionsParamsDataFile property, so it can be customized per archetype. This could change in the future so it uses PropertyInstances to be able to customize the filepath per actor instance on the level (see Future Plans section) and whose format is described in the definition file Scripts/GameRules/HitDeathReactions_Defs.xml"
(see the XML Loader documentation to understand how the code parses the data file and why we need a separate definition file).
Each reaction provides:
validationFunc
property (specifying the name of the lua function inside HitDeathReactions.lua
that contains customized validation code).reactionFunc
property (specifying the name of the lua function inside HitDeathReactions.lua
that contains customized execution code).The system goes through the list of reactions in the order they are defined in the data file and evaluates them. A reaction is OK to go if any of its validations is successful. Each validation can choose which validation code is run to know if it's valid. That validation code may use properties inside that validation. By default the validations use some LUA and C++ code that executes a default validation code, but if there is an specific case that needs additional/different checks there's also the possibility of specifying a customized LUA or C++ validation function to do it. More details about how to write reaction validations in this chapter of the Reactions XML Format Description page.
If no customized validation function is provided, the system runs the default validation code.
If the CVar g_hitDeathReactions_useLuaDefaultFunctions
is true, the default validation code consists on calling the LUA method HitDeathReactions:DefaultValidation()
(which internally calls the C++ method CHitDeathReactions::IsValidReaction()
), if not it calls directly the C++ code CHitDeathReactions::IsValidReaction()
.
For more information about the Default Validation method and properties check the .
Custom executions can be written in LUA or C++:
They are LUA functions that receives two arguments: the reaction params table for the reaction being evaluated and the hitInfo table, with the data of the hit that generated the Hit or Death event. It's expected to return a boolean true or false depending on if the reaction is validated or not.
Example:
function HitDeathReactions:ExampleValidation(validationParams, hitInfo, causedDamage)
-- uses the default C++ validation code and additionally checks for the damage caused by the hit to be greater than 80
local bValidated = self.binds:IsValidReaction(validationParams, hitInfo) and (causedDamage > 80);
return bValidated;
end
LUA Validation functions shouldn't be used on final builds, since they can make the hit process extremely slow.
Can be registered through the CCustomReactionFunctions
object (that you can obtain from the CHitDeathReactionsSystem
instance) and the method RegisterCustomValidationFunction
.
Their signature is bool (CActor&, const SReactionParams::SValidationParams&, const HitInfo&, float fCausedDamage)
. e.g.,
// ...
const CCustomReactionFunctions& customReactions = g_pGame->GetHitDeathReactionsSystem().GetCustomReactionFunctions();
customReactions.RegisterCustomValidationFunction("DeathImpulse_Reaction", functor(*this, &CMyClass::FallNPlay_Validation));
// ...
Once a reaction is validated successfully, the system executes it. There is some LUA and C++ code already that runs a default execution code, but if there is an specific case that needs additional effects there's also the choice of using a customized LUA execution function to do it.
For more information about the Default Execution method check the .
If no customized execution function is provided, the system runs the default execution code.
If the CVar g_hitDeathReactions_useLuaDefaultFunctions
is true, the default execution code consists on calling the LUA method HitDeathReactions:DefaultHitReaction()
(which internally calls the C++ method CHitDeathReactions::ExecuteHitReaction()
), if not it calls directly the C++ code CHitDeathReactions::ExecuteHitReaction()
.
The default execution does the following:
Custom executions can be written in LUA or C++:
They are LUA functions that receives only one argument: the reaction params table for the reaction being executed. Its return value is ignored.
Example:
function HitDeathReactions:ExampleExecution(reactionParams)
-- Enables Fall and Play on the actor and notifies the end of the current reaction after 1 second
self.entity.actor:Fall({x=0,y=0,z=0});
Script.SetTimer(1000, self.binds.EndCurrentReaction, self.binds);
end
Can be registered through the CCustomReactionFunctions
object (that you can obtain from the CHitDeathReactionsSystem
instance) and the method RegisterCustomExecutionFunction
.
Their signature is void (CActor&, const SReactionParams&, const HitInfo&)
. e.g.,
// ...
const CCustomReactionFunctions& customReactions = g_pGame->GetHitDeathReactionsSystem().GetCustomReactionFunctions();
customReactions.RegisterCustomExecutionFunction("DeathImpulse_Reaction", functor(*this, &CMyClass::DeathImpulse_Reaction));
// ...
Some C++ Custom execution functions are already being registered inside the CCustomReactionFunctions class. See void CCustomReactionFunctions::RegisterCustomFunctions()
Implementation is bound to be changed, so this section is more a list of observations than anything else.
To minimize the memory footprint of the reaction animations the system uses a streaming strategy, described next.
Since we can't predict which reaction is the next to be played and we need immediate animation playback (we can't afford 1 second of streaming between the hit and the reaction) we need to pre-load the next animation to be played per reaction. For reactions with several animations we only need to pre-load one of them, and when that one is played we can release it and request streaming of the next random variation. So only one reaction anim variation is loaded per reaction.
(Specific for "g_hitdeathReactions_streaming 1" mode): The above is only true if at least on of the actors using the reaction profile (= reaction xml) is alive AND has its AI enabled AND is not pooled in the Entity Pool. If no actor fulfills those requirements, the reaction animations are released from memory.
The reaction animations are only locked if the profile is valid and is loaded, that is, if there are entities using it.
Only reaction anims are locked. Animation-graph based reactions can't lock any animation since they don't know which anim the animation graph input will trigger (so those anims must be locked externally)
You can see debugging information about the entities using and forcing reaction anims in memory with a sub-menu on the perfHUD menu for that use. Access perfHUD (Console Variable: sys_perfHUD 1) and go to Game->HitDeathReaction Streaming. Click on that entry and you'll see the following:
(switch to perfHUD "view" mode to make the table translucent and be able to see the background)
The table shows the reactions profiles and the entities using them. When the reactions have their reaction animations loaded in memory, they are printed in white. When they are not they are printed in grey. The entities below show if they are alive, have their AI Proxy enabled and are out of the Entity pool (remember the three of them have to be True for that entity to be able to request the loading of its reaction anims). If any of the conditions are constantly displayed in red that could be pointing to a problem so please let a system's GoTo person know about it.
g_hitDeathReactions_enable
. Enables/disables the system.g_hitDeathReactions_useLuaDefaultFunctions
. If enabled, it'll use the default lua methods inside HitDeathReactions script instead of the default c++ version. This is not recommended for other than testing purposes, since the call to the lua default code (that calls back the C++ code) is expensive.g_hitDeathReactions_disable_ai
. If enabled, it won't allow to execute any AI instruction during the hit reaction.g_hitDeathReactions_debug
. If enabled, it displays some debug information on top of the actors using the system.g_hitDeathReactions_disableRagdoll
. Disables enabling the ragdoll at the end of death reactions.g_hitDeathReactions_logReactionAnimsOnLoading
. If this CVar is enabled it will make the system log the reaction anims specified on the reaction files. When using "1" as value, it will log the animation names; when using "2" as a value it will log the animation filepaths.g_hitDeathReactions_streaming
. Enables/disables the streaming management for the reaction assets. "1" enables the default logic, which locks the animations only if there's at least one character using them that is alive, with its AI enabled and out of the entity pool. "2" enables a simpler logic, reaction profile-based, which basically translates into saying that the reaction animations are locked whenever the entity of at least one of the actors using them exists.g_hitDeathReactions_reload
. Reloads all the system on-the-fly: LUA code, XML Definition file and XML Data files.g_hitDeathReactions_dumpAssetUsage
. This command dumps information about asset usage in the system, in a "loaded assets vs total assets" format. Useful when streaming management is enabled for the HitDeathReactions system to have a rough estimation of how much memory are you saving.GameDll/HitDeathReactions.cpp
GameDll/HitDeathReactions.h
GameDll/HitDeathReactionsSystem.cpp
GameDll/HitDeathReactionsSystem.h
GameDll/HitDeathReactionsDefs.cpp
GameDll/HitDeathReactionsDefs.h
GameDll/CustomReactionFunctions.cpp
GameDll/CustomReactionFunctions.h
GameDll/ScriptBind_HitDeathReactions.cpp
GameDll/ScriptBind_HitDeathReactions.h
Scripts/GameRules/HitDeathReactions.lua
Scripts/GameRules/HitDeathReactions_Defs.xml
Libs/HitDeathReactionsData/*.xml
g_hitDeathReactions_reload
command on each change and you'll be done in a few minutes."binds"
that holds all the script binds registered on ScriptBind_HitDeathReactions.cpp."entity"
that holds the entity LUA table of the associated actor."hitDeathReactions"
that holds the HitDeathReactions LUA table of that actor.Please read the .
DeathReactionEnd
. Ends the current reaction immediately. For Death Reactions this translates into triggering the ragdoll right away, for Hit Reactions this translates into triggering fall and play (if possible). If the custom parameter of this animation event is "sleep" the ragdoll is forced to sleep when triggered (which means it freezes until stimulated by a physic force); this is used only for microwave deaths at the moment.RagdollStart
. Ends the current reaction at a random time between the animation is triggered and the end of the animation. For Death Reactions this translates into triggering the ragdoll right away, for Hit Reactions this translates into triggering fall and play (if possible). If the custom parameter of this animation event is "sleep" the ragdoll is forced to sleep when triggered (which means it freezes until stimulated by a physic force); this is used only for microwave deaths at the moment. If triggered outside of a hit/death reaction it will activate the ragdoll on the character.ReactionOnCollision
. Used for animated collision with the environment reactions. See associated info on the .ForbidReactions
. Forbids/Allows reactions. If the custom parameter is "0" any reaction triggered while playing this reaction animation will be forbidden (when the animation ends, everything goes back to normal). If the custom parameter is different than "0" reactions will be allowed again.<?xml version="1.0"?>
<!-- DTD for defining XML Internal General Entities. Think of them as macros -->
<!DOCTYPE HitDeathReactions_Example [
<!ENTITY Legs '
<Part value="L Leg01"/>
<Part value="L Leg02"/>
<Part value="L Leg03"/>
<Part value="L Foot"/>
<Part value="R Leg01"/>
<Part value="R Leg02"/>
<Part value="R Leg03"/>
<Part value="R Foot"/>
'>
<!-- Lower body part names -->
<!ENTITY LowerBody '
<Part value="Pelvis"/>
&Legs;
'>
]>
<DeathHitReactionsParams>
<!-- HIT REACTIONS -->
<HitReactionParams>
<!-- Uses a customized validation and runs customized execution when moving forward and hit on the legs with the (fictional) hitType Sniper, 50% of the time -->
<HitReactionParam reactionFunc="ExampleExecution" validationFunc="ExampleValidation" movementDirection="forward" minimumSpeed="2.0" probabilityPercent="0.5">
<AllowedParts>
&Legs;
</AllowedParts>
<AllowedHitTypes>
<HitType value="Sniper"/>
</AllowedHitTypes>
</HitReactionParam>
<!-- Plays an animation when hit on the lower body from 180º behind, only when standing and static -->
<HitReactionParam animName="hitReact_Leg_back_3p_01" maximumSpeed="2.0" shotOrigin="behind">
<AllowedParts>
&LowerBody;
</AllowedParts>
<AllowedStances>
<Stance value="STANCE_STAND"/>
</AllowedStances>
</HitReactionParam>
</HitReactionParams>
<!-- DEATH REACTIONS -->
<DeathReactionParams>
<!-- Sets the input "signal" of the Animation Graph to be "DR_collapse" and sets VariationInput "part" to be "head" when killed by a headshot on any standing stance -->
<DeathReactionParam>
<AnimGraphReaction inputValue="DR_collapse">
<Variations>
<Variation name="part" value="head" />
</Variations>
</AnimGraphReaction>
<AllowedStances>
<Stance value="STANCE_STAND"/>
<Stance value="STANCE_RELAXED"/>
<Stance value="STANCE_STEALTH"/>
</AllowedStances>
<AllowedParts>
<Part value="Head"/>
</AllowedParts>
</DeathReactionParam>
</DeathReactionParams>
</DeathHitReactionsParams>