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 0000000000..eeb69e354a Binary files /dev/null and b/addons/spectator/data/b_air.paa differ diff --git a/addons/spectator/data/b_armor.paa b/addons/spectator/data/b_armor.paa new file mode 100644 index 0000000000..8e3922f0b1 Binary files /dev/null and b/addons/spectator/data/b_armor.paa differ diff --git a/addons/spectator/data/b_art.paa b/addons/spectator/data/b_art.paa new file mode 100644 index 0000000000..0aac8b828e Binary files /dev/null and b/addons/spectator/data/b_art.paa differ diff --git a/addons/spectator/data/b_inf.paa b/addons/spectator/data/b_inf.paa new file mode 100644 index 0000000000..235c2080c3 Binary files /dev/null and b/addons/spectator/data/b_inf.paa differ diff --git a/addons/spectator/data/b_installation.paa b/addons/spectator/data/b_installation.paa new file mode 100644 index 0000000000..3ce55f2d36 Binary files /dev/null and b/addons/spectator/data/b_installation.paa differ diff --git a/addons/spectator/data/b_maint.paa b/addons/spectator/data/b_maint.paa new file mode 100644 index 0000000000..53b969a731 Binary files /dev/null and b/addons/spectator/data/b_maint.paa differ diff --git a/addons/spectator/data/b_mech_inf.paa b/addons/spectator/data/b_mech_inf.paa new file mode 100644 index 0000000000..967cbe7ee1 Binary files /dev/null and b/addons/spectator/data/b_mech_inf.paa differ diff --git a/addons/spectator/data/b_med.paa b/addons/spectator/data/b_med.paa new file mode 100644 index 0000000000..82bccdef5e Binary files /dev/null and b/addons/spectator/data/b_med.paa differ diff --git a/addons/spectator/data/b_mortar.paa b/addons/spectator/data/b_mortar.paa new file mode 100644 index 0000000000..331bdcbcb0 Binary files /dev/null and b/addons/spectator/data/b_mortar.paa differ diff --git a/addons/spectator/data/b_motor_inf.paa b/addons/spectator/data/b_motor_inf.paa new file mode 100644 index 0000000000..d78ddfc9d6 Binary files /dev/null and b/addons/spectator/data/b_motor_inf.paa differ diff --git a/addons/spectator/data/b_naval.paa b/addons/spectator/data/b_naval.paa new file mode 100644 index 0000000000..812355fbc2 Binary files /dev/null and b/addons/spectator/data/b_naval.paa differ diff --git a/addons/spectator/data/b_plane.paa b/addons/spectator/data/b_plane.paa new file mode 100644 index 0000000000..d7be073299 Binary files /dev/null and b/addons/spectator/data/b_plane.paa differ diff --git a/addons/spectator/data/b_recon.paa b/addons/spectator/data/b_recon.paa new file mode 100644 index 0000000000..5c5dc8bb3d Binary files /dev/null and b/addons/spectator/data/b_recon.paa differ diff --git a/addons/spectator/data/b_service.paa b/addons/spectator/data/b_service.paa new file mode 100644 index 0000000000..3c74ca018c Binary files /dev/null and b/addons/spectator/data/b_service.paa differ diff --git a/addons/spectator/data/b_support.paa b/addons/spectator/data/b_support.paa new file mode 100644 index 0000000000..56d3773b93 Binary files /dev/null and b/addons/spectator/data/b_support.paa differ diff --git a/addons/spectator/data/b_uav.paa b/addons/spectator/data/b_uav.paa new file mode 100644 index 0000000000..e900cc2509 Binary files /dev/null and b/addons/spectator/data/b_uav.paa differ diff --git a/addons/spectator/data/b_unknown.paa b/addons/spectator/data/b_unknown.paa new file mode 100644 index 0000000000..f92ffbe9cb Binary files /dev/null and b/addons/spectator/data/b_unknown.paa differ diff --git a/addons/spectator/data/c_air.paa b/addons/spectator/data/c_air.paa new file mode 100644 index 0000000000..4170013fe2 Binary files /dev/null and b/addons/spectator/data/c_air.paa differ diff --git a/addons/spectator/data/c_car.paa b/addons/spectator/data/c_car.paa new file mode 100644 index 0000000000..832e17cbd6 Binary files /dev/null and b/addons/spectator/data/c_car.paa differ diff --git a/addons/spectator/data/c_plane.paa b/addons/spectator/data/c_plane.paa new file mode 100644 index 0000000000..d12fa68fbe Binary files /dev/null and b/addons/spectator/data/c_plane.paa differ diff --git a/addons/spectator/data/c_ship.paa b/addons/spectator/data/c_ship.paa new file mode 100644 index 0000000000..f53701175a Binary files /dev/null and b/addons/spectator/data/c_ship.paa differ diff --git a/addons/spectator/data/c_unknown.paa b/addons/spectator/data/c_unknown.paa new file mode 100644 index 0000000000..f67a3b5444 Binary files /dev/null and b/addons/spectator/data/c_unknown.paa differ 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); + }; + }; + }; + }; +}; +