diff --git a/addons/medical_state/ACE_Medical_StateMachine.hpp b/addons/medical_state/ACE_Medical_StateMachine.hpp new file mode 100644 index 0000000000..c6026f785c --- /dev/null +++ b/addons/medical_state/ACE_Medical_StateMachine.hpp @@ -0,0 +1,89 @@ +class ACE_Medical_StateMachine { + class Default { + onState = QUOTE(DFUNC(handleDefaultState)); + onStateEntered = ""; + onStateLeaving = ""; + class Injury { + targetState = "Injured"; + events[] = {'TakenInjury'}; + }; + class CriticalInjuryOrVitals { + targetState = "Unconscious"; + events[] = {'InjuryCritical', 'CriticalVitals'}; + }; + class FatalInjuryOrVitals { + targetState = "Dead"; + events[] = {'FatalVitals', 'InjuryFatal'}; + }; + }; + class Injured { + onState = QUOTE(DFUNC(handleInjuredState)); + onStateEntered = ""; + onStateLeaving = ""; + + class FullHeal { + targetState = "Default"; + events[] = {'FullHeal'}; + }; + class LastWoundTreated { + targetState = "Default"; + events[] = {'LastWoundTreated'}; + }; + class CriticalInjuryOrVitals { + targetState = "Unconscious"; + events[] = {'InjuryCritical', 'CriticalVitals'}; + }; + class FatalInjuryOrVitals { + targetState = "Dead"; + events[] = {'FatalVitals', 'InjuryFatal'}; + }; + }; + class Unconscious { + onState = QUOTE(DFUNC(handleUnconciousState)); + onStateEntered = QUOTE(DFUNC(enteredUnconscious)); // set unconscious animation & state + onStateLeaving = QUOTE(DFUNC(leavingUnconscious)); // leave unconscious animation & state + class WakeUpFromKnockDown { + targetState = "Injured"; + condition = QUOTE(_unit call FUNC(hasStableVitals)); + events[] = {'MinUnconsciousTimer'}; + }; + class WakeUpStable { + targetState = "Injured"; + condition = "unitUnconsciousTimer >= MinUnconsciousTimer"; + events[] = {'VitalsWentStable'}; + }; + class FatalTransitions { + targetState = "Dead"; + events[] = {'InjuryFatal', 'FatalVitals', 'UnconsciousTimerRanOut'}; + }; + }; + class Dead { + onStateEntered = "(_this select 0) setDamage 1"; // killing a unit also exits the state machine for this unit + }; + class Revive { + onState = QUOTE(DFUNC(handleReviveState)); + onStateEntered = QUOTE(DFUNC(enteredRevive)); // set unconscious animation & state + onStateLeaving = QUOTE(DFUNC(leavingRevive)); // leave unconscious animation & state + class FullHeal { + targetState = "Default"; + events[] = {'fullyHealed'}; + onTransition = ""; + }; + class Revived { + targetState = "Injured"; + events[] = {'Revived'}; + onTransition = ""; + }; + class TimerRanOut { + targetState = "Dead"; + condition = "timerValue >= maxReviveTime"; + events[] = {'ReviveTimer'}; + onTransition = ""; + }; + class FatalTransitions { + targetState = "Dead"; + condition = "killOnFatalDamageInRevive && inReviveState >= 10"; + events[] = {'FatalInjury'}; + }; + }; +}; diff --git a/addons/medical_state/functions/fnc_createStateMachine.sqf b/addons/medical_state/functions/fnc_createStateMachine.sqf new file mode 100644 index 0000000000..ed5ec3114d --- /dev/null +++ b/addons/medical_state/functions/fnc_createStateMachine.sqf @@ -0,0 +1,53 @@ +#include "script_component.hpp" + +params ["_stateMachineConfig"]; + +private _getCode = { + params ["_config", "_attribute"]; + private _value = getText (_config >> _attribute); + if (_value == "") {_value = "true"}; + compile _value; +}; + +private _states = []; +{ + private _stateName = configName _x; + private _onState = [_x, "onState"] call _getCode; + private _onEntry = [_x, "onEntry"] call _getCode; + private _onExit = [_x, "onExit"] call _getCode; + + // Collect all the transitions for the state + private _transitions = []; + { + private _transitionName = configName _x; + private _targetState = getText (_x >> "targetState"); + private _events = getArray (_x >> "events"); + private _condition = [_x, "condition"] call _getCode; + private _onTransition = [_x, "onTransition"] call _getCode; + + _transitions pushBack [_transitionName, _condition, _events, _onTransition, _targetState]; + } forEach ("true" configClasses _x); + _states pushBack [_stateName, _onState, _onEntry, _onExit, _transitions]; +} forEach ("true" configClasses _stateMachineConfig); + +// Helper method for finding the desired state when linking (See below) +private _getState = { + params ["_stateName"]; + private _state = ["Invalid", {}, {}, {}, []]; + { + if (_stateName == (_x select 0)) exitWith {_state = _x}; + } forEach _states; + _state; +}; + +// Now we have collected all the states, link them in transitions so we do not have to look them up on state transitions +{ + _x params ["", "", "", "", "_transitions"]; + { + _x params ["", "", "", "", "_targetState"]; + private _state = [_targetState] call _getState; + _x set [4, _state]; + } forEach _transitions; +} forEach _states; + +_states; diff --git a/addons/medical_state/functions/fnc_handleStateEvent.sqf b/addons/medical_state/functions/fnc_handleStateEvent.sqf new file mode 100644 index 0000000000..1dd54074a0 --- /dev/null +++ b/addons/medical_state/functions/fnc_handleStateEvent.sqf @@ -0,0 +1,33 @@ + +#include "script_component.hpp" + +params ["_unit", "_event", "_args"]; + +private _unitState = _unit getvariable [QGVAR(state), [CBA_missionTime, DEFAULT_STATE]]; +_unitState params ["_lastTime", "_state"]; +_state params ["_name", "_handler", "_onEntry", "_onExit", "_transitions"]; + +{ + _x params ["_transitionName", "_condition", "_events", "_onTransition", "_targetState"]; + + if (_condition && {_event in _events}) exitWith { + _targetState params ["_targetStateName", "_targetStateHandler", "_targetStateOnEntry", "_targetStateOnExit", "_targetStateTansitions"]; + + // Handle state leaving + [_unit, _event, _args] call { + params ["_unit", "_event", "_args"]; + [_unit, _event, _args] call _onExit; + [_unit, _event, _args] call _onTransition; + }; + + // Switch the state + _unitState set [1, _targetState]; + _unit setvariable [QGVAR(state), _unitState]; + + // Enter the state + [_unit, _event, _args] call { + params ["_unit", "_event", "_args"]; + [_unit, _event, _args] call _targetStateOnEntry; + }; + }; +} forEach _transitions; diff --git a/addons/medical_state/functions/fnc_stateMachine.sqf b/addons/medical_state/functions/fnc_stateMachine.sqf new file mode 100644 index 0000000000..cc34c8b0d6 --- /dev/null +++ b/addons/medical_state/functions/fnc_stateMachine.sqf @@ -0,0 +1,44 @@ +#include "script_component.hpp" + +// Delay between state runs +#define DELAY 10 +#define DEFAULT_STATE [0, "Default", {}, {}, {}, []]; + +GVAR(monitoredUnitsList) = []; +GVAR(monitoredUnitsListIsSorted) = false; + +[{ + params ["_args", "_pfhId"]; + + if (!GVAR(monitoredUnitsListIsSorted)) then { + GVAR(monitoredUnitsList) sort true; + GVAR(monitoredUnitsListIsSorted) = true; + }; + + private _delete = false; + private _exit = false; + { + _x params ["_unit"]; + + if (!isNull _unit && alive _unit) { + private _unitState = _unit getvariable [QGVAR(state), [CBA_missionTime, DEFAULT_STATE]]; + _unitState params ["_lastTime", "_state"]; + + if ((_lastTime + DELAY) > CBA_missionTime) exitWith {_exit = true;}; + _state params ["_name", "_handler", "_onEntry", "_onExit", "_transitions"]; + _unitState set [0, CBA_missionTime]; + + _unit setvariable [QGVAR(state), _unitState]; + + [_unit, _name] call _handler; + } else { + _delete = true; + GVAR(monitoredUnitsList) set [_forEachIndex, objNull]; + }; + if (_exit) exitwith {}; + } forEach GVAR(monitoredUnitsList); + + if (_delete) then { + GVAR(monitoredUnitsList) = GVAR(monitoredUnitsList) - [objNull]; + }; +}, 0, []] call CBA_fnc_addPerFrameHandler;