NLAW - Predicted Line Of Sight Guidance and Overfly Attack Mode (#4791)

* NLAW Prototype

* Make AI Compatible

* Add Overfly Top Attack Mode

* Limit Max Deflection

* Base prediction on AI skill

* Generic cycle attack profile key for missile guidance

* Add hint for weapons without huds

* Configure for attack cycle key

* Finish OTA ammo effects

* Cleanup

* Arm at 20m

* Disable Debug

* No models for short lived sub-ammos

* Fix Korean strings

* Change AI randomization to use skillFinal

* Add wiki doc for nlaw

* Cleanup

* Cleanup

* Cleanup
This commit is contained in:
PabstMirror 2017-06-02 16:51:20 -05:00 committed by GitHub
parent 4872e186cd
commit 49374feb2a
22 changed files with 577 additions and 0 deletions

View File

@ -5,6 +5,7 @@ class CfgWeapons {
magazines[] = {"ACE_PreloadedMissileDummy"}; // The dummy magazine magazines[] = {"ACE_PreloadedMissileDummy"}; // The dummy magazine
}; };
class ACE_launch_NLAW_Used_F: launch_NLAW_F { // the used tube should be a sub class of the disposable launcher class ACE_launch_NLAW_Used_F: launch_NLAW_F { // the used tube should be a sub class of the disposable launcher
EGVAR(nlaw,enabled) = 0; // disable guidance for the disposabled tube
scope = 1; scope = 1;
ACE_isUsedLauncher = 1; ACE_isUsedLauncher = 1;
author = ECSTRING(common,ACETeam); author = ECSTRING(common,ACETeam);

View File

@ -115,6 +115,15 @@ private _args = [_this,
[ diag_tickTime, [], [], _lastKnownPosState] [ diag_tickTime, [], [], _lastKnownPosState]
]; ];
// Run the "onFired" function passing the full guidance args array
private _onFiredFunc = getText (_config >> "onFired");
TRACE_1("",_onFiredFunc);
if (_onFiredFunc != "") then {
_args call (missionNamespace getVariable _onFiredFunc);
};
// Reverse: // Reverse:
// _args params ["_firedEH", "_launchParams", "_flightParams", "_seekerParams", "_stateParams"]; // _args params ["_firedEH", "_launchParams", "_flightParams", "_seekerParams", "_stateParams"];
// _firedEH params ["_shooter","","","","_ammo","","_projectile"]; // _firedEH params ["_shooter","","","","_ammo","","_projectile"];

1
addons/nlaw/$PBOPREFIX$ Normal file
View File

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

View File

@ -0,0 +1,14 @@
class EGVAR(missileguidance,AttackProfiles) {
class GVAR(directAttack) {
name = CSTRING(directAttack);
functionName = QFUNC(attackProfile);
};
class GVAR(overflyTopAttack): GVAR(directAttack) {
name = CSTRING(overflyTopAttack);
};
};
class EGVAR(missileguidance,SeekerTypes) {
class GVAR(seeker) {
functionName = QFUNC(seeker);
};
};

55
addons/nlaw/CfgAmmo.hpp Normal file
View File

@ -0,0 +1,55 @@
class CfgAmmo {
class M_NLAW_AT_F;
class ACE_NLAW: M_NLAW_AT_F {
hit = 400; // Default was 500
indirectHit = 20; // Default was 15
class ace_missileguidance {
enabled = 1;
minDeflection = 0.0005; // Minium flap deflection for guidance
maxDeflection = 0.01; // Maximum flap deflection for guidance
incDeflection = 0.0005; // The incrmeent in which deflection adjusts.
canVanillaLock = 0; // Can this default vanilla lock? Only applicable to non-cadet mode
// Guidance type for munitions
defaultSeekerType = QGVAR(seeker);
seekerTypes[] = {QGVAR(seeker)};
defaultSeekerLockMode = "LOBL";
seekerLockModes[] = {"LOBL"};
seekLastTargetPos = 0; // seek last target position [if seeker loses LOS of target, continue to last known pos]
seekerAngle = 45; // Angle in front of the missile which can be searched
seekerAccuracy = 1; // seeker accuracy multiplier
seekerMinRange = 0;
seekerMaxRange = 10; // Range from the missile which the seeker can visually search
// Attack profile type selection
defaultAttackProfile = QGVAR(directAttack);
attackProfiles[] = {QGVAR(directAttack), QGVAR(overflyTopAttack)};
showHintOnCycle = 1;
// Run once at fired event
onFired = QFUNC(onFired);
};
};
// Sub ammos used in OTA mode (see fnc_seeker.sqf)
class ACE_NLAW_Explosion: ACE_NLAW { // Based on FCS-Airburst, will explode right away
timeToLive = 0;
model = "";
};
class ACE_NLAW_ShapedCharge: ACE_NLAW { // Shaped charge from rocket explosion, no effects
timeToLive = 1;
model = "";
hit = 750;
indirectHit = 0;
indirectHitRange = 0;
explosionSoundEffect = "";
explosionEffects = "";
CraterEffects = "";
muzzleEffect = "";
};
};

