Jump to content
Rydygier

Making helicopter shoot at area from stationary hovering position

Recommended Posts

Title explains, what I try to achieve - thing like this. Shooting straight ahead seems to work (tested with code from BI CAS module used for planes, also works with forceWeaponFire), worse with targeting at ground targets - here I encountered few issues. 

 

Heli ignores/will not change direction/pitch/aiming at doWatch/doTarget with or without target reveal (apllied to the heli, heli's gunner and/or driver), target may be allied or hostile unit, whatever. It doesn't even turn minigun turret at the target. Ignores also setFormDir.

 

Said CAS module uses also spawned laser target object. But as soon I spawn it, heli starts to spin around:

 

 

BTW Similar thing happens after using BIS_fnc_fire (action "UseMagazine" way), but some shots are fired then.

 

Also doSuppressiveFire seems not work with the heli (pity, would be optimal for my need). If applied with position or object, heli will not shoot at it, instead will start doing flybys, like in normal attack pattern until reach some position or indefinitely. 

 

I would like to avoid using enforced ways, like setDir, addTorque or setVelocityTransformation, rather to base on AI than fighting with it. Still it may be unavoidable, since to shoot from fixed weaponry at ground level target, heli needs to dive a bit, which without invisible "helping hand" means flying forward (could be countered with setVelocity). Anyway, tested setVelocityTransformation, with partial success. This code:

 

Spoiler

	myPos = getPosASL _gun;
	vDir = vectorDir _gun;
	tPos = ATLtoASL _tPos;
	tVec = myPos vectorFromTo tPos;
	
	t1 = time; 
	t2 = time + 2;
	_id = addMissionEventHandler 
		[
		"EachFrame", 
		
			{
			v1 setVelocityTransformation 
				[
				myPos, 
				myPos, 
				[0,0,0], 
				[0,0,0], 
				vDir, 
				tVec, 
				[0,0,1], 
				[0,0,1],
				(linearConversion [t1, t2, time, 0, 1])
				];
			}
		];

 

 

makes the heli turning in right direction, but the turn is too wide (why?). This from the other hand does the trick:

 

Spoiler

	myPos = getPosASL _gun;
	vDir = vectorDir _gun;
	tPos = ATLtoASL _tPos;
	tVec = myPos vectorFromTo tPos;
	
	t1 = time; 
	t2 = time + 2;
	_id = addMissionEventHandler 
		[
		"EachFrame", 
		
			{
			v1 setVelocityTransformation 
				[
				myPos, 
				myPos, 
				[0,0,0], 
				[0,0,0], 
				(vectorDir v1), 
				tVec, 
				[0,0,1], 
				[0,0,1],
				(linearConversion [t1, t2, time, 0, 1])
				];
			}
		];

 

 

but faster, than in 2 seconds and after few more seconds heli starts very rapidly change its vector, like crazy (why?). I have no experience with setVelocityTransformation, so I might misuse it somehow. (_gun/v1 is the heli, _tPos) - target position.

 

I would appreciate any insight. 

  • Like 2

Share this post


Link to post
Share on other sites

Personally I'd rather avoid any such scripting. The ai will be highly confused and even if you make it work somehow, it'll just end up failing half the time.

 

If you need this for a singleplayer mission, it's probably better to just record a manual flight path and use this instead.

Share this post


Link to post
Share on other sites

I agree, still, I want to try, what can be done. It is not for a specific situation for a specific scenario. It should work each time for any heli, that flies somewhere by waypoint, peforms such kind of attack, then goes back. I noticed, after setVelocityTransformation AI rather behaves normally, so there's a hope. 

Share this post


Link to post
Share on other sites
3 hours ago, Rydygier said:

I agree, still, I want to try, what can be done.

As long as AI behavior will override most script commands (try to turn on vehicle lights at night outside of "SAFE" AI behavior etc.) and other AI components remain inaccessible through scripting, it's not really worth investing the time.

 

Cheers

Share this post


Link to post
Share on other sites

Yeah, I know the charms of AI and in general I would agree.

 

But I feel almost there. If not via using AI, I can at least make heli shoot despite AI moods, it works, and I nearly can make him aim, where I want via setVelocityTransformation. The only blocking me issues at this point are described unwelcomed side effects of my implementation of setVelocityTransformation, I pasted. Perhaps someone more experienced with this command could tell me, how to fix this, I'll try myself too. 

 

First snippet issue (too wide rotation) looks like this:

 

 

Second snippet, most promising, issue (crazy vector changes) like this:

 

 

Share this post


Link to post
Share on other sites

After some tests and analyze found such solution:

 

Spoiler

	myPos = getPosASL _gun;
	vDir = vectorDir _gun;
	tPos = ATLtoASL _tPos;
	tVec = myPos vectorFromTo tPos;
	tV0 = tVec select 0;
	tV1 = tVec select 1;
	tV2 = tVec select 2;
	
	t1 = time; 
	t2 = time + 5;

	_id = addMissionEventHandler 
		[
		"EachFrame", 
		
			{
			_actVec = vectorDir v1;
			if ((abs((_actVec select 0) - tV0)) < 0.001) then
				{
				_actVec set [0,tV0]
				};			

			if ((abs((_actVec select 1) - tV1)) < 0.001) then
				{
				_actVec set [1,tV1]
				};				
			
			if ((abs((_actVec select 2) - tV2)) < 0.001) then
				{
				_actVec set [2,tV2]
				};
							 
			v1 setVelocityTransformation 
				[
				myPos, 
				myPos, 
				[0,0,0], 
				[0,0,0], 
				_actVec, 
				tVec, 
				[0,0,1], 
				[0,0,1],
				(linearConversion [t1, t2, time, 0, 1])
				];
			}
		];
		
	sleep 2;
	
	_fire = [_gun,_weapons,_target,0] spawn //part based on BIS_fnc_moduleCAS
		{
		_plane = _this select 0;
		_planeDriver = gunner _plane;
		_weapons = _this select 1;
		_target = _this select 2;
		_weaponTypesID = _this select 3;
		_duration = 8;
		_time = time + _duration;
		waituntil 
			{
				{
				//_plane selectweapon (_x select 0);
				//_planeDriver forceweaponfire _x;
				_planeDriver fireattarget [_target,(_x select 0)];
				} 
			foreach _weapons;
			sleep 0.1;
			time > _time || _weaponTypesID == 3 || isnull _plane
			};
		};
	
	sleep 8.5;
	hint "end";
	removeMissionEventHandler ["EachFrame",_id];

 

 

Crucial fix lines:

 

			_actVec = vectorDir v1;
			if ((abs((_actVec select 0) - tV0)) < 0.001) then
				{
				_actVec set [0,tV0]
				};			

			if ((abs((_actVec select 1) - tV1)) < 0.001) then
				{
				_actVec set [1,tV1]
				};				
			
			if ((abs((_actVec select 2) - tV2)) < 0.001) then
				{
				_actVec set [2,tV2]
				};

Basically, after analyzing exact vectorDir values each frame found out, there are very tiny differences, where shouldn't be any. These differences was probably accumulating in time or something like that. Above lines stabilize vector values - counters the change if it is too small to consider it intentional. Feels like makeshift workaround (because it is) but works:

 

 

So, after all, it is possible to make AI heli to shoot at area from hovering position. But that was just preliminary step, far from desired final solution yet. 

  • Like 5

Share this post


Link to post
Share on other sites
4 hours ago, Rydygier said:

So, after all, it is possible to make AI heli to shoot at area from hovering position. But that was just preliminary step, far from desired final solution yet.

Way to be persistent Rydgier.  Please post your final solution (when you get it) as I can see this being very useful for others (helis defending an LZ, a cutscene, etc.).

  • Like 2

Share this post


Link to post
Share on other sites

Works out neat, if a bit robotical.

Why waste a single bullet when you can throw ordnance worth 5digits+ on a single unit, heh...

 

Cheers

  • Like 1
  • Haha 1

Share this post


Link to post
Share on other sites
Quote

a bit robotical

 

Sadly. That's why it was plan "B".

 

Quote

Why waste a single bullet when you can throw ordnance worth 5digits+ on a single unit

 

Exactly!

 

Quote

Please post your final solution (when you get it)

 

Sure thing, I'm working on it. 

  • Like 2

Share this post


Link to post
Share on other sites

It took a while, but I feel, it's ready enough now, also to consider adding it into HAS mod (that was the intent). Here's the code:

 

Spoiler

RYD_HAS_ManualTarget = 
	{
	params ["_veh","_tPos"];
	
	RYD_HAS_EF_veh = _veh;
	RYD_HAS_EF_myPos = _veh getVariable ["RYD_HAS_myFiringPos",(getPosASL _veh)];
	_veh setVariable ["RYD_HAS_myFiringPos",RYD_HAS_EF_myPos];
	RYD_HAS_EF_tVec = RYD_HAS_EF_myPos vectorFromTo ATLtoASL _tPos;
	RYD_HAS_EF_tV0 = RYD_HAS_EF_tVec select 0;
	RYD_HAS_EF_tV1 = RYD_HAS_EF_tVec select 1;
	RYD_HAS_EF_tV2 = RYD_HAS_EF_tVec select 2;
	
	RYD_HAS_EF_t1 = time; 
	RYD_HAS_EF_t2 = RYD_HAS_EF_t1 + 5;

	private _id = addMissionEventHandler 
		[
		"EachFrame", 
		
			{
			if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {};
			if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {};
			_actVec = vectorDir RYD_HAS_EF_veh;
			if ((abs((_actVec select 0) - RYD_HAS_EF_tV0)) < 0.001) then
				{
				_actVec set [0,RYD_HAS_EF_tV0]
				};			

			if ((abs((_actVec select 1) - RYD_HAS_EF_tV1)) < 0.001) then
				{
				_actVec set [1,RYD_HAS_EF_tV1]
				};				
			
			if ((abs((_actVec select 2) - RYD_HAS_EF_tV2)) < 0.001) then
				{
				_actVec set [2,RYD_HAS_EF_tV2]
				};
							 
			RYD_HAS_EF_veh setVelocityTransformation 
				[
				RYD_HAS_EF_myPos, 
				RYD_HAS_EF_myPos, 
				[0,0,0], 
				[0,0,0], 
				_actVec, 
				RYD_HAS_EF_tVec, 
				[0,0,1], 
				[0,0,1],
				(linearConversion [RYD_HAS_EF_t1, RYD_HAS_EF_t2, time, 0, 1])
				];
			}
		];
		
	waitUntil
		{
		_vc1 = vectorDir _veh;
		sleep 0.1;
		if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {_alive = false;true};
		if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {_cancelled = true;true};
		
		_vc2 = vectorDir _veh;
		
		((_vc1 distance _vc2) < 0.01)
		};
		
	if (not (_alive) or {_cancelled}) exitWith {-1};

	_id
	};

RYD_HAS_Fire = 
	{
	params ["_veh","_tPos","_weaponsAll","_duration","_targets","_homing","_modeAI"];
	
	private _weaponsHoming = _weaponsAll select 0;
	private _weapons = _weaponsAll select 1;
	
	if (((count _weaponsHoming) < 1) and {((count _weapons) < 1)}) exitWith {};
	
	if (_modeAI) then//not working
		{
		_allTargets = _homing + (_targets - _homing);
		_gp = group _veh;
		_gunner = gunner _veh;
		if (isNull _gunner) then
			{
			_gunner = driver _veh
			};
			
		_lTgts = [];
		_targetType = if (((side _veh) getFriend west) > 0.6) then {"LaserTargetW"} else {"LaserTargetE"};
			
			{
			_tPos = getPosATL _x;
			_tPos set [2,1];
			_target = createvehicle [_targetType,_tPos,[],0,"CAN_COLLIDE"];
			_veh reveal _target;
			_gunner reveal _target;
			_lTgts pushBack _target
			}
		foreach _homing;
		
		_gp setCombatMode "YELLOW";
		
			{
			_veh reveal _x;
			_gunner reveal _x;
			}
		foreach _allTargets;
		
			{
			_veh doTarget _x;
			_gunner doTarget _x;
			
			sleep _duration
			}
		foreach _lTgts;

		sleep (_duration * ((count _allTargets) - (count _lTgts)));
		
		_gp setCombatMode "BLUE";
		
			{
			deleteVehicle _x
			}
		foreach _lTgts;
		
			{
			_veh forgetTarget _x;
			_gunner forgetTarget _x;
			}
		foreach _allTargets;
		}
	else
		{
		private _id = [_veh,_tPos] call RYD_HAS_ManualTarget;	
		if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {_alive = false;removeMissionEventHandler ["EachFrame",_id];};
		if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {_cancelled = true;removeMissionEventHandler ["EachFrame",_id];};
		
		[_veh,_weaponsAll,_duration,_targets,_homing,_id,_tPos] call RYD_HAS_doFire;

		_veh setVariable ["RYD_HAS_myFiringPos",nil];
		}
	};
	
RYD_HAS_doFire = 
	{
	private ["_tme"];
	params ["_veh","_weaponsAll","_duration","_targets","_homing","_id","_tPos"];

	private _weaponsH = _weaponsAll select 0;
	private _weapons = _weaponsAll select 1;
	if (((count _weaponsH) < 1) and {((count _weapons) < 1)}) exitWith {};
	private _gunner = gunner _veh;
	if (isNull _gunner) then
		{
		_gunner = driver _veh
		};

	private _gp = group _veh;
	private _vehPos = getPosATL _veh;
	RYD_HAS_noAmmoWeapons = [];
	
	if ((({(alive _x)} count _homing) == 0) and {(({(alive _x)} count _targets) == 0)}) then
		{
		_hasAmmo = ({not ((_x select 0) in RYD_HAS_noAmmoWeapons)} count _weapons) > 0;
		if not (_hasAmmo) exitWith {};
		_tme = time + _duration;
		
		waituntil 
			{
				{
				_tPos set [2,1];
				_weap = _x select 0;
				_cm = currentMuzzle _gunner;
				if not (_weap in RYD_HAS_noAmmoWeapons) then
					{														
					_mags = getArray (configFile >> "CfgWeapons" >> (_x select 0) >> "magazines");
					_vehMags = magazines _veh;
					if not ((typeName _cm) isEqualTo (typeName "")) then
						{
						_cm = currentWeapon _veh
						};
						
					_ammo = _veh ammo _cm;
					_ihr = 1;
					
					if ((_ammo < 1) and {(({(_x in _vehMags)} count _mags) < 1)}) then
						{
						RYD_HAS_noAmmoWeapons pushBack _weap
						}
					else
						{
						if (isArray (configfile >> "CfgWeapons" >> _weap >> "magazines")) then
							{
							_mags = (getArray (configfile >> "CfgWeapons" >> _weap >> "magazines"));
							if ((count _mags) > 0) then
								{
								_mag = _mags select 0;
								
								if (isText (configfile >> "CfgMagazines" >> _mag >> "simulation")) then
									{
									_ammoC = getText (configfile >> "CfgMagazines" >> _mag >> "ammo");
									_sim = configFile >> "CfgAmmo" >> _ammoC >> "simulation";
									_ihrC = configFile >> "CfgAmmo" >> _ammoC >> "indirectHitRange";
									if (isNumber _ihrC) then
										{
										_ihr = (floor (getNumber _ihrC)) max 1
										};
									
									if not ((isText _sim) and {((toLower (getText _sim)) in ["shotmissile","shotrocket"])}) then
										{
										_dst = _veh distance _tPos;
										_addLvl = _dst/100;
										_ts = configFile >> "CfgAmmo" >> _ammoC >> "typicalSpeed";
										if (isNumber _ts) then
											{
											_ts = getNumber _ts;
											_elev = (aSin (9.81 * _dst/(_ts^2)))/2;
											_elev = _elev min (90 - _elev);
											_addLvl = _dst * (sin _elev);
											};
											
										_tPos set [2,((1 * 1000/_dst) + _addLvl)];
										//a2 setPosATL _tPos;
										};
									};
								};
							};		
							
						_tPos2 = +_tPos;
						_tPos2 set [2,1];
						if not (terrainIntersect [_vehPos,_tPos2]) then
							{							
							removeMissionEventHandler ["EachFrame",_id];
							_id = [_veh,_tPos] call RYD_HAS_ManualTarget;
							for "_i" from 1 to ((round(((_ammo/10) min 10)/_ihr)) max 1) do
								{
								_gunner fireAtTarget [_veh,_weap];
								sleep 0.1;
								if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {_alive = false};
								if ((isNull _gunner) or {not (alive _gunner)}) exitWith {_alive = false};
								if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {_cancelled = true;};
								if ((_veh ammo _cm) < 1) exitWith {RYD_HAS_noAmmoWeapons pushBack _weap};
								};
							}
						}
					};
					
				if (not (_alive) or {_cancelled}) exitWith {};
				if ((_veh ammo _cm) < 1) exitWith {};
				} 
			foreach _weapons;
			
			if (not (_alive) or {_cancelled}) exitWith {true};
			_hasAmmo = ({not ((_x select 0) in RYD_HAS_noAmmoWeapons)} count _weapons) > 0;
				
			((time > _tme) or {not (_hasAmmo)})
			};		
		};
	
	if ((({(alive _x)} count _homing) > 0) and {((count _weaponsH) > 0)}) then
		{
		private _fEH = _veh addEventHandler ["Fired",{RYD_HAS_Done = true;RYD_HAS_Proj = (_this select 6);}];
		
		while {(({(not(isNull _x) and {(alive _x)})} count _homing) > 0)} do
			{
			_hasAmmo = ({not ((_x select 0) in RYD_HAS_noAmmoWeapons)} count _weaponsH) > 0;
			if not (_hasAmmo) exitWith {};
			
				{
				_wh = _x;
				_weap = _wh select 0;
				if not (_weap in RYD_HAS_noAmmoWeapons) then
					{
					_mags = getArray (configFile >> "CfgWeapons" >> _weap >> "magazines");
					_vehMags = magazines _veh;
					_ammo = _veh ammo (currentMuzzle _gunner);
					if ((_ammo < 1) and {(({(_x in _vehMags)} count _mags) < 1)}) then
						{
						RYD_HAS_noAmmoWeapons pushBack _weap
						}
					else
						{
							{
							_ix = _foreachindex;
							if (alive _x) then
								{
								_cw = currentWeapon _veh;
								
								RYD_HAS_Proj = objNull;
								_tPos = getPosATL _x;
								_tPos set [2,1.5];
								
								_tPos2 = +_tPos;
								_tPos2 set [2,1];
								if not (terrainIntersect [_vehPos,_tPos2]) then
									{
									_gp setCombatMode "YELLOW";
									_veh reveal _x;
									_gunner reveal _x;
									_veh doTarget _x;
									_gunner doTarget _x;

								
									removeMissionEventHandler ["EachFrame",_id];
									_id = [_veh,_tPos] call RYD_HAS_ManualTarget;
									
									RYD_HAS_Done = false;
									_tme = time;

									_targetType = if (((side _veh) getFriend west) > 0.6) then {"LaserTargetW"} else {"LaserTargetE"};
									_target = createvehicle [_targetType,_tPos,[],0,"CAN_COLLIDE"];
									sleep 0.1;
									waitUntil
										{															
										_mags = getArray (configFile >> "CfgWeapons" >> _weap >> "magazines");
										_vehMags = magazines _veh;
										_ammo = _veh ammo (currentMuzzle _gunner);
										if ((_ammo < 1) and {(({(_x in _vehMags)} count _mags) < 1)}) then
											{
											RYD_HAS_noAmmoWeapons pushBack _weap
											}
										else
											{
											_gunner fireAtTarget [_target,_weap];
											sleep 0.5;
											if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {_alive = false};
											if ((isNull _gunner) or {not (alive _gunner)}) exitWith {_alive = false};
											if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {_cancelled = true;};
											};

										if (not (_alive) or {_cancelled}) exitWith {true};
										_hasAmmo = ({not ((_x select 0) in RYD_HAS_noAmmoWeapons)} count _weaponsH) > 0;
										
										((RYD_HAS_Done) or {((time - _tme) > 15) or {not (_hasAmmo)}})
										};
									
									if (not (_alive) or {_cancelled}) exitWith {true};
									_gp setCombatMode "BLUE";
									
									waitUntil
										{
										sleep 0.1;
										if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {_alive = false;true};
										if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {_cancelled = true;true};
										
										(isNull RYD_HAS_Proj)
										};
									
									deleteVehicle _target;
									if (not (_alive) or {_cancelled}) exitWith {} 
									}
								else
									{
									_homing set [_ix,objNull];
									}
								}
							}
						foreach _homing;
						if (not (_alive) or {_cancelled}) exitWith {};
						
						_homing = _homing - [objNull];
						}
					};
					
				if (not (_alive) or {_cancelled}) exitWith {};
				}
			foreach _weaponsH;
			
			if (not (_alive) or {_cancelled}) exitWith {};
			};
		
		_veh removeEventHandler ["Fired",_fEH];

		if (not (_alive) or {_cancelled}) exitWith {};
		_veh doWatch objNull;
		_gunner doWatch objNull;		
		};

	if ((({(alive _x)} count _targets) > 0) and {((count _weapons) > 0)}) then
		{
		_tol = 0;
		while {(({(not(isNull _x) and {(alive _x)})} count _targets) > 0)} do
			{
			_hasAmmo = ({not ((_x select 0) in RYD_HAS_noAmmoWeapons)} count _weapons) > 0;
			if not (_hasAmmo) exitWith {};
			
				{
				_tgt = _x;
				_ix = _foreachIndex;
				if (alive _x) then
					{
					_tPos = getPosATL _x;
					_tPos set [2,1];		
					
					_tme = time + _duration;
					
					waituntil 
						{
							{
							_weap = _x select 0;
							_cm = currentMuzzle _gunner;
							if not (_weap in RYD_HAS_noAmmoWeapons) then
								{									
								_mags = getArray (configFile >> "CfgWeapons" >> (_x select 0) >> "magazines");
								_vehMags = magazines _veh;
								
								if not ((typeName _cm) isEqualTo (typeName "")) then
									{
									_cm = currentWeapon _veh
									};

								_ammo = _veh ammo _cm;
								_ihr = 1;

								if ((_ammo < 1) and {(({(_x in _vehMags)} count _mags) < 1)}) then
									{
									RYD_HAS_noAmmoWeapons pushBack _weap
									}
								else
									{
									if (isArray (configfile >> "CfgWeapons" >> _weap >> "magazines")) then
										{
										_mags = (getArray (configfile >> "CfgWeapons" >> _weap >> "magazines"));
										if ((count _mags) > 0) then
											{
											_mag = _mags select 0;
											
											if (isText (configfile >> "CfgMagazines" >> _mag >> "simulation")) then
												{
												_ammoC = getText (configfile >> "CfgMagazines" >> _mag >> "ammo");
												_sim = configFile >> "CfgAmmo" >> _ammoC >> "simulation";
												_ihrC = configFile >> "CfgAmmo" >> _ammoC >> "indirectHitRange";
												if (isNumber _ihrC) then
													{
													_ihr = (floor (getNumber _ihrC)) max 1
													};
												
												if not ((isText _sim) and {((toLower (getText _sim)) in ["shotmissile","shotrocket"])}) then
													{
													_addLvl = (_veh distance _tPos)/100;
													_dst = _veh distance _tPos;
													_addLvl = _dst/100;
													_ts = configFile >> "CfgAmmo" >> _ammoC >> "typicalSpeed";
													if (isNumber _ts) then
														{
														_ts = getNumber _ts;
														_elev = (aSin (9.81 * _dst/(_ts^2)))/2;
														_elev = _elev min (90 - _elev);
														_addLvl = _dst * (sin _elev);
														};
														
													_tPos set [2,((1 * 1000/_dst) + _addLvl)];
													};
												};
											};
										};			
									
									_tPos2 = +_tPos;
									_tPos2 set [2,1];
									if not (terrainIntersect [_vehPos,_tPos2]) then
										{
										removeMissionEventHandler ["EachFrame",_id];
										_id = [_veh,_tPos] call RYD_HAS_ManualTarget;
										for "_i" from 1 to ((round(((_ammo/10) min 10)/_ihr)) max 1) do
											{
											_gunner fireAtTarget [_tgt,_weap];
											sleep 0.1;
											if ((isNull RYD_HAS_Chopper) or {not (alive RYD_HAS_Chopper) or {(isNull (driver RYD_HAS_Chopper)) or {not (alive (driver RYD_HAS_Chopper))}}}) exitwith {_alive = false};
											if ((isNull _gunner) or {not (alive _gunner)}) exitWith {_alive = false};
											if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage RYD_HAS_Chopper) select 2)) > 0)}) exitWith {_cancelled = true;};
											if ((isNull _tgt) or {not (alive _tgt)}) exitWith {_targets set [_ix,objNull];};
											if ((_tgt distance2D _tPos) > 5) exitWith {};
											if ((_veh ammo _cm) < 1) exitWith {RYD_HAS_noAmmoWeapons pushBack _weap};
											}
										}
									else
										{
										_targets set [_ix,objNull];
										_tme = 0;
										};
									}
								};
								
							if (not (_alive) or {_cancelled}) exitWith {};
							if ((isNull _tgt) or {not (alive _tgt)}) exitWith {_tme = 0;};
							if ((_tgt distance2D _tPos) > 5) exitWith {_tme = 0;};
							if ((_veh ammo _cm) < 1) exitWith {};
							} 
						foreach _weapons;
						
						if (not (_alive) or {_cancelled}) exitWith {true};
						_hasAmmo = ({not ((_x select 0) in RYD_HAS_noAmmoWeapons)} count _weapons) > 0;					
							
						((time > _tme) or {not (_hasAmmo)})					
						};
					};
					
				if (not (_alive) or {(_cancelled) or {not (_hasAmmo)}}) exitWith {};
				}
			foreach _targets;
			
			if (not (_alive) or {(_cancelled) or {not (_hasAmmo)}}) exitWith {};
			
			_cnt1 = count _targets;
			_targets = _targets - [objNull];
			_cnt2 = count _targets;
						
			if (_cnt1 == _cnt2) then 
				{
				_tol = _tol + 1 + ([_veh] call RYD_HAS_ActDamageSum)
				};
				
			if ((random _tol) > (random _cnt2)) exitWith {};
			};
		};
		
	removeMissionEventHandler ["EachFrame",_id];
	};
	
