Add AGM-114L Active Radar Homing Hellfire (#7337)

* Create AGM-114L

* If we lose LOS dont continue tracking magically Allow for datalinked targets to donate data to the missile.

* Update documentation

* RHS 2x hellfire compatability

* Make ARH more realistic by not allowing to switch targets after firing

* Fix filename. Change Hellfire attack profile to lead target. Switch to two LOS checks.

We check two Line's of Sight to ensure that we are 100% gone from the target. A raw LOS check will be blocked by bushes and light trees while the checkVisibility wont while on the otherhand smoke will block a visiblity check but not a raw LOS check. We get best of both worlds with this.
I changed the attack profile so that it will lead moving targets. This isnt needed with the laser version because the user will "lead" the target if needed, but with the radar scan we have velocity information so we might as well lead the target as much as possible

* Change function calls to FUNC macro. Slightly change radar logic. Up poll frequency to 7hz

Instead of the missile immediately going active when the shooter doesn't have radar, check if targets are in datalink. If they are, use the datalink to guide the missile instead of its internal radar.

* Add logic for missiles launched without target

If a missile is fired without a locked target, it will immediately go active and target the first thing its seeker picks up. This is an incredibly dangerous trait of active radar homing missiles and is so in this implementation. Be careful!

* Change from `exitWith` to basic `then`

Legacy code that never got changed. This is essentially what happened before

* Update CfgMagazineWells.hpp

Co-authored-by: PabstMirror <pabstmirror@gmail.com>
This commit is contained in:
Brandon Danyluk 2019-12-30 11:29:08 -07:00 committed by PabstMirror
parent f918b53caa
commit f2fff98ad0
14 changed files with 395 additions and 12 deletions

View File

@ -65,4 +65,65 @@ class CfgAmmo {
enabled = 1; // Missile Guidance must be explicitly enabled
};
};
class ACE_Hellfire_AGM114L: ACE_Hellfire_AGM114K {
displayName = "AGM-114L";
displayNameShort = "AGM-114L";
description = "AGM-114L";
descriptionShort = "AGM-114L";
class ace_missileguidance: ace_missileguidance {
canVanillaLock = 1;
enabled = 1; // Missile Guidance must be explicitly enabled
seekLastTargetPos = 0;
defaultSeekerType = "ARH";
seekerTypes[] = { "ARH" };
defaultSeekerLockMode = "LOBL";
seekerLockModes[] = { "LOBL" };
activeRadarEngageDistance = 1000;
seekerMaxRange = 2000; // distance that the hellfire internal radar can scan
};
// Vanilla lock system vars
weaponLockSystem = "8";
airLock = 1;
lockType = 0;
maneuvrability = 0; // no maneuvrability so that default guidance doesnt work
missileLockMaxDistance = 8000;
missileLockMinDistance = 250;
missileLockMaxSpeed = 600;
missileKeepLockedCone = 70;
flightProfiles[] = {};
class Components {
class SensorsManagerComponent {
class Components {
class MillimeterWaveRadar {
componentType = "ActiveRadarSensorComponent";
class AirTarget {
minRange = 0;
maxRange = 8000;
objectDistanceLimitCoef = -1
viewDistanceLimitCoef = -1;
};
class GroundTarget {
minRange = 0;
maxRange = 8000;
objectDistanceLimitCoef = -1;
viewDistanceLimitCoef = -1;
};
typeRecognitionDistance = 4000;
angleRangeHorizontal = 70;
angleRangeVertical = 70;
groundNoiseDistanceCoef = 0; // relevant to AA missiles - not really for this
maxGroundNoiseDistance = 250;
minSpeedThreshold = 0;
maxSpeedThreshold = 600;
nightRangeCoef = 1;
maxFogSeeThrough = 0.8;
};
};
};
};
};
};

View File

@ -1,4 +1,5 @@
class CfgMagazineWells {
class GVAR(K) {};
class GVAR(N) {};
class GVAR(L) {};
};

View File