View File

@ -0,0 +1,17 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preStart));
};
};
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preInit));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_postInit));
};
};

View File

@ -0,0 +1,6 @@
class CfgMagazines {
class CA_LauncherMagazine;
class NLAW_F: CA_LauncherMagazine {
ammo = "ACE_NLAW";
};
};

View File

@ -0,0 +1,13 @@
class CfgWeapons {
class Launcher_Base_F;
class launch_NLAW_F: Launcher_Base_F {
GVAR(enabled) = 1;
canLock = 1;
class OpticsModes {
class optic {
distanceZoomMin = 0;
distanceZoomMax = 0;
};
};
};
};

10
addons/nlaw/README.md Normal file
View File

@ -0,0 +1,10 @@
ace_nlaw
===============
Adds Predicted Line Of Sight guidance to the NLAW.
## Maintainers
The people responsible for merging changes to this component or answering potential questions.
- [PabstMirror](https://github.com/PabstMirror)

6
addons/nlaw/XEH_PREP.hpp Normal file
View File

@ -0,0 +1,6 @@
LOG("prep");
PREP(attackProfile);
PREP(keyDown);
PREP(onFired);
PREP(seeker);

View File

@ -0,0 +1,46 @@
#include "script_component.hpp"
if (!hasInterface) exitWith {};
GVAR(isLockKeyDown) = false;
// Degrees per second
GVAR(yawChange) = 0;
GVAR(pitchChange) = 0;
// Add keybind
["ACE3 Weapons", QGVAR(trackTarget), localize LSTRING(trackTarget), {
call FUNC(keyDown);
false // Return false so it doesn't block the rest weapon action
}, {
TRACE_1("lock key up",GVAR(isLockKeyDown));
GVAR(isLockKeyDown) = false;
false
}, [15, [false, false, false]], false] call CBA_fnc_addKeybind; //Tab Key
// Visual debuging, idealy used with a moving vehicle called "testTarget"
#ifdef DRAW_NLAW_INFO
addMissionEventHandler ["Draw3d", {
// GREEN - Draw an object called "testTarget"'s aim pos and 1 sec aimpos predicted by velocity
if ((!isNil "testTarget") && {!isNull testTarget}) then {
{
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [0,1,0,1], ASLtoAGL ((aimPos testTarget) vectorAdd ((velocity testTarget) vectorMultiply _x)), 0.75, 0.75, 0, format ["%1", _x], 1, 0.025, "TahomaB"];
} forEach [0, 1, 2, 3];
};
// RED - If lock key is down, draw weapon dir and predicted path at various times
if (GVAR(yawChange) != 0) then {
{
private _viewASL = AGLtoASL positionCameraToWorld [0,0,0];
private _viewDir = ACE_player weaponDirection (currentWeapon ACE_player);
(_viewDir call CBA_fnc_vect2Polar) params ["", "_yaw", "_pitch"];
private _realYaw = _yaw + GVAR(yawChange) * _x;
private _realPitch = _pitch + GVAR(pitchChange) * _x;
private _returnTargetPos = _viewASL vectorAdd ([1000, _realYaw, _realPitch] call CBA_fnc_polar2vect);
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1,0,0,1], ASLtoAGL _returnTargetPos, 0.75, 0.75, 0, format ["%1", _x], 1, 0.025, "TahomaB"];
} forEach [0, 1, 2, 3];
};
}];
#endif

View File

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

View File

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