RYD_HAS_TakeWeapons = 
	{
	private ["_modes","_mode","_isHoming","_mags","_mag","_ammo","_airLock","_irLock","_laserLock","_nvLock"];
	params ["_veh"];
	
	private _weaponsHoming = [];
	private _weapons = [];
	
		{
		_modes = getarray (configfile >> "cfgweapons" >> _x >> "modes");
		if (count _modes > 0) then 
			{
			_mode = _modes select 0;
			if (_mode == "this") then {_mode = _x;};
			
			_isHoming = false;
			_airLock = 0;
			
			if (isArray (configfile >> "CfgWeapons" >> _x >> "magazines")) then
				{
				_mags = (getArray (configfile >> "CfgWeapons" >> _x >> "magazines"));
				if ((count _mags) > 0) then
					{
					_mag = _mags select 0;
					
					if (isText (configfile >> "CfgMagazines" >> _mag >> "ammo")) then
						{
						_ammo = getText (configfile >> "CfgMagazines" >> _mag >> "ammo");
						_irLock = configFile >> "CfgAmmo" >> _ammo >> "irLock";
						_laserLock = configFile >> "CfgAmmo" >> _ammo >> "laserLock";
						_nvLock = configFile >> "CfgAmmo" >> _ammo >> "nvLock";
						_airLock = getNumber (configFile >> "CfgAmmo" >> _ammo >> "airLock");
						
						_isHoming = (({(not (isNumber _x) or {((getNumber _x) < 1)})} count [_irLock,_laserLock,_nvLock]) < 3) and {(_airLock < 1)};
						};
					};
				};
			
			if (_isHoming) then
				{
				_weaponsHoming pushBack [_x,_mode];
				}
			else
				{
				if (_airLock < 2) then
					{
					_weapons pushBack [_x,_mode];
					}
				}
			};
		} 
	foreach ((typeOf _veh) call bis_fnc_weaponsEntityType);
	
	[_weaponsHoming,_weapons]
	};
	
