diff --git a/addons/airway/functions/fnc_handlePuking.sqf b/addons/airway/functions/fnc_handlePuking.sqf index 4ee49ae4e..258de9327 100644 --- a/addons/airway/functions/fnc_handlePuking.sqf +++ b/addons/airway/functions/fnc_handlePuking.sqf @@ -35,7 +35,7 @@ _unit setVariable ["kat_pukeActive_PFH", true]; if (GVAR(occlusion_cooldownPeriod) > 0 && {(_unit getVariable [QGVAR(clearedTime), 0] > 0) && ((_unit getVariable [QGVAR(clearedTime), 0]) + GVAR(occlusion_cooldownPeriod)) > CBA_missionTime}) exitWith {}; - if (random(100) <= GVAR(probability_occluded)) then { + if ((random(100) <= GVAR(probability_occluded)) || (_unit getVariable [QEGVAR(brain,ICP),15] > 25)) then { if !(_unit getVariable [QGVAR(occluded), false]) then { _unit setVariable [QGVAR(occluded), true, true]; if (GVAR(checkbox_puking_sound)) then { diff --git a/addons/brain/$PBOPREFIX$ b/addons/brain/$PBOPREFIX$ new file mode 100644 index 000000000..9793c72cf --- /dev/null +++ b/addons/brain/$PBOPREFIX$ @@ -0,0 +1 @@ +x\kat\addons\brain \ No newline at end of file diff --git a/addons/brain/CfgEventHandlers.hpp b/addons/brain/CfgEventHandlers.hpp new file mode 100644 index 000000000..4551ce282 --- /dev/null +++ b/addons/brain/CfgEventHandlers.hpp @@ -0,0 +1,20 @@ +class Extended_PreInit_EventHandlers { + class ADDON { + init = QUOTE(call COMPILE_FILE(XEH_preInit)); + disableModuload = "true"; + }; +}; + +class Extended_Init_EventHandlers { + class CAManBase { + class ADDON { + init = QUOTE([ARR_2((_this select 0),false)] call FUNC(init)); + }; + }; +}; + +class Extended_PostInit_EventHandlers { + class ADDON { + init = QUOTE(call COMPILE_FILE(XEH_postInit)); + }; +}; diff --git a/addons/brain/XEH_PREP.hpp b/addons/brain/XEH_PREP.hpp new file mode 100644 index 000000000..036459261 --- /dev/null +++ b/addons/brain/XEH_PREP.hpp @@ -0,0 +1,6 @@ +PREP(concussion); +PREP(fullHealLocal); +PREP(handleAutoregulation); +PREP(handleBrainActivity); +PREP(handleRespawn); +PREP(init); \ No newline at end of file diff --git a/addons/brain/XEH_postInit.sqf b/addons/brain/XEH_postInit.sqf new file mode 100644 index 000000000..45722abb7 --- /dev/null +++ b/addons/brain/XEH_postInit.sqf @@ -0,0 +1,8 @@ +#include "script_component.hpp" + +[QGVAR(handleAutoregulation), LINKFUNC(handleAutoregulation)] call CBA_fnc_addEventHandler; +[QGVAR(handleBrainActivity), LINKFUNC(handleBrainActivity)] call CBA_fnc_addEventHandler; +[QACEGVAR(medical,woundReceived),LINKFUNC(concussion)] call CBA_fnc_addEventHandler; + +[QEGVAR(misc,handleRespawn), LINKFUNC(handleRespawn)] call CBA_fnc_addEventHandler; +[QACEGVAR(medical_treatment,fullHealLocalMod), LINKFUNC(fullHealLocal)] call CBA_fnc_addEventHandler; \ No newline at end of file diff --git a/addons/brain/XEH_preInit.sqf b/addons/brain/XEH_preInit.sqf new file mode 100644 index 000000000..b34d46a6a --- /dev/null +++ b/addons/brain/XEH_preInit.sqf @@ -0,0 +1,161 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +#define CBA_SETTINGS_CAT "KAT - ADV Medical: Brain Injuries" + +//Enable traumatic brain injuries +[ + QGVAR(enable), + "CHECKBOX", + LLSTRING(SETTING_enable), + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [true], + true +] call CBA_Settings_fnc_init; + +//Amount to reduce ICP by when brain is not actively swelling +[ + QGVAR(ICPreduction), + "SLIDER", + [LLSTRING(SETTING_ICPreduction)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [0, 2, 0.3, 1], + true +] call CBA_Settings_fnc_init; + +//Multiplier for ICP reduction if saline is being transfused +[ + QGVAR(ICPreductionMult), + "SLIDER", + [LLSTRING(SETTING_ICPreductionMult)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [1, 3, 1.3, 1], + true +] call CBA_Settings_fnc_init; + +// Minimum CMR to sustain consciousness. Not achieving will prevent awakening +[ + QGVAR(stableCMR), + "SLIDER", + [LLSTRING(SETTING_stableCMR)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [0, 100, 80, 1], + true +] call CBA_Settings_fnc_init; + +// Minimum CMR required for unit to pass out randomly +[ + QGVAR(CMRunconsciousThreshold), + "SLIDER", + [LLSTRING(SETTING_CMRunconsciousThreshold)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [0, 100, 85, 1], + true +] call CBA_Settings_fnc_init; + +//Chance for a unit to pass out randomly from low CMR +[ + QGVAR(CMRunconsciousChance), + "SLIDER", + [LLSTRING(SETTING_concussionChance)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [0, 100, 20, 1], + true +] call CBA_Settings_fnc_init; + +// Minimum ICP required for bradycardia to occur as a result +[ + QGVAR(ICPbradycardiaThreshold), + "SLIDER", + [LLSTRING(SETTING_ICPbradycardiaThreshold)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [20, 70, 50, 1], + true +] call CBA_Settings_fnc_init; + +//Chance of bradycardia occuring +[ + QGVAR(ICPbradycardiaChance), + "SLIDER", + [LLSTRING(SETTING_ICPbradycardiaChance)], + [CBA_SETTINGS_CAT, ELSTRING(GUI,SubCategory_Basic)], + [0, 100, 15, 1], + true +] call CBA_Settings_fnc_init; + +//Chance of a concussion occuring +[ + QGVAR(concussionChance), + "SLIDER", + [LLSTRING(SETTING_concussionChance)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_Concussions)], + [0, 100, 5, 1], + true +] call CBA_Settings_fnc_init; + +// Minimum impact damage required to cause healable tissue damage upon receiving a concussion +[ + QGVAR(tissueImpactDamage), + "SLIDER", + [LLSTRING(SETTING_tissueImpactDamage)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_Concussions)], + [0, 2, 0.3, 1], + true +] call CBA_Settings_fnc_init; + +// Minimum impact damage required to cause instant tissue necrosis upon receiving a concussion +[ + QGVAR(necrosisImpactDamage), + "SLIDER", + [LLSTRING(SETTING_necrosisImpactDamage)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_Concussions)], + [0, 2, 0.8, 1], + true +] call CBA_Settings_fnc_init; + +//How much reversible damage will decrease by every tick +[ + QGVAR(reversibleDamageLoss), + "SLIDER", + [LLSTRING(SETTING_reversibleDamageLoss)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_BrainDeath)], + [0, 2, 0.4, 1], + true +] call CBA_Settings_fnc_init; + +// Number of ticks required until tissue necrosis begins to occur, ticks occuring every 15 seconds +[ + QGVAR(necrosisTicks), + "SLIDER", + [LLSTRING(SETTING_necrosisTicks)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_BrainDeath)], + [1, 60, 10, 0], + true +] call CBA_Settings_fnc_init; + +// rO2 value required before necrosis occurs +[ + QGVAR(necrosisThreshold), + "SLIDER", + [LLSTRING(SETTING_necrosisThreshold)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_BrainDeath)], + [1, 80, 60, 0], + true +] call CBA_Settings_fnc_init; + +//How much necrosis increases each tick +[ + QGVAR(necrosisIncrease), + "SLIDER", + [LLSTRING(SETTING_necrosisIncrease)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_BrainDeath)], + [0, 10, 1.6, 1], + true +] call CBA_Settings_fnc_init; + +ADDON = true; \ No newline at end of file diff --git a/addons/brain/config.cpp b/addons/brain/config.cpp new file mode 100644 index 000000000..845094b1a --- /dev/null +++ b/addons/brain/config.cpp @@ -0,0 +1,36 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = COMPONENT_NAME; + requiredVersion = REQUIRED_VERSION; + units[] = {}; + weapons[] = {}; + magazines[] = { }; + requiredAddons[] = { + "ace_medical", + "ace_medical_ai", + "ace_medical_blood", + "ace_medical_damage", + "ace_medical_engine", + "ace_medical_feedback", + "ace_medical_gui", + "ace_medical_statemachine", + "ace_medical_status", + "ace_medical_treatment", + "ace_medical_vitals", + "ace_dogtags", + "kat_main", + "kat_airway", + "kat_circulation", + "cba_settings" + }; + author = "apo_tle"; + authors[] = {"apo_tle"}; + url = ECSTRING(main,URL); + VERSION_CONFIG; + }; +}; + +#include "CfgEventHandlers.hpp" +//#include "CfgWeapons.hpp" \ No newline at end of file diff --git a/addons/brain/functions/fnc_concussion.sqf b/addons/brain/functions/fnc_concussion.sqf new file mode 100644 index 000000000..32b6243ea --- /dev/null +++ b/addons/brain/functions/fnc_concussion.sqf @@ -0,0 +1,93 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Called when a unit is hit. Handles receiving TBIs and setting up dedicated PFHs to increase ICP. + * + * Arguments: + * 0: Unit That Was Hit + * 1: Damage done to each body part + * 0: Damage + * 1: Bodypart + * 2: Shooter + * 3: Ammo classname or damage type + * + * Return Value: + * None + * + * Example: + * [cursorTarget, [1, "Head"], objNull, "BulletBase"] call kat_brain_fnc_concussion + * + * Public: No + */ + + params ["_unit", "_allDamages", "", "_ammo"]; +_allDamages select 0 params ["_damage", "_bodyPart"]; + +if (!(GVAR(enable)) || !(_bodyPart isEqualTo "Head")) exitWith {}; + +// Increase the chance based on how much damage was received +private _chanceIncrease = linearConversion [0,1,_damage,5,30,true]; +// Increase the chance of concussions depending on the damage type +private _chanceMultiplier = 1; +if (_ammo in ["vehiclehit","explosive","shell","vehiclecrash"]) then { + _chanceMultiplier = linearConversion [0,3,(["vehiclehit","explosive","shell","vehiclecrash"] find _ammo),1.2,2,true]; +}; + +private _concussionChance = (GVAR(concussionChance) + _chanceIncrease) * _chanceMultiplier; +if (floor (random 100) <= _concussionChance) then { + private _currentSeverity = _unit getVariable [QGVAR(concussionSeverity),0]; + if (_damage > _currentSeverity) then { //Replace the current concussion with the more severe one + // Add instantaneous effects from concussions + if (_damage > GVAR(necrosisImpactDamage)) then { // Cause instant necrosis if threshold is surpassed + private _necrosis = _unit getVariable [QGVAR(necrosis),0]; + private _newNecrosis = linearConversion [0, 1,_damage,0,7,true]; //Increase tissue necrosis by 7% max on impact + if (_newNecrosis > _necrosis) then { // Prevent reverting existing necrosis levels + _unit setVariable [QGVAR(necrosis),_newNecrosis,true]; + }; + }; + if (_damage > GVAR(tissueImpactDamage)) then { // Cause reversible tissue damage if threshold is surpassed + private _reversibleDamage = _unit getVariable [QGVAR(reversibleDamage),0]; + _reversibleDamage = _reversibleDamage + (linearConversion [0,1,_damage,0,25,true]); //Increase reversible damage by max 25% on impact + _unit setVariable [QGVAR(reversibleDamage),_reversibleDamage,true]; + }; + + // Increase ICP to a base level depending on damage taken + private _ICP = _unit getVariable [QGVAR(ICP),10]; + private _ICPincrease = linearConversion [0, 1,_damage,1,3,true]; + _unit setVariable [QGVAR(ICP),_ICP+_damage,true]; + + // Set up PFH + if !(_currentSeverity isEqualTo 0) then { //Delete the existing concussion PFH if it exists + private _existingPFH = _unit getVariable [QGVAR(concussionPFH),0]; + [_existingPFH] call CBA_fnc_removePerFrameHandler; + }; + + private _maxICPIncrease = linearConversion [0,1,_damage,0,40]; + private _newPFH = [{ + params ["_args", "_idPFH"]; + _args params ["_unit","_severity","_maxICPIncrease"]; + if !(alive _unit) exitWith { + _unit setVariable [QGVAR(concussionPFH),0,true]; + _unit setVariable [QGVAR(concussionSeverity),0,true]; + [_idPFH] call CBA_fnc_removePerFrameHandler; + }; + + private _ICP = _unit getVariable [QGVAR(ICP),10]; + + //Kill the concussion once ICP reaches the limit for the concussion's severity + if (_ICP >= (20+_maxICPIncrease)) exitWith { + _unit setVariable [QGVAR(concussionPFH),0,true]; + _unit setVariable [QGVAR(concussionSeverity),0,true]; + [_idPFH] call CBA_fnc_removePerFrameHandler; + }; + + private _ICPincrease = linearConversion [0,1,_severity,0,2,true]; + _unit setVariable [QGVAR(ICP),_ICP+_ICPincrease,true]; //Increase ICP by concussion severity + + }, 10, [_unit,_damage,_maxICPIncrease]] call CBA_fnc_addPerFrameHandler; + + _unit setVariable [QGVAR(concussionPFH),_newPFH,true]; + _unit setVariable [QGVAR(concussionSeverity),_damage,true]; + } + +}; \ No newline at end of file diff --git a/addons/brain/functions/fnc_findSaline.sqf b/addons/brain/functions/fnc_findSaline.sqf new file mode 100644 index 000000000..6862b6ac7 --- /dev/null +++ b/addons/brain/functions/fnc_findSaline.sqf @@ -0,0 +1,33 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle, from _pharma_fnc_salineCheck + * Check for any active saline IV + * + * Arguments: + * 0: Patient + * + * Return Value: + * None + * + * Example: + * [_patient] call kat_pharma_fnc_findSaline; + * + * Public: No + */ + +params ["_patient"]; + +private _fluidCheck = _patient getVariable [QACEGVAR(medical,ivBags), []]; +private _check = false; + +{ + _x params ["_bagVolumeRemaining", "_type"]; + + if (_type isEqualTo "Saline" && _bagVolumeRemaining >= 30) exitWith { + _check = true; + }; +} forEach _fluidCheck; + +if (_check) exitWith {true}; + +false diff --git a/addons/brain/functions/fnc_fullHealLocal.sqf b/addons/brain/functions/fnc_fullHealLocal.sqf new file mode 100644 index 000000000..cf26d33ca --- /dev/null +++ b/addons/brain/functions/fnc_fullHealLocal.sqf @@ -0,0 +1,37 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Local callback for fully healing a patient. + * + * Arguments: + * 0: Patient + * + * Return Value: + * None + * + * Example: + * [player] call kat_brain_fnc_fullHealLocal + * + * Public: No + */ + +params ["_patient"]; +TRACE_1("fullHealLocal",_patient); + +_unit setVariable [QGVAR(CMR),100,true]; // Cerebral Metabolic Rate (%) +_unit setVariable [QGVAR(CBF),800,true]; // Cerebral Blood Flow +_unit setVariable [QGVAR(CVR),0.1,true]; // Cerebral Vascular Resistance +_unit setVariable [QGVAR(ICP),15,true]; // Intracranial Pressure +_unit setVariable [QGVAR(CPR),100,true]; // Cerebral Perfusion Rate +_unit setVariable [QGVAR(rO2),80,true]; // Brain O2 saturation + +_unit setVariable [QGVAR(necrosis),0,true]; +_unit setVariable [QGVAR(deoxygenatedTicks),0,true]; +_unit setVariable [QGVAR(reversibleDamage),0,true]; + +//TODO fix these PFHs so that they dont create duplicates when a player is healed +_unit setVariable [QGVAR(autoregulationPFH), [_unit] call FUNC(handleAutoregulation),true]; +_unit setVariable [QGVAR(activityPFH), [_unit] call FUNC(handleBrainActivity),true]; + +_unit setVariable [QGVAR(concussionPFH),0,true]; +_unit setVariable [QGVAR(concussionSeverity),0,true]; \ No newline at end of file diff --git a/addons/brain/functions/fnc_handleAutoregulation.sqf b/addons/brain/functions/fnc_handleAutoregulation.sqf new file mode 100644 index 000000000..74dd9b509 --- /dev/null +++ b/addons/brain/functions/fnc_handleAutoregulation.sqf @@ -0,0 +1,67 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Handles the autoregulation of CVR to achieve optimal CBF. + * + * Arguments: + * 0: The Unit + * + * Return Value: + * 0: PFH enabled + * + * Example: + * [bob] call kat_brain_fnc_handleAutoregulation + * + * Public: No + */ + +params ["_unit"]; + +if (!local _unit) then { + [QGVAR(handleAutoregulation), [_unit], _unit] call CBA_fnc_targetEvent; +}; + +if (!GVAR(enable) || _unit getVariable [QGVAR(autoregulationPFH),false]) exitWith { + true +}; + +[{ + params ["_args", "_idPFH"]; + _args params ["_unit"]; + if !(alive _unit) exitWith { + _unit setVariable [QGVAR(autoregulationPFH),false,true]; + [_idPFH] call CBA_fnc_removePerFrameHandler; + }; + + GET_BLOOD_PRESSURE(_unit) params ["_systolic","_diastolic"]; + private _MAP = _diastolic + ((_systolic-_diastolic)/3); + + private _CVR = _unit getVariable [QGVAR(CVR),0.1]; + private _ICP = 20 max (_unit getVariable [QGVAR(ICP),10]); + + // calculate cerebral blood flow and autoregulation + private _targetCBF = 800; //TODO change this value depending on blood oxygen saturation + + private _targetCVR = (_MAP-20)/_targetCBF; + _targetCVR = (0.0375 max _targetCVR) min 0.17875; //Clamp CVR between two values: + //If the required CVR is less than 0.0375, CBF will not be able to be high enough and reduced perfusion occurs. + //If the required CVR exceeds 0.17875, CBF will be too high and luxury perfusion occurs. + + private _newCVR = (((_CVR+_targetCVR ) / 2) + _targetCVR) / 2; // interpolate CVR to target value + + private _CPP = _MAP-_ICP max 0; + if (_CPP > 200) then { // Simulate autoregulation breakthrough (too large CPP causes spike in CBF) + _newCVR = 0.1; + }; + + private _CBF = round (_CPP/_newCVR); + private _CPR = (_CBF/800 * 100) min 200; + _CPR = _CPR * (GET_SPO2(_unit)/100); + + _unit setVariable [QGVAR(CVR),_newCVR,true]; + _unit setVariable [QGVAR(CBF),_CBF,true]; + _unit setVariable [QGVAR(CPR),_CPR,true]; + +}, 3, [_unit]] call CBA_fnc_addPerFrameHandler; + +true; diff --git a/addons/brain/functions/fnc_handleBrainActivity.sqf b/addons/brain/functions/fnc_handleBrainActivity.sqf new file mode 100644 index 000000000..b24364ba7 --- /dev/null +++ b/addons/brain/functions/fnc_handleBrainActivity.sqf @@ -0,0 +1,118 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Handles the simulation of oxygen perfusion into the brain and therefore the metabolic rate. + * + * Arguments: + * 0: The Unit + * + * Return Value: + * 0: PFH enabled + * + * Example: + * [bob] call kat_brain_fnc_handleBrainActivity + * + * Public: No + */ + + if (!local _unit) then { + [QGVAR(handleBrainActivity), [_unit], _unit] call CBA_fnc_targetEvent; +}; + +if (!GVAR(enable) || _unit getVariable [QGVAR(activityPFH),false]) exitWith { + true +}; + +[{ + params ["_args", "_idPFH"]; + _args params ["_unit"]; + if !(alive _unit) exitWith { + _unit setVariable [QGVAR(activityPFH),false,true]; + [_idPFH] call CBA_fnc_removePerFrameHandler; + }; + + // Calculate brain oxygen saturation + private _CPR = _unit getVariable [QGVAR(CPR),100]; + private _perfusionDelta = switch (true) do { // Calculate change in rO2 based on graph + case (_CPR <= 50): {-3}; + case (_CPR <= 100): {(_CPR/10) - 8}; + case (_CPR <= 110): {2}; + case (_CPR <= 121.052): {(_CPR/30) - (5/3)}; + case (_CPR <= 160): {(_CPR/-8) + 17.5}; + default {-2.5}; + }; + private _rO2 = _unit getVariable [QGVAR(rO2),80]; + _rO2 = (0 max (_rO2 + _perfusionDelta)) min 80; // Transform rO2 by the perfusion delta within bounds of 0 and 80 + _unit setVariable [QGVAR(rO2),_rO2,true]; + + //Calculate tissue necrosis and brain death + private _necrosis = _unit getVariable [QGVAR(necrosis),0]; + private _deoxygenatedTicks = _unit getVariable [QGVAR(deoxygenatedTicks),0]; + _deoxygenatedTicks = [_deoxygenatedTicks + 1,_deoxygenatedTicks - 2] select (rO2 < GVAR(necrosisThreshold)); + _deoxygenatedTicks = (0 max _dexoxygenation) min GVAR(necrosisTicks); + if (_deoxygenatedTicks == GVAR(necrosisThreshold)) then { + _necrosis = (_necrosis + GVAR(necrosisIncrease)) min 100; + //TODO kill unit when this gets too high + }; + + private _reversibleDamage = _unit getVariable [QGVAR(reversibleDamage),0]; + + //Finally, calculate total brain metabolic rate + private _CMR = 100 * ((100-_necrosis)/100) * ((100-_reversibleDamage)/100); + + _unit setVariable [QGVAR(necrosis),_necrosis,true]; + _unit setVariable [QGVAR(CMR),_CMR,true]; + + private _ICP = _unit getVariable [QGVAR(ICP),15]; + + if (_unit getVariable [QGVAR(concussionPFH),0] isEqualTo 0) then { + + //Reduce ICP if no longer swelling + private _hasSaline = [_unit] call FUNC(findSaline); + private _hasMaxBlood = (GET_SIMPLE_BLOOD_VOLUME(_unit) >= 6); // Workaround for not being able to overfill with fluids (no saline ivs can be started if no blood loss) + private _icpReduction = [GVAR(ICPreduction),GVAR(ICPreduction)*GVAR(ICPreductionMult)] select _hasSaline; // Multiply ICP reduction if saline present + private _newICP = _ICP - _icpReduction; + // Set "floors" for ICP, preventing ICP from returning to normal levels without saline + if (!(_hasSaline || _hasMaxblood) ) then { + switch (true) do { + case (ICP >= 45): { + _newICP = 45 max _newICP; + }; + case (ICP >= 38): { + _newICP = 38 max _newICP; + }; + default { //Prevent ICP from returning to normal without saline + _newICP = 25 max _newICP; + }; + }; + }; + _newICP = 15 max _newICP; + _unit setVariable [QGVAR(ICP),_newICP,true]; + + //Reduce reversible tissue damage + _unit setVariable [QGVAR(reversibleDamage),_reversibleDamage-GVAR(reversibleDamageLoss),true]; + }; + + //Chance to cause bradycardia if ICP is too high + if (_ICP >= GVAR(ICPbradycardiaThreshold)) then { + if !(floor (random 100) <= GVAR(ICPbradycardiaChance)) exitWith {}; + + scopeName "causeBradycardia"; + { //Prevent adding bradycardia if it already exists + _x params ["_medication"]; + if (_medication isEqualTo "BRADYCARDIA") exitWith { + breakOut "causeBradycardia"; + }; + } forEach (_unit getVariable [QACEGVAR(medical,medications), []]); + + [_unit, "BRADYCARDIA", 120, 1200, -40, 0, 0] call ACEFUNC(medical_status,addMedicationAdjustment); + }; + + //Cause LOC if CMR becomes too low + if (_CMR <= GVAR(CMRunconsciousThreshold) && !(_unit getVariable ["ACE_isUnconscious",false])) then { + if (!(floor (random 100) <= GVAR(CMRunconsciousChance)) && (_CMR >= GVAR(stableCMR))) exitWith {}; + [QACEGVAR(medical,CriticalVitals), _unit] call CBA_fnc_localEvent; + }; +}, 15, [_unit]] call CBA_fnc_addPerFrameHandler; + +true; \ No newline at end of file diff --git a/addons/brain/functions/fnc_handleRespawn.sqf b/addons/brain/functions/fnc_handleRespawn.sqf new file mode 100644 index 000000000..7af49fd35 --- /dev/null +++ b/addons/brain/functions/fnc_handleRespawn.sqf @@ -0,0 +1,36 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Ensures proper initial values reset on respawn + * + * Arguments: + * 0: Unit + * 1: Corpse + * + * Return Value: + * None + * + * Example: + * [alive, body] call kat_brain_fnc_handleRespawn; + * + * Public: No + */ + +params ["_unit","_dead"]; +TRACE_2("handleRespawn",_unit,_dead); + +[_unit] call FUNC(fullHealLocal); + +_unit setVariable [QEGVAR(brain,CMR),100,true]; +_unit setVariable [QEGVAR(brain,CBF),800,true]; +_unit setVariable [QEGVAR(brain,CVR),0.1,true]; +_unit setVariable [QEGVAR(brain,ICP),15,true]; +_unit setVariable [QEGVAR(brain,CPR),100,true]; +_unit setVariable [QEGVAR(brain,rO2),80,true]; +_unit setVariable [QEGVAR(brain,necrosis),0,true]; +_unit setVariable [QGVAR(deoxygenatedTicks),0,true]; +_unit setVariable [QEGVAR(brain,reversibleDamage),0,true]; +_unit setVariable [QEGVAR(brain,autoregulationPFH), [_unit] call EFUNC(brain,handleAutoregulation),true]; +_unit setVariable [QEGVAR(brain,activityPFH), [_unit] call EFUNC(brain,handleBrainActivity),true]; +_unit setVariable [QEGVAR(brain,concussionPFH),0,true]; +_unit setVariable [QEGVAR(brain,concussionSeverity),0,true]; \ No newline at end of file diff --git a/addons/brain/functions/fnc_init.sqf b/addons/brain/functions/fnc_init.sqf new file mode 100644 index 000000000..e0c2bd945 --- /dev/null +++ b/addons/brain/functions/fnc_init.sqf @@ -0,0 +1,23 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Initializes unit variables. + * + * Arguments: + * 0: The Unit + * + * Return Value: + * None + * + * Example: + * [bob] call kat_brain_fnc_init + * + * Public: No + */ + +params ["_unit", ["_isRespawn", true]]; + +if (!local _unit) exitWith {}; +if !(GVAR(enable)) exitWith {}; + +[_unit] call FUNC(fullHealLocal); \ No newline at end of file diff --git a/addons/brain/script_component.hpp b/addons/brain/script_component.hpp new file mode 100644 index 000000000..a34bec86a --- /dev/null +++ b/addons/brain/script_component.hpp @@ -0,0 +1,17 @@ +#define COMPONENT brain +#define COMPONENT_BEAUTIFIED KAT - Brain Injuries +#include "\x\kat\addons\main\script_mod.hpp" + +// #define DEBUG_MODE_FULL +// #define DISABLE_COMPILE_CACHE +// #define ENABLE_PERFORMANCE_COUNTERS + +#ifdef DEBUG_ENABLED_BRAIN + #define DEBUG_MODE_FULL +#endif + +#ifdef DEBUG_SETTINGS_BRAIN + #define DEBUG_SETTINGS DEBUG_SETTINGS_BRAIN +#endif + +#include "\x\kat\addons\main\script_macros.hpp" diff --git a/addons/brain/stringtable.xml b/addons/brain/stringtable.xml new file mode 100644 index 000000000..c9d3de738 --- /dev/null +++ b/addons/brain/stringtable.xml @@ -0,0 +1,8 @@ + + + + + Enable Brain Simulation + + + diff --git a/addons/feedback/XEH_PREP.hpp b/addons/feedback/XEH_PREP.hpp index 42df1399d..168d4c257 100644 --- a/addons/feedback/XEH_PREP.hpp +++ b/addons/feedback/XEH_PREP.hpp @@ -1,4 +1,5 @@ PREP(effectLowSpO2); +PREP(effectLossCMR); PREP(effectOpioid); PREP(handleEffects); PREP(initEffects); \ No newline at end of file diff --git a/addons/feedback/XEH_preInit.sqf b/addons/feedback/XEH_preInit.sqf index 9e766b44e..71ec6aaa8 100644 --- a/addons/feedback/XEH_preInit.sqf +++ b/addons/feedback/XEH_preInit.sqf @@ -27,4 +27,13 @@ PREP_RECOMPILE_END; true ] call CBA_Settings_fnc_init; +[ + QGVAR(enableBrainEffect), + "CHECKBOX", + [LLSTRING(SETTING_BrainEffect_display), LLSTRING(SETTING_BrainEffect_DESC)], + CBA_SETTINGS_CAT, + [true], + true +] call CBA_Settings_fnc_init; + ADDON = true; diff --git a/addons/feedback/functions/fnc_effectLossCMR.sqf b/addons/feedback/functions/fnc_effectLossCMR.sqf new file mode 100644 index 000000000..213958ee7 --- /dev/null +++ b/addons/feedback/functions/fnc_effectLossCMR.sqf @@ -0,0 +1,44 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle, MiszczuZPolski + * Triggers the minor CMR effect, radial blur. + * +* Arguments: + * 0: Enable + * 1: Current CMR + * + * Return Value: + * None + * + * Example: + * [true, 0.5] call kat_feedback_fnc_effectMinorLossCMR; + * + * Public: No + */ + +params ["_enable", "_cmr"]; + +if !GVAR(enableBrainEffect) exitWith {}; +if ((!_enable) || {_cmr > 200}) exitWith { + if (GVAR(minorLossCMR) != -1) then { GVAR(minorLossCMR) ppEffectEnable false; }; +}; +if (GVAR(minorLossCMR) != -1) then { GVAR(minorLossCMR) ppEffectEnable true; }; + +// Trigger effect every 2s +private _showNextTick = missionNamespace getVariable [QGVAR(showBrainNextTick), true]; +GVAR(showBrainNextTick) = !_showNextTick; +if (_showNextTick) exitWith {}; + +private _initialAdjust = []; +private _delayedAdjust = []; + +_initialAdjust = [1.5]; +_delayedAdjust = [0]; + +GVAR(minorLossCMR) ppEffectAdjust _initialAdjust; +GVAR(minorLossCMR) ppEffectCommit FX_MINOR_CMR_FADE_IN; +[{ + params ["_adjust"]; + GVAR(minorLossCMR) ppEffectAdjust _adjust; + GVAR(minorLossCMR) ppEffectCommit FX_MINOR_CMR_FADE_OUT; +}, [_delayedAdjust], FX_MINOR_CMR_FADE_IN] call CBA_fnc_waitAndExecute; diff --git a/addons/feedback/functions/fnc_handleEffects.sqf b/addons/feedback/functions/fnc_handleEffects.sqf index a178e9c77..dd5ff7b46 100644 --- a/addons/feedback/functions/fnc_handleEffects.sqf +++ b/addons/feedback/functions/fnc_handleEffects.sqf @@ -19,6 +19,7 @@ params [["_manualUpdate", false]]; if (ACEGVAR(common,OldIsCamera) || {!alive ACE_player}) exitWith { [false] call FUNC(effectOpioid); [false] call FUNC(effectLowSpO2); + [false] call FUNC(effectLossCMR); }; BEGIN_COUNTER(handleEffects); @@ -27,6 +28,7 @@ BEGIN_COUNTER(handleEffects); private _opioid = GET_PP(ACE_player); private _spO2 = GET_SPO2(ACE_player); private _unconscious = IS_UNCONSCIOUS(ACE_player); +private _cmr = GET_CMR(ACE_player); // - Visual effects ----------------------------------------------------------- @@ -37,5 +39,6 @@ private _unconscious = IS_UNCONSCIOUS(ACE_player); linearConversion [GVAR(effectLowSpO2), EGVAR(breathing,SpO2_dieValue), _spO2, 0, 1, true] ] call FUNC(effectLowSpO2); +[!_unconscious, _cmr] call FUNC(effectLossCMR); END_COUNTER(handleEffects); diff --git a/addons/feedback/functions/fnc_initEffects.sqf b/addons/feedback/functions/fnc_initEffects.sqf index ff0cbe28b..e31a2e64f 100644 --- a/addons/feedback/functions/fnc_initEffects.sqf +++ b/addons/feedback/functions/fnc_initEffects.sqf @@ -45,3 +45,10 @@ GVAR(lowSpO2) = [ 21370, [1, 1, 0, [0, 0, 0, 0], [0, 0, 0, 1], [0.33, 0.33, 0.33, 0], [0.55, 0.5, 0, 0, 0, 0, 4]] ] call _fnc_createEffect; + +// - Low CMR (brain addon) --------------------------------------------- +GVAR(minorLossCMR) = [ + "DynamicBlur", + 213702, + [0] +] call _fnc_createEffect; diff --git a/addons/feedback/script_component.hpp b/addons/feedback/script_component.hpp index 334ceebe7..7627cccb7 100644 --- a/addons/feedback/script_component.hpp +++ b/addons/feedback/script_component.hpp @@ -20,4 +20,7 @@ #define FX_OPIOD_FADE_OUT 0.7 #define FX_SPO2_FADE_IN 0.7 -#define FX_SPO2_FADE_OUT 1.6 \ No newline at end of file +#define FX_SPO2_FADE_OUT 1.6 + +#define FX_MINOR_CMR_FADE_IN 0.5 +#define FX_MINOR_CMR_FADE_OUT 1.8 \ No newline at end of file diff --git a/addons/main/script_macros.hpp b/addons/main/script_macros.hpp index 803129dd7..f3d6b067d 100644 --- a/addons/main/script_macros.hpp +++ b/addons/main/script_macros.hpp @@ -298,6 +298,22 @@ #define OXYGEN_PERCENTAGE_ARREST 80 #define OXYGEN_PERCENTAGE_FATAL 75 +// Brain +#define VAR_CMR QEGVAR(brain,CMR) // Cerebral Metabolic Rate (%) +#define VAR_CBF QEGVAR(brain,CBF) // Cerebral Blood Flow +#define VAR_CVR QEGVAR(brain,CVR) // Cerebral Vascular Resistance +#define VAR_ICP QEGVAR(brain,ICP) // Intracranial Pressure +#define VAR_CPR QEGVAR(brain,CPR) // Cerebral Perfusion Rate +#define VAR_RO2 QEGVAR(brain,rO2) // Brain O2 saturation + + +#define GET_CMR(unit) (unit getVariable [VAR_CMR, 100]) +#define GET_CBF(unit) (unit getVariable [VAR_CBF, 800]) +#define GET_CVR(unit) (unit getVariable [VAR_CVR, 0.1]) +#define GET_ICP(unit) (unit getVariable [VAR_ICP, 15]) +#define GET_CPR(unit) (unit getVariable [VAR_CPR, 100]) +#define GET_RO2(unit) (unit getVariable [VAR_RO2, 80]) + // Breathing #define VAR_SURFACE_AREA 400 #define GET_KAT_SURFACE_AREA(unit) (VAR_SURFACE_AREA - (((unit getVariable [QEGVAR(breathing,pneumothorax), 0]) * 75))) @@ -334,6 +350,10 @@ #define VAR_VASOCONSTRICTION QEGVAR(pharma,alphaAction) #define GET_VASOCONSTRICTION(unit) (unit getVariable [VAR_VASOCONSTRICTION, 1]) +// Statemachine +#define VAR_SEIZURE QEGVAR(statemachine,inSeizure) +#define IN_SEIZURE(unit) (unit getVariable [VAR_SEIZURE, false]) + //Surgery #define STRING_BODY_PARTS ["head", "body", "left arm", "right arm", "left leg", "right leg"] diff --git a/addons/misc/functions/fnc_handleRespawn.sqf b/addons/misc/functions/fnc_handleRespawn.sqf index 0204b8673..c1d15cf34 100644 --- a/addons/misc/functions/fnc_handleRespawn.sqf +++ b/addons/misc/functions/fnc_handleRespawn.sqf @@ -31,4 +31,4 @@ _unit setVariable [QGVAR(Tourniquet_LegNecrosis), 0]; _unit setVariable [QGVAR(Tourniquet_PFH), -1]; _unit setVariable [QGVAR(Tourniquet_LegNecrosis_Threshold), 0, true]; -[QGVAR(handleRespawn), _unit] call CBA_fnc_localEvent; +[QGVAR(handleRespawn), _unit] call CBA_fnc_localEvent; \ No newline at end of file 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..58ac85323 --- /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 ACEFUNC(medical_statemachine,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..88794c7cf --- /dev/null +++ b/addons/statemachine/Statemachine.hpp @@ -0,0 +1,134 @@ +// 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 EnterSeizure { + targetState = "Seizure"; + condition = QUOTE((GVAR(enableSeizure))); // wrapped to allow cba to read code //TODO also check brain sim is on + events[] = {QEGVAR(brain,enterSeizure)}; + }; + }; + 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 Seizure { + onStateEntered = QFUNC(enteredStateSeizure); + onState = QFUNC(handleStateSeizure); + class ExitSeizure { + targetState = "Unconscious"; + events[] = {QEGVAR(brain,exitSeizure)}; + }; + class FatalTransitions { + targetState = "CardiacArrest"; + events[] = {QACEGVAR(medical,FatalVitals), QACEGVAR(medical,Bleedout)}; + }; + class FatalInjury { + targetState = "FatalInjury"; + events[] = {QACEGVAR(medical,FatalInjury)}; + }; + }; + 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..13b03226e --- /dev/null +++ b/addons/statemachine/XEH_PREP.hpp @@ -0,0 +1,4 @@ +PREP(enteredStateSeizure); +PREP(handleStateSeizure); +PREP(localityChangedEH); +PREP(setSeizureState); \ No newline at end of file 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..fb0069803 --- /dev/null +++ b/addons/statemachine/XEH_preInit.sqf @@ -0,0 +1,44 @@ +#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" + +//Enable transitions to seizure state +[ + QGVAR(enableSeizure), + "CHECKBOX", + [LSTRING(enableSeizure_DisplayName), LSTRING(enableSeizure_Description)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_Seizures)], + false, + true +] call CBA_fnc_addSetting; + +//Minimum duration of seizure +[ + QGVAR(Seizure_Min_Length), + "SLIDER", + [LSTRING(Seizure_Min_Length_DisplayName),LSTRING(Seizure_Min_Length_Description)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_Seizures)], + [5, 180, 10, 0], + true +] call CBA_Settings_fnc_init; + +//Maximum duration of seizure +[ + QGVAR(Seizure_Max_Length), + "SLIDER", + [LSTRING(Seizure_Max_Length_DisplayName),LSTRING(Seizure_Max_Length_Description)], + [CBA_SETTINGS_CAT, LSTRING(SubCategory_Seizures)], + [5, 180, 120, 0], + true +] call CBA_Settings_fnc_init; + +// 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_enteredStateSeizure.sqf b/addons/statemachine/functions/fnc_enteredStateSeizure.sqf new file mode 100644 index 000000000..f88f107eb --- /dev/null +++ b/addons/statemachine/functions/fnc_enteredStateSeizure.sqf @@ -0,0 +1,35 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Handles a unit entering a seizure (calls for a status update). + * Sets variables to begin seizure logic. + * + * Arguments: + * 0: The Unit + * + * Return Value: + * None + * + * Example: + * [player] call kat_statemachine_fnc_enteredStateSeizure + * + * Public: No + */ + + params ["_unit"]; + if (isNull _unit || {!isNil {_unit getVariable QACEGVAR(medical,causeOfDeath)}}) exitWith { + WARNING_1("enteredStateSeizure: State transition on dead or null unit - %1",_unit); +}; + +private _CMR = _unit getVariable [QEGVAR(brain,CMR),100]; +private _CMRdiff = 100 - _CMR; + +private _time = linearConversion [0,100,_CMRdiff,GVAR(Seizure_Min_Length),GVAR(Seizure_Max_Length),true]; + +_unit setVariable [QGVAR(seizureTimeLeft),_time,true]; +_unit setVariable [QGVAR(seizureTimeLastUpdate),CBA_missionTime]; + +TRACE_3("enteredStateSeizure",_unit,_time,CBA_missionTime); + +// Update the unit status to reflect seizure +[_unit, true] call FUNC(setSeizureState); diff --git a/addons/statemachine/functions/fnc_handleStateSeizure.sqf b/addons/statemachine/functions/fnc_handleStateSeizure.sqf new file mode 100644 index 000000000..5dad39a66 --- /dev/null +++ b/addons/statemachine/functions/fnc_handleStateSeizure.sqf @@ -0,0 +1,16 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Handles updating a unit's status on the seizure. + * + * Arguments: + * 0: The Unit + * + * Return Value: + * None + * + * Example: + * [player] call kat_statemachine_fnc_enteredStateSeizure + * + * Public: No + */ \ 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..7b2611762 --- /dev/null +++ b/addons/statemachine/functions/fnc_localityChangedEH.sqf @@ -0,0 +1,63 @@ +#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 (IN_SEIZURE(_unit)): { + if (_currentState == "Seizure") exitWith {}; + _unit setVariable [VAR_SEIZURE, false]; // force reset vars so setSeizureState can run (enteredSeizureState will also be called) + _unit setVariable [VAR_UNCON, false]; + TRACE_1("manually changing state to Seizure",_currentState); + [_unit, ACEGVAR(medical,STATE_MACHINE), _currentState, "Seizure", {}, "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/functions/fnc_setSeizureState.sqf b/addons/statemachine/functions/fnc_setSeizureState.sqf new file mode 100644 index 000000000..f719dbab6 --- /dev/null +++ b/addons/statemachine/functions/fnc_setSeizureState.sqf @@ -0,0 +1,26 @@ +#include "..\script_component.hpp" +/* + * Author: apo_tle + * Sets a unit in the seizure state. Called from enteredStateSeizure. + * + * Arguments: + * 0: The unit that will be put in an unconscious state + * 1: Set seizure + * + * Return Value: + * None + * + * Example: + * [player, true] call kat_statemachine_fnc_setSeizureState + * + * Public: No + */ + + params ["_unit", "_active"]; + TRACE_2("setUnconsciousState",_unit,_active); + + if (_active isEqualTo IN_SEIZURE(_unit) || (!alive _unit)) exitWith {}; + + _unit setVariable [VAR_SEIZURE, _active, true]; + + [_unit, _active] call EFUNC(medical_engine,setUnconsciousAnim); //temporary, switch to seizure anim later maybe \ 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..0b1c5e4ab --- /dev/null +++ b/addons/statemachine/stringtable.xml @@ -0,0 +1,26 @@ + + + + + Seizures + + + Enable Seizures + + + Disabling this setting will prevent seizures from occuring + + + Minimum Seizure Duration + + + Avoid setting this higher than Max Duration or expect incorrect behaviour + + + Maximum Seizure Duration + + + Avoid setting this lower than Max Duration or expect incorrect behaviour + + + diff --git a/addons/vitals/functions/fnc_hasStableVitals.sqf b/addons/vitals/functions/fnc_hasStableVitals.sqf index 2705db788..f4a1e74a5 100644 --- a/addons/vitals/functions/fnc_hasStableVitals.sqf +++ b/addons/vitals/functions/fnc_hasStableVitals.sqf @@ -38,4 +38,7 @@ if (_heartRate < 40) exitWith { false }; private _o2 = GET_SPO2(_unit); if (_o2 < EGVAR(breathing,Stable_spo2)) exitWith { false }; +private _CMR = _unit getVariable [QEGVAR(brain,CMR),100]; +if (_CMR < EGVAR(brain,stableCMR)) exitWith { false }; + true