diff --git a/addons/medical_ai/XEH_preStart.sqf b/addons/medical_ai/XEH_preStart.sqf index b4d795cbbf6..ccb92538b74 100644 --- a/addons/medical_ai/XEH_preStart.sqf +++ b/addons/medical_ai/XEH_preStart.sqf @@ -20,6 +20,7 @@ private _itemHash = createHashMap; } forEach [ ["@bandage", ["FieldDressing", "PackingBandage", "ElasticBandage", "QuikClot"]], ["@iv", ["SalineIV", "SalineIV_500", "SalineIV_250", "BloodIV", "BloodIV_500", "BloodIV_250", "PlasmaIV", "PlasmaIV_500", "PlasmaIV_250"]], + ["tourniquet", ["ApplyTourniquet"]], ["splint", ["splint"]], ["morphine", ["morphine"]], ["epinephrine", ["epinephrine"]] diff --git a/addons/medical_ai/functions/fnc_healUnit.sqf b/addons/medical_ai/functions/fnc_healUnit.sqf index 3635088820e..319d5533b16 100644 --- a/addons/medical_ai/functions/fnc_healUnit.sqf +++ b/addons/medical_ai/functions/fnc_healUnit.sqf @@ -47,6 +47,10 @@ if (_this distance _target > 2.5) exitWith { _this setVariable [QGVAR(currentTreatment), nil]; if (CBA_missionTime >= (_this getVariable [QGVAR(nextMoveOrder), CBA_missionTime])) then { _this setVariable [QGVAR(nextMoveOrder), CBA_missionTime + 10]; + + // Medic, when doing a lot of treatment, moves away from injured over time (because of animations) + // Need to allow the medic to move back to the injured again + _this forceSpeed -1; _this doMove getPosATL _target; #ifdef DEBUG_MODE_FULL systemChat format ["%1 moving to %2", _this, _target]; diff --git a/addons/medical_ai/functions/fnc_healingLogic.sqf b/addons/medical_ai/functions/fnc_healingLogic.sqf index 213d763b973..c9fe046ef8c 100644 --- a/addons/medical_ai/functions/fnc_healingLogic.sqf +++ b/addons/medical_ai/functions/fnc_healingLogic.sqf @@ -27,13 +27,28 @@ if (_finishTime > 0) exitWith { if (CBA_missionTime >= _finishTime) then { TRACE_5("treatment finished",_finishTime,_treatmentTarget,_treatmentEvent,_treatmentArgs,_treatmentItem); _healer setVariable [QGVAR(currentTreatment), nil]; + + private _usedItem = ""; + if ((GVAR(requireItems) > 0) && {_treatmentItem != ""}) then { ([_healer, _treatmentItem] call FUNC(itemCheck)) params ["_itemOk", "_itemClassname", "_treatmentClass"]; - if (!_itemOk) exitWith { _treatmentEvent = "#fail"; }; // no item after delay - _healer removeItem _itemClassname; - if (_treatmentClass != "") then { _treatmentArgs set [2, _treatmentClass]; }; + // No item after treatment done + if (!_itemOk) exitWith { + _treatmentEvent = "#fail"; + }; + + if (_treatmentClass != "") then { + _healer removeItem _itemClassname; + _usedItem = _itemClassname; + _treatmentArgs set [2, _treatmentClass]; + }; }; if ((_treatmentTarget == _target) && {(_treatmentEvent select [0, 1]) != "#"}) then { + // There is no event for tourniquet removal, so handle calling function directly + if (_treatmentEvent == QGVAR(tourniquetRemove)) exitWith { + _treatmentArgs call EFUNC(medical_treatment,tourniquetRemove); + }; + [_treatmentEvent, _treatmentArgs, _target] call CBA_fnc_targetEvent; // Splints are already logged on their own @@ -42,14 +57,25 @@ if (_finishTime > 0) exitWith { [_target, "activity", ELSTRING(medical_treatment,Activity_bandagedPatient), [[_healer, false, true] call EFUNC(common,getName)]] call EFUNC(medical_treatment,addToLog); }; case QEGVAR(medical_treatment,ivBagLocal): { - [_target, _treatmentArgs select 2] call EFUNC(medical_treatment,addToTriageCard); + if (_usedItem == "") then { + _usedItem = "ACE_salineIV"; + }; + + [_target, _usedItem] call EFUNC(medical_treatment,addToTriageCard); [_target, "activity", ELSTRING(medical_treatment,Activity_gaveIV), [[_healer, false, true] call EFUNC(common,getName)]] call EFUNC(medical_treatment,addToLog); }; case QEGVAR(medical_treatment,medicationLocal): { - private _usedItem = ["ACE_epinephrine", "ACE_morphine"] select (_treatmentArgs select 2 == "Morphine"); + if (_usedItem == "") then { + _usedItem = ["ACE_epinephrine", "ACE_morphine"] select (_treatmentArgs select 2 == "Morphine"); + }; + [_target, _usedItem] call EFUNC(medical_treatment,addToTriageCard); [_target, "activity", ELSTRING(medical_treatment,Activity_usedItem), [[_healer, false, true] call EFUNC(common,getName), getText (configFile >> "CfgWeapons" >> _usedItem >> "displayName")]] call EFUNC(medical_treatment,addToLog); }; + case QEGVAR(medical_treatment,tourniquetLocal): { + [_target, "ACE_tourniquet"] call EFUNC(medical_treatment,addToTriageCard); + [_target, "activity", ELSTRING(medical_treatment,Activity_appliedTourniquet), [[_healer, false, true] call EFUNC(common,getName)]] call EFUNC(medical_treatment,addToLog); + }; }; #ifdef DEBUG_MODE_FULL @@ -63,29 +89,61 @@ if (_finishTime > 0) exitWith { // Find a suitable limb (no tourniquets) for injecting and giving IVs private _fnc_findNoTourniquet = { private _bodyPart = ""; - private _bodyParts = ["leftarm", "rightarm", "leftleg", "rightleg"]; - private _bodyPartsSaved = +_bodyParts; - while {_bodyParts isNotEqualTo []} do { - _bodyPart = selectRandom _bodyParts; + // 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 + }; - // If no tourniquet on, use that body part - if (_tourniquets select (ALL_BODY_PARTS find _bodyPart) == 0) exitWith {}; + // Bandage the least bleeding body part + private _bodyPartBleeding = [0, 0, 0, 0]; - _bodyParts deleteAt (_bodyParts find _bodyPart); - }; + { + // Ignore head and torso + private _partIndex = (ALL_BODY_PARTS find _x) - 2; - // If all limbs have tourniquets, use random limb - if (_bodyPart == "") then { - _bodyPart = selectRandom _bodyPartsSaved; + if (_partIndex >= 0) then { + { + _x params ["", "_amountOf", "_bleeding"]; + _bodyPartBleeding set [_partIndex, (_bodyPartBleeding select _partIndex) + (_amountOf * _bleeding)]; + } forEach _y; + }; + } forEach GET_OPEN_WOUNDS(_target); + + private _minBodyPartBleeding = selectMin _bodyPartBleeding; + private _selection = ALL_BODY_PARTS select ((_bodyPartBleeding find _minBodyPartBleeding) + 2); + + // If not bleeding anymore, remove the tourniquet + if (_minBodyPartBleeding == 0) exitWith { + _treatmentEvent = QGVAR(tourniquetRemove); + _treatmentTime = 7; + _treatmentArgs = [_healer, _target, _selection]; + }; + + // Otherwise keep bandaging + _treatmentEvent = QEGVAR(medical_treatment,bandageLocal); + _treatmentTime = 5; + _treatmentArgs = [_target, _selection, "FieldDressing"]; + _treatmentItem = "@bandage"; + } else { + // Select a random non-tourniqueted limb otherwise + private _bodyParts = ["leftarm", "rightarm", "leftleg", "rightleg"]; + + while {_bodyParts isNotEqualTo []} do { + _bodyPart = selectRandom _bodyParts; + + // If no tourniquet on, use that body part + if (_tourniquets select (ALL_BODY_PARTS find _bodyPart) == 0) exitWith {}; + + _bodyParts deleteAt (_bodyParts find _bodyPart); + }; }; - _bodyPart + _bodyPart // return }; -private _isMedic = [_healer] call EFUNC(medical_treatment,isMedic); -private _heartRate = GET_HEART_RATE(_target); -private _fractures = GET_FRACTURES(_target); private _tourniquets = GET_TOURNIQUETS(_target); private _treatmentEvent = "#none"; @@ -94,46 +152,77 @@ private _treatmentTime = 6; private _treatmentItem = ""; if (true) then { - if ( - (GET_WOUND_BLEEDING(_target) > 0) && - {([_healer, "@bandage"] call FUNC(itemCheck)) # 0} - ) exitWith { - // Select first bleeding wound and bandage it - private _selection = "?"; + if (IS_BLEEDING(_target)) exitWith { + private _hasBandage = ([_healer, "@bandage"] call FUNC(itemCheck)) # 0; + private _hasTourniquet = ([_healer, "tourniquet"] call FUNC(itemCheck)) # 0; + + // Patient is not worth treating if bloodloss can't be stopped + if !(_hasBandage || _hasTourniquet) exitWith { + _treatmentEvent = "#cantStabilise"; // TODO: Medic should be flagged as out of supplies + }; + + // Bandage the heaviest bleeding body part + private _bodyPartBleeding = [0, 0, 0, 0, 0, 0]; + { + private _partIndex = ALL_BODY_PARTS find _x; + // Ignore tourniqueted limbs - if (_tourniquets select (ALL_BODY_PARTS find _x) == 0 && { - _y findIf { - _x params ["", "_amount", "_percentage"]; - (_amount * _percentage) > 0 - } != -1} - ) exitWith { _selection = _x; }; + if (_tourniquets select _partIndex == 0) then { + { + _x params ["", "_amountOf", "_bleeding"]; + _bodyPartBleeding set [_partIndex, (_bodyPartBleeding select _partIndex) + (_amountOf * _bleeding)]; + } forEach _y; + }; } forEach GET_OPEN_WOUNDS(_target); + + private _maxBodyPartBleeding = selectMax _bodyPartBleeding; + private _bodyPartIndex = _bodyPartBleeding find _maxBodyPartBleeding; + private _selection = ALL_BODY_PARTS select _bodyPartIndex; + + // Apply tourniquet if moderate bleeding or no bandage is available, and if not head and torso + if (_hasTourniquet && {_bodyPartIndex > HITPOINT_INDEX_BODY} && {!_hasBandage || {_maxBodyPartBleeding > 0.3}}) exitWith { + _treatmentEvent = QEGVAR(medical_treatment,tourniquetLocal); + _treatmentTime = 7; + _treatmentArgs = [_target, _selection]; + _treatmentItem = "tourniquet"; + }; + _treatmentEvent = QEGVAR(medical_treatment,bandageLocal); _treatmentTime = 5; _treatmentArgs = [_target, _selection, "FieldDressing"]; _treatmentItem = "@bandage"; }; - private _hasIV = ([_healer, "@iv"] call FUNC(itemCheck)) # 0; private _bloodVolume = GET_BLOOD_VOLUME(_target); + private _needsIV = _bloodVolume < MINIMUM_BLOOD_FOR_STABLE_VITALS; + private _canGiveIV = _needsIV && + {_healer call EFUNC(medical_treatment,isMedic)} && + {([_healer, "@iv"] call FUNC(itemCheck)) # 0}; // Has IVs + private _doCPR = IN_CRDC_ARRST(_target) && {EGVAR(medical_treatment,cprSuccessChanceMin) > 0}; // If in cardiac arrest, first add some blood to injured if necessary, then do CPR (doing CPR when not enough blood is suboptimal if you have IVs) // If healer has no IVs, allow AI to do CPR to keep injured alive if ( - IN_CRDC_ARRST(_target) && - {EGVAR(medical_treatment,cprSuccessChanceMin) > 0} && - {!_hasIV || {_bloodVolume >= BLOOD_VOLUME_CLASS_3_HEMORRHAGE}} + _doCPR && + {!_canGiveIV || {_bloodVolume >= BLOOD_VOLUME_CLASS_3_HEMORRHAGE}} ) exitWith { _treatmentEvent = QEGVAR(medical_treatment,cprLocal); _treatmentArgs = [_healer, _target]; _treatmentTime = 15; }; - private _needsIv = _bloodVolume < MINIMUM_BLOOD_FOR_STABLE_VITALS; - private _canGiveIv = _isMedic && _hasIV && _needsIv; + private _bodypart = ""; + + if ( + _canGiveIV && { + // 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; + _bodyPart == "" + } + ) exitWith {}; - if (_canGiveIv) then { + if (_canGiveIV) then { // Check if patient's blood volume + remaining IV volume is enough to allow the patient to wake up private _totalIvVolume = 0; //in ml { @@ -144,17 +233,20 @@ if (true) then { // Check if the medic has to wait, which allows for a little multitasking if (_bloodVolume + (_totalIvVolume / 1000) >= MINIMUM_BLOOD_FOR_STABLE_VITALS) then { _treatmentEvent = "#waitForIV"; - _canGiveIv = false; + _needsIV = false; + _canGiveIV = false; }; }; - if (_canGiveIv) exitWith { + if (_canGiveIV) exitWith { _treatmentEvent = QEGVAR(medical_treatment,ivBagLocal); _treatmentTime = 5; - _treatmentArgs = [_target, call _fnc_findNoTourniquet, "SalineIV"]; + _treatmentArgs = [_target, _bodyPart, "SalineIV"]; _treatmentItem = "@iv"; }; + private _fractures = GET_FRACTURES(_target); + if ( ((_fractures select 4) == 1) && {([_healer, "splint"] call FUNC(itemCheck)) # 0} @@ -176,47 +268,70 @@ if (true) then { }; // Wait until the injured has enough blood before administering drugs - if (_needsIv) then { - _treatmentEvent = "#waitForIV" - }; + // (_needsIV && !_canGiveIV), but _canGiveIV is false here, otherwise IV would be given + if (_needsIV || {_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 + _treatmentArgs = [_healer, _target]; + _treatmentTime = 15; + }; - if (_treatmentEvent == "#waitForIV") exitWith {}; + // If the injured needs IVs, but healer can't give it to them, have healder wait + if (_needsIV) exitWith { + _treatmentEvent = "#needsIV"; // TODO: Medic can move onto another patient + }; + }; if ((count (_target getVariable [VAR_MEDICATIONS, []])) >= 6) exitWith { - _treatmentEvent = "#tooManyMeds"; + _treatmentEvent = "#tooManyMeds"; // TODO: Medic can move onto another patient }; + private _heartRate = GET_HEART_RATE(_target); + if ( - ((IS_UNCONSCIOUS(_target) && {_heartRate < 160}) || {_heartRate <= 50}) && + (IS_UNCONSCIOUS(_target) || {_heartRate <= 50}) && {([_healer, "epinephrine"] call FUNC(itemCheck)) # 0} ) exitWith { if (CBA_missionTime < (_target getVariable [QGVAR(nextEpinephrine), -1])) exitWith { _treatmentEvent = "#waitForEpinephrineToTakeEffect"; }; if (_heartRate > 180) exitWith { - _treatmentEvent = "#waitForSlowerHeart"; + _treatmentEvent = "#waitForSlowerHeart"; // TODO: Medic can move onto another patient, after X amount of time of high HR }; + + // 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; + + if (_bodyPart == "") exitWith {}; + _target setVariable [QGVAR(nextEpinephrine), CBA_missionTime + 10]; _treatmentEvent = QEGVAR(medical_treatment,medicationLocal); _treatmentTime = 2.5; - _treatmentArgs = [_target, call _fnc_findNoTourniquet, "Epinephrine"]; + _treatmentArgs = [_target, _bodyPart, "Epinephrine"]; _treatmentItem = "epinephrine"; }; if ( - (((GET_PAIN_PERCEIVED(_target) > 0.25) && {_heartRate > 40}) || {_heartRate >= 180}) && + ((GET_PAIN_PERCEIVED(_target) > 0.25) || {_heartRate >= 180}) && {([_healer, "morphine"] call FUNC(itemCheck)) # 0} ) exitWith { if (CBA_missionTime < (_target getVariable [QGVAR(nextMorphine), -1])) exitWith { _treatmentEvent = "#waitForMorphineToTakeEffect"; }; if (_heartRate < 60) exitWith { - _treatmentEvent = "#waitForFasterHeart"; + _treatmentEvent = "#waitForFasterHeart"; // TODO: Medic can move onto another patient, after X amount of time of low HR }; + + // 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; + + if (_bodyPart == "") exitWith {}; + _target setVariable [QGVAR(nextMorphine), CBA_missionTime + 30]; _treatmentEvent = QEGVAR(medical_treatment,medicationLocal); _treatmentTime = 2.5; - _treatmentArgs = [_target, call _fnc_findNoTourniquet, "Morphine"]; + _treatmentArgs = [_target, _bodyPart, "Morphine"]; _treatmentItem = "morphine"; }; }; @@ -224,11 +339,16 @@ if (true) then { _healer setVariable [QGVAR(currentTreatment), [CBA_missionTime + _treatmentTime, _target, _treatmentEvent, _treatmentArgs, _treatmentItem]]; // Play animation -if ((_treatmentEvent select [0,1]) != "#") then { - private _treatmentClassname = _treatmentArgs select 2; - if (_treatmentEvent == QEGVAR(medical_treatment,splintLocal)) then { _treatmentClassname = "Splint" }; - if (_treatmentEvent == QEGVAR(medical_treatment,cprLocal)) then { _treatmentClassname = "CPR" }; - [_healer, _treatmentClassname, (_healer == _target)] call FUNC(playTreatmentAnim); +if ((_treatmentEvent select [0, 1]) != "#") then { + private _treatmentClassname = switch (_treatmentEvent) do { + case QEGVAR(medical_treatment,splintLocal): {"Splint"}; + case QEGVAR(medical_treatment,cprLocal): {"CPR"}; + case QEGVAR(medical_treatment,tourniquetLocal): {"ApplyTourniquet"}; + case QGVAR(tourniquetRemove): {"RemoveTourniquet"}; + default {_treatmentArgs select 2}; + }; + + [_healer, _treatmentClassname, _healer == _target] call FUNC(playTreatmentAnim); }; #ifdef DEBUG_MODE_FULL diff --git a/addons/medical_treatment/functions/fnc_tourniquetRemove.sqf b/addons/medical_treatment/functions/fnc_tourniquetRemove.sqf index 8a6be10bb49..97680353e45 100644 --- a/addons/medical_treatment/functions/fnc_tourniquetRemove.sqf +++ b/addons/medical_treatment/functions/fnc_tourniquetRemove.sqf @@ -26,7 +26,9 @@ private _partIndex = ALL_BODY_PARTS find tolowerANSI _bodyPart; private _tourniquets = GET_TOURNIQUETS(_patient); if (_tourniquets select _partIndex == 0) exitWith { - [LSTRING(noTourniquetOnBodyPart), 1.5] call EFUNC(common,displayTextStructured); + if (_medic == ACE_player) then { + [LSTRING(noTourniquetOnBodyPart), 1.5] call EFUNC(common,displayTextStructured); + }; }; _tourniquets set [_partIndex, 0]; @@ -39,8 +41,15 @@ TRACE_1("clearConditionCaches: tourniquetRemove",_nearPlayers); [QEGVAR(interact_menu,clearConditionCaches), [], _nearPlayers] call CBA_fnc_targetEvent; // Add tourniquet item to medic or patient -private _receiver = [_patient, _medic, _medic] select GVAR(allowSharedEquipment); -[_receiver, "ACE_tourniquet"] call EFUNC(common,addToInventory); +if (_medic call EFUNC(common,isPlayer)) then { + private _receiver = [_patient, _medic, _medic] select GVAR(allowSharedEquipment); + [_receiver, "ACE_tourniquet"] call EFUNC(common,addToInventory); +} else { + // If the medic is AI, only return tourniquet if enabled + if (missionNamespace getVariable [QEGVAR(medical_ai,requireItems), 0] > 0) then { + [_medic, "ACE_tourniquet"] call EFUNC(common,addToInventory); + }; +}; // Handle occluded medications that were blocked due to tourniquet private _occludedMedications = _patient getVariable [QEGVAR(medical,occludedMedications), []];