Jump to content

Recommended Posts

Posted (edited)

AI Medic Auto Heal

by

Rydygier & Gunter Severloh

 

Description

AI Medic auto heal is a code where an ai medic in your squad you command, will automatically

heal each member of the squad or group including the player if they are injured without commanding them to do so.

    The code was written by Rydygier, which was a major upgrade of the code i had presented to him with.

 

 

How it works:

1. Every 10 seconds the code checks, if units in the group are damaged >0.1;

 

2. If so, the code looks for a valid medic unit in wounded unit's group (wounded unit itself excluded).

Valid medic must be alive, not on the move (unitReady), AI, with a trait "Medic" (so only certain classes, not any), closer, than 500m from the wounded unit,

and not busy with healing already. 

3. Closest of valid medics is chosen.


4. If both medic and wounded are in the same vehicle, auto heal should occur after 5 seconds (no FAK or medikit required), unless medic or wounded is killed

     or deleted during that 5 seconds. If not:

5. If wounded unit's vehicle is moving fast or not touching ground - the code breaks and no healing action will occur. Otherwise:

6. Medic gets doMove towards wounded unit. Medic will not try to use his current vehicle in order to reach wounded unit's position. Instead, it will disembark and move on foot.

If wounded unit is in a vehicle, medic's actual destination is only in rough vicinity though.   If in the meantime medic gets stuck, the distance from the wounded increase by

       additional 200m above initial distance for any reason or any mentioned above conditions become not OK, code breaks, medic is stopped, no healing will occur.

Otherwise:

7. The movement is concluded, if at the end medic is close enough to the wounded - healing action will occur.

