diff --git a/addons/iron_dome/$PBOPREFIX$ b/addons/iron_dome/$PBOPREFIX$ new file mode 100644 index 0000000000..269c37fe0d --- /dev/null +++ b/addons/iron_dome/$PBOPREFIX$ @@ -0,0 +1 @@ +z\ace\addons\iron_dome \ No newline at end of file diff --git a/addons/iron_dome/CfgEventHandlers.hpp b/addons/iron_dome/CfgEventHandlers.hpp new file mode 100644 index 0000000000..d1317048c8 --- /dev/null +++ b/addons/iron_dome/CfgEventHandlers.hpp @@ -0,0 +1,11 @@ +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)); + }; +}; + diff --git a/addons/iron_dome/README.md b/addons/iron_dome/README.md new file mode 100644 index 0000000000..b7d05ced59 --- /dev/null +++ b/addons/iron_dome/README.md @@ -0,0 +1,11 @@ +ace_iron_dome +=================== + +Adds an Iron Dome projectile interceptor system + + +## Maintainers + +The people responsible for merging changes to this component or answering potential questions. + +- [Brandon (TCVM)](https://github.com/TheCandianVendingMachine) diff --git a/addons/iron_dome/XEH_PREP.hpp b/addons/iron_dome/XEH_PREP.hpp new file mode 100644 index 0000000000..1fce1ccdaf --- /dev/null +++ b/addons/iron_dome/XEH_PREP.hpp @@ -0,0 +1,2 @@ +PREP(projectileTrackerPFH); +PREP(proximityFusePFH); diff --git a/addons/iron_dome/XEH_preInit.sqf b/addons/iron_dome/XEH_preInit.sqf new file mode 100644 index 0000000000..03085b34e3 --- /dev/null +++ b/addons/iron_dome/XEH_preInit.sqf @@ -0,0 +1,79 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +ADDON = true; + +// Server handles the tracking of all projectiles. It dispatches events to launchers to fire at specific targets +if (isServer) then { + GVAR(trackers) = []; + GVAR(launchers) = []; + GVAR(nonTrackingProjectiles) = []; + GVAR(trackingProjectiles) = []; + + GVAR(interceptors) = []; + GVAR(toBeShot) = []; + + [QGVAR(track), { + params ["_projectile"]; + GVAR(nonTrackingProjectiles) pushBack _projectile; + }] call CBA_fnc_addEventHandler; + + [QGVAR(registerLauncher), { + params ["_launcher"]; + GVAR(launchers) pushBackUnique _launcher; + _launcher setVariable [QGVAR(targetList), []]; + _launcher setVariable [QGVAR(launchState), LAUNCH_STATE_IDLE]; + _launcher setVariable [QGVAR(lastLaunchTime), 0]; + _launcher setVariable [QGVAR(engagedTargets), [[], objNull] call CBA_fnc_hashCreate]; + _launcher setVariable [QEGVAR(missileguidance,target), objNull]; + }] call CBA_fnc_addEventHandler; + + [QGVAR(registerTracker), { + params ["_tracker", "_range"]; + GVAR(trackers) pushBack [_tracker, _range]; + }] call CBA_fnc_addEventHandler; + + [QGVAR(registerInterceptor), { + params ["_interceptor", "_target"]; + GVAR(interceptors) pushBack [_interceptor, _target, getPosASLVisual _interceptor]; + + GVAR(toBeShot) deleteAt (GVAR(toBeShot) find _target); + }] call CBA_fnc_addEventHandler; + + [LINKFUNC(projectileTrackerPFH)] call CBA_fnc_addPerFrameHandler; + [LINKFUNC(proximityFusePFH)] call CBA_fnc_addPerFrameHandler; +}; + +// duplicate event to add event handler +[QGVAR(registerLauncher), { + params ["_launcher"]; + if !(local _launcher) exitWith {}; + _launcher addEventHandler ["Fired", { + params ["_launcher", "", "", "", "", "", "_projectile"]; + private _target = _launcher getVariable [QEGVAR(missileguidance,target), objNull]; + if !(isNull _target) then { + [QGVAR(registerInterceptor), [_projectile, _target]] call CBA_fnc_serverEvent; + }; + }]; +}] call CBA_fnc_addEventHandler; + +// When something is fired, determine if we want to track it. If so, send it to the server for processing +GVAR(projectilesToIntercept) = []; + +[QGVAR(addProjectileToIntercept), { + params ["_type"]; + GVAR(projectilesToIntercept) pushBackUnique _type; +}] call CBA_fnc_addEventHandler; + +["All", "fired", { + params ["", "", "", "", "", "", "_projectile"]; + if (local _projectile && { (typeOf _projectile) in GVAR(projectilesToIntercept) }) then { + [QGVAR(track), [_projectile]] call CBA_fnc_serverEvent; + }; +}] call CBA_fnc_addClassEventHandler; + diff --git a/addons/iron_dome/XEH_preStart.sqf b/addons/iron_dome/XEH_preStart.sqf new file mode 100644 index 0000000000..022888575e --- /dev/null +++ b/addons/iron_dome/XEH_preStart.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +#include "XEH_PREP.hpp" diff --git a/addons/iron_dome/config.cpp b/addons/iron_dome/config.cpp new file mode 100644 index 0000000000..8ef495ab10 --- /dev/null +++ b/addons/iron_dome/config.cpp @@ -0,0 +1,17 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = COMPONENT_NAME; + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = {"ace_common"}; + author = ECSTRING(common,ACETeam); + authors[] = {"Brandon (TCVM)"}; + url = ECSTRING(main,URL); + VERSION_CONFIG; + }; +}; + +#include "CfgEventHandlers.hpp" diff --git a/addons/iron_dome/functions/fnc_projectileTrackerPFH.sqf b/addons/iron_dome/functions/fnc_projectileTrackerPFH.sqf new file mode 100644 index 0000000000..7af18d969c --- /dev/null +++ b/addons/iron_dome/functions/fnc_projectileTrackerPFH.sqf @@ -0,0 +1,194 @@ +#include "script_component.hpp" +/* + * Author: Brandon (TCVM) + * Handles tracking of incoming projectiles per frame + * + * Arguments: + * 0: Args + * 1: Handle + * + * Return Value: + * None + * + * Example: + * [ace_iron_dome_projectileTrackerPFH] call CBA_fnc_addPerFrameHandler + * + * Public: No + */ + +GVAR(trackers) = GVAR(trackers) select { + _x params ["_tracker"]; + alive _tracker +}; + +GVAR(launchers) = GVAR(launchers) select { + alive _x +}; + +GVAR(toBeShot) = GVAR(toBeShot) select { + _x params ["", "_shotTime"]; + (CBA_missionTime - _shotTime) < RECYCLE_TIME +}; + +GVAR(nonTrackingProjectiles) = GVAR(nonTrackingProjectiles) select { + private _projectile = _x; + private _keep = true; + private _bestRange = 1e10; + + { + _x params ["_tracker", "_range"]; + _bestRange = _bestRange min (_projectile distanceSqr _tracker); + if (_projectile distanceSqr _tracker <= _range * _range) exitWith { + GVAR(trackingProjectiles) pushBack [_projectile, 0]; + _keep = false; + }; + } forEach GVAR(trackers); + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1,0,0,1], getPos _projectile, 0.75, 0.75, 0, format ["%1 %2m", typeOf _projectile, sqrt _bestRange], 1, 0.025, "TahomaB"]; + #endif + + _keep +}; + +GVAR(trackingProjectiles) = GVAR(trackingProjectiles) select { + _x params ["_projectile", "_lastFired"]; + + private _keep = false; + if (alive _projectile) then { + { + _x params ["_tracker", "_range"]; + private _withinRange = _projectile distanceSqr _tracker <= _range * _range; + + if (_withinRange) exitWith { + _keep = true; + }; + + } forEach GVAR(trackers); + + if !(_keep) then { + GVAR(nonTrackingProjectiles) pushBack _projectile; + } else { + private _bestLauncher = objNull; + private _bestAmmo = 0; + + private _engagedFuture = GVAR(toBeShot) findIf { + _x params ["_target", "_timeShot"]; + _projectile isEqualTo _target + }; + + private _engagedPast = GVAR(interceptors) findIf { + _x params ["", "_target"]; + _projectile isEqualTo _target; + }; + + private _engaged = (_engagedFuture != -1) || (_engagedPast != -1); + if !(_engaged) then { + // launch a missile + { + // try to get a launcher with the most ammo that is the closest to the incoming projectile + private _state = _x getVariable QGVAR(launchState); + private _ammo = parseNumber (((currentMagazineDetail _x) splitString "([ ]/:)") select 3); + if (_state == LAUNCH_STATE_IDLE && { _ammo >= _bestAmmo }) then { + _bestAmmo = _ammo; + _bestLauncher = _x; + }; + } forEach GVAR(launchers); + + private _targetList = _bestLauncher getVariable QGVAR(targetList); + _targetList pushBackUnique _projectile; + _bestLauncher setVariable [QGVAR(targetList), _targetList]; + + GVAR(toBeShot) pushBackUnique [_projectile, CBA_missionTime]; + }; + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [0,1,0,1], getPos _projectile, 0.75, 0.75, 0, format ["%1 %2m %3s", typeOf _projectile, _bestLauncher distance _projectile], 1, 0.025, "TahomaB"]; + #endif + }; + }; + + _keep +}; + +{ + private _launcher = _x; + private _state = _launcher getVariable QGVAR(launchState); + + switch (_state) do { + case LAUNCH_STATE_IDLE: { + private _targetList = _x getVariable QGVAR(targetList); + private _engagedTargets = _x getVariable QGVAR(engagedTargets); + + _targetList = _targetList select { + private _timeFiredAt = [_engagedTargets, _x, 0] call CBA_fnc_hashGet; + alive _x && { (CBA_missionTime - _timeFiredAt) >= RE_ENGAGEMENT_TIME } + }; + + private _bestTarget = objNull; + private _bestDistance = 1e10; + { + if (_x distanceSqr _launcher < _bestDistance) then { + _bestTarget = _x; + _bestDistance = _x distanceSqr _launcher; + }; + } forEach _targetList; + + if !(isNull _bestTarget) then { + _launcher setVariable [QEGVAR(missileguidance,target), _bestTarget]; + _launcher setVariable [QGVAR(launchState), LAUNCH_STATE_TRACKING]; + }; + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1, 1, 1, 1], getPos _launcher, 0.75, 0.75, 0, format ["IDLE"], 1, 0.025, "TahomaB"]; + #endif + }; + case LAUNCH_STATE_TRACKING: { + private _target = _launcher getVariable QEGVAR(missileguidance,target); + _launcher lookAt getPosVisual _target; + + if (isNull _target) then { + _launcher setVariable [QGVAR(launchState), LAUNCH_STATE_IDLE]; + } else { + private _directionToTarget = (getPosASLVisual _launcher) vectorFromTo (getPosASLVisual _target); + private _turretDirection = _launcher weaponDirection currentWeapon _launcher; + private _angle = acos (_turretDirection vectorCos _directionToTarget); + if (_angle <= LAUNCH_ACCEPTABLE_ANGLE) then { + _launcher setVariable [QGVAR(launchState), LAUNCH_STATE_FIRING]; + }; + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [0, 0, 1, 1], getPos _launcher, 0.75, 0.75, 0, format ["TRACKING: %1", _angle], 1, 0.025, "TahomaB"]; + drawLine3D [getPos _launcher, getPos _target, [0, 0, 1, 1]]; + #endif + }; + }; + case LAUNCH_STATE_FIRING: { + private _turret = [_launcher, (crew _launcher) select 0] call CBA_fnc_turretPath; + [_launcher, _launcher currentWeaponTurret _turret] call BIS_fnc_fire; + + _launcher setVariable [QGVAR(lastLaunchTime), CBA_missionTime]; + _launcher setVariable [QGVAR(launchState), LAUNCH_STATE_COOLDOWN]; + + private _target = _launcher getVariable QEGVAR(missileguidance,target); + private _engagedTargets = _x getVariable QGVAR(engagedTargets); + [_engagedTargets, _target, CBA_missionTime] call CBA_fnc_hashSet; + _x setVariable [QGVAR(engagedTargets), _engagedTargets]; + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [1, 0, 0, 1], getPos _launcher, 0.75, 0.75, 0, format ["FIRING"], 1, 0.025, "TahomaB"]; + #endif + }; + case LAUNCH_STATE_COOLDOWN: { + private _lastLaunchTime = _launcher getVariable QGVAR(lastLaunchTime); + if (CBA_missionTime - _lastLaunchTime >= TIME_BETWEEN_LAUNCHES) then { + _launcher setVariable [QGVAR(launchState), LAUNCH_STATE_IDLE]; + }; + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [0, 0, 1, 1], getPos _launcher, 0.75, 0.75, 0, format ["COOLDOWN %1", CBA_missionTime - _lastLaunchTime], 1, 0.025, "TahomaB"]; + #endif + }; + }; +} forEach GVAR(launchers); + diff --git a/addons/iron_dome/functions/fnc_proximityFusePFH.sqf b/addons/iron_dome/functions/fnc_proximityFusePFH.sqf new file mode 100644 index 0000000000..ac9db887c6 --- /dev/null +++ b/addons/iron_dome/functions/fnc_proximityFusePFH.sqf @@ -0,0 +1,49 @@ +#include "script_component.hpp" +/* + * Author: Brandon (TCVM) + * Handles the fusing and detonation of any and all interceptors in the air + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [ace_iron_dome_proximityFusePFH] call CBA_fnc_addPerFrameHandler + * + * Public: No + */ + +GVAR(interceptors) = GVAR(interceptors) select { + _x params ["_projectile", "_target", "_lastPosition"]; + // Sweep along path to ensure we don't overshoot target + private _minDistance = 0; + + private _currentPosition = getPosASLVisual _projectile; + private _targetPosition = getPosASLVisual _target; + + private _posDiff = (_currentPosition vectorDiff _lastPosition); + private _lengthSqr = _posDiff vectorDotProduct _posDiff; + if (_lengthSqr - 0.001 <= 0) then { + _minDistance = _lastPosition vectorDistance _targetPosition + } else { + private _d = (_targetPosition vectorDiff _lastPosition) vectorDotProduct (_currentPosition vectorDiff _lastPosition); + private _t = 0 max (1 min (_d / _lengthSqr)); + private _projection = _lastPosition vectorAdd ((_currentPosition vectorDiff _lastPosition) vectorMultiply _t); + _minDistance = _projection vectorDistance _targetPosition; + }; + + _x set [2, _currentPosition]; + + #ifdef DRAW_TRACKING_INFO + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\selectover_ca.paa", [0,0,1,1], (getPos _target) vectorAdd [0, 0, 0.5], 0.75, 0.75, 0, format ["%1m", _minDistance], 1, 0.025, "TahomaB"]; + #endif + if (!alive _target || { _minDistance <= PROX_RANGE }) then { + triggerAmmo _projectile; + deleteVehicle _target; + false + } else { + true + } +}; diff --git a/addons/iron_dome/functions/script_component.hpp b/addons/iron_dome/functions/script_component.hpp new file mode 100644 index 0000000000..b9c16cbb9a --- /dev/null +++ b/addons/iron_dome/functions/script_component.hpp @@ -0,0 +1 @@ +#include "\z\ace\addons\iron_dome\script_component.hpp" \ No newline at end of file diff --git a/addons/iron_dome/script_component.hpp b/addons/iron_dome/script_component.hpp new file mode 100644 index 0000000000..7f749aa128 --- /dev/null +++ b/addons/iron_dome/script_component.hpp @@ -0,0 +1,35 @@ +#define COMPONENT iron_dome +#define COMPONENT_BEAUTIFIED Iron Dome +#include "\z\ace\addons\main\script_mod.hpp" + +// #define DRAW_TRACKING_INFO +// #define DEBUG_MODE_FULL +// #define DISABLE_COMPILE_CACHE +// #define ENABLE_PERFORMANCE_COUNTERS + +#ifdef DEBUG_ENABLED_IRON_DOME + #define DEBUG_MODE_FULL +#endif + +#ifdef DEBUG_SETTINGS_IRON_DOME + #define DEBUG_SETTINGS DEBUG_SETTINGS_IRON_DOME +#endif + +#define LAUNCH_STATE_IDLE 0 +#define LAUNCH_STATE_TRACKING 1 +#define LAUNCH_STATE_FIRING 2 +#define LAUNCH_STATE_COOLDOWN 3 + +// How long it takes to recycle a target +#define RECYCLE_TIME 5 +// how many error degrees the launcher has to be pointing toward the target +#define LAUNCH_ACCEPTABLE_ANGLE 30 +// How fast the launcher launches +#define TIME_BETWEEN_LAUNCHES 1 +// how many seconds does a launcher have to wait before re-engaging the same target +#define RE_ENGAGEMENT_TIME 5 + +// Proximity fuse range +#define PROX_RANGE 10 + +#include "\z\ace\addons\main\script_macros.hpp" diff --git a/addons/missileguidance/functions/fnc_IR_onFired.sqf b/addons/missileguidance/functions/fnc_IR_onFired.sqf index 7bfcefe4d9..bc11e9833c 100644 --- a/addons/missileguidance/functions/fnc_IR_onFired.sqf +++ b/addons/missileguidance/functions/fnc_IR_onFired.sqf @@ -14,14 +14,16 @@ * * Public: No */ -params ["_firedEH", "", "", "", "_stateParams"]; +params ["_firedEH", "_launchParams", "", "", "_stateParams"]; _firedEH params ["_shooter","_weapon","","","","","_projectile"]; _stateParams params ["", "_seekerStateParams"]; +_launchParams params ["", "_targetLaunchParams"]; +_targetLaunchParams params ["_target"]; private _flareDistanceFilter = getNumber (configOf _projectile >> QUOTE(ADDON) >> "flareDistanceFilter"); private _flareAngleFilter = getNumber (configOf _projectile >> QUOTE(ADDON) >> "flareAngleFilter"); _seekerStateParams set [0, _flareDistanceFilter]; _seekerStateParams set [1, _flareAngleFilter]; -_seekerStateParams set [2, missileTarget _projectile]; +_seekerStateParams set [2, _target]; diff --git a/addons/missileguidance/functions/fnc_doppler_onFired.sqf b/addons/missileguidance/functions/fnc_doppler_onFired.sqf index 915166a25e..dc37f7890d 100644 --- a/addons/missileguidance/functions/fnc_doppler_onFired.sqf +++ b/addons/missileguidance/functions/fnc_doppler_onFired.sqf @@ -24,7 +24,8 @@ _target = missileTarget _projectile; if (isNull _target && isVehicleRadarOn vehicle _shooter) then { _target = cursorTarget; }; -if !(_target isKindOf "AllVehicles") then { +// always allow tracking of projectiles +if !(_target isKindOf "AllVehicles" || { _target isKindOf ["Default", configFile >> "CfgAmmo"] }) then { _target = nil; }; _launchParams set [0, _target]; diff --git a/addons/missileguidance/script_component.hpp b/addons/missileguidance/script_component.hpp index 70c679a1b9..f105e9444a 100644 --- a/addons/missileguidance/script_component.hpp +++ b/addons/missileguidance/script_component.hpp @@ -2,10 +2,10 @@ #define COMPONENT_BEAUTIFIED Missile Guidance #include "\z\ace\addons\main\script_mod.hpp" - #define DRAW_GUIDANCE_INFO +// #define DRAW_GUIDANCE_INFO // #define ENABLE_PROJECTILE_CAMERA // #define DEBUG_MODE_FULL - #define DISABLE_COMPILE_CACHE +// #define DISABLE_COMPILE_CACHE // #define ENABLE_PERFORMANCE_COUNTERS #ifdef DEBUG_ENABLED_MISSILEGUIDANCE diff --git a/addons/missileguidance/todo.txt b/addons/missileguidance/todo.txt index 2f28ebfa67..206c6d370d 100644 --- a/addons/missileguidance/todo.txt +++ b/addons/missileguidance/todo.txt @@ -91,3 +91,7 @@ General To-Do: X Fix GBU drag X Make sure all applicable pylons can hold all applicable weapons X NLAW is busted: figure out PLOS navigation system + Add 9m14 textures + Add 9m14 animation state where missile doesn't exist + Add 9m14 joystick view rotation (+- 5 deg vertical as well) + Add 9m14 proxy