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:
BaerMitUmlaut 2016-09-04 11:25:03 +02:00 committed by Glowbal
parent 0a6bfd987c
commit 278e7fa800
19 changed files with 479 additions and 0 deletions

View File

@ -0,0 +1 @@
z\ace\addons\medical_ai

View 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));
};
};

View 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)

View 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} \
);
};
};
};

View File

@ -0,0 +1,8 @@
PREP(canRequestMedic);
PREP(healSelf);
PREP(healUnit);
PREP(isInjured);
PREP(isSafe);
PREP(playTreatmentAnim);
PREP(requestMedic);
PREP(wasRequested);

View 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;

View File

@ -0,0 +1,7 @@
#include "script_component.hpp"
ADDON = false;
#include "XEH_PREP.hpp"
ADDON = true;

View File

@ -0,0 +1,3 @@
#include "script_component.hpp"
#include "XEH_PREP.hpp"

View 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"

View 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);

View 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
};
};

View 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
};
};

View 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}

View 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}

View 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);

View 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

View 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 [])

View File

@ -0,0 +1 @@
#include "\z\ace\addons\medical_ai\script_component.hpp"

View 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"