Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Medical Vitals - Extract update logic to separate functions #10566

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
1 change: 0 additions & 1 deletion addons/medical_status/XEH_PREP.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ PREP(adjustPainLevel);
PREP(getAllMedicationCount);
PREP(getBloodLoss);
PREP(getBloodPressure);
PREP(getBloodVolumeChange);
PREP(getCardiacOutput);
PREP(getMedicationCount);
PREP(handleKilled);
Expand Down
53 changes: 0 additions & 53 deletions addons/medical_status/functions/fnc_getBloodVolumeChange.sqf

This file was deleted.

4 changes: 4 additions & 0 deletions addons/medical_vitals/XEH_PREP.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
PREP(consumeIVs);
PREP(consumeMedications);
PREP(handleUnitVitals);
PREP(scanConfig);
PREP(updateBloodPressure);
PREP(updateHeartRate);
PREP(updateOxygen);
PREP(updatePainSuppress);
PREP(updatePeripheralResistance);
PREP(updateState);
1 change: 1 addition & 0 deletions addons/medical_vitals/XEH_preInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ PREP_RECOMPILE_END;
#include "initSettings.inc.sqf"

GVAR(oxygenSupplyConditionCache) = uiNamespace getVariable QGVAR(oxygenSupplyConditionCache);
GVAR(deferredEvents) = createHashMap;

ADDON = true;
60 changes: 60 additions & 0 deletions addons/medical_vitals/functions/fnc_consumeIVs.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "..\script_component.hpp"
/*
* Author: Glowbal, LinkIsGrim
* Calculates fluid intake and consumes unit's IVs.
*
* Arguments:
* 0: The Unit <OBJECT>
* 1: Time since last update <NUMBER>
* 2: Global Sync Values (IVs) <BOOL>
*
* Return Value:
* Blood volume change (liters per second) <NUMBER>
*
* Example:
* [player, 1, true] call ace_medical_vitals_fnc_consumeIVs
*
* Public: No
*/

params ["_unit", "_deltaT", "_syncValues"];

private _ivBags = _unit getVariable [QEGVAR(medical,ivBags), []];

if (_ivBags isEqualTo []) exitWith {0};

private _tourniquets = GET_TOURNIQUETS(_unit);
private _bloodVolumeChange = 0;
private _consumedIVs = [];

{
_x params ["_bagVolumeRemaining", "_type", "_bodyPartIndex", "_classname", "_rateCoef"];

if (_tourniquets select _bodyPartIndex > 0) then {
continue
};

private _bagChange = (_deltaT * EGVAR(medical,ivFlowRate) * IV_CHANGE_PER_SECOND * _rateCoef) min _bagVolumeRemaining; // absolute value of the change in miliLiters
_bagVolumeRemaining = _bagVolumeRemaining - _bagChange;
_consumedIVs pushBack [_type, _classname, _bagChange];

if (_type in ["Blood", "Plasma", "Saline"]) then {
_bloodVolumeChange = _bloodVolumeChange + (_bagChange / 1000);
};

if (_bagVolumeRemaining >= 0.01) then {
_x set [0, _bagVolumeRemaining];
} else {
_ivBags deleteAt _forEachIndex;
};
} forEachReversed _ivBags;

(GVAR(deferredEvents) getOrDefault [_unit, [], true]) pushBack ([QEGVAR(medical,consumedIVs), [_unit, _consumedIVs]]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key can't be an object, you need to use hashValue first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally not a huge fan of having a global hashmap for this kind of thing:

  1. If the unit is deleted, you need to handle the removal from the hashmap.
  2. If the unit switches locality, this can't handle it. Idk how important that is though.

Why not use an object variable instead? Handles 1) and no longer requires hashValue _unit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hashmap is slightly faster (might not be with hashvalue), that was the only incentive. If we don't have to worry about keeping 3rd-party code out of the counter, I'd rather just raise the events outright.
locality change and deletion won't (shouldn't) happen mid-execution, deferred events are still executed on the same frame, so it'll be either before or after handleUnitVitals is executed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather just raise the events outright

Do that then, makes more sense.

LinkIsGrim marked this conversation as resolved.
Show resolved Hide resolved

if (_ivBags isEqualTo []) then {
_unit setVariable [QEGVAR(medical,ivBags), nil, true];
} else {
_unit setVariable [QEGVAR(medical,ivBags), _ivBags, _syncValues];
};