If wounded is in the vehicle, likely medic will be teleported in the direct vicinity of the wounded (so may end within vehicle's model box until healing is over).

        "HealSoldier" action requires/consumes a FAK or medikit, but my code adds "setDamage 0" auto heal at the end of this action, so even, if medic is out of FAKs, wounded should be healed.

Without it healing wounded sitting in a vehicle could fail (would need more testing to confirm for sure). 

If described procedure is interrupted without healing action and the player is still alive and wounded, in the next cycle (10 seconds) it will be attempted again. 

 

8. Medics now can also heal themselves with this script.

9. Wounded are prioritized by distance from closest available medic and severity of the wound (both factors are weighted together).
10. Modified wound detection. Now it is based on hitPoints damage, not overall damage (so limping due to fall from above should trigger a medic).


Note - if both, player and medic are subordinates of an AI TL, TL may override medic's movements with other commands, like "return to formation".

Hence it is recommended for player TL groups. Otherwise player may be forced to approach the medic close enough (not tested).

Installation and Setup

Setup - Script

1. Create a notepad/notepad++ document and copy and paste the following code into it:

2. Save and name the document:  init.sqf and put it into your scenario folder.

[] spawn
	{
	RYD_SM_EnemyCloseDistance = 100;//if medics must move to the wounded, but he is engaged in combat with some enemy being closer than this value, medic is considered not available and will interrupt pending movement
	
	waitUntil
		{
		sleep 1;
		(alive player)
		};
		
	RYD_SM_HealCheck = 
		{
		params ["_unit","_medics"];
		
		if ((time - (_unit getVariable ["RYD_SM_LastCheck",0])) < 10) exitWith {};//check only once per 10 seconds 
		
		_unit setVariable ["RYD_SM_LastCheck",time];
		
		if not (alive _unit) exitWith {};//just in case, "Hit" would trigger for a dead player
		
		_vehicleUnit = vehicle _unit;
		
		if (alive (_unit getVariable ["RYD_SM_HealedBy",objNull])) exitWith {};//exit if already treated
		//potential medic is one of _unit's team mates, _unit excluded, that are alive, unitReady, AI, of proper medic class, closer, than 500m and not busy with healing already
				
		if (_unit in _medics) exitWith
			{
			_unit setVariable ["RYD_SM_Busy",true];
			_unit setVariable ["RYD_SM_HealedBy",_unit];
			
			[_unit,_vehicleUnit] spawn
				{
				params ["_unit","_vehicleUnit"];
				
				_unit action ["HealSoldierSelf",_unit];
				
				if not (_unit isEqualTo _vehicleUnit) exitWith
					{
					sleep 5;
					
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_unit setVariable ["RYD_SM_Busy",nil];
					
					if not (alive _unit) exitWith {};
					
					_unit setDamage 0;
					};
				
				_time = time;
				
				waitUntil
					{
					sleep 1;
					
					if not (alive _unit) exitWith {true};
					
					((((toLower (animationState _unit)) find "medic") >= 0) or {(time - _time) > 5})
					};
				
				if not (alive _unit) exitWith {};
					
				_time = time;
				
				waitUntil
					{
					sleep 1;
					
					if not (alive _unit) exitWith {true};
					
					((((toLower (animationState _unit)) find "medic") < 0) or {(time - _time) > 5})
					};
					
				_unit setVariable ["RYD_SM_HealedBy",nil];
				_unit setVariable ["RYD_SM_Busy",nil];
				};
			};
			
		_medics = _medics select {(_x distance _unit) < 500};
		
		if ((count _medics) < 1) exitWith {};//exit if no valid medics
		
		_medics = _medics apply {[_x distance _unit,_x]};
		_medics sort true;
		
		_medic = (_medics select 0) select 1;//closest of medics is chosen
		_vehicleMedic = vehicle _medic;
		
		_sameVehicle = _vehicleUnit isEqualTo _vehicleMedic;
		
		if (not (_sameVehicle) and {((abs (speed _vehicleUnit)) > 10)}) exitWith {};//exit if _unit is moving fast
		if (not (_sameVehicle) and {not (isTouchingGround _vehicleUnit)}) exitWith {};//exit if _unit is flying
		
		_unit setVariable ["RYD_SM_HealedBy",_medic];
		_medic setVariable ["RYD_SM_Busy",true];
		
		if (_sameVehicle) exitWith//if _medic and _unit are in the same vehicle, just automatically conclude healing with 5 sec delay 
			{
			[_unit,_medic] spawn
				{
				params ["_unit","_medic"];
				
				_medic action ["HealSoldier",_unit];
				
				sleep 5;
				
				_unit setVariable ["RYD_SM_HealedBy",nil];
				_medic setVariable ["RYD_SM_Busy",nil];
				
				if not (alive _unit) exitWith {};
				if not (alive _medic) exitWith {};//if during the delay _medic or _unit are killed or delted - no healing. Otherwise healing occurs even if one or both leave vehicle in the meantime.
				
				_unit setDamage 0;
				};
			};
		
		_unitPosition = getPosATL _vehicleUnit;
		_medicPosition = getPosATL _vehicleMedic;
		
		_distance = _unitPosition distance _medicPosition;
		
		_threshold = if (_unit isEqualTo _vehicleUnit) then
			{
			2.5
			}
		else
			{
			((sizeOf (typeOf _vehicleUnit))/2)
			};
		
		if (_distance > _threshold) then
			{
			if not (_medic isEqualTo _vehicleMedic) then
				{
				_medic action ["GetOut",_vehicleMedic];
				};
				
			_medic doMove _unitPosition;
			
			[_unit,_medic,_vehicleUnit,_distance,_threshold] spawn
				{
				params ["_unit","_medic","_vehicleUnit","_distance","_threshold"];
				
				sleep 1;
				
				_stuck = 0;
				_inFight = false;
				
				waitUntil
					{
					_pos1 = getPosATL _medic;
					
					sleep 2;
					
					_pos2 = getPosATL _medic;
					
					if not (alive _unit) exitWith {true};
					if not (alive _medic) exitWith {true};
					
					_enemy = getAttackTarget _medic;
					
					if ((alive _enemy) and {((_medic distance _enemy) < RYD_SM_EnemyCloseDistance) and {((_medic knowsAbout _enemy) > 1.5)}}) exitWith {_inFight = true;true};
					
					if ((abs (speed _vehicleUnit)) > 10) exitWith {true};
					if not (isTouchingGround _vehicleUnit) exitWith {true};
					
					if ((_pos1 distance _pos2) < 0.1) then
						{
						_stuck = _stuck + 1;
						};

					_threshold = if (_unit isEqualTo _vehicleUnit) then
						{
						2.5
						}
					else
						{
						((sizeOf (typeOf _vehicleUnit))/2)
						};
						
					if ((unitReady _medic) and {((_medic distance _vehicleUnit) >= _threshold)}) then
						{
						_medic doMove (getPosATL _vehicleUnit);
						};
					
					((unitReady _medic) or {(_stuck > 5) or {((_medic distance _vehicleUnit) > (_distance + 200))}})
					};
					
				if (_inFight) exitWith 
					{
					_medic doMove (getPosATL _medic);
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};
					
				if not (alive _unit) exitWith 
					{
					_medic doMove (getPosATL _medic);
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};
					
				if not (alive _medic) exitWith 
					{
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};
					
				if (_stuck > 10) exitWith 
					{
					_medic doMove (getPosATL _medic);
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};
					
				if ((abs (speed _vehicleUnit)) > 10) exitWith 
					{
					_medic doMove (getPosATL _medic);
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};//exit if _unit is moving fast
					
				if not (isTouchingGround _vehicleUnit) exitWith 
					{
					_medic doMove (getPosATL _medic);
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};//exit if _unit is flying
					
				if ((_medic distance _vehicleUnit) > (_distance + 200)) exitWith
					{
					_medic doMove (getPosATL _medic);
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					};//if for any reason distance increased too much (troublesome pathfinding for example) 
				
				_medic doMove (getPosATL _medic);

				if ((_medic distance _vehicleUnit) < _threshold) then
					{
					_medic action ["HealSoldier",_unit];
					
					_time = time;
					
					waitUntil
						{
						sleep 1;
						
						if not (alive _unit) exitWith {true};
						if not (alive _medic) exitWith {true};
						
						((((toLower (animationState _medic)) find "medic") >= 0) or {(time - _time) > 5})
						};
					
					if not (alive _unit) exitWith {};
					if not (alive _medic) exitWith {};
						
					_time = time;
					
					waitUntil
						{
						sleep 1;
						
						if not (alive _unit) exitWith {true};
						if not (alive _medic) exitWith {true};
						
						((((toLower (animationState _medic)) find "medic") < 0) or {(time - _time) > 5})
						};
						
					_unit setVariable ["RYD_SM_HealedBy",nil];
					_medic setVariable ["RYD_SM_Busy",nil];
					
					if not (alive _unit) exitWith {};
					if not (alive _medic) exitWith {};
						
					_unit setDamage 0;
					};
				};
			}
		else
			{
			[_unit,_medic] spawn
				{
				params ["_unit","_medic"];
				
				_medic doMove (getPosATL _medic);
				
				_medic action ["HealSoldier",_unit];
				
				_time = time;
				
				waitUntil
					{
					sleep 1;
					
					if not (alive _unit) exitWith {true};
					if not (alive _medic) exitWith {true};
					
					((((toLower (animationState _medic)) find "medic") >= 0) or {(time - _time) > 5})
					};
				
				if not (alive _unit) exitWith {};
				if not (alive _medic) exitWith {};
					
				_time = time;
				
				waitUntil
					{
					sleep 1;
					
					if not (alive _unit) exitWith {true};
					if not (alive _medic) exitWith {true};
					
					((((toLower (animationState _medic)) find "medic") < 0) or {(time - _time) > 5})
					};
					
				_unit setVariable ["RYD_SM_HealedBy",nil];
				_medic setVariable ["RYD_SM_Busy",nil];
				
				if not (alive _unit) exitWith {};
				if not (alive _medic) exitWith {};
					
				_unit setDamage 0;
				};
			};
		};
		
	while {alive player} do
		{
		_units = (units (group player)) select {(alive _x)};
		_wounded = _units select {((((getAllHitPointsDamage _x) select 2) findIf {_x > 0.1}) >= 0)};
		
		if ((count _wounded) > 0) then
			{
			_medics = _units select {(unitReady _x) and {not (isPlayer _x) and {(_x getUnitTrait "Medic") and {not (_x getVariable ["RYD_SM_Busy",false]) and {not (((alive (getAttackTarget _x)) and {((_x distance (getAttackTarget _x)) < RYD_SM_EnemyCloseDistance) and {((_x knowsAbout (getAttackTarget _x)) > 1.5)}}))}}}}};
			
			if ((count _medics) > 0) then
				{
				_wounded = _wounded apply
					{
					_unit = _x;
					_medics2 = _medics apply {[_x distance _unit,_x]};
					_medics2 sort true;
					
					_medicD = (_medics2 select 0) select 0;
					_val = (damage _x)/(_medicD max 1);
					[_val,_x]
					};
					
				_wounded sort false;

					{
					[(_x select 1),_medics] call RYD_SM_HealCheck;
					_medics = _medics select {not (_x getVariable ["RYD_SM_Busy",false])};
					
					if ((count _medics) == 0) exitWith {};
					}
				foreach _wounded;
				};
			};
		
		sleep 10;
		};
	};

Setup - External Script - (optional)

Instead of using a init.sqf for the code, you can setup an external script that can be used instead:

 

1. Create a notepad/notepad++ document and copy and paste the code from above into it:

2. Save and name the document:  RYD_AI_AutoMedic.sqf and put it into your scenario folder.

3. Create a notepad/notepad++ document and add the following code to it:  execVM "RYD_AI_AutoMedic.sqf";

4. Save and name the document: init.sqf

5. Add both RYD_AI_AutoMedic.sqf and the init.sqf to your scenario folder where the mission.sqm is located.

 

Credits

Rydygier wrote the code.

Both Gunter's and Rydygier's ideas.

Edited by Gunter Severloh
Updated code
  • Like 6
  • Thanks 4

Share this post


Link to post
Share on other sites

Update

 

v1.01

From Rydygier

Code updated in the OP.

 

Changes:

- medics now can also heal themselves with this script;

- wounded are prioritized by distance from closest available medic and severity of the wound (both factors are weighted together);
- modified wound detection. Now it is based on hitPoints damage, not overall damage (so limping due to fall from above should trigger a medic);

Currently damage threshold is set here:

_wounded = _units select {((((getAllHitPointsDamage _x) select 2) findIf {_x > 0.1}) >= 0)};

- added an exception for medics engaged in combat. Reliable combat detection isn't that simple to do (unclear and not always measurable criteria),

so this is experimental, comes with a global var parameter:

RYD_SM_EnemyCloseDistance = 100;//if medics must move to the wounded, but he is engaged in combat with some enemy being closer than this value, medic

is considered not available and will interrupt pending movement

So in practice, medic currently in fight with a close enough enemy unit should not aid wounded comrades. If not desired, reduce or set this parameter to 0. 

- slightly changed healing wounded in vehicles code. 

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

Can it be a mod for vanilla too ? if so , how to install it ? thanks

Share this post


Link to post
Share on other sites

Hi, welcome to BI forums!

A mod for vanilla no, a mod format is a bit more complicated to make, with the script its just a matter of putting the script

in your mission folder and using the init scripts to enable the function.

    Have you installed the script in your mission and tested it out or are you having trouble doing that?

Watch the video on the OP it will demonstrate and show how to install and setup the script, as well as the written instructions there if your not sure.

Share this post


Link to post
Share on other sites

The only constructive criticism I can give one this. Maybe prioritize killing threats before having AI medic moving to heal. If it does and i missed it ignore that criticism, I'm an idiot. lol Usually if they don't take out the threat in combat they get killed. Kind of like putting your breather on in a plane before you put it on your child. Meaning if you incapacitated you are no use to others.  Other than that. AWESOME SCRIPT as usual. 🙂

  • Like 1

Share this post


Link to post
Share on other sites

Ideally its best to have ai medic under your command, and you can order them to hold position, but in the script is the following:

RYD_SM_EnemyCloseDistance = 100;//if medics must move to the wounded, but he is engaged in combat with some enemy being closer than this value, medic is

considered not available and will interrupt pending movement.

 

I agree though prioritize the enemy threat, and determine from there when and where its safe for a medic to treat any wounded.

  • Like 1

Share this post


Link to post
Share on other sites

Hi, Gunter and thread,

 

I hope its ok but I edited yours' and Rydigers script. I used an Eventhandle rather than a while loop, and I made the script for all units instead of just players group. I also added self healing if firstaid is available and other units with firstaid will also be considered to heal wounded should no medics be found. I put a delay in for how quick medics, self healing, and other units with firstaid to respond to wounded unit. If this is not ok please delete this post. Here is what I did.

 

Spoiler

[] spawn
{
    RYD_SM_EnemyCloseDistance = 100; // Threshold distance to determine if a medic is in combat.

    waitUntil
    {
        sleep 1;
        alive player // Wait until the player is alive.
    };

    RYD_SM_HealCheck =
    {
        params ["_unit", "_medics"];

        // Perform a check only once every 10 seconds.
        if ((time - (_unit getVariable ["RYD_SM_LastCheck", 0])) < 10) exitWith {};
        _unit setVariable ["RYD_SM_LastCheck", time];
        // Creating a random delay
        sleep (round (5 + random 10));
        // Exit if the unit is dead or already being treated.
        if !(alive _unit) exitWith {};
        if (alive (_unit getVariable ["RYD_SM_HealedBy", objNull])) exitWith {};

        // Check if unit is self-healing.
        if (_unit in _medics) exitWith
        {
            _unit setVariable ["RYD_SM_Busy", true];
            _unit setVariable ["RYD_SM_HealedBy", _unit];

            [_unit, vehicle _unit] spawn
            {
                params ["_unit", "_vehicleUnit"];
                _unit action ["HealSoldierSelf", _unit];
                // Wait for healing animation to complete or timeout after 5 seconds.
                waitUntil
                {
                    sleep 1;
                    !alive _unit || (((toLower animationState _unit) find "medic") == -1)
                };

                // Reset busy and healing variables.
                _unit setVariable ["RYD_SM_HealedBy", nil];
                _unit setVariable ["RYD_SM_Busy", nil];

                // Fully heal the unit.
                if (alive _unit) then { _unit setDamage 0 };
            };
        };

        // Filter valid medics by distance (within 500 meters).
        _medics = _medics select {(_x distance _unit) < 500};
        if ((count _medics) < 1) exitWith {}; // Exit if no valid medics are available.

        // Sort medics by distance and select the closest one.
        _medics = _medics apply {[_x distance _unit, _x]};
        _medics sort true;
        _medic = (_medics select 0) select 1;

        _vehicleUnit = vehicle _unit;
        _vehicleMedic = vehicle _medic;
        _sameVehicle = _vehicleUnit isEqualTo _vehicleMedic;

        // Exit if the unit is moving too fast or in the air.
        if (!(_sameVehicle) && {((abs (speed _vehicleUnit)) > 10) || !isTouchingGround _vehicleUnit}) exitWith {};

        _unit setVariable ["RYD_SM_HealedBy", _medic];
        _medic setVariable ["RYD_SM_Busy", true];

        // Handle medic moving to the wounded unit if not in the same vehicle.
        [_unit, _medic, _vehicleUnit] spawn
        {
            params ["_unit", "_medic", "_vehicleUnit"];
            _medic doMove (getPosATL _unit);

            waitUntil
            {
                sleep 1;
                !alive _unit || !alive _medic || ((_medic distance _unit) <= 2.5)
            };

            if (!(alive _unit && alive _medic)) exitWith
            {
                _unit setVariable ["RYD_SM_HealedBy", nil];
                _medic setVariable ["RYD_SM_Busy", nil];
            };

            _medic action ["HealSoldier", _unit];

            // Wait for healing animation to complete.
            waitUntil
            {
                sleep 1;
                !alive _medic || (((toLower animationState _medic) find "medic") == -1)
            };

            // Reset busy and healing variables and fully heal the unit.
            _unit setVariable ["RYD_SM_HealedBy", nil];
            _medic setVariable ["RYD_SM_Busy", nil];
            if (alive _unit) then { _unit setDamage 0 };
        };
    };
    RYD_fnc_wounded = {
        params ["_unit"];

        // Consider all units on the map, excluding dead ones.
        _units = allUnits select {alive _x};

        // Filter available medics.
        _medics = _units select
        {
            (unitReady _x) &&
            !(isPlayer _x) &&
            (_x getUnitTrait "Medic") &&
            !(alive (getAttackTarget _x) && {(_x distance (getAttackTarget _x)) < RYD_SM_EnemyCloseDistance})
        };

        if ((count _medics) > 0) then
            {
            _medics = _medics select {not (_x getVariable ["RYD_SM_Busy", false])};

            // Assign medics to wounded units.// Current wounded unit being processed.
            _friendlyMedics = _medics select {(side _x) getFriend (side _unit) > 0.6 };

            if ((count _friendlyMedics) > 0) then
            {
                [_unit, _friendlyMedics] call RYD_SM_HealCheck;
            } else {
                // Self-healing condition updated to properly check for medpacks.
                if (((items _unit) findIf {_x isEqualTo "FirstAidKit"} >= 0) && {!(alive (getAttackTarget _unit)) || (_unit distance (getAttackTarget _unit)) > RYD_SM_EnemyCloseDistance}) then
                {
                    // Unit heals itself.
                    _unit setVariable ["RYD_SM_Busy", true];
                    _unit setVariable ["RYD_SM_HealedBy", _unit];

                    [_unit] spawn
                    {
                        params ["_unit"];
                        sleep (round (5 + random 10));
                        _unit action ["HealSoldierSelf", _unit];

                        waitUntil
                        {
                            sleep 1;
                            !alive _unit || (((toLower animationState _unit) find "medic") == -1);
                        };
                        _unit removeItem "FirstAidKit";
                        _unit setVariable ["RYD_SM_HealedBy", nil];
                        _unit setVariable ["RYD_SM_Busy", nil];
                        if (alive _unit) then { _unit setDamage 0.25 };
                    };

                };

                // Adding nearby units with medpacks to the medic pool.
                private _nearbyUnits = (_unit nearEntities ["Man", 20]) select
                {
                    alive _x && ((items _x) findIf {_x isEqualTo "FirstAidKit"} >= 0);
                };
                _medics append _nearbyUnits;
                _medics = _medics select {not (_x getVariable ["RYD_SM_Busy", false])};
                _friendlyMedics = _medics select {(side _x) getFriend (side _unit) > 0.6};
                [_unit, _friendlyMedics] call RYD_SM_HealCheck;
            };
        };
    };
    // Main EventHandler to check for Medics, medpacks, units with medpacks to heal unit thats been damaged.
    {
        _x addEventHandler ["HandleDamage", {
            params ["_unit", "_selection", "_damage", "_source", "_projectile"];

            // Only process if unit is wounded
            if ((((getAllHitPointsDamage _unit) select 2) findIf {_x > 0.1}) >= 0) then {
                _unit spawn RYD_fnc_wounded;
            };

            // Return the damage value
            _damage;
        }];
    } forEach allUnits;
};

 

 

Edited by mikey74
More info that i changed
  • Like 2

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×