RYD_HAS_ActDamageSum = 
	{
	params ["_veh"];
	
	private _sum = 0;
	
		{
		_sum = _sum + _x;
		}
	foreach ((getAllHitPointsDamage RYD_HAS_Chopper) select 2);	
	
	_sum
	};
	
RYD_HAS_isGunship = 
	{
	private ["_mags","_mag","_ammoC","_sim"];
	params ["_heli"];
	
	private _weaponsAll = [_heli] call RYD_HAS_TakeWeapons;	
	private _isGunship = false;

		{
		if (isArray (configfile >> "CfgWeapons" >> (_x select 0) >> "magazines")) then
			{
			_mags = (getArray (configfile >> "CfgWeapons" >> (_x select 0) >> "magazines"));
			if ((count _mags) > 0) then
				{
				_mag = _mags select 0;
				
				if (isText (configfile >> "CfgMagazines" >> _mag >> "simulation")) then
					{
					_ammoC = getText (configfile >> "CfgMagazines" >> _mag >> "ammo");
					_sim = configFile >> "CfgAmmo" >> _ammoC >> "simulation";
					
					_isGunship = (isText _sim) and {((toLower (getText _sim)) in ["shotmissile","shotrocket"])};
					};
				};
			};
			
		if (_isGunship) exitWith {};
		}
	foreach ((_weaponsAll select 0) + (_weaponsAll select 1));
	
	_isGunship
	};