@ -84,4 +84,43 @@ class CfgMagazines {
ammo = "ACE_Hellfire_AGM114N";
pylonWeapon = QGVAR(launcher_N);
};
// Lima - tandem shaped charge HEAT (anti-tank) Fire and Forget Active Radar Homing
class 6Rnd_ACE_Hellfire_AGM114L: 6Rnd_ACE_Hellfire_AGM114K { // Old style vehicle magazine
count = 6;
ammo = "ACE_Hellfire_AGM114L";
displayName = "AGM-114L [ACE]";
displayNameShort = "AGM-114L";
descriptionShort = "AGM-114L";
};
// 1.70 pylon magazines:
class PylonMissile_1Rnd_ACE_Hellfire_AGM114L: PylonMissile_1Rnd_ACE_Hellfire_AGM114K { // Bare missle
displayName = "1x AGM-114L [ACE]";
displayNameShort = "AGM-114L";
descriptionShort = "AGM-114L";
ammo = "ACE_Hellfire_AGM114L";
pylonWeapon = QGVAR(launcher_L);
};
class PylonRack_1Rnd_ACE_Hellfire_AGM114L: PylonRack_1Rnd_ACE_Hellfire_AGM114K { // 1x Launcher Support Rack
displayName = "1x AGM-114L [ACE]";
displayNameShort = "AGM-114L";
descriptionShort = "AGM-114L";
ammo = "ACE_Hellfire_AGM114L";
pylonWeapon = QGVAR(launcher_L);
};
class PylonRack_3Rnd_ACE_Hellfire_AGM114L: PylonRack_3Rnd_ACE_Hellfire_AGM114K { // 3x Launcher Support Rack
displayName = "3x AGM-114L [ACE]";
displayNameShort = "AGM-114L";
descriptionShort = "AGM-114L";
ammo = "ACE_Hellfire_AGM114L";
pylonWeapon = QGVAR(launcher_L);
};
class PylonRack_4Rnd_ACE_Hellfire_AGM114L: PylonRack_4Rnd_ACE_Hellfire_AGM114K { // 4x Launcher Support Rack
displayName = "4x AGM-114L [ACE]";
displayNameShort = "AGM-114L";
descriptionShort = "AGM-114L";
ammo = "ACE_Hellfire_AGM114L";
pylonWeapon = QGVAR(launcher_L);
};
};

View File

@ -1,5 +1,6 @@
class CfgWeapons {
class RocketPods;
class MissileLauncher;
class GVAR(launcher): RocketPods {
displayName = "AGM-114K Hellfire II";
GVAR(enabled) = 1; // handle adding interactions and adding Laser Designator
@ -29,4 +30,26 @@ class CfgWeapons {
magazines[] = {"6Rnd_ACE_Hellfire_AGM114N", "PylonMissile_1Rnd_ACE_Hellfire_AGM114N", "PylonRack_1Rnd_ACE_Hellfire_AGM114N", "PylonRack_3Rnd_ACE_Hellfire_AGM114N", "PylonRack_4Rnd_ACE_Hellfire_AGM114N"};
magazineWell[] += {QGVAR(N)};
};
class GVAR(launcher_L): GVAR(launcher) {
displayName = "AGM-114L Hellfire ""Longbow""";
magazines[] = {"6Rnd_ACE_Hellfire_AGM114L", "PylonMissile_1Rnd_ACE_Hellfire_AGM114L", "PylonRack_1Rnd_ACE_Hellfire_AGM114L", "PylonRack_3Rnd_ACE_Hellfire_AGM114L", "PylonRack_4Rnd_ACE_Hellfire_AGM114L"};
magazineWell[] += {QGVAR(L)};
EGVAR(laser,showHud) = 1; // Just to show the attack profile
EGVAR(laser,canSelect) = 0;
class StandardSound {
begin1[] = {"A3\Sounds_F\weapons\Rockets\missile_1",1.12202,1.3,1000};
soundBegin[] = {"begin1",1};
soundsetshot[] = {"RocketsMedium_Shot_SoundSet"};
};
cursor = "EmptyCursor";
cursorAim = "missile";
showAimCursorInternal = 0;
// vanilla weapon lock systems
weaponLockSystem = 8;
cmImmunity = 0.9;
lockAcquire = 0;
weaponLockDelay = 0.1;
canLock = 2;
};
};

View File

@ -18,8 +18,10 @@
*/
params ["_seekerTargetPos", "_args", "_attackProfileStateParams"];
_args params ["_firedEH", "_launchParams"];
_launchParams params ["","_targetLaunchParams"];
_args params ["_firedEH", "_launchParams", "", "", "_stateParams"];
_stateParams params ["", "_seekerStateParams"];
_launchParams params ["","_targetLaunchParams","_seekerType"];
_targetLaunchParams params ["", "", "_launchPos"];
_firedEH params ["","","","","","","_projectile"];
@ -80,5 +82,16 @@ switch (_attackStage) do {
};
};
// Special radar case. Adjust target position such that we are leading it
if (_attackStage >= 3 && { _seekerType isEqualTo "ARH" }) then {
_seekerStateParams params ["", "", "", "", "", "", "", "_lastKnownVelocity"];
private _projectileVelocity = velocity _projectile;
if (_projectileVelocity#2 < 0) then {
private _projectileSpeed = vectorMagnitude _projectileVelocity; // this gives a precise impact time versus using speed _projectile. Dont change
private _timeUntilImpact = (_seekerTargetPos distance _projectilePos) / _projectileSpeed;
_returnTargetPos = _returnTargetPos vectorAdd (_lastKnownVelocity vectorMultiply _timeUntilImpact);
};
};
// TRACE_1("Adjusted target position", _returnTargetPos);
_returnTargetPos;

