-
Notifications
You must be signed in to change notification settings - Fork 738
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 AI #4311
Medical AI #4311
Changes from all commits
7e6d847
c71b646
73389d1
607529c
acf84c0
38ec481
13b694f
e2689c1
677b45f
0683a8c
b6a84bf
e1915b2
fd1645a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
z\ace\addons\medical_ai |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} \ | ||
); | ||
}; | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
PREP(canRequestMedic); | ||
PREP(healSelf); | ||
PREP(healUnit); | ||
PREP(isInjured); | ||
PREP(isSafe); | ||
PREP(playTreatmentAnim); | ||
PREP(requestMedic); | ||
PREP(wasRequested); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), []]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the unit is already being healed at this moment ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't matter, the medic checks before moving to the unit if it still needs healing. |
||
_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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#include "script_component.hpp" | ||
|
||
ADDON = false; | ||
|
||
#include "XEH_PREP.hpp" | ||
|
||
ADDON = true; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#include "script_component.hpp" | ||
|
||
#include "XEH_PREP.hpp" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two conditions are the same in multiple places. Should we convert those to a macro? |
||
|
||
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 | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be a bit to much for an initial version, but shouldn't they find cover first, if under fire? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The medic will only be requested once the injured unit is considered safe, and the medic will only move to the injured unit when the medic is safe. The safety status of the injured unit might change in the mean time, but the medic simply aborts the process when he is not safe anymore. |
||
|
||
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 | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a global event. Some kind of locality check or duplication check will be necessary in this event handler. No need to add to the healQueue if the unit is already in that queue