22
addons/nlaw/config.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "script_component.hpp"
class CfgPatches {
class ADDON {
name = COMPONENT_NAME;
units[] = {};
weapons[] = {};
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {"ace_missileguidance"};
author = ECSTRING(common,ACETeam);
url = ECSTRING(main,URL);
VERSION_CONFIG;
};
};
#include "CfgEventhandlers.hpp"
#include "CfgAmmo.hpp"
#include "CfgMagazines.hpp"
#include "CfgWeapons.hpp"
#include "ACE_GuidanceConfig.hpp"

View File

@ -0,0 +1,60 @@
/*
* Author: PabstMirror
* NLAW missile guidance attack profile.
*
* Arguments:
* 0: Seeker Target PosASL <ARRAY>
* 1: Guidance Arg Array <ARRAY>
* 2: Attack Profile State <ARRAY>
*
* Return Value:
* Missile Aim PosASL <ARRAY>
*
* Example:
* [[1,2,3], [], []] call ace_nlaw_fnc_attackProfile
*
* Public: No
*/
#include "script_component.hpp"
params ["_seekerTargetPos", "_args", "_attackProfileStateParams"];
_args params ["_firedEH", "_launchParams"];
_launchParams params ["","_targetLaunchParams", "", "_attackProfile"];
_targetLaunchParams params ["", "", "_launchPos"];
_firedEH params ["","","","","","","_projectile"];
// Use seeker (if terminal)
if (!(_seekerTargetPos isEqualTo [0,0,0])) exitWith {_seekerTargetPos};
_attackProfileStateParams params ["_startTime", "_startLOS", "_yawChange", "_pitchChange"];
(_startLOS call CBA_fnc_vect2Polar) params ["", "_yaw", "_pitch"];
private _projectilePos = getPosASL _projectile;
private _distanceFromLaunch = (_launchPos distance _projectilePos) + 10;
private _flightTime = CBA_missionTime - _startTime;
private _realYaw = _yaw + _yawChange * _flightTime;
private _realPitch = _pitch + _pitchChange * _flightTime;
private _returnTargetPos = _launchPos vectorAdd ([_distanceFromLaunch, _realYaw, _realPitch] call CBA_fnc_polar2vect);
if (_attackProfile == QGVAR(overflyTopAttack)) then { // Add 2m height in OTA attack mode
_returnTargetPos = _returnTargetPos vectorAdd [0,0,2];
};
#ifdef DRAW_NLAW_INFO
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1,0,1,1], ASLtoAGL _launchPos, 0.75, 0.75, 0, "LAUNCH", 1, 0.025, "TahomaB"];
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [0,1,1,1], ASLtoAGL (_launchPos vectorAdd (_startLOS vectorMultiply (_distanceFromLaunch + 50))), 0.75, 0.75, 0, "Original LOS", 1, 0.025, "TahomaB"];
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1,1,0,1], ASLtoAGL (_launchPos vectorAdd ([_distanceFromLaunch + 50, _realYaw, _realPitch] call CBA_fnc_polar2vect)), 0.75, 0.75, 0, format ["Predicted @%1sec",(floor(_flightTime * 10)/10)], 1, 0.025, "TahomaB"];
drawLine3D [ASLtoAGL _launchPos, ASLtoAGL (_launchPos vectorAdd (_startLOS vectorMultiply (_distanceFromLaunch + 50))), [1,0,0,1]];
drawLine3D [ASLtoAGL _launchPos, ASLtoAGL (_launchPos vectorAdd ([_distanceFromLaunch + 50, _realYaw, _realPitch] call CBA_fnc_polar2vect)), [1,1,0,1]];
private _test = lineIntersectsSurfaces [_launchPos, _launchPos vectorAdd (_startLOS vectorMultiply 3000), player, _projectile];
if ((count _test) > 0) then {
private _posAGL = ASLtoAGL ((_test select 0) select 0);
drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1,0,0,1], _posAGL, 0.75, 0.75, 0, "Original Impact", 1, 0.025, "TahomaB"];
};
#endif
// TRACE_1("Adjusted target position", _returnTargetPos);
_returnTargetPos;

View File

