From 0b4029b12f28e067cf9553406698683cefbb7d9d Mon Sep 17 00:00:00 2001 From: PabstMirror Date: Sun, 11 Aug 2024 02:19:35 -0500 Subject: [PATCH] Medical AI - AI will remove tourniquets (#10166) * Medical AI - AI will remove tourniquets * Medical AI - Improve tourniquet removal (for #10166) (#10178) * Fixes & tweaks - Have AI remove tourniquets ASAP - Fixed bug where AI would not remove tourniquet, because it didn't have any bandages - Allowed for more multitasking * Allow healer to administer morphine if out of bandages * Remove TODO comment * Allow AI to remove tourniquets from limbs with no open wounds * Update addons/medical_ai/functions/fnc_healingLogic.sqf --------- Co-authored-by: johnb432 <58661205+johnb432@users.noreply.github.com> --- .../medical_ai/functions/fnc_healingLogic.sqf | 125 +++++++++++------- addons/medical_ai/functions/fnc_isInjured.sqf | 1 + 2 files changed, 81 insertions(+), 45 deletions(-) diff --git a/addons/medical_ai/functions/fnc_healingLogic.sqf b/addons/medical_ai/functions/fnc_healingLogic.sqf index f5d13f7410b..84b05327a56 100644 --- a/addons/medical_ai/functions/fnc_healingLogic.sqf +++ b/addons/medical_ai/functions/fnc_healingLogic.sqf @@ -16,9 +16,6 @@ * Public: No */ -// TODO: Add AI tourniquet behaviour -// For now, AI handle player or otherwise scripted tourniquets only - params ["_healer", "_target"]; (_healer getVariable [QGVAR(currentTreatment), [-1]]) params ["_finishTime", "_treatmentTarget", "_treatmentEvent", "_treatmentArgs", "_treatmentItem"]; @@ -87,47 +84,68 @@ if (_finishTime > 0) exitWith { }; }; -// Find a suitable limb (no tourniquets) for injecting and giving IVs -private _fnc_findNoTourniquet = { - private _bodyPart = ""; +// Bandage a limb up, then remove the tourniquet on it +private _fnc_removeTourniquet = { + params [["_removeAllTourniquets", false]]; - // If all limbs have tourniquets, find the least damaged limb and try to bandage it - if ((_tourniquets select [2]) find 0 == -1) then { - // If no bandages available, wait - if !(([_healer, "@bandage"] call FUNC(itemCheck)) # 0) exitWith { - _treatmentEvent = "#waitForNonTourniquetedLimb"; // TODO: Medic can move onto another patient/should be flagged as out of supplies + // Ignore head & torso if not removing all tourniquets (= administering drugs/IVs) + private _offset = [2, 0] select _removeAllTourniquets; + + // Bandage the least bleeding body part + private _bodyPartBleeding = []; + _bodyPartBleeding resize [[4, 6] select _removeAllTourniquets, -1]; + + { + // Ignore head and torso, if only looking for place to administer drugs/IVs + private _partIndex = (ALL_BODY_PARTS find _x) - _offset; + + if (_partIndex >= 0 && {_tourniquets select _partIndex != 0}) then { + { + _x params ["", "_amountOf", "_bleeding"]; + + // max 0, to set the baseline to 0, as body parts with no wounds are marked with -1 + _bodyPartBleeding set [_partIndex, ((_bodyPartBleeding select _partIndex) max 0) + (_amountOf * _bleeding)]; + } forEach _y; }; + } forEach GET_OPEN_WOUNDS(_target); - // Bandage the least bleeding body part - private _bodyPartBleeding = [0, 0, 0, 0]; + // If there are no open wounds, check if there are tourniquets on limbs with no open wounds (stitched or fully healed), + // as we know there have to be tourniquets at this point + if (_bodyPartBleeding findIf {_x != -1} == -1) then { + _bodyPartBleeding set [_tourniquets findIf {_x != 0}, 0]; + }; - { - // Ignore head and torso - private _partIndex = (ALL_BODY_PARTS find _x) - 2; + // Ignore body parts that don't have open wounds (-1) + private _minBodyPartBleeding = selectMin (_bodyPartBleeding select {_x != -1}); + private _selection = ALL_BODY_PARTS select ((_bodyPartBleeding find _minBodyPartBleeding) + _offset); - if (_partIndex >= 0) then { - { - _x params ["", "_amountOf", "_bleeding"]; - _bodyPartBleeding set [_partIndex, (_bodyPartBleeding select _partIndex) + (_amountOf * _bleeding)]; - } forEach _y; - }; - } forEach GET_OPEN_WOUNDS(_target); + // If not bleeding anymore, remove the tourniquet + if (_minBodyPartBleeding == 0) exitWith { + _treatmentEvent = QGVAR(tourniquetRemove); + _treatmentTime = 7; + _treatmentArgs = [_healer, _target, _selection]; + }; + + // If no bandages available, wait + // If check is done at the start of the scope, it will miss the edge case where the unit ran out of bandages just as they finished bandaging tourniqueted body part + if !(([_healer, "@bandage"] call FUNC(itemCheck)) # 0) exitWith { + _treatmentEvent = "#waitForBandages"; // TODO: Medic can move onto another patient/should be flagged as out of supplies + }; - private _minBodyPartBleeding = selectMin _bodyPartBleeding; - private _selection = ALL_BODY_PARTS select ((_bodyPartBleeding find _minBodyPartBleeding) + 2); + // Otherwise keep bandaging + _treatmentEvent = QEGVAR(medical_treatment,bandageLocal); + _treatmentTime = 5; + _treatmentArgs = [_target, _selection, "FieldDressing"]; + _treatmentItem = "@bandage"; +}; - // If not bleeding anymore, remove the tourniquet - if (_minBodyPartBleeding == 0) exitWith { - _treatmentEvent = QGVAR(tourniquetRemove); - _treatmentTime = 7; - _treatmentArgs = [_healer, _target, _selection]; - }; +// Find a suitable limb (no tourniquets) for adminstering drugs/IVs +private _fnc_findNoTourniquet = { + private _bodyPart = ""; - // Otherwise keep bandaging - _treatmentEvent = QEGVAR(medical_treatment,bandageLocal); - _treatmentTime = 5; - _treatmentArgs = [_target, _selection, "FieldDressing"]; - _treatmentItem = "@bandage"; + // If all limbs have tourniquets, find the least damaged limb and try to bandage it + if ((_tourniquets select [2]) find 0 == -1) then { + call _fnc_removeTourniquet; } else { // Select a random non-tourniqueted limb otherwise private _bodyParts = ["leftarm", "rightarm", "leftleg", "rightleg"]; @@ -270,7 +288,7 @@ if (true) then { // Wait until the injured has enough blood before administering drugs // (_needsIV && !_canGiveIV), but _canGiveIV is false here, otherwise IV would be given - if (_needsIV || {_treatmentEvent == "#waitForIV"}) exitWith { + if (_needsIV || {_doCPR && {_treatmentEvent == "#waitForIV"}}) exitWith { // If injured is in cardiac arrest and the healer is doing nothing else, start CPR if (_doCPR) exitWith { _treatmentEvent = QEGVAR(medical_treatment,cprLocal); // TODO: Medic remains in this loop until injured is given enough IVs or dies @@ -284,23 +302,30 @@ if (true) then { }; }; - if ((count (_target getVariable [VAR_MEDICATIONS, []])) >= 6) exitWith { + // These checks are not exitWith, so that the medic can try to bandage up tourniqueted body parts + if ((count (_target getVariable [VAR_MEDICATIONS, []])) >= 6) then { _treatmentEvent = "#tooManyMeds"; // TODO: Medic can move onto another patient }; private _heartRate = GET_HEART_RATE(_target); + private _canGiveEpinephrine = !(_treatmentEvent in ["#tooManyMeds", "#waitForIV"]) && + {IS_UNCONSCIOUS(_target) || {_heartRate <= 50}} && + {([_healer, "epinephrine"] call FUNC(itemCheck)) # 0}; - if ( - (IS_UNCONSCIOUS(_target) || {_heartRate <= 50}) && - {([_healer, "epinephrine"] call FUNC(itemCheck)) # 0} - ) exitWith { - if (CBA_missionTime < (_target getVariable [QGVAR(nextEpinephrine), -1])) exitWith { + // This allows for some multitasking + if (_canGiveEpinephrine) then { + if (CBA_missionTime < (_target getVariable [QGVAR(nextEpinephrine), -1])) then { _treatmentEvent = "#waitForEpinephrineToTakeEffect"; + _canGiveEpinephrine = false; }; - if (_heartRate > 180) exitWith { + + if (_heartRate > 180) then { _treatmentEvent = "#waitForSlowerHeart"; // TODO: Medic can move onto another patient, after X amount of time of high HR + _canGiveEpinephrine = false; }; + }; + if (_canGiveEpinephrine) exitWith { // If all limbs are tourniqueted, bandage the one with the least amount of wounds, so that the tourniquet can be removed _bodyPart = call _fnc_findNoTourniquet; @@ -313,8 +338,18 @@ if (true) then { _treatmentItem = "epinephrine"; }; + // Remove all remaining tourniquets by bandaging all body parts + if (_tourniquets isNotEqualTo DEFAULT_TOURNIQUET_VALUES) then { + true call _fnc_removeTourniquet; + }; + + // If the healer can bandage or remove tourniquets, do that + if (_treatmentEvent in [QEGVAR(medical_treatment,bandageLocal), QGVAR(tourniquetRemove)]) exitWith {}; + + // Otherwise, if the healer is either done or out of bandages, continue if ( - ((GET_PAIN_PERCEIVED(_target) > 0.25) || {_heartRate >= 180}) && + !(_treatmentEvent in ["#tooManyMeds", "#waitForIV"]) && + {(GET_PAIN_PERCEIVED(_target) > 0.25) || {_heartRate >= 180}} && {([_healer, "morphine"] call FUNC(itemCheck)) # 0} ) exitWith { if (CBA_missionTime < (_target getVariable [QGVAR(nextMorphine), -1])) exitWith { diff --git a/addons/medical_ai/functions/fnc_isInjured.sqf b/addons/medical_ai/functions/fnc_isInjured.sqf index 51ae37caaee..2a4b6895143 100644 --- a/addons/medical_ai/functions/fnc_isInjured.sqf +++ b/addons/medical_ai/functions/fnc_isInjured.sqf @@ -24,3 +24,4 @@ if !(alive _this) exitWith {false}; private _fractures = GET_FRACTURES(_this); ((_fractures select 4) == 1) || {(_fractures select 5) == 1} } +|| { GET_TOURNIQUETS(_this) isNotEqualTo DEFAULT_TOURNIQUET_VALUES }