diff --git a/addons/statemachine/$PBOPREFIX$ b/addons/statemachine/$PBOPREFIX$ new file mode 100644 index 000000000..467f97e45 --- /dev/null +++ b/addons/statemachine/$PBOPREFIX$ @@ -0,0 +1 @@ +x\kat\addons\statemachine diff --git a/addons/statemachine/CfgEventHandlers.hpp b/addons/statemachine/CfgEventHandlers.hpp new file mode 100644 index 000000000..6f940e6b8 --- /dev/null +++ b/addons/statemachine/CfgEventHandlers.hpp @@ -0,0 +1,29 @@ +class Extended_PreInit_EventHandlers { + class ADDON { + init = QUOTE(call COMPILE_FILE(XEH_preInit)); + }; +}; + +// class Extended_PostInit_EventHandlers { +// class ADDON { +// init = QUOTE(call COMPILE_FILE(XEH_postInit)); +// }; +// }; + +class Extended_Respawn_EventHandlers { + class CAManBase { + class ADDON { + respawn = QUOTE(call ACEFUNC(medical_statemachine,resetStateDefault)); + exclude[] = {IGNORE_BASE_UAVPILOTS}; + }; + }; +}; + +class Extended_Local_EventHandlers { + class CAManBase { + class ADDON { + local = QUOTE(call FUNC(localityChangedEH)); // TODO replace with kam function + exclude[] = {IGNORE_BASE_UAVPILOTS}; + }; + }; +}; diff --git a/addons/statemachine/CfgFunctions.hpp b/addons/statemachine/CfgFunctions.hpp new file mode 100644 index 000000000..b1f633530 --- /dev/null +++ b/addons/statemachine/CfgFunctions.hpp @@ -0,0 +1,10 @@ +class CfgFunctions { + class overwrite_ace_medical_statemachine { + tag = "ace_medical_statemachine"; + class ace_medical_statemachine { + class localityChangedEH { + file = QPATHTOF(functions\fnc_localityChangedEH.sqf); + }; + }; + }; +}; diff --git a/addons/statemachine/Statemachine.hpp b/addons/statemachine/Statemachine.hpp new file mode 100644 index 000000000..f311e1256 --- /dev/null +++ b/addons/statemachine/Statemachine.hpp @@ -0,0 +1,113 @@ +// Overwrite for ACE_Medical_StateMachine +class KAT_StateMachine { + list = QUOTE(call ACEFUNC(common,getLocalUnits)); + skipNull = 1; + class Default { + onState = QACEFUNC(medical_statemachine,handleStateDefault); + class Injury { + targetState = "Injured"; + events[] = {QACEGVAR(medical,injured), QACEGVAR(medical,LoweredVitals)}; + }; + class CriticalInjuryOrVitals { + targetState = "Unconscious"; + events[] = {QACEGVAR(medical,CriticalInjury), QACEGVAR(medical,CriticalVitals), QACEGVAR(medical,knockOut)}; + }; + class FatalVitals { + targetState = "CardiacArrest"; + events[] = {QACEGVAR(medical,FatalVitals), QACEGVAR(medical,Bleedout)}; + }; + class FatalInjury { + targetState = "FatalInjury"; + events[] = {QACEGVAR(medical,FatalInjury)}; + }; + }; + class Injured { + onState = QACEFUNC(medical_statemachine,handleStateInjured); + class FullHeal { + targetState = "Default"; + events[] = {QACEGVAR(medical,FullHeal)}; + }; + class CriticalInjuryOrVitals { + targetState = "Unconscious"; + events[] = {QACEGVAR(medical,CriticalInjury), QACEGVAR(medical,CriticalVitals), QACEGVAR(medical,knockOut)}; + }; + class FatalVitals { + targetState = "CardiacArrest"; + events[] = {QACEGVAR(medical,FatalVitals), QACEGVAR(medical,Bleedout)}; + }; + class FatalInjury { + targetState = "FatalInjury"; + events[] = {QACEGVAR(medical,FatalInjury)}; + }; + }; + class Unconscious { + onState = QACEFUNC(medical_statemachine,handleStateUnconscious); + onStateEntered = QACEFUNC(medical_statemachine,enteredStateUnconscious); + class DeathAI { + targetState = "Dead"; + condition = QUOTE(!(_this getVariable [ARR_2(QQACEGVAR(medical_statemachine,AIUnconsciousness),ACEGVAR(medical_statemachine,AIUnconsciousness))]) && {!isPlayer _this}); + }; + class WakeUp { + targetState = "Injured"; + condition = QACEFUNC(medical_status,hasStableVitals); + events[] = {QACEGVAR(medical,WakeUp)}; + onTransition = QUOTE([ARR_2(_this,false)] call ACEFUNC(medical_status,setUnconsciousState)); + }; + class FatalTransitions { + targetState = "CardiacArrest"; + events[] = {QACEGVAR(medical,FatalVitals), QACEGVAR(medical,Bleedout)}; + }; + class FatalInjury { + targetState = "FatalInjury"; + events[] = {QACEGVAR(medical,FatalInjury)}; + }; + }; + class FatalInjury { + // Transition state for handling instant death from fatal injuries + // This state raises the next transition in the same frame + onStateEntered = QACEFUNC(medical_statemachine,enteredStateFatalInjury); + class SecondChance { + events[] = {QACEGVAR(medical,FatalInjuryInstantTransition)}; + targetState = "CardiacArrest"; + condition = QACEFUNC(medical_statemachine,conditionSecondChance); + onTransition = QACEFUNC(medical_statemachine,transitionSecondChance); + }; + class Death { + events[] = {QACEGVAR(medical,FatalInjuryInstantTransition)}; + targetState = "Dead"; + }; + }; + class CardiacArrest { + onState = QACEFUNC(medical_statemachine,handleStateCardiacArrest); + onStateEntered = QACEFUNC(medical_statemachine,enteredStateCardiacArrest); + onStateLeaving = QACEFUNC(medical_statemachine,leftStateCardiacArrest); + class DeathAI { + // If an AI unit reanimates, they will immediately die upon entering unconsciousness if AI Unconsciousness is disabled + // As a result, we immediately kill the AI unit since cardiac arrest is effectively useless for it + targetState = "Dead"; + condition = QUOTE(!ACEGVAR(medical_statemachine,AIUnconsciousness) && {!isPlayer _this}); + }; + class Timeout { + targetState = "Dead"; + condition = QACEFUNC(medical_statemachine,conditionCardiacArrestTimer); + }; + class Reanimation { + targetState = "Unconscious"; + events[] = {QACEGVAR(medical,CPRSucceeded)}; + }; + class Execution { + targetState = "Dead"; + condition = QACEFUNC(medical_statemachine,conditionExecutionDeath); + events[] = {QACEGVAR(medical,FatalInjury)}; + }; + class Bleedout { + targetState = "Dead"; + condition = QUOTE((ACEGVAR(medical_statemachine,cardiacArrestBleedoutEnabled))); // wrap to ensure cba uses this as code and not a direct variable + events[] = {QACEGVAR(medical,Bleedout)}; + }; + }; + class Dead { + // When the unit is killed it's no longer handled by the statemachine + onStateEntered = QACEFUNC(medical_statemachine,enteredStateDeath); + }; +}; diff --git a/addons/statemachine/XEH_PREP.hpp b/addons/statemachine/XEH_PREP.hpp new file mode 100644 index 000000000..5804425bc --- /dev/null +++ b/addons/statemachine/XEH_PREP.hpp @@ -0,0 +1 @@ +PREP(localityChangedEH); diff --git a/addons/statemachine/XEH_postInit.sqf b/addons/statemachine/XEH_postInit.sqf new file mode 100644 index 000000000..5e9e46dbc --- /dev/null +++ b/addons/statemachine/XEH_postInit.sqf @@ -0,0 +1,14 @@ +#include "script_component.hpp" + +// ["ace_killed", { // global event +// params ["_unit"]; + +// // Prevent second ragdoll of uncon units when they're killed +// if ( +// IS_UNCONSCIOUS(_unit) && !isAwake _unit // uncon and not ragdolling +// && {isPlayer _unit || {_unit getVariable [QGVAR(AIUnconsciousness), GVAR(AIUnconsciousness)]}} +// ) then { +// _unit enableSimulation false; +// [{_this enableSimulation true}, _unit, 2] call CBA_fnc_waitAndExecute; +// }; +// }] call CBA_fnc_addEventHandler; diff --git a/addons/statemachine/XEH_preInit.sqf b/addons/statemachine/XEH_preInit.sqf new file mode 100644 index 000000000..33547fe06 --- /dev/null +++ b/addons/statemachine/XEH_preInit.sqf @@ -0,0 +1,14 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +#define CBA_SETTINGS_CAT "KAT - ADV Medical: Advanced States" + +// Overwrite ace statemachine +ACEGVAR(medical,STATE_MACHINE) = (configFile >> "KAT_StateMachine") call CBA_statemachine_fnc_createFromConfig; + +ADDON = true; diff --git a/addons/statemachine/config.cpp b/addons/statemachine/config.cpp new file mode 100644 index 000000000..9169e0182 --- /dev/null +++ b/addons/statemachine/config.cpp @@ -0,0 +1,22 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = COMPONENT_NAME; + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = { + "ace_medical_vitals", + "ace_medical_statemachine" + }; + author = "apo_tle"; + authors[] = {"Glowbal", "KoffeinFlummi","apo_tle"}; + url = ECSTRING(main,URL); + VERSION_CONFIG; + }; +}; + +#include "Statemachine.hpp" +#include "CfgEventHandlers.hpp" +#include "CfgFunctions.hpp" \ No newline at end of file diff --git a/addons/statemachine/functions/fnc_localityChangedEH.sqf b/addons/statemachine/functions/fnc_localityChangedEH.sqf new file mode 100644 index 000000000..c07df7e76 --- /dev/null +++ b/addons/statemachine/functions/fnc_localityChangedEH.sqf @@ -0,0 +1,56 @@ +#include "..\script_component.hpp" +/* + * Author: PabstMirror + * Modified: apo_tle + * Handles locality switch. Will also be called at unit init. + * Because state machine state is local only, when a unit transfers locality we need to manually transition to it's current state + * + * Arguments: + * 0: Unit + * 1: isLocal + * + * Return Value: + * None + * + * Example: + * [player, true] call ace_medical_statemachine_fnc_localityChangedEH + * + * Public: No + */ + +params ["_unit", "_isLocal"]; +TRACE_2("localityChangedEH",_unit,_isLocal); + +if (!alive _unit) exitWith {}; + +if (_isLocal) then { + private _currentState = [_unit, ACEGVAR(medical,STATE_MACHINE)] call CBA_statemachine_fnc_getCurrentState; + TRACE_1("local",_currentState); + + switch (true) do { + case (IN_CRDC_ARRST(_unit)): { + if (_currentState == "CardiacArrest") exitWith {}; + _unit setVariable [VAR_CRDC_ARRST, false]; // force reset vars so setCardiacArrestState can run (enteredStateCardiacArrest will also be called) + _unit setVariable [VAR_UNCON, false]; + TRACE_1("manually changing state to CardiacArrest",_currentState); + [_unit, ACEGVAR(medical,STATE_MACHINE), _currentState, "CardiacArrest", {}, "LocalityChange"] call CBA_statemachine_fnc_manualTransition; + }; + case (IS_UNCONSCIOUS(_unit)): { + if (_currentState == "Unconscious") exitWith {}; + _unit setVariable [VAR_UNCON, false]; // force reset var so ace_medical_status_fnc_setUnconsciousState can run + TRACE_1("manually changing state to Unconscious",_currentState); + [_unit, ACEGVAR(medical,STATE_MACHINE), _currentState, "Unconscious", {}, "LocalityChange"] call CBA_statemachine_fnc_manualTransition; + }; + case (IS_BLEEDING(_unit) || {IS_IN_PAIN(_unit)}): { + if (_currentState == "Injured") exitWith {}; + TRACE_1("manually changing state to Injured",_currentState); + [_unit, ACEGVAR(medical,STATE_MACHINE), _currentState, "Injured", {}, "LocalityChange"] call CBA_statemachine_fnc_manualTransition; + }; + default { + // If locality transfers back and forth, we could be in an old state and should transfer back to default + if (_currentState == "Default") exitWith {}; + TRACE_1("manually changing state to Default",_currentState); + [_unit, ACEGVAR(medical,STATE_MACHINE), _currentState, "Default", {}, "LocalityChange"] call CBA_statemachine_fnc_manualTransition; + }; + }; +} \ No newline at end of file diff --git a/addons/statemachine/script_component.hpp b/addons/statemachine/script_component.hpp new file mode 100644 index 000000000..e2e55adff --- /dev/null +++ b/addons/statemachine/script_component.hpp @@ -0,0 +1,17 @@ +#define COMPONENT statemachine +#define COMPONENT_BEAUTIFIED KAT - STATEMACHINE +#include "\x\kat\addons\main\script_mod.hpp" + +// #define DEBUG_MODE_FULL +// #define DISABLE_COMPILE_CACHE +// #define ENABLE_PERFORMANCE_COUNTERS + +#ifdef DEBUG_ENABLED_STATEMACHINE + #define DEBUG_MODE_FULL +#endif + +#ifdef DEBUG_SETTINGS_STATEMACHINE + #define DEBUG_SETTINGS DEBUG_SETTINGS_STATEMACHINE +#endif + +#include "\x\kat\addons\main\script_macros.hpp" \ No newline at end of file diff --git a/addons/statemachine/stringtable.xml b/addons/statemachine/stringtable.xml new file mode 100644 index 000000000..ee08a6528 --- /dev/null +++ b/addons/statemachine/stringtable.xml @@ -0,0 +1,5 @@ + + + + +