@ -0,0 +1,81 @@
/*
* Author: PabstMirror
* Handles the track key being held down.
* Tracks change in direction of weapon and computes angle change per second.
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* [] call ace_nlaw_fnc_keyDown
*
* Public: No
*/
// #define DEBUG_MODE_FULL
#include "script_component.hpp"
TRACE_1("lock key down",GVAR(isLockKeyDown));
if (!alive ACE_player) exitWith {};
if (!([ACE_player, objNull, ["isNotInside"]] call EFUNC(common,canInteractWith))) exitWith {};
if (!(ACE_player call CBA_fnc_canUseWeapon)) exitWith {};
if ((getNumber (configFile >> "CfgWeapons" >> (currentWeapon ACE_player) >> QGVAR(enabled))) == 0) exitWith {};
if (GVAR(isLockKeyDown)) exitWith {ERROR("already running?");};
GVAR(isLockKeyDown) = true;
playSound "ACE_Sound_Click";
// Get starting weapon dir
((ACE_player weaponDirection (currentWeapon ACE_player)) call CBA_fnc_vect2Polar) params ["", "_yaw", "_pitch"];
[{
params ["_args", "_pfID"];
_args params ["_lastTime", "_lastYaw", "_lastPitch", "_initPhase"];
if ((!alive ACE_player) ||
{!([ACE_player, objNull, ["isNotInside"]] call EFUNC(common,canInteractWith))} ||
{!GVAR(isLockKeyDown)} ||
{!(ACE_player call CBA_fnc_canUseWeapon)} ||
{(getNumber (configFile >> "CfgWeapons" >> (currentWeapon ACE_player) >> QGVAR(enabled))) == 0})
exitWith {
TRACE_1("ending track",_pfID);
[_pfID] call CBA_fnc_removePerFrameHandler;
playSound "ACE_Sound_Click";
[{ // reset gvars after a short delay
TRACE_1("reset vars",_this);
GVAR(yawChange) = 0;
GVAR(pitchChange) = 0;
}, [], 0.5] call CBA_fnc_waitAndExecute;
};
private _deltaT = CBA_missionTime - _lastTime;
if (_deltaT == 0) exitWith {};
if (_initPhase && {_deltaT < 0.75}) exitWith {};
((ACE_player weaponDirection (currentWeapon ACE_player)) call CBA_fnc_vect2Polar) params ["", "_yaw", "_pitch"];
private _yawChange = ([_yaw - _lastYaw] call CBA_fnc_simplifyAngle180) / _deltaT;
private _pitchChange = ([_pitch - _lastPitch] call CBA_fnc_simplifyAngle180) / _deltaT;
if (_initPhase) then { // initial value will use first 0.75 seconds of input
GVAR(yawChange) = _yawChange;
GVAR(pitchChange) = _pitchChange;
_args set [3, false];
} else {
// smoothing factor alpha - higher values will be more responsive to change, but also spike higher on jerky mouse movmeent
private _alpha = _deltaT / 3;
GVAR(yawChange) = (_yawChange * _alpha) + GVAR(yawChange) * (1 - _alpha);
GVAR(pitchChange) = (_pitchChange * _alpha) + GVAR(pitchChange) * (1 - _alpha);
};
_args set [0, CBA_missionTime];
_args set [1, _yaw];
_args set [2, _pitch];
#ifdef DEBUG_MODE_FULL
hintSilent format ["Instantaneous\nYaw: %1\n Pitch: %2\nGVAR\nYaw: %3\nPitch: %4", _yawChange, _pitchChange, GVAR(yawChange), GVAR(pitchChange)];
#endif
}, .25, [CBA_missionTime, _yaw, _pitch, true]] call CBA_fnc_addPerFrameHandler;

View File

