Spectator overhaul (#5171)

- Overhauls the spectator module entirely to share a similar UX to BI's "End Game Spectator" while maintaining some of the extended flexibility of ACE Spectator.
- Simplifies spectator setup by reducing the number of settings. More advanced setup is still possible via the API functions provided.
This commit is contained in:
SilentSpike 2017-08-12 14:25:48 +01:00 committed by GitHub
parent 606e9c088d
commit d3ce75daef
103 changed files with 4083 additions and 2561 deletions

View File

@ -77,6 +77,7 @@ PREP(getTurretDirection);
PREP(getUavControlPosition); PREP(getUavControlPosition);
PREP(getVehicleCargo); PREP(getVehicleCargo);
PREP(getVehicleCodriver); PREP(getVehicleCodriver);
PREP(getVehicleIcon);
PREP(getVersion); PREP(getVersion);
PREP(getWeaponAzimuthAndInclination); PREP(getWeaponAzimuthAndInclination);
PREP(getWeaponIndex); PREP(getWeaponIndex);
@ -99,6 +100,7 @@ PREP(isEngineer);
PREP(isEOD); PREP(isEOD);
PREP(isFeatureCameraActive); PREP(isFeatureCameraActive);
PREP(isInBuilding); PREP(isInBuilding);
PREP(isMedic);
PREP(isModLoaded); PREP(isModLoaded);
PREP(isPlayer); PREP(isPlayer);
PREP(isUnderwater); PREP(isUnderwater);

View File

@ -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 <OBJECT/STRING>
*
* Return Value:
* Icon of vehicle <STRING>
*
* 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

View File

@ -0,0 +1,23 @@
/*
* Author: SilentSpike
* Check if a unit is a medic
*
* Arguments:
* 0: The Unit <OBJECT>
*
* ReturnValue:
* Unit is medic <BOOL>
*
* 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

View File

@ -1,24 +1,16 @@
class ACE_Settings { class ACE_Settings {
class GVAR(filterUnits) { class GVAR(enableAI) {
displayName = CSTRING(units_DisplayName); displayName = CSTRING(ai_DisplayName);
description = CSTRING(units_Description); description = CSTRING(ai_Description);
typeName = "SCALAR"; typeName = "BOOL";
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";
value = 0; value = 0;
values[] = {CSTRING(sides_player), CSTRING(sides_friendly), CSTRING(sides_hostile), CSTRING(sides_all)};
}; };
class GVAR(restrictModes) { class GVAR(restrictModes) {
displayName = CSTRING(modes_DisplayName); displayName = CSTRING(modes_DisplayName);
description = CSTRING(modes_Description); description = CSTRING(modes_Description);
typeName = "SCALAR"; typeName = "SCALAR";
value = 0; 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) { class GVAR(restrictVisions) {
displayName = CSTRING(visions_DisplayName); displayName = CSTRING(visions_DisplayName);
@ -27,4 +19,10 @@ class ACE_Settings {
value = 0; value = 0;
values[] = {CSTRING(modes_all), CSTRING(visions_nv), CSTRING(visions_ti), "$STR_Special_None"}; 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;
};
}; };

View File

@ -16,3 +16,15 @@ class Extended_PostInit_EventHandlers {
init = QUOTE(call COMPILE_FILE(XEH_postInit)); 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));
};
};

View File

@ -3,59 +3,17 @@ class CfgVehicles {
class GVAR(moduleSettings): ACE_Module { class GVAR(moduleSettings): ACE_Module {
scope = 2; scope = 2;
displayName = CSTRING(Settings_DisplayName); displayName = CSTRING(Settings_DisplayName);
icon = QPATHTOF(UI\Icon_Module_Spectator_ca.paa); icon = QPATHTOF(data\Icon_Module_Spectator_ca.paa);
category = "ACE"; category = "ACE";
function = QFUNC(moduleSpectatorSettings); function = QFUNC(moduleSpectatorSettings);
isGlobal = 1; isGlobal = 1;
author = ECSTRING(common,ACETeam); author = ECSTRING(common,ACETeam);
class Arguments { class Arguments {
class unitsFilter { class enableAI {
displayName = CSTRING(units_DisplayName); displayName = CSTRING(ai_DisplayName);
description = CSTRING(units_Description); description = CSTRING(ai_Description);
typeName = "NUMBER"; typeName = "BOOL";
class values { defaultValue = 0;
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 cameraModes { class cameraModes {
displayName = CSTRING(modes_DisplayName); displayName = CSTRING(modes_DisplayName);
@ -72,15 +30,15 @@ class CfgVehicles {
value = 1; value = 1;
}; };
class free { class free {
name = CSTRING(modes_free); name = "$STR_A3_Spectator_free_camera_tooltip";
value = 2; value = 2;
}; };
class internal { class internal {
name = CSTRING(modes_internal); name = "$STR_A3_Spectator_1pp_camera_tooltip";
value = 3; value = 3;
}; };
class external { class external {
name = CSTRING(modes_external); name = "$STR_A3_Spectator_3pp_camera_tooltip";
value = 4; value = 4;
}; };
}; };
@ -109,9 +67,28 @@ class CfgVehicles {
}; };
}; };
}; };
class mapLocations {
displayName = CSTRING(mapLocations_DisplayName);
description = CSTRING(mapLocations_Description);
typeName = "BOOL";
defaultValue = 0;
};
}; };
class ModuleDescription { class ModuleDescription {
description = CSTRING(Settings_Description); 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;
};
}; };

View File

@ -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));
};
};
};

View File

@ -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); // UI functions
PREP(cycleCamera); PREP(ui);
PREP(handleCamera); PREP(ui_draw3D);
PREP(handleCompass); PREP(ui_fadeList);
PREP(handleIcons); PREP(ui_getTreeDataIndex);
PREP(handleInterface); PREP(ui_handleChildDestroyed);
PREP(handleMap); PREP(ui_handleKeyDown);
PREP(handleMouse); PREP(ui_handleKeyUp);
PREP(handleToolbar); PREP(ui_handleListClick);
PREP(handleUnits); PREP(ui_handleMapClick);
PREP(interrupt); 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(moduleSpectatorSettings);
PREP(respawnTemplate); PREP(respawnTemplate);
PREP(setFocus);
PREP(stageSpectator);
PREP(switchFocus);
// Public functions
PREP(addLocation);
PREP(getCameraAttributes);
PREP(players);
PREP(removeLocation);
PREP(setCameraAttributes); PREP(setCameraAttributes);
PREP(setSpectator); PREP(setSpectator);
PREP(stageSpectator);
PREP(transitionCamera);
PREP(toggleInterface);
PREP(updateCameraModes); PREP(updateCameraModes);
PREP(updateSpectatableSides); PREP(updateSides);
PREP(updateUnits); PREP(updateUnits);
PREP(updateVisionModes); PREP(updateVisionModes);
// Deprecated (temp)
PREP(interrupt);
DFUNC(updateSpectatableSides) = {
ACE_DEPRECATED(QFUNC(updateSpectatableSides),"3.12.0",QFUNC(updateSides));
_this call FUNC(updateSides);
};

View File

@ -1,31 +1,51 @@
#include "script_component.hpp" #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", { ["ace_settingsInitialized", {
GVAR(availableModes) = [[0,1,2], [1,2], [0], [1], [2]] select GVAR(restrictModes); 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); 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 (GVAR(mapLocations)) then {
if (isServer) then { private _worldWidth = worldSize / 2;
GVAR(channel) = radioChannelCreate [[0.729,0.149,0.098,1],"Spectator","Spectator (%UNIT_NAME)",[]]; {
publicVariable QGVAR(channel); [locationPosition _x, [text _x] call CBA_fnc_capitalize] call FUNC(addLocation);
}; } forEach nearestLocations [
[_worldWidth, _worldWidth],
// Should prevent unending spectator on mission end ["NameVillage", "NameCity", "NameCityCapital", "NameLocal", "NameMarine"],
if (isServer) then { _worldWidth * sqrt 2
addMissionEventHandler ["Ended", { ];
[QGVAR(endMission), []] call CBA_fnc_globalEvent;
}];
};
[QGVAR(endMission), {
if (GVAR(isSet)) then {
[false] call FUNC(setSpectator);
}; };
}] call CBA_fnc_addEventHandler; }] 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; [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;

View File

@ -6,39 +6,17 @@ PREP_RECOMPILE_START;
#include "XEH_PREP.hpp" #include "XEH_PREP.hpp"
PREP_RECOMPILE_END; PREP_RECOMPILE_END;
// Reset the stored display // Used by public functions
SETUVAR(GVAR(interface),displayNull); GVAR(availableModes) = [MODE_FREE, MODE_FPS, MODE_FOLLOW];
// Permanent variables
GVAR(availableModes) = [0,1,2];
GVAR(availableSides) = [west,east,resistance,civilian]; GVAR(availableSides) = [west,east,resistance,civilian];
GVAR(availableVisions) = [-2,-1,0,1]; GVAR(availableVisions) = [VISION_NORM,VISION_NVG,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(interrupts) = []; GVAR(interrupts) = [];
GVAR(isSet) = false; GVAR(locationCount) = 0;
GVAR(locationsList) = [];
GVAR(showComp) = true;
GVAR(showHelp) = true;
GVAR(showIcons) = true;
GVAR(showInterface) = true;
GVAR(showMap) = false;
GVAR(showTool) = true;
GVAR(showUnit) = true;
GVAR(unitList) = [];
GVAR(unitBlacklist) = []; GVAR(unitBlacklist) = [];
GVAR(unitWhitelist) = []; GVAR(unitWhitelist) = [];
GVAR(groupList) = [];
// Tracks whether spectator is active
GVAR(isSet) = false;
ADDON = true; ADDON = true;

View File

@ -17,13 +17,13 @@ class CfgPatches {
#include "ACE_Settings.hpp" #include "ACE_Settings.hpp"
#include "CfgEventHandlers.hpp" #include "CfgEventHandlers.hpp"
#include "CfgVehicles.hpp" #include "CfgVehicles.hpp"
#include "ui\interface.hpp" #include "ui.hpp"
class CfgRespawnTemplates { class CfgRespawnTemplates {
class ADDON { class ADDON {
displayName = CSTRING(DisplayName); displayName = CSTRING(DisplayName);
onPlayerKilled = QFUNC(respawnTemplate); onPlayerKilled = QFUNC(respawnTemplate);
onPlayerRespawn = QFUNC(respawnTemplate); onPlayerRespawn = QFUNC(respawnTemplate);
respawnTypes[] = {2,3}; respawnTypes[] = {1,2,3,4,5};
}; };
}; };

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,73 @@
/*
* Author: Nelson Duarte, SilentSpike
* Add a location that can be seen in spectator view. Local effect.
*
* Arguments:
* 0: Position <ARRAY or OBJECT>
* 1: Display Name <STRING> (Default: "")
* 2: Description <STRING> (Default: "")
* 3: Texture <STRING> (Default: "")
* 4: Camera Offset Vector <Array> (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) <STRING>
*
* 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

View File

@ -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 <OBJECT>
*
* Return Value:
* None <NIL>
*
* 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);

View File

@ -0,0 +1,143 @@
/*
* Author: Nelson Duarte, SilentSpike
* Handles camera initialisation and destruction
*
* Arguments:
* 0: Init/Terminate <BOOL>
*
* 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;
};

View File

@ -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 <OBJECT>
*
* 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];
};

View File

@ -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;
};

View File

@ -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 <NUMBER>
*
* 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);
};
};

View File

@ -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;
};

View File

@ -0,0 +1,45 @@
/*
* Author: SilentSpike
* Function used to select the camera vision mode
*
* Arguments:
* 0: New vision mode <NUMBER>
*
* 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;
};
};

View File

@ -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);

View File

@ -0,0 +1,36 @@
/*
* Author: Nelson Duarte, SilentSpike
* Function used to set camera slow speed mode
*
* Arguments:
* 0: Enable slow speed <BOOL>
*
* 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;
};
};

View File

@ -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 <DISPLAY>
*
* 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];

View File

@ -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 <DISPLAY>
*
* 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;

View File

@ -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 <DISPLAY>
*
* 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;
};
}];

View File

@ -1,58 +0,0 @@
/*
* Author: SilentSpike
* Cycle through the spectator camera vision/view/units in steps
*
* Arguments:
* 0: Camera mode steps <NUMBER>
* 1: Camera unit steps <NUMBER>
* 2: Vision mode steps <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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);

View File

@ -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] <ARRAY>
*
* 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)
]
};

View File

@ -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 <GROUP>
* 1: Return icons for draw3D use <BOOL> (Default: false)
*
* Return Value:
* Icon of group <STRING>
*
* 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

View File

@ -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 <BOOL>
*
* Return Value:
* Valid entities <ARRAY>
*
* 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

View File

@ -1,75 +0,0 @@
/*
* Author: F3 Project, Head, SilentSpike
* Handles free camera manipulation according to input
*
* Arguments:
* 0: Parameters <ANY>
* 1: PFH handle <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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;
};

View File

@ -1,60 +0,0 @@
/*
* Author: SilentSpike, voiper
* Handles the spectator UI compass
*
* Arguments:
* 0: Parameters <ANY>
* 1: PFH handle <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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;

View File

@ -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]]]];
};
};

View File

@ -1,46 +0,0 @@
/*
* Author: Head, SilentSpike
* Handles rendering the spectator 3D unit icons
*
* Arguments:
* 0: Parameters <ANY>
* 1: PFH handle <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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));

View File

@ -1,494 +0,0 @@
/*
* Author: SilentSpike
* Handles spectator interface events
*
* Arguments:
* 0: Event name <STRING>
* 1: Event arguments <ANY>
*
* Return Value:
* None <NIL>
*
* 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
};
};

View File

@ -1,67 +0,0 @@
/*
* Author: Head, SilentSpike
* Handles rendering the spectator map icons
*
* Arguments:
* 0: Parameters <ANY>
* 1: PFH handle <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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));

View File

@ -1,46 +0,0 @@
/*
* Author: F3 Project, Head, SilentSpike
* Processes the change in mouse position for the spectator camera
*
* Arguments:
* 0: Mouse x coord <NUMBER>
* 1: Mouse y coord <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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];

View File

@ -1,59 +0,0 @@
/*
* Author: Karel Moricky, SilentSpike
* Handles the spectator UI toolbar values
*
* Arguments:
* 0: Parameters <ANY>
* 1: PFH handle <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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;

View File

@ -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 <ANY>
* 1: PFH handle <NUMBER>
*
* Return Value:
* None <NIL>
*
* 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;
};

View File

@ -1,27 +1,17 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Interrupts the spectator interface for external systems * Deprecated. Technically never publically documented, but just in case.
*
* Arguments:
* 0: Reason <STRING>
* 1: Interrupting <BOOL> (default: true)
*
* Return Value:
* None <NIL>
*
* Example:
* ["mySystem"] call ace_spectator_fnc_interrupt
*
* Public: Yes
*/ */
#include "script_component.hpp" #include "script_component.hpp"
params [["_reason", "", [""]], ["_interrupt", true, [true]]]; params [["_reason", "", [""]], ["_interrupt", true, [true]]];
ACE_DEPRECATED(QFUNC(interrupt),"3.12.0","just close and reopen spectator");
// Nothing to do when spectator is closed // Nothing to do when spectator is closed
if !(GVAR(isSet)) exitWith {}; if !(GVAR(isSet)) exitWith {};
if (_reason == "") exitWith { ERROR("Invalid Reason"); }; if (_reason == "") exitWith { WARNING("Invalid Reason"); };
if (_interrupt) then { if (_interrupt) then {
GVAR(interrupts) pushBack _reason; GVAR(interrupts) pushBack _reason;
} else { } else {
@ -29,16 +19,11 @@ if (_interrupt) then {
}; };
if (GVAR(interrupts) isEqualTo []) then { if (GVAR(interrupts) isEqualTo []) then {
if (isNull (GETUVAR(GVAR(interface),displayNull))) then { if (isNull SPEC_DISPLAY) then {
(findDisplay 46) createDisplay QGVAR(interface); [true] call FUNC(ui);
[] call FUNC(transitionCamera);
}; };
} else { } else {
if !(isNull (GETUVAR(GVAR(interface),displayNull))) then { if !(isNull SPEC_DISPLAY) then {
while {dialog} do { [false] call FUNC(ui);
closeDialog 0;
};
(GETUVAR(GVAR(interface),displayNull)) closeDisplay 0;
}; };
}; };

View File

@ -8,7 +8,7 @@
* 2: activated <BOOL> * 2: activated <BOOL>
* *
* Return Value: * Return Value:
* None <NIL> * None
* *
* Example: * Example:
* [LOGIC, [bob, kevin], true] call ace_spectator_fnc_moduleSpectatorSettings * [LOGIC, [bob, kevin], true] call ace_spectator_fnc_moduleSpectatorSettings
@ -22,7 +22,7 @@ params ["_logic", "_units", "_activated"];
if !(_activated) exitWith {}; if !(_activated) exitWith {};
[_logic, QGVAR(filterUnits), "unitsFilter"] call EFUNC(common,readSettingFromModule); [_logic, QGVAR(enableAI), "enableAI"] call EFUNC(common,readSettingFromModule);
[_logic, QGVAR(filterSides), "sidesFilter"] call EFUNC(common,readSettingFromModule);
[_logic, QGVAR(restrictModes), "cameraModes"] call EFUNC(common,readSettingFromModule); [_logic, QGVAR(restrictModes), "cameraModes"] call EFUNC(common,readSettingFromModule);
[_logic, QGVAR(restrictVisions), "visionModes"] call EFUNC(common,readSettingFromModule); [_logic, QGVAR(restrictVisions), "visionModes"] call EFUNC(common,readSettingFromModule);
[_logic, QGVAR(mapLocations), "mapLocations"] call EFUNC(common,readSettingFromModule);

View File

@ -0,0 +1,19 @@
/*
* Author: SilentSpike
* Return all of the player entities who are currently in ace spectator
*
* Arguments:
* None
*
* Return Value:
* Spectator Players <ARRAY>
*
* Example:
* [] call ace_spectator_fnc_players
*
* Public: Yes
*/
#include "script_component.hpp"
allPlayers select { GETVAR(_x,GVAR(isSet),false) }

View File

@ -0,0 +1,39 @@
/*
* Author: Nelson Duarte, SilentSpike
* Remove a location that can be seen in spectator view. Local effect.
*
* Arguments:
* 0: Unique ID <STRING>
*
* Return Value:
* Success <BOOL>
*
* 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

View File

@ -1,7 +1,8 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* The ace_spectator respawn template, handles killed + respawn * The ace_spectator respawn template, compatible with types 1,2,3,4 & 5
* Can be used via BI's respawn framework, see: *
* Handles killed and respawned events as per BI's respawn framework:
* https://community.bistudio.com/wiki/Arma_3_Respawn * https://community.bistudio.com/wiki/Arma_3_Respawn
* *
* Arguments: * Arguments:
@ -21,26 +22,18 @@
#include "script_component.hpp" #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 // Compatibility handled via spectator display XEH
if (isNull _killer) then {_killer = _unit}; if (_respawn in [0,1,4,5]) exitWith {
private _vision = [-2,-1] select (sunOrMoon < 1); // This only applies to respawn type 1 (BIRD/SPECTATOR)
private _pos = (getPosATL _unit) vectorAdd [0,0,5]; // Remove the seagull (not actually the player, a CfgNonAIVehicles object)
if (typeOf _newCorpse == "seagull") then { deleteVehicle _newCorpse; };
// Enter/exit spectator based on the respawn type and whether killed/respawned };
if (alive _unit) then {
if (_respawn == 1) then { // If player died while already in spectator, ignore
[_unit] call FUNC(stageSpectator); if (!GVAR(isSet) || {alive _newCorpse}) then {
[2,_killer,_vision,_pos,getDir _unit] call FUNC(setCameraAttributes); // Negligible respawn delay can result in entering spectator after respawn
[true] call FUNC(setSpectator); // So we just use this value rather than living state of the unit
} else { [playerRespawnTime > 1] call FUNC(setSpectator);
[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);
}; };

View File

@ -1,27 +1,30 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Sets the spectator camera attributes as desired * Sets the spectator camera attributes as desired. Local effect.
* All values are optional and default to whatever the current value is * All values are optional and default to no change.
* *
* Arguments: * Arguments:
* 0: Camera mode <NUMBER> * 0: Camera mode <NUMBER>
* - 0: Free * - 0: Free
* - 1: Internal * - 1: First Person
* - 2: External * - 2: Follow
* 1: Camera unit (objNull for random) <OBJECT> * 1: Camera focus <OBJECT or BOOL>
* 2: Camera vision <NUMBER> * 2: Camera vision <NUMBER>
* - -2: Normal * - -2: Normal
* - -1: Night vision * - -1: Night vision
* - 0: Thermal white hot * - 0: Thermal white hot
* - 1: Thermal black hot * - 1: Thermal black hot
* - ...
* 3: Camera position (ATL) <ARRAY> * 3: Camera position (ATL) <ARRAY>
* 4: Camera pan (0 - 360) <NUMBER> * 4: Camera direction (0 - 360) <NUMBER>
* 5: Camera tilt (-90 - 90) <NUMBER> *
* 6: Camera zoom (0.01 - 2) <NUMBER> * Notes:
* 7: Camera speed in m/s (0.05 - 10) <NUMBER> * - 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: * Return Value:
* None <NIL> * None
* *
* Example: * Example:
* [1, objNull] call ace_spectator_fnc_setCameraAttributes * [1, objNull] call ace_spectator_fnc_setCameraAttributes
@ -32,37 +35,68 @@
#include "script_component.hpp" #include "script_component.hpp"
params [ params [
["_mode",GVAR(camMode),[0]], ["_mode",nil,[0]],
["_unit",GVAR(camUnit),[objNull]], ["_focus",nil,[objNull,true]],
["_vision",GVAR(camVision),[0]], ["_vision",nil,[0]],
["_position",ASLtoATL GVAR(camPos),[[]],3], ["_position",nil,[[]],3],
["_heading",GVAR(camPan),[0]], ["_direction",nil,[0]]
["_tilt",GVAR(camTilt),[0]],
["_zoom",GVAR(camZoom),[0]],
["_speed",GVAR(camSpeed),[0]]
]; ];
// Normalize input if (count _this > 5) then {
if !(_mode in GVAR(availableModes)) then { ACE_DEPRECATED("Use of ""tilt"", ""zoom"" and ""speed"" camera attributes","3.12.0","N/A")
_mode = GVAR(availableModes) select ((GVAR(availableModes) find GVAR(camMode)) max 0);
}; };
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 // Apply if camera exists
if (GVAR(isSet)) then { if !(isNil QGVAR(camera)) then {
GVAR(camPos) = (ATLtoASL _position); // Camera position will be updated in FUNC(handleCamera) // These functions are smart and handle unavailable inputs
[_mode,_unit,_vision] call FUNC(transitionCamera); 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 { } else {
GVAR(camMode) = _mode; if !(isNil "_focus") then {
GVAR(camPos) = (ATLtoASL _position); // 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;
};
}; };

View File

@ -0,0 +1,68 @@
/*
* Author: AACO, SilentSpike
* Function used to set the camera focus
*
* Arguments:
* 0: New focus <OBJECT>
* 1: Focus is a location <BOOL>
*
* 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);
};
};

View File

@ -1,17 +1,17 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Sets local client to the given spectator state (virtually) * Enter/exit spectator mode for the local player
* To physically handle a spectator see ace_spectator_fnc_stageSpectator
* *
* Client will be able to communicate in ACRE/TFAR as appropriate * 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: * Arguments:
* 0: Spectator state of local client <BOOL> (default: true) * 0: Spectator state of local client <BOOL> (default: true)
* 1: Force interface <BOOL> (default: true) * 1: Force interface <BOOL> (default: true)
* 2: Hide player (if alive) <BOOL> (default: true)
* *
* Return Value: * Return Value:
* None <NIL> * None
* *
* Example: * Example:
* [true] call ace_spectator_fnc_setSpectator * [true] call ace_spectator_fnc_setSpectator
@ -21,47 +21,43 @@
#include "script_component.hpp" #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 // Only clients can be spectators
if !(hasInterface) exitWith {}; 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 {}; 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 { if (["ace_hearing"] call EFUNC(common,isModLoaded)) then {
EGVAR(hearing,disableVolumeUpdate) = _set; EGVAR(hearing,disableVolumeUpdate) = _set;
EGVAR(hearing,deafnessDV) = 0; 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 (["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 (["task_force_radio"] call EFUNC(common,isModLoaded)) then {[player, _set] call TFAR_fnc_forceSpectator};
if (_set) then { if (_set) then {
// Initalize camera variables // Initalize the camera
GVAR(camBoom) = 0; [true] call FUNC(cam);
GVAR(camDolly) = [0,0];
GVAR(camGun) = false;
// Initalize display variables // Create the display when main display is ready
GVAR(ctrlKey) = false; [{ !isNull MAIN_DISPLAY },{ [true] call FUNC(ui) }] call CBA_fnc_waitUntilAndExecute;
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);
// Cache current channel to switch back to on exit // Cache current channel to switch back to on exit
GVAR(channelCache) = currentChannel; GVAR(channelCache) = currentChannel;
@ -70,35 +66,6 @@ if (_set) then {
GVAR(channel) radioChannelAdd [player]; GVAR(channel) radioChannelAdd [player];
setCurrentChannel (5 + GVAR(channel)); 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 // Cache and disable nametag settings
if (["ace_nametags"] call EFUNC(common,isModLoaded)) then { if (["ace_nametags"] call EFUNC(common,isModLoaded)) then {
GVAR(nametagSettingCache) = [EGVAR(nametags,showPlayerNames), EGVAR(nametags,showNamesForAI)]; GVAR(nametagSettingCache) = [EGVAR(nametags,showPlayerNames), EGVAR(nametags,showNamesForAI)];
@ -106,19 +73,14 @@ if (_set) then {
EGVAR(nametags,showNamesForAI) = false; EGVAR(nametags,showNamesForAI) = false;
}; };
} else { } else {
// Close any open dialogs (could be interrupts) // Kill the display (ensure main display exists, handles edge case where spectator turned off before display exists)
while {dialog} do { [{ !isNull MAIN_DISPLAY },{ [false] call FUNC(ui) }] call CBA_fnc_waitUntilAndExecute;
closeDialog 0;
};
// Kill the display // This variable doesn't matter anymore
(GETUVAR(GVAR(interface),displayNull)) closeDisplay 0; GVAR(uiForced) = nil;
// Terminate camera // Terminate camera
GVAR(freeCamera) cameraEffect ["terminate", "back"]; [false] call FUNC(cam);
camDestroy GVAR(freeCamera);
camDestroy GVAR(unitCamera);
camDestroy GVAR(targetCamera);
// Remove from spectator chat // Remove from spectator chat
GVAR(channel) radioChannelRemove [player]; GVAR(channel) radioChannelRemove [player];
@ -127,35 +89,6 @@ if (_set) then {
setCurrentChannel GVAR(channelCache); setCurrentChannel GVAR(channelCache);
GVAR(channelCache) = nil; 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 // Reset nametag settings
if (["ace_nametags"] call EFUNC(common,isModLoaded)) then { if (["ace_nametags"] call EFUNC(common,isModLoaded)) then {
EGVAR(nametags,showPlayerNames) = GVAR(nametagSettingCache) select 0; 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 // Reset interruptions
GVAR(interrupts) = []; GVAR(interrupts) = [];
// Mark spectator state for reference // Mark spectator state for reference
GVAR(isSet) = _set; GVAR(isSet) = _set;
player setVariable [QGVAR(isSet), true, true];
["ace_spectatorSet", [_set]] call CBA_fnc_localEvent; ["ace_spectatorSet", [_set, player]] call CBA_fnc_globalEvent;

View File

@ -1,26 +1,27 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Sets target unit to the given spectator state (physically) * Stores and hides a player safely out of the way (used by setSpectator on living players)
* To virtually handle a spectator see ace_spectator_fnc_setSpectator
* *
* Units will be gathered at marker ace_spectator_respawn (or [0,0,0] by default) * 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: * Arguments:
* 0: Unit to put into spectator stage <OBJECT> (default: player) * 0: Unit to handle <OBJECT>
* 1: Unit should be staged <BOOL> (default: true) * 1: Stage/Unstage <BOOL>
* *
* Return Value: * Return Value:
* None <NIL> * None
* *
* Example: * Example:
* [player, false] call ace_spectator_fnc_stageSpectator * [player, true] call ace_spectator_fnc_stageSpectator
* *
* Public: Yes * Public: No
*/ */
#include "script_component.hpp" #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]]]; params [["_unit",player,[objNull]], ["_set",true,[true]]];
// No change, no service (but allow spectators to be reset) // No change, no service (but allow spectators to be reset)
@ -39,20 +40,35 @@ _unit enableSimulation !_set;
if (_set) then { if (_set) then {
// Position should only be saved on first entry // Position should only be saved on first entry
if !(GETVAR(_unit,GVAR(isStaged),false)) then { 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 // Ghosts can't talk
[_unit, QGVAR(isStaged)] call EFUNC(common,hideUnit); [_unit, QGVAR(isStaged)] call EFUNC(common,hideUnit);
[_unit, QGVAR(isStaged)] call EFUNC(common,muteUnit); [_unit, QGVAR(isStaged)] call EFUNC(common,muteUnit);
// Position defaults to [0,0,0] if marker doesn't exist
_unit setPos (markerPos QGVAR(respawn)); _unit setPos (markerPos QGVAR(respawn));
} else { } else {
// Physical beings can talk // Physical beings can talk
[_unit, QGVAR(isStaged)] call EFUNC(common,unhideUnit); [_unit, QGVAR(isStaged)] call EFUNC(common,unhideUnit);
[_unit, QGVAR(isStaged)] call EFUNC(common,unmuteUnit); [_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) // 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 // No theoretical change if an existing spectator was reset
if !(_set isEqualTo (GETVAR(_unit,GVAR(isStaged),false))) then { if !(_set isEqualTo (GETVAR(_unit,GVAR(isStaged),false))) then {
// Mark spectator state for reference // Mark spectator state for reference
_unit setVariable [QGVAR(isStaged), _set, true]; _unit setVariable [QGVAR(isStaged), _set];
["ace_spectatorStaged", [_set]] call CBA_fnc_localEvent;
}; };
//BandAid for #2677 - if player in unitList weird before being staged, weird things can happen // If display exists already update the entity list to hide player
if ((player in GVAR(unitList)) || {ACE_player in GVAR(unitList)}) then { if !(isNull SPEC_DISPLAY) then {
[] call FUNC(updateUnits); //update list now if (GVAR(uiListType) == LIST_ENTITIES) then {
if (!(isNull (findDisplay 12249))) then {//If display is open now, close it and restart [] call FUNC(ui_updateListEntities);
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;
}; };
}; };

View File

@ -0,0 +1,31 @@
/*
* Author: Nelson Duarte, SilentSpike
* Function used to switch to next or previous camera focus
*
* Arguments:
* 0: Next/Prev unit <BOOL>
*
* 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);

View File

@ -1,82 +0,0 @@
/*
* Author: SilentSpike
* Correctly handles toggling of spectator interface elements for clean UX
*
* Arguments:
* 0: Display <DISPLAY>
* 1: Toogle compass <BOOL> <OPTIONAL>
* 2: Toogle help <BOOL> <OPTIONAL>
* 3: Toogle interface <BOOL> <OPTIONAL>
* 4: Toogle map <BOOL> <OPTIONAL>
* 5: Toogle toolbar <BOOL> <OPTIONAL>
* 6: Toogle unit list <BOOL> <OPTIONAL>
*
* Return Value:
* None <NIL>
*
* 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;
};

View File

@ -1,115 +0,0 @@
/*
* Author: SilentSpike
* Transitions the spectator camera vision/view/unit
*
* Arguments:
* 0: Camera mode <NUMBER>
* - 0: Free
* - 1: Internal
* - 2: External
* 1: Camera unit <OBJECT>
* 2: Vision mode <NUMBER>
* - -2: Normal
* - -1: NV
* - 0: White hot
* - 1: Black hot
*
* Return Value:
* None <NIL>
*
* 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;

View File

@ -0,0 +1,146 @@
/*
* Author: SilentSpike
* Handles UI initialisation and destruction
*
* Arguments:
* 0: Init/Terminate <BOOL>
*
* 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;
};

View File

@ -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);
};
};

View File

@ -0,0 +1,47 @@
/*
* Author: Nelson Duarte, AACO
* Function used to fade/unfade the entitiy/location list
*
* Arguments:
* 0: Fade the list <BOOL>
*
* 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;
};

View File

@ -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 <STRING>
*
* Return Value:
* Tree path to data <ARRAY>
*
* 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)

View File

@ -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 <DISPLAY>
* 1: Child display <DISPLAY>
* 2: Exit code of child <NUMBER>
*
* 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;
};

View File

@ -0,0 +1,209 @@
/*
* Author: Nelson Duarte, AACO, SilentSpike
* Function used to handle key down event
*
* Arguments:
* 0: Spectator display <DISPLAY>
* 1: Key DIK code <NUMBER>
* 2: State of shift <BOOL>
* 3: State of ctrl <BOOL>
* 4: State of alt <BOOL>
*
* 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

View File

@ -0,0 +1,31 @@
/*
* Author: Nelson Duarte, SilentSpike
* Function used to handle key up event
*
* Arguments:
* 0: Spectator display <DISPLAY>
* 1: Key DIK code <NUMBER>
* 2: State of shift <BOOL>
* 3: State of ctrl <BOOL>
* 4: State of alt <BOOL>
*
* 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

View File

@ -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 <BOOL>
* 1: List Click EH's _this <ARRAY>
*
* 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 [ "<t align='right' size='1.2'><t font='PuristaBold' size='1.6'>""%1""</t><br/>%2</t>", _name, _description], true, nil, 7, 0.7, 0] spawn BIS_fnc_textTiles;
};
_handled = true;
};
};
if (_handled) then {
playSound "ReadoutClick";
};
_handled

View File

@ -0,0 +1,40 @@
/*
* Author: Nelson Duarte, AACO
* Function used to handle map mouse click events
*
* Arguments:
* 0: Map control <CONTROL>
* 1: Mouse button pressed <NUMBER>
* 2: x screen coordinate clicked <BOOL>
* 3: y screen coordinate clicked <BOOL>
*
* 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);

View File

@ -0,0 +1,96 @@
/*
* Author: Nelson Duarte, AACO
* Function used to handle map draw
*
* Arguments:
* 0: Map control <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);

View File

@ -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 <CONTROL>
* 1: Mouse button pressed <NUMBER>
*
* 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);
};

View File

@ -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 <CONTROL>
* 1: Mouse button pressed <NUMBER>
*
* 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;
};

View File

@ -0,0 +1,31 @@
/*
* Author: Nelson Duarte, AACO
* Function used to handle mouse moving event
*
* Arguments:
* 0: Control <CONTROL>
* 1: Change in x <NUMBER>
* 2: Change in y <NUMBER>
*
* 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);
};
};

View File

@ -0,0 +1,27 @@
/*
* Author: Nelson Duarte, AACO
* Function used to handle mouse scroll event
*
* Arguments:
* 0: Control <CONTROL>
* 1: Change in Z <NUMBER>
*
* 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;
};
};

View File

@ -0,0 +1,37 @@
/*
* Author: Nelson Duarte, SilentSpike
* Function used to handle list tab change
*
* Arguments:
* 0: Control <CONTROL>
* 1: New tab index <NUMBER>
*
* 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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;

View File

@ -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);

View File

@ -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));
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -3,8 +3,10 @@
* Adds or removes spectator camera modes from the selection available to the local player. * Adds or removes spectator camera modes from the selection available to the local player.
* Possible camera modes are: * Possible camera modes are:
* - 0: Free * - 0: Free
* - 1: Internal * - 1: First person
* - 2: External * - 2: Follow
*
* Default selection is [0,1,2]
* *
* Arguments: * Arguments:
* 0: Camera modes to add <ARRAY> * 0: Camera modes to add <ARRAY>
@ -31,7 +33,7 @@ private ["_newModes","_currentModes"];
_currentModes = GVAR(availableModes); _currentModes = GVAR(availableModes);
// Restrict additions to only possible values // Restrict additions to only possible values
_newModes = _addModes arrayIntersect [0,1,2]; _newModes = _addModes arrayIntersect ALL_MODES;
_newModes append (_currentModes - _removeModes); _newModes append (_currentModes - _removeModes);
_newModes = _newModes arrayIntersect _newModes; _newModes = _newModes arrayIntersect _newModes;
@ -39,14 +41,19 @@ _newModes sort true;
// Can't become an empty array // Can't become an empty array
if (_newModes isEqualTo []) then { if (_newModes isEqualTo []) then {
["Cannot remove all camera modes (%1)", QFUNC(updateCameraModes)] call BIS_fnc_error; WARNING("Cannot remove all spectator camera modes");
} else { } else {
GVAR(availableModes) = _newModes; GVAR(availableModes) = _newModes;
}; };
// Update camera in case of change // Update camera in case of change
if (GVAR(isSet)) then { if !(isNil QGVAR(camera)) then {
[] call FUNC(transitionCamera); // 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 _newModes

View File

@ -1,7 +1,6 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Adds or removes sides from the selection available to spectate by the local player. * Adds or removes sides from the selection available to spectate. Local effect.
* Note that the side filter setting is applied to the available sides dynamically.
* *
* Default selection is [west,east,resistance,civilian] * Default selection is [west,east,resistance,civilian]
* *
@ -10,10 +9,10 @@
* 1: Sides to remove <ARRAY> * 1: Sides to remove <ARRAY>
* *
* Return Value: * Return Value:
* Spectatable sides <ARRAY> * Sides available <ARRAY>
* *
* Example: * Example:
* [[west], [east,civilian]] call ace_spectator_fnc_updateSpectatableSides * [[west], [east,civilian]] call ace_spectator_fnc_updateSides
* *
* Public: Yes * Public: Yes
*/ */

View File

@ -1,69 +1,40 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Adds units to spectator whitelist/blacklist and refreshes the filter units * Adds and removed units from the spectator list. Local effect.
* *
* Arguments: * Arguments:
* 0: Units to add to the whitelist <ARRAY> * 0: Units to show in the list <ARRAY>
* 1: Use blacklist <BOOL> (default: false) * 1: Units to hide in the list <ARRAY>
* *
* Return Value: * Return Value:
* None <NIL> * None <NIL>
* *
* Example: * Example:
* [allUnits,true] call ace_spectator_fnc_updateUnits * [allPlayers, [player]] call ace_spectator_fnc_updateUnits
* *
* Public: Yes * Public: Yes
*/ */
#include "script_component.hpp" #include "script_component.hpp"
params [["_newUnits",[],[[]]],["_blacklist",false,[false]]];
// Function only matters on player clients // Function only matters on player clients
if (!hasInterface) exitWith {}; if (!hasInterface) exitWith {};
// If adding to a list we can exit here, since it won't show up until the UI refreshes anyway params [["_addUnits",[],[[]]], ["_removeUnits",[],[[], true]]];
if !(_newUnits isEqualTo []) exitWith {
if (_blacklist) then { // Deprecated parameter (remember to remove bool from params when removed)
GVAR(unitWhitelist) = GVAR(unitWhitelist) - _newUnits; if (_removeUnits isEqualType true) then {
GVAR(unitBlacklist) append _newUnits; ACE_DEPRECATED("Boolean parameter","3.12.0","array (see function header or doc)");
} else { if (_removeUnits) then {
GVAR(unitBlacklist) = GVAR(unitBlacklist) - _newUnits; _removeUnits = _addUnits;
GVAR(unitWhitelist) append _newUnits; _addUnits = [];
}; };
}; };
// Unit setting filter // Add to the whitelist and prevent list overlap
private _newUnits = [[],allPlayers,playableUnits,allUnits] select GVAR(filterUnits); GVAR(unitBlacklist) = GVAR(unitBlacklist) - _addUnits;
GVAR(unitWhitelist) append _addUnits;
// Side setting filter // Blacklist overrides the whitelist
private _sideFilter = [ GVAR(unitWhitelist) = GVAR(unitWhitelist) - _removeUnits;
{_x == (side group player)}, GVAR(unitBlacklist) append _removeUnits;
{(_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;

View File

@ -1,7 +1,7 @@
/* /*
* Author: SilentSpike * Author: SilentSpike
* Adds or removes spectator vision modes from the selection available to the local player. * 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: * Possible vision modes are:
* - -2: Normal * - -2: Normal
* - -1: Night vision * - -1: Night vision
@ -14,6 +14,8 @@
* - 6: White Hot / Darker Red Cold * - 6: White Hot / Darker Red Cold
* - 7: Thermal (Shade of Red and Green, Bodies are white) * - 7: Thermal (Shade of Red and Green, Bodies are white)
* *
* Default selection is [-2,-1,0,1]
*
* Arguments: * Arguments:
* 0: Vision modes to add <ARRAY> * 0: Vision modes to add <ARRAY>
* 1: Vision modes to remove <ARRAY> * 1: Vision modes to remove <ARRAY>
@ -47,14 +49,14 @@ _newModes sort true;
// Can't become an empty array // Can't become an empty array
if (_newModes isEqualTo []) then { if (_newModes isEqualTo []) then {
["Cannot remove all vision modes (%1)", QFUNC(updateVisionModes)] call BIS_fnc_error; WARNING("Cannot remove all spectator vision modes");
} else { } else {
GVAR(availableVisions) = _newModes; GVAR(availableVisions) = _newModes;
}; };
// Update camera in case of change // Update camera in case of change
if (GVAR(isSet)) then { if !(isNil QGVAR(camera)) then {
[] call FUNC(transitionCamera); [GVAR(camVision)] call FUNC(cam_setVisionMode);
}; };
_newModes _newModes

Some files were not shown because too many files have changed in this diff Show More