_bloodVolumeChange // return
61 changes: 61 additions & 0 deletions addons/medical_vitals/functions/fnc_consumeMedications.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "..\script_component.hpp"
/*
* Author: Glowbal, LinkIsGrim
* Consumes unit's medications and update relevant vitals
*
* Arguments:
* 0: The Unit <OBJECT>
* 1: Time since last update <NUMBER>
* 2: Global Sync Values (medications) <BOOL>
*
* Return Value:
* Values should be synced <BOOL>
*
* Example:
* [player, 1, true] call ace_medical_vitals_fnc_consumeMedications
*
* Public: No
*/

params ["_unit", "_deltaT", "_syncValues"];

private _medications = _unit getVariable [VAR_MEDICATIONS, []];

if (_medications isEqualTo []) exitWith {
[0, 0, 0, false]
};

private _hrTargetAdjustment = 0;
private _painSupressAdjustment = 0;
private _peripheralResistanceAdjustment = 0;
private _consumedMedications = [];

{
_x params ["_medication", "_timeAdded", "_timeTillMaxEffect", "_maxTimeInSystem", "_hrAdjust", "_painAdjust", "_flowAdjust"];

private _timeInSystem = CBA_missionTime - _timeAdded;
if (_timeInSystem >= _maxTimeInSystem) then {
_syncValues = true;
_adjustments deleteAt _forEachIndex;
LinkIsGrim marked this conversation as resolved.
Show resolved Hide resolved
} else {
private _effectRatio = (((_timeInSystem / _timeTillMaxEffect) ^ 2) min 1) * (_maxTimeInSystem - _timeInSystem) / _maxTimeInSystem;

_consumedMedications pushBack [_medication, _effectRatio];

if (_hrAdjust != 0) then { _hrTargetAdjustment = _hrTargetAdjustment + _hrAdjust * _effectRatio; };
if (_painAdjust != 0) then { _painSupressAdjustment = _painSupressAdjustment + _painAdjust * _effectRatio; };
if (_flowAdjust != 0) then { _peripheralResistanceAdjustment = _peripheralResistanceAdjustment + _flowAdjust * _effectRatio; };
};
} forEachReversed _medications;

(GVAR(deferredEvents) getOrDefault [_unit, [], true]) pushBack ([QEGVAR(medical,consumeMedications), [_unit, _consumedMedications]]);
LinkIsGrim marked this conversation as resolved.
Show resolved Hide resolved

if (_syncValues) then {
_unit setVariable [VAR_MEDICATIONS, _medications, true]
};

[_unit, _hrTargetAdjustment, _deltaT, _syncValues] call FUNC(updateHeartRate);
[_unit, _painSupressAdjustment, _deltaT, _syncValues] call FUNC(updatePainSuppress);
[_unit, _peripheralResistanceAdjustment, _deltaT, _syncValues] call FUNC(updatePeripheralResistance);