@ -0,0 +1,62 @@
/*
* Author: PabstMirror
* Sets up missile guidance state arrays (called from missileGuidance's onFired).
*
* Arguments:
* Guidance Arg Array <ARRAY>
*
* Return Value:
* None
*
* Example:
* [] call ace_nlaw_fnc_onFired
*
* Public: No
*/
#include "script_component.hpp"
params ["_firedEH", "_launchParams", "_flightParams", "_seekerParams", "_stateParams"];
_firedEH params ["_shooter","","","","","","_projectile"];
_launchParams params ["","_targetLaunchParams","","_attackProfile"];
_targetLaunchParams params ["_target"];
_stateParams params ["", "", "_attackProfileStateParams"];
// Reset _launchPos origin as projectile's height instead of player's foot
_targetLaunchParams set [2, getPosASL _projectile];
// Get state params:
TRACE_3("start of attack profile",_attackProfile,_shooter,vectorDir _projectile);
private _firedLOS = _shooter weaponDirection (currentWeapon _shooter);
private _yawChange = 0;
private _pitchChange = 0;
if (_shooter == ACE_player) then {
TRACE_2("isPlayer",GVAR(yawChange),GVAR(pitchChange));
_yawChange = GVAR(yawChange);
_pitchChange = GVAR(pitchChange);
TRACE_1("los check",_firedLOS call CBA_fnc_vect2Polar);
} else {
if ((!isNil "_target") && {!isNull _target}) then {
_firedLOS = (getPosASL _projectile) vectorFromTo (aimPos _target);
(((eyePos _shooter) vectorFromTo (aimPos _target)) call CBA_fnc_vect2Polar) params ["", "_startYaw", "_startPitch"];
// Add some random error to AI's velocity prediction:
private _random = random [(_shooter skillFinal "aimingAccuracy") min 0.9, 1, 2-((_shooter skillFinal "aimingAccuracy") min 0.9)];
(((eyePos _shooter) vectorFromTo ((aimPos _target) vectorAdd ((velocity _target) vectorMultiply (_random)))) call CBA_fnc_vect2Polar) params ["", "_predictedYaw", "_predictedPitch"];
_yawChange = ([_predictedYaw - _startYaw] call CBA_fnc_simplifyAngle180);
_pitchChange = ([_predictedPitch - _startPitch] call CBA_fnc_simplifyAngle180);
TRACE_1("AI",_target);
} else {
TRACE_1("AI - no target",_target);
};
};
// Limit Max Deflection
_yawChange = -10 max _yawChange min 10;
_pitchChange = -10 max _pitchChange min 10;
TRACE_3("attackProfileStateParams",_firedLOS,_yawChange,_pitchChange);
_attackProfileStateParams set [0, CBA_missionTime];
_attackProfileStateParams set [1, _firedLOS];
_attackProfileStateParams set [2, _yawChange];
_attackProfileStateParams set [3, _pitchChange];

View File

@ -0,0 +1,94 @@
/*
* Author: PabstMirror
* Handles the top down attack seeker for missile guidance.
* Has a very short range (IR/Magnetic?) seeker that will trigger the shaped charge midair above the target.
*
* Arguments:
* 1: Guidance Arg Array <ARRAY>
* 2: Seeker State <ARRAY>
*
* Return Value:
* Seeker Pos <ARRAY>
*
* Example:
* [] call ace_nlaw_fnc_seeker
*
* Public: No
*/
// #define DEBUG_MODE_FULL
#include "script_component.hpp"
params ["", "_args", "_seekerStateParams"];
_args params ["_firedEH", "_launchParams", "", "_seekerParams", "_stateParams"];
_firedEH params ["","","","","","","_projectile"];
_launchParams params ["", "_targetLaunchParams", "", "_attackProfile"];
_targetLaunchParams params ["", "", "_launchPos"];
if (_attackProfile == QGVAR(directAttack)) exitWith {[0,0,0]};
private _projPos = getPosASL _projectile;
// Arm seeker after 20 meters
if ((_projPos distance _launchPos) >= 20) then {
scopeName "targetScan";
BEGIN_COUNTER(targetScan);
if (_seekerStateParams isEqualTo []) then {
TRACE_2("Seeker Armed",_projPos distance _launchPos,diag_fps);
_seekerStateParams set [0, _projPos]; // Set _lastPos to current position
};
_seekerStateParams params ["_lastPos", "_terminal"];
if (_terminal) exitWith {};
private _vectorDir = _lastPos vectorFromTo _projPos;
private _frameDistance = _lastPos vectorDistance _projPos;
// Distance traveled depends on velocity and FPS - at 60fps it will be ~4m
// Step size will effect accuracy and performance costs
for "_stepSize" from 0 to _frameDistance step 0.5 do {
// This represents a position that the missile was at between the last frame and now
private _virtualPos = _lastPos vectorAdd (_vectorDir vectorMultiply _stepSize);
#ifdef DRAW_NLAW_INFO
drawLine3D [ASLtoAGL _virtualPos, ASLtoAGL (_virtualPos vectorAdd [0,0,-5]), [1,0,_stepSize/(_frameDistance max 0.1),1]];
#endif
// Limit scan to 5 meters directly down (shaped charge jet has a very limited range)
private _res = lineIntersectsSurfaces [_virtualPos, (_virtualPos vectorAdd [0,0,-5]), _projectile];
if (!(_res isEqualTo [])) then {
(_res select 0) params ["_targetPos", "", "_target"];
if ((_target isKindOf "Tank") || {_target isKindOf "Car"} || {_target isKindOf "Air"}) exitWith {
TRACE_3("Firing shaped charge down",_target,_targetPos distance _virtualPos,_frameDistance);
TRACE_2("",_target worldToModel (ASLtoAGL _virtualPos),boundingBoxReal _target);
_virtualPos = _virtualPos vectorAdd (_vectorDir vectorMultiply 1.25);
deleteVehicle _projectile;
// Damage and effects of missile exploding (timeToLive is 0 so should happen next frame)
private _explosion = "ACE_NLAW_Explosion" createVehicle _virtualPos;
_explosion setPosASL _virtualPos;
// Just damage from shaped charge
private _shapedCharage = "ACE_NLAW_ShapedCharge" createVehicle _virtualPos;
_shapedCharage setPosASL _virtualPos;
_shapedCharage setVectorDirAndUp [[0,0,-1], [1,0,0]];
_shapedCharage setVelocity [0,0,-300];
_seekerStateParams set [1, true];
END_COUNTER(targetScan);
breakOut "targetScan";
};
};
};
_seekerStateParams set [0, _projPos];
END_COUNTER(targetScan);
};
// Exploded, return dummy value
if (_seekerStateParams param [1, false]) exitWith {
[0,0,1]
};
// return:
[0,0,0]

