From d3ce75daef791062f233bc681b2ba14035c509ad Mon Sep 17 00:00:00 2001 From: SilentSpike Date: Sat, 12 Aug 2017 14:25:48 +0100 Subject: [PATCH] Spectator overhaul (#5171) - Overhauls the spectator module entirely to share a similar UX to BI's "End Game Spectator" while maintaining some of the extended flexibility of ACE Spectator. - Simplifies spectator setup by reducing the number of settings. More advanced setup is still possible via the API functions provided. --- addons/common/XEH_PREP.hpp | 2 + .../common/functions/fnc_getVehicleIcon.sqf | 50 ++ addons/common/functions/fnc_isMedic.sqf | 23 + addons/spectator/ACE_Settings.hpp | 24 +- addons/spectator/CfgEventHandlers.hpp | 12 + addons/spectator/CfgVehicles.hpp | 79 +- addons/spectator/UI/interface.hpp | 255 ------ addons/spectator/XEH_PREP.hpp | 74 +- addons/spectator/XEH_postInit.sqf | 62 +- addons/spectator/XEH_preInit.sqf | 38 +- addons/spectator/config.cpp | 4 +- .../{UI => data}/Icon_Module_Spectator_ca.paa | Bin addons/spectator/data/b_air.paa | Bin 0 -> 22981 bytes addons/spectator/data/b_armor.paa | Bin 0 -> 22978 bytes addons/spectator/data/b_art.paa | Bin 0 -> 22637 bytes addons/spectator/data/b_inf.paa | Bin 0 -> 23199 bytes addons/spectator/data/b_installation.paa | Bin 0 -> 22554 bytes addons/spectator/data/b_maint.paa | Bin 0 -> 22889 bytes addons/spectator/data/b_mech_inf.paa | Bin 0 -> 23414 bytes addons/spectator/data/b_med.paa | Bin 0 -> 22633 bytes addons/spectator/data/b_mortar.paa | Bin 0 -> 22811 bytes addons/spectator/data/b_motor_inf.paa | Bin 0 -> 23260 bytes addons/spectator/data/b_naval.paa | Bin 0 -> 22929 bytes addons/spectator/data/b_plane.paa | Bin 0 -> 22925 bytes addons/spectator/data/b_recon.paa | Bin 0 -> 22872 bytes addons/spectator/data/b_service.paa | Bin 0 -> 22872 bytes addons/spectator/data/b_support.paa | Bin 0 -> 22489 bytes addons/spectator/data/b_uav.paa | Bin 0 -> 22660 bytes addons/spectator/data/b_unknown.paa | Bin 0 -> 22488 bytes addons/spectator/data/c_air.paa | Bin 0 -> 23402 bytes addons/spectator/data/c_car.paa | Bin 0 -> 22828 bytes addons/spectator/data/c_plane.paa | Bin 0 -> 22757 bytes addons/spectator/data/c_ship.paa | Bin 0 -> 22967 bytes addons/spectator/data/c_unknown.paa | Bin 0 -> 22848 bytes .../spectator/functions/fnc_addLocation.sqf | 73 ++ .../spectator/functions/fnc_cacheUnitInfo.sqf | 38 - addons/spectator/functions/fnc_cam.sqf | 143 ++++ .../functions/fnc_cam_prepareTarget.sqf | 51 ++ .../functions/fnc_cam_resetTarget.sqf | 29 + .../functions/fnc_cam_setCameraMode.sqf | 89 +++ .../spectator/functions/fnc_cam_setTarget.sqf | 32 + .../functions/fnc_cam_setVisionMode.sqf | 45 ++ addons/spectator/functions/fnc_cam_tick.sqf | 84 ++ .../functions/fnc_cam_toggleSlow.sqf | 36 + .../functions/fnc_compat_counter.sqf | 36 + .../functions/fnc_compat_spectatorBI.sqf | 53 ++ .../spectator/functions/fnc_compat_zeus.sqf | 34 + .../spectator/functions/fnc_cycleCamera.sqf | 58 -- .../functions/fnc_getCameraAttributes.sqf | 30 + .../spectator/functions/fnc_getGroupIcon.sqf | 162 ++++ .../functions/fnc_getTargetEntities.sqf | 45 ++ .../spectator/functions/fnc_handleCamera.sqf | 75 -- .../spectator/functions/fnc_handleCompass.sqf | 60 -- .../spectator/functions/fnc_handleFired.sqf | 48 ++ .../spectator/functions/fnc_handleIcons.sqf | 46 -- .../functions/fnc_handleInterface.sqf | 494 ------------ addons/spectator/functions/fnc_handleMap.sqf | 67 -- .../spectator/functions/fnc_handleMouse.sqf | 46 -- .../spectator/functions/fnc_handleToolbar.sqf | 59 -- .../spectator/functions/fnc_handleUnits.sqf | 40 - addons/spectator/functions/fnc_interrupt.sqf | 31 +- .../functions/fnc_moduleSpectatorSettings.sqf | 6 +- addons/spectator/functions/fnc_players.sqf | 19 + .../functions/fnc_removeLocation.sqf | 39 + .../functions/fnc_respawnTemplate.sqf | 39 +- .../functions/fnc_setCameraAttributes.sqf | 108 ++- addons/spectator/functions/fnc_setFocus.sqf | 68 ++ .../spectator/functions/fnc_setSpectator.sqf | 148 ++-- .../functions/fnc_stageSpectator.sqf | 51 +- .../spectator/functions/fnc_switchFocus.sqf | 31 + .../functions/fnc_toggleInterface.sqf | 82 -- .../functions/fnc_transitionCamera.sqf | 115 --- addons/spectator/functions/fnc_ui.sqf | 146 ++++ addons/spectator/functions/fnc_ui_draw3D.sqf | 156 ++++ .../spectator/functions/fnc_ui_fadeList.sqf | 47 ++ .../functions/fnc_ui_getTreeDataIndex.sqf | 45 ++ .../functions/fnc_ui_handleChildDestroyed.sqf | 28 + .../functions/fnc_ui_handleKeyDown.sqf | 209 +++++ .../functions/fnc_ui_handleKeyUp.sqf | 31 + .../functions/fnc_ui_handleListClick.sqf | 110 +++ .../functions/fnc_ui_handleMapClick.sqf | 40 + .../functions/fnc_ui_handleMapDraw.sqf | 96 +++ .../fnc_ui_handleMouseButtonDblClick.sqf | 27 + .../fnc_ui_handleMouseButtonDown.sqf | 50 ++ .../functions/fnc_ui_handleMouseMoving.sqf | 31 + .../functions/fnc_ui_handleMouseZChanged.sqf | 27 + .../functions/fnc_ui_handleTabSelected.sqf | 37 + .../spectator/functions/fnc_ui_toggleMap.sqf | 46 ++ .../spectator/functions/fnc_ui_toggleUI.sqf | 34 + .../functions/fnc_ui_updateCamButtons.sqf | 38 + .../spectator/functions/fnc_ui_updateHelp.sqf | 138 ++++ .../functions/fnc_ui_updateIconsToDraw.sqf | 162 ++++ .../functions/fnc_ui_updateListEntities.sqf | 222 ++++++ .../functions/fnc_ui_updateListFocus.sqf | 22 + .../functions/fnc_ui_updateListLocations.sqf | 69 ++ .../functions/fnc_ui_updateWidget.sqf | 70 ++ .../functions/fnc_updateCameraModes.sqf | 19 +- ...ectatableSides.sqf => fnc_updateSides.sqf} | 7 +- .../spectator/functions/fnc_updateUnits.sqf | 65 +- .../functions/fnc_updateVisionModes.sqf | 10 +- addons/spectator/script_component.hpp | 137 +++- addons/spectator/stringtable.xml | 742 +----------------- addons/spectator/ui.hpp | 450 +++++++++++ 103 files changed, 4083 insertions(+), 2561 deletions(-) create mode 100644 addons/common/functions/fnc_getVehicleIcon.sqf create mode 100644 addons/common/functions/fnc_isMedic.sqf delete mode 100644 addons/spectator/UI/interface.hpp rename addons/spectator/{UI => data}/Icon_Module_Spectator_ca.paa (100%) create mode 100644 addons/spectator/data/b_air.paa create mode 100644 addons/spectator/data/b_armor.paa create mode 100644 addons/spectator/data/b_art.paa create mode 100644 addons/spectator/data/b_inf.paa create mode 100644 addons/spectator/data/b_installation.paa create mode 100644 addons/spectator/data/b_maint.paa create mode 100644 addons/spectator/data/b_mech_inf.paa create mode 100644 addons/spectator/data/b_med.paa create mode 100644 addons/spectator/data/b_mortar.paa create mode 100644 addons/spectator/data/b_motor_inf.paa create mode 100644 addons/spectator/data/b_naval.paa create mode 100644 addons/spectator/data/b_plane.paa create mode 100644 addons/spectator/data/b_recon.paa create mode 100644 addons/spectator/data/b_service.paa create mode 100644 addons/spectator/data/b_support.paa create mode 100644 addons/spectator/data/b_uav.paa create mode 100644 addons/spectator/data/b_unknown.paa create mode 100644 addons/spectator/data/c_air.paa create mode 100644 addons/spectator/data/c_car.paa create mode 100644 addons/spectator/data/c_plane.paa create mode 100644 addons/spectator/data/c_ship.paa create mode 100644 addons/spectator/data/c_unknown.paa create mode 100644 addons/spectator/functions/fnc_addLocation.sqf delete mode 100644 addons/spectator/functions/fnc_cacheUnitInfo.sqf create mode 100644 addons/spectator/functions/fnc_cam.sqf create mode 100644 addons/spectator/functions/fnc_cam_prepareTarget.sqf create mode 100644 addons/spectator/functions/fnc_cam_resetTarget.sqf create mode 100644 addons/spectator/functions/fnc_cam_setCameraMode.sqf create mode 100644 addons/spectator/functions/fnc_cam_setTarget.sqf create mode 100644 addons/spectator/functions/fnc_cam_setVisionMode.sqf create mode 100644 addons/spectator/functions/fnc_cam_tick.sqf create mode 100644 addons/spectator/functions/fnc_cam_toggleSlow.sqf create mode 100644 addons/spectator/functions/fnc_compat_counter.sqf create mode 100644 addons/spectator/functions/fnc_compat_spectatorBI.sqf create mode 100644 addons/spectator/functions/fnc_compat_zeus.sqf delete mode 100644 addons/spectator/functions/fnc_cycleCamera.sqf create mode 100644 addons/spectator/functions/fnc_getCameraAttributes.sqf create mode 100644 addons/spectator/functions/fnc_getGroupIcon.sqf create mode 100644 addons/spectator/functions/fnc_getTargetEntities.sqf delete mode 100644 addons/spectator/functions/fnc_handleCamera.sqf delete mode 100644 addons/spectator/functions/fnc_handleCompass.sqf create mode 100644 addons/spectator/functions/fnc_handleFired.sqf delete mode 100644 addons/spectator/functions/fnc_handleIcons.sqf delete mode 100644 addons/spectator/functions/fnc_handleInterface.sqf delete mode 100644 addons/spectator/functions/fnc_handleMap.sqf delete mode 100644 addons/spectator/functions/fnc_handleMouse.sqf delete mode 100644 addons/spectator/functions/fnc_handleToolbar.sqf delete mode 100644 addons/spectator/functions/fnc_handleUnits.sqf create mode 100644 addons/spectator/functions/fnc_players.sqf create mode 100644 addons/spectator/functions/fnc_removeLocation.sqf create mode 100644 addons/spectator/functions/fnc_setFocus.sqf create mode 100644 addons/spectator/functions/fnc_switchFocus.sqf delete mode 100644 addons/spectator/functions/fnc_toggleInterface.sqf delete mode 100644 addons/spectator/functions/fnc_transitionCamera.sqf create mode 100644 addons/spectator/functions/fnc_ui.sqf create mode 100644 addons/spectator/functions/fnc_ui_draw3D.sqf create mode 100644 addons/spectator/functions/fnc_ui_fadeList.sqf create mode 100644 addons/spectator/functions/fnc_ui_getTreeDataIndex.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleChildDestroyed.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleKeyDown.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleKeyUp.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleListClick.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleMapClick.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleMapDraw.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleMouseButtonDblClick.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleMouseButtonDown.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleMouseMoving.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleMouseZChanged.sqf create mode 100644 addons/spectator/functions/fnc_ui_handleTabSelected.sqf create mode 100644 addons/spectator/functions/fnc_ui_toggleMap.sqf create mode 100644 addons/spectator/functions/fnc_ui_toggleUI.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateCamButtons.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateHelp.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateIconsToDraw.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateListEntities.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateListFocus.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateListLocations.sqf create mode 100644 addons/spectator/functions/fnc_ui_updateWidget.sqf rename addons/spectator/functions/{fnc_updateSpectatableSides.sqf => fnc_updateSides.sqf} (73%) create mode 100644 addons/spectator/ui.hpp diff --git a/addons/common/XEH_PREP.hpp b/addons/common/XEH_PREP.hpp index 5720d31873..868e5b27a5 100644 --- a/addons/common/XEH_PREP.hpp +++ b/addons/common/XEH_PREP.hpp @@ -77,6 +77,7 @@ PREP(getTurretDirection); PREP(getUavControlPosition); PREP(getVehicleCargo); PREP(getVehicleCodriver); +PREP(getVehicleIcon); PREP(getVersion); PREP(getWeaponAzimuthAndInclination); PREP(getWeaponIndex); @@ -99,6 +100,7 @@ PREP(isEngineer); PREP(isEOD); PREP(isFeatureCameraActive); PREP(isInBuilding); +PREP(isMedic); PREP(isModLoaded); PREP(isPlayer); PREP(isUnderwater); diff --git a/addons/common/functions/fnc_getVehicleIcon.sqf b/addons/common/functions/fnc_getVehicleIcon.sqf new file mode 100644 index 0000000000..1974c57b71 --- /dev/null +++ b/addons/common/functions/fnc_getVehicleIcon.sqf @@ -0,0 +1,50 @@ +/* + * Author: AACO + * Function used to get the vehicle icon for provided object (cached for repeat use) + * + * Arguments: + * 0: Object to get icon of + * + * Return Value: + * Icon of vehicle + * + * Examples: + * ["B_Soldier_F"] call ace_common_fnc_getVehicleIcon; + * + * Public: Yes + */ + +#include "script_component.hpp" +#define DEFAULT_TEXTURE "\A3\ui_f\data\Map\VehicleIcons\iconVehicle_ca.paa" + +params [["_object", objNull, [objNull, ""]]]; + +if ((_object isEqualType objNull && {isNull _object}) || {_object isEqualType "" && {_object == ""}}) exitWith { DEFAULT_TEXTURE }; + +ISNILS(GVAR(vehicleIconCache),call CBA_fnc_createNamespace); + +private _objectType = if (_object isEqualType objNull) then { + typeOf _object +} else { + _object +}; +private _cachedValue = GVAR(vehicleIconCache) getVariable _objectType; + +if (isNil "_cachedValue") then { + private _vehicleValue = getText (configfile >> "CfgVehicles" >> _objectType >> "icon"); + private _vehicleIconValue = getText (configfile >> "CfgVehicleIcons" >> _vehicleValue); + + if (_vehicleIconValue == "") then { + if (_vehicleValue != "" && {((toLower _vehicleValue) find ".paa") > -1}) then { + _cachedValue = _vehicleValue; + } else { + _cachedValue = DEFAULT_TEXTURE; + }; + } else { + _cachedValue = _vehicleIconValue; + }; + + GVAR(vehicleIconCache) setVariable [_objectType, _cachedValue]; +}; + +_cachedValue diff --git a/addons/common/functions/fnc_isMedic.sqf b/addons/common/functions/fnc_isMedic.sqf new file mode 100644 index 0000000000..2a0d0dc520 --- /dev/null +++ b/addons/common/functions/fnc_isMedic.sqf @@ -0,0 +1,23 @@ +/* + * Author: SilentSpike + * Check if a unit is a medic + * + * Arguments: + * 0: The Unit + * + * ReturnValue: + * Unit is medic + * + * Example: + * [player] call ace_common_fnc_isMedic + * + * Public: Yes + */ + +#include "script_component.hpp" + +params ["_unit"]; + +private _isMedic = _unit getVariable [QEGVAR(medical,medicClass), getNumber (configFile >> "CfgVehicles" >> typeOf _unit >> "attendant")]; + +_isMedic > 0 diff --git a/addons/spectator/ACE_Settings.hpp b/addons/spectator/ACE_Settings.hpp index 31e4ea3fd0..cc69bfc39d 100644 --- a/addons/spectator/ACE_Settings.hpp +++ b/addons/spectator/ACE_Settings.hpp @@ -1,24 +1,16 @@ class ACE_Settings { - class GVAR(filterUnits) { - displayName = CSTRING(units_DisplayName); - description = CSTRING(units_Description); - typeName = "SCALAR"; - value = 2; - values[] = {CSTRING(units_none), CSTRING(units_players), CSTRING(units_playable), CSTRING(units_all)}; - }; - class GVAR(filterSides) { - displayName = CSTRING(sides_DisplayName); - description = CSTRING(sides_Description); - typeName = "SCALAR"; + class GVAR(enableAI) { + displayName = CSTRING(ai_DisplayName); + description = CSTRING(ai_Description); + typeName = "BOOL"; value = 0; - values[] = {CSTRING(sides_player), CSTRING(sides_friendly), CSTRING(sides_hostile), CSTRING(sides_all)}; }; class GVAR(restrictModes) { displayName = CSTRING(modes_DisplayName); description = CSTRING(modes_Description); typeName = "SCALAR"; value = 0; - values[] = {CSTRING(modes_all), CSTRING(modes_unit), CSTRING(modes_free), CSTRING(modes_internal), CSTRING(modes_external)}; + values[] = {CSTRING(modes_all), CSTRING(modes_unit), "$STR_A3_Spectator_free_camera_tooltip", "$STR_A3_Spectator_1pp_camera_tooltip", "$STR_A3_Spectator_3pp_camera_tooltip"}; }; class GVAR(restrictVisions) { displayName = CSTRING(visions_DisplayName); @@ -27,4 +19,10 @@ class ACE_Settings { value = 0; values[] = {CSTRING(modes_all), CSTRING(visions_nv), CSTRING(visions_ti), "$STR_Special_None"}; }; + class GVAR(mapLocations) { + displayName = CSTRING(mapLocations_DisplayName); + description = CSTRING(mapLocations_Description); + typeName = "BOOL"; + value = 0; + }; }; diff --git a/addons/spectator/CfgEventHandlers.hpp b/addons/spectator/CfgEventHandlers.hpp index becf395052..7abe7ca4e3 100644 --- a/addons/spectator/CfgEventHandlers.hpp +++ b/addons/spectator/CfgEventHandlers.hpp @@ -16,3 +16,15 @@ class Extended_PostInit_EventHandlers { init = QUOTE(call COMPILE_FILE(XEH_postInit)); }; }; + +class Extended_DisplayLoad_EventHandlers { + class RscRespawnCounter { + ADDON = QUOTE(_this call FUNC(compat_counter)); + }; + class RscDisplayEGSpectator { + ADDON = QUOTE(_this call FUNC(compat_spectatorBI)); + }; + class RscDisplayCurator { + ADDON = QUOTE(_this call FUNC(compat_zeus)); + }; +}; diff --git a/addons/spectator/CfgVehicles.hpp b/addons/spectator/CfgVehicles.hpp index 268aabbef5..850c1c9db3 100644 --- a/addons/spectator/CfgVehicles.hpp +++ b/addons/spectator/CfgVehicles.hpp @@ -3,59 +3,17 @@ class CfgVehicles { class GVAR(moduleSettings): ACE_Module { scope = 2; displayName = CSTRING(Settings_DisplayName); - icon = QPATHTOF(UI\Icon_Module_Spectator_ca.paa); + icon = QPATHTOF(data\Icon_Module_Spectator_ca.paa); category = "ACE"; function = QFUNC(moduleSpectatorSettings); isGlobal = 1; author = ECSTRING(common,ACETeam); class Arguments { - class unitsFilter { - displayName = CSTRING(units_DisplayName); - description = CSTRING(units_Description); - typeName = "NUMBER"; - class values { - class none { - name = CSTRING(units_none); - value = 0; - }; - class players { - name = CSTRING(units_players); - value = 1; - }; - class playable { - name = CSTRING(units_playable); - value = 2; - default = 1; - }; - class all { - name = CSTRING(units_all); - value = 3; - }; - }; - }; - class sidesFilter { - displayName = CSTRING(sides_DisplayName); - description = CSTRING(sides_Description); - typeName = "NUMBER"; - class values { - class player { - name = CSTRING(sides_player); - value = 0; - default = 1; - }; - class friendly { - name = CSTRING(sides_friendly); - value = 1; - }; - class hostile { - name = CSTRING(sides_hostile); - value = 2; - }; - class all { - name = CSTRING(sides_all); - value = 3; - }; - }; + class enableAI { + displayName = CSTRING(ai_DisplayName); + description = CSTRING(ai_Description); + typeName = "BOOL"; + defaultValue = 0; }; class cameraModes { displayName = CSTRING(modes_DisplayName); @@ -72,15 +30,15 @@ class CfgVehicles { value = 1; }; class free { - name = CSTRING(modes_free); + name = "$STR_A3_Spectator_free_camera_tooltip"; value = 2; }; class internal { - name = CSTRING(modes_internal); + name = "$STR_A3_Spectator_1pp_camera_tooltip"; value = 3; }; class external { - name = CSTRING(modes_external); + name = "$STR_A3_Spectator_3pp_camera_tooltip"; value = 4; }; }; @@ -109,9 +67,28 @@ class CfgVehicles { }; }; }; + class mapLocations { + displayName = CSTRING(mapLocations_DisplayName); + description = CSTRING(mapLocations_Description); + typeName = "BOOL"; + defaultValue = 0; + }; }; class ModuleDescription { description = CSTRING(Settings_Description); }; }; + class VirtualMan_F; + class GVAR(virtual): VirtualMan_F { + author = ECSTRING(common,ACETeam); + displayName = CSTRING(DisplayName); + scope = 2; + scopeArsenal = 0; + scopeCurator = 0; + + weapons[] = {}; + + delete ACE_Actions; + delete ACE_SelfActions; + }; }; diff --git a/addons/spectator/UI/interface.hpp b/addons/spectator/UI/interface.hpp deleted file mode 100644 index 8c566844ee..0000000000 --- a/addons/spectator/UI/interface.hpp +++ /dev/null @@ -1,255 +0,0 @@ -// Temporary fix until BI take care of it -class RscFrame { - x = 0; - y = 0; - w = 0; - h = 0; -}; - - -class RscButtonMenu; -class RscControlsGroupNoScrollbars; -//class RscFrame; -class RscListBox; -class RscMapControl; -class RscPicture; -class RscText; -class RscTree; - -class GVAR(interface) { - idd = 12249; - enableSimulation = 1; - movingEnable = 0; - onLoad = QUOTE([ARR_2('onLoad',_this)] call FUNC(handleInterface)); - onUnload = QUOTE([ARR_2('onUnload',_this)] call FUNC(handleInterface)); - onKeyDown = QUOTE([ARR_2('onKeyDown',_this)] call FUNC(handleInterface)); - onKeyUp = QUOTE([ARR_2('onKeyUp',_this)] call FUNC(handleInterface)); - class controlsBackground { - class mouseHandler: RscControlsGroupNoScrollbars { - x = safeZoneXAbs; - y = safeZoneY; - w = safeZoneWAbs; - h = safeZoneH; - onMouseButtonDown = QUOTE([ARR_2('onMouseButtonDown',_this)] call FUNC(handleInterface)); - onMouseButtonUp = QUOTE([ARR_2('onMouseButtonUp',_this)] call FUNC(handleInterface)); - onMouseZChanged = QUOTE([ARR_2('onMouseZChanged',_this)] call FUNC(handleInterface)); - onMouseMoving = QUOTE([ARR_2('onMouseMoving',_this)] call FUNC(handleInterface)); - onMouseHolding = QUOTE([ARR_2('onMouseMoving',_this)] call FUNC(handleInterface)); - }; - }; - class controls { - class compass: RscControlsGroupNoScrollbars { - idc = IDC_COMP; - x = COMPASS_X; - y = safeZoneY; - w = COMPASS_W; - h = TOOL_H; - class controls { - class compassBack: RscText { - x = 0; - y = 0; - w = COMPASS_W; - h = TOOL_H; - colorBackground[] = {COL_BACK}; - }; - class compass0_90: RscPicture { - idc = IDC_COMP_0; - x = COMPASS_W * 0.5; - y = 0; - w = COMPASS_W * 0.5; - h = TOOL_H; - text = "A3\UI_F_Curator\Data\CfgIngameUI\compass\texture180_ca.paa"; - }; - class compass90_180: compass0_90 { - idc = IDC_COMP_90; - x = COMPASS_W; - text = "A3\UI_F_Curator\Data\CfgIngameUI\compass\texture270_ca.paa"; - }; - class compass180_270: compass0_90 { - idc = IDC_COMP_180; - x = 0; - text = "A3\UI_F_Curator\Data\CfgIngameUI\compass\texture0_ca.paa"; - }; - class compass270_0: compass0_90 { - idc = IDC_COMP_270; - x = COMPASS_W * -0.5; - text = "A3\UI_F_Curator\Data\CfgIngameUI\compass\texture90_ca.paa"; - }; - class compassCaret: RscFrame { - x = COMPASS_W * 0.5; - y = 0; - w = 0; - h = TOOL_H; - colorText[] = {COL_FORE}; - }; - class compassFrame: compassBack { - style = 64; - shadow=2; - colorText[] = {COL_FORE}; - }; - }; - }; - class toolbar: RscControlsGroupNoScrollbars { - idc = IDC_TOOL; - x = safeZoneX; - y = safeZoneY + safeZoneH - TOOL_H; - w = safeZoneW; - h = TOOL_H; - class controls { - class nameTool: RscText { - idc = IDC_TOOL_NAME; - style = 2; - x = 0; - y = 0; - w = TOOL_W * 2; - h = TOOL_H; - shadow = 2; - colorText[]={COL_FORE}; - colorBackground[] = {COL_BACK}; - sizeEx = H_PART(1); - }; - class nameFrame: nameTool { - idc = -1; - style = 64; - }; - class viewTool: nameTool { - idc = IDC_TOOL_VIEW; - x = TOOL_W * 2 + MARGIN; - w = TOOL_W; - }; - class viewFrame: viewTool { - idc = -1; - style = 64; - }; - class visionTool: viewTool { - idc = IDC_TOOL_VISION; - x = TOOL_W * 3 + MARGIN * 2; - }; - class visionFrame: visionTool { - idc = -1; - style = 64; - }; - class clockTool: viewTool { - idc = IDC_TOOL_CLOCK; - x = safeZoneW - TOOL_W * 3 - MARGIN * 2; - }; - class clockFrame: clockTool { - idc = -1; - style = 64; - }; - class zoomTool: viewTool { - idc = IDC_TOOL_FOV; - x = safeZoneW - TOOL_W * 2 - MARGIN; - }; - class zoomFrame: zoomTool { - idc = -1; - style = 64; - }; - class speedTool: viewTool { - idc = IDC_TOOL_SPEED; - x = safeZoneW - TOOL_W; - }; - class speedFrame: speedTool { - idc = -1; - style = 64; - }; - }; - }; - class unitWindow: RscControlsGroupNoScrollbars { - idc = IDC_UNIT; - x = safeZoneX; - y = safeZoneY + TOOL_H * 6; - w = TOOL_W * 2; - h = safeZoneH - TOOL_H * 13; - class controls { - class unitTitle: RscText { - x = 0; - y = 0; - w = TOOL_W * 2; - h = H_PART(1); - style = 2; - colorText[] = {COL_FORE}; - colorBackground[] = {COL_FORE_D}; - sizeEx = H_PART(1); - text = CSTRING(UnitTitle); - }; - class unitTree: RscTree { - idc = IDC_UNIT_TREE; - x = 0; - y = H_PART(1); - w = TOOL_W * 2; - h = safeZoneH - TOOL_H * 14; - sizeEx = H_PART(0.8); - colorText[] = {COL_FORE}; - colorBorder[] = {0,0,0,0}; - colorBackground[] = {COL_BACK}; - colorSelect[] = { - "profilenamespace getVariable ['GUI_BCG_RGB_R',0.77]", - "profilenamespace getVariable ['GUI_BCG_RGB_G',0.51]", - "profilenamespace getVariable ['GUI_BCG_RGB_B',0.08]", - 1 - }; - multiselectEnabled = 0; - disableKeyboardSearch = 1; - onTreeDblClick = QUOTE([ARR_2('onTreeDblClick',_this)] call FUNC(handleInterface)); - }; - class unitFrame: RscFrame { - x = 0; - y = 0; - w = TOOL_W * 2; - h = safeZoneH - TOOL_H * 13; - shadow = 2; - colorText[] = {COL_FORE}; - }; - }; - }; - class helpWindow: RscControlsGroupNoScrollbars { - idc = IDC_HELP; - x = safeZoneX + safeZoneW - TOOL_W * 2; - y = safeZoneY + TOOL_H * 6; - w = TOOL_W * 2; - h = safeZoneH - TOOL_H * 13; - class controls { - class helpTitle: RscText { - x = 0; - y = 0; - w = TOOL_W * 2; - h = H_PART(1); - style = 2; - colorText[] = {COL_FORE}; - colorBackground[] = {COL_FORE_D}; - sizeEx = H_PART(1); - text = CSTRING(HelpTitle); - }; - class helpContent: RscListBox { - idc = IDC_HELP_LIST; - x = 0; - y = H_PART(1); - w = TOOL_W * 2; - h = safeZoneH - TOOL_H * 14; - colorBackground[] = {COL_BACK}; - sizeEx = H_PART(0.8); - default = 1; - }; - class helpFrame: RscFrame { - x = 0; - y = 0; - w = TOOL_W * 2; - h = safeZoneH - TOOL_H * 13; - shadow = 2; - colorText[] = {COL_FORE}; - }; - }; - }; - class mapOverlay: RscMapControl { - idc = IDC_MAP; - type = 100; - x = safeZoneX; - y = safeZoneY; - w = safeZoneW; - h = safeZoneH; - onMouseButtonDown = QUOTE([ARR_2('onMapClick',_this)] call FUNC(handleInterface)); - onDraw = QUOTE(_this call FUNC(handleMap)); - }; - }; -}; diff --git a/addons/spectator/XEH_PREP.hpp b/addons/spectator/XEH_PREP.hpp index cc29fde611..96802dcf4b 100644 --- a/addons/spectator/XEH_PREP.hpp +++ b/addons/spectator/XEH_PREP.hpp @@ -1,23 +1,67 @@ +// Camera functions +PREP(cam); +PREP(cam_prepareTarget); +PREP(cam_resetTarget); +PREP(cam_setCameraMode); +PREP(cam_setTarget); +PREP(cam_setVisionMode); +PREP(cam_tick); +PREP(cam_toggleSlow); -PREP(cacheUnitInfo); -PREP(cycleCamera); -PREP(handleCamera); -PREP(handleCompass); -PREP(handleIcons); -PREP(handleInterface); -PREP(handleMap); -PREP(handleMouse); -PREP(handleToolbar); -PREP(handleUnits); -PREP(interrupt); +// UI functions +PREP(ui); +PREP(ui_draw3D); +PREP(ui_fadeList); +PREP(ui_getTreeDataIndex); +PREP(ui_handleChildDestroyed); +PREP(ui_handleKeyDown); +PREP(ui_handleKeyUp); +PREP(ui_handleListClick); +PREP(ui_handleMapClick); +PREP(ui_handleMapDraw); +PREP(ui_handleMouseButtonDblClick); +PREP(ui_handleMouseButtonDown); +PREP(ui_handleMouseMoving); +PREP(ui_handleMouseZChanged); +PREP(ui_handleTabSelected); +PREP(ui_toggleMap); +PREP(ui_toggleUI); +PREP(ui_updateCamButtons); +PREP(ui_updateHelp); +PREP(ui_updateIconsToDraw); +PREP(ui_updateListEntities); +PREP(ui_updateListFocus); +PREP(ui_updateListLocations); +PREP(ui_updateWidget); + +// Utility functions +PREP(compat_counter); +PREP(compat_spectatorBI); +PREP(compat_zeus); +PREP(getGroupIcon); +PREP(getTargetEntities); +PREP(handleFired); PREP(moduleSpectatorSettings); PREP(respawnTemplate); +PREP(setFocus); +PREP(stageSpectator); +PREP(switchFocus); + +// Public functions +PREP(addLocation); +PREP(getCameraAttributes); +PREP(players); +PREP(removeLocation); PREP(setCameraAttributes); PREP(setSpectator); -PREP(stageSpectator); -PREP(transitionCamera); -PREP(toggleInterface); PREP(updateCameraModes); -PREP(updateSpectatableSides); +PREP(updateSides); PREP(updateUnits); PREP(updateVisionModes); + +// Deprecated (temp) +PREP(interrupt); +DFUNC(updateSpectatableSides) = { + ACE_DEPRECATED(QFUNC(updateSpectatableSides),"3.12.0",QFUNC(updateSides)); + _this call FUNC(updateSides); +}; diff --git a/addons/spectator/XEH_postInit.sqf b/addons/spectator/XEH_postInit.sqf index 72c53a2af0..ad021a3550 100644 --- a/addons/spectator/XEH_postInit.sqf +++ b/addons/spectator/XEH_postInit.sqf @@ -1,31 +1,51 @@ #include "script_component.hpp" -//#include "initKeybinds.sqf"; - -// Add interaction menu exception -["isNotSpectating", {!(GETVAR((_this select 0),GVAR(isStaged),false))}] call EFUNC(common,addCanInteractWithCondition); ["ace_settingsInitialized", { GVAR(availableModes) = [[0,1,2], [1,2], [0], [1], [2]] select GVAR(restrictModes); GVAR(availableVisions) = [[-2,-1,0,1], [-2,-1], [-2,0,1], [-2]] select GVAR(restrictVisions); -}] call CBA_fnc_addEventHandler; -// Create a radio channel for any spectators to text chat in -if (isServer) then { - GVAR(channel) = radioChannelCreate [[0.729,0.149,0.098,1],"Spectator","Spectator (%UNIT_NAME)",[]]; - publicVariable QGVAR(channel); -}; - -// Should prevent unending spectator on mission end -if (isServer) then { - addMissionEventHandler ["Ended", { - [QGVAR(endMission), []] call CBA_fnc_globalEvent; - }]; -}; - -[QGVAR(endMission), { - if (GVAR(isSet)) then { - [false] call FUNC(setSpectator); + if (GVAR(mapLocations)) then { + private _worldWidth = worldSize / 2; + { + [locationPosition _x, [text _x] call CBA_fnc_capitalize] call FUNC(addLocation); + } forEach nearestLocations [ + [_worldWidth, _worldWidth], + ["NameVillage", "NameCity", "NameCityCapital", "NameLocal", "NameMarine"], + _worldWidth * sqrt 2 + ]; }; }] call CBA_fnc_addEventHandler; +if (isServer) then { + // Create a radio channel for any spectators to text chat in + GVAR(channel) = radioChannelCreate [[0.729,0.149,0.098,1],"Spectator","Spectator (%UNIT_NAME)",[]]; + publicVariable QGVAR(channel); + + // Used by the template to transfer zeus to virtual unit + // Commands must be ran on server + [QGVAR(transferZeus),{ + unassignCurator (_this select 1); + + // Can only re-assign when ready + [ + {isNull getAssignedCuratorUnit (_this select 0)}, + {(_this select 0) assignCurator (_this select 1)}, + _this + ] call CBA_fnc_waitUntilAndExecute; + }] call CBA_fnc_addEventHandler; +}; + [QGVAR(stageSpectator), FUNC(stageSpectator)] call CBA_fnc_addEventHandler; + +// Delay until local player (must not be ACE_Player) is fully initalized +[ + { !isNil { player } && { !isNull player } }, + { + // Initalise virtual spectator players (must not be ACE_Player) + [QGVAR(virtual),"initpost",{ + if !(GVAR(isSet)) then { + if (player == (_this select 0)) then { [true] call FUNC(setSpectator) }; + }; + },false,[],true] call CBA_fnc_addClassEventHandler; + },[] +] call CBA_fnc_waitUntilAndExecute; diff --git a/addons/spectator/XEH_preInit.sqf b/addons/spectator/XEH_preInit.sqf index 4bdf07b3a1..82e8100a10 100644 --- a/addons/spectator/XEH_preInit.sqf +++ b/addons/spectator/XEH_preInit.sqf @@ -6,39 +6,17 @@ PREP_RECOMPILE_START; #include "XEH_PREP.hpp" PREP_RECOMPILE_END; -// Reset the stored display -SETUVAR(GVAR(interface),displayNull); - -// Permanent variables -GVAR(availableModes) = [0,1,2]; +// Used by public functions +GVAR(availableModes) = [MODE_FREE, MODE_FPS, MODE_FOLLOW]; GVAR(availableSides) = [west,east,resistance,civilian]; -GVAR(availableVisions) = [-2,-1,0,1]; - -GVAR(camAgent) = objNull; -GVAR(camDistance) = 10; -GVAR(camMode) = 0; -GVAR(camPan) = 0; -GVAR(camPos) = ATLtoASL [worldSize * 0.5, worldSize * 0.5, 20]; -GVAR(camSpeed) = 1.5; -GVAR(camTilt) = -10; -GVAR(camUnit) = objNull; -GVAR(camVision) = -2; -GVAR(camZoom) = 1.25; - +GVAR(availableVisions) = [VISION_NORM,VISION_NVG,0,1]; GVAR(interrupts) = []; -GVAR(isSet) = false; - -GVAR(showComp) = true; -GVAR(showHelp) = true; -GVAR(showIcons) = true; -GVAR(showInterface) = true; -GVAR(showMap) = false; -GVAR(showTool) = true; -GVAR(showUnit) = true; - -GVAR(unitList) = []; +GVAR(locationCount) = 0; +GVAR(locationsList) = []; GVAR(unitBlacklist) = []; GVAR(unitWhitelist) = []; -GVAR(groupList) = []; + +// Tracks whether spectator is active +GVAR(isSet) = false; ADDON = true; diff --git a/addons/spectator/config.cpp b/addons/spectator/config.cpp index aee0eaaa76..3ff6000fdc 100644 --- a/addons/spectator/config.cpp +++ b/addons/spectator/config.cpp @@ -17,13 +17,13 @@ class CfgPatches { #include "ACE_Settings.hpp" #include "CfgEventHandlers.hpp" #include "CfgVehicles.hpp" -#include "ui\interface.hpp" +#include "ui.hpp" class CfgRespawnTemplates { class ADDON { displayName = CSTRING(DisplayName); onPlayerKilled = QFUNC(respawnTemplate); onPlayerRespawn = QFUNC(respawnTemplate); - respawnTypes[] = {2,3}; + respawnTypes[] = {1,2,3,4,5}; }; }; diff --git a/addons/spectator/UI/Icon_Module_Spectator_ca.paa b/addons/spectator/data/Icon_Module_Spectator_ca.paa similarity index 100% rename from addons/spectator/UI/Icon_Module_Spectator_ca.paa rename to addons/spectator/data/Icon_Module_Spectator_ca.paa diff --git a/addons/spectator/data/b_air.paa b/addons/spectator/data/b_air.paa new file mode 100644 index 0000000000000000000000000000000000000000..eeb69e354ad8ebff9f5293b22cba8484c767a301 GIT binary patch literal 22981 zcmeHP4Qx|Y6h8O8c5CUn*AXTd&FIGFGBd_%T!Kl*-lo7B7>vOi6$UFTQ3(*MKmcLs zWl91OT(j^aQkWrPbO{g=SR$6~K~T|%AObP+vtkf1C^RrwhV7pB_JG0OZ~-F=c^uY@HevjQkBS_z`7CD*mVu#XT~ zrk7`e7&dhfA+IP1_EDv- znZlD_)zlf~UktPt!o=4-&1lFrfpw#Dar=^{{8j>Bw%ejo@9VImFPJKNYU%g#H!iWv z1fm_h(-p;T3ktknj)-KWo62sAWnrA?sW2i?q!7=t-_q`Aosm$hcA*DAsjXAGZHs+l1trZQ`nriB zZ1IU3&0>Mm@sdDD+6a4AJoSpGbY-eR-5qH+dTP^Czm&_#`-_*7>gmeqItziqH(CP5 z6s5!r^!z2s9$0ov9g;B z7$BeJM9c1`3Z2L0!1A16k*HDk&0JSUqgAkH1ScCbD*7$1_qL}_ce4nma!MV|ED8@% z!*Trr(nBD?!Fg)Ek*4RLTR}FO84hKA_4MZvPRBOM6UZRiq2Y3nkj%%5Ic4Cfh!mNh z5d2e%j(@$}C)OmRX69|kCZ9LI;f^HJlTgFfQ%i10z} zy7Ow;ojJofd#g|Q{Z!RjbJ?iCeWi`fDKX#1>KXHe#Py^P_vibQ?FZ2=?bx}H^#YHV znKf)!QZuL@j)-m#4G&cno$TOu_cK$ei#-qc4Vrc>)EFLC9^%0 zwFA*VC>~uHiS>^YZou;Guj_v#p?^xR+|Z^!&Yy1pW|deVNGsVt7%jP)*0O;;?y?Eu zkNuWCD#6ompvq1&G!n~z{Ufve+!#0Q2!jU0mFwbRgA>)0XR|3PE^-TE@57; zHs!(lYS+}ugen35-9#9WD0;mVngU$w=A5agd2h@X(0O|qtptAwz z2h;=V0rj9GfOS18$LFRh^*zVP(zsf>;*qW(H*D5q^HQO$vJB@~z@5*7zw5sN_h1o&~*qS_$ zTM|CR{n78lLGsN_CfHy3^>_RbkfC07@czBU>@M{jZu@%d+l@C?nal?cAJFL9+rPf@ zqhotdwkhN^7lr7WH%GIB_rHNzf9KcV@o7)drHV=O*|NEv1?$#z0}bsd2G?BXf9S9Z z8uL{l?pMwCg2L(_8vPEu%a&=f{?GL#(;GO;4igzYs(efWiun(WVHoQXM*#VVd`xPA zeSvT5h;N!g){m0s? zYM#!pY1)ky;Opy62E5;R|F->@PaD=OWWOX_=viGbA`0OBV1fx^yR#HFT;;pJB&@&g zkMZLOAifVqhW|C6Z2YG{zAaYVl4bn_TwBx3o7t~1w*n`;)!^PuxBak{Z-UgaAyrCE zHviu|t((~9zA`}R()NZYVx!5kJ7wmV`}SJT#(ZTL(NX%_ZX4K$%J3XnJZ?eqYeVi~ F^*iAe|FQr8 literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_armor.paa b/addons/spectator/data/b_armor.paa new file mode 100644 index 0000000000000000000000000000000000000000..8e3922f0b1ad238bd261163a4e1e07129cca86e7 GIT binary patch literal 22978 zcmeHPZA?>F7=F*aZJ|oJb(6`mL@ulaaWGt_%YN{2VG4Aa$|eO|Ksqp)M$K3RIz zx>4iTl)p>(4cA(Lib{a$8h|aE0Z#Mx1E&2zzL4P&7z9=fkPV>DWc)FoU)B@?qaPBr znYan01c=`;N^`Rv zi>ZUUIvp;_8j%AeqdYesvZ8Rfm$W%X6Ud5pNFcnKkisZoisCo9F1Bj1QsLRCa&fe@YtF9@(pHb`sG6Aqj zD+4eVHZ9I61VYp$`RS0%=raG}_l~S(EIqNR#-lO1uPt*(ex9-7M61o|j1?PIF4(Rd zPrlN<)iSaAbkBA*Z(vnH0rc0kY)wk5Z+ZU}4H9Y-#LtIkV#O+mUPA($*(^;c(j3(Z~b>OHjGb7!YHBT|&1 z6%p3%k}|!kR9SF%8}4pSc-LnQCCZjzI?1NW&a2=hW_MSv)J@Af2ab^Db6g6%0(WF% z(Y^Z1mP^Ea&QuGegW8VQPMO=Nv6qC_WB ze77kNoqD7IAWAeZ5qC&L5nm$3gou}a<>BRgSR%#%3ZUuy@J}&h%mT#7#{$tqaQyBY zMR#9I6Y8xP^80C!opi+X%SV{AT;>mn1L42PKhC}z3E9R(eJRURQ{$dN|IE|)5e&-t z{3UAl^F*m05ynIW!NaI4rD5GEXD>&dkxUyM)b%kIkM9f?`D1#yr3?mw$+S0|p6DF& zQ2G@wg)|YpcQRj0Ts}0yUjnD{q}PVy=e+6vzW8@imw&CR$H7?3_k1M?bSkS!%D$k8 zcv-EsOhX?+0q`fRN0@#I77s|1Vr51t_0VtACdo28pASTSB4hk@#m`&3XAC!6|9$Z{ z;;YA#(e&<0{`*0o5f9hHpz9951Ti|CbsJdR7no6pWetBm5LRK#3DCh8t$cCK^Vd!A z#XX--(ElO?v-}Id)4bvFpd5+S4=_~0FM@h}27rAwZgUkj8s>YZ zHvB9xc95SHGMy9xzNZefPR7>n;2UpYe~90ANqifbj`jQHhjkKLJ@E$4Rr}_;{1A}G z{rn~RXEGfyg{XyQmA7RvmeW{eCDssy?gOsg0|E~RX^;*DwWL8UN%Y^-(AY57?VIcJ zY53`ov|$I|Hg_;PDQOzoLN7|-YT$VNj;dJvU5j+g`_FL*Zk8X0JkZ`1?o0rg-ofPO$f7;3=r5&iIReo)1cjHX#7 zJt&R4`}z+#Dx`IRa84t9()W!2v;NWDHsC*7f7l1D6FGOqsjqST)r?DK& zJ{1LcweRZ{))>xd{DzPb+{@yGu+tMf!i6n~2ryxHIQL~Jy9^hA*3l0AL48!^1} fv^eQEp5ip!k4=0;<$UJkmG0EDb$_}v{tWy9)p7CA literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_art.paa b/addons/spectator/data/b_art.paa new file mode 100644 index 0000000000000000000000000000000000000000..0aac8b828e3a592eea246fa9c0a03c0565db6b2e GIT binary patch literal 22637 zcmeI4UuauZ9LIm>-b53ZELB>ph}|vTIs4V9RzjKqD18u<;mQCmH zaFg60Irp4%zrXM2oHlTeU-b9K_dM4>8utOf$8Sn1v z+iBrB$Jc@eu($)@uMq(4C4gWGU>IL(EJuLt*Wf;w0}%&^0vPoRe~;m5YC8!288aqW zl0n<|%r>tuFs6IJcZFFSn5CHm@FDYC@~Xuf5zQEY3D&j)IPcDY@kuXUonRtyHfy<6 z+@?pXkwU1*a%N)Y)$yN3lDZYTpS}MF(=+20Uh>xW^9O!9xg2G_n&OZ2=mM?-J0xq=en86vTa>KO1d#UK?6&!?|kLQQ+#7SJoQ#YPX-u&1p zp05@Qhc3zfI@+-PdimGrLE~{1pnOP&szQ2@9?A{$_bTx~JP;2`0pfvpP;Q|0fp{Pu zhzF$r@jyH%H_-Y(JP;4WgHnKaARd$(Xni0ahzH_9DL_0B56TU+J`fMY1M#2~ARdSZ z0>s1WJXmrMfR=_% z&60^P;WJZ1*EAufa9fMTPzmrcT(ELN3?9G-wPE#m;COA&*|aynW{t1e?U#T(VB;b0 z*N24{apwxY(Ga3D08ZNBWw6=fHWT7#3k1W#FL*F#;34lnjiuS+Yj*p5X-i2PI)IPO zO+`bYMa;LE$boID{4bAx8#oT1;`j3Yv)=IVvA)%9>{X!eNgvM(@{{~Pejq<61&9aY zLAinEN8*8aARd$g#KZrS2SaWe>1+64tu}b;hr+Rf9f%4sI|5%Fnd4UrgKz7tmJk;| zfb5TRUW03GZ@u-m^TDG9cQ=-)s9puwYajY9?8e>u9J%{Ty)>(TYQLrc>3e5P8!hsu zK<2r+@!#_c(@($lRQV13^=9jr!{5w9>J)$I*U#l9NE2WCBe+p)z1%4_2tYvoEc(pB s!oCOH*<|S~?wJd*cfSo^s>e*J)R1Yp3_hGuQP1dOeFweETD>~(HxGJf!TI@rR_`kt-^0E4+7|QCaHSi!%QGGK8wg?cDx7W0|%)m#f8TxhU9VY0&XU88caA z$Re&ulJM1Mlf)V8>0~50F<#|E%Gye3zxBf(;|p7a@qr;R>`7m_aBU0vY=sOCYh6m= z8sDqvaY0Z9a(1s%*;R;9Ia1WVEK`Nmh7HaAQNHf%SL$25AYn)XDIoihbxeECgMSM% zKq99tR$H%j1-rZR-JOMFa*85{Wa3XglPR1=lJ3Rl)nW2tj~6AkJg1G}S=u$Z!3;_3 zwZQ~#Yp`|a`_K!E5W(bO05x{Cu+or=E1=ukG~B#zQ1yxEMx-lRm)ifbCu|cxf)$96 zcMTtMyr+2aeA?fjYi4s>tp$lr?N0xJ*@t|exuA8Kj3DW9bSN$CLH{7XupR6BnI|Ho z;9Fg#1^rJtMFkmlcDd^exvC1TU|~C``;guyqIoiGIEPELdT;u|9c!OupC1AE+EbSm zvGljP47w7>}+Bx(3p=2o*|l(8V%-J1URv2We^y zwZJrjgU*^fW3@$ruvgRY&Air1mA1(dED=O}PF8}+$YHe!%JQgk!!fgM@<5&L9CjrI z?4h)JTkc3f(B*vt*~YXR#qsPpQF+fNeuuvXpeS$C4&q8p)$7t1$kPo?hf{sx_J~uy zgwqS1!5Y+?Lf!2)b`}I3f*441_w~t+xot^+FZkyOkr(^2@`5fg0_&8svpp81R`1hT z`vpT@)dOCHA|NP_Q6O+c2;@5}O9ZVU8iB$JnsrwN{M05MZbu!Cs2cX79a(nYH}+<0 zJ6MKULoTVJj&l`8U(%lNfpV$>==H-?P-gjAiNVG<|4oP8<&eP!I80fJ3q$|1@$hcI zW3I@39!Z zEWVJ~|0&t}WbL1ZWBI1_x1}jMy`FvlQFeT0<|Ek&W;j1g5wkofJ*3b`V>x}Xvj=Fw z*TVv91tF3{r=NK4?8zX=YWpph)p)tgYzSvdDYOwmnG&2r;_{Jyn#o}yQ*gjSs)b+;C#?P0~`wRV#;|eAAPseUL zyR!*#Z=!p2f=>nPmZY_WIIL|g=bZSee}2zJ`B#J}(EFR`!+4z5qW8)dVr~}c4f4JD zc>mw4M?Cu9`SCyc|IdW>_b*SC9w9`fuzKG4C(ecIt=;*P3ofg7O&Yw1H_|U5-T8!c z_t0B*_(K2APm8PQ{|Rwf8+7`KYlO9yx9!Xyg7aJpp8nJ}c`=&cKa*CCm4x4T{DAe4 zKI}5WtiRa&AAryw{4->zncf8`aGXx~5OU8llv#Y@o*3>c4*dmYG9HH zKb!v_PjolU&sty>9&60nP*G!E^(kA2<)32hM}30Ox`8V7h_N51a?i1Lwh1fb+n4Fx|lC2hIcMf%9N0 zz6M-4nlV|N~17E{wi7h((XSV+Xs$<|yopIjcVzq4bh z+qcyDCk2!hKsf<+y{}1TLJ!I+ZPmy=;k&s8rA5()BOJ4=X$V&|R(m E1zjU$T>t<8 literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_installation.paa b/addons/spectator/data/b_installation.paa new file mode 100644 index 0000000000000000000000000000000000000000..3ce55f2d3669ba890c00dd369aebad56425b7083 GIT binary patch literal 22554 zcmeI4PiPcZ9LIlecUBTix-sqImV%wig2aUMR(crYY=k6R8r@naEg}*X+JFtM(0Gtw zMrCg?9RevLX>AV$(L-%5duq#g30{Pp8}05va!>?mOLEzC`uk0seMxF*4q4;k_c1d& z-}&?Az3=;d-rE_NNd&#U*+ct!2eJkc6$*tdq5d%2FFN2Px;Oi7k7&iS==Xbi4%ocz z@_ac)^ldlM)q_Oe4-geUAX?&io#`B@mt)jQwl^x6U@ujI!Nlrf+QK@cn-44Fs7hM#%8EfZ= zvTfNPrujV{iDQM?T)g#W1IMv83!#CFiIWr*g4*i==t1uJGYKi?&H@ zxkPrjSr!tCRZ*9k3!TsBqdJ7OaU93x05>?mBh3NNKuLXh!TZ;0cI7c8A6NIU&2N=X zrBYhgyMOgre!sNv>E4B7uS@Ckv!3Vr!Ica*ifHE=hxtb9QgG*Px%qd=KeZ#BM*A;2 zuxb5=@5e2kq&+-NuDiNC8?OD^7w_^U*GqFk`>*!y5`-4|Qea+c;?U!yZm5Rf2a=jhZe}8g4*`gCP{Ls;W_y7;!0X%32 zzyo;D(SY#*Jb(xApdA1Y;6X9Zk06c&P9Ss;Czyo*y5846n03LKSV0-`% z-~l{n2fzb((9wYL0X%>Q@Sq(458y#Z1I7pN03N`Db^ttp2OSL(9YX8HSKymd@rO7VOd{oK(I4pXAU&0>@TBm1++jF9) zSeNCy6x25%diT`WU{t_bm9N(Mmr1?q@e~C8Ck;;j_geBdZsVlMlhl8ZQ?1Ka>-^ot z&T?%3Nq%jv(AL!S7q@RFH%VSb{NNY$pQ7*gP0%IYFZCabiig+i7=Wr7yi%_`M0G&< z(VkH($S>jn@ql>H4uA*nprZl(BX|H0;6XdEQ9PLPtwf`m?|QTS%r9OjdPaELe)xJk zeuIW$_S{eX5~SZfh;DLg*KKvV{pX9}*H{W|@g<_e-tf)lPM+Kil8z`DFRD^vo;KG4v0( Cygf1i literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_maint.paa b/addons/spectator/data/b_maint.paa new file mode 100644 index 0000000000000000000000000000000000000000..53b969a7317a2a667304785843cbd743a83680bc GIT binary patch literal 22889 zcmeI4acC1)9LIm}?qUsTa;>f?o72;!ld@Vjr&C$2S7mK8TAQJ1t8_K9`(vz@ouZCm zYM!-%g()$%l6BGU9}yg5Q)t*9ou05iWH2bhB2;5D7~0K0ma4OJB;MY;G`(3jaA4<@ z-y_%FH}CG0TWu%6G$rA0*<&-XO78URUG}^>p#Lpl}c8Fxr zps%j6Rk-Qi-Ch{8Ih@MNDOrqzH2Pg{7m=veRBvt*Z8>)?>d;lL%4de!1~O5}9K^9Cb|B{hF5! z8Dp}nMGum}LZ@93QoNU-rsT!voFor;S2w62(jJmDj6PAA4`of;1c>hkMOY2A=fc-H zZn~EUXP5Z*e-5C8=5|e)r2%o~-U5Qhk#~jgH+ehnFz3>^@&TZ){CUCf(E? z?!U{TmB+qNwxltW;jD+%XS ze!POYvu9_SL4F?7^+VUiQug6T53xeegG(8Yze?G4jlRF6xTvR^*V~szw083+K)c&8 z&W^Fo2xZ&wl8v81Jw3Ta&n~*kYN-}kN=H_D1+!iOn}g@mg@aevhqpJI6?5mm?2kI* zK-SMG_hWMW>HWv2NsyE3zBV2&zh8?`-E)(z0+0SFACG57^LkS!OGjr`?QJ{G>R&8e z8kw9Q82|hI%_xhX|6EUw|9KD+>?C+Rsmhj-w!>sxFSj#InP4TGpAYCCR2Mbt*$;HK zLPrWi?1qMIuYkkGH1f~fu>3Q{ zKk{Szl(PAqJwwzyVpc&v&<`sckYD5hc|aa42apHk!O8|4ACL#+0eP?-Kpv0>D;scp zKpv0>2W(Z0c<7?P{W2%)52cVhQHTUhcZD5`ltH*rn-Haa9smeg1tsq)AFq( zNeQZLcN5J$Y~0vnmo`YF)D!8N5$!UJ>nrs{Z{kOK;K+*3BP)3SoBjSLzyD(;*MxN~ z?6tZ5MIYzh2AW_~PO|Ech%n9jAIyel;f<1h-aZ8+_RI{_Xb1VCKbE_9TJdR=$u1*c7Tc<1}Yy zDQ+HNvpZ$WR2?7N&p3eoJ{_0+JpL$MP+Pkmbd5=A-ImxDmHGKn PNyXO8Zn3^+CcOF+Z*tD* literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_mech_inf.paa b/addons/spectator/data/b_mech_inf.paa new file mode 100644 index 0000000000000000000000000000000000000000..967cbe7ee1fbbb026a57553a9db967ff3030fded GIT binary patch literal 23414 zcmeI4e{2)y8ONXZ?)>A5jYCQ_&{Sd*8^a)wb&WJ6hn#_uc!S~&J29;xA<%XM0#-0X{ssJWE2ax|fz-qcZVA%y|s|xbrBoI5m8~|hH#;Z1Z z=&`{(7i$(@eo-X4fZ!IvLhv>rY`%oRxUCFM4IoTD528FUxdoG_>-(}`{-aIGEFb8t4Tm@lTIT^@iU~iC*e(MeARt;c#OPcH5R2z za&?1X?v24%OWRyQi)9idt0#qav}Az5#+%MEC&T92DxESCN7*VaR?S@bd+@#4D% ze1029LuwIeUC3lxASio>lBB3?flt2N=G6!ih~a9Btz6jNbQr}qYi5ou5|#+0$zkm_ zz(O1NRo&=BBl>9od9wasZf2_7BdY80uuuXte*T~!h?c%qYqztZsLRQk z&ZkY2ju)CQ1^fI-J3ZksuAj4SM|GY(0EQizH}fRPq`hBviPb?`x7j_*mDQxReBaz2{T!8;C`lI=OUE!^DIzh}+gHkJ&Kby~n4>m97Y z?q^s&0C3d{fKNI`=pnHDe#A}nk+J(THo+3SO!G%i!mh0v0!ZSOp;_(GsZ0-o-0s5{ zuI=f+){*7EFoPriy+8$#^#$8MEPW7=!;sGRZ2kv`rKZG5XZ=MsUmv6Kh1ht!S?^f9 zzp5QO4I|_AW$wS7yupIb>MNiR60+rCjhDAK?g8%a{|#?ke_Vge1Drq3Up5zTJ#Zd) zKH&L)=YyOFI1ikMoErH2z+06mO?3Yzx_&nP<9FB7*YW^#Z{38@;0gg8br%N+ z9e+ChR1DN6WM10FgQt$?pI(=~zHUoRwtM`w6AHjRXV8O3m51yYc>j4kvsz$#t_Q9M zu7{ilI1ikMoEmt3Y>2H@`gF z+5LiEkqO!I3e*gLmaXtX>-(zLE5F|qs!y->0&sK;pyqAI964hj31(~KX>+3dpY<@W z|Lc*K^Vp+6y3}%}WW#z29KXtwe?b?~n)G~lFb*}{_=%6AY>}mBU)SjA;?MV8|ESwq z(6Yy(_8pz;znRV^kyf^r+_I(<`imJG<-GJC|FlXpqf&n^cyih1@!Kv$r)FIP{|3wG B#?Sx& literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_med.paa b/addons/spectator/data/b_med.paa new file mode 100644 index 0000000000000000000000000000000000000000..82bccdef5e53e80f731002740612ef25864e59c6 GIT binary patch literal 22633 zcmeI4Z)h839LIl8{wxbixhg9wh;6KaQ5=?v+d4?wYlXJa)yBGvDplg9C{_^*tx((L zao88W*f^(%!Zu%7ABe+$Ni=QAm0nHHVm+S5WqbGupjq-nLYq^$N>++_aJ%zS^@m!g1I0bj&y8syr6Wvu(rmZCEy>c%m1g=jraJ-yFvHxstZZdnbNB~f zf;IF4=ewq{csC?@i2WLhy&E6ab#U-YvAhSfICo=-w(pqOd5!^0*V#>Zub?sE{kVR> zYTQ^+OM!R+{H`0Lv6l7jROn@F*9?P}V5h$+2=3I9u`GnvI>Yowf?X_?i{(R_$G^g2TeNwoZ~dqKTMp3u zmqe*Vevlt38tD51@jyHf56S`Jfp}2SK;r}PKs*o+$^qhmcu>(m;{)+PJP;4c0pfvp zP|-l+1MxsS5D&@$;(>Tj(Lmz^@jyHf56S`Jfp}2SK;r}PKs*o+$^qhmcu>(m;{)+P zJP;4c0pfvpP|-l+1MxsS5D&_MyM+fGbb$IwyKB{KzJ_zAp8d@s#4IjpvEt)d)FFp1 z#NZfC#O9VB8pdf`bL{{{XI9SPgn{EFsA5sOfwgL1t?TE1KAv@0{hw?1`40GUJal>q zj3qfumGcN+<Rh|i8FBv3Kl-EtPISorN2B*Z|JP*y$8g%+Y^%?=0z3;Y zJ#+~5;28EFwL1($Qp^4qAqz_b$KiAI!|K0%doVueFTj(Lm#a&4XXAmFTKCWB+acv6-IyHgPOqe2A}UyAH!mR^+L~!{e^) z<3jxLKFs_$bEoqE^?Vl}JesgpW0`Hmw?=p|{!@q7((&ORM!@+h)@6k literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_mortar.paa b/addons/spectator/data/b_mortar.paa new file mode 100644 index 0000000000000000000000000000000000000000..331bdcbcb0a3881da46b3e90c62a976704b9561f GIT binary patch literal 22811 zcmeI4ZA?>F7{{M;Z#(ELUR-Q~ag&(Wgj$~nOR0QCag6v!(<ui6a&Kq)nbw98%~G~-3+Jp0SJLKFTHx;Qs? zwaWJ?e|;nav@8c`D+V}K0`Qp&V2r=+v(y8bEyH7Q4wxMv1;9Ff;xAcz*R>27{g7C< z5hnr5S>jC^Bw#&~4TeFY&LFCkln*t;sEV^j#uUk#58H_41wN3}YOp3|fuV;w8j>6h z?#=B*uM?H_lz?eCrc+jI)i4DL&2U&lP=h0)vzLEiP?upUF^sJduW9$1vq>{vF+I2Uhs{FjxRU_MM zXPk7f$RK&$@=%7In8XP!L1o13C(W&nL#3ODJ(g;vn+#NjT_7*5lu1dSZ9!?A|ZuhsG~S8tc7yqGR+3X%o3zLL_dr0Iht z#wJ3jR^=>}B`H47R(ZGQ2;gU4}TXd zP6*WK$A2UrH22)q9X@6?{k2_*eT$@t&+P{?jY|eoY?bJreih%Y}SLbKK;+A>Ho$=e6d)5cGF$ zydthQ&%)auFVltx>`%x6jEBfjy`UfHhn@}idmni~9*_s!0ptOB(6a&O2jl^HKpu1l zkO$;J&jy?ykO$-edC(m|9*_q;8*qL=9*_s*L3aRoKpymL!1)1rKpv0>-2vnQdC;=~ z=Lh5gc|aa?2apHkLC*%9ACL#+0eR3JKpv0>JsWU-Kpv0>pYPTMyr3(Za)BI zj+bv@yarxqKS+Dvkz~eF%hZ78`HNOO`&89;3mEH7Fe&oP3XTo;sksFUkd6~=XpEb`%6{yy;W$1=tE>muFbkM&KR z#*qqm9Q}#pg80OIzcV?kOw^*@cxKAAP>lc?f~-e|K!0cmbf%bLB*-g zy-8lFjj{G3XsfTM-*m3qLe{8^eY+j%&hlLfNjF)I#AKMQ*fc`!lw~cu5S0G=2b-YBv`r_dwZ#{SG9sY04fjyUx55w9P z`ovF7Vi6=~hfy&`sy{Tpwqqey{4JWfp|fmm=x(yDL*qK<-P3VbLfY9O>n`1M*am(_ T<$gYsmb)Q*+7I2cC%pO_EGoWW literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_motor_inf.paa b/addons/spectator/data/b_motor_inf.paa new file mode 100644 index 0000000000000000000000000000000000000000..d78ddfc9d6d6f99cc01ad9027c43272e55e29153 GIT binary patch literal 23260 zcmeHPe@q+K9sj<&GqwZ94he0lYO*PItThm*8YV?@$a%C0Zo{An2Cp(;U|ri$8m|da zx*7cNXqHrqa4iz06NG6^G=(%;GHpnkR`8nr5fq6=OC3cc5Y~{9Rf=kUB+S~u+j}lD5ZL!E8bkv$s2U zY-2W}KW6XRwyjd8$C&=S8i3|!0lw!3`2M#6_P+=)On>*7IswHb$bmlqaRPiDz_MKN zuMT?XaX>*e>lWYmu1K=l1-A$`g7*nw>o@|7u@rFk5A;UMIKo4SXf5^t8b^Oz$|Sdg z$OM*Z$Z)%>s|m=0wH(?-jm(;#QH&Z)T*o9=SlWkTxd0N5lhaF4p+DrV@n>npi$YvS z#;P?Mr%o`$YVmYmD+-`8q{q0>feK5Z8&_raXS&bGG6=Xk7}u6Vn`o1vRDq3KK*?Hz zB34&poiWdA1dYE2&sBXyg8KwQs7tjm1Hn;?cH*%%SJvaqT|qI{PYV{>2FeWtH!247 zr>bnZA31gVcZeE17mN{4HV(Avf8+K>33h#Gl|{KngTxZ|iQ{`h)2(^>d;_5BrCL zCvOHs6u(G@14xJcH8DJzApJ#_`phQNiwR*2G}nI^GU+XnLts5sDMkv^G4F0~Tf^?Q zGjh3K!c&Rvo1Wu6qR#E{xTkItvX#u(iv&E?ov9b&y8ccn_`U|1oERe{9}e{HM~e;PdM)IkS2a!R5jJw;|ShvS(YnwL)IEft+786aopE5#^Q z1KwAbk!)skK=Ig<|3(m zf^3a?-Fc284M>}IM644F-hZGH5l+}0rD)Sh%FWeEB?a3bj9E{$Ky-Fhv?VGh=U6 z)uk}$0vIe?^E|WNr|kQ2l=NFE&y)55oez_?Brn@QZDM`Yk5c97tdY!TdZ6_Os7yX3 zl@S>J4x1d^1HPm923frRH^T(W&&@TjOKXPfhasY-2Zl#9Dmv)KdJog|TRifP)fKT4 z66x{L%OUvcp3tEq)V4t3wMm%Au376VJ1-D&%Zc&fD+HH^tmUgJS$%{$-&F9(qoMF+ zTHcyBdCex^q11%J^7XVm9KCS(8r_(^j;6H756s`?4talU1Zeug_+y+rOZA(|pTd+3 zuE;tccT5L25_qfO<1={6m|uBR-;*V%0;_SgxwAKdaS77t{&Rw%$B zT;B7HtLGU)es`rzmSud9-j>ph)kicDoOdjcj2{3rTtEU>hSneLUE8rhJ=G8S@a0>F zCvNqn^>2RI{{O%CPe31rBD(CrQ;2^QwVT87(>*=@!)u zTwr_72hIo12NeOX2d)Q|4Lm+_J#am6J*Wt9J#am!Y~b?)*8|rB*Mo`x*8|ss$_73^ za6NE6a6PC9a6NE6sBGZ#1J?uB1J{F!0M`T8gUSXzKX5&8J#am!2yi`cJ*aHp^8?od z*8|ssiU8LG*MrIiK0k0ha6NE6s0gI>AhR_9Oe^Qpr_(3ymmQ7MZ?ZaWeKSSE`S&N; zH(MY_2&rhHA6f%u`L8}t{|$Y5Gr*#VKcV)>xtvt_hS%@+^*reI@MQKBWKUB6Dq6ZE zNg59|<(zhzPERbfwivF;SARVER^$p${S?Y&R>NlLj7a|2Ckpkv_lh5aS(ARsv z`(yr}U23ME=Qzb4D7I<(pn_ zNM8>BOo0k=1q^y{zx0`Ilefq5O>=?mIUhJ5I3H95xE{D3R5tMV$o0VW!1bUa@MYD5 zg)KcQt@v@TZuqYZ9uAwdhlUpDWBZx+euPH{>kgb)>m+1!4>Wu>l5X%`+xu_JzX#&S zt;?&wY*++n7-)S`pIrTguI>^{x5mTt{|f&!!o2;j#x!7JuL9}Hy2~j&tkpmNhOg-E zEA(wlR`aEworStz;;;R=mo2i?+M>-gri*`No)&eWW3NTGyfN7Pcru#I^+u^()^W}- gmb8D>BdRFRoirO?Fv@^1fgtF7=F*az0gXbIM<0W+FL1O8UeQ%vmXep8lc9HZsTfU)m3rXCNsln7fD)r zgb}9+rZRCX!H>9*I5RR#9Lo}%Rky@Nw-^(DEPl{ALtOR)bPR|T?#^i`H}NC7WS}JP zn{(-Xx#zy;oacR>bD+sNl3HBsC|z2-#-RfMzu%t}^WBb>+*g@#zu2*&i2Hd4>FbkSV{q2bOYp+0c>a26WUH7dqj8+&VcFwmTL}b+Wan@7jhF;%PFForiFCQQ$c$yCM+E`?XmtX z+tVVFxWPCeP_LbaLw6itHHoUCn1;ejUm>EchPZ-8G3ETdw?>lH!?p|MR=r~Oh`F8M zAw;kKV>w)(*JS2HtBaa)3ZQ{{s-5@Lw7YGJUnE~%t<7mk@sM>}?}1S;4N~#7nm}t7 zz&eS}>jct3?Tx32uTA0s>W3}AWe(@gSzo!#T1KSY036LONK@KswhmBN@K8{OvS5}h z)es>o({Fv*Pxwm}QHyTS->zQxCf~o!U|zd+4ZR6=ATIC^5fF(erw^M3A(M>UHg(U5 z<{9jsQV$)>?j#kf6mUabwbjV~p<~`)uW$i3X_Ii|02QPk>TIP_JWDZ&D%{@hj*P?RJcy|YAe8Zv{jqFnJvB-ktnof5f{CB!JR zCi-?K@dT}5zvtcT#f(Nx?8h*%$Hz~iCLW$!j8}O3AP%qR!&EM53XB;E<1;|wo`0f` zcuWR9guD7bEbQN4;n{Ls3WaExP0KO<0w~8B%^&LPBfP$HoZ9I{wX0)BNDTCFUrc^@ zeXfVmcwOo**CMGmxNn}Fk&(a%^}-R+Y{KvehsAtWc3CH6x`KNyX80|tI&nL*eRrG5}@`^8bRdr!2rL{W;=#JWKR`_$mELMMc z`}BDI{6(1mct)REen$!Q$o3zT{~L7oK3h!x#tJWsMK4?S?~0gM{x_!Y{?1M5<>?=H zt3Q)Kk3+w+x@ii%`<06~sj9W1a~aM$_au8u8NdHmUC7Jt;9S$wd6F$6F_oUxiA9*Bp=hG%$wAMGUa75R#M)et~E zpdK_f;QWnxKs}%yGz3r&s0WP=xIUmBP!FgF4FS{x>Oo@zt`DdO)C1~4Ljd)FdeGQ_ z>jUZm^?-WN5I{Yk9yB)K`ha>sJ)j;m1W*sC2aOH5KA;{@52yzX0n`KPL1P2152y#! z1L{FT0QE32da(0NvFT&Dj~DE8fbE%D^_QTk_3Y@6J|m4V-zj)ib!8>nSWV;p8*JNF z%qJ0Gd-kAYZybc6H_^fI>fdd>gO7beLy4bq+`yF%%5M;99EeB z&OQ?J_ime&sz3y8mF$IPwr!h_KbYtq$Oq(uh5+gT^`Nl< z=SS27>H+njA%J?A9D0!X*^0Wcygg~!s!nt?*e(fMZw%yA!k=fX^rwzlA5A^!P}Ro$ z5OA(e1botX|FwR>$;`SfvC~-U=hh7al%Mn7FikY$5WMONV`HmeYQm@ARX$M6Or7@W9AGVIos0`0}xkVquUpw&a^NBj}FSjM# A!T!#h;yCK=ZKi_w}@ZJ z$yufFZOY$fo{_i$VBQ*l1zv#k7Xixo`yZxiAayc4499^vd0_zCWWj+dMt2ii z1@RC_`j&+3mkHP!vp{#5C~-uQ?0HZ{^ol4Iz8x>w@}PnwEeA>$2Eew{!ArXca~%sR zkzFE(er!B`x8+Q|-2lZK8`_G9Lcs*Fbs5k7{3T;fL!YnE^VP6>l(EH%r_E_FUDThT zJ^G;p_VKP*STNk<)~SI)+W*>>)ucQ?ym2w@ zy9Z7WIoQGes<>41Y_BOf4wk^&&@9Gk5f5NN{lT!6 z4Os((G-RY^EOyNADYCdE`Zn33#)N54LG`bD8|AZwx-emfT*O%e75wt*e6n=Iq#rUh z^E%2|^w8X1;|B%Cps}Ay7HbTxGW%n#Djk-RX9o>jvy3M@C2{(t^VMZ%UiIfGtUS1AXKdnzGb ze#zo*jw1j;HBh^LtKUM8Z5=a>$jlmA={nI|VT@}pETmWIf&s=HTg?7qUz~2#am_Gy z3G5A~nq^Uqf-#45lD_|u{Ze({9YTlQv%2IIgQul7uf9t@RYYu7hhx|;j~jh0BwgQ0 zh||zMWtGgaRh`5aOv%jly_%NAE!4n2#)%Rj;?QI)UVMoZ@f2?URfpA?@Q}cn0w@#u z@J~ViRDdz+@gMPn+B^KZV|YWdaBqHhC`47Z*B+bAO!`=JGG)4D6caMB*|2y@Xh-UEqu~(JHcqK%C+?~%iRM&OB z8Hrmx4?b3VPmcF?Y`!SYH$RCVe=<$^5#xvgsE2TfmQWAWLvzCn_Lq~DKtG}%(T|z} z$OH1AxdHoU1C9^K1M+}8XbK zO#$QqdC=UTjSq@g10Z24XS!@cNBEqnm4gyvwR{?F*whDn4jw4=jJeDBL~XctoVR15 z(SrVH1*fZi(|vr}XXyjWRX#1vU8d8$ryHcMrN4c+?*Lp_l)yl*a*zLYohpBdfD4ELFwoFO%th^~d}P1yCRC7KIZM z#G^p5EY?Jd+QNJIDgWY*SNPMJ#Qojn-QfM2K6>FZu?Uid5k?*6}d8esr~cr3c? tt&Y-pk=~@EUEy(7;2ZPBNZi;?G?ngq**e~%ay=hkl(Q*XEOAP!KLObE^soQ` literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_recon.paa b/addons/spectator/data/b_recon.paa new file mode 100644 index 0000000000000000000000000000000000000000..5c5dc8bb3d1551693578a59633bb0bc86388c92e GIT binary patch literal 22872 zcmeI4ZERCj7{{N}+pZm>Yk8RtKe#(7F3J{X92jGPThVPLj2Bs1A%k_ie84d&2_xz{ zdqxI<1mxO;i3-Cgd~lJ31;Z!YuEr0b$(Z621A{I)AYh0Kn=Fm(j_0(yyO}Q;M&<%} zPTKW8>AmOW|2)6*oHaSe!b(a!<%>&Jd&~gP*w|=E+I~-&uo*AHF7Yfa7B=B;VXrDK zUas(K%HKaq0MW$&*H;1Dt^gSH1H8C~e{pZ65y(mjro)%OJOEDt$Rp2Pck@gCLSXcL zBG(Zgfvk3-x~>qA_bh;~qlB{C4a7-`;B8V~o?%f;u2N`_EQ+XMaM)pzOQDWry#$oz zhd|!u26!Y+y%CqUnlP4Y0|>6B11m1D@Jn$6cYh$L%V2Rf6a;D3c zxKs3?D&ctVZ2kIebtl&lB^*26R=7@rH)0ka{r(nX;c1z_dTeWA8JX;0VY}61^T*`b zaalaLJnjh96W6}q$mhfwSWgF_BhF$|LNR|#Dxf#Q&E$xM035Q*FUJeT1BD4k*G@2B zOOuGLoTLS5KkU7hU=>fCEhAc`?@*;R!(6m9;<`En;F~AG@jUlJQgniQq&%J!%Q5Vr z?C9pEE8*@?jQDdPFgvu+7F_M4opIK2PzKR9G10zRJyw!yO9y#-C?Hy%5zm=h4pm$3 zTy(_v@m1^vmD;d9h25RMD{$K4|YN%aeDsIUFt;_ zW3JrZo;fDko=Pa9{&Ze7)y^I0;V)rl^Md{#tM~t|$2k7)OZqpG&6sxy?^_^7y)Di? z`=1p4rom?W?^HUS$Jcy}RokBD3)S)B1^qwC{vHW1op0?TpZxDuFB;*$>o)gy)3b76 zl=~B?KabJ8y$hBJ|0>W^V1j+LlrQjw?Y_a6w&VMojvX)hf2M2mYjGgiyT zuO05B`?lWq&C8wA=r z2f(|5+hsNOr2qM*Oi=mw^?2wjeFqM6oA22Tg!S&>u?!r5Q8D<%gUW+G2JAojQ;!Al ziSdB(fbn1`fIJ`%Ml|63h&&(<$b+H4{o+9e8NfVR@4Z$VJay)POEniT*7_=ZN*ZbB znX)|}7b!{mt#7{4E4J{$muV0698+~+SB&r=`8shHF4U@NSUbivHp|- zsPDruEjz_ef#O)Kv2I;*VE(4(=GX9FYu?@8^-&j8HPfS)J`{&6)z(<2Wa_s+H2=NX zz$ShceGR;7Q*w5hc|zm1duN;da&mSV`hplL{o}CBd`9JZK3!0}Rex;es8)Xfmt@~% literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_service.paa b/addons/spectator/data/b_service.paa new file mode 100644 index 0000000000000000000000000000000000000000..3c74ca018cd50881121d2a34bcdeb57735763792 GIT binary patch literal 22872 zcmeI4acmP+9LIm}?mF6y(Xm+uqFHA;=Zw<@1tb<|MHw{=2DG#!jq8R+l11nrj7Zke zw-G>12qYj5BRB}= z_i}CT*WUZxz3=b$dGC73dnQ(1?)I)J-|AKXps}&hl(Ylx3Sl!|gkA1lzgF0Uzl6PI z?b?klzEAn<2N@u?24Hv#z;!>scmUvqZTyFsl|~?&WXOf{z}x^O0QRYR!%n^%SOtu} zPwaKXN5FQDsE!{A*blCR3o%03^#+ndo`ZeF>&-H`B*!{vkWDV(Vf>a=vaf?WVtWxN zeJTR>T~2@njCz_Kp4SOu)nw=9@Qui@Aw_jC; zqA3r%xrg?N=7rn?gy?lpMta%t$X+uI`}-MH;M9)M)_TQ%EL$3&;aGjO*i%{ZUQE?#O{IxQp;JlIq35R>EMwvkVedr22vzTrS``nWui(nQ9y?2JMVSh ztYi^c8DpO%!0L*Uet+1UC69qhqWxZzQfP+kWB#w&4@t%I3Lm31 zChmfM0;7zbU8L=k$IiDcb{Fp1>gu4)_meG4?XQwa@}%v?sqt{m#Ld zqcA>LDp4&ppa}mK?wV>B4-fK9*b8~V`2SPyd#=YF#=nWjTjBS3dVbub^0Q@~>WAWn zZ+UTm(m#0%WHnR7A3!RdmOmK(WQcYB?s>eYPMALb9maoJJZZ|I?vD;$%X)%;#hBYX z7zg-^ZfttIrS+j0x53g9qTgOSJany1=f5CT8}FZV{qDIQF-=Y+;|0)H$#*o*DDRqi z`~W(9cW-OG{J%!mo0o*YU%9^o3$lcp#buLJQ&%4;{)=5JRlE-0>}gMU}OW156A=ZfIJuqAP>lckqtOLAP>j`@?a={ zJRlE7HsJVxJRlFqgP{QOfIJx4fa3%5fIJ`%h62a~@?c~Gjt|HK@_;-T3Lp>2gOLq5 zJ|GXs1M*-ffIQ5M2bWj_ASbQcY{^C^_?)Q?zwvc64)AHV<8$M{=V0=g)r?it@`=@O zyG^g~<9j-DQ;n$FsbAo1UEgf`j{{kx@=df?;iWM-?O07uL%n*aCx1yecL`%xSHztb zC;gO=93VNO{WYObXtu9!w*AwIr{mIwT0V{LK*`*><2>FDPcw-2r{YiJG!r~E{P&95 z5YGpC<;yduEuhpM4ki)ovwY-GPOF(si^J^yUp3sUso9Y|3A9r9PucS=uu7o z8+(N=du{nL@$|;~QDs>lRDD1n9XKf#MOwc-BXi!ge&({F|9LIle-7O_Wix!N)_OMV&T|N0{)?oisnptZWLK_Ai%!l1&Er7e1(Qz9QSkX z%A6RCYionUN88z*2mG-qv~eLDiJ#g^|MJCl6;;aXaITH^%hb?`w7e+%DKn{Pz=j?b zk`HEP{buL-)gv-{dlwBuH?60BJAa9yvz+ETZD*`^%d2&tiL;Ty#Aj5horyZkr2}%Y4Z38o40;zv{i~dLaI`K@@2wdv1xcq$K7 zJmxr9D*sB||0;9V`NbyD%37G0=Zy>fIn8 z1qvsM#WXo(v|fANI>b+JlGBU6UK%j;m5(!W6Qozud#gQLu6KS=scBG6)$*}uU^qQ= r*q%*pPwQ+4M_<-06>OJMsVLL$x9#Q`mGx{n5xkynwlROuxrTlKFs3v! literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_uav.paa b/addons/spectator/data/b_uav.paa new file mode 100644 index 0000000000000000000000000000000000000000..e900cc2509d7d90c3707f65e678e9ebc0cebd967 GIT binary patch literal 22660 zcmeI4e`s4(6vxlakF_Of6N|+bWs4Y;VCoQm;2-^wSZy1zHCo@Ajc%>0qOcWPT>Yo5 zxt*=BKQ4K}CrFxa}$au_0>z>HgT-C6iT9Ck`nsLb~g@@8$KvrmTZz zt#S@8$vb)X-FM#ioX@=^@UB}lHAP=-Xxb8W0l>(}h}-lN(H7|oH0d`*Uu={lYBmmyT*9vVI*pLIt;cF04fDoRaKk?@p+)dPh;D=eD zpJ@y#&M-YV%OG&D7F@F|;$aa-uo;G!J0j1Tzu|KPnxUUnJPVwM2O;obJ)Rw7B6d0% zF`GcclJA!`IH8~Yl2vP;c{3~(jP2dgGx18)!2tv~`zYiAm z6?Jy9eOuXhi5no>u4(*QgC}{A9WBwOOUr~$zu|P3%HI2Pn>N?@hN+6+#{=)14Jx+@Ld-DvO_-U@gJ!NBeU>!{laUTWc9|Ube$XGXwa-* z0Br)xe6{_I_tm^-^t$W* zo#vlg5!&(JHs3e5r~I1&+WJ{)u3`Q&hBJpjNS>$SxMRfr#9h;$&{p3!GUt{u>7GmS zdK)XS|GcdmJt!Vi0m_H0P-UbC>7lA&4ae(z5LAy;k5rFJ0pfvpP}M-=Gx0z?5D!WL z;(>Tj)j;zD@jyHf4@v>zfp}2WK=T9fKs*o+N&(`5cu>_q^8@ifJP;2`0pfvpP}M;5 z1MxsS5D!WL;(>Tj)j;zD@jyHf4@v>zfp}2WK=T9fKs*o+N&(_weew{IYXIPruz4#~ zn)o6@v zysv!c`NKgWK6t!%#sGeWfBRgOoHoDPcI;3I9OEUa0B-XByU+?9U!nWwi;ra;t-J8C zxzUi6Wp-L$IP~=EPb|EJ|K2=#B0f0U6kr2bQGUyecJ vqR;G3ci(T0CX1&d=-2G|y!2AuXG*1iy=@ncsHkTs+_>91?Y=YjE^GBSeAaX9 literal 0 HcmV?d00001 diff --git a/addons/spectator/data/b_unknown.paa b/addons/spectator/data/b_unknown.paa new file mode 100644 index 0000000000000000000000000000000000000000..f92ffbe9cb76e6a57a0c2efafa1369b301fd0e70 GIT binary patch literal 22488 zcmeI4Pe>F|9LIle|CrH=7A+WoZLm;=L?`R8>a4;3i8M1c3n9bEgDrK)Q^rn#5Qw%c z6m^I?*dZY>g1wY6-MmzGDlpqUgqLn1deNreo88%08nnT#VtpSo>-()U@6Y#rKku;v z^W0`Q9PBt6?h3kyXk=u>ZN=Te78#qIWE>7256M{YDdSV2&W?+f3c=oI9AB@+HQLB6{Uc{y1-{YS)oXf0$ zoR4SPIAcV=an_0_I*T70>FJepDmE+YtMPLAxwK*aNBx!p&|f-ICFlV?lpFB-9z1{t z@Sqd`58y$$0qX;J01x0nDF7b8gK`7b2k-zMz=Ki%Jb(w~2CNU@0X%>Qr2u#U56TT# zAHV~601rw5@Bki^8?Zir2k-zMlmg%ZJSaC{eE<*O0X!%Lzyo+tZov8g9>4>5Pzr#D z!g$c+9spH&-AW~!zQpfLwSIM&<}mLdvEa#2H0dWTX__qq{GvAfK8}oKI}z`-8(6CL zmAd>4iCqakWPe?p{`6Y%BYkVq$A|2HFQ-zsuhix1O9wNK)&YKPZn)OtS>W;ZN0XHB ztMOkQe~FCIJ^sJ!f81^!wq;-d@?vmiv+`i~0qw_l+Fp=<;RpBueozX42k@ZWfcX(T zfCuoP6et!CKDk$-D_?G*#a@|AjrtR=sA*2L(-R@|$<%{!D(f}Pch@Lx7U zH|i}nQ`T-Q!?n&8qO*zE^9n1j>oer;FS%5z`cZyX0qFa0Ov_dBQJ}D0o&Pm3H0BXok9P>2n{)Rf`wr%Jdr3_k9WA!6JZfoV5KK0L?mp<@EiSr5aF?1mod6kPLvC09M%Q07L*; zwn90|&jf-ignIiPM5J%$RZP`KuvYiGdC z+_wRN-ipp4JtF#ZRe9_-0GWg4-&d6@{wR(3QP~Kdf2w;}2T~HtB+Wa}V6b z?hT?h!K^K`7?skm69RDP6^+~7e!ZilC)E=`Xxd>f4woMlKr@7;);#4G^9k9cn_C*3 zj<3>N#GSIV`UGPtyIEn_9EC$c)1DcpT}WfMmG-4*eu=X^yEl|{w3n8E<_^yC+j{CZ z6C%yjPRNr}Z>>wY9Mjy_T&8p7JFkP%P^+}q!zK{!W95ss<%jmRi}M({)Jan z>09DGF>9(~za8uCjbyXZ;@TE-H&zZ}=*KI%?AdM8JxC*n$k>9Frl|NpI%;f}WlvRN zI9-~3=<~l#FS*juTxO$|tvl6prs`UKYxUJR<{3k|&Xc9(0={E}=;m>#%c4zuA)F*W z7@vCn%CxEBBs4k2u4Ok~8P1g+;sSqi+8eQ+-GU~i&wV7Jb4p|T3xO%K93XV#%&C+H zM98)rs}xvZQv9{b#Jjf!Y&r;*HVxs*8wZe0rx#gg`GXse+8ox-pS@eQdQb!Z#$|;m zXPZ2pokGmqzTl_ls_AUjipEPD(dM3p4U#U?(64dBb-Yga(%wWU7(iySsD-8Wk!$w*X|-|io7WTO zVUf+oc}$XUkhI#>`B3`=owjP9IjYv5fOl2Z8^dEC=q`cHQW6x{v)dw zyh}YhF8Wh)_HBKe*`Tj${|M)={OnT01wuYsYkngO<1^IxFh%vhEX$eP|HBdUq{ch$ zoQG@XdE-_8*VTQ1&!O3D#Dw(kMgryk*RtUvHvE}wv+`)cO%?8#4!4<*4BbRpF#hiR z@OhN}vFFl4gVRIEXATE=c#vz8r+~(SOBC^W?N! z(+Js7hUHi-{vD$Ee|Y`RqzeG||C8;19Hg7SO0T+`m84@%m4D@-Y6G00_R?seHsk$- zYL`r9_WDs<1ZJ!I1GSzyUl~$uSuUoJDSZFOB7h3Qc1)v%+gy*)V4xQ~9(X+Pc!(&# zdEh)mqJif}&I9Lx^AJ&h^T2tCL<3(RI1ii$&O<~2&I9Km5)FKP;5=|1I1dp8I1ikM zNHp;Ef%Cw5;5KZ~uYSL;X+MLc*J81RcEOkLzkPe| zt%2n)JC7vZ4NZYl4fmQFSUrhu^MwFsr5(yjV|H9@Z0yJY$7+0I-T#4Lh5Mz7Em}4o zdQOR@acSRs&rdq25YlGJltW(p&JW!18W?WVdc9sR$U)-`_G0aSM9Wx@Z>;;L{%@lG z*QxWjSb9TvxqrxK1m=IyaKstHD{k~v2=w2EmcLx{n`mbUsPhkr^H_W8{%sMiL<8r8^T2uFJVX@WJa8UZG+5aoiBY3*@wj2JxpPI) z&%X%m=p5L2aMwGvwHK_Gu9?SIKF35YE-ow_>F{y;`+wWlb+&|3I!dmy)4dY^32*`4 zG~J;G{IL7Gi${9n%idV&KcX=2|ND_fEbObm(K9VapQZ}hQVVNR3n^yr9qO&`h2rDY z#=dHX-BjJ6;7!_yiTI?%DNMbpbViYBSqrGp;e3$XCj(?)u7Go5xvd*tNL Y|D9c;Xhx-aCMGT3KJu{%BhtWs093lAOaK4? literal 0 HcmV?d00001 diff --git a/addons/spectator/data/c_car.paa b/addons/spectator/data/c_car.paa new file mode 100644 index 0000000000000000000000000000000000000000..832e17cbd6164c0f58b1cc8b915e7e22a41e74ec GIT binary patch literal 22828 zcmeHPeP|PB7=PYNu1U0QvTS7qDNQuB)2%XOib7W}Eo&RJE~sY}t(Ml6(hr^6>?T(G z#wk@NEOwnJ)H)_*!zqf(f{dm__raL^qf=LObz9iV#{Q6sIJ)7jx981A4>|@;C^YiC zz4Y$ad+)vX`S|^wYv4Uuuc+{@DXUoPH3L8(5Rl_|owrJ09h<-v-WBBnGyDj=y1e{F z1&=v?&X@o`E(7>>EoM>GA@1w)v^YHjEK3%hFKBklrlCvu*S&ey-PV5FYAI;; zSwYK|hKjtiLY1&XmJ`EvY4!zs@-H^bFbzwy+gEPcgQIyqTi^3Vbwfz&kn2x{x7~-{ zu$C=fb6R!=1KptxS(a6oqunKLREIMv8Vcmy2;}9C?}CvwX|NLwa+hwhLCx#F%wKv; zH4KU*C>+?lKioPpE*~1%!}4xua!F!_dy{YIY8Xbc@RP zl~7*G!F#OVl<`Lv?+u>_H_t4HY}GS!%|8vSxY?QaeW6Rr__~hmQ>6aWt9mRMIeaMl zJUk8%>9C9Wy{0Xb`T!3HUVS;bk+GW>?l0?QZmV@vvcf2@tkJvXlswC%g&IiOVGYc^ zmgE{qya@CEO&eQTt?>NA;7QxIKtZquCFE zM<6>EAFoMKI0&uS>k*h?OZhBa{q?BLU*}$@2R9KP)JK`~goUa2oDu@<>x$~S$Bk$F z30yZv^v9Mnxj$+QDt6E;&MVj`{&7D|TYZ=u|H<(_Uw3mfvPT=@kzM# z%?;munZ$lLsVomQ9SVv5m=6UM|MZUkIn-~|d#neJeF~cL0lzHtAF zTEzS#Q5Hl#xve&_C;dr^cuJs>?GJs>?G zJuo~#d>}p;*+BJy_&|IhJ{TS#J`f*_Y@qr;d>}p$9}Eu=ABYb|Hc)*aJ`f*>4~7Sb z55xx}8>l`IABYdc2g3uz2jYW~4OAb955x!JgW&<<1M$Jg2C5Il2jW9I@Ievp0B}sq zbgI%e1_y%&4jwxol48)|aKt!>WqBjIUe&DPs=FjEOyU_a;AKm@%a)4%`MJ5dsR>Ti z{HA(*9&M|&;3n)r^BvCK)@$Jtx84)W43)+;x1J1CU7ee;YN0r9oA>TfB|5WTYyzYC zX>UKq{}{&KnzVl~#{YW9~T#3tLK@33>9gx|jJ)%Xt@EnAdZ6e(`SrO-kwV)PN5<-v902d2wzm6mA_BZ}&NT56$>| z%eNVEjBoR4N{vQkZ>s#C@-U76Xvl~|d=Kcs6~Sivg37A UoL^Mlg0BijZ;eHsNVx|70UeyE-2eap literal 0 HcmV?d00001 diff --git a/addons/spectator/data/c_plane.paa b/addons/spectator/data/c_plane.paa new file mode 100644 index 0000000000000000000000000000000000000000..d12fa68fbeb69d7b0b50bab4049a81503ff054e7 GIT binary patch literal 22757 zcmeHPZ)jUp6hAkAmWHi;aqdIurb{thSGF}GrL3$=Usi1!w655tR@}zcGHJV&RmbYk zZoTcCbl^0^e%P?3L#WJ+i5n6pbn+Y%{NRTfKRBsM5b>iFC+P64*K=O>@))I92Zd&y z!%Ol`?tAy$^E>DFyDx!vy{fS>x}(0aE$RUPDW%u)+oR3oD>U*Oqt7&uFYrZvYeU0! z9rq=^_PGHL)B_x91$gawfT4DPukf`%8v^l@3s%AxprQa3072-g1))F$y5O+bu?>{$ zcO^ncMI5gl?B5FTqi9(!nubG7a8w8#Pmoi7e4Evz+&BQigLQa{Unu|8!4_)GTK}we zJiP)$NMCWiU!27O?i8LxDAl>;;qexC>9xw^(med6Yv*A)Vc68XAa75&YZ7kL-41yD zSTa<;Mr^7DZ+BWdFrDh1E{_1jHVlA(^lfd!8rRl{P&fdMI@n=qt%EfQwcga8I}~$O zm_-{S(4B4_Ox-amOl`M2>A|K~Afc*F2-jMp+-qvVz*b0?&V2=c z;i^eN-JUSRkqEqWvGmjM)0f?!2))3QesSu{iyLoF4K9nvd(#+BYHxY%<73{Pe;;$Z zTtz;4vDGE|;B%j_dU)5|?p7nGZWj2J@7#&6OU>}^*Kdu5rjAAGK%Vdk<(tx8c-2K0 zBm2NS7{rhFXEh~5GrLCJrLr>kas>QvN3;wGYAeNI;|JNV*+6(Vhr?5~@DR~N0_X&b zWyC6bpFZ=|m&RZ3ly8=9iY7kk^-4ocgsgbbKaXMGhyM!{Wvu>sbX@|Gd^kkDK(jH+ zSG*py@hcf!8N!Y7<3*D)8=;+_3lc>$may6Z3|(HpcK;r%cREg+bG(wVgaot z_u`rLmX2P`$-f~2)0ZBBTVpr@qLs+l-s#l%svr)WYNA9obmGKWW&08U=T(5)x$`P>G>UBm0TTxH~=T*R~b zK>A)aiD%;76Z4-CTq^m^W^?}W{9FE|aj7|J#RJebi}wQ`rp9yk2Ur7!Y`RAG)k=Lk zCh%7T=Rb>hzT{sTc=LB;*it92cQP&w9DnS0%Lh1rKYmdrHfx=)>pgMS|NN?l>}coL z3vTCp5(4?6Z(OfjuUxOpSJtbg`TLXgnf00Vnf00Vnf2M83s?_W4_FUa4_FTz1eg!Z z2d5f%eqcT@AD9mg0?Y^IgHsJWKQJGd56lM#0p zJ!VUf*){Kv<5PE;KA2Hz0xxg*A_KC%X0*PBj$d0`Tx<_;vBtO9{Y#^Nj}JGB2g47% z9lHG3mgKr2^4r?v**AUN@1?wb_!tT7#(yId zPvK+Ng&1%1{yq#j6OZ-Uei`|I$scuZrsFaFYZp5|+vY~ z9!@ndKbQ~92j+u=0P}(QK-CbW1+P50x>#sgJaOgOpOe33k_6(pf+Hi>gDbaHe%4V* ze$SqL9d?Hc?eD)H-~aJPnGh~}sZ`*NxBkE|u6KU3%<=&`dhFhK+FLCAZG^f12SY{z z^jBbRP0QS`skSrK9re{6NVDWoWkUo10M8kY&Hw-a literal 0 HcmV?d00001 diff --git a/addons/spectator/data/c_ship.paa b/addons/spectator/data/c_ship.paa new file mode 100644 index 0000000000000000000000000000000000000000..f53701175a50cda2c5d2b66fd20ecfbdd8a882a8 GIT binary patch literal 22967 zcmeI4e@s(X6vxl&4@-s8f_CdPj)zuJFu082a1?k2fp(Elp^pJWR3r|JC^JeX7QL`( zU~!QUaAt$}W8)79HE5*SA84wmS+XsGKTM3Bvy02(Kik|`a6c+<_dX~uF{CvARC%?q50f5HFMy+g@+6$O1(Z=k2`=&f*bM(pV;=H^< zfgTh3e4U=i+z62C2G~>zP+SJ^34K0gYX)vm4Y6<)Bs;(w03K>=z)8TFYTy9p&IXAz zsM@VfoJjvW-;e=tmvcpOjuxu}nmA6N7m#5+eVH^8vepCq-Yj~Fo|E*KkGR-bfTEC5 ze}*_atj-RNB=r}SbHZ2;r>a!PM{MCl-k1qwtmn_DTZ>Xz@0O5JHQ*_HsBwTwlI~lC zY)GjEqdIV5S>V2nI>1Bc4zP35=xr$;*SGVYU#oX( zcX|W9I;RRQX+b55-lV{w0bJ>l#DgG+Vp2)5G*}nVs`WJv={)jV=5XbW2&6f|U z*a+@W7I&UF+e3Tn|JJ_z@lez4w!4MaP}739+$pKPx`1z4LM4|1R?k*mmkF=E*`zZh zJdD$j@tZ-2t?#B&(Hb~5&_1bfjBL2q93N59!$Cp@i2U*lIG!*Z$9+}K$?M-A+RA!f zn>-;AYkhL3Ivz6lcQZi`k2#lzW9Q;SM)rrbAL$C@13aA8A=d^m415A0Oe-^$CuR(K z!1Bg?w2A+Z6A5WV&1Nt5YPG~KowmyBp>{uA_A}T&Ky*+ZZwx%509dutxJ$ zujg(460yfx>4W)4OIs%`8lOAcY0)^~1tJK25Lf_@f1&5?5zsG+vQ0yT^=G!d&-zlw z>b4G620*3POEjrzMVM%>}7W=+tWWRqV+-)&8xZ`rzdLf zO|YI$dp7bft2KA;51!2b)qN|5Zv-yZG5@*h3G{*UHwgx1TckGxPuld2q+Vyh$NHO7 z7t+rmnE#o}=W4#64#%1K5kH0hy%suMR{wiN;-@{R%bCpz{4?0mLFc4=y&&uoG5<3i z=TAIL%@*f7U-(6TO(}r>n#$N14;T+j0ptOB22gAxr`ACL#+0eMgqKpv0>B^t0kAP>j`@}MYy zJRlEBG+=!|9*_s*K~VsCKpvE6!1{nZAP>lcq5$&n|L`ENJpfEIewZyc1fi|Hwe3_Z zTjZ$8WFkSjH3reppJk3%Bsf!A6LcM@+AOG-%kLy~nDDD~|x^Z*ryPpf6qz?N(-YQ9B6K`JL)CdHohk+-&;? z$DdMQzVe0sL;o=aFn(FaR2u4mdMMF=d>{|V1M;9KfIJ`%EE;&WNup*ZE}k|lzMy-2 zddFhv$MkEaf!3ND{(2q%Lgn@s({2K66YK@y0iSlf|Jr}^;LkxT-LOfLOdkyV4sacM zlfS3CgfgdA?Fx^EWpB3h4=ar0KObqt#J&og+0%07)6}{X6II5EDvH^7_vq*-@W;;@ z?wn=VE$-Fnbm1tN=^uV$zJ^5vM6j<#)5?51mde@X3 U7Y)s*R8MO{-tO>ai^I~uKW(JcivR!s literal 0 HcmV?d00001 diff --git a/addons/spectator/data/c_unknown.paa b/addons/spectator/data/c_unknown.paa new file mode 100644 index 0000000000000000000000000000000000000000..f67a3b5444316c60c554bf2f694f0be5343e8253 GIT binary patch literal 22848 zcmeI4e`u3O7{{M`-@Iu;+vII0G&rT{8m$GlTC~nWoNw%Go3YMdjdrZgST~ebu^kvU zmY8c?nU0O6YM~Cb|E&8_l>MMM*{Z}jHp^frST<-oGbY9Tp>!(igx1^L8`BppV^t_L z<2l|m@6-3*yL-OR^SOHy$SJI<%Co+rYJ^~Oy65bbHaVEpgbbGGHrl=$BOSW*YMjIAk#W{3M4^-3#q4)Ofpwz3%L zEl^**H1{{g_Br6lA6|a_;0|l3P`^GJn~U+E-SK`f|E9!$co8vcQh>3Zb<5`Y=uPU) zU3&g^9{;EcD~cp~E63rUrux{n18! zgaVi!;uC{WP!H6@$OhyCc|abJ2SWkm0eLX80oMoQ0eL_k3QGJFRY(bV7A6X6<-BT?rAysX=+2~@`l3Y z4IH!fcVnN&An;Mg{9if*c8?u1+s&z{PS#KTW45~aU*ofYMSLyl4xZZjxIUXWzSp>| hMZd@%yk{FbfByUIV&XF@*YnBJ${nfO=A^oYe*o*yrL6z} literal 0 HcmV?d00001 diff --git a/addons/spectator/functions/fnc_addLocation.sqf b/addons/spectator/functions/fnc_addLocation.sqf new file mode 100644 index 0000000000..414c7c3d62 --- /dev/null +++ b/addons/spectator/functions/fnc_addLocation.sqf @@ -0,0 +1,73 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Add a location that can be seen in spectator view. Local effect. + * + * Arguments: + * 0: Position + * 1: Display Name (Default: "") + * 2: Description (Default: "") + * 3: Texture (Default: "") + * 4: Camera Offset Vector (Default: [0,0,0]) + * + * Notes: + * - Position array is of form ATL + * - Position objects will remove location upon objNull + * - If an empty name is supplied, a descriptive name will be used + * - Camera offset is used when teleporting to location, default is treated as random + * + * Return Value: + * Unique ID (used to remove a location) + * + * Example: + * [[2000, 3202, 0], "Objective Alpha"] call ace_spectator_fnc_addLocation + * + * Public: Yes + */ + +#include "script_component.hpp" + +params [ + ["_pos",[],[[],objNull],3], + ["_name","",[""]], + ["_description","",[""]], + ["_texture","",[""]], + ["_offset",[0,0,0],[[]],3] +]; + +private _id = ""; + +if (_pos isEqualTo []) then { + WARNING("Invalid position supplied"); +} else { + // Get a unique ID + INC(GVAR(locationCount)); + _id = [QGVAR(id),GVAR(locationCount)] joinString ""; + + // Must have a name to display in the list + if (_name == "") then { + if (_pos isEqualType objNull) then { + _name = [_pos] call EFUNC(common,getName); + } else { + _name = _pos call BIS_fnc_locationDescription; + }; + }; + + // AGL is used for rendering purposes, but it makes sense for public function to take ATL + if (_pos isEqualType []) then { + _pos = ASLtoAGL ATLtoASL _pos; + }; + + // When no texture, just use a transparent procedural + if (_texture == "") then { _texture = "#(rgb,8,8,3)color(0,0,0,0)"; }; + + GVAR(locationsList) pushBack [_id, _name, _description, _texture, _pos, _offset]; + + // Update the list if appropriate + if !(isNull SPEC_DISPLAY) then { + if (GVAR(uiListType) == LIST_LOCATIONS) then { + [] call FUNC(ui_updateListLocations); + }; + }; +}; + +_id diff --git a/addons/spectator/functions/fnc_cacheUnitInfo.sqf b/addons/spectator/functions/fnc_cacheUnitInfo.sqf deleted file mode 100644 index 9f40651748..0000000000 --- a/addons/spectator/functions/fnc_cacheUnitInfo.sqf +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Author: SilentSpike - * Caches the units information for quick retrevial in spectator interface PFHs - * - * Arguments: - * 0: Unit to have info cached for - * - * Return Value: - * None - * - * Example: - * [vehicle player] call ace_spectator_fnc_cacheUnitInfo - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_unit"]; -private ["_color","_icon","_name"]; - -// Group info only needs to be cached once (groups can't change) -if (isNil { GETVAR((group _unit),GVAR(gColor),nil) }) then { - _color = [side group _unit] call BIS_fnc_sideColor; - SETVAR((group _unit),GVAR(gColor),_color); -}; - -// Unit info should be updated each time -_icon = getText (configFile >> "CfgVehicles" >> typeOf _unit >> "Icon"); -_name = [_unit,false] call EFUNC(common,getName); - -// Handle CfgVehicleIcons -if (isText (configFile >> "CfgVehicleIcons" >> _icon)) then { - _icon = getText (configFile >> "CfgVehicleIcons" >> _icon); -}; - -SETVAR(_unit,GVAR(uIcon),_icon); -SETVAR(_unit,GVAR(uName),_name); diff --git a/addons/spectator/functions/fnc_cam.sqf b/addons/spectator/functions/fnc_cam.sqf new file mode 100644 index 0000000000..84a9df4e4c --- /dev/null +++ b/addons/spectator/functions/fnc_cam.sqf @@ -0,0 +1,143 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Handles camera initialisation and destruction + * + * Arguments: + * 0: Init/Terminate + * + * Return Value: + * None + * + * Example: + * [true] call ace_spectator_fnc_cam + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_init"]; + +// No change +if (_init isEqualTo !isNil QGVAR(camera)) exitWith {}; + +// Note that init and destroy intentionally happen in reverse order +// Init: Vars > Camera > Camera Stuff +// Destroy: Camera Stuff > Camera > Vars +if (_init) then { + // Start tracking camera attributes if not pre-set by public function + ISNILS(GVAR(camMode),MODE_FREE); + ISNILS(GVAR(camVision),VISION_NORM); + ISNILS(GVAR(camTarget),objNull); + ISNILS(GVAR(camOnLocation),false); + + // Ticking related + GVAR(camDeltaTime) = 0; + GVAR(camLastTickTime) = 0; + GVAR(camHasTarget) = false; + GVAR(camTargetInVehicle) = false; + + // Follow camera related + GVAR(camDistance) = 0; + GVAR(camDistanceTemp) = 0; + GVAR(camYaw) = 0; + GVAR(camPitch) = 0; + + // Toggles + GVAR(camSlow) = false; + GVAR(camLights) = []; + GVAR(camLight) = false; + + // Handle pre-set pos and dir (delete GVARs when done) + private _pos = if (isNil QGVAR(camPos)) then {eyePos player} else {GVAR(camPos)}; + private _dir = if (isNil QGVAR(camDir)) then {getDirVisual player} else {GVAR(camDir)}; + GVAR(camPos) = nil; + GVAR(camDir) = nil; + + // Create the camera (CamCurator required for engine driven controls) + private _camera = "CamCurator" camCreate _pos; + + if (isNull _camera) exitWith { ERROR("Camera wasn't created successfully"); }; + + // Switch to the camera and set its attributes + _camera cameraEffect ["internal", "back"]; + _camera setPosASL _pos; + _camera setDir _dir; + _camera camCommand "maxPitch 89"; + _camera camCommand "minPitch -89"; + _camera camCommand format ["speedDefault %1", SPEED_DEFAULT]; + _camera camCommand format ["speedMax %1", SPEED_FAST]; + _camera camCommand "ceilingHeight 5000"; + cameraEffectEnableHUD true; + + // If camera followed terrain it would be annoying to track units, etc. + _camera camCommand "atl off"; + + // Camera speed should be consistent irrespective of height (painfully slow otherwise) + _camera camCommand "surfaceSpeed off"; + + // Store camera + GVAR(camera) = _camera; + + // Create dummy target used for follow camera + GVAR(camDummy) = "Logic" createVehicleLocal getPosASLVisual GVAR(camTarget); + + // Handle initial camera mode limitation + if !(GVAR(camMode) in GVAR(availableModes)) then { + GVAR(camMode) = GVAR(availableModes) select 0; + }; + + // If inital camera mode is not free cam and no focus, find initial focus + if (GVAR(camMode) != MODE_FREE && isNull GVAR(camTarget)) then { + [true] call FUNC(setFocus); + }; + + // Set the initial camera mode (could be pre-set or limited) + [GVAR(camMode)] call FUNC(cam_setCameraMode); + + // Handle initial vision mode limitation + if !(GVAR(camVision) in GVAR(availableVisions)) then { + GVAR(camVision) = GVAR(availableVisions) select 0; + }; + + // Set the initial vision mode (could be pre-set or limited) + [GVAR(camVision)] call FUNC(cam_setVisionMode); + + // Start ticking (follow camera requires EachFrame to avoid jitter) + GVAR(camTick) = addMissionEventHandler ["EachFrame", {call FUNC(cam_tick)}]; +} else { + // Stop ticking + removeMissionEventHandler ["EachFrame", GVAR(camTick)]; + GVAR(camTick) = nil; + + // Return to player view + if !(isNull GVAR(camera)) then { + GVAR(camera) cameraEffect ["terminate", "back"]; + deleteVehicle GVAR(camera); + }; + player switchCamera "internal"; + + // Remove camera variable + GVAR(camera) = nil; + + // Destroy dummy target + deleteVehicle (GVAR(camDummy)); + GVAR(camDummy) = nil; + + // Stop tracking everything + GVAR(camMode) = nil; + GVAR(camVision) = nil; + GVAR(camTarget) = nil; + GVAR(camOnLocation) = nil; + GVAR(camDeltaTime) = nil; + GVAR(camLastTickTime) = nil; + GVAR(camHasTarget) = nil; + GVAR(camTargetInVehicle) = nil; + GVAR(camDistance) = nil; + GVAR(camDistanceTemp) = nil; + GVAR(camYaw) = nil; + GVAR(camPitch) = nil; + GVAR(camSlow) = nil; + GVAR(camLights) = nil; + GVAR(camLight) = nil; +}; diff --git a/addons/spectator/functions/fnc_cam_prepareTarget.sqf b/addons/spectator/functions/fnc_cam_prepareTarget.sqf new file mode 100644 index 0000000000..abdce831b4 --- /dev/null +++ b/addons/spectator/functions/fnc_cam_prepareTarget.sqf @@ -0,0 +1,51 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Moves the spectator camera to a position relative to the camera focus. + * Used for 3PP camera and teleporting, etc. + * + * Arguments: + * 0: New Target + * + * Return Value: + * None + * + * Example: + * [player] call ace_spectator_fnc_cam_prepareTarget + * + * Public: No + */ + +#include "script_component.hpp" + +private _focus = vehicle (param [0, objNull, [objNull]]); + +if !(isNull _focus) then { + // Interpolate zoom + private _zoom = [0, GVAR(camDistance)] select (GVAR(camMode) == MODE_FOLLOW); + private _zoomTemp = GVAR(camDistanceTemp); + + if (_zoomTemp != _zoom) then { + _zoomTemp = [_zoomTemp, _zoom, 10, GVAR(camDeltaTime)] call BIS_fnc_lerp; + GVAR(camDistanceTemp) = _zoomTemp; + }; + + // The distance at which to place camera from the focus pivot + private _bbd = [_focus] call BIS_fnc_getObjectBBD; + private _distance = (_bbd select 1) + _zoomTemp; + + // The pivot on the target vehicle + private _isMan = _focus isKindOf "Man"; + private _height = if !(_isMan) then { (_bbd select 2) / 3 } else { switch (stance _focus) do { case "STAND": {1.4}; case "CROUCH": {0.8}; default {0.4}; }; }; + + private _center = if (_isMan) then { AGLToASL (_focus modelToWorldVisual (_focus selectionPosition "Spine3")) } else { AGLToASL (_focus modelToWorldVisual [0,0,_height]) }; + + // Set dummy location and rotation + private _dummy = GVAR(camDummy); + + _dummy setPosASL _center; + [_dummy, [GVAR(camYaw), GVAR(camPitch), 0]] call BIS_fnc_setObjectRotation; + + // Apply location and rotation to camera + GVAR(camera) setPosASL (AGLToASL (_dummy modelToWorldVisual [0, -_distance, 0])); + GVAR(camera) setVectorDirAndUp [vectorDirVisual _dummy, vectorUpVisual _dummy]; +}; diff --git a/addons/spectator/functions/fnc_cam_resetTarget.sqf b/addons/spectator/functions/fnc_cam_resetTarget.sqf new file mode 100644 index 0000000000..94f934d6d6 --- /dev/null +++ b/addons/spectator/functions/fnc_cam_resetTarget.sqf @@ -0,0 +1,29 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Removes the current camera interest and detaches dummy target. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_cam_resetTarget + * + * Public: No + */ + +#include "script_component.hpp" + +private _camera = GVAR(camera); +private _dummy = GVAR(camDummy); + +if !(isNull _camera || isNull _dummy) then { + _camera camPrepareTarget objNull; + _camera camCommitPrepared 0; + + detach _dummy; + + GVAR(camHasTarget) = false; +}; diff --git a/addons/spectator/functions/fnc_cam_setCameraMode.sqf b/addons/spectator/functions/fnc_cam_setCameraMode.sqf new file mode 100644 index 0000000000..82bca7e5d5 --- /dev/null +++ b/addons/spectator/functions/fnc_cam_setCameraMode.sqf @@ -0,0 +1,89 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to select the camera mode + * + * Intended to run even if new mode == old mode, as it handles focus + * + * Arguments: + * 0: New camera mode + * + * Return Value: + * None + * + * Example: + * [1] call ace_spectator_fnc_cam_setCameraMode + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_newMode"]; + +private _oldMode = GVAR(camMode); +private _modes = GVAR(availableModes); +private _focus = GVAR(camTarget); + +// If new mode isn't available then keep current (unless current also isn't) +if !(_newMode in _modes) then { + _newMode = _modes select ((_modes find _oldMode) max 0); +}; + +// Can't switch camera from free mode when focus is a location +if (!(isNull _focus || GVAR(camOnLocation)) || _newMode == MODE_FREE) then { + private _camera = GVAR(camera); + private _showHUD = [true,true,true,true,true,true,true,true]; + + if (_newMode == MODE_FPS) then { + _camera cameraEffect ["Terminate", "BACK"]; + _focus switchCamera "INTERNAL"; + + // Reset vision mode + [VISION_NORM] call FUNC(cam_setVisionMode); + + [] call FUNC(cam_resetTarget); + + // Disable camera input + _camera camCommand "manual off"; + + // Hide all unit/group information in first person view + _showHUD = [true,false,false,false,false,false,false,true]; + }; + + if (_newMode == MODE_FOLLOW) then { + _camera cameraEffect ["Internal", "BACK"]; + _focus switchCamera "EXTERNAL"; + + [] call FUNC(cam_resetTarget); + + // Disable camera input + _camera camCommand "manual off"; + }; + + if (_newMode == MODE_FREE) then { + _camera cameraEffect ["Internal", "BACK"]; + player switchCamera "INTERNAL"; + _camera setDir getDirVisual _camera; + + if (!isNull _focus) then { + if (_oldMode == MODE_FPS) then { + [_focus] call FUNC(cam_prepareTarget); + }; + [_focus] call FUNC(cam_setTarget); + }; + + // Enable camera input + _camera camCommand "manual on"; + }; + + // Update the HUD + cameraEffectEnableHUD true; + showHUD _showHUD; + GVAR(camMode) = _newMode; + + // Only update display if it exists, this function is independent of it + if !(isNull SPEC_DISPLAY) then { + [] call FUNC(ui_updateCamButtons); + [] call FUNC(ui_updateHelp); + }; +}; diff --git a/addons/spectator/functions/fnc_cam_setTarget.sqf b/addons/spectator/functions/fnc_cam_setTarget.sqf new file mode 100644 index 0000000000..51cd80215a --- /dev/null +++ b/addons/spectator/functions/fnc_cam_setTarget.sqf @@ -0,0 +1,32 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Sets the current camera interest using dummy target. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [player] call ace_spectator_fnc_cam_setTarget + * + * Public: No + */ + +#include "script_component.hpp" +#define CAMERA_TARGET_CHANGE_TIME 0.5 + +params ["_object"]; + +private _camera = GVAR(camera); +private _dummy = GVAR(camDummy); +private _location = _object worldToModel (_object modelToWorldVisual (_object selectionPosition "Head")); + +if (!isNull _camera && !isNull _dummy) then { + _dummy attachTo [vehicle _object, _location]; + _camera camPrepareTarget _dummy; + _camera camCommitPrepared CAMERA_TARGET_CHANGE_TIME; + + GVAR(camhasTarget) = true; +}; diff --git a/addons/spectator/functions/fnc_cam_setVisionMode.sqf b/addons/spectator/functions/fnc_cam_setVisionMode.sqf new file mode 100644 index 0000000000..c9eed499fe --- /dev/null +++ b/addons/spectator/functions/fnc_cam_setVisionMode.sqf @@ -0,0 +1,45 @@ +/* + * Author: SilentSpike + * Function used to select the camera vision mode + * + * Arguments: + * 0: New vision mode + * + * Return Value: + * None + * + * Example: + * [-1] call ace_spectator_fnc_cam_setVisionMode + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_newVision"]; + +private _oldVision = GVAR(camVision); +private _visions = GVAR(availableVisions); + +// If new vision isn't available then keep current (unless current also isn't) +if !(_newVision in _visions) then { + _newVision = _visions select ((_visions find _oldVision) max 0); +}; + +// Vision mode does not apply to fps view +if (GVAR(camMode) != MODE_FPS) then { + // 0+ are all thermal vision types + if (_newVision < 0) then { + false setCamUseTi 0; + camUseNVG (_newVision >= VISION_NVG); + } else { + true setCamUseTi _newVision; + }; + + // Give user feedback that vision mode changed + if (_newVision != _oldVision) then { + playSound "RscDisplayCurator_visionMode"; + + GVAR(camVision) = _newVision; + }; +}; diff --git a/addons/spectator/functions/fnc_cam_tick.sqf b/addons/spectator/functions/fnc_cam_tick.sqf new file mode 100644 index 0000000000..7609234a8e --- /dev/null +++ b/addons/spectator/functions/fnc_cam_tick.sqf @@ -0,0 +1,84 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to perform camera ticks + * + * Updates camera position in follow mode + * Updates camera focus if current focus becomes null (in unit modes) + * Updates camera when focus enters/exits a vehicle + * Updates camera lights position + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * addMissionEventHandler ["EachFrame", {call ace_spectator_fnc_cam_tick}] + * + * Public: No + */ + +#include "script_component.hpp" + +BEGIN_COUNTER(camTick); +private _cameraMode = GVAR(camMode); +private _camTarget = GVAR(camTarget); + +// UI mouse handler makes use of delta time between camera ticks +private _currentTime = diag_tickTime; +GVAR(camDeltaTime) = _currentTime - GVAR(camLastTickTime); +GVAR(camLastTickTime) = _currentTime; + + +// If no focus in unit camera modes try to find a new one +if (_cameraMode != MODE_FREE) then { + private _focus = if (isNull _camTarget) then { + private _testFocus = ([] call FUNC(getTargetEntities)) select 0; + if (isNil "_testFocus") then { + objNull + } else { + _testFocus + } + } else { + _camTarget + }; + + // If new focus was found then switch to it + if (!isNull _focus && {_focus != _camTarget}) then { + [_focus] call FUNC(setFocus); + }; + + // Update the follow camera position + if (!isNull _focus && {_cameraMode == MODE_FOLLOW}) then { + [_focus] call FUNC(cam_prepareTarget); + }; +}; + +// Refresh the local variable +_camTarget = GVAR(camTarget); + +// Focus get in / out of vehicle state +if !(isNull _camTarget) then { + private _targetInVeh = GVAR(camTargetInVehicle); + + if (GVAR(camHasTarget)) then { + if (!_targetInVeh && { vehicle _camTarget != _camTarget }) then { + [_camTarget] call FUNC(cam_setTarget); + GVAR(camTargetInVehicle) = true; + }; + + if (_targetInVeh && { vehicle _camTarget == _camTarget }) then { + [_camTarget] call FUNC(cam_setTarget); + GVAR(camTargetInVehicle) = false; + }; + }; +} else { + GVAR(camTargetInVehicle) = false; +}; + +// Camera lights +if (count GVAR(camLights) > 1) then { + (GVAR(camLights) select 1) setPosASL (AGLToASL (screenToWorld getMousePosition)); +}; +END_COUNTER(camTick); diff --git a/addons/spectator/functions/fnc_cam_toggleSlow.sqf b/addons/spectator/functions/fnc_cam_toggleSlow.sqf new file mode 100644 index 0000000000..e689351ce8 --- /dev/null +++ b/addons/spectator/functions/fnc_cam_toggleSlow.sqf @@ -0,0 +1,36 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Function used to set camera slow speed mode + * + * Arguments: + * 0: Enable slow speed + * + * Return Value: + * None + * + * Example: + * [true] call ace_spectator_fnc_cam_toggleSlow + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_slowSpeed"]; + +if !(GVAR(camSlow) isEqualTo _slowSpeed) then { + private _camera = GVAR(camera); + + if (GVAR(camMode) == MODE_FREE) then { + GVAR(camSlow) = _slowSpeed; + + if (_slowSpeed) then { + _camera camCommand format ["speedDefault %1", SPEED_SLOW]; + } else { + _camera camCommand format ["speedDefault %1", SPEED_DEFAULT]; + }; + } else { + _camera camCommand format ["speedDefault %1", SPEED_DEFAULT]; + GVAR(camSlow) = false; + }; +}; diff --git a/addons/spectator/functions/fnc_compat_counter.sqf b/addons/spectator/functions/fnc_compat_counter.sqf new file mode 100644 index 0000000000..8a54d6dbdf --- /dev/null +++ b/addons/spectator/functions/fnc_compat_counter.sqf @@ -0,0 +1,36 @@ +/* + * Author: SilentSpike + * Handles integrating the counter respawn template into the spectator UI + * + * Should be called from both RscRespawnCounter XEH and spectator init to account for arbitrary order + * + * Arguments: + * 0: RscRespawnCounter + * + * Return Value: + * None + * + * Example: + * [GETUVAR(RscRespawnCounter,displayNull)] call ace_spectator_fnc_compat_counter + * + * Public: No + */ + +#include "script_component.hpp" +#define IDC_COUNTER_TITLE 1001 +#define IDC_COUNTER_BACK 1002 +#define IDC_COUNTER_TEXT 1003 + +params ["_display"]; + +if (isNull _display) exitWith {}; + +{ + private _ctrl = _display displayCtrl _x; + + (ctrlPosition _ctrl) params ["_xOld","","_w","_h"]; + + // Center controls at top middle of screen + _ctrl ctrlSetPosition [_xOld, safeZoneY, _w, _h]; + _ctrl ctrlCommit 0; +} forEach [IDC_COUNTER_TITLE, IDC_COUNTER_BACK, IDC_COUNTER_TEXT]; diff --git a/addons/spectator/functions/fnc_compat_spectatorBI.sqf b/addons/spectator/functions/fnc_compat_spectatorBI.sqf new file mode 100644 index 0000000000..3c90585aa5 --- /dev/null +++ b/addons/spectator/functions/fnc_compat_spectatorBI.sqf @@ -0,0 +1,53 @@ +/* + * Author: SilentSpike + * Handles "compatibility" (i.e. override) for BI spectator respawn types 1, 4 & 5 + * + * Called from the RscDisplayEGSpectator XEH + * + * Arguments: + * 0: RscDisplayEGSpectator + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_compat_spectatorBI + * + * Public: No + */ + +#include "script_component.hpp" + +private _respawn = getMissionConfigValue ["respawn",0]; +if (_respawn isEqualType "") then { _respawn = ["","bird","","","group","side"] find (toLower _respawn); }; +if !(_respawn in [1,4,5]) exitWith {}; + +// Remember to check for side specific templates +private _templates = getMissionConfigValue [["respawnTemplates",side group player] joinString "",getMissionConfigValue ["respawnTemplates",[]]]; +if !(QUOTE(ADDON) in _templates) exitWith {}; + +// Kill BI spectator +["Terminate"] call BIS_fnc_EGSpectator; + +// Start our spectator +[true] call FUNC(setSpectator); + +// Delete the seagull that spawns (not actually the player, a CfgNonAIVehicles object) +// Respawn type 1 is handled in the template where seagull is passed as paremeter +if (_respawn in [4,5]) then { + // This could delete seagulls created by a wildlife module (a necessary evil) + // TODO: Try to find seagull position and delete more accurately with reduced radius + { if (_x isKindOf "seagull") then {deleteVehicle _x;}; } forEach (nearestObjects [player, [], 250]); +}; + +// Switch to a virtual unit so draw3D continues to work +private _grp = createGroup [sideLogic, true]; +private _virtual = _grp createUnit [QGVAR(virtual),[0,0,0],[],0,""]; + +// Transfer assigned zeus if applicable +private _zeus = getAssignedCuratorLogic player; +if !(isNull _zeus) then { + [QGVAR(transferZeus), [_virtual,_zeus]] call CBA_fnc_serverEvent; +}; + +selectPlayer _virtual; diff --git a/addons/spectator/functions/fnc_compat_zeus.sqf b/addons/spectator/functions/fnc_compat_zeus.sqf new file mode 100644 index 0000000000..f379e549ce --- /dev/null +++ b/addons/spectator/functions/fnc_compat_zeus.sqf @@ -0,0 +1,34 @@ +/* + * Author: SilentSpike + * Handles compatibility with curator interface (i.e. re-opens spectator if applicable) + * + * Called from the RscDisplayCurator XEH + * + * Arguments: + * 0: RscDisplayCurator + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_compat_zeus + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_display"]; + +_display displayAddEventHandler ["Unload",{ + // Only re-open if still a spectator (and not remote-controlling) + if (GVAR(isSet) && {isNull (GETMVAR(bis_fnc_moduleRemoteControl_unit,objNull))}) then { + // Display must be opened next frame to prevent game crash + [{ + // Reset the camera and vision modes + [GVAR(camMode)] call FUNC(cam_setCameraMode); + [GVAR(camVision)] call FUNC(cam_setVisionMode); + [true] call FUNC(ui); + }] call CBA_fnc_execNextFrame; + }; +}]; diff --git a/addons/spectator/functions/fnc_cycleCamera.sqf b/addons/spectator/functions/fnc_cycleCamera.sqf deleted file mode 100644 index 474d25cd0c..0000000000 --- a/addons/spectator/functions/fnc_cycleCamera.sqf +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Author: SilentSpike - * Cycle through the spectator camera vision/view/units in steps - * - * Arguments: - * 0: Camera mode steps - * 1: Camera unit steps - * 2: Vision mode steps - * - * Return Value: - * None - * - * Example: - * [0, -1] call ace_spectator_fnc_cycleCamera - * - * Public: No - */ - -#include "script_component.hpp" - -params [["_stepMode",0], ["_stepUnit",0], ["_stepVision",0]]; -private ["_modes","_visions","_iMode","_iVision","_countModes","_countVisions","_newMode","_newVision","_newUnit"]; - -_modes = GVAR(availableModes); -_units = GVAR(unitList); -_visions = GVAR(availableVisions); - -// Get current index -_iMode = (_modes find GVAR(camMode)) max 0; -_iUnit = (_units find GVAR(camUnit)) max 0; -_iVision = (_visions find GVAR(camVision)) max 0; - -_countModes = count _modes; -_countUnits = count _units; -_countVisions = count _visions; - -// Step index by step number (loop at ends) -if (_countModes != 0) then { - _iMode = (_iMode + _stepMode) % _countModes; - if (_iMode < 0) then { _iMode = _countModes + _iMode; }; -}; - -if (_countUnits != 0) then { - _iUnit = (_iUnit + _stepUnit) % _countUnits; - if (_iUnit < 0) then { _iUnit = _countUnits + _iUnit; }; -}; - -if (_countVisions != 0) then { - _iVision = (_iVision + _stepVision) % _countVisions; - if (_iVision < 0) then { _iVision = _countVisions + _iVision; }; -}; - -// Get value at new index -_newMode = _modes select _iMode; -_newUnit = _units select _iUnit; -_newVision = _visions select _iVision; - -[_newMode, _newUnit, _newVision] call FUNC(transitionCamera); diff --git a/addons/spectator/functions/fnc_getCameraAttributes.sqf b/addons/spectator/functions/fnc_getCameraAttributes.sqf new file mode 100644 index 0000000000..adaa8b57c8 --- /dev/null +++ b/addons/spectator/functions/fnc_getCameraAttributes.sqf @@ -0,0 +1,30 @@ +/* + * Author: SilentSpike + * Returns the current spectator camera attributes (see setCameraAttributes for details). + * + * Arguments: + * None + * + * Return Value: + * [Mode, Focus, Vision, Position, Direction] + * + * Example: + * [] call ace_spectator_fnc_getCameraAttributes + * + * Public: Yes + */ + +#include "script_component.hpp" + +if !(isNil QGVAR(camera)) then { + [GVAR(camMode), GVAR(camTarget), GVAR(camVision), getPosATL GVAR(camera), getDirVisual GVAR(camera)] +} else { + // These values could be pre-set by function + [ + GETMVAR(GVAR(camMode),0), + GETMVAR(GVAR(camTarget),objNull), + GETMVAR(GVAR(camVision),-2), + GETMVAR(GVAR(camPos),[ARR_3(0,0,0)]), + GETMVAR(GVAR(camDir),0) + ] +}; diff --git a/addons/spectator/functions/fnc_getGroupIcon.sqf b/addons/spectator/functions/fnc_getGroupIcon.sqf new file mode 100644 index 0000000000..fdc2772edc --- /dev/null +++ b/addons/spectator/functions/fnc_getGroupIcon.sqf @@ -0,0 +1,162 @@ +/* + * Author: SilentSpike + * Function used to get an appropriate icon for provided group. Approximate. + * + * Arguments: + * 0: Group to get the icon of + * 1: Return icons for draw3D use (Default: false) + * + * Return Value: + * Icon of group + * + * Examples: + * [group player] call ace_spectator_fnc_getGroupIcon + * + * Public: No + */ + +#include "script_component.hpp" +#define ICON_PATH(var1) QUOTE(a3\ui_f\data\Map\Markers\NATO\var1) + +// Military icons +#define ICON_UNKNOWN [ICON_PATH(b_unknown.paa), QPATHTOF(data\b_unknown.paa)] select _forDraw +#define ICON_UAV [ICON_PATH(b_uav.paa), QPATHTOF(data\b_uav.paa)] select _forDraw +#define ICON_SUPPORT [ICON_PATH(b_support.paa), QPATHTOF(data\b_support.paa)] select _forDraw +#define ICON_SERVICE [ICON_PATH(b_service.paa), QPATHTOF(data\b_service.paa)] select _forDraw +#define ICON_RECON [ICON_PATH(b_recon.paa), QPATHTOF(data\b_recon.paa)] select _forDraw +#define ICON_PLANE [ICON_PATH(b_plane.paa), QPATHTOF(data\b_plane.paa)] select _forDraw +#define ICON_NAVAL [ICON_PATH(b_naval.paa), QPATHTOF(data\b_naval.paa)] select _forDraw +#define ICON_MOTOR_INF [ICON_PATH(b_motor_inf.paa), QPATHTOF(data\b_motor_inf.paa)] select _forDraw +#define ICON_MORTAR [ICON_PATH(b_mortar.paa), QPATHTOF(data\b_mortar.paa)] select _forDraw +#define ICON_MED [ICON_PATH(b_med.paa), QPATHTOF(data\b_med.paa)] select _forDraw +#define ICON_MECH_INF [ICON_PATH(b_mech_inf.paa), QPATHTOF(data\b_mech_inf.paa)] select _forDraw +#define ICON_MAINT [ICON_PATH(b_maint.paa), QPATHTOF(data\b_maint.paa)] select _forDraw +#define ICON_INSTALLATION [ICON_PATH(b_installation.paa), QPATHTOF(data\b_installation.paa)] select _forDraw +#define ICON_INF [ICON_PATH(b_inf.paa), QPATHTOF(data\b_inf.paa)] select _forDraw +#define ICON_ART [ICON_PATH(b_art.paa), QPATHTOF(data\b_art.paa)] select _forDraw +#define ICON_ARMOR [ICON_PATH(b_armor.paa), QPATHTOF(data\b_armor.paa)] select _forDraw +#define ICON_AIR [ICON_PATH(b_air.paa), QPATHTOF(data\b_air.paa)] select _forDraw + +// Civilian icons +#define CIV_ICON_UNKNOWN [ICON_PATH(c_unknown.paa), QPATHTOF(data\c_unknown.paa)] select _forDraw +#define CIV_ICON_AIR [ICON_PATH(c_air.paa), QPATHTOF(data\c_air.paa)] select _forDraw +#define CIV_ICON_CAR [ICON_PATH(c_car.paa), QPATHTOF(data\c_car.paa)] select _forDraw +#define CIV_ICON_PLANE [ICON_PATH(c_plane.paa), QPATHTOF(data\c_plane.paa)] select _forDraw +#define CIV_ICON_SHIP [ICON_PATH(c_ship.paa), QPATHTOF(data\c_ship.paa)] select _forDraw + +params [["_group", grpNull, [grpNull]], ["_forDraw", false, [true]]]; + +// Handle empty or null group +private _leader = leader _group; +if (isNull _leader) exitWith { [ICON_UNKNOWN, CIV_ICON_UNKNOWN] select (side _group == civilian) }; + +// Civilians are easy, just check leader's vehicle (unlikely group is large) +if (side _group == civilian) exitWith { + if (_leader != vehicle _leader) then { + // More common cases should be checked first + (vehicle _leader) call { + if (_this isKindOf "Car") exitWith { + CIV_ICON_CAR + }; + + // Plane inherits Air, check first + if (_this isKindOf "Plane") exitWith { + CIV_ICON_PLANE + }; + + if (_this isKindOf "Air") exitWith { + CIV_ICON_AIR + }; + + if (_this isKindOf "Ship") exitWith { + CIV_ICON_SHIP + }; + + CIV_ICON_UNKNOWN + }; + } else { + CIV_ICON_UNKNOWN + }; +}; + +// Handle military groups +private _units = units _group; +private _vehicles = (_units apply { vehicle _x }) - _units; + +// If more than 33% of the group is mounted, use most common vehicle +if (count _vehicles >= 0.33 * count _units) exitWith { + // Check the most likely cases first + _vehicles call { + private _threshold = 0.5 * count _this; + + if ("Car" countType _this >= _threshold) exitWith { + ICON_MOTOR_INF + }; + + // APC inherits Tank, check first + if ("APC" countType _this >= _threshold) exitWith { + ICON_MECH_INF + }; + + // MBT_01_arty_base_F inherits Tank, check first + // Unfortunately no common arty class to check + if ("MBT_01_arty_base_F" countType _this >= _threshold) exitWith { + ICON_ART + }; + if ("MBT_02_arty_base_F" countType _this >= _threshold) exitWith { + ICON_ART + }; + + if ("Tank" countType _this >= _threshold) exitWith { + ICON_ARMOR + }; + + // UAV inherits Plane, check first + if ("UAV" countType _this >= _threshold) exitWith { + ICON_UAV + }; + + // Plane inherits Air, check first + if ("Plane" countType _this >= _threshold) exitWith { + ICON_PLANE + }; + + if ("Air" countType _this >= _threshold) exitWith { + ICON_AIR + }; + + if ("Ship" countType _this >= _threshold) exitWith { + ICON_NAVAL + }; + + // StaticMortar inherits StaticWeapon, check first + if ("StaticMortar" countType _this >= _threshold) exitWith { + ICON_MORTAR + }; + + if ("StaticWeapon" countType _this >= _threshold) exitWith { + ICON_INSTALLATION + }; + + // If it reaches here then it's a mixed group of vehicles + ICON_UNKNOWN + }; +}; + +// Check leader for medic/engineer/etc, otherwise just default to infantry +private _medic = [_leader] call EFUNC(common,isMedic); +private _engineer = [_leader] call EFUNC(common,isEngineer); + +if (_medic && _engineer) exitWith { + ICON_SUPPORT +}; + +if (_medic) exitWith { + ICON_MED +}; + +if (_engineer) exitWith { + ICON_MAINT +}; + +ICON_INF diff --git a/addons/spectator/functions/fnc_getTargetEntities.sqf b/addons/spectator/functions/fnc_getTargetEntities.sqf new file mode 100644 index 0000000000..80ee72fe03 --- /dev/null +++ b/addons/spectator/functions/fnc_getTargetEntities.sqf @@ -0,0 +1,45 @@ +/* + * Author: SilentSpike + * Gets the possible entities to spectate based on settings. + * Optionally includes dead units for the list and switching. + * + * Arguments: + * 0: Include dead + * + * Return Value: + * Valid entities + * + * Example: + * [true] call ace_spectator_fnc_getTargetEntities + * + * Public: No + */ + +#include "script_component.hpp" + +// Include dead units if specified (used by entity list) +private _entities = allUnits; +if (param [0,false]) then { _entities append allDeadMen; }; + +// Quicker to use local vars that are accessed often in iteration +private _sides = GVAR(availableSides); + +// Apply entity filtering +_entities = _entities select { + (GVAR(enableAI) || {isPlayer _x}) && // AI setting + {(side group _x) in _sides} && // Available sides + {simulationEnabled _x && {simulationEnabled vehicle _x}} && // Hide disabled things + { !isObjectHidden _x && {!isObjectHidden vehicle _x} } // Hide hidden things +}; + +// Respect the blacklist +_entities = _entities - GVAR(unitBlacklist); + +// Whitelist overrides filtering +_entities append GVAR(unitWhitelist); + +// Never include the local player +_entities deleteAt (_entities find player); + +// Return no duplicates +_entities arrayIntersect _entities diff --git a/addons/spectator/functions/fnc_handleCamera.sqf b/addons/spectator/functions/fnc_handleCamera.sqf deleted file mode 100644 index f21581ea58..0000000000 --- a/addons/spectator/functions/fnc_handleCamera.sqf +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Author: F3 Project, Head, SilentSpike - * Handles free camera manipulation according to input - * - * Arguments: - * 0: Parameters - * 1: PFH handle - * - * Return Value: - * None - * - * Example: - * [ace_spectator_fnc_handleCamera, 0] call CBA_fnc_addPerFrameHandler; - * - * Public: No - */ - -#include "script_component.hpp" - -// Kill PFH when not in free cam (or display is closed) -if (isNil QGVAR(camHandler)) exitWith { [_this select 1] call CBA_fnc_removePerFrameHandler; }; - -private ["_camera","_pan","_tilt"]; - -_pan = (GVAR(camPan) + 360) % 360; -_tilt = GVAR(camTilt); - -if (GVAR(camMode) == 0) then { - private ["_oldPos","_altMod","_zoomMod","_mX","_mY","_mZ","_x","_y","_z"]; - _camera = GVAR(freeCamera); - _oldPos = GVAR(camPos); - - // Dolly/Boom amount should be influnced by zoom level (it should really be exponential) - // Dollying should also slow as the camera gets close to the ground - _zoomMod = (GVAR(camZoom) * 0.8) max 1; - _altMod = ((((getPos _camera) select 2) * 0.05) max 0.1) min 1; - - _mX = (GVAR(camDolly) select 0) * _altMod / _zoomMod; - _mY = (GVAR(camDolly) select 1) * _altMod / _zoomMod; - _mZ = GVAR(camBoom) / _zoomMod; - - _x = (_oldPos select 0) + (_mX * cos(_pan)) + (_mY * sin(_pan)); - _y = (_oldPos select 1) - (_mX * sin(_pan)) + (_mY * cos(_pan)); - _z = (_oldPos select 2) + _mZ; - - // Prevent camera going under terrain - GVAR(camPos) = [_x,_y,_z max (getTerrainHeightASL [_x,_y])]; - - // Update camera position and rotation - _camera setPosASL GVAR(camPos); - _camera setDir _pan; - [_camera, _tilt, 0] call BIS_fnc_setPitchBank; -} else { - private ["_unit","_target","_distance","_vector"]; - _camera = GVAR(unitCamera); - - _unit = GVAR(camUnit); - _target = GVAR(targetCamera); - _distance = GVAR(camDistance); - - // Generate a position vector relative to the unit - _vector = [0,-_distance*cos(_tilt),0]; - _vector = [_vector,[-_pan] call CBA_fnc_simplifyAngle180] call BIS_fnc_rotateVector2D; - _vector = _vector vectorAdd [0,0,_distance*sin(-_tilt)]; - - // Update the position of the target camera (used for smooth unit tracking) - _target camSetPos ((ASLToAGL getPosASLVisual _unit) vectorAdd [0,0,1.5]); - _target camCommit 0; - - // Update the relative position of the unit camera - _camera camSetRelPos _vector; - _camera camCommit 0; - - GVAR(camPos) = getPosASL _camera; -}; diff --git a/addons/spectator/functions/fnc_handleCompass.sqf b/addons/spectator/functions/fnc_handleCompass.sqf deleted file mode 100644 index 6f0f94b0ee..0000000000 --- a/addons/spectator/functions/fnc_handleCompass.sqf +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Author: SilentSpike, voiper - * Handles the spectator UI compass - * - * Arguments: - * 0: Parameters - * 1: PFH handle - * - * Return Value: - * None - * - * Example: - * [ace_spectator_fnc_handleCompass, 0, _display] call CBA_fnc_addPerFrameHandler; - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_display"]; - -// Kill PFH when compass hidden (or display is closed) -if (isNil QGVAR(compHandler)) exitWith { [_this select 1] call CBA_fnc_removePerFrameHandler; }; - -private ["_compass","_NE","_ES","_SW","_WN","_compassW","_degree","_heading","_offset","_positions","_sequence"]; - -_compass = _display displayCtrl IDC_COMP; - -_NE = _compass controlsGroupCtrl IDC_COMP_0; -_ES = _compass controlsGroupCtrl IDC_COMP_90; -_SW = _compass controlsGroupCtrl IDC_COMP_180; -_WN = _compass controlsGroupCtrl IDC_COMP_270; - -_compassW = (ctrlPosition _compass) select 2; -_degree = _compassW / 180; - -// Get direction of screen rather than object (accounts for unit freelook) -_heading = (positionCameraToWorld [0,0,1]) vectorDiff (positionCameraToWorld [0,0,0]); -_heading = (((_heading select 0) atan2 (_heading select 1)) + 360) % 360; -_offset = -(_heading % 90) * _degree; - -_positions = [ - [_compassW * -0.5 + _offset, 0], - [_offset, 0], - [_compassW * 0.5 + _offset, 0], - [_compassW + _offset, 0] -]; - -_sequence = [ - [_SW, _WN, _NE, _ES], - [_WN, _NE, _ES, _SW], - [_NE, _ES, _SW, _WN], - [_ES, _SW, _WN, _NE] -] select floor(_heading/90); - - -{ - _x ctrlSetPosition (_positions select _forEachIndex); - _x ctrlCommit 0; -} forEach _sequence; diff --git a/addons/spectator/functions/fnc_handleFired.sqf b/addons/spectator/functions/fnc_handleFired.sqf new file mode 100644 index 0000000000..b9258cbd02 --- /dev/null +++ b/addons/spectator/functions/fnc_handleFired.sqf @@ -0,0 +1,48 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to add projectiles to be drawn when a unit fires + * + * Arguments: + * Fired EH arguments + * + * Return Value: + * None + * + * Example: + * _unit addEventHandler ["Fired",{_this call ace_spectator_fnc_handleFired}] + * + * Public: No + */ + +#include "script_component.hpp" + +params [ + "_unit", + ["_weapon", "", [""]], + "", // Muzzle + "", // Mode + "", // Ammo + "", // Magazine + ["_projectile", objNull, [objNull]] +]; + +// Remove the EH when spectator is no longer active or unit is removed +if (isNil QGVAR(entitiesToDraw) || {!(_unit in GVAR(entitiesToDraw))}) exitWith { + //USES_VARIABLES ["_thisEventHandler"] + _unit removeEventHandler ["Fired", _thisEventHandler]; + SETVAR(_unit,GVAR(firedEH),nil); +}; + +// Fire time used for unit icon highlighting +_unit setVariable [QGVAR(highlightTime), time + FIRE_HIGHLIGHT_TIME]; + +// Store projectiles / grenades for drawing +if (GVAR(drawProjectiles) && {!isNull _projectile}) then { + if (_weapon == "Throw") then { + if (count GVAR(grenadesToDraw) > MAX_GRENADES) then { GVAR(grenadesToDraw) deleteAt 0; }; + GVAR(grenadesToDraw) pushBack _projectile; + } else { + if (count GVAR(projectilesToDraw) > MAX_PROJECTILES) then { GVAR(projectilesToDraw) deleteAt 0; }; + GVAR(projectilesToDraw) pushBack [_projectile, [[getPosVisual _projectile, [1,0,0,0]]]]; + }; +}; diff --git a/addons/spectator/functions/fnc_handleIcons.sqf b/addons/spectator/functions/fnc_handleIcons.sqf deleted file mode 100644 index 8f735c2a32..0000000000 --- a/addons/spectator/functions/fnc_handleIcons.sqf +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Author: Head, SilentSpike - * Handles rendering the spectator 3D unit icons - * - * Arguments: - * 0: Parameters - * 1: PFH handle - * - * Return Value: - * None - * - * Example: - * [ace_spectator_fnc_handleIcons, 0] call CBA_fnc_addPerFrameHandler; - * - * Public: No - */ - -#include "script_component.hpp" - -if !(GVAR(showIcons)) exitWith {}; -private ["_refPoint","_drawVehicles","_leader","_color","_txt","_unit"]; - -// Draw groups unless leader is within distance -_refPoint = [GVAR(freeCamera),GVAR(camUnit)] select (GVAR(camMode) > 0); -_drawVehicles = []; -{ - _leader = leader _x; - if ((_leader distanceSqr _refPoint) > 40000) then { - _color = GETVAR(_x,GVAR(gColor),[ARR_4(0,0,0,0)]); - _txt = groupID _x; - - drawIcon3D ["\A3\ui_f\data\map\markers\nato\b_inf.paa", _color, (_leader modelToWorldVisual (_leader selectionPosition "Head")) vectorAdd [0,0,28.5], 1, 1, 0, _txt, 2, 0.02]; - } else { - _drawVehicles append (units _x); - }; - false -} count GVAR(groupList); - -// Draw units for groups within distance -{ - _color = GETVAR((group _x),GVAR(gColor),[ARR_4(0,0,0,0)]); - _txt = ["", GETVAR(_x,GVAR(uName),"")] select (isPlayer _x); - - drawIcon3D ["a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\UnitIcon_ca.paa", _color, (_x modelToWorldVisual (_x selectionPosition "Head")) vectorAdd [0,0,1.5], 0.7, 0.7, 0, _txt, 1, 0.02]; - false -} count (_drawVehicles arrayIntersect GVAR(unitList)); diff --git a/addons/spectator/functions/fnc_handleInterface.sqf b/addons/spectator/functions/fnc_handleInterface.sqf deleted file mode 100644 index 9ba64ec0f0..0000000000 --- a/addons/spectator/functions/fnc_handleInterface.sqf +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Author: SilentSpike - * Handles spectator interface events - * - * Arguments: - * 0: Event name - * 1: Event arguments - * - * Return Value: - * None - * - * Example: - * ["onLoad",_this] call ace_spectator_fnc_handleInterface - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_mode",["_args",[]]]; - -switch (toLower _mode) do { - case "onload": { - _args params ["_display"]; - SETUVAR(GVAR(interface),_display); - - // Always show interface and hide map upon opening - [_display,nil,nil,!GVAR(showInterface),GVAR(showMap)] call FUNC(toggleInterface); - - // Initalize the unit tree - ["onUnitsUpdate",[(_display displayCtrl IDC_UNIT) controlsGroupCtrl IDC_UNIT_TREE]] call FUNC(handleInterface); - - // Keep unit list and tree up to date - [FUNC(handleUnits), 9, _display] call CBA_fnc_addPerFrameHandler; - - // Handle 3D unit icons - GVAR(iconHandler) = addMissionEventHandler ["Draw3D", {call FUNC(handleIcons)}]; - - // Populate the help window - private _help = (_display displayCtrl IDC_HELP) controlsGroupCtrl IDC_HELP_LIST; - { - _i = _help lbAdd (_x select 0); - if ((_x select 1) == "") then { - _help lbSetPicture [_i,"\A3\ui_f\data\map\markers\military\dot_CA.paa"]; - _help lbSetPictureColor [_i,[COL_FORE]]; - } else { - _help lbSetTooltip [_i,_x select 1]; - }; - } forEach [ - [localize LSTRING(uiControls),""], - [localize LSTRING(uiToggleUnits),keyName 2], - [localize LSTRING(uiToggleHelp),keyName 3], - [localize LSTRING(uiToggleTools),keyName 4], - [localize LSTRING(uiToggleCompass),keyName 5], - [localize LSTRING(uiToggleIcons),keyName 6], - [localize LSTRING(uiToggleMap),keyName 50], - [localize LSTRING(uiToggleInterface),keyName 14], - [localize LSTRING(freeCamControls),""], - [localize LSTRING(freeCamForward),keyName 17], - [localize LSTRING(freeCamBackward),keyName 31], - [localize LSTRING(freeCamLeft),keyName 30], - [localize LSTRING(freeCamRight),keyName 32], - [localize LSTRING(freeCamUp),keyName 16], - [localize LSTRING(freeCamDown),keyName 44], - [localize LSTRING(freeCamPan),"RMB (Hold)"], - [localize LSTRING(freeCamDolly),"LMB (Hold)"], - [localize LSTRING(freeCamBoost),"Shift (Hold)"], - [localize LSTRING(attributeControls),""], - [localize LSTRING(nextCam),keyName 200], - [localize LSTRING(prevCam),keyName 208], - [localize LSTRING(nextUnit),keyName 205], - [localize LSTRING(prevUnit),keyName 203], - [localize LSTRING(nextVis),keyName 49], - [localize LSTRING(prevVis),format["%1 + %2",keyName 29,keyname 49]], - [localize LSTRING(adjZoom),"Scrollwheel"], - [localize LSTRING(adjSpeed),format["%1 + Scrollwheel",keyName 29]], - [localize LSTRING(incZoom),format["%1/%2",keyName 74,keyName 78]], - [localize LSTRING(incSpeed),format["%1 + %2/%3",keyName 29,keyName 74,keyName 78]], - [localize LSTRING(reZoom),format["%1 + %2",keyName 56,keyName 74]], - [localize LSTRING(reSpeed),format["%1 + %2",keyName 56,keyName 78]] - ]; - - // Handle support for BI's respawn counter - [{ - if !(isNull (GETUVAR(RscRespawnCounter,displayNull))) then { - disableSerialization; - private ["_counter","_title","_back","_timer","_frame","_x","_y"]; - _counter = GETUVAR(RscRespawnCounter,displayNull); - _title = _counter displayCtrl 1001; - _back = _counter displayCtrl 1002; - _timer = _counter displayCtrl 1003; - _frame = _counter ctrlCreate ["RscFrame",1008]; - - _x = safeZoneX + safeZoneW - TOOL_W * 4 - MARGIN * 3; - _y = safeZoneY + safeZoneH - TOOL_H; - - // Timer - _title ctrlSetPosition [_x,_y,TOOL_W,TOOL_H]; - _back ctrlSetPosition [_x,_y,TOOL_W,TOOL_H]; - _timer ctrlSetPosition [_x,_y,TOOL_W,TOOL_H]; - _frame ctrlSetPosition [_x,_y,TOOL_W,TOOL_H]; - - _title ctrlSetBackgroundColor [0,0,0,0]; - _back ctrlSetBackgroundColor [COL_BACK]; - _timer ctrlSetFontHeight TOOL_H; - _frame ctrlSetTextColor [COL_FORE]; - - _title ctrlCommit 0; - _back ctrlCommit 0; - _timer ctrlCommit 0; - _frame ctrlCommit 0; - }; - },[],0.5] call CBA_fnc_waitAndExecute; - }; - case "onunload": { - // Kill GUI PFHs - removeMissionEventHandler ["Draw3D",GVAR(iconHandler)]; - GVAR(camHandler) = nil; - GVAR(compHandler) = nil; - GVAR(iconHandler) = nil; - GVAR(toolHandler) = nil; - - // Reset variables - GVAR(camBoom) = 0; - GVAR(camDolly) = [0,0]; - GVAR(ctrlKey) = false; - GVAR(heldKeys) = []; - GVAR(heldKeys) resize 255; - GVAR(mouse) = [false,false]; - GVAR(mousePos) = [0.5,0.5]; - }; - // Mouse events - case "onmousebuttondown": { - _args params ["_ctrl","_button"]; - GVAR(mouse) set [_button,true]; - - // Detect right click - if ((_button == 1) && (GVAR(camMode) == 1)) then { - // In first person toggle sights mode - GVAR(camGun) = !GVAR(camGun); - [] call FUNC(transitionCamera); - }; - }; - case "onmousebuttonup": { - _args params ["_ctrl","_button"]; - - GVAR(mouse) set [_button,false]; - if (_button == 0) then { GVAR(camDolly) = [0,0]; }; - }; - case "onmousezchanged": { - _args params ["_ctrl","_zChange"]; - - // Scroll to modify distance value in third person - if (GVAR(camMode) == 0) then { - // Scroll to change speed, modifier for zoom - if (GVAR(ctrlKey)) then { - [nil,nil,nil,nil,nil,nil,nil, GVAR(camSpeed) + _zChange * 0.2] call FUNC(setCameraAttributes); - } else { - [nil,nil,nil,nil,nil,nil, GVAR(camZoom) + _zChange * 0.1] call FUNC(setCameraAttributes); - }; - } else { - GVAR(camDistance) = ((GVAR(camDistance) - _zChange * 2) max 5) min 50; - }; - }; - case "onmousemoving": { - _args params ["_ctrl","_x","_y"]; - - [_x,_y] call FUNC(handleMouse); - }; - // Keyboard events - case "onkeydown": { - _args params ["_display","_dik","_shift","_ctrl","_alt"]; - - if ((alive player) && {_dik in (actionKeys "curatorInterface")} && {!isNull (getAssignedCuratorLogic player)}) exitWith { - [QGVAR(zeus)] call FUNC(interrupt); - ["zeus"] call FUNC(handleInterface); - }; - if (_dik in (actionKeys "Chat")) exitWith { - false - }; - if (_dik in (actionKeys "PrevChannel" + actionKeys "NextChannel")) exitWith { - !(isServer || serverCommandAvailable "#kick") - }; - - // Handle held keys (prevent repeat calling) - if (GVAR(heldKeys) param [_dik,false]) exitWith {}; - // Exclude movement/adjustment keys so that speed can be adjusted on fly - if !(_dik in [16,17,30,31,32,44,74,78]) then { - GVAR(heldKeys) set [_dik,true]; - }; - - switch (_dik) do { - case 1: { // Esc - [QGVAR(escape)] call FUNC(interrupt); - ["escape"] call FUNC(handleInterface); - }; - case 2: { // 1 - [_display,nil,nil,nil,nil,nil,true] call FUNC(toggleInterface); - }; - case 3: { // 2 - [_display,nil,true] call FUNC(toggleInterface); - }; - case 4: { // 3 - [_display,nil,nil,nil,nil,true] call FUNC(toggleInterface); - }; - case 5: { // 4 - [_display,true] call FUNC(toggleInterface); - }; - case 6: { // 5 - GVAR(showIcons) = !GVAR(showIcons); - }; - case 14: { // Backspace - [_display,nil,nil,true] call FUNC(toggleInterface); - }; - case 16: { // Q - GVAR(camBoom) = 0.5 * GVAR(camSpeed) * ([1, 2] select _shift); - }; - case 17: { // W - GVAR(camDolly) set [1, GVAR(camSpeed) * ([1, 2] select _shift)]; - }; - case 29: { // Ctrl - GVAR(ctrlKey) = true; - }; - case 30: { // A - GVAR(camDolly) set [0, -GVAR(camSpeed) * ([1, 2] select _shift)]; - }; - case 31: { // S - GVAR(camDolly) set [1, -GVAR(camSpeed) * ([1, 2] select _shift)]; - }; - case 32: { // D - GVAR(camDolly) set [0, GVAR(camSpeed) * ([1, 2] select _shift)]; - }; - case 44: { // Z - GVAR(camBoom) = -0.5 * GVAR(camSpeed) * ([1, 2] select _shift); - }; - case 49: { // N - if (GVAR(camMode) != 1) then { - if (_ctrl) then { - [nil,nil,-1] call FUNC(cycleCamera); - } else { - [nil,nil,1] call FUNC(cycleCamera); - }; - }; - }; - case 50: { // M - [_display,nil,nil,nil,true] call FUNC(toggleInterface); - }; - case 57: { // Spacebar - // Switch between unit and freecam here - }; - case 74: { // Num - - if (_alt) exitWith { [nil,nil,nil,nil,nil,nil, 1.25] call FUNC(setCameraAttributes); }; - if (_ctrl) then { - [nil,nil,nil,nil,nil,nil,nil, GVAR(camSpeed) - 0.05] call FUNC(setCameraAttributes); - } else { - [nil,nil,nil,nil,nil,nil, GVAR(camZoom) - 0.01] call FUNC(setCameraAttributes); - }; - }; - case 78: { // Num + - if (_alt) exitWith { [nil,nil,nil,nil,nil,nil,nil, 1.5] call FUNC(setCameraAttributes); }; - if (_ctrl) then { - [nil,nil,nil,nil,nil,nil,nil, GVAR(camSpeed) + 0.05] call FUNC(setCameraAttributes); - } else { - [nil,nil,nil,nil,nil,nil, GVAR(camZoom) + 0.01] call FUNC(setCameraAttributes); - }; - }; - case 200: { // Up arrow - [-1] call FUNC(cycleCamera); - }; - case 203: { // Left arrow - [nil,1] call FUNC(cycleCamera); - }; - case 205: { // Right arrow - [nil,-1] call FUNC(cycleCamera); - }; - case 208: { // Down arrow - [1] call FUNC(cycleCamera); - }; - }; - - true - }; - case "onkeyup": { - _args params ["_display","_dik","_shift","_ctrl","_alt"]; - - // No longer being held - GVAR(heldKeys) set [_dik,nil]; - - switch (_dik) do { - case 16: { // Q - GVAR(camBoom) = 0; - }; - case 17: { // W - GVAR(camDolly) set [1, 0]; - }; - case 29: { // Ctrl - GVAR(ctrlKey) = false; - }; - case 30: { // A - GVAR(camDolly) set [0, 0]; - }; - case 31: { // S - GVAR(camDolly) set [1, 0]; - }; - case 32: { // D - GVAR(camDolly) set [0, 0]; - }; - case 44: { // Z - GVAR(camBoom) = 0; - }; - }; - - true - }; - // Tree events - case "ontreedblclick": { - // Update camera view when listbox unit is double clicked on - _args params ["_tree","_sel"]; - - // Ensure a unit was selected - if (count _sel == 3) then { - private ["_netID","_newUnit","_newMode"]; - _netID = (_args select 0) tvData _sel; - _newUnit = objectFromNetId _netID; - - // When unit is reselected, toggle camera mode - if (_newUnit == GVAR(camUnit) || GVAR(camMode) == 0) then { - _newMode = [2,2,1] select GVAR(camMode); - }; - - [_newMode,_newUnit] call FUNC(transitionCamera); - }; - }; - case "onunitsupdate": { - _args params ["_tree"]; - private ["_cachedUnits","_cachedGrps","_cachedSides","_sT","_gT","_uT","_s","_g","_u","_grp","_unit","_side"]; - - // Cache existing group and side nodes and cull removed data - _cachedUnits = []; - _cachedGrps = []; - _cachedSides = []; - // Track deleted nodes to account for decrease in index - _sT = _tree tvCount []; - for [{_s = 0;}, {_s < _sT}, {_s = _s + 1}] do { - _gT = _tree tvCount [_s]; - - for [{_g = 0;}, {_g < _gT}, {_g = _g + 1}] do { - _grp = groupFromNetID (_tree tvData [_s,_g]); - - if (_grp in GVAR(groupList)) then { - _cachedGrps pushBack _grp; - _cachedGrps pushBack _g; - - _uT = _tree tvCount [_s,_g]; - for [{_u = 0;}, {_u < _uT}, {_u = _u + 1}] do { - _unit = objectFromNetId (_tree tvData [_s,_g,_u]); - - if (_unit in GVAR(unitList)) then { - _cachedUnits pushBack _unit; - } else { - _tree tvDelete [_s,_g,_u]; - _u = _u - 1; - _uT = _uT - 1; - }; - }; - } else { - _tree tvDelete [_s,_g]; - _g = _g - 1; - _gT = _gT - 1; - }; - }; - - if ((_tree tvCount [_s]) > 0) then { - _cachedSides pushBack (_tree tvText [_s]); - _cachedSides pushBack _s; - } else { - _tree tvDelete [_s]; - _s = _s - 1; - _sT = _sT - 1; - }; - }; - - // Update the tree from the unit list - { - _grp = group _x; - _side = [side _grp] call BIS_fnc_sideName; - - // Use correct side node - if !(_side in _cachedSides) then { - // Add side node - _s = _tree tvAdd [[], _side]; - _tree tvExpand [_s]; - - _cachedSides pushBack _side; - _cachedSides pushBack _s; - } else { - // If side already processed, use existing node - _s = _cachedSides select ((_cachedSides find _side) + 1); - }; - - // Use correct group node - if !(_grp in _cachedGrps) then { - // Add group node - _g = _tree tvAdd [[_s], groupID _grp]; - _tree tvSetData [[_s,_g], netID _grp]; - - _cachedGrps pushBack _grp; - _cachedGrps pushBack _g; - } else { - // If group already processed, use existing node - _g = _cachedGrps select ((_cachedGrps find _grp) + 1); - }; - - _u = _tree tvAdd [[_s,_g], GETVAR(_x,GVAR(uName),"")]; - _tree tvSetData [[_s,_g,_u], netID _x]; - _tree tvSetPicture [[_s,_g,_u], GETVAR(_x,GVAR(uIcon),"")]; - _tree tvSetPictureColor [[_s,_g,_u], GETVAR(_grp,GVAR(gColor),[ARR_4(1,1,1,1)])]; - - _tree tvSort [[_s,_g],false]; - } forEach (GVAR(unitList) - _cachedUnits); - - _tree tvSort [[],false]; - - if ((_tree tvCount []) <= 0) then { - _tree tvAdd [[], localize LSTRING(units_none)]; - }; - }; - // Map events - case "onmapclick": { - _args params ["_map","_button","_x","_y","_shift","_ctrl","_alt"]; - private ["_newPos","_oldZ"]; - - if ((GVAR(camMode) == 0) && (_button == 0)) then { - _newPos = _map ctrlMapScreenToWorld [_x,_y]; - _oldZ = (ASLtoATL GVAR(camPos)) select 2; - _newPos set [2, _oldZ]; - [nil,nil,nil, _newPos] call FUNC(setCameraAttributes); - }; - }; - // Interrupt events - case "escape": { - createDialog (["RscDisplayInterrupt", "RscDisplayMPInterrupt"] select isMultiplayer); - - disableSerialization; - private _dlg = finddisplay 49; - _dlg displayAddEventHandler ["KeyDown", { - _key = _this select 1; - !(_key == 1) - }]; - - // Disable save, respawn, options & manual buttons - (_dlg displayCtrl 103) ctrlEnable false; - if !(alive player) then { - (_dlg displayCtrl 1010) ctrlEnable false; - }; - (_dlg displayCtrl 101) ctrlEnable false; - (_dlg displayCtrl 122) ctrlEnable false; - - // Initalize abort button (the "spawn" is a necessary evil) - (_dlg displayCtrl 104) ctrlAddEventHandler ["ButtonClick",{_this spawn { - disableSerialization; - _display = ctrlparent (_this select 0); - _abort = [localize "str_msg_confirm_return_lobby",nil,localize "str_disp_xbox_hint_yes",localize "str_disp_xbox_hint_no",_display,nil,true] call BIS_fnc_guiMessage; - if (_abort) then {_display closeDisplay 2; failMission "loser"}; - }}]; - - // PFH to re-open display when menu closes - [{ - if !(isNull (_this select 0)) exitWith {}; - - // If still a spectator then re-enter the interface - [QGVAR(escape),false] call FUNC(interrupt); - - [_this select 1] call CBA_fnc_removePerFrameHandler; - },0,_dlg] call CBA_fnc_addPerFrameHandler; - }; - case "zeus": { - openCuratorInterface; - - [{ - // PFH to re-open display when menu closes - [{ - if !((isNull curatorCamera) && {isNull (GETMVAR(bis_fnc_moduleRemoteControl_unit,objNull))}) exitWith {}; - - // If still a spectator then re-enter the interface - [QGVAR(zeus),false] call FUNC(interrupt); - - [_this select 1] call CBA_fnc_removePerFrameHandler; - },0] call CBA_fnc_addPerFrameHandler; - },[],5] call CBA_fnc_waitAndExecute; - - true - }; -}; diff --git a/addons/spectator/functions/fnc_handleMap.sqf b/addons/spectator/functions/fnc_handleMap.sqf deleted file mode 100644 index b83ccdcd7d..0000000000 --- a/addons/spectator/functions/fnc_handleMap.sqf +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Author: Head, SilentSpike - * Handles rendering the spectator map icons - * - * Arguments: - * 0: Parameters - * 1: PFH handle - * - * Return Value: - * None - * - * Example: - * [ace_spectator_fnc_handleIcons, 0] call CBA_fnc_addPerFrameHandler; - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_map"]; -private ["_center","_radius","_scaled","_drawVehicles","_leader","_color","_cachedVehicles","_unit","_icon","_txt"]; - -if (GVAR(camMode) == 0) then { - _map drawIcon ["\A3\UI_F\Data\GUI\Rsc\RscDisplayMissionEditor\iconcamera_ca.paa",[0,0,0,1],GVAR(freeCamera),20,20,GVAR(camPan)]; -}; - -_center = _map ctrlMapScreenToWorld [0.5,0.5]; -_radius = (_map ctrlMapScreenToWorld [safeZoneX,safeZoneY]) distance2D _center; -_scaled = (ctrlMapScale _map) > 0.2; - -// Draw only group icons when scaled out -_drawVehicles = []; -{ - _leader = leader _x; - if (_scaled) then { - _color = GETVAR(_x,GVAR(gColor),[ARR_4(0,0,0,0)]); - _map drawIcon ["\A3\ui_f\data\map\markers\nato\b_inf.paa", _color, _leader, 20, 20, 0, "", 0, 0]; - } else { - if ((_leader distance2D _center) < _radius) then { - _drawVehicles append (units _x); - }; - }; - nil -} count GVAR(groupList); - -// Draw units when group leader is within screen bounds -_cachedVehicles = []; -{ - _unit = vehicle _x; - - if !(_unit in _cachedVehicles) then { - _cachedVehicles pushBack _unit; - - // Use previously cached info where possible - if (GETVAR(_unit,GVAR(uIcon),"") == "") then { - [_unit] call FUNC(cacheUnitInfo); - }; - - // Function has caching built in - _color = [side effectiveCommander _unit] call BIS_fnc_sideColor; - _icon = GETVAR(_unit,GVAR(uIcon),""); - _txt = ["", GETVAR(_x,GVAR(uName),"")] select (isPlayer _x); - - _map drawIcon [_icon, _color, _unit, 19, 19, getDir _unit, _txt, 1, 0.04]; - }; - nil -} count (_drawVehicles arrayIntersect GVAR(unitList)); diff --git a/addons/spectator/functions/fnc_handleMouse.sqf b/addons/spectator/functions/fnc_handleMouse.sqf deleted file mode 100644 index 1c2b62798c..0000000000 --- a/addons/spectator/functions/fnc_handleMouse.sqf +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Author: F3 Project, Head, SilentSpike - * Processes the change in mouse position for the spectator camera - * - * Arguments: - * 0: Mouse x coord - * 1: Mouse y coord - * - * Return Value: - * None - * - * Example: - * [0.5, 0.5] call ace_spectator_fnc_handleMouse; - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_x","_y"]; -private ["_leftButton","_rightButton","_oldX","_oldY","_deltaX","_deltaY","_zoomMod"]; - -_leftButton = GVAR(mouse) select 0; -_rightButton = GVAR(mouse) select 1; - -_oldX = GVAR(mousePos) select 0; -_oldY = GVAR(mousePos) select 1; - -// Get change in pos -_deltaX = _oldX - _x; -_deltaY = _oldY - _y; - -if (_leftButton) then { - GVAR(camDolly) set [0, _deltaX * -100 * GVAR(camSpeed)]; - GVAR(camDolly) set [1, _deltaY * 100 * GVAR(camSpeed)]; -} else { - if (_rightButton) then { - // Pan/Tilt amount should be influnced by zoom level (it should really be exponential) - _zoomMod = (GVAR(camZoom) * 0.8) max 1; - - GVAR(camPan) = GVAR(camPan) - ((_deltaX * 360) / _zoomMod); - GVAR(camTilt) = ((GVAR(camTilt) + ((_deltaY * 180) / _zoomMod)) min 89) max -89; - }; -}; - -GVAR(mousePos) = [_x,_y]; diff --git a/addons/spectator/functions/fnc_handleToolbar.sqf b/addons/spectator/functions/fnc_handleToolbar.sqf deleted file mode 100644 index 4e79c172bd..0000000000 --- a/addons/spectator/functions/fnc_handleToolbar.sqf +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Author: Karel Moricky, SilentSpike - * Handles the spectator UI toolbar values - * - * Arguments: - * 0: Parameters - * 1: PFH handle - * - * Return Value: - * None - * - * Example: - * [ace_spectator_fnc_handleToolbar, 0, _display] call CBA_fnc_addPerFrameHandler; - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_display"]; - -// Kill PFH when toolbar hidden (or display is closed) -if (isNil QGVAR(toolHandler)) exitWith { [_this select 1] call CBA_fnc_removePerFrameHandler; }; - -private ["_name","_vision","_fov","_speed","_mode","_time","_toolbar"]; -_toolbar = _display displayCtrl IDC_TOOL; - -// Find all tool values -if (GVAR(camVision) >= 0) then { - _vision = localize LSTRING(VisionThermal); -} else { - _vision = [localize LSTRING(VisionNight), localize LSTRING(VisionNormal)] select (GVAR(camVision) < -1); -}; - -if (GVAR(camMode) == 0) then { - _fov = format ["%1x", floor(GVAR(camZoom) * 100) * 0.01]; - _speed = format ["%1 m/s", floor(GVAR(camSpeed) * 100) * 0.01]; -} else { - _vision = [side group GVAR(camUnit)] call BIS_fnc_sideName; - _fov = format ["%1 m", floor(getPosASL GVAR(camUnit) select 2)]; - _speed = format ["%1 km/h", floor(speed GVAR(camUnit)) max 0]; -}; - -if (alive GVAR(camUnit)) then { - _name = GETVAR(GVAR(camUnit),GVAR(uName),""); -} else { - _name = localize "STR_Special_None"; -}; - -_mode = [localize LSTRING(ViewFree),localize LSTRING(ViewInternal),localize LSTRING(ViewExternal)] select GVAR(camMode); -_time = [daytime,"HH:MM"] call BIS_fnc_timeToString; - -// Update the UI tools -(_toolbar controlsGroupCtrl IDC_TOOL_CLOCK) ctrlSetText _time; -(_toolbar controlsGroupCtrl IDC_TOOL_VISION) ctrlSetText _vision; -(_toolbar controlsGroupCtrl IDC_TOOL_FOV) ctrlSetText _fov; -(_toolbar controlsGroupCtrl IDC_TOOL_NAME) ctrlSetText _name; -(_toolbar controlsGroupCtrl IDC_TOOL_SPEED) ctrlSetText _speed; -(_toolbar controlsGroupCtrl IDC_TOOL_VIEW) ctrlSetText _mode; diff --git a/addons/spectator/functions/fnc_handleUnits.sqf b/addons/spectator/functions/fnc_handleUnits.sqf deleted file mode 100644 index f51c922b8e..0000000000 --- a/addons/spectator/functions/fnc_handleUnits.sqf +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Author: SilentSpike - * Maintains the spectatable unit list and updates the unit tree accordingly - * Also updates current camera unit when status changes - * - * Arguments: - * 0: Parameters - * 1: PFH handle - * - * Return Value: - * None - * - * Example: - * [ace_spectator_fnc_handleUnits, 10, _display] call CBA_fnc_addPerFrameHandler; - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_display"]; - -// Kill PFH when display is closed -if (isNull _display) exitWith { [_this select 1] call CBA_fnc_removePerFrameHandler; }; - -// Remove all dead and null units from the list -[] call FUNC(updateUnits); - -// Camera shouldn't stay on unit that isn't in the list (unless dead) -if !(GVAR(camUnit) in GVAR(unitList)) then { - if (alive GVAR(camUnit) || isNull GVAR(camUnit)) then { - [nil,1] call FUNC(cycleCamera); - }; -}; - -// Reduce overhead when unit tree is hidden -if (ctrlShown (_display displayCtrl IDC_UNIT)) then { - // Reduce overhead by spreading across frames - [FUNC(handleInterface),["onUnitsUpdate",[(_display displayCtrl IDC_UNIT) controlsGroupCtrl IDC_UNIT_TREE]],1] call CBA_fnc_waitAndExecute; -}; diff --git a/addons/spectator/functions/fnc_interrupt.sqf b/addons/spectator/functions/fnc_interrupt.sqf index 0826949b30..60c733db38 100644 --- a/addons/spectator/functions/fnc_interrupt.sqf +++ b/addons/spectator/functions/fnc_interrupt.sqf @@ -1,27 +1,17 @@ /* * Author: SilentSpike - * Interrupts the spectator interface for external systems - * - * Arguments: - * 0: Reason - * 1: Interrupting (default: true) - * - * Return Value: - * None - * - * Example: - * ["mySystem"] call ace_spectator_fnc_interrupt - * - * Public: Yes + * Deprecated. Technically never publically documented, but just in case. */ #include "script_component.hpp" params [["_reason", "", [""]], ["_interrupt", true, [true]]]; +ACE_DEPRECATED(QFUNC(interrupt),"3.12.0","just close and reopen spectator"); + // Nothing to do when spectator is closed if !(GVAR(isSet)) exitWith {}; -if (_reason == "") exitWith { ERROR("Invalid Reason"); }; +if (_reason == "") exitWith { WARNING("Invalid Reason"); }; if (_interrupt) then { GVAR(interrupts) pushBack _reason; } else { @@ -29,16 +19,11 @@ if (_interrupt) then { }; if (GVAR(interrupts) isEqualTo []) then { - if (isNull (GETUVAR(GVAR(interface),displayNull))) then { - (findDisplay 46) createDisplay QGVAR(interface); - [] call FUNC(transitionCamera); + if (isNull SPEC_DISPLAY) then { + [true] call FUNC(ui); }; } else { - if !(isNull (GETUVAR(GVAR(interface),displayNull))) then { - while {dialog} do { - closeDialog 0; - }; - - (GETUVAR(GVAR(interface),displayNull)) closeDisplay 0; + if !(isNull SPEC_DISPLAY) then { + [false] call FUNC(ui); }; }; diff --git a/addons/spectator/functions/fnc_moduleSpectatorSettings.sqf b/addons/spectator/functions/fnc_moduleSpectatorSettings.sqf index 8224c77e57..6d5f8222f0 100644 --- a/addons/spectator/functions/fnc_moduleSpectatorSettings.sqf +++ b/addons/spectator/functions/fnc_moduleSpectatorSettings.sqf @@ -8,7 +8,7 @@ * 2: activated * * Return Value: - * None + * None * * Example: * [LOGIC, [bob, kevin], true] call ace_spectator_fnc_moduleSpectatorSettings @@ -22,7 +22,7 @@ params ["_logic", "_units", "_activated"]; if !(_activated) exitWith {}; -[_logic, QGVAR(filterUnits), "unitsFilter"] call EFUNC(common,readSettingFromModule); -[_logic, QGVAR(filterSides), "sidesFilter"] call EFUNC(common,readSettingFromModule); +[_logic, QGVAR(enableAI), "enableAI"] call EFUNC(common,readSettingFromModule); [_logic, QGVAR(restrictModes), "cameraModes"] call EFUNC(common,readSettingFromModule); [_logic, QGVAR(restrictVisions), "visionModes"] call EFUNC(common,readSettingFromModule); +[_logic, QGVAR(mapLocations), "mapLocations"] call EFUNC(common,readSettingFromModule); diff --git a/addons/spectator/functions/fnc_players.sqf b/addons/spectator/functions/fnc_players.sqf new file mode 100644 index 0000000000..3525b835d5 --- /dev/null +++ b/addons/spectator/functions/fnc_players.sqf @@ -0,0 +1,19 @@ +/* + * Author: SilentSpike + * Return all of the player entities who are currently in ace spectator + * + * Arguments: + * None + * + * Return Value: + * Spectator Players + * + * Example: + * [] call ace_spectator_fnc_players + * + * Public: Yes + */ + +#include "script_component.hpp" + +allPlayers select { GETVAR(_x,GVAR(isSet),false) } diff --git a/addons/spectator/functions/fnc_removeLocation.sqf b/addons/spectator/functions/fnc_removeLocation.sqf new file mode 100644 index 0000000000..9c9d531f5e --- /dev/null +++ b/addons/spectator/functions/fnc_removeLocation.sqf @@ -0,0 +1,39 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Remove a location that can be seen in spectator view. Local effect. + * + * Arguments: + * 0: Unique ID + * + * Return Value: + * Success + * + * Example: + * [_id] call ace_spectator_fnc_removeLocation + * + * Public: Yes + */ + +#include "script_component.hpp" + +params [["_id","",[""]]]; + +private _index = -1; + +{ + if ((_x select 0) == _id) exitWith { + _index = _forEachIndex; + }; +} forEach GVAR(locationsList); + +GVAR(locationsList) deleteAt _index; + +// Update the list if appropriate +if !(isNull SPEC_DISPLAY) then { + if (GVAR(uiListType) == LIST_LOCATIONS) then { + [] call FUNC(ui_updateListLocations); + }; +}; + +// Return +_index != -1 diff --git a/addons/spectator/functions/fnc_respawnTemplate.sqf b/addons/spectator/functions/fnc_respawnTemplate.sqf index 59759d1371..7cc8b28031 100644 --- a/addons/spectator/functions/fnc_respawnTemplate.sqf +++ b/addons/spectator/functions/fnc_respawnTemplate.sqf @@ -1,7 +1,8 @@ /* * Author: SilentSpike - * The ace_spectator respawn template, handles killed + respawn - * Can be used via BI's respawn framework, see: + * The ace_spectator respawn template, compatible with types 1,2,3,4 & 5 + * + * Handles killed and respawned events as per BI's respawn framework: * https://community.bistudio.com/wiki/Arma_3_Respawn * * Arguments: @@ -21,26 +22,18 @@ #include "script_component.hpp" -params [["_unit",objNull,[objNull]], ["_killer",objNull,[objNull]], ["_respawn",0,[0]], ["_respawnDelay",0,[0]]]; +params [["_newCorpse",objNull,[objNull]], ["_oldKiller",objNull,[objNull]], ["_respawn",0,[0]], ["_respawnDelay",0,[0]]]; -// Some environment information can be used for the initial camera attributes -if (isNull _killer) then {_killer = _unit}; -private _vision = [-2,-1] select (sunOrMoon < 1); -private _pos = (getPosATL _unit) vectorAdd [0,0,5]; - -// Enter/exit spectator based on the respawn type and whether killed/respawned -if (alive _unit) then { - if (_respawn == 1) then { - [_unit] call FUNC(stageSpectator); - [2,_killer,_vision,_pos,getDir _unit] call FUNC(setCameraAttributes); - [true] call FUNC(setSpectator); - } else { - [false] call FUNC(setSpectator); - }; -} else { - // Negligible respawn delay can result in entering spectator after respawn - if (playerRespawnTime <= 1) exitWith {}; - - [2,_killer,_vision,_pos,getDir _unit] call FUNC(setCameraAttributes); - [true] call FUNC(setSpectator); +// Compatibility handled via spectator display XEH +if (_respawn in [0,1,4,5]) exitWith { + // This only applies to respawn type 1 (BIRD/SPECTATOR) + // Remove the seagull (not actually the player, a CfgNonAIVehicles object) + if (typeOf _newCorpse == "seagull") then { deleteVehicle _newCorpse; }; +}; + +// If player died while already in spectator, ignore +if (!GVAR(isSet) || {alive _newCorpse}) then { + // Negligible respawn delay can result in entering spectator after respawn + // So we just use this value rather than living state of the unit + [playerRespawnTime > 1] call FUNC(setSpectator); }; diff --git a/addons/spectator/functions/fnc_setCameraAttributes.sqf b/addons/spectator/functions/fnc_setCameraAttributes.sqf index fac9c94059..1081fcc5a9 100644 --- a/addons/spectator/functions/fnc_setCameraAttributes.sqf +++ b/addons/spectator/functions/fnc_setCameraAttributes.sqf @@ -1,27 +1,30 @@ /* * Author: SilentSpike - * Sets the spectator camera attributes as desired - * All values are optional and default to whatever the current value is + * Sets the spectator camera attributes as desired. Local effect. + * All values are optional and default to no change. * * Arguments: * 0: Camera mode * - 0: Free - * - 1: Internal - * - 2: External - * 1: Camera unit (objNull for random) + * - 1: First Person + * - 2: Follow + * 1: Camera focus * 2: Camera vision * - -2: Normal * - -1: Night vision * - 0: Thermal white hot * - 1: Thermal black hot + * - ... * 3: Camera position (ATL) - * 4: Camera pan (0 - 360) - * 5: Camera tilt (-90 - 90) - * 6: Camera zoom (0.01 - 2) - * 7: Camera speed in m/s (0.05 - 10) + * 4: Camera direction (0 - 360) + * + * Notes: + * - If camera mode is not free and camera has no focus, random will be used + * - To remove any current camera focus in free cam, use objNull + * - To select a random camera focus, use a boolean * * Return Value: - * None + * None * * Example: * [1, objNull] call ace_spectator_fnc_setCameraAttributes @@ -32,37 +35,68 @@ #include "script_component.hpp" params [ - ["_mode",GVAR(camMode),[0]], - ["_unit",GVAR(camUnit),[objNull]], - ["_vision",GVAR(camVision),[0]], - ["_position",ASLtoATL GVAR(camPos),[[]],3], - ["_heading",GVAR(camPan),[0]], - ["_tilt",GVAR(camTilt),[0]], - ["_zoom",GVAR(camZoom),[0]], - ["_speed",GVAR(camSpeed),[0]] + ["_mode",nil,[0]], + ["_focus",nil,[objNull,true]], + ["_vision",nil,[0]], + ["_position",nil,[[]],3], + ["_direction",nil,[0]] ]; -// Normalize input -if !(_mode in GVAR(availableModes)) then { - _mode = GVAR(availableModes) select ((GVAR(availableModes) find GVAR(camMode)) max 0); +if (count _this > 5) then { + ACE_DEPRECATED("Use of ""tilt"", ""zoom"" and ""speed"" camera attributes","3.12.0","N/A") }; -if !(_vision in GVAR(availableVisions)) then { - _vision = GVAR(availableVisions) select ((GVAR(availableVisions) find GVAR(camVision)) max 0); -}; - -GVAR(camPan) = _heading % 360; -GVAR(camSpeed) = (_speed max 0.05) min 10; -GVAR(camTilt) = (_tilt max -89) min 89; -GVAR(camUnit) = _unit; -GVAR(camVision) = _vision; -GVAR(camZoom) = (_zoom min 2) max 0.01; - // Apply if camera exists -if (GVAR(isSet)) then { - GVAR(camPos) = (ATLtoASL _position); // Camera position will be updated in FUNC(handleCamera) - [_mode,_unit,_vision] call FUNC(transitionCamera); +if !(isNil QGVAR(camera)) then { + // These functions are smart and handle unavailable inputs + if !(isNil "_focus") then { + [_focus] call FUNC(setFocus); + }; + + if !(isNil "_mode") then { + // If mode not free and no focus, find focus + if ((_mode != MODE_FREE) && {isNull GVAR(camTarget) || GVAR(camOnLocation)}) then { + [true] call FUNC(setFocus); + }; + + [_mode] call FUNC(cam_setCameraMode); + }; + + if !(isNil "_vision") then { + [_vision] call FUNC(cam_setVisionMode); + }; + + if !(isNil "_position") then { + GVAR(camera) setPosATL _position; + }; + + if !(isNil "_direction") then { + GVAR(camera) setDir _direction; + }; } else { - GVAR(camMode) = _mode; - GVAR(camPos) = (ATLtoASL _position); + if !(isNil "_focus") then { + // If there are no entities this becomes nil, handled on camera startup + if (_focus isEqualType true) then { + _focus = ([] call FUNC(getTargetEntities)) select 0; + }; + + GVAR(camTarget) = _focus; + }; + + if !(isNil "_mode") then { + GVAR(camMode) = _mode; + }; + + if !(isNil "_vision") then { + GVAR(camVision) = _vision; + }; + + // GVARs exits purely for pre-setting of these attributes + if !(isNil "_position") then { + GVAR(camPos) = ATLtoASL _position; + }; + + if !(isNil "_direction") then { + GVAR(camDir) = _direction; + }; }; diff --git a/addons/spectator/functions/fnc_setFocus.sqf b/addons/spectator/functions/fnc_setFocus.sqf new file mode 100644 index 0000000000..7e7d36393a --- /dev/null +++ b/addons/spectator/functions/fnc_setFocus.sqf @@ -0,0 +1,68 @@ +/* + * Author: AACO, SilentSpike + * Function used to set the camera focus + * + * Arguments: + * 0: New focus + * 1: Focus is a location + * + * Return Value: + * None + * + * Example: + * [player, false] call ace_spectator_fnc_setFocus + * + * Public: No + */ + +#include "script_component.hpp" + +params [["_newFocus", objNull, [objNull,true]], ["_focusIsLocation",false]]; + +// If boolean provided then first find a focus +if (_newFocus isEqualType true) then { + private _testFocus = ([] call FUNC(getTargetEntities)) select 0; + + if (isNil "_testFocus") then { + if (MODE_FREE in GVAR(availableModes)) then { + WARNING("No available entities to focus on. Switching to free cam."); + [MODE_FREE] call FUNC(cam_setCameraMode); + _newFocus = objNull; + } else { + // Default to player if necessary + WARNING("No available entities to focus on. Using player."); + _newFocus = player; + }; + } else { + _newFocus = _testFocus; + }; +}; + +if (_newFocus != GVAR(camTarget) && { !(isNull _newFocus && { isNull GVAR(camTarget) }) }) then { + GVAR(camTarget) = _newFocus; + + if (isNull _newFocus) then { + if (GVAR(camMode) == MODE_FREE) then { + [] call FUNC(cam_resetTarget); + } else { + [MODE_FREE] call FUNC(cam_setCameraMode); + }; + } else { + // Locations can only be focused on in free camera + if (_focusIsLocation) then { + [MODE_FREE] call FUNC(cam_setCameraMode); + } else { + [GVAR(camMode)] call FUNC(cam_setCameraMode); + }; + }; + + // GVAR used to prevent camera switching and UI info on locations + GVAR(camOnLocation) = _focusIsLocation; + + // Only update display if it exists, this function is independent of it + if !(isNull SPEC_DISPLAY) then { + [] call FUNC(ui_updateListFocus); + [] call FUNC(ui_updateWidget); + [] call FUNC(ui_updateHelp); + }; +}; diff --git a/addons/spectator/functions/fnc_setSpectator.sqf b/addons/spectator/functions/fnc_setSpectator.sqf index 45bb15df42..79496df15d 100644 --- a/addons/spectator/functions/fnc_setSpectator.sqf +++ b/addons/spectator/functions/fnc_setSpectator.sqf @@ -1,17 +1,17 @@ /* * Author: SilentSpike - * Sets local client to the given spectator state (virtually) - * To physically handle a spectator see ace_spectator_fnc_stageSpectator + * Enter/exit spectator mode for the local player * * Client will be able to communicate in ACRE/TFAR as appropriate - * The spectator interface will be opened/closed + * If "hide player" is true player will be hidden from group, invisible and invulnerable, but unmoved * * Arguments: * 0: Spectator state of local client (default: true) * 1: Force interface (default: true) + * 2: Hide player (if alive) (default: true) * * Return Value: - * None + * None * * Example: * [true] call ace_spectator_fnc_setSpectator @@ -21,47 +21,43 @@ #include "script_component.hpp" -params [["_set",true,[true]], ["_force",true,[true]]]; +params [["_set",true,[true]], ["_force",true,[true]], ["_hide",true,[true]]]; // Only clients can be spectators if !(hasInterface) exitWith {}; -// Exit if no change +// Let the display know if it is or isn't forced +// Could be switched after spectator has already started +GVAR(uiForced) = _force; + +// Exit if no change (everything above this may need to be ran again) if (_set isEqualTo GVAR(isSet)) exitWith {}; -// Handle common addon audio +// Delay if local player (must not be ACE_Player) is not fully initalized +if (isNil { player } || { isNull player }) exitWith { + [ + { !isNil { player } && { !isNull player } }, + FUNC(setSpectator), + _this + ] call CBA_fnc_waitUntilAndExecute; +}; + +// Remove any current deafness and disable volume updates while spectating if (["ace_hearing"] call EFUNC(common,isModLoaded)) then { EGVAR(hearing,disableVolumeUpdate) = _set; EGVAR(hearing,deafnessDV) = 0; }; + +// Toggle spectator mode in 3rd party radio addons if (["acre_sys_radio"] call EFUNC(common,isModLoaded)) then {[_set] call acre_api_fnc_setSpectator}; if (["task_force_radio"] call EFUNC(common,isModLoaded)) then {[player, _set] call TFAR_fnc_forceSpectator}; if (_set) then { - // Initalize camera variables - GVAR(camBoom) = 0; - GVAR(camDolly) = [0,0]; - GVAR(camGun) = false; + // Initalize the camera + [true] call FUNC(cam); - // Initalize display variables - GVAR(ctrlKey) = false; - GVAR(heldKeys) = []; - GVAR(heldKeys) resize 255; - GVAR(mouse) = [false,false]; - GVAR(mousePos) = [0.5,0.5]; - - // Update units before opening to support pre-set camera unit - [] call FUNC(updateUnits); - - // Initalize the camera objects - GVAR(freeCamera) = "Camera" camCreate (ASLtoATL GVAR(camPos)); - GVAR(unitCamera) = "Camera" camCreate [0,0,0]; - GVAR(targetCamera) = "Camera" camCreate [0,0,0]; - - // Initalize view - GVAR(unitCamera) camSetTarget GVAR(targetCamera); - GVAR(unitCamera) camCommit 0; - [] call FUNC(transitionCamera); + // Create the display when main display is ready + [{ !isNull MAIN_DISPLAY },{ [true] call FUNC(ui) }] call CBA_fnc_waitUntilAndExecute; // Cache current channel to switch back to on exit GVAR(channelCache) = currentChannel; @@ -70,35 +66,6 @@ if (_set) then { GVAR(channel) radioChannelAdd [player]; setCurrentChannel (5 + GVAR(channel)); - // Close map and clear the chat - openMap [false,false]; - clearRadio; - enableRadio false; - - // Disable BI damage effects - BIS_fnc_feedback_allowPP = false; - - // Close any open dialogs - while {dialog} do { - closeDialog 0; - }; - - [{ - disableSerialization; - // Create the display - _display = (findDisplay 46) createDisplay QGVAR(interface); - - // If not forced, make esc end spectator - if (_this) then { - _display displayAddEventHandler ["KeyDown", { - if (_this select 1 == 1) then { - [false] call FUNC(setSpectator); - true - }; - }]; - }; - }, !_force] call CBA_fnc_execNextFrame; - // Cache and disable nametag settings if (["ace_nametags"] call EFUNC(common,isModLoaded)) then { GVAR(nametagSettingCache) = [EGVAR(nametags,showPlayerNames), EGVAR(nametags,showNamesForAI)]; @@ -106,19 +73,14 @@ if (_set) then { EGVAR(nametags,showNamesForAI) = false; }; } else { - // Close any open dialogs (could be interrupts) - while {dialog} do { - closeDialog 0; - }; + // Kill the display (ensure main display exists, handles edge case where spectator turned off before display exists) + [{ !isNull MAIN_DISPLAY },{ [false] call FUNC(ui) }] call CBA_fnc_waitUntilAndExecute; - // Kill the display - (GETUVAR(GVAR(interface),displayNull)) closeDisplay 0; + // This variable doesn't matter anymore + GVAR(uiForced) = nil; // Terminate camera - GVAR(freeCamera) cameraEffect ["terminate", "back"]; - camDestroy GVAR(freeCamera); - camDestroy GVAR(unitCamera); - camDestroy GVAR(targetCamera); + [false] call FUNC(cam); // Remove from spectator chat GVAR(channel) radioChannelRemove [player]; @@ -127,35 +89,6 @@ if (_set) then { setCurrentChannel GVAR(channelCache); GVAR(channelCache) = nil; - // Clear any residual spectator chat - clearRadio; - enableRadio true; - - // Return to player view - player switchCamera "internal"; - - // Enable BI damage effects - BIS_fnc_feedback_allowPP = true; - - // Cleanup camera variables - GVAR(camBoom) = nil; - GVAR(camDolly) = nil; - GVAR(camGun) = nil; - GVAR(freeCamera) = nil; - GVAR(unitCamera) = nil; - GVAR(targetCamera) = nil; - - //Kill these PFEH handlers now because the PFEH can run before the `onunload` event is handled - GVAR(camHandler) = nil; - GVAR(compHandler) = nil; - GVAR(toolHandler) = nil; - - // Cleanup display variables - GVAR(ctrlKey) = nil; - GVAR(heldKeys) = nil; - GVAR(mouse) = nil; - GVAR(mousePos) = nil; - // Reset nametag settings if (["ace_nametags"] call EFUNC(common,isModLoaded)) then { EGVAR(nametags,showPlayerNames) = GVAR(nametagSettingCache) select 0; @@ -164,10 +97,27 @@ if (_set) then { }; }; +// Hide/Unhide the player if enabled and alive +if (alive player) then { + private _hidden = (_hide && _set); + + // Ignore damage (vanilla and ace_medical) + player allowDamage !_hidden; + player setVariable [QEGVAR(medical,allowDamage), !_hidden]; + + // Move to/from group as appropriate + [player, _hidden, QGVAR(isSet), side group player] call EFUNC(common,switchToGroupSide); + + // Ghosts can't talk + [_hidden, QGVAR(isSet)] call EFUNC(common,hideUnit); + [_hidden, QGVAR(isSet)] call EFUNC(common,muteUnit); +}; + // Reset interruptions GVAR(interrupts) = []; // Mark spectator state for reference GVAR(isSet) = _set; +player setVariable [QGVAR(isSet), true, true]; -["ace_spectatorSet", [_set]] call CBA_fnc_localEvent; +["ace_spectatorSet", [_set, player]] call CBA_fnc_globalEvent; diff --git a/addons/spectator/functions/fnc_stageSpectator.sqf b/addons/spectator/functions/fnc_stageSpectator.sqf index a7cc926d33..d11402f22a 100644 --- a/addons/spectator/functions/fnc_stageSpectator.sqf +++ b/addons/spectator/functions/fnc_stageSpectator.sqf @@ -1,26 +1,27 @@ /* * Author: SilentSpike - * Sets target unit to the given spectator state (physically) - * To virtually handle a spectator see ace_spectator_fnc_setSpectator + * Stores and hides a player safely out of the way (used by setSpectator on living players) * * Units will be gathered at marker ace_spectator_respawn (or [0,0,0] by default) - * Upon unstage, units will be moved to the position they were in upon staging + * Upon unstage, units will be moved to the position they were in before staging * * Arguments: - * 0: Unit to put into spectator stage (default: player) - * 1: Unit should be staged (default: true) + * 0: Unit to handle + * 1: Stage/Unstage * * Return Value: - * None + * None * * Example: - * [player, false] call ace_spectator_fnc_stageSpectator + * [player, true] call ace_spectator_fnc_stageSpectator * - * Public: Yes + * Public: No */ #include "script_component.hpp" +ACE_DEPRECATED(QFUNC(stageSpectator),"3.12.0",[ARR_2(QFUNC(setSpectator),"'s new 3rd parameter")] joinString ""); + params [["_unit",player,[objNull]], ["_set",true,[true]]]; // No change, no service (but allow spectators to be reset) @@ -39,20 +40,35 @@ _unit enableSimulation !_set; if (_set) then { // Position should only be saved on first entry if !(GETVAR(_unit,GVAR(isStaged),false)) then { - GVAR(oldPos) = getPosATL _unit; + SETVAR(_unit,GVAR(preStagePos),getPosATL _unit); + + // Handle players respawning via pause menu (or script) + private _id = _unit addEventHandler ["Respawn",{ + params ["_unit"]; + [_unit] call FUNC(stageSpectator); + }]; + + SETVAR(_unit,GVAR(respawnEH),_id); }; // Ghosts can't talk [_unit, QGVAR(isStaged)] call EFUNC(common,hideUnit); [_unit, QGVAR(isStaged)] call EFUNC(common,muteUnit); + // Position defaults to [0,0,0] if marker doesn't exist _unit setPos (markerPos QGVAR(respawn)); } else { // Physical beings can talk [_unit, QGVAR(isStaged)] call EFUNC(common,unhideUnit); [_unit, QGVAR(isStaged)] call EFUNC(common,unmuteUnit); - _unit setPosATL GVAR(oldPos); + // Restore original position and delete stored value + _unit setPosATL (GETVAR(_unit,GVAR(preStagePos),getPosATL _unit)); + SETVAR(_unit,GVAR(preStagePos),nil); + + // Remove the respawn handling + _unit removeEventHandler ["Respawn",GETVAR(_unit,GVAR(respawnEH),-1)]; + SETVAR(_unit,GVAR(respawnEH),nil); }; // Spectators ignore damage (vanilla and ace_medical) @@ -62,17 +78,12 @@ _unit setVariable [QEGVAR(medical,allowDamage), !_set]; // No theoretical change if an existing spectator was reset if !(_set isEqualTo (GETVAR(_unit,GVAR(isStaged),false))) then { // Mark spectator state for reference - _unit setVariable [QGVAR(isStaged), _set, true]; - - ["ace_spectatorStaged", [_set]] call CBA_fnc_localEvent; + _unit setVariable [QGVAR(isStaged), _set]; }; -//BandAid for #2677 - if player in unitList weird before being staged, weird things can happen -if ((player in GVAR(unitList)) || {ACE_player in GVAR(unitList)}) then { - [] call FUNC(updateUnits); //update list now - if (!(isNull (findDisplay 12249))) then {//If display is open now, close it and restart - WARNING("Player in unitList, call ace_spectator_fnc_stageSpectator before ace_spectator_fnc_setSpectator"); - ["fixWeirdList", true] call FUNC(interrupt); - [{["fixWeirdList", false] call FUNC(interrupt);}, []] call CBA_fnc_execNextFrame; +// If display exists already update the entity list to hide player +if !(isNull SPEC_DISPLAY) then { + if (GVAR(uiListType) == LIST_ENTITIES) then { + [] call FUNC(ui_updateListEntities); }; }; diff --git a/addons/spectator/functions/fnc_switchFocus.sqf b/addons/spectator/functions/fnc_switchFocus.sqf new file mode 100644 index 0000000000..9aea8b061a --- /dev/null +++ b/addons/spectator/functions/fnc_switchFocus.sqf @@ -0,0 +1,31 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Function used to switch to next or previous camera focus + * + * Arguments: + * 0: Next/Prev unit + * + * Return Value: + * None + * + * Example: + * [false] call ace_spectator_fnc_switchFocus + * + * Public: No + */ + +#include "script_component.hpp" + +private _next = param [0, true]; +private _entities = [true] call FUNC(getTargetEntities); +private _focus = GVAR(camTarget); + +// No entities to switch to +if ((_entities isEqualTo []) || (_entities isEqualTo [_focus])) exitWith {}; + +private _index = (_entities find _focus) max 0; + +_index = (_index + ([-1, 1] select _next)) % (count _entities); +if (_index < 0) then { _index = count _entities + _index; }; + +[_entities select _index] call FUNC(setFocus); diff --git a/addons/spectator/functions/fnc_toggleInterface.sqf b/addons/spectator/functions/fnc_toggleInterface.sqf deleted file mode 100644 index 4bf10fdbfe..0000000000 --- a/addons/spectator/functions/fnc_toggleInterface.sqf +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Author: SilentSpike - * Correctly handles toggling of spectator interface elements for clean UX - * - * Arguments: - * 0: Display - * 1: Toogle compass - * 2: Toogle help - * 3: Toogle interface - * 4: Toogle map - * 5: Toogle toolbar - * 6: Toogle unit list - * - * Return Value: - * None - * - * Example: - * [_display, nil, true] call ace_spectator_fnc_toggleInterface - * - * Public: No - */ - -#include "script_component.hpp" - -params ["_display", ["_toggleComp",false], ["_toggleHelp",false], ["_toggleInterface",false], ["_toggleMap",false], ["_toggleTool",false], ["_toggleUnit",false]]; - -private ["_comp","_help","_map","_tool","_unit"]; -_comp = _display displayCtrl IDC_COMP; -_help = _display displayCtrl IDC_HELP; -_map = _display displayCtrl IDC_MAP; -_tool = _display displayCtrl IDC_TOOL; -_unit = _display displayCtrl IDC_UNIT; - -// Map operates outside of interface -GVAR(showMap) = [GVAR(showMap), !GVAR(showMap)] select _toggleMap; -_map ctrlShow GVAR(showMap); - -if (GVAR(showMap)) then { - // When map is shown, temporarily hide interface to stop overlapping - { - _x ctrlShow false; - } forEach [_comp,_help,_tool,_unit]; - - // Centre map on camera/unit upon opening - if (_toggleMap) then { - _map ctrlMapAnimAdd [0, 0.5, [GVAR(camUnit),GVAR(freeCamera)] select (GVAR(camMode) == 0)]; - ctrlMapAnimCommit _map; - }; -} else { - // Can only toggle interface with map minimised - GVAR(showInterface) = [GVAR(showInterface), !GVAR(showInterface)] select _toggleInterface; - - if (GVAR(showInterface)) then { - // Can only toggle interface elements with interface shown - GVAR(showComp) = [GVAR(showComp), !GVAR(showComp)] select _toggleComp; - GVAR(showHelp) = [GVAR(showHelp), !GVAR(showHelp)] select _toggleHelp; - GVAR(showTool) = [GVAR(showTool), !GVAR(showTool)] select _toggleTool; - GVAR(showUnit) = [GVAR(showUnit), !GVAR(showUnit)] select _toggleUnit; - - _comp ctrlShow GVAR(showComp); - _help ctrlShow GVAR(showHelp); - _tool ctrlShow GVAR(showTool); - _unit ctrlShow GVAR(showUnit); - } else { - { - _x ctrlShow false; - } forEach [_comp,_help,_tool,_unit]; - }; -}; - -// Only run PFHs when respective control is shown, otherwise kill -if (ctrlShown _comp) then { - if (isNil QGVAR(compHandler)) then { GVAR(compHandler) = [FUNC(handleCompass), 0, _display] call CBA_fnc_addPerFrameHandler; }; -} else { - GVAR(compHandler) = nil; -}; - -if (ctrlShown _tool) then { - if (isNil QGVAR(toolHandler)) then { GVAR(toolHandler) = [FUNC(handleToolbar), 0, _display] call CBA_fnc_addPerFrameHandler; }; -} else { - GVAR(toolHandler) = nil; -}; diff --git a/addons/spectator/functions/fnc_transitionCamera.sqf b/addons/spectator/functions/fnc_transitionCamera.sqf deleted file mode 100644 index 254dfef131..0000000000 --- a/addons/spectator/functions/fnc_transitionCamera.sqf +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Author: SilentSpike - * Transitions the spectator camera vision/view/unit - * - * Arguments: - * 0: Camera mode - * - 0: Free - * - 1: Internal - * - 2: External - * 1: Camera unit - * 2: Vision mode - * - -2: Normal - * - -1: NV - * - 0: White hot - * - 1: Black hot - * - * Return Value: - * None - * - * Example: - * [0,objNull] call ace_spectator_fnc_transitionCamera - * - * Public: No - */ - -#include "script_component.hpp" - -params [["_newMode",GVAR(camMode)], ["_newUnit",GVAR(camUnit)], ["_newVision",GVAR(camVision)]]; - -// If new mode isn't available then keep current (unless current also isn't) -if !(_newMode in GVAR(availableModes)) then { - _newMode = GVAR(availableModes) select ((GVAR(availableModes) find GVAR(camMode)) max 0); -}; - -// When no units available to spectate, exit to freecam -if ((GVAR(unitList) isEqualTo []) && (alive _newUnit || isNull _newUnit)) then { - _newMode = 0; - _newUnit = objNull; -}; - -// Reset gun cam if not internal -if (_newMode != 1) then { - GVAR(camGun) = false; -}; - -private ["_camera"]; -if (_newMode == 0) then { // Free - _camera = GVAR(freeCamera); - - // Preserve camUnit value for consistency when manually changing view - _camera cameraEffect ["internal", "back"]; - - // Apply the camera zoom - _camera camSetFov -(linearConversion [0.01,2,GVAR(camZoom),-2,-0.01,true]); - _camera camCommit 0; - - // Agent is switched to in free cam to hide death table and prevent AI chat while allowing icons to draw (also prevents systemChat and unit HUD) - // (Why is so much stuff tied into the current camera unit BI?!) - if (isNull GVAR(camAgent)) then { - GVAR(camAgent) = createAgent ["VirtualMan_F",markerPos QGVAR(respawn),[],0,""]; - }; - - GVAR(camAgent) switchCamera "internal"; -} else { - _camera = GVAR(unitCamera); - - // When null unit is given choose random - if (isNull _newUnit) then { - _newUnit = selectRandom GVAR(unitList); - }; - - // Switch camera view to internal unit view (external uses the camera) - if (GVAR(camGun)) then { - _newUnit switchCamera "gunner"; - } else { - _newUnit switchCamera "internal"; - }; - - // Handle camera differently for internal/external view - if (_newMode == 1) then { - // Terminate camera view - _camera cameraEffect ["terminate", "back"]; - GVAR(camHandler) = nil; - } else { - // Switch to the camera - _camera cameraEffect ["internal", "back"]; - }; - - GVAR(camUnit) = _newUnit; -}; - -if (_newMode in [0,2]) then { - // Set up camera UI - showCinemaBorder false; - cameraEffectEnableHUD true; - - // Handle camera movement - if (isNil QGVAR(camHandler)) then { GVAR(camHandler) = [FUNC(handleCamera), 0] call CBA_fnc_addPerFrameHandler; }; - - // If new vision isn't available then keep current (unless current also isn't) - if !(_newVision in GVAR(availableVisions)) then { - _newVision = GVAR(availableVisions) select ((GVAR(availableVisions) find GVAR(camVision)) max 0); - }; - - // Vision mode applies to free and external cam - if (_newVision < 0) then { - false setCamUseTi 0; - camUseNVG (_newVision >= -1); - } else { - true setCamUseTi _newVision; - }; - GVAR(camVision) = _newVision; -}; - -GVAR(camMode) = _newMode; diff --git a/addons/spectator/functions/fnc_ui.sqf b/addons/spectator/functions/fnc_ui.sqf new file mode 100644 index 0000000000..a8fba7de5d --- /dev/null +++ b/addons/spectator/functions/fnc_ui.sqf @@ -0,0 +1,146 @@ +/* + * Author: SilentSpike + * Handles UI initialisation and destruction + * + * Arguments: + * 0: Init/Terminate + * + * Return Value: + * None + * + * Example: + * [false] call ace_spectator_fnc_ui + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_init"]; + +// No change +if (_init isEqualTo !isNull SPEC_DISPLAY) exitWith {}; + +// Close map +openMap [false,false]; + +// Close any open dialogs +while {dialog} do { + closeDialog 0; +}; + +// Note that init and destroy intentionally happen in reverse order +// Init: Vars > Display > UI Stuff +// Destroy: UI Stuff > Display > Vars +if (_init) then { + // UI visibility tracking + GVAR(uiVisible) = true; + GVAR(uiHelpVisible) = true; + GVAR(uiMapVisible) = true; + GVAR(uiWidgetVisible) = true; + + // Drawing related + GVAR(drawProjectiles) = false; + GVAR(drawUnits) = true; + GVAR(entitiesToDraw) = []; + GVAR(grenadesToDraw) = []; + GVAR(iconsToDraw) = []; + GVAR(locationsToDraw) = []; + GVAR(projectilesToDraw) = []; + + // RMB tracking is used for follow camera mode + GVAR(holdingRMB) = false; + + // List type is used for list updates and interaction + GVAR(uiListType) = LIST_ENTITIES; + + // Highlighted map object is used for click and drawing events + GVAR(uiMapHighlighted) = objNull; + + // Holds the current list data + GVAR(curList) = []; + + // Cache view distance and set spectator default + GVAR(oldViewDistance) = viewDistance; + setViewDistance DEFAULT_VIEW_DISTANCE; + + // If counter already exists handle it, otherwise display XEH will handle it + [GETUVAR(RscRespawnCounter,displayNull)] call FUNC(compat_counter); + + // Create the display + MAIN_DISPLAY createDisplay QGVAR(display); + + // Store default H value for scaling purposes + GVAR(uiHelpH) = (ctrlPosition CTRL_HELP) select 3; + + // Initially hide map + [] call FUNC(ui_toggleMap); + + // Initially fade the list + [true] call FUNC(ui_fadeList); + + // Initalise the help, widget and list information + [] call FUNC(ui_updateCamButtons); + [] call FUNC(ui_updateListEntities); + [] call FUNC(ui_updateListFocus); + [] call FUNC(ui_updateWidget); + [] call FUNC(ui_updateHelp); + + // Start updating things to draw + GVAR(collectPFH) = [LINKFUNC(ui_updateIconsToDraw), 0.2] call CBA_fnc_addPerFrameHandler; + + // Draw icons and update the cursor object + GVAR(uiDraw3D) = addMissionEventHandler ["Draw3D", {call FUNC(ui_draw3D)}]; + + // Periodically update list and focus widget + GVAR(uiPFH) = [{ + if (GVAR(uiListType) == LIST_ENTITIES) then { + [] call FUNC(ui_updateListEntities); + } else { + [] call FUNC(ui_updateListLocations); + }; + + [] call FUNC(ui_updateWidget); + }, 5] call CBA_fnc_addPerFrameHandler; +} else { + // Stop updating the list and focus widget + [GVAR(uiPFH)] call CBA_fnc_removePerFrameHandler; + + // Stop drawing icons and tracking cursor object + removeMissionEventHandler ["Draw3D", GVAR(uiDraw3D)]; + GVAR(uiDraw3D) = nil; + + // Stop updating things to draw + [GVAR(collectPFH)] call CBA_fnc_removePerFrameHandler; + GVAR(collectPFH) = nil; + + // Destroy the display + SPEC_DISPLAY closeDisplay 1; + + // Stop tracking everything + GVAR(uiVisible) = nil; + GVAR(uiHelpVisible) = nil; + GVAR(uiMapVisible) = nil; + GVAR(uiWidgetVisible) = nil; + GVAR(holdingRMB) = nil; + GVAR(uiListType) = nil; + GVAR(uiMapHighlighted) = nil; + GVAR(curList) = nil; + GVAR(uiHelpH) = nil; + + // Stop drawing + GVAR(drawProjectiles) = nil; + GVAR(drawUnits) = nil; + GVAR(entitiesToDraw) = nil; + GVAR(grenadesToDraw) = nil; + GVAR(iconsToDraw) = nil; + GVAR(locationsToDraw) = nil; + GVAR(projectilesToDraw) = nil; + + // Reset view distance + setViewDistance GVAR(oldViewDistance); + GVAR(oldViewDistance) = nil; + + // Ensure chat is shown again + showChat true; +}; diff --git a/addons/spectator/functions/fnc_ui_draw3D.sqf b/addons/spectator/functions/fnc_ui_draw3D.sqf new file mode 100644 index 0000000000..05e6079661 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_draw3D.sqf @@ -0,0 +1,156 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to draw the 3D icons and track the cursor object + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * addMissionEventHandler ["Draw3D", {call ace_spectator_fnc_ui_draw3D}] + * + * Public: No + */ + +#include "script_component.hpp" +#define HEIGHT_OFFSET 1.5 + +BEGIN_COUNTER(updateCursor); +private _camTarget = GVAR(camTarget); +private _cursorObject = objNull; + +// This function doesn't work for units underwater, due to use of screenToWorld. Would be complicated to work around this. +private _intersections = [getMousePosition select 0, getMousePosition select 1, _camTarget, vehicle _camTarget] call BIS_fnc_getIntersectionsUnderCursor; + +if !(_intersections isEqualTo []) then { + _cursorObject = (_intersections select 0) select 3; +}; + +if !(_cursorObject isKindOf "Man") then { + _cursorObject = effectiveCommander _cursorObject; +}; + +GVAR(cursorObject) = _cursorObject; +END_COUNTER(updateCursor); + +if !(GVAR(uiMapVisible)) then { + if (GVAR(drawUnits)) then { + BEGIN_COUNTER(drawTags); + // Groups and Units + { + _x params ["_unit", "_type", "_icon"]; + private _position = (_unit modelToWorldVisual (_unit selectionPosition "Head")) vectorAdd [0,0,HEIGHT_OFFSET]; + + if (_type == 2 && { _unit distanceSqr GVAR(camera) < DISTANCE_NAMES_SQR } && {_unit in _camTarget || _unit in _cursorObject}) then { + drawIcon3D [ + ICON_BACKGROUND_UNIT, + [0, 0, 0, [0.4, 0.8] select (_unit in _camTarget)], + _position, + 5, + 4, + 0, + "", + 0, + 0.035, + "PuristaMedium", + "center" + ]; + }; + + // Apply modifiers + if (_type == 1 && { time <= GETVAR(_unit,GVAR(highlightTime),0) }) then { + _icon set [1, [1,1,1, ((_icon select 1) select 3)]]; + }; + _icon set [2, _position]; + + // Draw icon + drawIcon3D _icon; + + nil // Speed loop + } count GVAR(iconsToDraw); + + // Draw locations + { + _x params ["_pos", "_name", "_texture"]; + + if (_pos isEqualType objNull) then { + _pos = (_pos modelToWorldVisual (_pos selectionPosition "Head")) vectorAdd [0,0,2*HEIGHT_OFFSET]; + }; + + drawIcon3D [_texture, [1,1,1,0.4], _pos, 0.8, 0.8, 0, _name]; + + nil // Speed loop + } count (GVAR(locationsToDraw)); + END_COUNTER(drawTags); + }; + + // Draw projectiles and grenades paths + if (GVAR(drawProjectiles)) then { + BEGIN_COUNTER(drawTracers); + private _projectilesNew = []; + private _grenadesNew = []; + + // Draw projectiles if there are any + { + _x params [ + ["_projectile", objNull, [objNull]], + ["_segments", [], [[]]] + ]; + + if !(isNull _projectile) then { + // Store new segment + private _newestIndex = _segments pushBack [ + getPosVisual _projectile, + (vectorMagnitude velocity _projectile) call { + if (_this < 250) exitWith { [0,0,1,1] }; + if (_this < 500) exitWith { [0,1,0,1] }; + [1,0,0,1] + } + ]; + + // Clamp number of segments to be drawn + if (_newestIndex > MAX_PROJECTILE_SEGMENTS) then { + _segments deleteAt 0; + DEC(_newestIndex); + }; + + // Store projectiles for next frame + _projectilesNew pushBack [_projectile, _segments]; + + // Draw all projectile segments + private _oldLoc = []; + { + _x params ["_locNew", "_colorNew"]; + if !(_oldLoc isEqualTo []) then { + drawLine3D [_oldLoc, _locNew, _colorNew]; + }; + _oldLoc = _locNew; + + nil // Speed loop + } count _segments; + }; + + nil // Speed loop + } count GVAR(projectilesToDraw); + GVAR(projectilesToDraw) = _projectilesNew; + + { + if !(isNull _x) then { + private _grenadeVelocityMagnitude = vectorMagnitude velocity _x; + + // Draw grenade (rotate icon to represent spinning) + drawIcon3D [ICON_GRENADE, [1,0,0,1], getPosVisual _x, 0.6, 0.6, if (_grenadeVelocityMagnitude > 0) then { time * 100 * _grenadeVelocityMagnitude } else { 0 }, "", 0, 0.05, "TahomaB"]; + + // Store grenade for next frame + _grenadesNew pushBack _x; + }; + + nil // Speed loop + } count GVAR(grenadesToDraw); + + GVAR(grenadesToDraw) = _grenadesNew; + END_COUNTER(drawTracers); + }; +}; diff --git a/addons/spectator/functions/fnc_ui_fadeList.sqf b/addons/spectator/functions/fnc_ui_fadeList.sqf new file mode 100644 index 0000000000..b0d3dfeb4a --- /dev/null +++ b/addons/spectator/functions/fnc_ui_fadeList.sqf @@ -0,0 +1,47 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to fade/unfade the entitiy/location list + * + * Arguments: + * 0: Fade the list + * + * Return Value: + * None + * + * Example: + * [false] call ace_spectator_fnc_ui_fadeList + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_fadeList"]; + +if (GVAR(uiVisible)) then { + private _list = CTRL_LIST; + + if (_fadeList) then { + _list ctrlSetBackgroundColor [0,0,0,0]; + _list ctrlSetFade 0.8; + ctrlSetFocus CTRL_MOUSE; + + // if (GVAR(camMode) == MODE_FREE) then { + // GVAR(camera) camCommand "manual on"; + // }; + + showChat true; + } else { + _list ctrlSetBackgroundColor [0,0,0,0.75]; + _list ctrlSetFade 0; + ctrlSetFocus _list; + + // Disable camera input while using the list + // GVAR(camera) camCommand "manual off"; + + // List overlaps with chat + showChat false; + }; + + _list ctrlCommit 0.2; +}; diff --git a/addons/spectator/functions/fnc_ui_getTreeDataIndex.sqf b/addons/spectator/functions/fnc_ui_getTreeDataIndex.sqf new file mode 100644 index 0000000000..4963a6799f --- /dev/null +++ b/addons/spectator/functions/fnc_ui_getTreeDataIndex.sqf @@ -0,0 +1,45 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to find a tree path of a unit + * + * Arguments: + * 0: Data to search tree for + * + * Return Value: + * Tree path to data + * + * Example: + * [groupID _group] call ace_spectator_fnc_ui_getTreeDataIndex + * + * Public: No + */ + +#include "script_component.hpp" + +params [["_data", "", [""]]]; + +scopeName QGVAR(getTreeDataIndex); + +// Make sure data is not empty +if (_data != "") then { + private _ctrl = CTRL_LIST; + + // This also handles the locations list (_sideIndex = _locationIndex) + for "_sideIndex" from 0 to ((_ctrl tvCount []) - 1) do { + if (_ctrl tvData [_sideIndex] == _data) then { + [_sideIndex] breakOut QGVAR(getTreeDataIndex); + }; + for "_groupIndex" from 0 to ((_ctrl tvCount [_sideIndex]) - 1) do { + if (_ctrl tvData [_sideIndex, _groupIndex] == _data) then { + [_sideIndex, _groupIndex] breakOut QGVAR(getTreeDataIndex); + }; + for "_unitIndex" from 0 to ((_ctrl tvCount [_sideIndex, _groupIndex]) - 1) do { + if (_ctrl tvData [_sideIndex, _groupIndex, _unitIndex] == _data) then { + [_sideIndex, _groupIndex, _unitIndex] breakOut QGVAR(getTreeDataIndex); + }; + }; + }; + }; +}; + +[-1] // return empty path if not found (worst case) diff --git a/addons/spectator/functions/fnc_ui_handleChildDestroyed.sqf b/addons/spectator/functions/fnc_ui_handleChildDestroyed.sqf new file mode 100644 index 0000000000..f2dcab47ef --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleChildDestroyed.sqf @@ -0,0 +1,28 @@ +/* + * Author: Nelson Duarte + * Function used to handle child destroyed event + * This only matters when abort button is pressed in child escape menu + * Will close main display to exit client from mission + * + * Arguments: + * 0: Spectator display + * 1: Child display + * 2: Exit code of child + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleChildDestroyed + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_display","_child","_exitCode"]; + +if (_exitCode == 104) then { + _display closeDisplay 2; + MAIN_DISPLAY closeDisplay 2; +}; diff --git a/addons/spectator/functions/fnc_ui_handleKeyDown.sqf b/addons/spectator/functions/fnc_ui_handleKeyDown.sqf new file mode 100644 index 0000000000..753bba7715 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleKeyDown.sqf @@ -0,0 +1,209 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to handle key down event + * + * Arguments: + * 0: Spectator display + * 1: Key DIK code + * 2: State of shift + * 3: State of ctrl + * 4: State of alt + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleKeyDown + * + * Public: No + */ + +#include "script_component.hpp" +#include "\A3\ui_f\hpp\defineDIKCodes.inc" + +params ["","_key","_shift","_ctrl","_alt"]; + +// Handle map toggle +if (_key == DIK_M) exitWith { + [] call FUNC(ui_toggleMap); + true +}; + +// Handle very fast speed +if (_key == DIK_LALT) exitWith { + [true] call FUNC(cam_toggleSlow); + true +}; + +// Handle escape menu +if (_key == DIK_ESCAPE) exitWith { + if (GVAR(uiMapVisible)) then { + [] call FUNC(ui_toggleMap); + } else { + if (GVAR(uiForced)) then { + private _displayType = ["RscDisplayInterrupt","RscDisplayMPInterrupt"] select isMultiplayer; + SPEC_DISPLAY createDisplay _displayType; + } else { + [false] call FUNC(setSpectator); + }; + }; + true +}; + +// Handle perspective cycling +if (_key in [DIK_SPACE, DIK_NUMPADENTER]) exitWith { + private _oldMode = GVAR(camMode); + private _modes = GVAR(availableModes); + + // Get current index and index count + private _iMode = (_modes find _oldMode) max 0; + private _countModes = count _modes; + + if (_countModes != 0) then { + _iMode = (_iMode + 1) % _countModes; + if (_iMode < 0) then { _iMode = _countModes + _iMode; }; + }; + + private _newMode = _modes select _iMode; + + [_newMode] call FUNC(cam_setCameraMode); + + true +}; + +// Handle vision mode cycling +if (_key == DIK_N) exitWith { + private _oldVision = GVAR(camVision); + private _visions = GVAR(availableVisions); + + // Get current index and index count + private _iVision = (_visions find _oldVision) max 0; + private _countVisions = count _visions; + + if (_countVisions != 0) then { + _iVision = (_iVision + 1) % _countVisions; + if (_iVision < 0) then { _iVision = _countVisions + _iVision; }; + }; + + private _newVision = _visions select _iVision; + + [_newVision] call FUNC(cam_setVisionMode); + true +}; + +// Handle postive change in draw +if (_key == DIK_PGUP) exitWith { + setViewDistance ((viewDistance + 250) min MAX_VIEW_DISTANCE); + true +}; + +// Handle negative change in draw +if (_key == DIK_PGDN) exitWith { + setViewDistance ((viewDistance - 250) max MIN_VIEW_DISTANCE); + true +}; + +// Handle spectate lights +if (_key == DIK_L) exitWith { + if (GVAR(camLight)) then { + { deleteVehicle _x; } forEach GVAR(camLights); + GVAR(camLights) = []; + } else { + private _cameraLight = "#lightpoint" createvehicleLocal getPosASL GVAR(camera); + _cameraLight setLightBrightness 2; + _cameraLight setLightAmbient [1,1,1]; + _cameraLight setLightColor [0,0,0]; + _cameraLight lightAttachObject [GVAR(camera), [0,0,0]]; + + private _pointerLight = "#lightpoint" createvehicleLocal getPosASL GVAR(camera); + _pointerLight setLightBrightness 1; + _pointerLight setLightAmbient [1,1,1]; + _pointerLight setLightColor [0,0,0]; + + GVAR(camLights) = [_cameraLight, _pointerLight]; + }; + + GVAR(camLight) = !GVAR(camLight); + + true +}; + +// Handle toggling the UI +if (_key == DIK_BACKSPACE) exitWith { + [] call FUNC(ui_toggleUI); + true +}; + +// Handle toggling help +if (_key == DIK_F1) exitWith { + GVAR(uiHelpVisible) = !GVAR(uiHelpVisible); + + [] call FUNC(ui_updateHelp); + + CTRL_HELP ctrlShow GVAR(uiHelpVisible); + CTRL_HELP_BACK ctrlShow GVAR(uiHelpVisible); + + true +}; + +// Handle toggle focus info widget +if (_key == DIK_P) exitWith { + GVAR(uiWidgetVisible) = !GVAR(uiWidgetVisible); + [] call FUNC(ui_updateWidget); + true +}; + +// Handle toggling projectile drawing +if (_key == DIK_O) exitWith { + GVAR(drawProjectiles) = !GVAR(drawProjectiles); + true +}; + +// Handle toggling unit drawing +if (_key == DIK_I) exitWith { + GVAR(drawUnits) = !GVAR(drawUnits); + true +}; + +// Handle getting next focus target +if (_key == DIK_RIGHT) exitWith { + [true] call FUNC(switchFocus); + true +}; + +// Handle getting previous focus target +if (_key == DIK_LEFT) exitWith { + [false] call FUNC(switchFocus); + true +}; + +// If the zeus key is pressed and unit is curator, open zeus interface +if ((_key in (actionKeys "CuratorInterface")) && {!isNull (getAssignedCuratorLogic player)}) exitWith { + // Close the UI and disable camera input + [false] call FUNC(ui); + GVAR(camera) camCommand "manual off"; + + // Display XEH handles re-opening + openCuratorInterface; + + // Set the curator camera to the spectator camera location + [{!isNull curatorCamera},{ + curatorCamera setPosASL (getPosASL GVAR(camera)); + curatorCamera setDir (getDirVisual GVAR(camera)); + + // Curator tracks its own vision mode + [getAssignedCuratorLogic player, 0] call bis_fnc_toggleCuratorVisionMode; + }] call CBA_fnc_waitUntilAndExecute; + true +}; + +// Handle acre spectate headset down (if present) +if ( + ["acre_sys_radio"] call EFUNC(common,isModLoaded) && + { [_key, [_shift, _ctrl, _alt]] isEqualTo ((["ACRE2", "HeadSet"] call CBA_fnc_getKeybind) select 5) } +) exitWith { + [] call acre_sys_core_fnc_toggleHeadset; + true +}; + +false // default to unhandled diff --git a/addons/spectator/functions/fnc_ui_handleKeyUp.sqf b/addons/spectator/functions/fnc_ui_handleKeyUp.sqf new file mode 100644 index 0000000000..672f2f7bb8 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleKeyUp.sqf @@ -0,0 +1,31 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Function used to handle key up event + * + * Arguments: + * 0: Spectator display + * 1: Key DIK code + * 2: State of shift + * 3: State of ctrl + * 4: State of alt + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleKeyUp + * + * Public: No + */ + +#include "script_component.hpp" +#include "\A3\ui_f\hpp\defineDIKCodes.inc" + +params ["","_key","_shift","_ctrl","_alt"]; + +if (_key == DIK_LALT) exitWith { + [false] call FUNC(cam_toggleSlow); + true +}; + +false diff --git a/addons/spectator/functions/fnc_ui_handleListClick.sqf b/addons/spectator/functions/fnc_ui_handleListClick.sqf new file mode 100644 index 0000000000..39d0e9fe9d --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleListClick.sqf @@ -0,0 +1,110 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to handle list single/double clicks + * + * Expected behaviour: + * Clicking an entry focuses the camera on it (any camera mode) + * Double clicking an entry teleports the free camera nearby and focuses on it + * + * Arguments: + * 0: Double clicked + * 1: List Click EH's _this + * + * Return Value: + * None + * + * Example: + * [false, _this] call ace_spectator_fnc_ui_handleListClick + * + * Public: No + */ +#include "script_component.hpp" + +params ["_dblClick","_params"]; +_params params ["_list","_index"]; + +private _handled = false; +private _entityList = GVAR(uiListType) == LIST_ENTITIES; +private _data = _list tvData _index; + +if (_entityList) then { + // List contains unique object variables + private _object = missionNamespace getVariable [_data, objNull]; + + if !(isNull _object) then { + if (_dblClick) then { + // Place camera within ~10m of the object and above ground level + private _pos = getPosASLVisual _object; + GVAR(camera) setPosASL (AGLtoASL (_pos getPos [1 + random 10, random 360]) vectorAdd [0,0,2 + random 10]); + + // Reset the focus + [objNull] call FUNC(setFocus); + [_object] call FUNC(setFocus); + + _handled = true; + } else { + if (_object != GVAR(camTarget)) then { + [_object] call FUNC(setFocus); + + _handled = true; + }; + }; + }; +} else { + private _location = []; + + // Try to find the location + { + if ((_x select 0) == _data) exitWith { + // Don't want to accidentally modify the GVAR + _location = +_x; + }; + } forEach GVAR(locationsList); + + if !(_location isEqualTo []) then { + _location params ["", "_name", "_description", "", "_pos", "_offset"]; + + // Use dummy object if location is a position array + private _dummy = GVAR(camDummy); + if (_pos isEqualType objNull) then { + _dummy = _pos; + } else { + // Use dummy to have camera target the location position + detach _dummy; + _dummy setPosASL _pos; + }; + + // If in a unit camera mode then only focus when double click + if (GVAR(camMode) == MODE_FREE || {_dblClick && {FREE_MODE in GVAR(availableModes)}}) then { + // Reset the focus + [objNull] call FUNC(setFocus); + [_dummy, true] call FUNC(setFocus); + }; + + // If double clicked ande mode is now free camera, teleport the camera + if (_dblClick && {GVAR(camMode) == MODE_FREE}) then { + // If location has unspecified offset place randomly within ~30m above ground level + if (_offset isEqualTo [0,0,0]) then { + _pos = AGLtoASL (_pos getPos [5 + random 30, random 360]) vectorAdd [0,0,2 + random 28]; + } else { + if (_pos isEqualType objNull) then { + _pos = (getPosASL _pos) vectorAdd _offset; + } else { + _pos = (AGLtoASL _pos) vectorAdd _offset; + }; + }; + GVAR(camera) setPosASL _pos; + + // Location info text + [parseText format [ """%1""
%2
", _name, _description], true, nil, 7, 0.7, 0] spawn BIS_fnc_textTiles; + }; + + _handled = true; + }; +}; + +if (_handled) then { + playSound "ReadoutClick"; +}; + +_handled diff --git a/addons/spectator/functions/fnc_ui_handleMapClick.sqf b/addons/spectator/functions/fnc_ui_handleMapClick.sqf new file mode 100644 index 0000000000..8d870ae0d0 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleMapClick.sqf @@ -0,0 +1,40 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to handle map mouse click events + * + * Arguments: + * 0: Map control + * 1: Mouse button pressed + * 2: x screen coordinate clicked + * 3: y screen coordinate clicked + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleMapClick + * + * Public: No + */ + +#include "script_component.hpp" + +params ["", "", "_x", "_y"]; + +if (isNull GVAR(uiMapHighlighted)) then { + // Give user feedback that camera is no longer focused + if !(isNull GVAR(camTarget)) then { + playSound "ReadoutHideClick1"; + }; + + // Preserve camera height on teleport + private _pos = CTRL_MAP ctrlMapScreenToWorld [_x, _y]; + _pos set [2, (getPosASLVisual GVAR(camera)) select 2]; + + GVAR(camera) setPosASL _pos; +} else { + // Give user feedback that camera is focused on highlighted unit + playSound "ReadoutClick"; +}; + +[GVAR(uiMapHighlighted)] call FUNC(setFocus); diff --git a/addons/spectator/functions/fnc_ui_handleMapDraw.sqf b/addons/spectator/functions/fnc_ui_handleMapDraw.sqf new file mode 100644 index 0000000000..0bb45a7a58 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleMapDraw.sqf @@ -0,0 +1,96 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to handle map draw + * + * Arguments: + * 0: Map control + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleMapDraw + * + * Public: No + */ + +#include "script_component.hpp" +#define MAP_MIN_ENTITY_DISTANCE 30 + +// Moved timer into map controls group, update here +CTRL_TIME ctrlSetText (["+", [time / 3600] call BIS_fnc_timeToString] joinString ""); + +BEGIN_COUNTER(drawMap); + +params ["_map"]; + +// Track nearest unit +private _loc = _map ctrlMapScreenToWorld getMousePosition; +private _nearestEntity = objNull; +private _minDist = 999999; + +// Draw unit icons +private _handledVehicles = []; +{ + private _dist = _x distance2D _loc; + + if (_dist < _minDist && { _dist < MAP_MIN_ENTITY_DISTANCE }) then { + _minDist = _dist; + _nearestEntity = _x; + }; + + private _vehicle = vehicle _x; + if !(_vehicle in _handledVehicles) then { + _handledVehicles pushBack _vehicle; + + private _vehicleTexture = [_vehicle] call EFUNC(common,getVehicleIcon); + private _sideColor = [side group _vehicle] call BIS_fnc_sideColor; + private _text = ""; + + if (GVAR(uiMapHighlighted) == _vehicle || {GVAR(uiMapHighlighted) in _vehicle}) then { + _text = ([GVAR(uiMapHighlighted)] call EFUNC(common,getName)) select [0, NAME_MAX_CHARACTERS]; + if !(isPlayer GVAR(uiMapHighlighted)) then { _text = format ["%1: %2", localize "str_player_ai", _text]; }; + _sideColor = [0.8, 0.8, 0.5, 1]; + }; + + if (NEEDS_REVIVE(_vehicle)) then { + _vehicleTexture = ICON_REVIVE; + _sideColor = [0.5, 0, 0, 1]; + }; + + if (time <= _vehicle getVariable [QGVAR(highlightTime), 0]) then { + _sideColor = [1, 1, 1, 1]; + }; + + _map drawIcon [_vehicleTexture, _sideColor, getPosASLVisual _vehicle, 24, 24, getDirVisual _vehicle, _text, 1, 0.04, "TahomaB", "right"]; + }; + nil // Speed loop +} count ([] call FUNC(getTargetEntities)); + +// Set highlighted unit +private _text = if (isNull _nearestEntity) then { + "" +} else { + format ["%1 [%2 m]", [_nearestEntity] call EFUNC(common,getName), round (_nearestEntity distance2D GVAR(camera))] +}; + +GVAR(uiMapHighlighted) = _nearestEntity; +CTRL_MAP_FOOTER ctrlSetText _text; + +// Draw camera icon +if !(isNil QGVAR(camera)) then { + private _cameraPos = getPosASLVisual GVAR(camera); + private _cameraDir = getDirVisual GVAR(camera); + _map drawIcon [ICON_CAMERA, [0.5, 1, 0.5, 1], _cameraPos, 32, 48, _cameraDir, "", 1, 0.05, "TahomaB", "right"]; + _map drawArrow [_cameraPos, (_cameraPos getPos [300, _cameraDir]), [0.5, 1, 0.5, 1]]; +}; + +// Draw locations +{ + _x params ["", "_name", "", "_texture", "_pos"]; + _map drawIcon [_texture, [1,1,1,0.5], _pos, 36, 36, 0, _name, true, 0.04, "TahomaB", "right"]; + + nil // Speed loop +} count (GVAR(locationsList)); + +END_COUNTER(drawMap); diff --git a/addons/spectator/functions/fnc_ui_handleMouseButtonDblClick.sqf b/addons/spectator/functions/fnc_ui_handleMouseButtonDblClick.sqf new file mode 100644 index 0000000000..f880daf81b --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleMouseButtonDblClick.sqf @@ -0,0 +1,27 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to handle mouse button double clicks + * + * Expected behaviour: + * Double left click teleports free camera toward the unit, but does not focus + * + * Arguments: + * 0: Control + * 1: Mouse button pressed + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleMouseButtonDblClick + * + * Public: No + */ + +#include "script_component.hpp" + +params ["", "_button"]; + +if (_button == 0 && {!isNull GVAR(cursorObject)}) then { + [GVAR(cursorObject)] call FUNC(cam_prepareTarget); +}; diff --git a/addons/spectator/functions/fnc_ui_handleMouseButtonDown.sqf b/addons/spectator/functions/fnc_ui_handleMouseButtonDown.sqf new file mode 100644 index 0000000000..52c094f174 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleMouseButtonDown.sqf @@ -0,0 +1,50 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to handle mouse down event + * + * Expected behaviour: + * Left clicking a unit focuses the camera on that unit (in any camera mode) + * Left clicking empty space removes the current camera focus in free camera + * Right clicking removes the camera lock, but retains the focus in free camera + * Right clicking and dragging orbits around the unit in follow camera + * + * Arguments: + * 0: Control + * 1: Mouse button pressed + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleMouseButtonDown + * + * Public: No + */ + +#include "script_component.hpp" + +params ["", "_button"]; + +// Left click +if (_button == 0) exitWith { + if (isNull GVAR(cursorObject)) then { + if (GVAR(camMode) == MODE_FREE && { !isNull GVAR(camTarget) }) then { + playSound "ReadoutHideClick1"; + [objNull] call FUNC(setFocus); + }; + } else { + playSound "ReadoutClick"; + + // Focus will be at screen center + [GVAR(cursorObject)] call FUNC(setFocus); + setMousePosition [0.5, 0.5]; + }; +}; + +// Right click +if (_button == 1) then { + if (GVAR(camMode) == MODE_FREE && { !isNull GVAR(camTarget) } && { !isNull (attachedTo GVAR(camDummy)) }) then { + [] call FUNC(cam_resetTarget); + }; + GVAR(holdingRMB) = true; +}; diff --git a/addons/spectator/functions/fnc_ui_handleMouseMoving.sqf b/addons/spectator/functions/fnc_ui_handleMouseMoving.sqf new file mode 100644 index 0000000000..af3f1ede62 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleMouseMoving.sqf @@ -0,0 +1,31 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to handle mouse moving event + * + * Arguments: + * 0: Control + * 1: Change in x + * 2: Change in y + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleMouseMoving + * + * Public: No + */ + +#include "script_component.hpp" + +if (GVAR(holdingRMB) && { GVAR(camMode) == MODE_FOLLOW }) then { + params ["", "_deltaX", "_deltaY"]; + + if (_deltaX != 0) then { + GVAR(camYaw) = ((GVAR(camYaw) + (_deltaX * 100 * GVAR(camDeltaTime)) + 180) % 360) - 180; + }; + + if (_deltaY != 0) then { + GVAR(camPitch) = (((GVAR(camPitch) - (_deltaY * 100 * GVAR(camDeltaTime))) max -90) min 90); + }; +}; diff --git a/addons/spectator/functions/fnc_ui_handleMouseZChanged.sqf b/addons/spectator/functions/fnc_ui_handleMouseZChanged.sqf new file mode 100644 index 0000000000..5e54cb8b72 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleMouseZChanged.sqf @@ -0,0 +1,27 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to handle mouse scroll event + * + * Arguments: + * 0: Control + * 1: Change in Z + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleMouseZChanged + * + * Public: No + */ + +#include "script_component.hpp" +#define FOLLOW_CAMERA_MAX_DISTANCE 5 + +if (GVAR(camMode) == MODE_FOLLOW) then { + if ((_this select 1) > 0) then { + GVAR(camDistance) = (GVAR(camDistance) - 1) max 0; + } else { + GVAR(camDistance) = (GVAR(camDistance) + 1) min FOLLOW_CAMERA_MAX_DISTANCE; + }; +}; diff --git a/addons/spectator/functions/fnc_ui_handleTabSelected.sqf b/addons/spectator/functions/fnc_ui_handleTabSelected.sqf new file mode 100644 index 0000000000..a69d884601 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_handleTabSelected.sqf @@ -0,0 +1,37 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Function used to handle list tab change + * + * Arguments: + * 0: Control + * 1: New tab index + * + * Return Value: + * None + * + * Example: + * _this call ace_spectator_fnc_ui_handleTabSelected + * + * Public: No + */ + +#include "script_component.hpp" + +params ["_ctrl", "_index"]; + +// Nothing to do if it's the same tab +private _newType = [LIST_ENTITIES, LIST_LOCATIONS] select _index; +if (GVAR(uiListType) == _newType) exitWith {}; + +// Clear list +tvClear CTRL_LIST; + +// Force initial update +if (_index == 0) then { + [] call FUNC(ui_updateListEntities); +} else { + [] call FUNC(ui_updateListLocations); +}; + +// Track current list type +GVAR(uiListType) = _newType; diff --git a/addons/spectator/functions/fnc_ui_toggleMap.sqf b/addons/spectator/functions/fnc_ui_toggleMap.sqf new file mode 100644 index 0000000000..744eb25065 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_toggleMap.sqf @@ -0,0 +1,46 @@ +/* + * Author: Nelson Duarte, AACO + * Function used to toggle the map + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_toggleMap + * + * Public: No + */ + +#include "script_component.hpp" + +if (GVAR(uiMapVisible)) then { + CTRL_MAP ctrlShow false; + CTRL_MAP_GROUP ctrlShow false; + + ctrlSetFocus CTRL_MOUSE; + + // if (GVAR(camMode) == MODE_FREE) then { + // GVAR(camera) camCommand "manual on"; + // }; +} else { + CTRL_MAP ctrlShow true; + CTRL_MAP_GROUP ctrlShow true; + + CTRL_MAP_TITLE ctrlSetText (getMissionConfigValue ["onLoadName", getMissionConfigValue ["briefingName", localize ELSTRING(common,unknown)]]); + CTRL_MAP_SPEC_NUM ctrlSetText str ({GETVAR(_x,GVAR(isSet),false)} count allPlayers); + + CTRL_MAP ctrlMapAnimAdd [0, 0.05, getPosASLVisual GVAR(camera)]; + ctrlMapAnimCommit CTRL_MAP; + + // Disable camera input while map is open + // GVAR(camera) camCommand "manual off"; +}; + +// Toggle the tracking variable +GVAR(uiMapVisible) = !GVAR(uiMapVisible); + +// Reset highlighted object +GVAR(uiMapHighlighted) = objNull; diff --git a/addons/spectator/functions/fnc_ui_toggleUI.sqf b/addons/spectator/functions/fnc_ui_toggleUI.sqf new file mode 100644 index 0000000000..b15e8b1962 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_toggleUI.sqf @@ -0,0 +1,34 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to toggle the whole user interface + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_toggleUI + * + * Public: No + */ + +#include "script_component.hpp" + +private _visible = !GVAR(uiVisible); + +{ + private _fade = 1; + if (_visible) then { + _fade = getNumber (configFile >> QGVAR(display) >> "Controls" >> ctrlClassName _x >> "fade"); + }; + + _x ctrlSetFade _fade; + _x ctrlCommit 0.25; +} forEach [CTRL_LIST, CTRL_TABS, CTRL_CAM_TYPES, CTRL_WIDGET]; + +showChat !_visible; +playSound (["HintExpand","HintCollapse"] select _visible); + +GVAR(uiVisible) = _visible; diff --git a/addons/spectator/functions/fnc_ui_updateCamButtons.sqf b/addons/spectator/functions/fnc_ui_updateCamButtons.sqf new file mode 100644 index 0000000000..d110aac4ea --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateCamButtons.sqf @@ -0,0 +1,38 @@ +/* + * Author: SilentSpike + * Used to update the docked camera buttons + * Disables unavailable, highlights current + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_updateCamButtons + * + * Public: No + */ + +#include "script_component.hpp" + +// These correspond to the camera mode indices +#define ENUM_IDCs [IDC_FREE, IDC_FPS, IDC_FOLLOW] +#define ENUM_ACTIVE [CAM_ICON_FREE_SELECTED, CAM_ICON_FPS_SELECTED, CAM_ICON_FOLLOW_SELECTED] +#define ENUM_INACTIVE [CAM_ICON_FREE, CAM_ICON_FPS, CAM_ICON_FOLLOW] + +private _current = ENUM_IDCs select GVAR(camMode); + +{ + if (_forEachIndex in GVAR(availableModes)) then { + // Highlight the current camera mode button + private _icon = ([ENUM_INACTIVE, ENUM_ACTIVE] select (_x == _current)) select _forEachIndex; + + (CTRL_CAM_TYPES controlsGroupCtrl _x) ctrlSetText _icon; + (CTRL_CAM_TYPES controlsGroupCtrl _x) ctrlShow true; + } else { + // Disable any inactive camera modes + (CTRL_CAM_TYPES controlsGroupCtrl _x) ctrlShow false; + }; +} forEach ENUM_IDCs; diff --git a/addons/spectator/functions/fnc_ui_updateHelp.sqf b/addons/spectator/functions/fnc_ui_updateHelp.sqf new file mode 100644 index 0000000000..50dc1781bc --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateHelp.sqf @@ -0,0 +1,138 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Updates spectator UI help element + * + * Note that there are some redundant conditions in this file + * This is intentional, since controls appear via priority que + * The overhead is minimal + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_updateHelp + * + * Public: No + */ + +#include "script_component.hpp" +#include "\A3\ui_f\hpp\defineDIKCodes.inc" +#define MAX_CONTROLS_HELP_ENTRIES 12 + +if !(GVAR(uiHelpVisible)) exitWith {}; + +private _cameraMode = GVAR(camMode); +private _availableModes = GVAR(availableModes); +private _hasTarget = !isNull GVAR(camTarget); + +private _controls = []; + +// When not in first person, camera rotation applies +if (_cameraMode != MODE_FPS) then { + _controls pushback ["[RMB]", localize "STR_A3_Spectator_Helper_CameraRotation"]; +}; + +// When in free camera, focus/un-focus with LMB +if (_cameraMode == MODE_FREE) then { + if (_hasTarget) then { + _controls pushBack ["[LMB]", localize "STR_A3_Spectator_Helper_Unfocus"]; + } else { + _controls pushBack ["[LMB]", localize "STR_A3_Spectator_Helper_Focus"]; + }; +}; + +// When the camera has a focus, switch mode applies (if other modes are available) +if (_hasTarget && {!GVAR(camOnLocation)} && {count _availableModes > 1}) then { + _controls pushBack [ + format ["[%1]", toUpper ([DIK_SPACE] call CBA_fnc_localizeKey)], + localize "STR_A3_Spectator_Helper_CameraMode" + ]; +}; + +if (_cameraMode == MODE_FREE) then { + _controls pushback [ + format ["[%1/%2]", [DIK_W] call CBA_fnc_localizeKey, [DIK_S] call CBA_fnc_localizeKey], + localize "STR_A3_Spectator_Helper_Movement" + ]; + _controls pushback [ + format ["[%1/%2]", [DIK_A] call CBA_fnc_localizeKey, [DIK_D] call CBA_fnc_localizeKey], + localize "STR_A3_Spectator_Helper_Strafing" + ]; + _controls pushback [ + format ["[%1/%2]", [DIK_Q] call CBA_fnc_localizeKey, [DIK_Z] call CBA_fnc_localizeKey], + localize "STR_A3_Spectator_Helper_Height" + ]; +} else { + _controls pushback [ + format ["[%1]", toUpper ([DIK_RIGHT] call CBA_fnc_localizeKey)], + localize LSTRING(nextUnit) + ]; + _controls pushback [ + format ["[%1]", toUpper ([DIK_LEFT] call CBA_fnc_localizeKey)], + localize LSTRING(prevUnit) + ]; +}; + +if (_cameraMode != MODE_FPS) then { + _controls pushback [ + format ["[%1]", ([DIK_N] call CBA_fnc_localizeKey)], + localize LSTRING(nextVis) + ]; +}; + +_controls pushBack [ + format ["[%1]", toUpper ([DIK_BACK] call CBA_fnc_localizeKey)], + localize "STR_A3_Spectator_Helper_Interface" +]; +_controls pushBack [ + format ["[%1]", [DIK_F1] call CBA_fnc_localizeKey], + localize "STR_A3_Spectator_Helper_Controls" +]; + +// Too many controls in the UI, leave these out? +// _controls pushBack [ +// format ["[%1]", [DIK_M] call CBA_fnc_localizeKey], +// localize "str_usract_map" +// ]; +// _controls pushBack [ +// format ["[%1]", [DIK_I] call CBA_fnc_localizeKey], +// localize LSTRING(uiIcons) +// ]; +// _controls pushBack [ +// format ["[%1]", [DIK_O] call CBA_fnc_localizeKey], +// localize LSTRING(uiProjectiles) +// ]; + +if (_cameraMode == MODE_FREE) then { + _controls pushBack ["[LSHIFT]", localize "STR_A3_Spectator_Helper_Shift"]; + _controls pushBack ["[LALT]", localize LSTRING(camSlow)]; +}; + +if (count _controls > MAX_CONTROLS_HELP_ENTRIES) then { + _controls resize MAX_CONTROLS_HELP_ENTRIES; +}; + +private _help = CTRL_HELP; + +_help ctrlEnable false; +_help lnbSetColumnsPos [0, 0.45]; +lnbClear _help; + +{ + _help lnbAddRow _x; + _help lnbSetColor [[_forEachIndex, 0], [0.75,0.6,0,1]]; +} forEach _controls; + +// Set height based on number of rows +private _newH = (GVAR(uiHelpH) / MAX_CONTROLS_HELP_ENTRIES) * count _controls; +private _newY = safezoneY + safezoneH - _newH; + +(ctrlPosition _help) params ["_newX","","_newW"]; + +{ + _x ctrlSetPosition [_newX, _newY, _newW, _newH]; + _x ctrlCommit 0.15; +} forEach [CTRL_HELP_BACK, _help]; diff --git a/addons/spectator/functions/fnc_ui_updateIconsToDraw.sqf b/addons/spectator/functions/fnc_ui_updateIconsToDraw.sqf new file mode 100644 index 0000000000..1877995a46 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateIconsToDraw.sqf @@ -0,0 +1,162 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used update the things to 3D draw + * + * Arguments: + * None + * + * Return Value: + * None + * + * Examples: + * [] call ace_spectator_fnc_ui_updateIconsToDraw + * + * Public: No + */ + +#include "script_component.hpp" + +private _iconsToDraw = []; +private _entitiesToDraw = []; + +{ + private _vehicle = vehicle _x; + private _inVehicle = (_vehicle != _x); + private _distanceToCameraSqr = GVAR(camera) distanceSqr _x; + + if (_distanceToCameraSqr <= DISTANCE_ICONS_SQR && { !_inVehicle || { _x == effectiveCommander _vehicle } }) then { + private _group = group _x; + private _groupSide = side _group; + private _groupName = groupId _group; + private _groupLeader = leader _group; + private _groupColor = [_groupSide] call BIS_fnc_sideColor; + + // Calculate distance fade + (_distanceToCameraSqr call { + if (_this <= 250000) exitWith { // 500^2 + [1, 4, -2.5, 0.04] + }; + if (_this <= 1000000) exitWith { // 1000^2 + [0.75, 3.5, -2.2, 0.035] + }; + if (_this <= 2250000) exitWith { // 1500^2 + [0.5, 3, -1.9, 0.03] + }; + if (_this <= 4000000) exitWith { // 2000^2 + [0.3, 2.5, -1.6, 0.025] + }; + if (_this <= 6250000) exitWith { // 2500^2 + [0.2, 2, -1.3, 0.02] + }; + [0.15, 1.5, -1, 0.015] + }) params ["_fadeByDistance", "_sizeByDistance", "_heightByDistance", "_fontSizeByDistance"]; + + // Apply color fade + _groupColor set [3, _fadeByDistance]; + + private _name = ([_x] call EFUNC(common,getName)) select [0, NAME_MAX_CHARACTERS]; + if !(isPlayer _x) then { _name = format ["%1: %2", localize "str_player_ai", _name]; }; + + if (_inVehicle) then { + private _crewCount = (({alive _x} count (crew _vehicle)) - 1); + if (_crewCount > 0) then { + _name = format ["%1 (+%2)", _name, _crewCount]; + }; + }; + + // Show unit name only if camera is near enough + if (_distanceToCameraSqr < DISTANCE_NAMES_SQR) then { + // Unit name + _iconsToDraw pushBack [_x, 2, [ + "", + [1,1,1,1], + [0,0,0], + 0, + _heightByDistance, + 0, + _name, + 2, + _fontSizeByDistance, + "PuristaMedium", + "center" + ]]; + } else { + if (_x == _groupLeader) then { + // Group name + _iconsToDraw pushBack [_x, 0, [ + "", + [1,1,1,_fadeByDistance], + [0,0,0], + 0, + _heightByDistance, + 0, + _groupName, + 2, + _fontSizeByDistance, + "PuristaMedium", + "center" + ]]; + }; + }; + + if (_x == _groupLeader || { _inVehicle && { _x == effectiveCommander _vehicle } }) then { + // Group icon + _iconsToDraw pushBack [_x, 0, [ + [_group, true] call FUNC(getGroupIcon), + _groupColor, + [0,0,0], + _sizeByDistance, + _sizeByDistance, + 0, + "", + 0, + 0.035, + "PuristaMedium", + "center" + ]]; + }; + + // Draw unit icon + _iconsToDraw pushBack [_x, 1, [ + [ICON_UNIT, ICON_REVIVE] select (NEEDS_REVIVE(_x)), + _groupColor, + [0,0,0], + _sizeByDistance, + _sizeByDistance, + 0, + "", + 0, + 0.035, + "PuristaMedium", + "center" + ]]; + }; + + // Track entities themselves for use with fired EH + _entitiesToDraw pushBack _vehicle; + + // Add fired EH for drawing and icon highlighting + if (GETVAR(_vehicle,GVAR(firedEH),-1) == -1) then { + SETVAR(_vehicle,GVAR(firedEH),_vehicle addEventHandler [ARR_2("Fired",{_this call FUNC(handleFired)})]); + }; + + nil // Speed loop +} count ([] call FUNC(getTargetEntities)); + +// Remove object locations that are now null +{ + _x params ["_id", "_name", "", "_texture", "_pos"]; + + if ((_pos isEqualType objNull) && {isNull _pos}) then { + [_id] call FUNC(removeLocation); + } else { + if ((GVAR(camera) distanceSqr _pos) < DISTANCE_NAMES_SQR) then { + GVAR(locationsToDraw) pushBack [_pos, _name, _texture]; + }; + }; + + nil // Speed loop +} count (GVAR(locationsList)); + +GVAR(iconsToDraw) = _iconsToDraw; +GVAR(entitiesToDraw) = _entitiesToDraw; diff --git a/addons/spectator/functions/fnc_ui_updateListEntities.sqf b/addons/spectator/functions/fnc_ui_updateListEntities.sqf new file mode 100644 index 0000000000..cb165b5f45 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateListEntities.sqf @@ -0,0 +1,222 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Updates spectator UI list of units/groups + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_updateListEntities + * + * Public: No + */ + +#include "script_component.hpp" + +private _newUnits = []; +private _newGroups = []; +// Always show the 4 main sides in this intuative order +private _newSides = [str west, str east, str resistance, str civilian]; +private _newList = [ + [west, str west, [west] call BIS_fnc_sideName, [west] call BIS_fnc_sideColor, []], + [east, str east, [east] call BIS_fnc_sideName, [east] call BIS_fnc_sideColor, []], + [resistance, str resistance, [resistance] call BIS_fnc_sideName, [resistance] call BIS_fnc_sideColor, []], + [civilian, str civilian, [civilian] call BIS_fnc_sideName, [civilian] call BIS_fnc_sideColor, []] +]; + +// Go through entity groups and cache information (include dead entities) +private _entities = [true] call FUNC(getTargetEntities); +{ + // Add the group if new + private _group = _x; + if !(str _group in _newGroups) then { + // Include the group if it contains valid entities + private _entitiesGroup = units _group arrayIntersect _entities; + + if !(_entitiesGroup isEqualTo []) then { + // Cache the info of valid units in the group + private _unitsInfo = []; + { + _newUnits pushBack ([_x] call BIS_fnc_objectVar); + + private _name = ([_x] call EFUNC(common,getName)) select [0, NAME_MAX_CHARACTERS]; + if !(isPlayer _x) then { _name = format ["%1: %2", localize "str_player_ai", _name]; }; + + _unitsInfo pushBack [ + _x, + alive _x, + alive _x && { NEEDS_REVIVE(_x) }, + _name + ]; + nil // Speed loop + } count _entitiesGroup; + + // Cache the info of the group itself + private _groupTexture = [_group] call FUNC(getGroupIcon); + private _groupInfo = [_group, str _group, _groupTexture, groupID _group]; + + // Add the group to the correct side + private _side = side _group; + private _sideIndex = _newSides find (str _side); + + // Add the side if new + if (_sideIndex < 0) then { + _newList pushBack [ + _side, + str _side, + [_side] call BIS_fnc_sideName, + [_side] call BIS_fnc_sideColor, + [] + ]; + + _sideIndex = _newSides pushBack (str _side); + }; + + // Add it to the right index + _newGroups pushBack (str _group); + ((_newList select _sideIndex) select 4) pushBack [_groupInfo, _unitsInfo]; + }; + }; + nil // Speed loop +} count allGroups; + +// Whether an update to the list is required (really only if something changed) +if !(GVAR(curList) isEqualTo _newList) then { + private _ctrl = CTRL_LIST; + + // Remove groups/units that are no longer there + for "_sideIndex" from ((_ctrl tvCount []) - 1) to 0 step -1 do { + for "_groupIndex" from ((_ctrl tvCount [_sideIndex]) - 1) to 0 step -1 do { + for "_unitIndex" from ((_ctrl tvCount [_sideIndex, _groupIndex]) - 1) to 0 step -1 do { + private _lookup = _newUnits find (_ctrl tvData [_sideIndex, _groupIndex, _unitIndex]); + if (_lookup < 0) then { + _ctrl tvDelete [_sideIndex, _groupIndex, _unitIndex]; + } else { + _newUnits deleteAt _lookup; + }; + }; + private _lookup = _newGroups find (_ctrl tvData [_sideIndex, _groupIndex]); + if (_lookup < 0) then { + _ctrl tvDelete [_sideIndex, _groupIndex]; + } else { + _newGroups deleteAt _lookup; + }; + }; + private _lookup = _newSides find (_ctrl tvData [_sideIndex]); + if (_lookup < 0) then { + _ctrl tvDelete [_sideIndex]; + } else { + _newSides deleteAt _lookup; + }; + }; + + // Hash location lookups, note hashing assumes unique side/group/unit data + private _sideDataToPathHash = [[], []]; + private _groupDataToPathHash = [[], []]; + private _unitDataToPathHash = [[], []]; + + for "_sideIndex" from 0 to ((_ctrl tvCount []) - 1) do { + (_sideDataToPathHash select 0) pushBack (_ctrl tvData [_sideIndex]); + (_sideDataToPathHash select 1) pushBack [_sideIndex]; + for "_groupIndex" from 0 to ((_ctrl tvCount [_sideIndex]) - 1) do { + (_groupDataToPathHash select 0) pushBack (_ctrl tvData [_sideIndex, _groupIndex]); + (_groupDataToPathHash select 1) pushBack [_sideIndex, _groupIndex]; + for "_unitIndex" from 0 to ((_ctrl tvCount [_sideIndex, _groupIndex]) - 1) do { + (_unitDataToPathHash select 0) pushBack (_ctrl tvData [_sideIndex, _groupIndex, _unitIndex]); + (_unitDataToPathHash select 1) pushBack [_sideIndex, _groupIndex, _unitIndex]; + }; + }; + }; + + // Update/add the values + { + _x params ["_side", "_sideStr", "_sideTitle", "_sideColor", "_nestedGroupData"]; + + private _sideIndex = -1; + private _lookup = (_sideDataToPathHash select 0) find _sideStr; + if (_lookup < 0) then { + _sideIndex = _ctrl tvAdd [[], _sideTitle]; + _ctrl tvSetData [[_sideIndex], _sideStr]; + _ctrl tvExpand [_sideIndex]; + } else { + // pop data out of hash to improve later lookups + (_sideDataToPathHash select 0) deleteAt _lookup; + private _path = (_sideDataToPathHash select 1) deleteAt _lookup; + _sideIndex = _path select 0; + + _ctrl tvSetText [_path, _sideTitle]; + }; + + { + _x params ["_groupInfo", "_nestedUnitData"]; + _groupInfo params ["_group", "_groupStr", "_groupTexture", "_groupId"]; + + private _groupIndex = -1; + private _lookup = (_groupDataToPathHash select 0) find _groupStr; + if (_lookup < 0) then { + _groupIndex = _ctrl tvAdd [[_sideIndex], _groupId]; + _ctrl tvSetData [[_sideIndex, _groupIndex], _groupStr]; + _ctrl tvSetPicture [[_sideIndex, _groupIndex], _groupTexture]; + _ctrl tvSetPictureColor [[_sideIndex, _groupIndex], _sideColor]; + _ctrl tvSetTooltip [[_sideIndex, _groupIndex], _groupId]; + _ctrl tvExpand [_sideIndex, _groupIndex]; + } else { + // pop data out of hash to improve later lookups + (_groupDataToPathHash select 0) deleteAt _lookup; + private _path = (_groupDataToPathHash select 1) deleteAt _lookup; + _groupIndex = _path select 1; + + _ctrl tvSetText [_path, _groupId]; + _ctrl tvSetPicture [_path, _groupTexture]; + _ctrl tvSetPictureColor [_path, _sideColor]; + _ctrl tvSetTooltip [_path, _groupId]; + }; + + { + _x params ["_unit", "_isAlive", "_isIncapacitated", "_name"]; + + // Show full name in tooltip + whether medic + whether engineer + private _tooltip = [[_unit] call EFUNC(common,getName)]; + if ([_unit] call EFUNC(common,isMedic)) then { _tooltip pushBack (localize "str_support_medic"); }; + if ([_unit] call EFUNC(common,isEngineer)) then { _tooltip pushBack (localize LSTRING(TooltipEngineer)); }; + _tooltip = _tooltip joinString " - "; + + private _texture = [_isAlive, _isIncapacitated, _unit] call { + params ["","","_unit"]; + if !(_this select 0) exitWith { ICON_DEAD }; + if (_this select 1) exitWith { ICON_REVIVE }; + [vehicle _unit] call EFUNC(common,getVehicleIcon) + }; + + private _lookup = (_unitDataToPathHash select 0) find ([_unit] call BIS_fnc_objectVar); + if (_lookup < 0) then { + private _unitIndex = _ctrl tvAdd [[_sideIndex, _groupIndex], _name]; + _ctrl tvSetData [[_sideIndex, _groupIndex, _unitIndex], [_unit] call BIS_fnc_objectVar]; + _ctrl tvSetPicture [[_sideIndex, _groupIndex, _unitIndex], _texture]; + _ctrl tvSetPictureColor [[_sideIndex, _groupIndex, _unitIndex], _sideColor]; + _ctrl tvSetTooltip [[_sideIndex, _groupIndex, _unitIndex], _tooltip]; + } else { + // pop data out of hash to improve later lookups + (_unitDataToPathHash select 0) deleteAt _lookup; + private _path = (_unitDataToPathHash select 1) deleteAt _lookup; + _ctrl tvSetText [_path, _name]; + _ctrl tvSetPicture [_path, _texture]; + _ctrl tvSetPictureColor [_path, _sideColor]; + _ctrl tvSetTooltip [_path, _tooltip]; + }; + nil // Speed loop + } count _nestedUnitData; + nil // Speed loop + } count _nestedGroupData; + nil // Speed loop + } count _newList; + + // Store the new list as the current list + GVAR(curList) = _newList; +}; + +// Update focus if required +[] call FUNC(ui_updateListFocus); diff --git a/addons/spectator/functions/fnc_ui_updateListFocus.sqf b/addons/spectator/functions/fnc_ui_updateListFocus.sqf new file mode 100644 index 0000000000..4bdd191017 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateListFocus.sqf @@ -0,0 +1,22 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Function used to update the list current selection + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_updateListFocus + * + * Public: No + */ + +#include "script_component.hpp" + +// Don't update list when in location mode or focus is a location +if (!GVAR(camOnLocation) && {GVAR(uiListType) != LIST_LOCATIONS}) then { + CTRL_LIST tvSetCurSel ([[GVAR(camTarget)] call BIS_fnc_objectVar] call FUNC(ui_getTreeDataIndex)); +}; diff --git a/addons/spectator/functions/fnc_ui_updateListLocations.sqf b/addons/spectator/functions/fnc_ui_updateListLocations.sqf new file mode 100644 index 0000000000..9693cba2a7 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateListLocations.sqf @@ -0,0 +1,69 @@ +/* + * Author: Nelson Duarte, AACO, SilentSpike + * Updates spectator UI list of locations + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_updateListLocations + * + * Public: No + */ + +#include "script_component.hpp" + +private _newLocations = []; +private _newList = GVAR(locationsList); + +// Whether an update to the list is required (really only if something changed) +if !(GVAR(curList) isEqualTo _newList) then { + private _ctrl = CTRL_LIST; + + // Remove locations that are no longer there + for "_locationIndex" from ((_ctrl tvCount []) - 1) to 0 step -1 do { + private _lookup = _newLocations find (_ctrl tvData [_locationIndex]); + if (_lookup < 0) then { + _ctrl tvDelete [_locationIndex]; + } else { + _newLocations deleteAt _lookup; + }; + }; + + // Hash location lookups, note hashing assumes unique location data + private _locationDataToPathHash = [[], []]; + + for "_locationIndex" from 0 to ((_ctrl tvCount []) - 1) do { + (_locationDataToPathHash select 0) pushBack (_ctrl tvData [_locationIndex]); + (_locationDataToPathHash select 1) pushBack [_locationIndex]; + }; + + { + _x params ["_id", "_name", "_description", "_texture"]; + + private _lookup = (_locationDataToPathHash select 0) find _id; + if (_lookup < 0) then { + private _locationIndex = _ctrl tvAdd [[], _name]; + _ctrl tvSetData [[_locationIndex], _id]; + _ctrl tvSetPicture [[_locationIndex], _texture]; + _ctrl tvSetPictureColor [[_locationIndex], [1,1,1,1]]; + _ctrl tvSetTooltip [[_locationIndex], _description]; + } else { + // pop data out of hash to improve later lookups + (_locationDataToPathHash select 0) deleteAt _lookup; + private _path = (_locationDataToPathHash select 1) deleteAt _lookup; + + _ctrl tvSetText [_path, _name]; + _ctrl tvSetPicture [_path, _texture]; + _ctrl tvSetPictureColor [_path, [1,1,1,1]]; + _ctrl tvSetTooltip [_path, _description]; + }; + + nil // Speed loop + } count _newList; + + GVAR(curList) = _newList; +}; diff --git a/addons/spectator/functions/fnc_ui_updateWidget.sqf b/addons/spectator/functions/fnc_ui_updateWidget.sqf new file mode 100644 index 0000000000..53850daf94 --- /dev/null +++ b/addons/spectator/functions/fnc_ui_updateWidget.sqf @@ -0,0 +1,70 @@ +/* + * Author: Nelson Duarte, SilentSpike + * Updates spectator UI unit info widget + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call ace_spectator_fnc_ui_updateWidget + * + * Public: No + */ + +#include "script_component.hpp" +#define IMG_COMMANDER "a3\Ui_f\data\IGUI\Cfg\CommandBar\imageCommander_ca.paa" +#define IMG_DRIVER "a3\Ui_f\data\IGUI\Cfg\CommandBar\imageDriver_ca.paa" +#define IMG_GUNNER "a3\Ui_f\data\IGUI\Cfg\CommandBar\imageGunner_ca.paa" +#define IMG_CARGO "a3\Ui_f\data\IGUI\Cfg\CommandBar\imageCommander_ca.paa" + +// Hide if no target, or target is a location or widget is toggled off +if (!GVAR(uiWidgetVisible) || {GVAR(camOnLocation)} || {isNull GVAR(camTarget)}) exitWith {CTRL_WIDGET ctrlShow false}; + +private _focus = GVAR(camTarget); + +private _name = ([_focus] call EFUNC(common,getName)) select [0, NAME_MAX_CHARACTERS]; +if !(isPlayer _focus) then { _name = format ["%1: %2", localize "str_player_ai", _name]; }; + +private _unitTypePicture = [_focus] call EFUNC(common,getVehicleIcon); +private _vehicleTypePicture = getText (configFile >> "CfgVehicles" >> typeOf vehicle _focus >> "Picture"); +private _insigniaTexture = ["GetGroupTexture", [group _focus]] call BIS_fnc_dynamicGroups; + +private _weapon = currentWeapon _focus; +private _weaponPicture = if (_weapon != "") then { + getText (configFile >> "CfgWeapons" >> _weapon >> "Picture"); +} else { + if (_focus != vehicle _focus) then { + if (commander vehicle _focus == _focus) exitWith {IMG_COMMANDER}; + if (driver vehicle _focus == _focus) exitWith {IMG_DRIVER}; + if (gunner vehicle _focus == _focus) exitWith {IMG_GUNNER}; + IMG_CARGO + } else {""}; +}; + +(getPlayerScores _focus) params [["_kills",0,[0]], ["_softKills",0,[0]], ["_armoredKills",0,[0]], ["_airKills",0,[0]], ["_deaths",0,[0]], ["_total",0,[0]]]; + +CTRL_WIDGET_NAME ctrlSetText _name; +CTRL_WIDGET_AVATAR ctrlSetText _insigniaTexture; +CTRL_WIDGET_KILLS ctrlSetText str _kills; +CTRL_WIDGET_LAND ctrlSetText str _softKills; +CTRL_WIDGET_ARMORED ctrlSetText str _armoredKills; +CTRL_WIDGET_AIR ctrlSetText str _airKills; +CTRL_WIDGET_DEATHS ctrlSetText str _deaths; +CTRL_WIDGET_TOTAL ctrlSetText str _total; +CTRL_WIDGET_WEAPON ctrlSetText _weaponPicture; + +CTRL_WIDGET_UNIT ctrlSetText (["",_unitTypePicture] select (vehicle _focus == _focus)); +CTRL_WIDGET_UNIT ctrlShow (vehicle _focus == _focus); +CTRL_WIDGET_VEHICLE ctrlSetText (["",_vehicleTypePicture] select (vehicle _focus != _focus)); +CTRL_WIDGET_VEHICLE ctrlShow (vehicle _focus != _focus); + +CTRL_WIDGET_WEAPON ctrlShow (_weaponPicture != ""); +CTRL_WIDGET_WEAPON_BACK ctrlShow (_weaponPicture != ""); + +// Handle widget toggling +if !(ctrlShown CTRL_WIDGET) then { + CTRL_WIDGET ctrlShow true; +}; diff --git a/addons/spectator/functions/fnc_updateCameraModes.sqf b/addons/spectator/functions/fnc_updateCameraModes.sqf index 819636ee22..c6287c9239 100644 --- a/addons/spectator/functions/fnc_updateCameraModes.sqf +++ b/addons/spectator/functions/fnc_updateCameraModes.sqf @@ -3,8 +3,10 @@ * Adds or removes spectator camera modes from the selection available to the local player. * Possible camera modes are: * - 0: Free - * - 1: Internal - * - 2: External + * - 1: First person + * - 2: Follow + * + * Default selection is [0,1,2] * * Arguments: * 0: Camera modes to add @@ -31,7 +33,7 @@ private ["_newModes","_currentModes"]; _currentModes = GVAR(availableModes); // Restrict additions to only possible values -_newModes = _addModes arrayIntersect [0,1,2]; +_newModes = _addModes arrayIntersect ALL_MODES; _newModes append (_currentModes - _removeModes); _newModes = _newModes arrayIntersect _newModes; @@ -39,14 +41,19 @@ _newModes sort true; // Can't become an empty array if (_newModes isEqualTo []) then { - ["Cannot remove all camera modes (%1)", QFUNC(updateCameraModes)] call BIS_fnc_error; + WARNING("Cannot remove all spectator camera modes"); } else { GVAR(availableModes) = _newModes; }; // Update camera in case of change -if (GVAR(isSet)) then { - [] call FUNC(transitionCamera); +if !(isNil QGVAR(camera)) then { + // If mode was free and no longer available, find a focus + if (!(MODE_FREE in _newModes) && {GVAR(camMode) == MODE_FREE} && {isNull GVAR(camTarget) || GVAR(camOnLocation)}) then { + [true] call FUNC(setFocus); + }; + + [GVAR(camMode)] call FUNC(cam_setCameraMode); }; _newModes diff --git a/addons/spectator/functions/fnc_updateSpectatableSides.sqf b/addons/spectator/functions/fnc_updateSides.sqf similarity index 73% rename from addons/spectator/functions/fnc_updateSpectatableSides.sqf rename to addons/spectator/functions/fnc_updateSides.sqf index 57f7756d54..ec4606b951 100644 --- a/addons/spectator/functions/fnc_updateSpectatableSides.sqf +++ b/addons/spectator/functions/fnc_updateSides.sqf @@ -1,7 +1,6 @@ /* * Author: SilentSpike - * Adds or removes sides from the selection available to spectate by the local player. - * Note that the side filter setting is applied to the available sides dynamically. + * Adds or removes sides from the selection available to spectate. Local effect. * * Default selection is [west,east,resistance,civilian] * @@ -10,10 +9,10 @@ * 1: Sides to remove * * Return Value: - * Spectatable sides + * Sides available * * Example: - * [[west], [east,civilian]] call ace_spectator_fnc_updateSpectatableSides + * [[west], [east,civilian]] call ace_spectator_fnc_updateSides * * Public: Yes */ diff --git a/addons/spectator/functions/fnc_updateUnits.sqf b/addons/spectator/functions/fnc_updateUnits.sqf index 418643be38..31bde1ac19 100644 --- a/addons/spectator/functions/fnc_updateUnits.sqf +++ b/addons/spectator/functions/fnc_updateUnits.sqf @@ -1,69 +1,40 @@ /* * Author: SilentSpike - * Adds units to spectator whitelist/blacklist and refreshes the filter units + * Adds and removed units from the spectator list. Local effect. * * Arguments: - * 0: Units to add to the whitelist - * 1: Use blacklist (default: false) + * 0: Units to show in the list + * 1: Units to hide in the list * * Return Value: * None * * Example: - * [allUnits,true] call ace_spectator_fnc_updateUnits + * [allPlayers, [player]] call ace_spectator_fnc_updateUnits * * Public: Yes */ #include "script_component.hpp" -params [["_newUnits",[],[[]]],["_blacklist",false,[false]]]; - // Function only matters on player clients if (!hasInterface) exitWith {}; -// If adding to a list we can exit here, since it won't show up until the UI refreshes anyway -if !(_newUnits isEqualTo []) exitWith { - if (_blacklist) then { - GVAR(unitWhitelist) = GVAR(unitWhitelist) - _newUnits; - GVAR(unitBlacklist) append _newUnits; - } else { - GVAR(unitBlacklist) = GVAR(unitBlacklist) - _newUnits; - GVAR(unitWhitelist) append _newUnits; +params [["_addUnits",[],[[]]], ["_removeUnits",[],[[], true]]]; + +// Deprecated parameter (remember to remove bool from params when removed) +if (_removeUnits isEqualType true) then { + ACE_DEPRECATED("Boolean parameter","3.12.0","array (see function header or doc)"); + if (_removeUnits) then { + _removeUnits = _addUnits; + _addUnits = []; }; }; -// Unit setting filter -private _newUnits = [[],allPlayers,playableUnits,allUnits] select GVAR(filterUnits); +// Add to the whitelist and prevent list overlap +GVAR(unitBlacklist) = GVAR(unitBlacklist) - _addUnits; +GVAR(unitWhitelist) append _addUnits; -// Side setting filter -private _sideFilter = [ - {_x == (side group player)}, - {(_x getFriend (side group player)) >= 0.6}, - {(_x getFriend (side group player)) < 0.6}, - {true} -] select GVAR(filterSides); - -private _filteredSides = GVAR(availableSides) select _sideFilter; - -// Filter units and append to list -private _filteredUnits = (_newUnits - GVAR(unitBlacklist)) select { - (alive _x) && - {(_x isKindOf "CAManBase")} && - {(side group _x) in _filteredSides} && // Side filter - {simulationEnabled _x} && - {!(_x getVariable [QGVAR(isStaged), false])} // Who watches the watchmen? -}; -_filteredUnits append GVAR(unitWhitelist); - -// Cache icons and colour for drawing -private _filteredGroups = []; -{ - // Intentionally re-applied to units in case their status changes - [_x] call FUNC(cacheUnitInfo); - _filteredGroups pushBackUnique (group _x); -} forEach _filteredUnits; - -// Replace previous lists entirely (removes any no longer valid) -GVAR(groupList) = _filteredGroups; -GVAR(unitList) = _filteredUnits arrayIntersect _filteredUnits; +// Blacklist overrides the whitelist +GVAR(unitWhitelist) = GVAR(unitWhitelist) - _removeUnits; +GVAR(unitBlacklist) append _removeUnits; diff --git a/addons/spectator/functions/fnc_updateVisionModes.sqf b/addons/spectator/functions/fnc_updateVisionModes.sqf index 6db965af3e..d1f3af5bae 100644 --- a/addons/spectator/functions/fnc_updateVisionModes.sqf +++ b/addons/spectator/functions/fnc_updateVisionModes.sqf @@ -1,7 +1,7 @@ /* * Author: SilentSpike * Adds or removes spectator vision modes from the selection available to the local player. - * The default selection is [-2,-1,0,1]. + * * Possible vision modes are: * - -2: Normal * - -1: Night vision @@ -14,6 +14,8 @@ * - 6: White Hot / Darker Red Cold * - 7: Thermal (Shade of Red and Green, Bodies are white) * + * Default selection is [-2,-1,0,1] + * * Arguments: * 0: Vision modes to add * 1: Vision modes to remove @@ -47,14 +49,14 @@ _newModes sort true; // Can't become an empty array if (_newModes isEqualTo []) then { - ["Cannot remove all vision modes (%1)", QFUNC(updateVisionModes)] call BIS_fnc_error; + WARNING("Cannot remove all spectator vision modes"); } else { GVAR(availableVisions) = _newModes; }; // Update camera in case of change -if (GVAR(isSet)) then { - [] call FUNC(transitionCamera); +if !(isNil QGVAR(camera)) then { + [GVAR(camVision)] call FUNC(cam_setVisionMode); }; _newModes diff --git a/addons/spectator/script_component.hpp b/addons/spectator/script_component.hpp index 38561f3cdd..54a4577792 100644 --- a/addons/spectator/script_component.hpp +++ b/addons/spectator/script_component.hpp @@ -24,39 +24,118 @@ #define X_PART(num) (W_PART(num) + (safezoneX + (safezoneW - SIZEX)/2)) #define Y_PART(num) (H_PART(num) + (safezoneY + (safezoneH - SIZEY)/2)) -// UI tools -#define TOOL_H H_PART(1) -#define TOOL_W W_PART(5) -#define MARGIN TOOL_W * 0.05 +// UI/Camera related values +#define SPEED_SLOW 0.1 +#define SPEED_DEFAULT 1 +#define SPEED_FAST 2 // Seems to be some form of multiplier (but using 1 stil makes it faster...?) -// UI compass -#define COMPASS_W (TOOL_W * 4) -#define COMPASS_X (safeZoneX + safeZoneW * 0.5 - COMPASS_W * 0.5) +#define MODE_FREE 0 +#define MODE_FPS 1 +#define MODE_FOLLOW 2 +#define ALL_MODES [MODE_FREE,MODE_FPS,MODE_FOLLOW] -// UI IDCs -#define IDC_COMP 4490 -#define IDC_COMP_0 5000 -#define IDC_COMP_90 5090 -#define IDC_COMP_180 5180 -#define IDC_COMP_270 5270 +#define VISION_NORM -2 +#define VISION_NVG -1 -#define IDC_HELP 7631 -#define IDC_HELP_LIST 7622 +#define MAX_VIEW_DISTANCE 2500 +#define MIN_VIEW_DISTANCE 500 +#define DEFAULT_VIEW_DISTANCE 1200 -#define IDC_MAP 6791 +#define FIRE_HIGHLIGHT_TIME 0.05 +#define MAX_GRENADES 15 +#define MAX_PROJECTILES 50 +#define MAX_PROJECTILE_SEGMENTS 50 -#define IDC_TOOL 3000 -#define IDC_TOOL_CLOCK 3003 -#define IDC_TOOL_FOV 3005 -#define IDC_TOOL_NAME 3001 -#define IDC_TOOL_SPEED 3006 -#define IDC_TOOL_VIEW 3002 -#define IDC_TOOL_VISION 3004 +#define DISTANCE_ICONS_SQR 9000000 // Icons are rendered within 3000m, squared for `distanceSqr` speed +#define DISTANCE_NAMES_SQR 30625 // Names are rendered within 175m, squared for `distanceSqr` speed +#define NAME_MAX_CHARACTERS 17 -#define IDC_UNIT 6002 -#define IDC_UNIT_TREE 6005 +#define LIST_ENTITIES localize "STR_A3_Spectator_Entities" +#define LIST_LOCATIONS localize "STR_A3_Spectator_Locations" +#define LIST_UPDATE_RATE 1 -// UI colours -#define COL_BACK 0.1,0.1,0.1,0.7 -#define COL_FORE 1,1,1,1 -#define COL_FORE_D 0.1,0.1,0.1,0.8 +// Revive variables +#define BIS_REVIVE "BIS_revive_incapacitated" +#define ACE_REVIVE "ACE_isUnconscious" +#define NEEDS_REVIVE(unit) (unit getVariable [ACE_REVIVE,false]) || {unit getVariable [BIS_REVIVE,false]} + +// Icons used in the UI/drawing +#define ICON_DEAD "a3\Ui_F_Curator\Data\CfgMarkers\kia_ca.paa" +#define ICON_GRENADE "A3\Ui_f\data\IGUI\Cfg\HoldActions\holdAction_connect_ca.paa" +#define ICON_UNIT "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\UnitIcon_ca.paa" +#define ICON_REVIVE "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\ReviveIcon_ca.paa" +#define ICON_BACKGROUND_UNIT "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\UnitName_ca.paa" +#define ICON_CAMERA "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\cameraTexture_ca.paa" +#define CAM_ICON_FREE "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\FreeSelected.paa" +#define CAM_ICON_FREE_SELECTED "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\Free.paa" +#define CAM_ICON_FOLLOW "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\FollowSelected.paa" +#define CAM_ICON_FOLLOW_SELECTED "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\Follow.paa" +#define CAM_ICON_FPS "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\FpsSelected.paa" +#define CAM_ICON_FPS_SELECTED "a3\Ui_f\data\GUI\Rsc\RscDisplayEGSpectator\Fps.paa" + +// IDCs +#define MAIN_DISPLAY ([] call BIS_fnc_displayMission) + +#define IDD_SPEC_DISPLAY 60000 +#define SPEC_DISPLAY (findDisplay IDD_SPEC_DISPLAY) + +#define IDC_MOUSE 60001 +#define CTRL_MOUSE (SPEC_DISPLAY displayCtrl IDC_MOUSE) + +#define IDC_TIME 60002 +#define CTRL_TIME (SPEC_DISPLAY displayCtrl IDC_TIME) + +#define IDC_LIST 60003 +#define CTRL_LIST (SPEC_DISPLAY displayCtrl IDC_LIST) + +#define IDC_TABS 60004 +#define CTRL_TABS (SPEC_DISPLAY displayCtrl IDC_TABS) + +#define IDC_CAM_TYPES 60005 +#define CTRL_CAM_TYPES (SPEC_DISPLAY displayCtrl IDC_CAM_TYPES) +#define IDC_FREE 60006 +#define IDC_FOLLOW 60007 +#define IDC_FPS 60008 + +#define IDC_MAP_GROUP 60010 +#define CTRL_MAP_GROUP (SPEC_DISPLAY displayCtrl IDC_MAP_GROUP) +#define IDC_MAP_TITLE 60011 +#define CTRL_MAP_TITLE (SPEC_DISPLAY displayCtrl IDC_MAP_TITLE) +#define IDC_MAP_FOOTER 60012 +#define CTRL_MAP_FOOTER (SPEC_DISPLAY displayCtrl IDC_MAP_FOOTER) +#define IDC_MAP_SPEC_NUM 60013 +#define CTRL_MAP_SPEC_NUM (SPEC_DISPLAY displayCtrl IDC_MAP_SPEC_NUM) +#define IDC_MAP 60014 +#define CTRL_MAP (SPEC_DISPLAY displayCtrl IDC_MAP) + +#define IDC_HELP_BACK 60020 +#define CTRL_HELP_BACK (SPEC_DISPLAY displayCtrl IDC_HELP_BACK) +#define IDC_HELP 60021 +#define CTRL_HELP (SPEC_DISPLAY displayCtrl IDC_HELP) + +#define IDC_WIDGET 60030 +#define CTRL_WIDGET (SPEC_DISPLAY displayCtrl IDC_WIDGET) +#define IDC_WIDGET_VEHICLE 60031 +#define CTRL_WIDGET_VEHICLE (SPEC_DISPLAY displayCtrl IDC_WIDGET_VEHICLE) +#define IDC_WIDGET_UNIT 60032 +#define CTRL_WIDGET_UNIT (SPEC_DISPLAY displayCtrl IDC_WIDGET_UNIT) +#define IDC_WIDGET_NAME 60033 +#define CTRL_WIDGET_NAME (SPEC_DISPLAY displayCtrl IDC_WIDGET_NAME) +#define IDC_WIDGET_AVATAR 60034 +#define CTRL_WIDGET_AVATAR (SPEC_DISPLAY displayCtrl IDC_WIDGET_AVATAR) +#define IDC_WIDGET_KILLS 60035 +#define CTRL_WIDGET_KILLS (SPEC_DISPLAY displayCtrl IDC_WIDGET_KILLS) +#define IDC_WIDGET_LAND 60036 +#define CTRL_WIDGET_LAND (SPEC_DISPLAY displayCtrl IDC_WIDGET_LAND) +#define IDC_WIDGET_ARMORED 60037 +#define CTRL_WIDGET_ARMORED (SPEC_DISPLAY displayCtrl IDC_WIDGET_ARMORED) +#define IDC_WIDGET_AIR 60038 +#define CTRL_WIDGET_AIR (SPEC_DISPLAY displayCtrl IDC_WIDGET_AIR) +#define IDC_WIDGET_DEATHS 60039 +#define CTRL_WIDGET_DEATHS (SPEC_DISPLAY displayCtrl IDC_WIDGET_DEATHS) +#define IDC_WIDGET_TOTAL 60040 +#define CTRL_WIDGET_TOTAL (SPEC_DISPLAY displayCtrl IDC_WIDGET_TOTAL) +#define IDC_WIDGET_WEAPON 60041 +#define CTRL_WIDGET_WEAPON (SPEC_DISPLAY displayCtrl IDC_WIDGET_WEAPON) +#define IDC_WIDGET_WEAPON_BACK 60042 +#define CTRL_WIDGET_WEAPON_BACK (SPEC_DISPLAY displayCtrl IDC_WIDGET_WEAPON_BACK) diff --git a/addons/spectator/stringtable.xml b/addons/spectator/stringtable.xml index 3cc6a3098d..b25ce556bf 100644 --- a/addons/spectator/stringtable.xml +++ b/addons/spectator/stringtable.xml @@ -1,4 +1,4 @@ - + @@ -40,185 +40,11 @@ 设定旁观者系统相关配置 設定旁觀者系統相關配置 - - Unit filter - Einheitenfilter - Filtr jednostek - Filtro de unidades - Фильтр юнитов - Filtr jednotek - Filtro de unidad - Filtro Unità - Filtre d'unités - ユニット フィルタ - 인원 필터 - 单位过滤器 - 單位過濾器 + + AI Enabled - - Method of filtering spectatable units. - Einheiten denen zugeschaut werden kann. - Wybierz jednostki, jakie będzie można obserwować po uruchomeniu obserwatora. - Método para filtrar unidades espectáveis - Метод фильтрации наблюдаемых юнитов. - Método de filtrado de unidades de espectador - Metoda filtrování pozorovaných jednotek. - Metodo di filtraggio delle unità osservabili. - Méthode de filtration des unités regardables. - 観察できるユニットへのフィルタ設定ができます。 - 관전할 수 있는 인원을 고릅니다 - 过滤哪些单位可以使用旁观者系统 - 過濾哪些單位可以使用旁觀者系統 - - - No units - Keine Einheiten - Brak jednostek - Sem unidades - Никто - Žádné jednotky - Ninguna - Nessuna unità - Pas d'unités - ユニットなし - 인원 없음 - 无单位 - 無單位 - - - Only players - Nur Spieler - Tylko gracze - Somente jogadores - Только игроки - Pouze hráči - Solo jugadores - Solo giocatori - Joueurs seulements - プレイヤのみ - 플레이어만 - 只有玩家 - 只有玩家 - - - Playable Units - Nur spielbare Einheiten - Grywalne jednostki - Unidades jogáveis - Играбельные юниты - Hratelné jednotky - Unidades jugables - Unità giocabili - Unités jouables - プレイ可能なユニットのみ - 플레이 가능한 인원 - 可扮演单位 - 可扮演單位 - - - All units - Alle Einheiten - Wszystkie jednostki - Todas unidades - Все юниты - Všechny jednotky - Todas las unidades - Tutte le unità - Toutes les unités - 全てのユニット - 모든 인원 - 所有单位 - 所有單位 - - - Side filter - Fraktionenfilter - Filtr stron - Filtro de lados - Фильтр стороны - Filtr stran - Filtro de bando - Filtro Lato - Filtre de faction - 勢力フィルタ - 진영 필터 - 阵营过滤器 - 陣營過濾器 - - - Method of filtering spectatable sides. - Fraktionen denen zugeschaut werden kann. - Wybierz strony, jakie będzie można obserwować po uruchomeniu obserwatora. - Método para filtrar lados espectáveis. - Метод фильтрации наблюдаемых сторон. - Método de filtrado de bandos de espectador - Metoda filtrování pozorovaných stran. - Metodo per filtrare i lati osservabili. - Méthode de filtration des factions regardables - 観察できる勢力へのフィルタ設定ができます。 - 관전할 수 있는 진영을 고릅니다 - 过滤可旁观的阵营 - 過濾可旁觀的陣營 - - - Player side - Spielerseite - Strona gracza - Lado do jogador - Сторона игрока - Strana hráče - Bando del jugador - Lato giocatore - Faction du joueur - プレイヤーと同じ勢力 - 플레이어 진영 - 玩家 - 玩家 - - - Friendly sides - Verbündete - Strony sojusznicze - Lados aliados - Дружественные стороны - Strana spojenců - Bandos amigos - Lati alleati - Factions amies - 友軍勢力 - 아군 진영 - 友军 - 友軍 - - - Hostile sides - Feinde - Strony wrogie - Lados hostis - Враждебные стороны - Strana nepřítele - Bandos enemigos - Lati nemici - Factions hostiles - 敵対勢力 - 적군 진영 - 敌方 - 敵方 - - - All sides - Alle Fraktionen - Wszystkie strony - Todos os lados - Все стороны - Všechny strany - Todos los bandos - Tutti i lati - Toutes les factions - 全ての勢力 - 모든 진영 - 所有阵营 - 所有陣營 + + Make AI viewable in spectator Camera modes @@ -236,7 +62,7 @@ 攝影機模式 - Camera modes that can be used. + Camera modes that can be used Verwendbare Kameramodi Tryby kamery, jakie mogą być używane. Modos de camera que podem ser utilizados @@ -265,43 +91,18 @@ 所有 所有 - - Free only - Nur freie Kamera - Tylko wolna - Somente livre - Только свободная - Pouze volná - Solo libre - Solo libera - Libre seulement - 自由視点のみ - 오직 자유만 - 只有自由模式 - 只有自由模式 - - - Internal only - Erste Person //Bitte überprüfen! - Tylko wewnętrznaSomente internaТолько внутренняяPouze pohled z první osobySolo internaSolo internaInterne seulement一人称視点のみ오직 내부만只有第一人称只有第一人稱 - - External only - Dritte Person //Bitte überpfüfen! - Tylko zewnętrznaSomente externaТолько внешняяPouze pohled z třetí osobySolo externaSolo esternaExterne seulement三人称視点のみ오직 외부만只有第三人称只有第三人稱 - Internal and external - Erste und dritte Person - Wewnętrzna i zewnętrzna - Interna e externa - Внутренняя и внешняя - Pohled z první a třetí osoby - Interna y externa - Interna ed Esterna - Interne et externe - 一人称と三人称視点 - 외부 및 내부 - 第一和第三人称 - 第一和第三人稱 + 1PP and 3PP + 1ère et 3e personne + 1.ª y 3.ª persona + 1° e 3° pers + 1 i 3 os. + первого а также третьего + 1PP und 3PP + 1. a 3. osoby + 1ª e 3ª pessoa + 1PP 과 3PP 카메라 + 1PP そして 3PP カメラ Vision modes @@ -319,7 +120,7 @@ 視覺模式 - Vision modes that can be used. + Vision modes that can be used Sichtmodi die verwendet werden können. Tryby wizji, jakie mogą być używane. Modos de visão que podem ser utilizados @@ -364,35 +165,18 @@ 熱成像 - - Spectator Units - Zuschauereinheiten - Jednostki obserwatora - Unidades espectadoras - Юниты - Unidades espectador - Jednotky pozorovatele - Unità Osservabili - Unités spectatrices - スペクテイター ユニット - 관전 인원 - 旁观者单位 - 旁觀者單位 - - - Spectator Controls - Zuschauersteuerung - Sterowanie obserwatorem - Controle do espectador - Управление спектатором - Controles de espectador - Ovládání pozorovatele - Controlli Osservatore - Contrôles de spectateur - スペクテイター操作 - 관전 조작 - 旁观者控制 - 旁觀者控制 + + Engineer + Pionier + Mechanik + Engenheiro + Инженер + Inženýr + Ingeniero + Geniere + Ingénieur + 専門兵 + 정비공 Free @@ -409,14 +193,6 @@ 自由模式 自由模式 - - Internal - Erste Person //Bitte überprüfen! - WewnętrznaInternaВнутренняяPohled z první osobyInternaInternaInterne一人称視点내부第一人称第一人稱 - - External - Dritte Person //Bitte überprüfen! - ZewnętrznaExternaВнешняяPohled z třetí osobyExternaEsternaExterne三人称視点외부第三人称第三人稱 Normal Normal @@ -462,336 +238,18 @@ 热成像 熱成像 + + Add Map Locations + + + Add map locations to the spectator UI + - - Free Camera - Freie Kamera - Kamera swobodna - Câmera livre - Свободная камера - Volná Kamera - Cámara libre - Camera Libera - Caméra libre - 自由視点 - 자유 카메라 - 自由摄影机 - 自由攝影機 + + Icons - - Camera Forward - Kamera vor - Kamera naprzód - Câmera para frente - Камера вперед - Vpřed (Kamera) - Cámara delantera - Camera Avanti - Caméra en avant - カメラを前に - 카메라 앞으로 - 摄影机往前 - 攝影機往前 - - - Camera Backward - Kamera zurück - Kamera w tył - Câmera para trás - Камера назад - Zpět (Kamera) - Cámara trasera - Camera Indietro - Caméra en arrière - カメラを後ろに - 카메라 뒤로 - 摄影机往后 - 攝影機往後 - - - Camera Left - Kamera links - Kamera w lewo - Câmera para esquerda - Камера влево - Doleva (Kamera) - Cámara izquierda - Camera Sinistra - Caméra à gauche - カメラを左に - 카메라 왼쪽으로 - 摄影机往左 - 攝影機往左 - - - Camera Right - Kamera rechts - Kamera w prawo - Câmera para direita - Камера вправо - Doprava (Kamera) - Cámara derecha - Camera Destra - Caméra à droite - カメラを右に - 카메라 오른쪽으로 - 摄影机往右 - 攝影機往右 - - - Camera Up - Kamera hoch - Kamera w górę - Câmera para cima - Камера вверх - Nahoru (Kamera) - Cámara arriba - Camera Su - Caméra en haut - カメラを上に - 카메라 위로 - 摄影机往上 - 攝影機往上 - - - Camera Down - Kamera runter - Kamera w dół - Câmera para baixo - Камера вниз - Dolů (Kamera) - Cámara abajo - Camera Giù - Caméra en bas - カメラを下に - 카메라 아래로 - 摄影机往下 - 攝影機往下 - - - Pan Camera - Kamera mitschwenken - Panoramowanie - Câmera panorâmica - Панорамирование - Cámara panorámica - Otáčet kameru - Camera Panoramica - Tourner la caméra - カメラを振る - 카메라 돌리기 - 平移摄影机 - 平移攝影機 - - - Dolly Camera - Kamerafahrt - Płynna kamera - Câmera dolly - Рельсовая камера - Cámara dolly - Posouvat kameru - Camera dolly - Bouger la caméra - カメラを動かす - 카메라 추적 - 移动摄影机 - 移動攝影機 - - - Lock Camera to Target - Kamera Ziel verfolgen - Zablokuj kamerę na celu - Travar câmera em alvo - Зафиксировать камеру на цели - Zamknout kameru na Cíl - Fijar cámara al objetivo - Blocca la camera su obbiettivo - Verrouiller la caméra sur la cible - カメラを目標に固定 - 목표에 카메라 고정 - 锁定摄影机观察单一目标 - 鎖定攝影機觀察單一目標 - - - Speed Boost - Geschwindigkeitserhöhung - Przyśpieszenie kamery - Aumento de velocidade - Ускорение камеры - Aumento de velocidad - Zrychlení kamery - Aumento Velocità - Boost de vitesse - 速度の増加 - 속도 증가 - 速度提升 - 速度提升 - - - Interface - Nuteroberfläche - Interfejs - Interface - Интерфейс - Rozhraní - Interfaz - Interfaccia - Interface - インターフェイス - 인터페이스 - 介面 - 介面 - - - Toggle Interface - Nutzeroberfläche umschalten - Przełącz interfejs - Alternar interface - Переключить интерфейс - Zobrazit/skrýt rozhraní - Conmutar - Apri Interfaccia - Bascule de l'interface - インターフェイスをトグル - 인터페이스 토글 - 切换介面 - 切換介面 - - - Toggle Unit Icons - Einheitensymbole umschalten - Przełącz ikony jednostek - Alternar ícone de unidades - Вкл./выкл. иконки юнитов - Zobrazit/skrýt ikony jednotek - Conmutar iconos de unidad - Apri Icone Unità - Bascule des icônes des unités - ユニット アイコンをトグル - 인원 아이콘 토글 - 切换单位图示 - 切換單位圖示 - - - Toggle Unit List - Einheitenliste umschalten - Przełącz listę jednostek - Alternar lista de unidades - Вкл./выкл. список юнитов - Zobrazit/skrýt seznam jednotek - Conmutar lista de unidades - Apri Lista Unità - Bascule de la liste des unités - ユニット一覧をトグル - 인원 목록 토글 - 切换单位名单 - 切換單位名單 - - - Toggle Toolbar - Werkzeuge umschalten - Przełącz pasek narzędzi - Alternar barra de ferramentas - Вкл./выкл. тулбар - Conmutar barra de herramientas - Zobrazit/skrýt spodní panel - Apri Barra degli Strumenti - Bascule de la barre d'outils - ツールバーをトグル - 툴바 토글 - 切换工具栏 - 切換工具欄 - - - Toggle Compass - Kompass umschalten - Przełącz kompas - Alternar bússola - Вкл./выкл. компас - Zobrazit/skrýt kompas - Conmutar brújula - Apri Bussola - Basculer le compas - 方位磁石をトグル - 나침반 토글 - 切换指北针 - 切換指北針 - - - Toggle Map - Karte umschalten - Przełącz mapę - Alternar mapa - Вкл./выкл. карту - Zobrazit/skrýt mapu - Conmutar map - Apri Mappa - Basculer la carte - 地図をトグル - 지도 토글 - 切换地图 - 切換地圖 - - - Toggle Help - Hilfe umschalten - Przełącz pomoc - Alternar ajuda - Вкл./выкл. помощь - Zobrazit/skrýt ovládání - Conmutar ayuda - Apri Aiuti - Basculer l'aide - ヘルプをトグル - 도움 토글 - 切换帮助 - 切換幫助 - - - Camera Attributes - Kameraeigenschaften - Atrybuty kamery - Atributos de câmera - Атрибуты камеры - Atributos de cámara - Atributy kamery - Attributi Camera - Propriétés de la caméra - カメラ高度 - 카메라 속성 - 摄影机属性 - 攝影機屬性 - - - Next Camera - Nächste Kamera - Następna kamera - Próxima câmera - Следующая камера - Následující kamera - Siguiente cámara - Prossima Camera - Caméra suivante - 次のカメラ - 다음 카메라 - 下个镜头 - 下個鏡頭 - - - Previous Camera - Vorherige Kamera - Poprzednia kamera - Câmera anterior - Предыдущая камера - Předchozí kamera - Anterior cámara - Precedente Camera - Caméra précédente - 前のカメラ - 이전 카메라 - 上个镜头 - 上個鏡頭 + + Projectiles Next Unit @@ -824,124 +282,10 @@ 上個單位 - Next Vision Mode - Nächster Sichtmodus - Następny tryb wizji - Próximo modo de visão - Следующий режим видения - Siguiente modo de visión - Následující mód zobrazení - Prossima Modalità Visiva - Mode de vision suivant - 次のビジョン モード - 다음 시야 모드 - 下个视觉模式 - 下個視覺模式 + Vision Mode - - Previous Vision Mode - Vorheriger Sichtmodus - Poprzedni tryb wizji - Modo de visão anterior - Предыдущий режим видения - Anterior modo de visión - Předchozí mód zobrazení - Precedente Modalità Visiva - Mode de vision précédent - 前のビジョン モード - 이전 시야 모드 - 上个视觉模式 - 上個視覺模式 - - - Adjust Zoom - Vergrößerung einstellen - Reguluj zoom - Ajustar zoom - Настроить зум - Regulovat přiblížení - Ajustar aumento - Aggiusta Zoom - Ajuster le zoom - 拡大倍率を調節 - 줌 조절 - 调整倍率 - 調整倍率 - - - Adjust Speed - Geschwindigkeit einstellen - Reguluj prędkość - Ajuster velocidade - Настроить скорость - Regulovat rychlost - Ajustar velocidad - Aggiusta Velocità - Ajuster la vitesse - 速度を調節 - 속도 조절 - 调整速度 - 調整速度 - - - Increment Zoom - Vergrößern - Reguluj zoom (krok) - Incrementar zoom - Увеличить зум - Incrementar aumento - Regulovat přiblížení (pomalu) - Aumenta Zoom - Augmenter le zoom - 拡大倍率を増やす - 줌 증가 - 增加放大 - 增加放大 - - - Increment Speed - Geschwindkeit erhöhen - Reguluj prędkość (krok) - Incrementar velocidade - Увеличить скорость - Incrementar velocidad - Regulovat rychlost (pomalu) - Aumenta Velocità - Augmenter la vitesse - 速度を増やす - 속도 증가 - 增加速度 - 增加速度 - - - Reset Zoom - Vergrößerung zurücksetzen - Resetuj zoom - Redefinir zoom - Сбросить зум - Obnovit přiblížení - Reiniciar aumento - Resetta Zoom - RAZ zoom - 拡大倍率を初期化 - 줌 초기화 - 重置缩放 - 重置縮放 - - - Reset Speed - Geschwindigkeit zurücksetzen - Resetuj prędkość - Redefinir velocidade - Сбросить скорость - Obnovit rychlost - Reiniciar velocidad - Resetta Velocità - RAZ vitesse - 速度を初期化 - 속도 초기화 - 重置速度 - 重置速度 + + Slow Speed diff --git a/addons/spectator/ui.hpp b/addons/spectator/ui.hpp new file mode 100644 index 0000000000..56b404619e --- /dev/null +++ b/addons/spectator/ui.hpp @@ -0,0 +1,450 @@ +class RscButton; +class RscControlsGroup; +class RscControlsGroupNoScrollbars; +class RscListNBox { + class ScrollBar; +}; +class RscMapControl; +class RscPicture; +class RscPictureKeepAspect; +class RscText; +class RscToolbox; +class RscTree; + +// Based on RscDisplayEGSpectator (sadly Arma doesn't like display inheritance) +class GVAR(display) { + idd = IDD_SPEC_DISPLAY; + enableSimulation = 1; + movingEnable = 0; + closeOnMissionEnd = 1; + + onKeyDown = QUOTE(_this call FUNC(ui_handleKeyDown)); + onKeyUp = QUOTE(_this call FUNC(ui_handleKeyUp)); + onMouseMoving = QUOTE(_this call FUNC(ui_handleMouseMoving)); + onChildDestroyed = QUOTE(_this call FUNC(ui_handleChildDestroyed)); + + class ControlsBackground { + class MouseHandler: RscText { + idc = IDC_MOUSE; + + onMouseButtonDown = QUOTE(_this call FUNC(ui_handleMouseButtonDown)); + onMouseButtonUp = QUOTE(if ((_this select 1) == 1) then { GVAR(holdingRMB) = false; };); + onMouseButtonDblClick = QUOTE(_this call FUNC(ui_handleMouseButtonDblClick)); + onMouseZChanged = QUOTE(_this call FUNC(ui_handleMouseZChanged)); + + text = ""; + x = "safeZoneXAbs"; + y = "safeZoneY"; + w = "safeZoneWAbs"; + h = "safeZoneH"; + colorBackground[] = {1,1,1,0}; + style = 16; + }; + }; + class Controls { + class List: RscTree { + idc = IDC_LIST; + + onMouseEnter = QUOTE([false] call FUNC(ui_fadeList)); + onMouseExit = QUOTE([true] call FUNC(ui_fadeList)); + onTreeSelChanged = QUOTE([ARR_2(false,_this)] call FUNC(ui_handleListClick)); + onTreeDblClick = QUOTE([ARR_2(true,_this)] call FUNC(ui_handleListClick)); + + x = "safeZoneX"; + y = safeZoneY + H_PART(1.5); + w = W_PART(13.5); + h = safeZoneH - H_PART(1.5); + + disableKeyboardSearch = 1; + multiselectEnabled = 0; + colorBorder[] = {0,0,0,0}; + colorBackground[] = {0,0,0,0.75}; + expandOnDoubleclick = 1; + fade = 0.8; + shadow = 1; + colorLines[] = {0,0,0,0}; + class ScrollBar { + width = 0; + height = 0; + scrollSpeed = 0.1; + color[] = {1,1,1,0}; + }; + }; + class Tabs: RscToolbox { + idc = IDC_TABS; + + onToolBoxSelChanged = QUOTE(_this call FUNC(ui_handleTabSelected)); + onMouseEnter = QUOTE([false] call FUNC(ui_fadeList)); + onMouseExit = QUOTE([true] call FUNC(ui_fadeList)); + + x = "safeZoneX"; + y = "safezoneY"; + w = W_PART(13.5); + h = H_PART(1.5); + + fade = 0.8; + rows = 1; + columns = 2; + strings[] = {"$STR_A3_Spectator_Entities","$STR_A3_Spectator_Locations"}; + values[] = {0,1}; + sizeEx = H_PART(1); + colorBackground[] = {0,0,0,0.75}; + colorSelectedBg[] = {0,0,0,0.65}; + }; + class CameraTypesGroup: RscControlsGroupNoScrollbars { + idc = IDC_CAM_TYPES; + x = X_PART(15.5); + y = safezoneY + safezoneH - H_PART(2.38); + w = W_PART(8.6); + h = 2.6; + class controls { + class CameraTypesBackground: RscText { + x = W_PART(0.6); + y = H_PART(0.4); + w = W_PART(7.5); + h = H_PART(2); + colorBackground[] = {0,0,0,0.75}; + }; + class Free: RscButton { + style = 48; + idc = IDC_FREE; + + onButtonClick = QUOTE([MODE_FREE] call FUNC(cam_setCameraMode)); + + x = W_PART(1.3); + y = H_PART(0.8); + w = W_PART(1.63); + h = H_PART(1.37); + + colorBackground[] = {0,0,0,0}; + colorBackgroundDisabled[] = {0,0,0,0}; + colorBackgroundActive[] = {0,0,0,0}; + colorFocused[] = {0,0,0,0}; + text = CAM_ICON_FREE; + tooltip = "$STR_A3_Spectator_free_camera_tooltip"; + }; + class Follow: RscButton { + style = 48; + idc = IDC_FOLLOW; + + onButtonClick = QUOTE([MODE_FOLLOW] call FUNC(cam_setCameraMode)); + + x = W_PART(3.6); + y = H_PART(0.8); + w = W_PART(1.63); + h = H_PART(1.37); + + colorBackground[] = {0,0,0,0}; + colorBackgroundDisabled[] = {0,0,0,0}; + colorBackgroundActive[] = {0,0,0,0}; + colorFocused[] = {0,0,0,0}; + text = CAM_ICON_FOLLOW; + tooltip = "$STR_A3_Spectator_3pp_camera_tooltip"; + }; + class Fps: RscButton { + style = 48; + idc = IDC_FPS; + + onButtonClick = QUOTE([MODE_FPS] call FUNC(cam_setCameraMode)); + + x = W_PART(5.8); + y = H_PART(0.8); + w = W_PART(1.63); + h = H_PART(1.37); + + colorBackground[] = {0,0,0,0}; + colorBackgroundDisabled[] = {0,0,0,0}; + colorBackgroundActive[] = {0,0,0,0}; + colorFocused[] = {0,0,0,0}; + text = CAM_ICON_FPS; + tooltip = "$STR_A3_Spectator_1pp_camera_tooltip"; + + }; + }; + }; + class MapGroup: RscControlsGroupNoScrollbars { + idc = IDC_MAP_GROUP; + x = 0; + y = 0.1; + w = 1; + h = 0.8; + class controls { + class MapHeader: RscText { + x = 0; + y = 0; + w = 1; + h = 0.05; + colorBackground[] = {0,0,0,0.75}; + }; + class MapFooter: RscText { + idc = IDC_MAP_FOOTER; + x = 0; + y = 0.75; + w = 1; + h = 0.05; + text = ""; + style = 2; + colorBackground[] = {0,0,0,0.75}; + sizeEx = H_PART(1); + }; + class GameTimeText: RscText { + idc = IDC_TIME; + x = 0.01; + y = 0.76; + w = 0.29; + h = 0.03; + text = "00:00:00"; + sizeEx = H_PART(1); + }; + class MapTitle: RscText { + idc = IDC_MAP_TITLE; + x = 0.01; + y = 0.01; + w = 0.69; + h = 0.03; + text = ""; + colorText[] = {1,1,1,1}; + sizeEx = H_PART(1); + }; + class SpectatorsCount: RscText { + idc = IDC_MAP_SPEC_NUM; + x = 0.97; + y = 0.01; + w = 0.03; + h = 0.03; + text = ""; + colorText[] = {1,1,1,1}; + sizeEx = H_PART(1); + }; + class SpectatorsIcon: RscPictureKeepAspect { + x = 0.94; + y = 0.01; + w = 0.03; + h = 0.03; + text = CAM_ICON_FPS_SELECTED; + }; + }; + }; + class Map: RscMapControl { + idc = IDC_MAP; + + onDraw = QUOTE(_this call FUNC(ui_handleMapDraw)); + onMouseButtonClick = QUOTE(_this call FUNC(ui_handleMapClick)); + + x = 0; + y = 0.15; + w = 1; + h = 0.7; + + maxSatelliteAlpha = 0.75; + colorBackground[] = {1,1,1,1}; + }; + class HelpBackground: RscText { + idc = IDC_HELP_BACK; + x = safezoneX + safezoneW - W_PART(12); + y = safezoneY + safezoneH - H_PART(8); + w = W_PART(12); + h = H_PART(8); + colorBackground[] = {0,0,0,0.75}; + }; + class Help: RscListNBox { + class ListScrollBar: ScrollBar {}; + disableOverflow = 0; + rowHeight = H_PART(1); + idc = IDC_HELP; + x = safezoneX + safezoneW - W_PART(12); + y = safezoneY + safezoneH - H_PART(12); + w = W_PART(12); + h = H_PART(12); + }; + class FocusInfo: RscControlsGroupNoScrollbars { + idc = IDC_WIDGET; + x = X_PART(12.1); + y = Y_PART(24); + w = W_PART(16); + h = H_PART(3.5); + class controls { + class UpperBackground: RscText { + x = W_PART(3.5); + y = 0; + w = W_PART(12.4); + h = H_PART(1.4); + colorBackground[] = {0,0,0,0.75}; + }; + class LowerLeftBackground: RscText { + idc = CTRL_WIDGET_WEAPON_BACK; + x = W_PART(9.8); + y = H_PART(1.5); + w = W_PART(6.1); + h = H_PART(2); + colorBackground[] = {1,1,1,0.4}; + }; + class LowerRightBackground: RscText { + x = W_PART(3.5); + y = H_PART(1.5); + w = W_PART(6.2); + h = H_PART(2); + colorBackground[] = {0,0,0,0.75}; + }; + class AvatarBackground: RscText { + x = W_PART(-0.2); + y = 0; + w = W_PART(3.6); + h = H_PART(3.5); + colorBackground[] = {0,0,0,0.75}; + }; + class VehicleType: RscPicture { + idc = IDC_WIDGET_VEHICLE; + text = "\A3\ui_f\data\map\vehicleicons\iconMan_ca.paa"; + x = W_PART(13.5); + y = H_PART(0.3); + w = W_PART(2.1); + h = H_PART(1); + }; + class UnitType: RscPictureKeepAspect { + idc = IDC_WIDGET_UNIT; + text = "\A3\ui_f\data\map\vehicleicons\iconMan_ca.paa"; + x = W_PART(14.6); + y = H_PART(0.3); + w = W_PART(1); + h = H_PART(1); + }; + class Name: RscText { + shadow = 0; + idc = IDC_WIDGET_NAME; + text = ""; + x = W_PART(3.6); + y = 0; + w = W_PART(9.9); + h = H_PART(1.4); + sizeEx = H_PART(1); + }; + class Avatar: RscPictureKeepAspect { + idc = IDC_WIDGET_AVATAR; + text = "a3\Ui_f\data\GUI\Cfg\UnitInsignia\bi_ca.paa"; + x = 0; + y = H_PART(0.3); + w = W_PART(3.2); + h = H_PART(2.9); + }; + class Kills: RscPictureKeepAspect { + text = "a3\Ui_f\data\IGUI\Cfg\MPTable\infantry_ca.paa"; + x = W_PART(3.6); + y = H_PART(1.6); + w = W_PART(0.8); + h = H_PART(0.8); + }; + class LandKills: RscPictureKeepAspect { + text = "a3\Ui_f\data\IGUI\Cfg\MPTable\soft_ca.paa"; + x = W_PART(4.64); + y = H_PART(1.6); + w = W_PART(0.8); + h = H_PART(0.8); + }; + class ArmoredKills: RscPictureKeepAspect { + text = "a3\Ui_f\data\IGUI\Cfg\MPTable\armored_ca.paa"; + x = W_PART(5.76); + y = H_PART(1.6); + w = W_PART(0.8); + h = H_PART(0.8); + }; + class AirKills: RscPictureKeepAspect { + text = "a3\Ui_f\data\IGUI\Cfg\MPTable\air_ca.paa"; + x = W_PART(6.9); + y = H_PART(1.6); + w = W_PART(0.8); + h = H_PART(0.8); + }; + class Deaths: RscPictureKeepAspect { + text = "a3\Ui_f\data\IGUI\Cfg\MPTable\killed_ca.paa"; + x = W_PART(7.92); + y = H_PART(1.6); + w = W_PART(0.8); + h = H_PART(0.8); + }; + class Total: RscPictureKeepAspect { + text = "a3\Ui_f\data\IGUI\Cfg\MPTable\total_ca.paa"; + x = W_PART(8.86); + y = H_PART(1.6); + w = W_PART(0.8); + h = H_PART(0.8); + }; + class Kills_Count: RscText { + style = 2; + shadow = 0; + idc = IDC_WIDGET_KILLS; + text = ""; + x = W_PART(3.6); + y = H_PART(2.5); + w = W_PART(0.9); + h = H_PART(0.9); + sizeEx = H_PART(0.7); + }; + class LandKills_Count: RscText { + style = 2; + shadow = 0; + idc = IDC_WIDGET_LAND; + text = ""; + x = W_PART(4.6); + y = H_PART(2.5); + w = W_PART(0.8); + h = H_PART(0.9); + sizeEx = H_PART(0.7); + }; + class ArmoredKills_Count: RscText { + style = 2; + shadow = 0; + idc = IDC_WIDGET_ARMORED; + text = ""; + x = W_PART(5.7); + y = H_PART(2.5); + w = W_PART(0.8); + h = H_PART(0.9); + sizeEx = H_PART(0.7); + }; + class AirKills_Count: RscText { + style = 2; + shadow = 0; + idc = IDC_WIDGET_AIR; + text = ""; + x = W_PART(6.8); + y = H_PART(2.5); + w = W_PART(0.8); + h = H_PART(0.9); + sizeEx = H_PART(0.7); + }; + class Deaths_Count: RscText { + style = 2; + shadow = 0; + idc = IDC_WIDGET_DEATHS; + text = ""; + x = W_PART(7.9); + y = H_PART(2.5); + w = W_PART(0.8); + h = H_PART(0.9); + sizeEx = H_PART(0.7); + }; + class Total_Count: RscText { + style = 2; + shadow = 0; + idc = IDC_WIDGET_TOTAL; + text = ""; + x = W_PART(8.8); + y = H_PART(2.5); + w = W_PART(0.8); + h = H_PART(0.9); + sizeEx = H_PART(0.7); + }; + class WeaponPicture: RscPictureKeepAspect { + idc = IDC_WIDGET_WEAPON; + text = "A3\weapons_F\Rifles\MX\data\UI\gear_mx_rifle_X_CA.paa"; + x = W_PART(9.9); + y = H_PART(1.6); + w = W_PART(5.9); + h = H_PART(1.8); + }; + }; + }; + }; +}; +