RYD_HAS_CAS = 
	{
	params ["_heli","_tPos","_obs","_CASDst","_range","_onlyKnownTargets"];
	
	if not ([_heli] call RYD_HAS_isGunship) exitWith {};
	
	_obs groupchat "CAS assigned";

	private _i = "CASMark_" + (str _tPos);
	_i = createMarker [_i,_tPos];
	_i setMarkerColor "ColorRed";
	_i setMarkerShape "ICON";
	_i setMarkerType "mil_destroy";
	_i setMarkerSize [1,1];
	
	private _alive = true;
	private _cancelled = false;
	RYD_HAS_wasHit = false;

	private _hG = group _heli;
	private _ldr = leader _hG;

	RYD_HAS_oldBeh = behaviour (leader _hG);
	RYD_HAS_oldCM = combatMode _hG;

	_hG setBehaviour "CARELESS";
	_hG setCombatMode "BLUE";

	RYD_HAS_done = false;
	private _sPos = getPosATL _heli;
	private _CASPos = _tPos getPos [((_CASDst min ((getObjectViewDistance select 0) - _range)) max (_CASDst * 0.6)),(_tPos getDir _sPos)];

	private _wp = _hG addWaypoint [_CASPos,0];	
	_wp setWaypointType "MOVE";
	_wp setWaypointStatements ["true", "RYD_HAS_done = true;deleteWaypoint [(group this), 0];"];	

	private _lvl = 30;
	_heli flyInHeight _lvl;

	waitUntil
		{
		sleep 1;
		if ((isNull _heli) or {not (alive _heli) or {(isNull (driver _heli)) or {not (alive (driver _heli))}}}) exitwith {_alive = false;true};
		if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage _heli) select 2)) > 0)}) exitWith {_cancelled = true;true};
		(RYD_HAS_Done)
		};
		
	if (not (_alive) or {_cancelled}) exitWith {deleteMarker _i};

	_obs groupchat "CAS imminent";

	_tPos set [2,5];	
	private _tB = terrainIntersect [(getPosATL _heli),_tPos];

	while {_tB} do
		{
		_lvl = _lvl + 20;
		_heli flyInHeight _lvl;
		_cPos = getPosATL _heli;
		_cPos set [2,(_cPos select 2) - 10];
		_tB = terrainIntersect [_cPos,_tPos];
		_cLvl = _cPos select 2;
		_tme = time;
		
		waitUntil
			{
			sleep 0.1;
			
			if ((isNull _heli) or {not (alive _heli) or {(isNull (driver _heli)) or {not (alive (driver _heli))}}}) exitwith {_alive = false;true};
			if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage _heli) select 2)) > 0)}) exitWith {_cancelled = true;true};
			
			(((((getPosATL _heli) select 2) - _cLvl) > 15) or {((time - _tme) > 5)})
			};
			
		if (not (_alive) or {_cancelled}) exitWith {};
		};
			
	if not (_alive) exitWith {deleteMarker _i};

	if not (_cancelled) then
		{
		_obs groupchat "CAS begins";
		
		_heli flyInHeight ((getPosATL _heli) select 2);

		private _inf = _tPos nearEntities [["CAManBase"],_range];
		private _air = _tPos nearEntities [["Air"],_range];
		private _soft = _tPos nearEntities [["Car"],_range];
		private _arm = _tPos nearEntities [["Tank"],_range];
		
		if (_onlyKnownTargets) then
			{
				{
				if ((((_ldr knowsAbout _x) max (_obs knowsAbout _x)) < 0.01) or {not (((side _ldr) getFriend (side _x)) < 0.6)}) then
					{
					_inf set [_foreachIndex,objNull]
					}
				}
			foreach _inf;
			_inf = _inf - [objNull];
				
				{
				if ((((_ldr knowsAbout _x) max (_obs knowsAbout _x)) < 0.01) or {not (((side _ldr) getFriend (side _x)) < 0.6)}) then
					{
					_air set [_foreachIndex,objNull]
					}
				}
			foreach _air;
			_air = _air - [objNull];				
					
				{
				if ((((_ldr knowsAbout _x) max (_obs knowsAbout _x)) < 0.01) or {not (((side _ldr) getFriend (side _x)) < 0.6)}) then
					{
					_soft set [_foreachIndex,objNull]
					}
				}
			foreach _soft;
			_soft = _soft - [objNull];				
					
				{
				if ((((_ldr knowsAbout _x) max (_obs knowsAbout _x)) < 0.01) or {not (((side _ldr) getFriend (side _x)) < 0.6)}) then
					{
					_arm set [_foreachIndex,objNull]
					}
				}
			foreach _arm;
			_arm = _arm - [objNull];	
			};

		private _homing = [];
		private _targets = [];

		_homing appEnd _arm;
		_homing appEnd _air;
		_homing appEnd _soft;
				
			{
			_inf appEnd (crew _x)
			}
		foreach _homing;

		_targets appEnd _inf;
		_targets appEnd _air;
		_targets appEnd _soft;
		
		RYD_HAS_myDamage = [_heli] call RYD_HAS_ActDamageSum;

		private _hEH = _heli addEventHandler ["Hit",{RYD_HAS_wasHit = (((([_heli] call RYD_HAS_ActDamageSum) - RYD_HAS_myDamage) > 0.2) or {(({_x == 1} count ((getAllHitPointsDamage _heli) select 2)) > 0) or {not (canMove _heli)}})}];
		
		private _weaponsAll = [_heli] call RYD_HAS_TakeWeapons;
		[_heli,_tPos,_weaponsAll,10,_targets,_homing,false] call RYD_HAS_Fire;
				
		_heli removeEventHandler ["Hit",_hEH];
		if ((isNull _heli) or {not (alive _heli) or {(isNull (driver _heli)) or {not (alive (driver _heli)) or {not (canMove _heli)}}}}) exitWith {};

		_obs groupchat "CAS complete";
		
		deleteMarker _i;
		
		_heli flyInHeight 30;
		private _tme = time;

		waitUntil
			{
			sleep 1;
			
			if ((isNull _heli) or {not (alive _heli) or {(isNull (driver _heli)) or {not (alive (driver _heli))}}}) exitwith {_alive = false;true};
			if ((RYD_HAS_wasHit) and {(({_x > 0.5} count ((getAllHitPointsDamage _heli) select 2)) > 0)}) exitWith {_cancelled = true;true};
			(((((getPosATL _heli) select 2)) < 40) or {((time - _tme) > 10)})
			};
		};
		
	deleteMarker _i;
		
	if ((isNull _heli) or {not (alive _heli) or {(isNull (driver _heli)) or {not (alive (driver _heli)) or {not (canMove _heli)}}}}) exitWith {};

	RYD_HAS_done = false;
	_wp = _hG addWaypoint [_sPos, 0];
	_wp setWaypointType "MOVE";
	_wp setWaypointStatements ["true", "_heli land 'land';(group this) setBehaviour RYD_HAS_oldBeh;(group this) setCombatMode RYD_HAS_oldCM;RYD_HAS_done = true;"];

	_heli flyInHeight 30;
	};
	
	

 

 

 

