From f5a838e87d819c02ad83277c3e43aeb8b592febc Mon Sep 17 00:00:00 2001 From: BaerMitUmlaut Date: Mon, 13 Jan 2025 22:20:10 +0100 Subject: [PATCH] Marking Laser - IR marking laser for aircraft (#7761) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: PabstMirror Co-authored-by: Jouni Järvinen Co-authored-by: johnb432 <58661205+johnb432@users.noreply.github.com> Co-authored-by: Grim <69561145+LinkIsGrim@users.noreply.github.com> Co-authored-by: BrettMayson --- addons/markinglaser/$PBOPREFIX$ | 1 + addons/markinglaser/CfgEventHandlers.hpp | 25 +++++++ addons/markinglaser/CfgIRLaserSettings.hpp | 4 + addons/markinglaser/CfgVehicles.hpp | 17 +++++ addons/markinglaser/README.md | 5 ++ addons/markinglaser/XEH_PREP.hpp | 4 + addons/markinglaser/XEH_postInit.sqf | 41 ++++++++++ addons/markinglaser/XEH_preInit.sqf | 9 +++ addons/markinglaser/XEH_preStart.sqf | 3 + addons/markinglaser/config.cpp | 19 +++++ .../markinglaser/functions/fnc_findTurret.sqf | 75 +++++++++++++++++++ .../functions/fnc_onAircraftInit.sqf | 68 +++++++++++++++++ .../markinglaser/functions/fnc_onLaserOn.sqf | 22 ++++++ .../markinglaser/functions/fnc_renderPFH.sqf | 72 ++++++++++++++++++ addons/markinglaser/script_component.hpp | 23 ++++++ addons/markinglaser/stringtable.xml | 33 ++++++++ 16 files changed, 421 insertions(+) create mode 100644 addons/markinglaser/$PBOPREFIX$ create mode 100644 addons/markinglaser/CfgEventHandlers.hpp create mode 100644 addons/markinglaser/CfgIRLaserSettings.hpp create mode 100644 addons/markinglaser/CfgVehicles.hpp create mode 100644 addons/markinglaser/README.md create mode 100644 addons/markinglaser/XEH_PREP.hpp create mode 100644 addons/markinglaser/XEH_postInit.sqf create mode 100644 addons/markinglaser/XEH_preInit.sqf create mode 100644 addons/markinglaser/XEH_preStart.sqf create mode 100644 addons/markinglaser/config.cpp create mode 100644 addons/markinglaser/functions/fnc_findTurret.sqf create mode 100644 addons/markinglaser/functions/fnc_onAircraftInit.sqf create mode 100644 addons/markinglaser/functions/fnc_onLaserOn.sqf create mode 100644 addons/markinglaser/functions/fnc_renderPFH.sqf create mode 100644 addons/markinglaser/script_component.hpp create mode 100644 addons/markinglaser/stringtable.xml diff --git a/addons/markinglaser/$PBOPREFIX$ b/addons/markinglaser/$PBOPREFIX$ new file mode 100644 index 00000000000..52d485cb365 --- /dev/null +++ b/addons/markinglaser/$PBOPREFIX$ @@ -0,0 +1 @@ +z\ace\addons\markinglaser diff --git a/addons/markinglaser/CfgEventHandlers.hpp b/addons/markinglaser/CfgEventHandlers.hpp new file mode 100644 index 00000000000..7b2710a4840 --- /dev/null +++ b/addons/markinglaser/CfgEventHandlers.hpp @@ -0,0 +1,25 @@ +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)); + }; +}; + +class Extended_InitPost_EventHandlers { + class Air { + class ADDON { + init = QUOTE(_this call FUNC(onAircraftInit)); + }; + }; +}; diff --git a/addons/markinglaser/CfgIRLaserSettings.hpp b/addons/markinglaser/CfgIRLaserSettings.hpp new file mode 100644 index 00000000000..2432e338d0e --- /dev/null +++ b/addons/markinglaser/CfgIRLaserSettings.hpp @@ -0,0 +1,4 @@ +class CfgIRLaserSettings { + laserMaxRange = LASER_MAX; // 4000 is max range of the laser in engine + maxViewDistance = 6000; +}; diff --git a/addons/markinglaser/CfgVehicles.hpp b/addons/markinglaser/CfgVehicles.hpp new file mode 100644 index 00000000000..4e9b3fd26c9 --- /dev/null +++ b/addons/markinglaser/CfgVehicles.hpp @@ -0,0 +1,17 @@ +class CfgVehicles { + class AllVehicles; + class Air: AllVehicles { + class Attributes { + class GVAR(enabled) { + displayName = CSTRING(Attribute_Enabled_DisplayName); + tooltip = CSTRING(Attribute_Enabled_Tooltip); + property = QGVAR(enabled); + control = "Checkbox"; + typeName = "BOOL"; + expression = QUOTE(_this setVariable [ARR_3(QQGVAR(enabled),_value,true)]); + defaultValue = "true"; + condition = "objectVehicle"; + }; + }; + }; +}; diff --git a/addons/markinglaser/README.md b/addons/markinglaser/README.md new file mode 100644 index 00000000000..48a05ad4f34 --- /dev/null +++ b/addons/markinglaser/README.md @@ -0,0 +1,5 @@ +ace_markinglaser +=================== + +Adds an IR marking laser for aircraft. + diff --git a/addons/markinglaser/XEH_PREP.hpp b/addons/markinglaser/XEH_PREP.hpp new file mode 100644 index 00000000000..b5aed75c2d7 --- /dev/null +++ b/addons/markinglaser/XEH_PREP.hpp @@ -0,0 +1,4 @@ +PREP(findTurret); +PREP(onAircraftInit); +PREP(onLaserOn); +PREP(renderPFH); diff --git a/addons/markinglaser/XEH_postInit.sqf b/addons/markinglaser/XEH_postInit.sqf new file mode 100644 index 00000000000..231000bc63d --- /dev/null +++ b/addons/markinglaser/XEH_postInit.sqf @@ -0,0 +1,41 @@ +#include "script_component.hpp" +#include "\a3\ui_f\hpp\defineDIKCodes.inc" + +if (!hasInterface) exitWith {}; + +// Keybinds +["ACE3 Vehicles", QGVAR(toggleLaser), LLSTRING(ToggleLaser), { + // Ignore when in Zeus + if (!isNull curatorCamera) exitWith {false}; + + private _vehicle = cameraOn; + if !(_vehicle getVariable [QGVAR(enabled), false]) exitWith {false}; + + private _controlledUnit = [ACE_player, ACE_controlledUAV # 1] select (unitIsUAV _vehicle); + + private _turretInfo = _vehicle getVariable [QGVAR(turretInfo), []]; + private _canTurnOn = _controlledUnit == _vehicle turretUnit _turretInfo; + if (!_canTurnOn) exitWith { false }; + + playSound "ACE_Sound_Click"; + private _currentMode = _vehicle getVariable [QGVAR(laserMode), MODE_OFF]; + private _newMode = (_currentMode + 1) % 3; + _vehicle setVariable [QGVAR(laserMode), _newMode, true]; + true +}, "", [DIK_L, [false, false, true]]] call CBA_fnc_addKeybind; // ALT-L + +["CBA_settingsInitialized", { + TRACE_1("settingsInitialized",1); + + GVAR(pfEH) = -1; + ["visionMode", LINKFUNC(onLaserOn), true] call CBA_fnc_addPlayerEventHandler; + ["ace_laserOn", { + params ["", "_args"]; + _args params ["_object"]; + if !(_object getVariable [QGVAR(enabled), false]) exitWith {}; + _object setVariable [QGVAR(smoothing), []]; + _object setVariable [QGVAR(flashOffset), random 1]; // make flashes not synchronized + + [] call LINKFUNC(onLaserOn); + }] call CBA_fnc_addEventHandler; +}] call CBA_fnc_addEventHandler; diff --git a/addons/markinglaser/XEH_preInit.sqf b/addons/markinglaser/XEH_preInit.sqf new file mode 100644 index 00000000000..b47cf6628db --- /dev/null +++ b/addons/markinglaser/XEH_preInit.sqf @@ -0,0 +1,9 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +ADDON = true; diff --git a/addons/markinglaser/XEH_preStart.sqf b/addons/markinglaser/XEH_preStart.sqf new file mode 100644 index 00000000000..022888575ed --- /dev/null +++ b/addons/markinglaser/XEH_preStart.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +#include "XEH_PREP.hpp" diff --git a/addons/markinglaser/config.cpp b/addons/markinglaser/config.cpp new file mode 100644 index 00000000000..23e8bfb6e01 --- /dev/null +++ b/addons/markinglaser/config.cpp @@ -0,0 +1,19 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = COMPONENT_NAME; + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = {"ace_common", "ace_laser"}; + author = ECSTRING(common,ACETeam); + authors[] = {"BaerMitUmlaut"}; + url = ECSTRING(main,URL); + VERSION_CONFIG; + }; +}; + +#include "CfgIRLaserSettings.hpp" +#include "CfgEventHandlers.hpp" +#include "CfgVehicles.hpp" diff --git a/addons/markinglaser/functions/fnc_findTurret.sqf b/addons/markinglaser/functions/fnc_findTurret.sqf new file mode 100644 index 00000000000..19b3f36fa3d --- /dev/null +++ b/addons/markinglaser/functions/fnc_findTurret.sqf @@ -0,0 +1,75 @@ +#include "..\script_component.hpp" +/* + * Author: BaerMitUmlaut + * Finds the turret that has control over the marking laser. + * + * Arguments: + * 0: Aircraft config + * + * Return Value: + * + * + * Example: + * [configOf _plane] call ace_markinglaser_fnc_findTurret + * + * Public: No + */ + +params ["_config"]; + +private _copilotPath = nil; +private _copilotConfig = configNull; +private _primaryPath = nil; +private _primaryConfig = configNull; +private _isUAV = getNumber (_config >> "isUAV") == 1; + +private _walkTurrets = { + params ["_path", "_turrets"]; + + { + // Check turret rotation for symmetry, filters door gunner turrets + if (abs getNumber (_x >> "minTurn") != abs getNumber (_x >> "maxTurn")) then { + continue; + }; + + // Check if turret has a optics with night or thermal vision + private _visionModes = flatten (("true" configClasses (_x >> "OpticsIn")) apply { + (getArray (_x >> "visionMode")) apply {toLowerANSI _x} + }); + + if !("nvg" in _visionModes || {"ti" in _visionModes}) then { + continue; + }; + + // Use copilot turret if possible + // Not all helicopter gun turrets use this flag (for example the Kajman) + if (getNumber (_x >> "isCopilot") == 1) then { + _copilotPath = _path + [_forEachIndex]; + _copilotConfig = _x; + break; + }; + + // Fallback to primary gunner + if (isNil "_primaryPath" && {getNumber (_x >> "primaryGunner") == 1}) then { + _primaryPath = _path + [_forEachIndex]; + _primaryConfig = _x; + }; + + // Search subturrets + if (isClass (_x >> "Turrets")) then { + private _turrets = "true" configClasses (_x >> "Turrets"); + [_path + [_forEachIndex], _turrets] call _walkTurrets; + }; + } forEach _turrets; +}; + +[[], "true" configClasses (_config >> "Turrets")] call _walkTurrets; + +if (!isNil "_copilotPath") exitWith { + [_copilotPath, _copilotConfig] +}; +if (!isNil "_primaryPath") exitWith { + [_primaryPath, _primaryConfig] +}; + +[] diff --git a/addons/markinglaser/functions/fnc_onAircraftInit.sqf b/addons/markinglaser/functions/fnc_onAircraftInit.sqf new file mode 100644 index 00000000000..b6ba81308d9 --- /dev/null +++ b/addons/markinglaser/functions/fnc_onAircraftInit.sqf @@ -0,0 +1,68 @@ +#include "..\script_component.hpp" +/* + * Author: BaerMitUmlaut + * Equips an aircraft with a marking laser. + * + * Arguments: + * 0: Aircraft + * + * Return Value: + * None + * + * Example: + * [plane] call ace_markinglaser_fnc_onAircraftInit + * + * Public: No + */ + +params ["_aircraft"]; +TRACE_2("onAircraftInit",_aircraft,typeOf _aircraft); + +// Assume enabled by default +if !(_aircraft getVariable [QGVAR(enabled), true]) exitWith {}; + +private _config = configOf _aircraft; +private _turretData = [_config] call FUNC(findTurret); +private _hasPilotCamera = getNumber (_config >> "PilotCamera" >> "controllable") > 0; + +if ((_turretData isEqualTo []) && {!_hasPilotCamera}) exitWith { + _aircraft setVariable [QGVAR(enabled), false]; + WARNING_1("Class %1 does not have a pilot camera nor a turret that could be equipped with an IR marking laser.",configName _config); +}; + +_aircraft setVariable [QGVAR(enabled), true]; +_aircraft setVariable [QGVAR(smoothing), []]; + +if (_turretData isEqualTo []) then { + TRACE_1("pilot",_turretData); + // Use pilot camera if no turrets are available + _aircraft setVariable [QGVAR(turretInfo), [-1]]; +} else { + TRACE_1("turret",_turretData); + _turretData params ["_turretPath"]; + _aircraft setVariable [QGVAR(turretInfo), _turretPath]; +}; + + +TRACE_1("Add interaction",_aircraft); +private _condition = { + if !(_target getVariable [QGVAR(enabled), false]) exitWith {false}; + private _controlledUnit = [ACE_player, ACE_controlledUAV # 1] select (unitIsUAV _target); + private _turretInfo = _target getVariable [QGVAR(turretInfo), []]; + private _canTurnOn = _controlledUnit == _target turretUnit _turretInfo; + _canTurnOn +}; + +private _getChildren = { + private _statement = { + _target setVariable [QGVAR(laserMode), _actionParams, true]; + }; + private _current = _target getVariable [QGVAR(laserMode), 0]; + [[MODE_OFF, LELSTRING(common,disabled)], [MODE_ON, LELSTRING(common,enabled)], [MODE_FLASH, LLSTRING(Flashing)]] apply { + _x params ["_mode", "_text"]; + if (_current == _mode) then { _text = ">" + _text }; + [[str _mode, _text, "", _statement, {true}, {}, _mode] call EFUNC(interact_menu,createAction), [], _target] + } +}; +private _actionBase = [QGVAR(actionBase), LLSTRING(Name), "", {}, _condition, _getChildren] call EFUNC(interact_menu,createAction); +private _basePath = [_aircraft, 1, ["ACE_SelfActions"], _actionBase] call EFUNC(interact_menu,addActionToObject); diff --git a/addons/markinglaser/functions/fnc_onLaserOn.sqf b/addons/markinglaser/functions/fnc_onLaserOn.sqf new file mode 100644 index 00000000000..1f82f7e1b87 --- /dev/null +++ b/addons/markinglaser/functions/fnc_onLaserOn.sqf @@ -0,0 +1,22 @@ +#include "..\script_component.hpp" +/* + * Author: BaerMitUmlaut, PabstMirror + * Starts render PFEH + * + * Arguments: + * 0: Aircraft + * + * Return Value: + * None + * + * Example: + * [] call ace_markinglaser_fnc_onLaserOn + * + * Public: No + */ + +if (GVAR(pfEH) != -1) exitWith {}; +if (((currentVisionMode focusOn) != 1) || {EGVAR(laser,laserEmitters) isEqualTo []}) exitWith {}; + +GVAR(pfEH) = [LINKFUNC(renderPFH), 0, []] call CBA_fnc_addPerFrameHandler; +TRACE_1("start PFEH",GVAR(pfEH)); diff --git a/addons/markinglaser/functions/fnc_renderPFH.sqf b/addons/markinglaser/functions/fnc_renderPFH.sqf new file mode 100644 index 00000000000..18e2a724bdb --- /dev/null +++ b/addons/markinglaser/functions/fnc_renderPFH.sqf @@ -0,0 +1,72 @@ +#include "..\script_component.hpp" +/* + * Author: BaerMitUmlaut, PabstMirror + * Renders all marking lasers. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_markinglaser_fnc_renderPFH + * + * Public: No + */ + +if (((currentVisionMode focusOn) != 1) || {EGVAR(laser,laserEmitters) isEqualTo []}) exitWith { + GVAR(pfEH) call CBA_fnc_removePerFrameHandler; + GVAR(pfEH) = -1; + TRACE_1("end PFEH",GVAR(pfEH)); +}; + +{ + _y params ["_aircraft", "", "_laserMethod"]; + + private _currentMode = _aircraft getVariable [QGVAR(laserMode), MODE_OFF]; + if (_currentMode == MODE_OFF) then { continue }; + if (_laserMethod != QEFUNC(laser,findLaserSource)) then { continue }; // Normal vanilla laserTarget func + + if ((_currentMode == MODE_FLASH) && { + private _cycle = (CBA_missionTime + (_aircraft getVariable [QGVAR(flashOffset), 0])) % (1/3); + _cycle > 1/6 + }) then { continue }; + + (_y call EFUNC(laser,findLaserSource)) params ["_laserPosASL", "_laserDir"]; + + #ifdef DEBUG_MODE_FULL + private _targetObject = _aircraft getVariable [QEGVAR(laser,targetObject), objNull]; + private _targetPosASL = getPosASL _targetObject; + + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\select_target_ca.paa", [1,0,1,1], (ASLToAGL _targetPosASL), 0.5, 0.5, 0, "Laser", 0.5, 0.025, "TahomaB"]; + drawIcon3D ["\a3\ui_f\data\IGUI\Cfg\Cursors\select_target_ca.paa", [1,0,1,1], (ASLToAGL _laserPosASL), 0.5, 0.5, 0, "Origin", 0.5, 0.025, "TahomaB"]; + #endif + + // If our camera is the laser source, offset it just a bit so it isn't dead center + if (((AGLToASL positionCameraToWorld [0,0,0]) distance _laserPosASL) < 0.09) then { + _laserPosASL = AGLToASL positionCameraToWorld [-0.02, -0.05, 0]; + }; + + private _smoothing = _aircraft getVariable [QGVAR(smoothing), []]; + _smoothing pushBack _laserDir; + if (count _smoothing > 5) then { _smoothing deleteAt 0 }; + private _smoothDir = [0,0,0]; + { _smoothDir = _smoothDir vectorAdd _x } forEach _smoothing; + _smoothDir = _smoothDir vectorMultiply (1/count _smoothing); + + private _startPos = _laserPosASL vectorAdd (_smoothDir vectorMultiply 0.25); // go forward a bit so first drawLaser doesn't hit aircraft + private _endPos = _laserPosASL vectorAdd (_smoothDir vectorMultiply LASER_MAX); + private _intersects = []; + while { _intersects isEqualTo [] } do { + // drawLaser has an internal maximum distance it can draw, so we may need to draw multiple segments + drawLaser [_startPos, _smoothDir, [250, 0, 0, 1], [], 0, 1, LASER_MAX, true]; // Draw a segment + if ((_startPos distance _laserPosASL) > 9999) exitWith {}; // just exit loop if we've drawn far enough + _intersects = lineIntersectsSurfaces [_startPos, _endPos, _aircraft]; + if (_intersects isEqualTo []) then { // Check if we hit anything + _startPos = _endPos; // if we didn't then move up the startpos to where the last draw ended + _endPos = _endPos vectorAdd (_smoothDir vectorMultiply LASER_MAX); + }; + }; + drawLaser [_laserPosASL, _smoothDir, [250, 0, 0, 1], [], 0.5, 1, LASER_MAX, true]; // final draw from actual origin +} forEach EGVAR(laser,laserEmitters); diff --git a/addons/markinglaser/script_component.hpp b/addons/markinglaser/script_component.hpp new file mode 100644 index 00000000000..57b051e0786 --- /dev/null +++ b/addons/markinglaser/script_component.hpp @@ -0,0 +1,23 @@ +#define COMPONENT markinglaser +#define COMPONENT_BEAUTIFIED Marking Laser +#include "\z\ace\addons\main\script_mod.hpp" + +// #define DEBUG_MODE_FULL +// #define DISABLE_COMPILE_CACHE +// #define ENABLE_PERFORMANCE_COUNTERS + +#ifdef DEBUG_ENABLED_MARKINGLASER + #define DEBUG_MODE_FULL +#endif + +#ifdef DEBUG_SETTINGS_MARKINGLASER + #define DEBUG_SETTINGS DEBUG_SETTINGS_MARKINGLASER +#endif + +#include "\z\ace\addons\main\script_macros.hpp" + +#define LASER_MAX 3000 + +#define MODE_OFF 0 +#define MODE_ON 1 +#define MODE_FLASH 2 diff --git a/addons/markinglaser/stringtable.xml b/addons/markinglaser/stringtable.xml new file mode 100644 index 00000000000..205e4ab652e --- /dev/null +++ b/addons/markinglaser/stringtable.xml @@ -0,0 +1,33 @@ + + + + + IR marking laser + IR Markierungslaser + + + Blinking + Blinken + + + Toggle IR marking laser + IR Markierungslaser umschalten + + + Equip with IR marking laser + Mit IR Markierungslaser ausstatten + + + Equips the aircraft's main turret or pilot camera with an IR marking laser. + Rüstet das Hauptgeschütz oder die Pilotenkamera des Luftfahrzeugs mit einem IR Markierungslaser aus. + + + Enable IR marking lasers for aircraft + IR Markierungslaser für Luftfahrzeuge aktivieren + + + Enables the usage of IR marking lasers for aircraft. + Ermöglicht die Nutzung von IR Markierungslasern für Luftfahrzeuge. + + +