mirror of
https://github.com/acemod/ACE3.git
synced 2024-08-30 18:23:18 +00:00
Add Medical AI implementation for basic medical (#4311)
* Initial commit medical AI * Finished non-healing functions * Initial work on self healin * AI healing * Finished medical AI for basic medical * Finito * Fix for dead units, medic not being close enough * Make ace_medical required * Fixed double systemChat * Made AI units able to heal players * Fixed wound treatment * Fixed medic movement * Made units heal themselves earlier
This commit is contained in:
parent
0a6bfd987c
commit
278e7fa800
1
addons/medical_ai/$PBOPREFIX$
Normal file
1
addons/medical_ai/$PBOPREFIX$
Normal file
@ -0,0 +1 @@
|
||||
z\ace\addons\medical_ai
|
17
addons/medical_ai/CfgEventHandlers.hpp
Normal file
17
addons/medical_ai/CfgEventHandlers.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
class Extended_PreStart_EventHandlers {
|
||||
class ADDON {
|
||||
init = QUOTE(call COMPILE_FILE(XEH_preStart));
|
||||
};
|
||||
};
|
||||
|
||||
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));
|
||||
};
|
||||
};
|
10
addons/medical_ai/README.md
Normal file
10
addons/medical_ai/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
ace_medical_ai
|
||||
==========
|
||||
|
||||
Makes AI units heal themselves and each other.
|
||||
|
||||
## Maintainers
|
||||
|
||||
The people responsible for merging changes to this component or answering potential questions.
|
||||
|
||||
- [BaerMitUmlaut](https://github.com/BaerMitUmlaut)
|
86
addons/medical_ai/StateMachine.hpp
Normal file
86
addons/medical_ai/StateMachine.hpp
Normal file
@ -0,0 +1,86 @@
|
||||
class GVAR(stateMachine) {
|
||||
list = "allUnits select {local _x}";
|
||||
skipNull = 1;
|
||||
|
||||
class Initial {
|
||||
class Injured {
|
||||
targetState = "Injured";
|
||||
condition = QUOTE(call FUNC(isInjured));
|
||||
};
|
||||
class HealUnit {
|
||||
targetState = "HealUnit";
|
||||
condition = QUOTE((call FUNC(isSafe)) && {call FUNC(wasRequested)});
|
||||
};
|
||||
};
|
||||
|
||||
class Injured {
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
onState = "systemChat format [""%1 is injured"", _this]";
|
||||
#endif
|
||||
|
||||
class InSafety {
|
||||
targetState = "Safe";
|
||||
condition = QUOTE(call FUNC(isSafe));
|
||||
};
|
||||
};
|
||||
|
||||
class Safe {
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
onState = "systemChat format [""%1 is injured, but safe"", _this]";
|
||||
#endif
|
||||
|
||||
class RequestMedic {
|
||||
targetState = "HealSelf";
|
||||
condition = QUOTE(call FUNC(canRequestMedic));
|
||||
onTransition = QUOTE(call FUNC(requestMedic));
|
||||
};
|
||||
class HealSelf {
|
||||
targetState = "HealSelf";
|
||||
condition = "true";
|
||||
};
|
||||
};
|
||||
|
||||
class HealSelf {
|
||||
onState = QUOTE(call FUNC(healSelf));
|
||||
onStateLeaving = QUOTE(_this setVariable [ARR_2(QUOTE(QGVAR(treatmentOverAt)),nil)]);
|
||||
|
||||
class Initial {
|
||||
// Go back to initial state when done healing
|
||||
targetState = "Initial";
|
||||
condition = QUOTE( \
|
||||
!(call FUNC(isInjured)) \
|
||||
&& {_this getVariable [ARR_2(QUOTE(QGVAR(treatmentOverAt)),CBA_missionTime)] <= CBA_missionTime} \
|
||||
);
|
||||
};
|
||||
class Injured {
|
||||
// Stop treating when it's no more safe
|
||||
targetState = "Injured";
|
||||
condition = QUOTE( \
|
||||
!(call FUNC(isSafe)) \
|
||||
&& {_this getVariable [ARR_2(QUOTE(QGVAR(treatmentOverAt)),CBA_missionTime)] <= CBA_missionTime} \
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
class HealUnit {
|
||||
onState = QUOTE(call FUNC(healUnit));
|
||||
onStateLeaving = QUOTE(_this setVariable [ARR_2(QUOTE(QGVAR(treatmentOverAt)),nil)]);
|
||||
|
||||
class Initial {
|
||||
// Go back to initial state when done healing or it's no more safe to treat
|
||||
targetState = "Initial";
|
||||
condition = QUOTE( \
|
||||
!((call FUNC(wasRequested)) && {call FUNC(isSafe)}) \
|
||||
&& {_this getVariable [ARR_2(QUOTE(QGVAR(treatmentOverAt)),CBA_missionTime)] <= CBA_missionTime} \
|
||||
);
|
||||
};
|
||||
class Injured {
|
||||
// Treating yourself has priority
|
||||
targetState = "Injured";
|
||||
condition = QUOTE( \
|
||||
(call FUNC(isInjured)) \
|
||||
&& {_this getVariable [ARR_2(QUOTE(QGVAR(treatmentOverAt)),CBA_missionTime)] <= CBA_missionTime} \
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
8
addons/medical_ai/XEH_PREP.hpp
Normal file
8
addons/medical_ai/XEH_PREP.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
PREP(canRequestMedic);
|
||||
PREP(healSelf);
|
||||
PREP(healUnit);
|
||||
PREP(isInjured);
|
||||
PREP(isSafe);
|
||||
PREP(playTreatmentAnim);
|
||||
PREP(requestMedic);
|
||||
PREP(wasRequested);
|
31
addons/medical_ai/XEH_postInit.sqf
Normal file
31
addons/medical_ai/XEH_postInit.sqf
Normal file
@ -0,0 +1,31 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
["ace_settingsInitialized", {
|
||||
// Only run for AI that does not have to deal with advanced medical
|
||||
if (EGVAR(medical,enableFor) == 1 || {hasInterface && {EGVAR(medical,level) == 2}}) exitWith {};
|
||||
|
||||
["ace_firedNonPlayer", {
|
||||
_unit setVariable [QGVAR(lastFired), CBA_missionTime];
|
||||
}] call CBA_fnc_addEventHandler;
|
||||
|
||||
if (hasInterface) then {
|
||||
["ace_unconscious", {
|
||||
params ["_unit", "_unconscious"];
|
||||
if (!_unconscious || {_unit != ACE_player}) exitWith {};
|
||||
|
||||
private _medic = objNull;
|
||||
{
|
||||
if ((!isPlayer _x) && {[_x] call EFUNC(medical,isMedic)}) exitWith {
|
||||
_medic = _x;
|
||||
};
|
||||
} forEach (units _unit);
|
||||
if (isNull _medic) exitWith {};
|
||||
|
||||
private _healQueue = _medic getVariable [QGVAR(healQueue), []];
|
||||
_healQueue pushBack _unit;
|
||||
_medic setVariable [QGVAR(healQueue), _healQueue];
|
||||
}] call CBA_fnc_addEventHandler;
|
||||
};
|
||||
|
||||
GVAR(statemachine) = [configFile >> "ACE_Medical_AI_StateMachine"] call CBA_statemachine_fnc_createFromConfig;
|
||||
}] call CBA_fnc_addEventHandler;
|
7
addons/medical_ai/XEH_preInit.sqf
Normal file
7
addons/medical_ai/XEH_preInit.sqf
Normal file
@ -0,0 +1,7 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
ADDON = false;
|
||||
|
||||
#include "XEH_PREP.hpp"
|
||||
|
||||
ADDON = true;
|
3
addons/medical_ai/XEH_preStart.sqf
Normal file
3
addons/medical_ai/XEH_preStart.sqf
Normal file
@ -0,0 +1,3 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
#include "XEH_PREP.hpp"
|
18
addons/medical_ai/config.cpp
Normal file
18
addons/medical_ai/config.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "script_component.hpp"
|
||||
|
||||
class CfgPatches {
|
||||
class ADDON {
|
||||
name = COMPONENT_NAME;
|
||||
units[] = {};
|
||||
weapons[] = {};
|
||||
requiredVersion = REQUIRED_VERSION;
|
||||
requiredAddons[] = {"ace_medical"};
|
||||
author = ECSTRING(common,ACETeam);
|
||||
authors[] = {"BaerMitUmlaut"};
|
||||
url = ECSTRING(main,URL);
|
||||
VERSION_CONFIG;
|
||||
};
|
||||
};
|
||||
|
||||
#include "CfgEventHandlers.hpp"
|
||||
#include "StateMachine.hpp"
|
27
addons/medical_ai/functions/fnc_canRequestMedic.sqf
Normal file
27
addons/medical_ai/functions/fnc_canRequestMedic.sqf
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Checks if there is a medic available in the unit's group.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Can request medic <BOOL>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
// Note: Although an unconscious unit cannot call for a medic itself,
|
||||
// we ignore this here. We need to "notice" the medic that he should
|
||||
// treat other units, or else he won't do anything on his own.
|
||||
|
||||
if ([_this] call EFUNC(medical,isMedic) || {vehicle _this != _this}) exitWith {false};
|
||||
|
||||
{
|
||||
if ([_x] call EFUNC(medical,isMedic)) exitWith {
|
||||
_this setVariable [QGVAR(assignedMedic), _x];
|
||||
true
|
||||
};
|
||||
false
|
||||
} forEach (units _this);
|
55
addons/medical_ai/functions/fnc_healSelf.sqf
Normal file
55
addons/medical_ai/functions/fnc_healSelf.sqf
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Makes the unit heal itself.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Nothing
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
// Player will have to do this manually of course
|
||||
if (isPlayer _this) exitWith {};
|
||||
// Can't heal self when unconscious
|
||||
if (_this getVariable ["ACE_isUnconscious", false]) exitWith {};
|
||||
// Check if we're still treating
|
||||
if ((_this getVariable [QGVAR(treatmentOverAt), CBA_missionTime]) > CBA_missionTime) exitWith {};
|
||||
|
||||
private _needsBandaging = ([_this] call EFUNC(medical,getBloodLoss)) > 0;
|
||||
private _needsMorphine = (_this getVariable [QEGVAR(medical,pain), 0]) > 0.2;
|
||||
|
||||
switch (true) do {
|
||||
case _needsBandaging: {
|
||||
// Select first wound and bandage it
|
||||
private _openWounds = _this getVariable [QEGVAR(medical,openWounds), []];
|
||||
private _partIndex = {
|
||||
_x params ["", "", "_index", "_amount", "_percentage"];
|
||||
if (_amount * _percentage > 0) exitWith {
|
||||
_index
|
||||
};
|
||||
} forEach _openWounds;
|
||||
private _selection = ["head","body","hand_l","hand_r","leg_l","leg_r"] select _partIndex;
|
||||
[_this, "Bandage", _selection] call EFUNC(medical,treatmentAdvanced_bandageLocal);
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["%1 is bandaging selection %2", _this, _selection];
|
||||
#endif
|
||||
|
||||
// Play animation
|
||||
[_this, true, true] call FUNC(playTreatmentAnim);
|
||||
_this setVariable [QGVAR(treatmentOverAt), CBA_missionTime + 5];
|
||||
};
|
||||
case _needsMorphine: {
|
||||
[_this] call EFUNC(medical,treatmentBasic_morphineLocal);
|
||||
[_this, false, true] call FUNC(playTreatmentAnim);
|
||||
_this setVariable [QGVAR(treatmentOverAt), CBA_missionTime + 2];
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["%1 is giving himself morphine", _this];
|
||||
#endif
|
||||
};
|
||||
};
|
84
addons/medical_ai/functions/fnc_healUnit.sqf
Normal file
84
addons/medical_ai/functions/fnc_healUnit.sqf
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Makes a medic heal the next unit that needs treatment.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Nothing
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
// Can't heal other units when unconscious
|
||||
if (_this getVariable ["ACE_isUnconscious", false]) exitWith {};
|
||||
// Check if we're still treating
|
||||
if ((_this getVariable [QGVAR(treatmentOverAt), CBA_missionTime]) > CBA_missionTime) exitWith {};
|
||||
|
||||
// Find next unit to treat
|
||||
private _healQueue = _this getVariable [QGVAR(healQueue), []];
|
||||
private _target = _healQueue select 0;
|
||||
|
||||
// If unit died or was healed, be lazy and wait for the next tick
|
||||
if (isNull _target || {!alive _target} || {!(_target call FUNC(isInjured))}) exitWith {
|
||||
_target forceSpeed -1;
|
||||
_healQueue deleteAt 0;
|
||||
_this getVariable [QGVAR(healQueue), _healQueue];
|
||||
_this forceSpeed -1;
|
||||
// return to formation instead of going where the injured unit was if it healed itself in the mean time
|
||||
_this doFollow leader _this;
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["%1 finished healing %2", _this, _target];
|
||||
#endif
|
||||
};
|
||||
|
||||
// Move to target...
|
||||
if (_this distance _target > 2) exitWith {
|
||||
if !(_this getVariable [QGVAR(movingToInjured), false]) then {
|
||||
_this setVariable [QGVAR(movingToInjured), true];
|
||||
_this doMove getPosATL _target;
|
||||
};
|
||||
};
|
||||
_this setVariable [QGVAR(movingToInjured), false];
|
||||
|
||||
// ...and make sure medic and target don't move
|
||||
_this forceSpeed 0;
|
||||
_target forceSpeed 0;
|
||||
|
||||
private _needsBandaging = ([_target] call EFUNC(medical,getBloodLoss)) > 0;
|
||||
private _needsMorphine = (_target getVariable [QEGVAR(medical,pain), 0]) > 0.2;
|
||||
|
||||
switch (true) do {
|
||||
case _needsBandaging: {
|
||||
// Select first wound and bandage it
|
||||
private _openWounds = _target getVariable [QEGVAR(medical,openWounds), []];
|
||||
private _partIndex = {
|
||||
_x params ["", "", "_index", "_amount", "_percentage"];
|
||||
if (_amount * _percentage > 0) exitWith {
|
||||
_index
|
||||
};
|
||||
} forEach _openWounds;
|
||||
private _selection = ["head","body","hand_l","hand_r","leg_l","leg_r"] select _partIndex;
|
||||
[_target, "Bandage", _selection] call EFUNC(medical,treatmentAdvanced_bandageLocal);
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["%1 is bandaging selection %2 on %3", _this, _selection, _target];
|
||||
#endif
|
||||
|
||||
// Play animation
|
||||
[_this, true, false] call FUNC(playTreatmentAnim);
|
||||
_this setVariable [QGVAR(treatmentOverAt), CBA_missionTime + 5];
|
||||
};
|
||||
case _needsMorphine: {
|
||||
[_target] call EFUNC(medical,treatmentBasic_morphineLocal);
|
||||
[_this, false, false] call FUNC(playTreatmentAnim);
|
||||
_this setVariable [QGVAR(treatmentOverAt), CBA_missionTime + 2];
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["%1 is giving %2 morphine", _this, _target];
|
||||
#endif
|
||||
};
|
||||
};
|
22
addons/medical_ai/functions/fnc_isInjured.sqf
Normal file
22
addons/medical_ai/functions/fnc_isInjured.sqf
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Checks if a unit needs treatment.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Does unit need treatment <BOOL>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
if !(alive _this) exitWith {false};
|
||||
|
||||
private _bloodLoss = [_this] call EFUNC(medical,getBloodLoss);
|
||||
private _pain = _this getVariable [QEGVAR(medical,pain), 0];
|
||||
// Advanced only?
|
||||
// private _heartRate = _this getVariable [QEGVAR(medical,heartRate), 70];
|
||||
|
||||
(_bloodLoss > 0) || {_pain > 0.2} // || {_heartRate > 100} || {_heartRate < 40}
|
15
addons/medical_ai/functions/fnc_isSafe.sqf
Normal file
15
addons/medical_ai/functions/fnc_isSafe.sqf
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Checks if a unit is currently considered safe enough to treat itself.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Is unit safe enough <BOOL>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
(getSuppression _this == 0) && {CBA_missionTime - (_this getVariable [QGVAR(lastFired), -30]) > 30}
|
38
addons/medical_ai/functions/fnc_playTreatmentAnim.sqf
Normal file
38
addons/medical_ai/functions/fnc_playTreatmentAnim.sqf
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Plays the corresponding treatment animation.
|
||||
*
|
||||
* Arguments:
|
||||
* 0: Unit <OBJECT>
|
||||
* 1: Is bandage <BOOL>
|
||||
* 2: Is self treatment <BOOL>
|
||||
*
|
||||
* Return Value:
|
||||
* Nothing
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
params ["_unit", "_isBandage", "_isSelfTreatment"];
|
||||
|
||||
if (vehicle _unit != _unit) exitWith {};
|
||||
|
||||
private _animConfig = if (_isBandage) then {
|
||||
configFile >> "ACE_Medical_Actions" >> "Basic" >> "Bandage";
|
||||
} else {
|
||||
configFile >> "ACE_Medical_Actions" >> "Basic" >> "Morphine";
|
||||
};
|
||||
|
||||
private _configProperty = "animationCaller";
|
||||
if (_isSelfTreatment) then {
|
||||
_configProperty = _configProperty + "Self";
|
||||
};
|
||||
if (stance _unit == "PRONE") then {
|
||||
_configProperty = _configProperty + "Prone";
|
||||
};
|
||||
|
||||
private _anim = getText (_animConfig >> _configProperty);
|
||||
private _wpn = ["non", "rfl", "pst"] select (1 + ([primaryWeapon _unit, handgunWeapon _unit] find (currentWeapon _unit)));
|
||||
_anim = [_anim, "[wpn]", _wpn] call CBA_fnc_replace;
|
||||
|
||||
[_unit, _anim] call EFUNC(common,doAnimation);
|
22
addons/medical_ai/functions/fnc_requestMedic.sqf
Normal file
22
addons/medical_ai/functions/fnc_requestMedic.sqf
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Sends a request to the units assigned medic to heal it.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Nothing
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
private _assignedMedic = _this getVariable QGVAR(assignedMedic);
|
||||
private _healQueue = _assignedMedic getVariable [QGVAR(healQueue), []];
|
||||
_healQueue pushBack _this;
|
||||
_assignedMedic setVariable [QGVAR(healQueue), _healQueue];
|
||||
|
||||
#ifdef DEBUG_MODE_FULL
|
||||
systemChat format ["%1 requested %2 for medical treatment", _this, _assignedMedic];
|
||||
#endif
|
16
addons/medical_ai/functions/fnc_wasRequested.sqf
Normal file
16
addons/medical_ai/functions/fnc_wasRequested.sqf
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Author: BaerMitUmlaut
|
||||
* Checks if the unit was requested to treat another unit.
|
||||
*
|
||||
* Arguments:
|
||||
* None
|
||||
*
|
||||
* Return Value:
|
||||
* Was requested <BOOL>
|
||||
*
|
||||
* Public: No
|
||||
*/
|
||||
#include "script_component.hpp"
|
||||
|
||||
private _healQueue = _this getVariable [QGVAR(healQueue), []];
|
||||
!(_healQueue isEqualTo [])
|
1
addons/medical_ai/functions/script_component.hpp
Normal file
1
addons/medical_ai/functions/script_component.hpp
Normal file
@ -0,0 +1 @@
|
||||
#include "\z\ace\addons\medical_ai\script_component.hpp"
|
18
addons/medical_ai/script_component.hpp
Normal file
18
addons/medical_ai/script_component.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#define COMPONENT medical_ai
|
||||
#define COMPONENT_BEAUTIFIED Medical AI
|
||||
#include "\z\ace\addons\main\script_mod.hpp"
|
||||
|
||||
// #define DEBUG_ENABLED_MEDICAL_AI
|
||||
// #define DISABLE_COMPILE_CACHE
|
||||
// #define CBA_DEBUG_SYNCHRONOUS
|
||||
// #define ENABLE_PERFORMANCE_COUNTERS
|
||||
|
||||
#ifdef DEBUG_ENABLED_MEDICAL_AI
|
||||
#define DEBUG_MODE_FULL
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_SETTINGS_MEDICAL_AI
|
||||
#define DEBUG_SETTINGS DEBUG_SETTINGS_MEDICAL_AI
|
||||
#endif
|
||||
|
||||
#include "\z\ace\addons\main\script_macros.hpp"
|
Loading…
Reference in New Issue
Block a user