and simple demo scenario

 

Accuracy seems decent, considering dispersion, although I'm not 100% sure of my ballistic calculations. So far tested with Blackfoot and Pawnee. 

 

Exemplary usage:

 

[RYD_HAS_Chopper,(screenToWorld [0.5,0.5]),player,600,150,false] call RYD_HAS_CAS;

 

RYD_HAS_Chopper - heli object to be used. 

(screenToWorld [0.5,0.5]) - intended CAS coordinates (ATL).

player - caller unit.

600 - desired distance between the heli and CAS coords when firing. May be lowered, if objects visibility distance is lower, but never below 60% of set value. The closer, the better accuracy of non-guided weaponry (less dispersion impact), but the higher risk of enemy fire from CAS coords vicinity. 600 seems close enough for good accuracy of non-guided, for guided 1000 or more seems enough. 

150 - how wide around CAS coords code should search for hostile targets.

false - if should be considered only targets actually known to caller unit or to helicopter crew. 

 

Procedure:

 

Heli will be used only, if according to the script it is proper attack helo/gunship. It is checked by the presence of rocket/missile-based weaponry (assuming, such weaponry is on firing forward pylons/mounts/turrets, as only such weapons can be used here). 

After that, heli is set to "BLUE"/"CARELESS" and gets move waypoint in the straight line between heli's position and CAS coords at set distance from the target. 