_syncValues // return
129 changes: 18 additions & 111 deletions addons/medical_vitals/functions/fnc_handleUnitVitals.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,15 @@ if (_syncValues) then {
// Update SPO2 intake and usage since last update
[_unit, _deltaT, _syncValues] call FUNC(updateOxygen);

private _bloodVolume = GET_BLOOD_VOLUME(_unit) + ([_unit, _deltaT, _syncValues] call EFUNC(medical_status,getBloodVolumeChange));
private _bleeding = -_deltaT * GET_BLOOD_LOSS(_unit);
private _ivChange = [_unit, _deltaT, _syncValues] call FUNC(consumeIVs);

private _bloodVolume = GET_BLOOD_VOLUME(_unit) + _bleeding + _ivChange;
_bloodVolume = 0 max _bloodVolume min DEFAULT_BLOOD_VOLUME;

// @todo: replace this and the rest of the setVariable with EFUNC(common,setApproximateVariablePublic)
_unit setVariable [VAR_BLOOD_VOL, _bloodVolume, _syncValues];

// Set variables for synchronizing information across the net
private _hemorrhage = switch (true) do {
case (_bloodVolume < BLOOD_VOLUME_CLASS_4_HEMORRHAGE): { 4 };
case (_bloodVolume < BLOOD_VOLUME_CLASS_3_HEMORRHAGE): { 3 };
case (_bloodVolume < BLOOD_VOLUME_CLASS_2_HEMORRHAGE): { 2 };
case (_bloodVolume < BLOOD_VOLUME_CLASS_1_HEMORRHAGE): { 1 };
default {0};
};

if (_hemorrhage != GET_HEMORRHAGE(_unit)) then {
_unit setVariable [VAR_HEMORRHAGE, _hemorrhage, true];
};

private _woundBloodLoss = GET_WOUND_BLEEDING(_unit);

private _inPain = GET_PAIN_PERCEIVED(_unit) > 0;
if (_inPain isNotEqualTo IS_IN_PAIN(_unit)) then {
_unit setVariable [VAR_IN_PAIN, _inPain, true];
};

// Handle pain due tourniquets, that have been applied more than 120 s ago
private _tourniquetPain = 0;
private _tourniquets = GET_TOURNIQUETS(_unit);
Expand All @@ -72,103 +55,27 @@ if (_tourniquetPain > 0) then {
[_unit, _tourniquetPain] call EFUNC(medical_status,adjustPainLevel);
};

// Get Medication Adjustments:
private _hrTargetAdjustment = 0;
private _painSupressAdjustment = 0;
private _peripheralResistanceAdjustment = 0;
private _adjustments = _unit getVariable [VAR_MEDICATIONS,[]];

if (_adjustments isNotEqualTo []) then {
private _deleted = false;
{
_x params ["_medication", "_timeAdded", "_timeTillMaxEffect", "_maxTimeInSystem", "_hrAdjust", "_painAdjust", "_flowAdjust"];
private _timeInSystem = CBA_missionTime - _timeAdded;
if (_timeInSystem >= _maxTimeInSystem) then {
_deleted = true;
_adjustments deleteAt _forEachIndex;
} else {
private _effectRatio = (((_timeInSystem / _timeTillMaxEffect) ^ 2) min 1) * (_maxTimeInSystem - _timeInSystem) / _maxTimeInSystem;
if (_hrAdjust != 0) then { _hrTargetAdjustment = _hrTargetAdjustment + _hrAdjust * _effectRatio; };
if (_painAdjust != 0) then { _painSupressAdjustment = _painSupressAdjustment + _painAdjust * _effectRatio; };
if (_flowAdjust != 0) then { _peripheralResistanceAdjustment = _peripheralResistanceAdjustment + _flowAdjust * _effectRatio; };
};
} forEachReversed _adjustments;

if (_deleted) then {
_unit setVariable [VAR_MEDICATIONS, _adjustments, true];
_syncValues = true;
};
};
// Consume medications
_syncValues = [_unit, _deltaT, _syncValues] call FUNC(consumeMedications);

private _heartRate = [_unit, _hrTargetAdjustment, _deltaT, _syncValues] call FUNC(updateHeartRate);
[_unit, _painSupressAdjustment, _deltaT, _syncValues] call FUNC(updatePainSuppress);
[_unit, _peripheralResistanceAdjustment, _deltaT, _syncValues] call FUNC(updatePeripheralResistance);
// Update blood presure
[_unit, _syncValues] call FUNC(updateBloodPressure);

private _bloodPressure = GET_BLOOD_PRESSURE(_unit);
_unit setVariable [VAR_BLOOD_PRESS, _bloodPressure, _syncValues];
// Consume IVs
[_unit, _deltaT] call FUNC(consumeIVs);

_bloodPressure params ["_bloodPressureL", "_bloodPressureH"];
// Update statemachine and status variables
_unit call FUNC(updateState);

// Statements are ordered by most lethal first.
switch (true) do {
case (_bloodVolume < BLOOD_VOLUME_FATAL): {
TRACE_3("BloodVolume Fatal",_unit,BLOOD_VOLUME_FATAL,_bloodVolume);
[QEGVAR(medical,Bleedout), _unit] call CBA_fnc_localEvent;
};
case (IN_CRDC_ARRST(_unit)): {}; // if in cardiac arrest just break now to avoid throwing unneeded events
case (_hemorrhage == 4): {
TRACE_3("Class IV Hemorrhage",_unit,_hemorrhage,_bloodVolume);
[QEGVAR(medical,FatalVitals), _unit] call CBA_fnc_localEvent;
};
case (_heartRate < 20 || {_heartRate > 220}): {
TRACE_2("heartRate Fatal",_unit,_heartRate);
[QEGVAR(medical,FatalVitals), _unit] call CBA_fnc_localEvent;
};
case (_bloodPressureH < 50 && {_bloodPressureL < 40} && {_heartRate < 40}): {
TRACE_4("bloodPressure (H & L) + heartRate Fatal",_unit,_bloodPressureH,_bloodPressureL,_heartRate);
[QEGVAR(medical,FatalVitals), _unit] call CBA_fnc_localEvent;
};
case (_bloodPressureL >= 190): {
TRACE_2("bloodPressure L above limits",_unit,_bloodPressureL);
[QEGVAR(medical,FatalVitals), _unit] call CBA_fnc_localEvent;
};
case (_heartRate < 30): { // With a heart rate below 30 but bigger than 20 there is a chance to enter the cardiac arrest state
private _nextCheck = _unit getVariable [QGVAR(nextCheckCriticalHeartRate), CBA_missionTime];
private _enterCardiacArrest = false;
if (CBA_missionTime >= _nextCheck) then {
_enterCardiacArrest = random 1 < (0.4 + 0.6*(30 - _heartRate)/10); // Variable chance of getting into cardiac arrest.
_unit setVariable [QGVAR(nextCheckCriticalHeartRate), CBA_missionTime + 5];
};
if (_enterCardiacArrest) then {
TRACE_2("Heart rate critical. Cardiac arrest",_unit,_heartRate);
[QEGVAR(medical,FatalVitals), _unit] call CBA_fnc_localEvent;
} else {
TRACE_2("Heart rate critical. Critical vitals",_unit,_heartRate);
[QEGVAR(medical,CriticalVitals), _unit] call CBA_fnc_localEvent;
};
};
case (_woundBloodLoss > BLOOD_LOSS_KNOCK_OUT_THRESHOLD): {
[QEGVAR(medical,CriticalVitals), _unit] call CBA_fnc_localEvent;
};
case (_woundBloodLoss > 0): {
[QEGVAR(medical,LoweredVitals), _unit] call CBA_fnc_localEvent;
};
case (_inPain): {
[QEGVAR(medical,LoweredVitals), _unit] call CBA_fnc_localEvent;
};
};
END_COUNTER(Vitals);

#ifdef DEBUG_MODE_FULL
private _cardiacOutput = [_unit] call EFUNC(medical_status,getCardiacOutput);
if (!isPlayer _unit) then {
private _painLevel = _unit getVariable [VAR_PAIN, 0];
hintSilent format["blood volume: %1, blood loss: [%2, %3]\nhr: %4, bp: %5, pain: %6", round(_bloodVolume * 100) / 100, round(_woundBloodLoss * 1000) / 1000, round((_woundBloodLoss / (0.001 max _cardiacOutput)) * 100) / 100, round(_heartRate), _bloodPressure, round(_painLevel * 100) / 100];
};
#endif
//placed outside the counter as 3rd-party code may be called from these events
{
_x call CBA_fnc_localEvent;
} forEach (GVAR(deferredEvents) getOrDefault [_unit, []]);
LinkIsGrim marked this conversation as resolved.
Show resolved Hide resolved

END_COUNTER(Vitals);
GVAR(deferredEvents) deleteAt _unit;
LinkIsGrim marked this conversation as resolved.
Show resolved Hide resolved

//placed outside the counter as 3rd-party code may be called from this event
[QEGVAR(medical,handleUnitVitals), [_unit, _deltaT]] call CBA_fnc_localEvent;

true
22 changes: 22 additions & 0 deletions addons/medical_vitals/functions/fnc_updateBloodPressure.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "..\script_component.hpp"
/*
* Author: LinkIsGrim
* Update unit blood pressure
*
* Arguments:
* 0: The Unit <OBJECT>
* 1: Sync value? <BOOL>
*
* Return Value:
* None
*
* Example:
* [player, false] call ace_medical_vitals_fnc_updateBloodPressure
*
* Public: No
*/

params ["_unit", "_syncValues"];

private _bloodPressure = GET_BLOOD_PRESSURE(_unit);
_unit setVariable [VAR_BLOOD_PRESS, _bloodPressure, _syncValues];
Loading
Loading