View File

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

View File

@ -0,0 +1,18 @@
#define COMPONENT nlaw
#define COMPONENT_BEAUTIFIED NLAW
#include "\z\ace\addons\main\script_mod.hpp"
// #define DRAW_NLAW_INFO
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
// #define ENABLE_PERFORMANCE_COUNTERS
#ifdef DEBUG_ENABLED_NLAW
#define DEBUG_MODE_FULL
#endif
#ifdef DEBUG_SETTINGS_NLAW
#define DEBUG_SETTINGS DEBUG_SETTINGS_NLAW
#endif
#include "\z\ace\addons\main\script_macros.hpp"

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project name="ACE">
<Package name="NLAW">
<Key ID="STR_ACE_NLAW_trackTarget">
<English>NLAW Track Target (Hold)</English>
</Key>
<Key ID="STR_ACE_NLAW_directAttack">
<English>Direct Attack</English>
</Key>
<Key ID="STR_ACE_NLAW_overflyTopAttack">
<English>Overfly Top Attack</English>
</Key>
</Package>
</Project>

35
docs/wiki/feature/nlaw.md Normal file
View File

@ -0,0 +1,35 @@
---
layout: wiki
title: NLAW
description: NLAW
group: feature
category: equipment
parent: wiki
mod: ace
version:
major: 3
minor: 10
patch: 0
---
## 1. Overview
### 1.1 Guidance
NLAW uses Predicted Line Of Sight guidance.
Before firing the shooter tracks the targets for several seconds.
This programs the missile with the angular rotation and allows it to fly a curved path that will hit the target.
It will also correct for gravity drop.
### 1.2 Attack profiles
- Direct - Normal impact fuze for non-armored targets. Note that the missile's shaped charge is aimed downards, so this mode is not recomended against armor.
- Overfly Top Attack - Flies high and when sensors detects a target below it triggers the shaped charge to fire downards into the weak top armor.
## 2. Usage
- Cycle attack profiles with the missile guidance "Cycle Fire Mode" keybind (default: <kbd>Ctrl</kbd> + <kbd>Tab</kbd>)
- Start tracking by pressing and holding the "NLAW Track Target" keybind (default: <kbd>Tab</kbd>)
- While holding the key down track the target for 2-3 seconds and fire.
- Can also be fired against static targets without tracking.
## 3. Dependencies
{% include dependencies_list.md component="nlaw" %}