When on place, there is LOS check performed (terrain only, I'm still considering, if also should be checked for objects). if no LOS, hovering altitude is raised until there's LOS with several meters of reserve. 

At this point code checks for enemy targets presence at CAS coords. Ignored will be targets not visible (terrain-sensitive LOS) from heli's position.

Targets are engaged in certain priority order depending on expected threat posing to the heli and heli's armament. If no guided AT weaponry available, attacked will be only soft targets, infantry first, but including vehicle's crews even, when inside, the landed aerial assets and cars. Otherwise first heli attempts to neutralize armored targets, then aerial, then cars and then infantry. Guided weaponry will be used only against vehicles.  

Each target in the queues gets certain amount of time of "attention", then fire moves on next target. This procedure is looped. Heli is quite persistent, if not uder fire, CAS may take few minutes - picking every single AI unit from the grass using minigun takes time at these distancies. 

If no targets detected at CAS coord, heli will perform shorter unguided fire on the CAS coords directly. 

CAS will end, when all targets are destroyed, ammo depleted or some point earlier, if code "decide" it's enough/pointless to continue, which may happen earlier, if heli is damaged. Will end also when heli is considered too damaged by returning fire or destroyed/unable to fly/fire.

If able to fly, next heli gets land waypoint at its initial position, while flight altitude is resetted.   

 

Footage:

 

https://youtu.be/uA0m2dx0UXg

 
  • Like 1
  • Thanks 1
  • Haha 1

Share this post


Link to post
Share on other sites

You could contact mad cheese and ask him how he is doing it for his C2 mod. Here is a link for his video. I hope this can help.

 

Share this post


Link to post
Share on other sites

Thanks. Well, that impressive stuff smells like lots of workhours put into it, especially polished GUI functions, no doubt. Great mod. From what I see helis may be aiming same way, I figured out, but maybe I'll ask, perhaps he found something better. 

Share this post


Link to post
Share on other sites

LOL. I've been working on this for weeks myself. How insane is it we can't get a simple Heli to just ATTACK an area? You have the patience of a Jedi.

  • Haha 1

Share this post


Link to post
Share on other sites

Hi Rydygier, I ran your Simple Demo Mission but the heli didn't attack anything. It arrived, then just sat in mid air, then CAS Complete was indicated. Heli turned to leave and was shot down.

 

Am I doing something wrong? I am so excited to get this to work and maybe apply  to a mission, with your blessing 🙂

Share this post


Link to post
Share on other sites

Hi. Not sure about the cause of your issue, but since this thread, that piece of code was improved and integrated into this:

 

You may either use HAS as a whole in your mission, either rip off the CAS code itself, which resides in the HAS_fnc.sqf, RYD_HAS_CAS function along with functions called from it. But it is a large and rather complex chunk of code... Currently I cannot assist with this sadly, being busy long term elsewhere. 

 

  • Like 1

Share this post


Link to post
Share on other sites

Yup, sure, perfectly OK. Implement it, if you find it useful. 🙂

Share this post


Link to post
Share on other sites
52 minutes ago, Rydygier said:

Yup, sure, perfectly OK. Implement it, if you find it useful. 🙂

Great....Thanks a lot. I am already testing it...It works great. Is it working with planes, too?

Share this post


Link to post
Share on other sites

Well, as the title suggest (stationary hovering position), it's for helicopters. Plane CAS would be quite different story. 

Share this post


Link to post
Share on other sites
On 2/14/2024 at 10:47 AM, Rydygier said:

Well, as the title suggest (stationary hovering position), it's for helicopters. Plane CAS would be quite different story. 

oh..sure....I will see how I implement this all...Ideally I need a separate function for planes than. I am currently strugling with problems that a Heli wont return and even asiggning Waypoints in Zeus doesnt make it move....trying some stuff out now.

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

×