View File

@ -82,4 +82,12 @@ class GVAR(SeekerTypes) {
functionName = QFUNC(seekerType_SACLOS);
onFired = QFUNC(SACLOS_onFired);
};
class ARH {
name = "";
visualName = "";
description = "";
functionName = QFUNC(seekerType_ARH);
onFired = QFUNC(ahr_onFired);
};
};

View File

@ -33,9 +33,11 @@ PREP(attackProfile_JAV_TOP);
PREP(seekerType_SALH);
PREP(seekerType_Optic);
PREP(seekerType_SACLOS);
PREP(seekerType_ARH);
// Attack Profiles OnFired
PREP(wire_onFired);
// Seeker OnFired
PREP(SACLOS_onFired);
PREP(ahr_onFired);

View File

@ -0,0 +1,73 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* Sets up Active Radar state arrays (called from missileGuidance's onFired).
*
* Arguments:
* Guidance Arg Array <ARRAY>
*
* Return Value:
* None
*
* Example:
* [] call ace_missileguidance_fnc_ahr_onFired
*
* Public: No
*/
params ["_firedEH", "_launchParams", "", "", "_stateParams"];
_firedEH params ["_shooter","","","","","","_projectile"];
_stateParams params ["", "_seekerStateParams"];
_launchParams params ["","_targetLaunchParams"];
_targetLaunchParams params ["_target"];
_target = missileTarget _projectile;
if (isNull _target && isVehicleRadarOn vehicle _shooter) then {
_target = cursorTarget;
};
if !(_target isKindOf "AllVehicles") then {
_target = nil;
};
_launchParams set [0, _target];
_projectile setMissileTarget objNull; // to emulate a no launch warning
private _projectileConfig = [_projectile] call CBA_fnc_getObjectConfig;
private _config = _projectileConfig >> "ace_missileguidance";
private _isActive = false;
private _activeRadarDistance = [_config >> "activeRadarEngageDistance", "NUMBER", 500] call CBA_fnc_getConfigEntry;
private _projectileThrust = [_projectileConfig >> "thrust", "NUMBER", 0] call CBA_fnc_getConfigEntry;
private _projectileThrustTime = [_projectileConfig >> "thrustTime", "NUMBER", 0] call CBA_fnc_getConfigEntry;
private _velocityAtImpact = _projectileThrust * _projectileThrustTime;
private _timeToActive = 0;
if (!isNil "_target" && _velocityAtImpact > 0) then {
private _distanceUntilActive = (((getPosASL _shooter) vectorDistance (getPosASL _target)) - _activeRadarDistance);
_timeToActive = 0 max (_distanceUntilActive / _velocityAtImpact);
};
if (isNil "_target") then {
_timeToActive = 0;
_isActive = true;
_target = objNull;
};
private _shooterHasActiveRadar = {
if ("ActiveRadarSensorComponent" in _x) exitWith { true };
false
} forEach listVehicleSensors vehicle _shooter;
if !(isVehicleRadarOn vehicle _shooter) then {
_isActive = true;
};
_seekerStateParams set [0, _isActive];
_seekerStateParams set [1, _activeRadarDistance];
_seekerStateParams set [2, CBA_missionTime + _timeToActive];
_seekerStateParams set [3, getPosASL _target];
_seekerStateParams set [4, CBA_missionTime];
_seekerStateParams set [5, _shooterHasActiveRadar];
_seekerStateParams set [6, false];
_seekerStateParams set [7, [0, 0, 0]];
_seekerStateParams set [8, CBA_missionTime];
_seekerStateParams set [9, isNull _target];

View File

@ -6,6 +6,7 @@
* Arguments:
* 0: Seeker <OBJECT>
* 1: Target <OBJECT>
* 2: Whether or not to use checkVisibility to test for LOS <BOOLEAN>
*
* Return Value:
* Has LOS <BOOL>
@ -16,7 +17,30 @@
* Public: No
*/
params ["_seeker", "_target"];
params ["_seeker", "_target", ["_checkVisibilityTest", true]];
// Boolean checkVisibilityTest so that if the seeker type is one that ignores smoke we revert back to raw LOS checking.
if (_checkVisibilityTest) exitWith {
private _visibility = [_seeker, "VIEW", _target] checkVisibility [getPosASL _seeker, aimPos _target];
_visibility > 0.001
};
if ((isNil "_seeker") || {isNil "_target"}) exitWith {
ERROR_2("nil",_seeker,_target);
false
};
private _targetPos = getPosASL _target;
private _targetAimPos = aimPos _target;
private _seekerPos = getPosASL _seeker;
private _return = true;
if (!((terrainIntersectASL [_seekerPos, _targetPos]) && {terrainIntersectASL [_seekerPos, _targetAimPos]})) then {
if (lineIntersects [_seekerPos, _targetPos, _seeker, _target]) then {
_return = false;
};
} else {
_return = false;
};
_return
private _visibility = [_seeker, "VIEW", _target] checkVisibility [getPosASL _seeker, aimPos _target];
_visibility > 0.001

View File

@ -0,0 +1,119 @@
#include "script_component.hpp"
/*
* Author: Brandon (TCVM)
* Active Radar Homing seeker
*
* Arguments:
* 1: Guidance Arg Array <ARRAY>
* 2: Seeker State <ARRAY>
*
* Return Value:
* Seeker Pos <ARRAY>
*
* Example:
* [] call call ace_missileguidance_fnc_seekerType_ARH;
*
* Public: No
*/
params ["", "_args", "_seekerStateParams"];
_args params ["_firedEH", "_launchParams", "", "_seekerParams", "_stateParams"];
_firedEH params ["_shooter","","","","","","_projectile"];
_launchParams params ["_target","","","",""];
_seekerParams params ["_seekerAngle", "", "_seekerMaxRange"];
_seekerStateParams params ["_isActive", "_activeRadarEngageDistance", "_timeWhenActive", "_expectedTargetPos", "_lastTargetPollTime", "_shooterHasRadar", "_wasActive", "_lastKnownVelocity", "_lastTimeSeen", "_doesntHaveTarget"];
if (_isActive || { CBA_missionTime >= _timeWhenActive }) then {
if !(_isActive) then {
_seekerStateParams set [0, true];
};
if !(_wasActive) then {
_seekerStateParams set [6, true];
TRACE_1("Missile Pitbull");
};
// Internal radar homing
// For performance reasons only poll for target every so often instead of each frame
if ((_lastTargetPollTime + ACTIVE_RADAR_POLL_FREQUENCY) - CBA_missionTime < 0) then {
private _searchPos = _expectedTargetPos;
if (_searchPos isEqualTo [0, 0, 0] || { _doesntHaveTarget }) then {
_seekerStateParams set [9, true];
// no target pos - shot without lock. Have the missile's radar search infront of it on the ground
_searchPos = (getPosASL _projectile) vectorAdd (_projectile vectorModelToWorld [0, _seekerMaxRange, -((getPos _projectile)#2)]);
};
_target = objNull;
_lastTargetPollTime = CBA_missionTime;
_seekerStateParams set [4, _lastTargetPollTime];
private _distanceToExpectedTarget = _seekerMaxRange min ((getPosASL _projectile) vectorDistance _searchPos);
// Simulate how much the seeker can see at the ground
private _projDir = vectorDir _projectile;
private _projYaw = getDir _projectile;
private _rotatedYaw = (+(_projDir select 0) * sin _projYaw) + (+(_projDir select 1) * cos _projYaw);
if (_rotatedYaw isEqualTo 0) then { _rotatedYaw = 0.001 };
private _projPitch = atan ((_projDir select 2) / _rotatedYaw);
private _a1 = abs _projPitch;
private _a2 = 180 - ((_seekerAngle / 2) + _a1);
private _seekerBaseRadiusAtGround = ACTIVE_RADAR_MINIMUM_SCAN_AREA max (_distanceToExpectedTarget / sin(_a2) * sin(_seekerAngle / 2));
private _seekerBaseRadiusAdjusted = linearConversion [0, _seekerBaseRadiusAtGround, (CBA_missionTime - _lastTimeSeen) * vectorMagnitude _lastKnownVelocity, ACTIVE_RADAR_MINIMUM_SCAN_AREA, _seekerBaseRadiusAtGround, false];
if (_doesntHaveTarget) then {
_seekerBaseRadiusAdjusted = _seekerBaseRadiusAtGround;
};
// Look in front of seeker for any targets
private _nearestObjects = nearestObjects [_searchPos, ["Air", "LandVehicle", "Ship"], _seekerBaseRadiusAdjusted, false];
_nearestObjects = _nearestObjects apply {
// I check both Line of Sight versions to make sure that a single bush doesnt make the target lock dissapear but at the same time ensure that this can see through smoke. Should work 80% of the time
if ([_projectile, getPosASL _x, _seekerAngle] call FUNC(checkSeekerAngle) && { ([_projectile, _x, true] call FUNC(checkLOS)) || { ([_projectile, _x, false] call FUNC(checkLOS)) } }) then {
_x
} else {
objNull
};
};
_nearestObjects = _nearestObjects select { !isNull _x };
// Select closest object to the expected position to be the current radar target
if ((count _nearestObjects) <= 0) exitWith {
_projectile setMissileTarget objNull;
_searchPos
};
private _closestDistance = _seekerBaseRadiusAtGround;
{
if ((_x distance2d _searchPos) < _closestDistance) then {
_closestDistance = _x distance2d _searchPos;
_target = _x;
};
} forEach _nearestObjects;
_expectedTargetPos = _searchPos;
};
_projectile setMissileTarget _target;
} else {
#ifdef DRAW_GUIDANCE_INFO
_seekerTypeName = "AHR - EXT";
#endif
// External radar homing
// if the target is in the remote targets for the side, whoever the donor is will "datalink" the target for the hellfire.
private _remoteTargets = listRemoteTargets side _shooter;
if ((_remoteTargets findIf { (_target in _x) && (_x#1 > 0) }) < 0) then {
// I check both Line of Sight versions to make sure that a single bush doesnt make the target lock dissapear but at the same time ensure that this can see through smoke. Should work 80% of the time
if (!_shooterHasRadar || { !isVehicleRadarOn vehicle _shooter } || { !alive vehicle _shooter } || { !([vehicle _shooter, _target, true] call FUNC(checkLOS)) && { !([vehicle _shooter, _target, false] call FUNC(checkLOS)) } }) then {
_seekerStateParams set [0, true];
_target = objNull; // set up state for active guidance
};
};
};
if !(isNull _target) then {
private _centerOfObject = getCenterOfMass _target;
private _targetAdjustedPos = _target modelToWorldWorld _centerOfObject;
_expectedTargetPos = _targetAdjustedPos;
_seekerStateParams set [3, _expectedTargetPos];
_seekerStateParams set [7, velocity _target];
_seekerStateParams set [8, CBA_missionTime];
_seekerStateParams set [9, false];
};
_launchParams set [0, _target];
_expectedTargetPos

View File

@ -26,4 +26,6 @@
#define DEFAULT_CORRECTION_DISTANCE 2
#define DEFAULT_LEAD_DISTANCE 5
#define ACTIVE_RADAR_POLL_FREQUENCY (1 / 7)
#define ACTIVE_RADAR_MINIMUM_SCAN_AREA 30

View File

@ -14,11 +14,14 @@ version:
## 1. Overview
### 1.1 Guidance
Hellfire missile is a semi-active laser guided weapon.
It requires an observer (either the launch platform or an external source) to provide laser designation.
### 1.1 AGM-114K/N Guidance
The AGM-114K/N Hellfire is a semi-active laser guided weapon. It requires an observer (either the launch platform or an external source) to provide laser designation.
### 1.2 Attack profiles
### 1.2 AGM-114L Guidance
The AGM-114L Hellfire is an active-radar homing guided weapon. It is fire and forget as long as you lock a target initially. This does not require an external laser designation
The missile uses an external radar source to guide the missile towards the target. If this radar lock is dropped or if it gets within range of the target, it switches to an internal radar to guide itself for the terminal phase.
### 1.3 Attack profiles
Missile does not need line of sight to target when fired and can Lock-On-After-Launch (can also delay lasing target).
This and the attack profile used will effect missile's flight and max altitude.
- LOBL: Lock-On-Before-Launch, standard top attack.
@ -26,12 +29,19 @@ This and the attack profile used will effect missile's flight and max altitude.
- LOAL-LOW: Missile immediately gains ~90m altitude.
- LOAL-HI: Missile immediately gains ~300m altitude.
## 2. Usage
## 2.1 AGM-114K/N Usage
- Switching to the hellfire weapon will show additional information about the weapon in weapon status display.
- Shows: lock mode, laser code and a laser receiver indicator. E.G. `LOAL-DIR CODE: 1111`
- Laser receiver indicator turns red when it detects a laser pulse set the the current code.
- Cycle attack profiles with vehicle's ACE3 Interaction Menu or with the missile guidance "Cycle Fire Mode" keybind (default: <kbd>Ctrl</kbd> + <kbd>Tab</kbd>)
## 2.2 AGM-114L Usage
- Switching to the hellfire weapon will show additional information about the weapon in weapon status display.
- Shows: lock mode. E.G. `LOAL-HI`
- Cycle attack profiles with vehicle's ACE3 Interaction Menu or with the missile guidance "Cycle Fire Mode" keybind (default: <kbd>Ctrl</kbd> + <kbd>Tab</kbd>)
- Lock onto a target using the default ARMA targeting (default: <kbd>T</kbd>)
- Once you fire the missile you can either stay locked on or abandon the lock and let the missile guide itself
## 3 Adding to vehicles
- Easiest way to add is via the 1.70 Pylons system.
- Hellfires can also be added to other vehicles via config or script.

View File

@ -3,6 +3,9 @@ class CfgMagazineWells {
ADDON[] = {QGVAR(pylon_mag_2rnd_hellfire_k)};
};
class ace_hellfire_N {
ADDON[] = {GVAR(pylon_mag_2rnd_hellfire_n)};
ADDON[] = {QGVAR(pylon_mag_2rnd_hellfire_n)};
};
class ace_hellfire_L {
ADDON[] = {QGVAR(pylon_mag_2rnd_hellfire_L)};
};
};

View File

@ -50,6 +50,11 @@ class cfgMagazines {
pylonWeapon = "ace_hellfire_launcher_N";
ammo = "ACE_Hellfire_AGM114N";
};
class GVAR(pylon_mag_2rnd_hellfire_l): rhs_mag_AGM114K_2 {
displayName = "2x AGM-114L [ACE]";
pylonWeapon = "ace_hellfire_launcher_L";
ammo = "ACE_Hellfire_AGM114L";
};
class rhsusf_m112_mag: CA_Magazine {
ace_explosives_DelayTime = 1;