From b4285eccce52c2c4181fd0d317697c99f2703308 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Sat, 26 Mar 2022 17:56:31 -0400 Subject: [PATCH 01/26] migration to CBA system, rework of functionality --- addons/@ocap/addons/ocap/config.cpp | 82 ------ .../ocap/functions/fn_addEventMission.sqf | 73 ------ .../addons/ocap/functions/fn_eh_connected.sqf | 3 - .../ocap/functions/fn_eh_disconnected.sqf | 9 - .../addons/ocap/functions/fn_exportData.sqf | 113 --------- .../addons/ocap/functions/fn_getDelay.sqf | 40 --- .../addons/ocap/functions/fn_getUnitType.sqf | 76 ------ .../ocap/functions/fn_handleCustomEvent.sqf | 53 ---- .../@ocap/addons/ocap/functions/fn_init.sqf | 107 -------- .../addons/ocap/functions/script_macros.hpp | 1 - x/ocap2/.editorconfig | 13 + x/ocap2/.gitignore | 3 + x/ocap2/addons/extension/XEH_postInit.sqf | 3 + x/ocap2/addons/extension/XEH_preInit.sqf | 4 + x/ocap2/addons/extension/XEH_prep.sqf | 3 + x/ocap2/addons/extension/config.cpp | 32 +++ .../ocap2/addons/extension/fnc_sendData.sqf | 7 +- x/ocap2/addons/extension/script_component.hpp | 4 + {addons/@ocap => x/ocap2/addons}/licence.txt | 0 .../@ocap => x/ocap2/addons}/logo_ocap.paa | Bin x/ocap2/addons/main/CfgSettings.hpp | 0 x/ocap2/addons/main/XEH_postInit.sqf | 220 ++++++++++++++++ x/ocap2/addons/main/XEH_preInit.sqf | 224 ++++++++++++++++ x/ocap2/addons/main/XEH_prep.sqf | 1 + x/ocap2/addons/main/config.cpp | 32 +++ x/ocap2/addons/main/script_component.hpp | 4 + .../ocap2/addons/main}/script_macros.hpp | 35 ++- {addons/@ocap => x/ocap2/addons}/mod.cpp | 0 x/ocap2/addons/recorder/XEH_postInit.sqf | 8 + x/ocap2/addons/recorder/XEH_preInit.sqf | 8 + x/ocap2/addons/recorder/XEH_prep.sqf | 13 + x/ocap2/addons/recorder/config.cpp | 32 +++ .../addons/recorder/fnc_addEventMission.sqf | 76 ++++++ x/ocap2/addons/recorder/fnc_captureLoop.sqf | 240 ++++++++++++++++++ x/ocap2/addons/recorder/fnc_exportData.sqf | 139 ++++++++++ x/ocap2/addons/recorder/fnc_getClass.sqf | 14 + x/ocap2/addons/recorder/fnc_getDelay.sqf | 41 +++ x/ocap2/addons/recorder/fnc_getUnitType.sqf | 77 ++++++ .../addons/recorder/fnc_handleCustomEvent.sqf | 54 ++++ x/ocap2/addons/recorder/fnc_init.sqf | 95 +++++++ x/ocap2/addons/recorder/fnc_isKindOfApc.sqf | 9 + .../ocap2/addons/recorder/fnc_updateTime.sqf | 12 +- .../functions/fn_addEventHandlers.sqf | 2 +- .../recorder}/functions/fn_autoStart.sqf | 0 .../recorder/functions/fn_eh_connected.sqf | 3 + .../recorder/functions/fn_eh_disconnected.sqf | 9 + .../recorder}/functions/fn_eh_fired.sqf | 6 +- .../addons/recorder}/functions/fn_eh_hit.sqf | 12 +- .../recorder}/functions/fn_eh_killed.sqf | 12 +- .../functions/fn_getEventWeaponText.sqf | 0 .../recorder}/functions/fn_getInstigator.sqf | 0 .../recorder}/functions/fn_handleMarkers.sqf | 24 +- x/ocap2/addons/recorder/functions/fn_init.sqf | 106 ++++++++ .../functions/fn_startCaptureLoop.sqf | 80 +++--- .../functions/fn_trackAceExplLife.sqf | 0 .../functions/fn_trackAceExplPlace.sqf | 0 .../functions/fn_trackAceRemoteDet.sqf | 0 .../functions/fn_trackAceThrowing.sqf | 0 .../functions/markerTestingChecklist.txt | 0 .../recorder/functions/script_macros.hpp | 1 + x/ocap2/addons/recorder/script_component.hpp | 4 + 61 files changed, 1571 insertions(+), 648 deletions(-) delete mode 100644 addons/@ocap/addons/ocap/config.cpp delete mode 100644 addons/@ocap/addons/ocap/functions/fn_addEventMission.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_eh_connected.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_eh_disconnected.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_exportData.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_getDelay.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_getUnitType.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_handleCustomEvent.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/fn_init.sqf delete mode 100644 addons/@ocap/addons/ocap/functions/script_macros.hpp create mode 100644 x/ocap2/.editorconfig create mode 100644 x/ocap2/.gitignore create mode 100644 x/ocap2/addons/extension/XEH_postInit.sqf create mode 100644 x/ocap2/addons/extension/XEH_preInit.sqf create mode 100644 x/ocap2/addons/extension/XEH_prep.sqf create mode 100644 x/ocap2/addons/extension/config.cpp rename addons/@ocap/addons/ocap/functions/fn_extension.sqf => x/ocap2/addons/extension/fnc_sendData.sqf (77%) create mode 100644 x/ocap2/addons/extension/script_component.hpp rename {addons/@ocap => x/ocap2/addons}/licence.txt (100%) rename {addons/@ocap => x/ocap2/addons}/logo_ocap.paa (100%) create mode 100644 x/ocap2/addons/main/CfgSettings.hpp create mode 100644 x/ocap2/addons/main/XEH_postInit.sqf create mode 100644 x/ocap2/addons/main/XEH_preInit.sqf create mode 100644 x/ocap2/addons/main/XEH_prep.sqf create mode 100644 x/ocap2/addons/main/config.cpp create mode 100644 x/ocap2/addons/main/script_component.hpp rename {addons/@ocap/addons/ocap => x/ocap2/addons/main}/script_macros.hpp (59%) rename {addons/@ocap => x/ocap2/addons}/mod.cpp (100%) create mode 100644 x/ocap2/addons/recorder/XEH_postInit.sqf create mode 100644 x/ocap2/addons/recorder/XEH_preInit.sqf create mode 100644 x/ocap2/addons/recorder/XEH_prep.sqf create mode 100644 x/ocap2/addons/recorder/config.cpp create mode 100644 x/ocap2/addons/recorder/fnc_addEventMission.sqf create mode 100644 x/ocap2/addons/recorder/fnc_captureLoop.sqf create mode 100644 x/ocap2/addons/recorder/fnc_exportData.sqf create mode 100644 x/ocap2/addons/recorder/fnc_getClass.sqf create mode 100644 x/ocap2/addons/recorder/fnc_getDelay.sqf create mode 100644 x/ocap2/addons/recorder/fnc_getUnitType.sqf create mode 100644 x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf create mode 100644 x/ocap2/addons/recorder/fnc_init.sqf create mode 100644 x/ocap2/addons/recorder/fnc_isKindOfApc.sqf rename addons/@ocap/addons/ocap/functions/fn_updateTime.sqf => x/ocap2/addons/recorder/fnc_updateTime.sqf (70%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_addEventHandlers.sqf (94%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_autoStart.sqf (100%) create mode 100644 x/ocap2/addons/recorder/functions/fn_eh_connected.sqf create mode 100644 x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_eh_fired.sqf (98%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_eh_hit.sqf (84%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_eh_killed.sqf (84%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_getEventWeaponText.sqf (100%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_getInstigator.sqf (100%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_handleMarkers.sqf (90%) create mode 100644 x/ocap2/addons/recorder/functions/fn_init.sqf rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_startCaptureLoop.sqf (69%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_trackAceExplLife.sqf (100%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_trackAceExplPlace.sqf (100%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_trackAceRemoteDet.sqf (100%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/fn_trackAceThrowing.sqf (100%) rename {addons/@ocap/addons/ocap => x/ocap2/addons/recorder}/functions/markerTestingChecklist.txt (100%) create mode 100644 x/ocap2/addons/recorder/functions/script_macros.hpp create mode 100644 x/ocap2/addons/recorder/script_component.hpp diff --git a/addons/@ocap/addons/ocap/config.cpp b/addons/@ocap/addons/ocap/config.cpp deleted file mode 100644 index 740304c..0000000 --- a/addons/@ocap/addons/ocap/config.cpp +++ /dev/null @@ -1,82 +0,0 @@ -//////////////////////////////////////////////////////////////////// -//DeRap: ocap\config.bin -//Produced from mikero's Dos Tools Dll version 8.02 -//https://mikero.bytex.digital/Downloads -//'now' is Sun Apr 25 20:52:44 2021 : 'file' last modified on Tue Mar 16 12:25:11 2021 -//////////////////////////////////////////////////////////////////// - -#define _ARMA_ - -class CfgPatches -{ - class OCAP - { - - name = "OCAP2"; - author = "Dell, Zealot, Kurt, IndigoFox, Fank"; - authors[] = {"Dell", "Zealot", "Kurt", "IndigoFox", "Fank"}; - url = "https://github.com/OCAP2/OCAP"; - version = 1.1; - versionStr = "1.2.0-alpha"; - versionAr[] = {1, 1, 0}; - requiredAddons[] = {"A3_Functions_F","cba_main"}; - requiredVersion = 2.04; - units[] = {}; - weapons[] = {}; - }; -}; - -class CfgFunctions -{ - class OCAP - { - class null - { - file = "ocap\functions"; - class autostart { - preInit = 1; - }; - class init{}; - class addEventHandlers{}; - class addEventMission{}; - class eh_connected{}; - class eh_disconnected{}; - class eh_fired{}; - class eh_hit{}; - class eh_killed{}; - class exportData{}; - class extension{}; - class getDelay{}; - class getInstigator{}; - class getEventWeaponText{}; - class getUnitType{}; - class handleMarkers{}; - class handleCustomEvent{}; - class startCaptureLoop{}; - class trackAceExplLife{}; - class trackAceExplPlace{}; - class trackAceRemoteDet{}; - class trackAceThrowing{}; - class updateTime{}; - }; - }; -}; - -class CfgRemoteExec -{ - class Functions - { - class handleMarkers - { - allowedTargets = 2; - }; - class handleCustomEvent - { - allowedTargets = 2; - }; - class trackAceThrowing - { - allowedTargets = 0; - }; - }; -}; diff --git a/addons/@ocap/addons/ocap/functions/fn_addEventMission.sqf b/addons/@ocap/addons/ocap/functions/fn_addEventMission.sqf deleted file mode 100644 index 6c6fb26..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_addEventMission.sqf +++ /dev/null @@ -1,73 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_addEventMission - -Description: - Used for applying mission event handlers. - - * Applied during initialization of OCAP2 in . - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - call ocap_fnc_addEventMission; - --- - -Public: - No - -Author: - IndigoFox, Dell ----------------------------------------------------------------------------- */ - -addMissionEventHandler["HandleDisconnect", { - _this call ocap_fnc_eh_disconnected; -}]; - -addMissionEventHandler["PlayerConnected", { - _this call ocap_fnc_eh_connected; -}]; - -addMissionEventHandler ["EntityKilled", { - _this call ocap_fnc_eh_killed; -}]; - -addMissionEventHandler ["EntityRespawned", { - params ["_entity", "_corpse"]; - - // Reset unit back to normal - _entity setvariable ["ocapIsKilled", false]; - - // Stop tracking old unit - if (_corpse getVariable ["ocap_isInitialised", false]) then { - _corpse setVariable ["ocap_exclude", true]; - - [_entity, true] spawn ocap_fnc_addEventHandlers; - }; -}]; - -if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then { - call ocap_fnc_trackAceExplPlace; -}; - -if (ocap_saveMissionEnded) then { - addMissionEventHandler ["MPEnded", { - ["Mission ended automatically"] call ocap_fnc_exportData; - }]; -}; - -// Custom event handler -ocap_customEvent_handle = ["ocap_handleCustomEvent", { - _this call ocap_fnc_handleCustomEvent; -}] call CBA_fnc_addEventHandler; - -// Add event saving markers -call ocap_fnc_handleMarkers; - -["WMT_fnc_EndMission", { - _this call ocap_fnc_exportData; -}] call CBA_fnc_addEventHandler; diff --git a/addons/@ocap/addons/ocap/functions/fn_eh_connected.sqf b/addons/@ocap/addons/ocap/functions/fn_eh_connected.sqf deleted file mode 100644 index 6a6f47a..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_eh_connected.sqf +++ /dev/null @@ -1,3 +0,0 @@ -[":EVENT:", - [ocap_captureFrameNo, "connected", _this select 2] -] call ocap_fnc_extension; diff --git a/addons/@ocap/addons/ocap/functions/fn_eh_disconnected.sqf b/addons/@ocap/addons/ocap/functions/fn_eh_disconnected.sqf deleted file mode 100644 index 137bf0c..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_eh_disconnected.sqf +++ /dev/null @@ -1,9 +0,0 @@ -params ["_unit", "_id", "_uid", "_name"]; - -[":EVENT:", - [ocap_captureFrameNo, "disconnected", _name] -] call ocap_fnc_extension; - -if (_unit getVariable ["ocap_isInitialised", false]) then { - _unit setVariable ["ocap_exclude", true]; -}; diff --git a/addons/@ocap/addons/ocap/functions/fn_exportData.sqf b/addons/@ocap/addons/ocap/functions/fn_exportData.sqf deleted file mode 100644 index aadd168..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_exportData.sqf +++ /dev/null @@ -1,113 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_exportData - -Description: - This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component. - - This function MUST be called in order to save a mission recording. A boolean true in the correct option of userconfig.hpp will automatically execute this function when the "MPEnded" Event Handler triggers. - -Parameters: - _side - The winning side [optional, Side] - _message - A custom description of how the victory was achieved [optional, String] - _tag - A custom tag to override that which is defined in userconfig.hpp that will make it filterable in web [optional, String] - -Returns: - Nothing - -Examples: - --- Code - // "Mission ended" - [] call ocap_fnc_exportData; - - // "BLUFOR Win." - [west] call ocap_fnc_exportData; - - // "OPFOR Win. OPFOR controlled all sectors! - [east, "OPFOR controlled all sectors!"] call ocap_fnc_exportData; - - // "Independent Win. INDFOR stole the intel!" - // Mission is saved under filterable "SnatchAndGrab" tag on web - [independent, "INDFOR stole the intel!", "SnatchAndGrab"] call ocap_fnc_exportData; - --- - -Public: - Yes - -Author: - Dell, Zealot, IndigoFox, TyroneMF ----------------------------------------------------------------------------- */ - -#include "script_macros.hpp" -if (!ocap_capture) exitWith {LOG(["fnc_exportData.sqf called, but recording hasn't started."]);}; - -[] spawn { - _realyTime = time - ocap_startTime; - _ocapTime = ocap_frameCaptureDelay * ocap_captureFrameNo; - LOG(ARR6("fnc_exportData.sqf: RealyTime =", _realyTime," OcapTime =", _ocapTime," delta =", _realyTime - _OcapTime)); -}; - -ocap_capture = false; -ocap_endFrameNo = ocap_captureFrameNo; - -publicVariable "ocap_endFrameNo"; - -params ["_side", "_message", "_tag"]; -switch (count _this) do { - case 0: { - [":EVENT:", [ocap_endFrameNo, "endMission", ["", "Mission ended"]]] call ocap_fnc_extension; - }; - case 1: { - [":EVENT:", [ocap_endFrameNo, "endMission", ["", _side]]] call ocap_fnc_extension; - }; - default { - private _sideString = str(_side); - if (_side == sideUnknown) then { _sideString = "" }; - [":EVENT:", [ocap_endFrameNo, "endMission", [_sideString, _message]]] call ocap_fnc_extension; - }; -}; - -if (ocap_needToSave) then { - if (!isNil "_tag") then { - [":SAVE:", [worldName, ocap_missionName, getMissionConfigValue ["author", ""], ocap_frameCaptureDelay, ocap_endFrameNo, _tag]] call ocap_fnc_extension; - LOG(ARR4("Saved recording of mission", ocap_missionName, "with tag", _tag)); - } else { - [":SAVE:", [worldName, ocap_missionName, getMissionConfigValue ["author", ""], ocap_frameCaptureDelay, ocap_endFrameNo]] call ocap_fnc_extension; - LOG(ARR3("Saved recording of mission", ocap_missionName, "with default tag")); - }; - - // briefingName is used here, no need for publicVariable for a simple confirmation log. - { - player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - ( - "OCAP2 capture of " + briefingName + " has been exported with " + str(ocap_endFrameNo) + " frames saved." + - "

" + - "Upload results have been logged." - ) - ] - ]; - player setDiarySubjectPicture [ - "OCAP2Info", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\upload_ca.paa" - ]; - } remoteExec ["call", 0, false]; -} else { - LOG(["ocap_needToSave is set to false. Not saving"]); - { - player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - ( - "OCAP2 capture of " + briefingName + " has not been saved, as the configured criteria have not been met." - ) - ] - ]; - player setDiarySubjectPicture [ - "OCAP2Info", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\danger_ca.paa" - ]; - } remoteExec ["call", 0, false]; -}; diff --git a/addons/@ocap/addons/ocap/functions/fn_getDelay.sqf b/addons/@ocap/addons/ocap/functions/fn_getDelay.sqf deleted file mode 100644 index b7f7768..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_getDelay.sqf +++ /dev/null @@ -1,40 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_exportData - -Description: - Determines the the appropriate interval at which to loop the function. - - Устанавливает точную задержку между кадрами - -Parameters: - None - -Returns: - Sleep duration [Number] - -Examples: - --- Code - call ocap_fnc_getDelay; - --- - -Public: - No - -Author: - Dell ----------------------------------------------------------------------------- */ - -#include "script_macros.hpp" -private "_sleep"; -isNil { - _relativelyTime = time - ocap_startTime; - _sleep = (ocap_captureFrameNo + 1) * ocap_frameCaptureDelay - _relativelyTime; - if ((ocap_captureFrameNo % 10) isEqualTo 0) then { - LOG(ARR4("DEBUG: Frame", ocap_captureFrameNo, "is created in ~", ocap_frameCaptureDelay - _sleep)); - }; - if (_sleep < 0) then { - LOG(ARR3("ERROR: Frame delay is negative", ocap_captureFrameNo, _sleep)); - _sleep = 0; - }; -}; -_sleep diff --git a/addons/@ocap/addons/ocap/functions/fn_getUnitType.sqf b/addons/@ocap/addons/ocap/functions/fn_getUnitType.sqf deleted file mode 100644 index 421bb6e..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_getUnitType.sqf +++ /dev/null @@ -1,76 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_getUnitType - -Description: - Identifies the role of a unit using similar methodology to Arma 3's. Used in . - -Parameters: - _unitToCheck - Unit to evaluate. [Object] - -Returns: - The role text. [String] - -Examples: - --- Code - [_x] call ocap_fnc_getUnitType; - --- - -Public: - No - -Author: - IndigoFox ----------------------------------------------------------------------------- */ - -params ["_unitToCheck"]; - -private _role = "Man"; -private _typePic = getText (configOf _unitToCheck >> "icon"); - - -switch (true) do { - case ( - ["Officer", _typePic] call BIS_fnc_inString - ): {_role = "Officer"}; - case ( - _unitToCheck == leader group _unitToCheck - ): {_role = "Leader"}; -}; - -if (_role == "Man") then { - switch (true) do { - case (_unitToCheck getUnitTrait 'medic'): { - _role = 'Medic'; - }; - case (_unitToCheck getUnitTrait 'engineer'): { - _role = 'Engineer'; - }; - case (_unitToCheck getUnitTrait 'explosiveSpecialist'): { - _role = 'ExplosiveSpecialist'; - }; - }; -}; - -if (_role == "Man") then { - private _weaponPicture = toLower getText (configFile >> "CfgWeapons" >> secondaryWeapon _unitToCheck >> "UiPicture"); - switch (true) do { - case ("_mg_" in _weaponPicture): {_role = "MG"}; - case ("_gl_" in _weaponPicture): {_role = "GL"}; - case ("_at_" in _weaponPicture): {_role = "AT"}; - case ("_sniper_" in _weaponPicture):{_role = "Sniper"}; - case ("_aa_" in _weaponPicture): {_role = "AA"}; - }; -}; - -if (_role == "Man") then { - private _weaponPicture = toLower getText (configFile >> "CfgWeapons" >> primaryWeapon _unitToCheck >> "UiPicture"); - switch (true) do { - case ("_mg_" in _weaponPicture): {_role = "MG"}; - case ("_gl_" in _weaponPicture): {_role = "GL"}; - case ("_at_" in _weaponPicture): {_role = "AT"}; - case ("_sniper_" in _weaponPicture):{_role = "Sniper"}; - case ("_aa_" in _weaponPicture): {_role = "AA"}; - }; -}; - -_role diff --git a/addons/@ocap/addons/ocap/functions/fn_handleCustomEvent.sqf b/addons/@ocap/addons/ocap/functions/fn_handleCustomEvent.sqf deleted file mode 100644 index 8d9a2db..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_handleCustomEvent.sqf +++ /dev/null @@ -1,53 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_handleCustomEvent - -Description: - Used for applying global event handlers. - - * Applied during initialization of OCAP2 in . - -Parameters: - _type - objective type that will define the text & icon [String, one of: "flag"] - _unit - name of the unit that performed the action [String] - _unitColor - color for the unit's name shown in Events list and for the pulse on the map [String, Hex RGB, defaults "" and will show as white] - _objectiveColor - color representing the icon in Events list [String, Hex RGB, defaults "" and will show as white] - _position - the location to pulse on the map [optional, PositionATL, default nil] - -Returns: - Nothing - -Examples: - --- Code - ["ocap_handleCustomEvent", ["eventType", "eventMessage"]] call CBA_fnc_serverEvent; - - // indicates a flag has been captured - ["ocap_handleCustomEvent", ["captured", [ - "flag", - name _unit, - str side group _unit, - "#FF0000", - getPosAtl _flag - ]]] call call CBA_fnc_serverEvent; - - - // Not yet implemented - ["ocap_handleCustomEvent", ["captured", [ - "sector", - name _unit, - str side group _unit, - "#FF0000", - getPosAtl _sectorObject - ]]] call call CBA_fnc_serverEvent; - --- - -Public: - Yes - -Author: - Fank, Zealot ----------------------------------------------------------------------------- */ - -params ["_eventName", "_eventMessage"]; -[":EVENT:", - [ocap_captureFrameNo, _eventName, _eventMessage] -] call ocap_fnc_extension; diff --git a/addons/@ocap/addons/ocap/functions/fn_init.sqf b/addons/@ocap/addons/ocap/functions/fn_init.sqf deleted file mode 100644 index 282c3d9..0000000 --- a/addons/@ocap/addons/ocap/functions/fn_init.sqf +++ /dev/null @@ -1,107 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_init - -Description: - Automatic Start: Called from ocap_fnc_autoStart. - Manual Start: Server execution to begin. - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - call ocap_fnc_init; - --- - -Public: - No - -Author: - Dell, Zealot, IndigoFox ----------------------------------------------------------------------------- */ - -#include "\userconfig\ocap\config.hpp" -#include "script_macros.hpp" - -// bool: ocap_capture -ocap_capture = false; -// int: ocap_captureFrameNo -ocap_captureFrameNo = 0; -// bool: ocap_needToSave -ocap_needToSave = [false, true] select (ocap_minMissionTime < 10); - -if (ocap_excludeMarkerFromRecord isEqualType []) then { - publicVariable "ocap_excludeMarkerFromRecord"; -} else { - LOG(["excludeMarkerFromRecord in config is not an array, skipping exclusions"]); -}; - -// macro: OCAP_ADDON_VERSION -ocap_addon_ver = OCAP_ADDON_VERSION; -publicVariable "ocap_addon_ver"; - -ocap_extension_ver = ([":VERSION:", []] call ocap_fnc_extension); -publicVariable "ocap_extension_ver"; - - -{ - [{!isNil "ocap_addon_ver" && !isNil "ocap_extension_ver"}, { - player createDiarySubject ["OCAP2Info", "OCAP2 AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; - - ocap_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; - ocap_diaryAbout = player createDiaryRecord [ - "OCAP2Info", - [ - "About", - ( - "OCAP2
" + - "Addon version: " + ocap_addon_ver + - "
" + - "Extension version: " + (ocap_extension_ver # 0) + " (built " + (ocap_extension_ver # 1) + ")" + - "
" + - "https://github.com/OCAP2/OCAP" + - "

" + - "OCAP2 is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + - "

" + - "Recording status can be found in the Status section." + - "

" + - "DISCLAIMER: This mission may be recorded and made publicly available at the discretion of the server administrators. Please be aware that your actions during this mission will be tracked and attributed to your in-game username." - ) - ] - ]; - - ocap_diaryStatus = player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - "OCAP2 initialized. Awaiting configured capture conditions." - ] - ]; - }] call CBA_fnc_waitUntilAndExecute; -} remoteExecCall ["call", 0, true]; - -// Support both methods of setting mission name. -ocap_missionName = getMissionConfigValue ["onLoadName", ""]; -if (ocap_missionName == "") then { - ocap_missionName = briefingName; -}; - -// Add event missions -call ocap_fnc_addEventMission; -[":START:", [worldName, ocap_missionName, getMissionConfigValue ["author", ""], ocap_frameCaptureDelay]] call ocap_fnc_extension; -0 spawn ocap_fnc_startCaptureLoop; - -[":SET:VERSION:", [OCAP_ADDON_VERSION]] call ocap_fnc_extension; - -0 spawn { - if (ocap_needToSave) exitWith {}; - LOG(["Waiting freeze end!"]); - waitUntil {sleep 1.4; missionNamespace getVariable["WMT_pub_frzState", 3] >= 3}; - LOG(["Waiting until ocap_minMissionTime ends"]); - sleep ocap_minMissionTime; - LOG(["ocap_needToSave is set to true"]); - ocap_needToSave = true; -}; diff --git a/addons/@ocap/addons/ocap/functions/script_macros.hpp b/addons/@ocap/addons/ocap/functions/script_macros.hpp deleted file mode 100644 index d1f39d9..0000000 --- a/addons/@ocap/addons/ocap/functions/script_macros.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "\ocap\script_macros.hpp" diff --git a/x/ocap2/.editorconfig b/x/ocap2/.editorconfig new file mode 100644 index 0000000..1b9210f --- /dev/null +++ b/x/ocap2/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# every file +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/x/ocap2/.gitignore b/x/ocap2/.gitignore new file mode 100644 index 0000000..ecbbd4e --- /dev/null +++ b/x/ocap2/.gitignore @@ -0,0 +1,3 @@ +*.pbo + +*.bak diff --git a/x/ocap2/addons/extension/XEH_postInit.sqf b/x/ocap2/addons/extension/XEH_postInit.sqf new file mode 100644 index 0000000..19f1494 --- /dev/null +++ b/x/ocap2/addons/extension/XEH_postInit.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +ADDON = true; diff --git a/x/ocap2/addons/extension/XEH_preInit.sqf b/x/ocap2/addons/extension/XEH_preInit.sqf new file mode 100644 index 0000000..44484b5 --- /dev/null +++ b/x/ocap2/addons/extension/XEH_preInit.sqf @@ -0,0 +1,4 @@ +#include "script_component.hpp" +#include "XEH_prep.sqf" + +ADDON = true; diff --git a/x/ocap2/addons/extension/XEH_prep.sqf b/x/ocap2/addons/extension/XEH_prep.sqf new file mode 100644 index 0000000..728a456 --- /dev/null +++ b/x/ocap2/addons/extension/XEH_prep.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +PREP(sendData); diff --git a/x/ocap2/addons/extension/config.cpp b/x/ocap2/addons/extension/config.cpp new file mode 100644 index 0000000..9edd0f1 --- /dev/null +++ b/x/ocap2/addons/extension/config.cpp @@ -0,0 +1,32 @@ +#define _ARMA_ +#include "script_component.hpp" + +class CfgPatches +{ + class ADDON + { + + name = COMPONENT_NAME; + author = "Dell, Zealot, Kurt, IndigoFox, Fank"; + authors[] = {"Dell", "Zealot", "Kurt", "IndigoFox", "Fank"}; + url = "https://github.com/OCAP2/OCAP"; + VERSION_CONFIG; + requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh","ocap2_main"}; + units[] = {}; + weapons[] = {}; + }; +}; + +class Extended_PreInit_EventHandlers { + class ADDON { + // This will be executed once in 3DEN, main menu and before briefing has started for every mission + init = QUOTE( call COMPILE_FILE(XEH_preInit) ); + }; +}; + +class Extended_PostInit_EventHandlers { + class ADDON { + // This will be executed once for each mission, once the mission has started + init = QUOTE( call COMPILE_FILE(XEH_postInit) ); + }; +}; diff --git a/addons/@ocap/addons/ocap/functions/fn_extension.sqf b/x/ocap2/addons/extension/fnc_sendData.sqf similarity index 77% rename from addons/@ocap/addons/ocap/functions/fn_extension.sqf rename to x/ocap2/addons/extension/fnc_sendData.sqf index aacf12b..dc0d8b9 100644 --- a/addons/@ocap/addons/ocap/functions/fn_extension.sqf +++ b/x/ocap2/addons/extension/fnc_sendData.sqf @@ -1,5 +1,5 @@ /* ---------------------------------------------------------------------------- -Script: ocap_fnc_extension +Script: EFUNC(extension,sendData) Description: Manages raw extension calls and returns values / logs errors where relevant. @@ -13,7 +13,7 @@ Returns: Examples: --- Code - [":VERSION", []] call ocap_fnc_extension; + [":VERSION", []] call EFUNC(extension,sendData); --- Public: @@ -22,6 +22,7 @@ Public: Author: Dell, Zealot ---------------------------------------------------------------------------- */ +#include "script_component.hpp" params ["_command","_args"]; @@ -32,7 +33,7 @@ private _res = _dllName callExtension [_command, _args]; _res params ["_result","_returnCode","_errorCode"]; if (_errorCode != 0 || _returnCode != 0) then { - diag_log ["fnc_callextension_zlt.sqf: Error: ", _result, _returnCode, _errorCode, _command, _args]; + textLogFormat ["Error when calling extension: %1", [_result, _returnCode, _errorCode, _command, _args]]; }; if ( diff --git a/x/ocap2/addons/extension/script_component.hpp b/x/ocap2/addons/extension/script_component.hpp new file mode 100644 index 0000000..5220b67 --- /dev/null +++ b/x/ocap2/addons/extension/script_component.hpp @@ -0,0 +1,4 @@ +#define COMPONENT extension +#define COMPONENT_BEAUTIFIED Extension + +#include "\x\ocap2\addons\main\script_macros.hpp" diff --git a/addons/@ocap/licence.txt b/x/ocap2/addons/licence.txt similarity index 100% rename from addons/@ocap/licence.txt rename to x/ocap2/addons/licence.txt diff --git a/addons/@ocap/logo_ocap.paa b/x/ocap2/addons/logo_ocap.paa similarity index 100% rename from addons/@ocap/logo_ocap.paa rename to x/ocap2/addons/logo_ocap.paa diff --git a/x/ocap2/addons/main/CfgSettings.hpp b/x/ocap2/addons/main/CfgSettings.hpp new file mode 100644 index 0000000..e69de29 diff --git a/x/ocap2/addons/main/XEH_postInit.sqf b/x/ocap2/addons/main/XEH_postInit.sqf new file mode 100644 index 0000000..0e5c047 --- /dev/null +++ b/x/ocap2/addons/main/XEH_postInit.sqf @@ -0,0 +1,220 @@ +#include "script_component.hpp" + +// AUTO START SETTINGS +[ + QEGVAR(settings,autoStart), + "CHECKBOX", // setting type + [ + "Auto Start Recording", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Automatically start OCAP recordings at session start. Default: true" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,minPlayerCount), + "SLIDER", // setting type + [ + "Minimum Player Count", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Auto-start will begin once this player count is reached. Default: 15" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 1, // min + 150, // max + 15, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,minMissionTime), + "SLIDER", // setting type + [ + "Required Duration to Save", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "A recording must be at least this long (in minutes) to save. Default: 20" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 1, // min + 120, // max + 20, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + + + + +// RECORDING SETTINGS +[ + QEGVAR(settings,frameCaptureDelay), + "SLIDER", // setting type + [ + "Frame Capture Delay", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds. Default: 1" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 0.25, // min + 10, // max + 1, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,preferACEUnconscious), + "CHECKBOX", // setting type + [ + "Use ACE3 Medical", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If true, will check ACE3 medical status on units. If false, or ACE3 isn't loaded, fall back to vanilla. Default: true" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,excludeClassFromRecord), + "EDITBOX", // setting type + [ + "Classnames to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,excludeKindFromRecord), + "EDITBOX", // setting type + [ + "Object KindOfs to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "[]", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,excludeMarkerFromRecord), + "EDITBOX", // setting type + [ + "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_']" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "['SystemMarker_']", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,trackTimes), + "CHECKBOX", // setting type + [ + "Enable Mission Time Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Will continuously track in-game world time during a mission. Useful for accelerated/skipped time scenarios. Default: false" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + false, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + +[ + QEGVAR(settings,trackTimeInterval), + "SLIDER", // setting type + [ + "Mission Time Tracking Interval", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If time tracking is enabled, it will be checked every X capture frames. Default: 10" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 5, // min + 25, // max + 10, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + + + +// SAVING SETTINGS +[ + QEGVAR(settings,saveMissionEnded), + "CHECKBOX", // setting type + [ + "Auto-save on MPEnded Event", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If true, automatically save and export the mission when the MPEnded event fires. Default: true" + ], + [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + + + +// DEBUG +[ + QGVARMAIN(enabled), + "CHECKBOX", // setting type + [ + "Debug Mode", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Enables increased logging of addon actions. Default: false" + ], + [COMPONENT_NAME, "Debug"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + false, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; +[ + QGVARMAIN(isDebug), + "CHECKBOX", // setting type + [ + "Debug Mode", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Enables increased logging of addon actions. Default: false" + ], + [COMPONENT_NAME, "Debug"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + false, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; + + +ADDON = true; diff --git a/x/ocap2/addons/main/XEH_preInit.sqf b/x/ocap2/addons/main/XEH_preInit.sqf new file mode 100644 index 0000000..56b9c7f --- /dev/null +++ b/x/ocap2/addons/main/XEH_preInit.sqf @@ -0,0 +1,224 @@ +#include "script_component.hpp" +#include "XEH_prep.sqf" + +// AUTO START SETTINGS +[ + QEGVAR(settings,autoStart), + "CHECKBOX", // setting type + [ + "Auto Start Recording", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Automatically start OCAP recordings at session start. Default: true" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,minPlayerCount), + "SLIDER", // setting type + [ + "Minimum Player Count", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Auto-start will begin once this player count is reached. Default: 15" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 1, // min + 150, // max + 15, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply +] call CBA_fnc_addSetting; + + + +// RECORDING SETTINGS +[ + QEGVAR(settings,frameCaptureDelay), + "SLIDER", // setting type + [ + "Frame Capture Delay", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds. Default: 1" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 0.25, // min + 10, // max + 1, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,minMissionTime), + "SLIDER", // setting type + [ + "Required Duration to Save", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "A recording must be at least this long (in minutes) to save. Default: 20" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 1, // min + 120, // max + 20, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,preferACEUnconscious), + "CHECKBOX", // setting type + [ + "Use ACE3 Medical", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If true, will check ACE3 medical status on units. If false, or ACE3 isn't loaded, fall back to vanilla. Default: true" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,excludeClassFromRecord), + "EDITBOX", // setting type + [ + "Classnames to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,excludeKindFromRecord), + "EDITBOX", // setting type + [ + "Object KindOfs to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "[]", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,excludeMarkerFromRecord), + "EDITBOX", // setting type + [ + "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_']" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "['SystemMarker_']", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,trackTimes), + "CHECKBOX", // setting type + [ + "Enable Mission Time Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Will continuously track in-game world time during a mission. Useful for accelerated/skipped time scenarios. Default: false" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + false, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + +[ + QEGVAR(settings,trackTimeInterval), + "SLIDER", // setting type + [ + "Mission Time Tracking Interval", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If time tracking is enabled, it will be checked every X capture frames. Default: 10" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 5, // min + 25, // max + 10, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + + + +// SAVING SETTINGS +[ + QEGVAR(settings,saveMissionEnded), + "CHECKBOX", // setting type + [ + "Auto-save on MPEnded Event", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If true, automatically save and export the mission when the MPEnded event fires. Default: true" + ], + [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply +] call CBA_fnc_addSetting; + + + +// DEBUG +[ + QGVARMAIN(enabled), + "CHECKBOX", // setting type + [ + "Recording Enabled", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Turns on or off all recording functionality. Will not reset anything from existing session, will just stop recording any new data. Default: true" + ], + [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + { + params ["_value"]; + if (!isServer) exitWith {}; + EFUNC(recorder,init); + }, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; +[ + QGVARMAIN(isDebug), + "CHECKBOX", // setting type + [ + "Debug Mode", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Enables increased logging of addon actions. Default: false" + ], + [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + false, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply +] call CBA_fnc_addSetting; + + +ADDON = true; diff --git a/x/ocap2/addons/main/XEH_prep.sqf b/x/ocap2/addons/main/XEH_prep.sqf new file mode 100644 index 0000000..421c54b --- /dev/null +++ b/x/ocap2/addons/main/XEH_prep.sqf @@ -0,0 +1 @@ +#include "script_component.hpp" diff --git a/x/ocap2/addons/main/config.cpp b/x/ocap2/addons/main/config.cpp new file mode 100644 index 0000000..f0ff80a --- /dev/null +++ b/x/ocap2/addons/main/config.cpp @@ -0,0 +1,32 @@ +#define _ARMA_ +#include "script_component.hpp" + +class CfgPatches +{ + class ADDON + { + + name = COMPONENT_NAME; + author = "Dell, Zealot, Kurt, IndigoFox, Fank"; + authors[] = {"Dell", "Zealot", "Kurt", "IndigoFox", "Fank"}; + url = "https://github.com/OCAP2/OCAP"; + VERSION_CONFIG; + requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh"}; + units[] = {}; + weapons[] = {}; + }; +}; + +class Extended_PreInit_EventHandlers { + class ADDON { + // This will be executed once in 3DEN, main menu and before briefing has started for every mission + init = QUOTE( call COMPILE_FILE(XEH_preInit) ); + }; +}; + +class Extended_PostInit_EventHandlers { + class ADDON { + // This will be executed once for each mission, once the mission has started + init = QUOTE( call COMPILE_FILE(XEH_postInit) ); + }; +}; diff --git a/x/ocap2/addons/main/script_component.hpp b/x/ocap2/addons/main/script_component.hpp new file mode 100644 index 0000000..9f444aa --- /dev/null +++ b/x/ocap2/addons/main/script_component.hpp @@ -0,0 +1,4 @@ +#define COMPONENT main +#define COMPONENT_BEAUTIFIED Main + +#include "script_macros.hpp" diff --git a/addons/@ocap/addons/ocap/script_macros.hpp b/x/ocap2/addons/main/script_macros.hpp similarity index 59% rename from addons/@ocap/addons/ocap/script_macros.hpp rename to x/ocap2/addons/main/script_macros.hpp index f8a91f6..8b18ae3 100644 --- a/addons/@ocap/addons/ocap/script_macros.hpp +++ b/x/ocap2/addons/main/script_macros.hpp @@ -1,25 +1,26 @@ // Header: script_macros.hpp // Defines macros imported to other functions -#include "\userconfig\ocap\config.hpp" +#define PREFIX OCAP2 + +#ifdef COMPONENT_BEAUTIFIED + #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT_BEAUTIFIED) +#else + #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT) +#endif -// define: REQUIRED_VERSION -// The version of Arma required to run this mod correctly. -#define REQUIRED_VERSION 2.04 -// define: OCAP_ADDON_VERSION // The current version of OCAP2. -#define OCAP_ADDON_VERSION "1.2.0-alpha" +#define VERSION 1.0 +#define VERSION_STR 1.0.0 +#define VERSION_AR 1,0,0 +#define VERSION_REQUIRED 2.08 + // define: LOG // Used for logging messages via the extension. -#define LOG(_args) [":LOG:", _args] call ocap_fnc_extension +#define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData) -// define: DEBUG -// Conditional, used for logging debug messages when is true. -#if ocap_isDebug - #define DEBUG(_args) [":LOG:", _args] call ocap_fnc_extension -#else - #define DEBUG(_args) /* disabled */ -#endif +// #define DEBUG_MODE_NORMAL +#define DEBUG_MODE_FULL // define: BOOL // Forces a true/false return of input. @@ -32,3 +33,9 @@ #define ARR4(_arg1, _arg2, _arg3, _arg4) [_arg1, _arg2, _arg3, _arg4] #define ARR5(_arg1, _arg2, _arg3, _arg4, _arg5) [_arg1, _arg2, _arg3, _arg4, _arg5] #define ARR6(_arg1, _arg2, _arg3, _arg4, _arg5, _arg6) [_arg1, _arg2, _arg3, _arg4, _arg5, _arg6] + + + + +#include "\x\cba\addons\main\script_macros_common.hpp" +#include "\x\cba\addons\xeh\script_xeh.hpp" diff --git a/addons/@ocap/mod.cpp b/x/ocap2/addons/mod.cpp similarity index 100% rename from addons/@ocap/mod.cpp rename to x/ocap2/addons/mod.cpp diff --git a/x/ocap2/addons/recorder/XEH_postInit.sqf b/x/ocap2/addons/recorder/XEH_postInit.sqf new file mode 100644 index 0000000..521017f --- /dev/null +++ b/x/ocap2/addons/recorder/XEH_postInit.sqf @@ -0,0 +1,8 @@ +#include "script_component.hpp" +#include "XEH_prep.sqf" + +if (!EGVAR(settings,autoStart)) then { + call FUNC(init); +}; + +ADDON = true; diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap2/addons/recorder/XEH_preInit.sqf new file mode 100644 index 0000000..d9174b5 --- /dev/null +++ b/x/ocap2/addons/recorder/XEH_preInit.sqf @@ -0,0 +1,8 @@ +#include "script_component.hpp" +#include "XEH_prep.sqf" + +if (EGVAR(settings,autoStart) && !is3DEN) then { + call FUNC(init); +}; + +ADDON = true; diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap2/addons/recorder/XEH_prep.sqf new file mode 100644 index 0000000..9f9d41e --- /dev/null +++ b/x/ocap2/addons/recorder/XEH_prep.sqf @@ -0,0 +1,13 @@ +PREP(init); + +PREP(updateTime); + +PREP(captureLoop); +PREP(isKindOfApc); +PREP(getClass); +PREP(getUnitType); + +PREP(addEventMission); +PREP(handleCustomEvent); + +PREP(exportData); diff --git a/x/ocap2/addons/recorder/config.cpp b/x/ocap2/addons/recorder/config.cpp new file mode 100644 index 0000000..b43487e --- /dev/null +++ b/x/ocap2/addons/recorder/config.cpp @@ -0,0 +1,32 @@ +#define _ARMA_ +#include "script_component.hpp" + +class CfgPatches +{ + class ADDON + { + + name = COMPONENT_NAME; + author = "Dell, Zealot, Kurt, IndigoFox, Fank"; + authors[] = {"Dell", "Zealot", "Kurt", "IndigoFox", "Fank"}; + url = "https://github.com/OCAP2/OCAP"; + VERSION_CONFIG; + requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh","ocap2_main","ocap2_extension"}; + units[] = {}; + weapons[] = {}; + }; +}; + +class Extended_PreInit_EventHandlers { + class ADDON { + // This will be executed once in 3DEN, main menu and before briefing has started for every mission + init = QUOTE( call COMPILE_FILE(XEH_preInit) ); + }; +}; + +class Extended_PostInit_EventHandlers { + class ADDON { + // This will be executed once for each mission, once the mission has started + init = QUOTE( call COMPILE_FILE(XEH_postInit) ); + }; +}; diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf new file mode 100644 index 0000000..cfb899b --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -0,0 +1,76 @@ +/* ---------------------------------------------------------------------------- +Script: ocap_fnc_addEventMission + +Description: + Used for applying mission event handlers. + + * Applied during initialization of OCAP2 in . + +Parameters: + None + +Returns: + Nothing + +Examples: + --- Code + call ocap_fnc_addEventMission; + --- + +Public: + No + +Author: + IndigoFox, Dell +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +addMissionEventHandler["HandleDisconnect", { + _this call ocap_fnc_eh_disconnected; +}]; + +addMissionEventHandler["PlayerConnected", { + _this call ocap_fnc_eh_connected; +}]; + +addMissionEventHandler ["EntityKilled", { + _this call ocap_fnc_eh_killed; +}]; + +addMissionEventHandler ["EntityRespawned", { + params ["_entity", "_corpse"]; + + // Reset unit back to normal + _entity setvariable ["ocapIsKilled", false]; + + // Stop tracking old unit + if (_corpse getVariable [QGVARMAIN(isInitialized), false]) then { + _corpse setVariable [QGVARMAIN(exclude), true]; + + [_entity, true] spawn ocap_fnc_addEventHandlers; + }; +}]; + +if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then { + call ocap_fnc_trackAceExplPlace; +}; + +addMissionEventHandler ["MPEnded", { + if (EGVAR(settings,saveMissionEnded)) then { + ["Mission ended automatically"] call FUNC(exportData); + }; +}]; + +// Add event saving markers +call ocap_fnc_handleMarkers; + +// Custom event handler with key "ocap2_customEvent" +// Used for showing custom events in playback events list +EGVAR(listener,customEvent) = [QGVARMAIN(customEvent), { + _this call FUNC(handleCustomEvent); +}] call CBA_fnc_addEventHandler; + +// Custom event handler with key "ocap2_exportData" +EGVAR(listener,exportData) = [QGVARMAIN(exportData), { + _this call FUNC(exportData); +}] call CBA_fnc_addEventHandler; diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf new file mode 100644 index 0000000..1f697f2 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -0,0 +1,240 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(captureLoop) + +Description: + Iterates through units, declares they exist, and conditional records their state at an interval defined in userconfig.hpp. + + This is the core processing loop that determines when new units enter the world, all the details about them, classifies which to exclude, and determines their health/life status. It has both unit and vehicle tracking. + + This is spawned during . + +Parameters: + None + +Returns: + Nothing + +Examples: + --- Code + 0 spawn FUNC(captureLoop); + --- + +Public: + No + +Author: + Dell, Zealot, IndigoFox, Fank +---------------------------------------------------------------------------- */ + +#include "script_component.hpp" + +if (!isNil QGVAR(PFHObject)) then { + [GVAR(PFHObject)] call CBA_fnc_deletePerFrameHandlerObject; + GVAR(PFHObject) = nil; +} else { + GVAR(startTime) = time; + LOG(ARR3(__FILE__, QGVAR(capturing) + " started, time:", GVAR(startTime))); +}; + +GVAR(PFHObject) = [ + { + if (!isNil {_private#0}) then { + if (EGVAR(settings,frameCaptureDelay) != _private#0) exitWith { + OCAPEXTLOG(ARR_3("Frame capture delay changed", _private#0, EGVAR(settings,frameCaptureDelay))); + TRACE_3("Frame capture delay changed", _private#0, EGVAR(settings,frameCaptureDelay)); + GVAR(capturing) = false; + [{call FUNC(startCaptureLoop)}, [], EGVAR(settings,frameCaptureDelay) + _private#0] call CBA_fnc_waitAndExecute; + }; + }; + _frameCaptureDelay = EGVAR(settings,frameCaptureDelay); + + TRACE_2("Frame", _frameCaptureDelay); + if (GVAR(captureFrameNo) == 10 || (GVAR(captureFrameNo) > 10 && EGVAR(settings,trackTimes) && GVAR(captureFrameNo) % EGVAR(settings,trackTimeInterval) == 0)) then { + [] call FUNC(updateTime); + }; + + if (GVAR(captureFrameNo) % (60 / EGVAR(settings,frameCaptureDelay)) == 0) then { + publicVariable QGVAR(captureFrameNo); + { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + ("Capture frame: " + QGVAR(captureFrameNo) + "") + ] + ]; + } remoteExecCall ["call", 0, false]; + }; + + { + if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { + if (_x isKindOf "Logic") exitWith { + _x setVariable [QGVARMAIN(exclude), true]; + _x setVariable [QGVARMAIN(isInitialized), true]; + }; + _x setVariable [QGVARMAIN(id), _id]; + [":NEW:UNIT:", [ + GVAR(captureFrameNo), //1 + GVAR(nextId), //2 + name _x, //3 + groupID (group _x), //4 + str side group _x, //5 + BOOL(isPlayer _x), //6 + roleDescription _x // 7 + ]] call EFUNC(extension,sendData); + [_x] spawn ocap_fnc_addEventHandlers; + GVAR(nextId) = GVAR(nextId) + 1; + _x setVariable [QGVARMAIN(isInitialized), true]; + }; + if !(_x getVariable [QGVARMAIN(exclude), false]) then { + private _unitRole = _x getVariable [QGVARMAIN(unitType), ""]; + if (GVAR(captureFrameNo) % 10 == 0 || _unitRole == "") then { + _unitRole = [_x] call FUNC(getUnitType); + _x setVariable [QGVARMAIN(unitType), _unitRole]; + }; + + private _lifeState = 0; + if (alive _x) then { + if (EGVAR(settings,preferACEUnconscious) && !isNil "ace_common_fnc_isAwake") then { + _lifeState = if ([_x] call ace_common_fnc_isAwake) then {1} else {2}; + } else { + _lifeState = if (lifeState _x isEqualTo "INCAPACITATED") then {2} else {1}; + }; + }; + + _pos = getPosASL _x; + [":UPDATE:UNIT:", [ + (_x getVariable QGVARMAIN(id)), //1 + _pos, //2 + round getDir _x, //3 + _lifeState, //4 + BOOL(!((vehicle _x) isEqualTo _x)), //5 + if (alive _x) then {name _x} else {""}, //6 + BOOL(isPlayer _x), //7 + _unitRole //8 + ]] call EFUNC(extension,sendData); + }; + false + } count (allUnits + allDeadMen); + + { + if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { + _vehType = typeOf _x; + _class = _vehType call FUNC(getClass); + _toExcludeKind = false; + if (count (parseSimpleArray EGVAR(settings,excludeKindFromRecord)) > 0) then { + private _vic = _x; + { + if (_vic isKindOf _x) exitWith { + _toExcludeKind = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeKindFromRecord)); + }; + if ((_class isEqualTo "unknown") || (_vehType in (parseSimpleArray EGVAR(settings,excludeClassFromRecord))) || _toExcludeKind) exitWith { + LOG(ARR2("WARNING: vehicle is defined as 'unknown' or exclude:", _vehType)); + _x setVariable [QGVARMAIN(isInitialized), true]; + _x setVariable [QGVARMAIN(exclude), true]; + }; + + _x setVariable [QGVARMAIN(id), _id]; + [":NEW:VEH:", [ + GVAR(captureFrameNo), //1 + _id, //2 + _class, //3 + getText (configFile >> "CfgVehicles" >> _vehType >> "displayName") //4 + ]] call EFUNC(extension,sendData); + [_x] spawn ocap_fnc_addEventHandlers; + _id = _id + 1; + _x setVariable [QGVARMAIN(isInitialized), true]; + }; + if !(_x getVariable [QGVARMAIN(exclude), false]) then { + private _crew = []; + { + if (_x getVariable [QGVARMAIN(isInitialized), false]) then { + _crew pushBack (_x getVariable QGVARMAIN(id)); + }; false + } count (crew _x); + _pos = getPosASL _x; + [":UPDATE:VEH:", [ + (_x getVariable QGVARMAIN(id)), //1 + _pos, //2 + round getDir _x, //3 + BOOL(alive _x), //4 + _crew, //5 + GVAR(captureFrameNo) // 6 + ]] call EFUNC(extension,sendData); + }; + false + } count vehicles; + + GVAR(captureFrameNo) = GVAR(captureFrameNo) + 1; + }, + EGVAR(settings,frameCaptureDelay), // delay + [], // args + { + GVAR(capturing) = true; + + { // add diary entry for clients on recording start + [{!isNull player}, { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + "OCAP2 began recording." + ], taskNull, "", false + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ]; + }] call CBA_fnc_waitUntilAndExecute; + } remoteExecCall ["call", 0, true]; + }, // code, executed when added + { + GVAR(capturing) = false; + + { // add diary entry for clients on recording start + [{!isNull player}, { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + "OCAP2 stopped recording." + ], taskNull, "", false + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ]; + }] call CBA_fnc_waitUntilAndExecute; + } remoteExecCall ["call", 0, true]; + }, // code, executed when removed + {GVAR(capturing)}, // if true, execute PFH cycle + {!GVAR(capturing) || !GVARMAIN(enabled)}, // if true, delete object + ["_frameCaptureDelay"] +] call CBA_fnc_createPerFrameHandlerObject; + + + + + + + + + + + + + + + + + + + + + + + + +waitUntil{(count(allPlayers) >= EGVAR(settings,minPlayerCount))}; diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf new file mode 100644 index 0000000..dca8622 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -0,0 +1,139 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(exportData) + +Description: + This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component. + + This function MUST be called in order to save a mission recording. A boolean true in the correct option of userconfig.hpp will automatically execute this function when the "MPEnded" Event Handler triggers. + +Parameters: + _side - The winning side [optional, Side] + _message - A custom description of how the victory was achieved [optional, String] + _tag - A custom tag to override that which is defined in userconfig.hpp that will make it filterable in web [optional, String] + +Returns: + Nothing + +Examples: + --- Code + // "Mission ended" + [] call FUNC(exportData); + + // "BLUFOR Win." + [west] call FUNC(exportData); + + // "OPFOR Win. OPFOR controlled all sectors! + [east, "OPFOR controlled all sectors!"] call FUNC(exportData); + + // "Independent Win. INDFOR stole the intel!" + // Mission is saved under filterable "SnatchAndGrab" tag on web + [independent, "INDFOR stole the intel!", "SnatchAndGrab"] call FUNC(exportData); + --- + +Public: + Yes + +Author: + Dell, Zealot, IndigoFox, TyroneMF +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_side", "_message", "_tag", ["_overrideLimits", false, [false]]]; +// overrideLimits will bypass any restriction checks, in case someone wants to save a mission even if doesn't meet their usual criteria + + +if (isNil QGVAR(startTime)) exitWith { + // if recording hasn't started, there's nothing to save + LOG(["Export data call received, but recording of this session hasn't yet started."]); + + { + [{!isNull player}, { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + "OCAP2 was asked to save, but recording hasn't started yet." + ], taskNull, "", false + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ]; + }] call CBA_fnc_waitUntilAndExecute; + } remoteExecCall ["call", 0, true]; +}; + + +_elapsedTime = time - GVAR(startTime); +_frameTimeDuration = EGVAR(settings,frameCaptureDelay) * GVAR(captureFrameNo); +TRACE_7("Save attempted. Elapsed Time =", _elapsedTime," Frame Count * Delay Duration =", _frameTimeDuration," delta =", _elapsedTime - _frameTimeDuration); + + +if (_frameTimeDuration < EGVAR(settings,minMissionTime) && !_overrideLimits) exitWith { + // if the total duration in minutes is not met based on how many frames have been recorded & the frame capture delay, + // then we won't save, but will continue recording in case admins want to save once that threshold is met. + LOG("Save attempted, but the minimum recording duration hasn't been met. Not saving, continuing to record."); + { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + ( + "OCAP2 capture of " + briefingName + " has not yet reached the minimum duration to save it. Recording will continue." + ) + ] + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\danger_ca.paa" + ]; + } remoteExec ["call", 0, false]; +}; + +GVAR(capturing) = false; +GVAR(endFrameNumber) = GVAR(captureFrameNo); + +publicVariable QGVAR(endFrameNumber); + + +switch (count _this) do { + case 0: { + [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", "Mission ended"]]] call EFUNC(extension,sendData); + }; + case 1: { + [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", _side]]] call EFUNC(extension,sendData); + }; + default { + private _sideString = str(_side); + if (_side == sideUnknown) then { _sideString = "" }; + [":EVENT:", [GVAR(endFrameNumber), "endMission", [_sideString, _message]]] call EFUNC(extension,sendData); + }; +}; + + +if (!isNil "_tag") then { + [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay), GVAR(endFrameNumber), _tag]] call EFUNC(extension,sendData); + LOG(ARR4("Saved recording of mission", GVAR(missionName), "with tag", _tag)); +} else { + [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay), GVAR(endFrameNumber)]] call EFUNC(extension,sendData); + LOG(ARR3("Saved recording of mission", GVAR(missionName), "with default tag")); +}; + +// briefingName is used here, no need for publicVariable for a simple confirmation log. +{ + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + ( + "OCAP2 capture of " + briefingName + " has been exported with " + str(GVAR(endFrameNumber)) + " frames saved." + + "

" + + "Upload results have been logged." + ) + ] + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\upload_ca.paa" + ]; +} remoteExec ["call", 0, false]; diff --git a/x/ocap2/addons/recorder/fnc_getClass.sqf b/x/ocap2/addons/recorder/fnc_getClass.sqf new file mode 100644 index 0000000..4568e34 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getClass.sqf @@ -0,0 +1,14 @@ +#include "script_component.hpp" + +if (_this isKindOf "Truck_F") exitWith {"truck"}; // Should be higher than Car +if (_this call FUNC(isKindOfApc)) exitWith {"apc"}; +if (_this isKindOf "Car") exitWith {"car"}; +if (_this isKindOf "Tank") exitWith {"tank"}; +if (_this isKindOf "StaticMortar") exitWith {"static-mortar"}; +if (_this isKindOf "StaticWeapon") exitWith {"static-weapon"}; +if (_this isKindOf "ParachuteBase") exitWith {"parachute"}; +if (_this isKindOf "Helicopter") exitWith {"heli"}; +if (_this isKindOf "Plane") exitWith {"plane"}; +if (_this isKindOf "Air") exitWith {"plane"}; +if (_this isKindOf "Ship") exitWith {"sea"}; +"unknown" diff --git a/x/ocap2/addons/recorder/fnc_getDelay.sqf b/x/ocap2/addons/recorder/fnc_getDelay.sqf new file mode 100644 index 0000000..9b151c2 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getDelay.sqf @@ -0,0 +1,41 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(exportData) + +Description: + Determines the the appropriate interval at which to loop the function. + + Устанавливает точную задержку между кадрами + +Parameters: + None + +Returns: + Sleep duration [Number] + +Examples: + --- Code + call ocap_fnc_getDelay; + --- + +Public: + No + +Author: + Dell +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +private "_sleep"; +isNil { + _elapsedTime = time - GVAR(startTime); + _sleep = (GVAR(captureFrameNo) + 1) * EGVAR(settings,frameCaptureDelay) - _elapsedTime; + + if ((GVAR(captureFrameNo) % 10) isEqualTo 0) then { + LOG(ARR4("DEBUG: Frame", GVAR(captureFrameNo), "is created in ~", EGVAR(settings,frameCaptureDelay) - _sleep)); + }; + if (_sleep < 0) then { + LOG(ARR3("ERROR: Frame delay is negative", GVAR(captureFrameNo), _sleep)); + _sleep = 0.4; + }; +}; +_sleep diff --git a/x/ocap2/addons/recorder/fnc_getUnitType.sqf b/x/ocap2/addons/recorder/fnc_getUnitType.sqf new file mode 100644 index 0000000..8004c6b --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getUnitType.sqf @@ -0,0 +1,77 @@ +/* ---------------------------------------------------------------------------- +Script: ocap_fnc_getUnitType + +Description: + Identifies the role of a unit using similar methodology to Arma 3's. Used in . + +Parameters: + _unitToCheck - Unit to evaluate. [Object] + +Returns: + The role text. [String] + +Examples: + --- Code + [_x] call ocap_fnc_getUnitType; + --- + +Public: + No + +Author: + IndigoFox +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_unitToCheck"]; + +private _role = "Man"; +private _typePic = getText (configOf _unitToCheck >> "icon"); + + +switch (true) do { + case ( + ["Officer", _typePic] call BIS_fnc_inString + ): {_role = "Officer"}; + case ( + _unitToCheck == leader group _unitToCheck + ): {_role = "Leader"}; +}; + +if (_role == "Man") then { + switch (true) do { + case (_unitToCheck getUnitTrait 'medic'): { + _role = 'Medic'; + }; + case (_unitToCheck getUnitTrait 'engineer'): { + _role = 'Engineer'; + }; + case (_unitToCheck getUnitTrait 'explosiveSpecialist'): { + _role = 'ExplosiveSpecialist'; + }; + }; +}; + +if (_role == "Man") then { + private _weaponPicture = toLower getText (configFile >> "CfgWeapons" >> secondaryWeapon _unitToCheck >> "UiPicture"); + switch (true) do { + case ("_mg_" in _weaponPicture): {_role = "MG"}; + case ("_gl_" in _weaponPicture): {_role = "GL"}; + case ("_at_" in _weaponPicture): {_role = "AT"}; + case ("_sniper_" in _weaponPicture): {_role = "Sniper"}; + case ("_aa_" in _weaponPicture): {_role = "AA"}; + }; +}; + +if (_role == "Man") then { + private _weaponPicture = toLower getText (configFile >> "CfgWeapons" >> primaryWeapon _unitToCheck >> "UiPicture"); + switch (true) do { + case ("_mg_" in _weaponPicture): {_role = "MG"}; + case ("_gl_" in _weaponPicture): {_role = "GL"}; + case ("_at_" in _weaponPicture): {_role = "AT"}; + case ("_sniper_" in _weaponPicture): {_role = "Sniper"}; + case ("_aa_" in _weaponPicture): {_role = "AA"}; + }; +}; + +_role diff --git a/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf b/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf new file mode 100644 index 0000000..7efc0b1 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf @@ -0,0 +1,54 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(handleCustomEvent) + +Description: + Used for applying global event handlers. + + * Applied during initialization of OCAP2 in . + +Parameters: + _type - objective type that will define the text & icon [String, one of: "flag"] + _unit - name of the unit that performed the action [String] + _unitColor - color for the unit's name shown in Events list and for the pulse on the map [String, Hex RGB, defaults "" and will show as white] + _objectiveColor - color representing the icon in Events list [String, Hex RGB, defaults "" and will show as white] + _position - the location to pulse on the map [optional, PositionATL, default nil] + +Returns: + Nothing + +Examples: + --- Code + ["ocap_handleCustomEvent", ["eventType", "eventMessage"]] call CBA_fnc_serverEvent; + + // indicates a flag has been captured + ["ocap_handleCustomEvent", ["captured", [ + "flag", + name _unit, + str side group _unit, + "#FF0000", + getPosAtl _flag + ]]] call call CBA_fnc_serverEvent; + + + // Not yet implemented + ["ocap_handleCustomEvent", ["captured", [ + "sector", + name _unit, + str side group _unit, + "#FF0000", + getPosAtl _sectorObject + ]]] call call CBA_fnc_serverEvent; + --- + +Public: + Yes + +Author: + Fank, Zealot +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_eventName", "_eventMessage"]; +[":EVENT:", + [GVAR(captureFrameNo), _eventName, _eventMessage] +] call EFUNC(extension,sendData); diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf new file mode 100644 index 0000000..a02dc75 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -0,0 +1,95 @@ +/* ---------------------------------------------------------------------------- +Script: ocap_fnc_init + +Description: + Automatic Start: Called from ocap_fnc_autoStart. + Manual Start: Server execution to begin. + +Parameters: + None + +Returns: + Nothing + +Examples: + --- Code + call ocap_fnc_init; + --- + +Public: + No + +Author: + Dell, Zealot, IndigoFox +---------------------------------------------------------------------------- */ + +#include "script_component.hpp" + +// bool: GVAR(capturing) +GVAR(capturing) = false; +// int: GVAR(captureFrameNo) +GVAR(captureFrameNo) = 0; +// bool: GVAR(shouldSave) +GVAR(shouldSave) = false; + +// macro: GVARMAIN(version)SION +GVARMAIN(version) = QUOTE(VERSION_STR); +publicVariable QGVARMAIN(version); + +EGVAR(extension,version) = ([":VERSION:", []] call EFUNC(extension,sendData)); +publicVariable QEGVAR(extension,version); + + +// remoteExec diary creation commands to clients listing version numbers and waiting start state +{ + [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { + player createDiarySubject ["OCAP2Info", "OCAP2 AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; + + ocap_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; + EGVAR(diary,about) = player createDiaryRecord [ + "OCAP2Info", + [ + "About", + ( + "OCAP2
" + + "Addon version: " + GVARMAIN(version) + + "
" + + "Extension version: " + (EGVAR(extension,version) # 0) + " (built " + (EGVAR(extension,version) # 1) + ")" + + "
" + + "https://github.com/OCAP2/OCAP" + + "

" + + "OCAP2 is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + + "

" + + "Recording status can be found in the Status section." + + "

" + + "DISCLAIMER: This mission may be recorded and made publicly available at the discretion of the server administrators. Please be aware that your actions during this mission will be tracked and attributed to your in-game username." + ) + ] + ]; + + EGVAR(diary,status) = player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + "OCAP2 initialized." + ] + ]; + }] call CBA_fnc_waitUntilAndExecute; +} remoteExecCall ["call", 0, true]; + + +// Support both methods of setting mission name. +GVAR(missionName) = getMissionConfigValue ["onLoadName", ""]; +if (GVAR(missionName) == "") then { + GVAR(missionName) = briefingName; +}; + +// Add event missions +call FUNC(addEventMission); +[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay)]] call EFUNC(extension,sendData); +[] call FUNC(updateTime); +GVAR(nextId) = 0; +0 spawn FUNC(captureLoop); + + +[":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); diff --git a/x/ocap2/addons/recorder/fnc_isKindOfApc.sqf b/x/ocap2/addons/recorder/fnc_isKindOfApc.sqf new file mode 100644 index 0000000..189377c --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_isKindOfApc.sqf @@ -0,0 +1,9 @@ +#include "script_component.hpp" + +_bool = false; +{ + if (_this isKindOf _x) exitWith {_bool = true;}; + false; +} count ["Wheeled_APC_F","Tracked_APC","APC_Wheeled_01_base_F","APC_Wheeled_02_base_F", +"APC_Wheeled_03_base_F","APC_Tracked_01_base_F","APC_Tracked_02_base_F","APC_Tracked_03_base_F"]; +_bool diff --git a/addons/@ocap/addons/ocap/functions/fn_updateTime.sqf b/x/ocap2/addons/recorder/fnc_updateTime.sqf similarity index 70% rename from addons/@ocap/addons/ocap/functions/fn_updateTime.sqf rename to x/ocap2/addons/recorder/fnc_updateTime.sqf index 0383625..62b5113 100644 --- a/addons/@ocap/addons/ocap/functions/fn_updateTime.sqf +++ b/x/ocap2/addons/recorder/fnc_updateTime.sqf @@ -1,8 +1,8 @@ /* ---------------------------------------------------------------------------- -Script: ocap_fnc_updateTime +Script: FUNC(updateTime) Description: - Sends server's system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension. Will run on a recurring basis as part of if the setting in userconfig.hpp is configured to do so. This is required in missions that utilize time acceleration or have time skips as part of mission flow. + Sends server's system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension. Will run on a recurring basis as part of if the setting in userconfig.hpp is configured to do so. This is required in missions that utilize time acceleration or have time skips as part of mission flow. Parameters: _date - A manual in-game time to check. [optional, Array] @@ -12,7 +12,7 @@ Returns: Examples: --- Code - [] call ocap_fnc_updateTime; + [] call FUNC(updateTime); --- Public: @@ -22,7 +22,7 @@ Author: Fank ---------------------------------------------------------------------------- */ -#include "script_macros.hpp" +#include "script_component.hpp" params [ ["_date", []] @@ -37,9 +37,9 @@ if (_date isEqualTo []) then { _missionDateFormat append (_date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); [":TIME:", [ - ocap_captureFrameNo, + GVAR(captureFrameNo), format _systemTimeFormat, format _missionDateFormat, timeMultiplier, time -]] call ocap_fnc_extension; +]] call EFUNC(extension,sendData); diff --git a/addons/@ocap/addons/ocap/functions/fn_addEventHandlers.sqf b/x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf similarity index 94% rename from addons/@ocap/addons/ocap/functions/fn_addEventHandlers.sqf rename to x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf index 2ad7878..e58572a 100644 --- a/addons/@ocap/addons/ocap/functions/fn_addEventHandlers.sqf +++ b/x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf @@ -4,7 +4,7 @@ Script: ocap_fnc_addEventHandlers Description: Used for applying unit-specific event handlers to units during initialization. These event handlers will trigger on the server. - Applied during initialization of a unit in . + Applied during initialization of a unit in . Parameters: _entity - Object to apply event handlers to. [Object] diff --git a/addons/@ocap/addons/ocap/functions/fn_autoStart.sqf b/x/ocap2/addons/recorder/functions/fn_autoStart.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_autoStart.sqf rename to x/ocap2/addons/recorder/functions/fn_autoStart.sqf diff --git a/x/ocap2/addons/recorder/functions/fn_eh_connected.sqf b/x/ocap2/addons/recorder/functions/fn_eh_connected.sqf new file mode 100644 index 0000000..fecb2fd --- /dev/null +++ b/x/ocap2/addons/recorder/functions/fn_eh_connected.sqf @@ -0,0 +1,3 @@ +[":EVENT:", + [GVAR(captureFrameNo), "connected", _this select 2] +] call EFUNC(extension,sendData); diff --git a/x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf b/x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf new file mode 100644 index 0000000..8d6e247 --- /dev/null +++ b/x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf @@ -0,0 +1,9 @@ +params ["_unit", "_id", "_uid", "_name"]; + +[":EVENT:", + [GVAR(captureFrameNo), "disconnected", _name] +] call EFUNC(extension,sendData); + +if (_unit getVariable [QGVARMAIN(isInitialized), false]) then { + _unit setVariable [QGVARMAIN(exclude), true]; +}; diff --git a/addons/@ocap/addons/ocap/functions/fn_eh_fired.sqf b/x/ocap2/addons/recorder/functions/fn_eh_fired.sqf similarity index 98% rename from addons/@ocap/addons/ocap/functions/fn_eh_fired.sqf rename to x/ocap2/addons/recorder/functions/fn_eh_fired.sqf index f38aade..829d636 100644 --- a/addons/@ocap/addons/ocap/functions/fn_eh_fired.sqf +++ b/x/ocap2/addons/recorder/functions/fn_eh_fired.sqf @@ -30,7 +30,7 @@ Author: params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; -_frame = ocap_captureFrameNo; +_frame = GVAR(captureFrameNo); // if (!isServer) exitWith {}; @@ -55,10 +55,10 @@ if (_ammoSimType isEqualTo "shotBullet") then { if !((count _lastPos) isEqualTo 0) then { [":FIRED:", [ - (_firer getVariable "ocap_id"), + (_firer getVariable QGVARMAIN(id)), _frame, _lastPos - ]] call ocap_fnc_extension; + ]] call EFUNC(extension,sendData); }; }; diff --git a/addons/@ocap/addons/ocap/functions/fn_eh_hit.sqf b/x/ocap2/addons/recorder/functions/fn_eh_hit.sqf similarity index 84% rename from addons/@ocap/addons/ocap/functions/fn_eh_hit.sqf rename to x/ocap2/addons/recorder/functions/fn_eh_hit.sqf index 9881a7d..1d72ae6 100644 --- a/addons/@ocap/addons/ocap/functions/fn_eh_hit.sqf +++ b/x/ocap2/addons/recorder/functions/fn_eh_hit.sqf @@ -34,13 +34,13 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; _instigator = [_unit, _causedBy] call ocap_fnc_getInstigator; }; - _unitID = _unit getVariable ["ocap_id", -1]; + _unitID = _unit getVariable [QGVARMAIN(id), -1]; if (_unitID == -1) exitWith {}; - private _eventData = [ocap_captureFrameNo, "hit", _unitID, ["null"], -1]; + private _eventData = [GVAR(captureFrameNo), "hit", _unitID, ["null"], -1]; if (!isNull _instigator) then { - _causedById = _causedBy getVariable ["ocap_id", -1]; - _instigatorId = _instigator getVariable ["ocap_id", -1]; + _causedById = _causedBy getVariable [QGVARMAIN(id), -1]; + _instigatorId = _instigator getVariable [QGVARMAIN(id), -1]; private _causedByInfo = []; private _distanceInfo = 0; @@ -63,7 +63,7 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; }; }; _eventData = [ - ocap_captureFrameNo, + GVAR(captureFrameNo), "hit", _unitID, _causedByInfo, @@ -72,5 +72,5 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; }; DEBUG(_eventData); - [":EVENT:", _eventData] call ocap_fnc_extension; + [":EVENT:", _eventData] call EFUNC(extension,sendData); }; diff --git a/addons/@ocap/addons/ocap/functions/fn_eh_killed.sqf b/x/ocap2/addons/recorder/functions/fn_eh_killed.sqf similarity index 84% rename from addons/@ocap/addons/ocap/functions/fn_eh_killed.sqf rename to x/ocap2/addons/recorder/functions/fn_eh_killed.sqf index a6d9fb0..4aab3c9 100644 --- a/addons/@ocap/addons/ocap/functions/fn_eh_killed.sqf +++ b/x/ocap2/addons/recorder/functions/fn_eh_killed.sqf @@ -46,13 +46,13 @@ if !(_victim getvariable ["ocapIsKilled",false]) then { _instigator = [_victim, _killer] call ocap_fnc_getInstigator; }; - // [ocap_captureFrameNo, "killed", _victimId, ["null"], -1]; - private _victimId = _victim getVariable ["ocap_id", -1]; + // [GVAR(captureFrameNo), "killed", _victimId, ["null"], -1]; + private _victimId = _victim getVariable [QGVARMAIN(id), -1]; if (_victimId == -1) exitWith {}; - private _eventData = [ocap_captureFrameNo, "killed", _victimId, ["null"], -1]; + private _eventData = [GVAR(captureFrameNo), "killed", _victimId, ["null"], -1]; if (!isNull _instigator) then { - _killerId = _instigator getVariable ["ocap_id", -1]; + _killerId = _instigator getVariable [QGVARMAIN(id), -1]; if (_killerId == -1) exitWith {}; private _killerInfo = []; @@ -66,7 +66,7 @@ if !(_victim getvariable ["ocapIsKilled",false]) then { }; _eventData = [ - ocap_captureFrameNo, + GVAR(captureFrameNo), "killed", _victimId, _killerInfo, @@ -75,6 +75,6 @@ if !(_victim getvariable ["ocapIsKilled",false]) then { }; DEBUG(_eventData); - [":EVENT:", _eventData] call ocap_fnc_extension; + [":EVENT:", _eventData] call EFUNC(extension,sendData); }; }; diff --git a/addons/@ocap/addons/ocap/functions/fn_getEventWeaponText.sqf b/x/ocap2/addons/recorder/functions/fn_getEventWeaponText.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_getEventWeaponText.sqf rename to x/ocap2/addons/recorder/functions/fn_getEventWeaponText.sqf diff --git a/addons/@ocap/addons/ocap/functions/fn_getInstigator.sqf b/x/ocap2/addons/recorder/functions/fn_getInstigator.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_getInstigator.sqf rename to x/ocap2/addons/recorder/functions/fn_getInstigator.sqf diff --git a/addons/@ocap/addons/ocap/functions/fn_handleMarkers.sqf b/x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf similarity index 90% rename from addons/@ocap/addons/ocap/functions/fn_handleMarkers.sqf rename to x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf index ed0c753..02369ad 100644 --- a/addons/@ocap/addons/ocap/functions/fn_handleMarkers.sqf +++ b/x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf @@ -94,7 +94,7 @@ ocap_markers_handle = ["ocap_handleMarker", { _sideOfMarker = -1; } else { _sideOfMarker = (side _mrk_owner) call BIS_fnc_sideID; - _mrk_owner = _mrk_owner getVariable["ocap_id", 0]; + _mrk_owner = _mrk_owner getVariable[QGVARMAIN(id), 0]; }; if (_sideOfMarker isEqualTo 4 || @@ -119,13 +119,13 @@ ocap_markers_handle = ["ocap_handleMarker", { _dir = 0; } else {if (_dir isEqualTo "") then {_dir = 0}}; - private _captureFrameNo = ocap_captureFrameNo; + private _captureFrameNo = GVAR(captureFrameNo); if (_creationTime > 0) then { private _delta = time - _creationTime; - private _lastFrameTime = (ocap_captureFrameNo * ocap_frameCaptureDelay) + ocap_startTime; + private _lastFrameTime = (GVAR(captureFrameNo) * EGVAR(settings,frameCaptureDelay)) + GVAR(startTime); if (_delta > (time - _lastFrameTime)) then { // marker was initially created in some frame(s) before - _captureFrameNo = ceil _lastFrameTime - (_delta / ocap_frameCaptureDelay); - private _logParams = (str [ocap_captureFrameNo, time, _creationTime, _delta, _lastFrameTime, _captureFrameNo]); + _captureFrameNo = ceil _lastFrameTime - (_delta / EGVAR(settings,frameCaptureDelay)); + private _logParams = (str [GVAR(captureFrameNo), time, _creationTime, _delta, _lastFrameTime, _captureFrameNo]); DEBUG(ARR2("CREATE:MARKER: adjust frame ", _logParams)); }; }; @@ -133,7 +133,7 @@ ocap_markers_handle = ["ocap_handleMarker", { private _logParams = (str [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]); DEBUG(ARR4("CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension -- ", _logParams)); - [":MARKER:CREATE:", [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]] call ocap_fnc_extension; + [":MARKER:CREATE:", [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]] call EFUNC(extension,sendData); }; case "UPDATED":{ @@ -142,11 +142,11 @@ ocap_markers_handle = ["ocap_handleMarker", { if (isNil "_dir") then {_dir = 0}; if (ocap_isDebug) then { - private _logParams = str [_mrk_name, ocap_captureFrameNo, _pos, _dir, _alpha]; + private _logParams = str [_mrk_name, GVAR(captureFrameNo), _pos, _dir, _alpha]; DEBUG(ARR4("MARKER:MOVE: Valid UPDATED process of", _mrk_name, ", sending to extension -- ", _logParams)); }; - [":MARKER:MOVE:", [_mrk_name, ocap_captureFrameNo, _pos, _dir, _alpha]] call ocap_fnc_extension; + [":MARKER:MOVE:", [_mrk_name, GVAR(captureFrameNo), _pos, _dir, _alpha]] call EFUNC(extension,sendData); }; }; @@ -154,7 +154,7 @@ ocap_markers_handle = ["ocap_handleMarker", { if (_mrk_name in ocap_markers_tracked) then { DEBUG(ARR3("MARKER:DELETE: Marker", _mrk_name, "deleted")); - [":MARKER:DELETE:", [_mrk_name, ocap_captureFrameNo]] call ocap_fnc_extension; + [":MARKER:DELETE:", [_mrk_name, GVAR(captureFrameNo)]] call EFUNC(extension,sendData); ocap_markers_tracked = ocap_markers_tracked - [_mrk_name]; }; }; @@ -175,7 +175,7 @@ ocap_markers_handle = ["ocap_handleMarker", { // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server // if value is undefined, then skip private _isExcluded = false; - if (!isNil "ocap_excludeMarkerFromRecord") then { + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { { if ((str _marker) find _x >= 0) exitWith { _isExcluded = true; @@ -216,7 +216,7 @@ ocap_markers_handle = ["ocap_handleMarker", { // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server // if value is undefined, then skip private _isExcluded = false; - if (!isNil "ocap_excludeMarkerFromRecord") then { + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { { if ((str _marker) find _x >= 0) exitWith { _isExcluded = true; @@ -239,7 +239,7 @@ ocap_markers_handle = ["ocap_handleMarker", { // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server // if value is undefined, then skip private _isExcluded = false; - if (!isNil "ocap_excludeMarkerFromRecord") then { + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { { if ((str _marker) find _x > 0) exitWith { _isExcluded = true; diff --git a/x/ocap2/addons/recorder/functions/fn_init.sqf b/x/ocap2/addons/recorder/functions/fn_init.sqf new file mode 100644 index 0000000..72e011d --- /dev/null +++ b/x/ocap2/addons/recorder/functions/fn_init.sqf @@ -0,0 +1,106 @@ +/* ---------------------------------------------------------------------------- +Script: ocap_fnc_init + +Description: + Automatic Start: Called from ocap_fnc_autoStart. + Manual Start: Server execution to begin. + +Parameters: + None + +Returns: + Nothing + +Examples: + --- Code + call ocap_fnc_init; + --- + +Public: + No + +Author: + Dell, Zealot, IndigoFox +---------------------------------------------------------------------------- */ + +#include "script_macros.hpp" + +// bool: GVAR(capturing) +GVAR(capturing) = false; +// int: GVAR(captureFrameNo) +GVAR(captureFrameNo) = 0; +// bool: GVAR(shouldSave) +GVAR(shouldSave) = [false, true] select (EGVAR(settings,minMissionTime) < 10); + +if (ocap_excludeMarkerFromRecord isEqualType []) then { + publicVariable QEGVAR(settings,excludeMarkerFromRecord); +} else { + LOG(["excludeMarkerFromRecord in config is not an array, skipping exclusions"]); +}; + +// macro: GVARMAIN(version)SION +GVARMAIN(version) = QUOTE(VERSION_STR); +publicVariable QGVARMAIN(version); + +EGVAR(extension,version) = ([":VERSION:", []] call EFUNC(extension,sendData)); +publicVariable QEGVAR(extension,version); + + +{ + [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { + player createDiarySubject ["OCAP2Info", "OCAP2 AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; + + ocap_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; + EGVAR(diary,about) = player createDiaryRecord [ + "OCAP2Info", + [ + "About", + ( + "OCAP2
" + + "Addon version: " + GVARMAIN(version) + + "
" + + "Extension version: " + (EGVAR(extension,version) # 0) + " (built " + (EGVAR(extension,version) # 1) + ")" + + "
" + + "https://github.com/OCAP2/OCAP" + + "

" + + "OCAP2 is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + + "

" + + "Recording status can be found in the Status section." + + "

" + + "DISCLAIMER: This mission may be recorded and made publicly available at the discretion of the server administrators. Please be aware that your actions during this mission will be tracked and attributed to your in-game username." + ) + ] + ]; + + EGVAR(diary,status) = player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + "OCAP2 initialized. Awaiting configured capture conditions." + ] + ]; + }] call CBA_fnc_waitUntilAndExecute; +} remoteExecCall ["call", 0, true]; + +// Support both methods of setting mission name. +GVAR(missionName) = getMissionConfigValue ["onLoadName", ""]; +if (GVAR(missionName) == "") then { + GVAR(missionName) = briefingName; +}; + +// Add event missions +call FUNC(addEventMission); +[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay)]] call EFUNC(extension,sendData); +0 spawn FUNC(captureLoop); + +[":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); + +0 spawn { + if (GVAR(shouldSave)) exitWith {}; + LOG(["Waiting freeze end!"]); + waitUntil {sleep 1.4}; + LOG(["Waiting until EGVAR(settings,minMissionTime) ends"]); + sleep EGVAR(settings,minMissionTime); + LOG(["Recording duration minimum has been exceeded. will save."]); + GVAR(shouldSave) = true; +}; diff --git a/addons/@ocap/addons/ocap/functions/fn_startCaptureLoop.sqf b/x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf similarity index 69% rename from addons/@ocap/addons/ocap/functions/fn_startCaptureLoop.sqf rename to x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf index ddee340..579602b 100644 --- a/addons/@ocap/addons/ocap/functions/fn_startCaptureLoop.sqf +++ b/x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf @@ -1,5 +1,5 @@ /* ---------------------------------------------------------------------------- -Script: ocap_fnc_startCaptureLoop +Script: FUNC(captureLoop) Description: Iterates through units, declares they exist, and conditional records their state at an interval defined in userconfig.hpp. @@ -16,7 +16,7 @@ Returns: Examples: --- Code - 0 spawn ocap_fnc_startCaptureLoop; + 0 spawn FUNC(captureLoop); --- Public: @@ -62,10 +62,10 @@ private _getClass = { waitUntil{(count(allPlayers) >= ocap_minPlayerCount)}; -// bool: ocap_capture -ocap_capture = true; -ocap_startTime = time; -LOG(ARR3(__FILE__, "ocap_capture start, time:", ocap_startTime)); +// bool: GVAR(capturing) +GVAR(capturing) = true; +GVAR(startTime) = time; +LOG(ARR3(__FILE__, "GVAR(capturing) start, time:", GVAR(startTime))); { [{!isNull player}, { @@ -84,53 +84,53 @@ LOG(ARR3(__FILE__, "ocap_capture start, time:", ocap_startTime)); } remoteExecCall ["call", 0, true]; -[] call ocap_fnc_updateTime; +[] call FUNC(updateTime); private _id = 0; -while {ocap_capture} do { +while {GVAR(capturing)} do { isNil { - if (ocap_captureFrameNo == 10 || (ocap_captureFrameNo > 10 && ocap_trackTimes && ocap_captureFrameNo % ocap_trackTimeInterval == 0)) then { - [] call ocap_fnc_updateTime; + if (GVAR(captureFrameNo) == 10 || (GVAR(captureFrameNo) > 10 && ocap_trackTimes && GVAR(captureFrameNo) % ocap_trackTimeInterval == 0)) then { + [] call FUNC(updateTime); }; - if (ocap_captureFrameNo % (60 / ocap_frameCaptureDelay) == 0) then { - publicVariable "ocap_captureFrameNo"; + if (GVAR(captureFrameNo) % (60 / EGVAR(settings,frameCaptureDelay)) == 0) then { + publicVariable "GVAR(captureFrameNo)"; { player createDiaryRecord [ "OCAP2Info", [ "Status", - ("Capture frame: " + str(ocap_captureFrameNo) + "") + ("Capture frame: " + str(GVAR(captureFrameNo)) + "") ] ]; } remoteExecCall ["call", 0, false]; }; { - if !(_x getVariable ["ocap_isInitialised", false]) then { + if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { if (_x isKindOf "Logic") exitWith { - _x setVariable ["ocap_exclude", true]; - _x setVariable ["ocap_isInitialised", true]; + _x setVariable [QGVARMAIN(exclude), true]; + _x setVariable [QGVARMAIN(isInitialized), true]; }; - _x setVariable ["ocap_id", _id]; + _x setVariable [QGVARMAIN(id), _id]; [":NEW:UNIT:", [ - ocap_captureFrameNo, //1 + GVAR(captureFrameNo), //1 _id, //2 name _x, //3 groupID (group _x), //4 str side group _x, //5 BOOL(isPlayer _x), //6 roleDescription _x // 7 - ]] call ocap_fnc_extension; + ]] call EFUNC(extension,sendData); [_x] spawn ocap_fnc_addEventHandlers; _id = _id + 1; - _x setVariable ["ocap_isInitialised", true]; + _x setVariable [QGVARMAIN(isInitialized), true]; }; - if !(_x getVariable ["ocap_exclude", false]) then { - private _unitRole = _x getVariable ["ocap_unitType", ""]; - if (ocap_captureFrameNo % 10 == 0 || _unitRole == "") then { + if !(_x getVariable [QGVARMAIN(exclude), false]) then { + private _unitRole = _x getVariable [QGVARMAIN(unitType), ""]; + if (GVAR(captureFrameNo) % 10 == 0 || _unitRole == "") then { _unitRole = [_x] call ocap_fnc_getUnitType; - _x setVariable ["ocap_unitType", _unitRole]; + _x setVariable [QGVARMAIN(unitType), _unitRole]; }; private _lifeState = 0; @@ -144,7 +144,7 @@ while {ocap_capture} do { _pos = getPosASL _x; [":UPDATE:UNIT:", [ - (_x getVariable "ocap_id"), //1 + (_x getVariable QGVARMAIN(id)), //1 _pos, //2 round getDir _x, //3 _lifeState, //4 @@ -152,13 +152,13 @@ while {ocap_capture} do { if (alive _x) then {name _x} else {""}, //6 BOOL(isPlayer _x), //7 _unitRole //8 - ]] call ocap_fnc_extension; + ]] call EFUNC(extension,sendData); }; false } count (allUnits + allDeadMen); { - if !(_x getVariable ["ocap_isInitialised", false]) then { + if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { _vehType = typeOf _x; _class = _vehType call _getClass; _toExcludeKind = false; @@ -172,41 +172,41 @@ while {ocap_capture} do { }; if ((_class isEqualTo "unknown") || (_vehType in ocap_excludeClassFromRecord) || _toExcludeKind) exitWith { LOG(ARR2("WARNING: vehicle is defined as 'unknown' or exclude:", _vehType)); - _x setVariable ["ocap_isInitialised", true]; - _x setVariable ["ocap_exclude", true]; + _x setVariable [QGVARMAIN(isInitialized), true]; + _x setVariable [QGVARMAIN(exclude), true]; }; - _x setVariable ["ocap_id", _id]; + _x setVariable [QGVARMAIN(id), _id]; [":NEW:VEH:", [ - ocap_captureFrameNo, //1 + GVAR(captureFrameNo), //1 _id, //2 _class, //3 getText (configFile >> "CfgVehicles" >> _vehType >> "displayName") //4 - ]] call ocap_fnc_extension; + ]] call EFUNC(extension,sendData); [_x] spawn ocap_fnc_addEventHandlers; _id = _id + 1; - _x setVariable ["ocap_isInitialised", true]; + _x setVariable [QGVARMAIN(isInitialized), true]; }; - if !(_x getVariable ["ocap_exclude", false]) then { + if !(_x getVariable [QGVARMAIN(exclude), false]) then { private _crew = []; { - if (_x getVariable ["ocap_isInitialised", false]) then { - _crew pushBack (_x getVariable "ocap_id"); + if (_x getVariable [QGVARMAIN(isInitialized), false]) then { + _crew pushBack (_x getVariable QGVARMAIN(id)); }; false } count (crew _x); _pos = getPosASL _x; [":UPDATE:VEH:", [ - (_x getVariable "ocap_id"), //1 + (_x getVariable QGVARMAIN(id)), //1 _pos, //2 round getDir _x, //3 BOOL(alive _x), //4 _crew, //5 - ocap_captureFrameNo // 6 - ]] call ocap_fnc_extension; + GVAR(captureFrameNo) // 6 + ]] call EFUNC(extension,sendData); }; false } count vehicles; }; sleep (call ocap_fnc_getDelay); - ocap_captureFrameNo = ocap_captureFrameNo + 1; + GVAR(captureFrameNo) = GVAR(captureFrameNo) + 1; }; diff --git a/addons/@ocap/addons/ocap/functions/fn_trackAceExplLife.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceExplLife.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_trackAceExplLife.sqf rename to x/ocap2/addons/recorder/functions/fn_trackAceExplLife.sqf diff --git a/addons/@ocap/addons/ocap/functions/fn_trackAceExplPlace.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceExplPlace.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_trackAceExplPlace.sqf rename to x/ocap2/addons/recorder/functions/fn_trackAceExplPlace.sqf diff --git a/addons/@ocap/addons/ocap/functions/fn_trackAceRemoteDet.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceRemoteDet.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_trackAceRemoteDet.sqf rename to x/ocap2/addons/recorder/functions/fn_trackAceRemoteDet.sqf diff --git a/addons/@ocap/addons/ocap/functions/fn_trackAceThrowing.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceThrowing.sqf similarity index 100% rename from addons/@ocap/addons/ocap/functions/fn_trackAceThrowing.sqf rename to x/ocap2/addons/recorder/functions/fn_trackAceThrowing.sqf diff --git a/addons/@ocap/addons/ocap/functions/markerTestingChecklist.txt b/x/ocap2/addons/recorder/functions/markerTestingChecklist.txt similarity index 100% rename from addons/@ocap/addons/ocap/functions/markerTestingChecklist.txt rename to x/ocap2/addons/recorder/functions/markerTestingChecklist.txt diff --git a/x/ocap2/addons/recorder/functions/script_macros.hpp b/x/ocap2/addons/recorder/functions/script_macros.hpp new file mode 100644 index 0000000..e430f34 --- /dev/null +++ b/x/ocap2/addons/recorder/functions/script_macros.hpp @@ -0,0 +1 @@ +#include "\ocap2\script_macros.hpp" diff --git a/x/ocap2/addons/recorder/script_component.hpp b/x/ocap2/addons/recorder/script_component.hpp new file mode 100644 index 0000000..05bac07 --- /dev/null +++ b/x/ocap2/addons/recorder/script_component.hpp @@ -0,0 +1,4 @@ +#define COMPONENT recorder +#define COMPONENT_BEAUTIFIED Recorder + +#include "\x\ocap2\addons\main\script_macros.hpp" From 8a26ce294350898e0817e409ae8b079724772efb Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Sat, 26 Mar 2022 22:04:11 -0400 Subject: [PATCH 02/26] ready for debugging --- licence.txt | 17 + logo_ocap.paa | Bin 0 -> 24169 bytes mod.cpp | 22 + x/ocap2/addons/main/XEH_postInit.sqf | 219 +--------- x/ocap2/addons/main/XEH_preInit.sqf | 397 +++++++++--------- x/ocap2/addons/main/script_macros.hpp | 1 + x/ocap2/addons/recorder/XEH_prep.sqf | 17 + x/ocap2/addons/recorder/fnc_aceExplosives.sqf | 97 +++++ x/ocap2/addons/recorder/fnc_aceThrowing.sqf | 151 +++++++ .../addons/recorder/fnc_addEventMission.sqf | 29 +- .../recorder/fnc_addUnitEventHandlers.sqf | 44 ++ x/ocap2/addons/recorder/fnc_captureLoop.sqf | 49 +-- x/ocap2/addons/recorder/fnc_eh_connected.sqf | 5 + ...sconnected.sqf => fnc_eh_disconnected.sqf} | 2 + x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 182 ++++++++ x/ocap2/addons/recorder/fnc_eh_hit.sqf | 76 ++++ x/ocap2/addons/recorder/fnc_eh_killed.sqf | 86 ++++ x/ocap2/addons/recorder/fnc_exportData.sqf | 3 +- .../recorder/fnc_getEventWeaponText.sqf | 67 +++ x/ocap2/addons/recorder/fnc_getInstigator.sqf | 48 +++ x/ocap2/addons/recorder/fnc_handleMarkers.sqf | 308 ++++++++++++++ x/ocap2/addons/recorder/fnc_init.sqf | 21 +- .../addons/recorder/fnc_startRecording.sqf | 29 ++ .../functions/fn_addEventHandlers.sqf | 41 -- .../recorder/functions/fn_autoStart.sqf | 27 -- .../recorder/functions/fn_eh_connected.sqf | 3 - .../addons/recorder/functions/fn_eh_fired.sqf | 179 -------- .../addons/recorder/functions/fn_eh_hit.sqf | 76 ---- .../recorder/functions/fn_eh_killed.sqf | 80 ---- .../functions/fn_getEventWeaponText.sqf | 66 --- .../recorder/functions/fn_getInstigator.sqf | 47 --- .../recorder/functions/fn_handleMarkers.sqf | 292 ------------- x/ocap2/addons/recorder/functions/fn_init.sqf | 106 ----- .../functions/fn_startCaptureLoop.sqf | 212 ---------- .../functions/fn_trackAceExplLife.sqf | 71 ---- .../functions/fn_trackAceExplPlace.sqf | 34 -- .../functions/fn_trackAceRemoteDet.sqf | 30 -- .../functions/fn_trackAceThrowing.sqf | 142 ------- .../functions/markerTestingChecklist.txt | 19 - .../recorder/functions/script_macros.hpp | 1 - 40 files changed, 1409 insertions(+), 1887 deletions(-) create mode 100644 licence.txt create mode 100644 logo_ocap.paa create mode 100644 mod.cpp create mode 100644 x/ocap2/addons/recorder/fnc_aceExplosives.sqf create mode 100644 x/ocap2/addons/recorder/fnc_aceThrowing.sqf create mode 100644 x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf create mode 100644 x/ocap2/addons/recorder/fnc_eh_connected.sqf rename x/ocap2/addons/recorder/{functions/fn_eh_disconnected.sqf => fnc_eh_disconnected.sqf} (88%) create mode 100644 x/ocap2/addons/recorder/fnc_eh_firedMan.sqf create mode 100644 x/ocap2/addons/recorder/fnc_eh_hit.sqf create mode 100644 x/ocap2/addons/recorder/fnc_eh_killed.sqf create mode 100644 x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf create mode 100644 x/ocap2/addons/recorder/fnc_getInstigator.sqf create mode 100644 x/ocap2/addons/recorder/fnc_handleMarkers.sqf create mode 100644 x/ocap2/addons/recorder/fnc_startRecording.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_autoStart.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_eh_connected.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_eh_fired.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_eh_hit.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_eh_killed.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_getEventWeaponText.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_getInstigator.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_init.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_trackAceExplLife.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_trackAceExplPlace.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_trackAceRemoteDet.sqf delete mode 100644 x/ocap2/addons/recorder/functions/fn_trackAceThrowing.sqf delete mode 100644 x/ocap2/addons/recorder/functions/markerTestingChecklist.txt delete mode 100644 x/ocap2/addons/recorder/functions/script_macros.hpp diff --git a/licence.txt b/licence.txt new file mode 100644 index 0000000..ba93af7 --- /dev/null +++ b/licence.txt @@ -0,0 +1,17 @@ +Copyright (C) 2016 Jamie Goodson (aka MisterGoodson) (goodsonjamie@yahoo.co.uk) + +References to "this program" include all files, folders, and subfolders +bundled with this license file. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/logo_ocap.paa b/logo_ocap.paa new file mode 100644 index 0000000000000000000000000000000000000000..aa72d97b36ef93ecf553b7fc7aefb277f655b849 GIT binary patch literal 24169 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 "CfgAmmo" >> _explType >> "defaultMagazine"); + _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); + + _placedPos = getPosASL _explosive; + _unit addOwnedMine _explosive; + + _markTextLocal = format["%1", _explosiveDisp]; + _markName = format["%1#%2/%3", QGVARMAIN(mine), _int, _placedPos]; + _markColor = "ColorRed"; + _markerType = "Minefield"; + + [QGVARMAIN(handleMarker), [ + "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true + ]] call CBA_fnc_localEvent; + + TRACE_2("Created explosive placed marker ", _markName); + if (GVARMAIN(isDebug)) then { + ("Created explosive placed marker " + _markName) SYSCHAT; + }; + + + [{isNull (_this#0)}, { // wait until the mine is null (exploded), and mark this for playback + + params ["_explosive", "_explosiveDisp", "_unit", "_placedPos", "_markName", "_int"]; + + // remove previous marker + ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_localEvent; + + TRACE_2("Removed explosive placed marker ", _markName); + if (GVARMAIN(isDebug)) then { + ("Removed explosive placed marker " + _markName) SYSCHAT; + }; + + _markTextLocal = format["%1", _explosiveDisp]; + _markName = format["Detonation#%1", _int]; + _markColor = "ColorRed"; + _markerType = "waypoint"; + + [QGVARMAIN(handleMarker), [ + "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true + ]] call CBA_fnc_localEvent; + + TRACE_2("Created explosive explosion marker ", _markName); + if (GVARMAIN(isDebug)) then { + ("Created explosive explosion " + _markName) SYSCHAT; + }; + + [{ + params ["_markName"]; + [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent; + TRACE_2("Removed explosive explosion marker ", _markName); + if (GVARMAIN(isDebug)) then { + ("Removed explosive explosion marker " + _markName) SYSCHAT; + }; + }, [_markName], 10] call CBA_fnc_waitAndExecute; + + }, [_explosive, _explosiveDisp, _unit, _placedPos, _markName, _int]] call CBA_fnc_waitUntilAndExecute; + +}] call CBA_fnc_addEventHandler; diff --git a/x/ocap2/addons/recorder/fnc_aceThrowing.sqf b/x/ocap2/addons/recorder/fnc_aceThrowing.sqf new file mode 100644 index 0000000..ce805e3 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_aceThrowing.sqf @@ -0,0 +1,151 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(aceThrowing) + +Description: + Adds a local CBA event listener on units that will trigger when a projectile is thrown using ACE Advanced Throwing and add markers to playback that trace its path. Added to units in . + +Parameters: + None + +Returns: + Nothing + +Examples: + --- Code + FUNC(aceThrowing) remoteExec ["call", _entity]; + --- + +Public: + No + +Author: + IndigoFox +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +EGVAR(listener,aceThrowing) = ["ace_throwableThrown", { + + _this spawn { + + params["_unit", "_projectile"]; + + if (isNull _projectile) then { + _projectile = nearestObject [_unit, "CA_Magazine"]; + }; + + // systemChat str _this; + + // note that thrown objects outside of ACE explosives do not include a "default magazine" property in their config. + // this script will attempt to find a matching classname in CfgMagazines, as some chemlights and smokes are built this way. + // if not found, a default magazine value will be assigned (m67 frag, white smoke, green chemlight) + + _projType = typeOf _projectile; + _projConfig = configOf _projectile; + _projName = getText(configFile >> "CfgAmmo" >> _projType >> "displayName"); + + TRACE_2("Detected ACE throwing of ", _projName); + if (GVARMAIN(isDebug)) then { + ("Detected ACE throwing of " + _projName) SYSCHAT; + }; + + // systemChat format["Config name: %1", configOf _projectile]; + + _ammoSimType = getText(configFile >> "CfgAmmo" >> _projType >> "simulation"); + // systemChat format["Projectile type: %1", _ammoSimType]; + + _markerType = ""; + _markColor = ""; + _magDisp = ""; + _magPic = ""; + + _magType = getText(_projConfig >> "defaultMagazine"); + if (_magType == "") then { + _magType = configName(configfile >> "CfgMagazines" >> _projType) + }; + + if (!(_magType isEqualTo "")) then { + // systemChat format["Mag type: %1", _magType]; + + _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayNameShort"); + if (_magDisp == "") then { + _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayName") + }; + if (_magDisp == "") then { + _magDisp = _projName; + }; + + _magPic = (getText(configfile >> "CfgMagazines" >> _magType >> "picture")); + // hint parseText format["Projectile fired:
", _magPic]; + if (_magPic == "") then { + _markerType = "mil_triangle"; + _markColor = "ColorRed"; + } else { + _magPicSplit = _magPic splitString "\"; + _magPic = _magPicSplit#((count _magPicSplit) - 1); + _markerType = format["magIcons/%1", _magPic]; + _markColor = "ColorWhite"; + }; + } else { + _markerType = "mil_triangle"; + _markColor = "ColorRed"; + // set defaults based on ammo sim type, if no magazine could be matched + switch (_ammoSimType) do { + case "shotGrenade":{ + _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; + _magDisp = "Frag"; + }; + case "shotSmokeX":{ + _magPic = "\A3\Weapons_f\data\ui\gear_smokegrenade_white_ca.paa"; + _magDisp = "Smoke"; + }; + case "shotIlluminating":{ + _magPic = "\A3\Weapons_F\Data\UI\gear_flare_white_ca.paa"; + _magDisp = "Flare"; + }; + default { + _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; + _magDisp = "Frag"; + }; + }; + // hint parseText format["Projectile fired:
", _magPic]; + _magPicSplit = _magPic splitString "\"; + _magPic = _magPicSplit#((count _magPicSplit) - 1); + _markerType = format["magIcons/%1", _magPic]; + _markColor = "ColorWhite"; + }; + + if (!(_ammoSimType isEqualTo "shotBullet")) then { + + _int = random 2000; + + _markTextLocal = format["%1", _magDisp]; + _markName = format["Projectile#%1", _int]; + + _throwerPos = getPosASL _unit; + + [QGVARMAIN(handleMarker), ["CREATED", _markName, _unit, _throwerPos, _markerType, "ICON", [1,1], 0, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_serverEvent; + + private _lastPos = []; + waitUntil { + _pos = getPosASL _projectile; + if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { + true + }; + _lastPos = _pos; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _unit, _pos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; + + // here, monitor fast moving missiles/rockets/shells every 0.3 seconds. monitor smokes, grenades, flares, mines in line with the configured frame capture delay + sleep ([0.3, EGVAR(settings,frameCaptureDelay)] select {_ammoSimType in ["ShotSmokeX","ShotGrenade","ShotIlluminating","ShotMine"]}); + false; + }; + + if !((count _lastPos) isEqualTo 0) then { + // if (count _lastPos == 3) then { + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _unit, _lastPos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; + }; + + sleep 10; + [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_serverEvent; + }; + }; +}] call CBA_fnc_addEventHandler; diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf index cfb899b..194a10d 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -26,33 +26,33 @@ Author: #include "script_component.hpp" addMissionEventHandler["HandleDisconnect", { - _this call ocap_fnc_eh_disconnected; + _this call FUNC(eh_disconnected); }]; addMissionEventHandler["PlayerConnected", { - _this call ocap_fnc_eh_connected; + _this call FUNC(eh_connected); }]; addMissionEventHandler ["EntityKilled", { - _this call ocap_fnc_eh_killed; + _this call FUNC(eh_killed); }]; addMissionEventHandler ["EntityRespawned", { params ["_entity", "_corpse"]; // Reset unit back to normal - _entity setvariable ["ocapIsKilled", false]; + _entity setvariable [QGVARMAIN(isKilled), false]; // Stop tracking old unit if (_corpse getVariable [QGVARMAIN(isInitialized), false]) then { _corpse setVariable [QGVARMAIN(exclude), true]; - [_entity, true] spawn ocap_fnc_addEventHandlers; + [_entity, true] spawn FUNC(addUnitEventHandlers); }; }]; if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then { - call ocap_fnc_trackAceExplPlace; + call FUNC(aceExplosives); }; addMissionEventHandler ["MPEnded", { @@ -62,7 +62,7 @@ addMissionEventHandler ["MPEnded", { }]; // Add event saving markers -call ocap_fnc_handleMarkers; +call FUNC(handleMarkers); // Custom event handler with key "ocap2_customEvent" // Used for showing custom events in playback events list @@ -70,7 +70,22 @@ EGVAR(listener,customEvent) = [QGVARMAIN(customEvent), { _this call FUNC(handleCustomEvent); }] call CBA_fnc_addEventHandler; +// Custom event handler with key "ocap2_record" +// This will START OR RESUME recording if not already. +EGVAR(listener,exportData) = [QGVARMAIN(record), { + call FUNC(startRecording); +}] call CBA_fnc_addEventHandler; + +// Custom event handler with key "ocap2_pause" +// This will PAUSE recording +EGVAR(listener,exportData) = [QGVARMAIN(pause), { + GVAR(recording) = false; +}] call CBA_fnc_addEventHandler; + // Custom event handler with key "ocap2_exportData" +// This will export the mission immediately regardless of restrictions. +// params ["_side", "_message", "_tag"]; EGVAR(listener,exportData) = [QGVARMAIN(exportData), { + _this set [3, true]; _this call FUNC(exportData); }] call CBA_fnc_addEventHandler; diff --git a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf new file mode 100644 index 0000000..467a828 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf @@ -0,0 +1,44 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(addUnitEventHandlers) + +Description: + Used for applying unit-specific event handlers to units during initialization. These event handlers will trigger on the server. + + Applied during initialization of a unit in . + +Parameters: + _entity - Object to apply event handlers to. [Object] + _respawn - Determines if unit is initialized for the first time, or has respawned and does not need certain handlers reapplied. [Boolean, defaults to false] + +Returns: + Nothing + +Examples: + --- Code + [_unit] spawn FUNC(addUnitEventHandlers); + --- + +Public: + No + +Author: + IndigoFox, Fank +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_entity", ["_respawn", false]]; + +if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then { + _entity addEventHandler ["FiredMan", { _this spawn FUNC(eh_firedMan); }]; +}; +_entity addMPEventHandler ["MPHit", { _this spawn FUNC(eh_hit); }]; + +if ( + !_respawn && + (_entity call BIS_fnc_objectType) # 0 == "Soldier" && + isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing") +) then { + // here, we must place a local listener of ACE throw events on the owner of the entity + // the client will then notify the server when such an event happens + FUNC(aceThrowing) remoteExec ["call", _entity]; +}; diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf index 1f697f2..d812cf1 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -31,9 +31,10 @@ Author: if (!isNil QGVAR(PFHObject)) then { [GVAR(PFHObject)] call CBA_fnc_deletePerFrameHandlerObject; GVAR(PFHObject) = nil; -} else { +}; +if (isNil QGVAR(startTime)) then { GVAR(startTime) = time; - LOG(ARR3(__FILE__, QGVAR(capturing) + " started, time:", GVAR(startTime))); + LOG(ARR3(__FILE__, QGVAR(recording) + " started, time:", GVAR(startTime))); }; GVAR(PFHObject) = [ @@ -42,7 +43,7 @@ GVAR(PFHObject) = [ if (EGVAR(settings,frameCaptureDelay) != _private#0) exitWith { OCAPEXTLOG(ARR_3("Frame capture delay changed", _private#0, EGVAR(settings,frameCaptureDelay))); TRACE_3("Frame capture delay changed", _private#0, EGVAR(settings,frameCaptureDelay)); - GVAR(capturing) = false; + GVAR(recording) = false; [{call FUNC(startCaptureLoop)}, [], EGVAR(settings,frameCaptureDelay) + _private#0] call CBA_fnc_waitAndExecute; }; }; @@ -53,7 +54,8 @@ GVAR(PFHObject) = [ [] call FUNC(updateTime); }; - if (GVAR(captureFrameNo) % (60 / EGVAR(settings,frameCaptureDelay)) == 0) then { + // update diary record every 320 frames + if (GVAR(captureFrameNo) % (320 / EGVAR(settings,frameCaptureDelay)) == 0) then { publicVariable QGVAR(captureFrameNo); { player createDiaryRecord [ @@ -72,7 +74,7 @@ GVAR(PFHObject) = [ _x setVariable [QGVARMAIN(exclude), true]; _x setVariable [QGVARMAIN(isInitialized), true]; }; - _x setVariable [QGVARMAIN(id), _id]; + _x setVariable [QGVARMAIN(id), GVAR(nextId)]; [":NEW:UNIT:", [ GVAR(captureFrameNo), //1 GVAR(nextId), //2 @@ -82,7 +84,7 @@ GVAR(PFHObject) = [ BOOL(isPlayer _x), //6 roleDescription _x // 7 ]] call EFUNC(extension,sendData); - [_x] spawn ocap_fnc_addEventHandlers; + [_x] spawn FUNC(addUnitEventHandlers); GVAR(nextId) = GVAR(nextId) + 1; _x setVariable [QGVARMAIN(isInitialized), true]; }; @@ -143,7 +145,7 @@ GVAR(PFHObject) = [ _class, //3 getText (configFile >> "CfgVehicles" >> _vehType >> "displayName") //4 ]] call EFUNC(extension,sendData); - [_x] spawn ocap_fnc_addEventHandlers; + [_x] spawn FUNC(addUnitEventHandlers); _id = _id + 1; _x setVariable [QGVARMAIN(isInitialized), true]; }; @@ -172,7 +174,7 @@ GVAR(PFHObject) = [ EGVAR(settings,frameCaptureDelay), // delay [], // args { - GVAR(capturing) = true; + GVAR(recording) = true; { // add diary entry for clients on recording start [{!isNull player}, { @@ -191,7 +193,7 @@ GVAR(PFHObject) = [ } remoteExecCall ["call", 0, true]; }, // code, executed when added { - GVAR(capturing) = false; + GVAR(recording) = false; { // add diary entry for clients on recording start [{!isNull player}, { @@ -209,32 +211,7 @@ GVAR(PFHObject) = [ }] call CBA_fnc_waitUntilAndExecute; } remoteExecCall ["call", 0, true]; }, // code, executed when removed - {GVAR(capturing)}, // if true, execute PFH cycle - {!GVAR(capturing) || !GVARMAIN(enabled)}, // if true, delete object + {GVAR(recording)}, // if true, execute PFH cycle + {!GVAR(recording) || !GVARMAIN(enabled)}, // if true, delete object ["_frameCaptureDelay"] ] call CBA_fnc_createPerFrameHandlerObject; - - - - - - - - - - - - - - - - - - - - - - - - -waitUntil{(count(allPlayers) >= EGVAR(settings,minPlayerCount))}; diff --git a/x/ocap2/addons/recorder/fnc_eh_connected.sqf b/x/ocap2/addons/recorder/fnc_eh_connected.sqf new file mode 100644 index 0000000..1302f2c --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_eh_connected.sqf @@ -0,0 +1,5 @@ +#include "script_component.hpp" + +[":EVENT:", + [GVAR(captureFrameNo), "connected", _this select 2] +] call EFUNC(extension,sendData); diff --git a/x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf b/x/ocap2/addons/recorder/fnc_eh_disconnected.sqf similarity index 88% rename from x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf rename to x/ocap2/addons/recorder/fnc_eh_disconnected.sqf index 8d6e247..810220e 100644 --- a/x/ocap2/addons/recorder/functions/fn_eh_disconnected.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_disconnected.sqf @@ -1,3 +1,5 @@ +#include "script_component.hpp" + params ["_unit", "_id", "_uid", "_name"]; [":EVENT:", diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf new file mode 100644 index 0000000..85ac8e1 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -0,0 +1,182 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(eh_firedMan) + +Description: + Tracks bullet and non-bullet projectiles. This is the code triggered when a unit firing is detected by the "FiredMan" Event Handler applied to units during . + +Parameters: + _firer - Unit the event handler is assigned to (the instigator) [Object] + _weapon - Fired weapon [String] + _muzzle - Muzzle that was used [String] + _mode - Current mode of the fired weapon [String] + _ammo - Ammo used [String] + _magazine - Magazine name which was used [String] + _projectile - Object of the projectile that was shot out [Object] + _vehicle - if weapon is vehicle weapon, otherwise objNull [Object] + +Returns: + Nothing + +Examples: + --- Code + --- + +Public: + No + +Author: + IndigoFox, Dell +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; + +_frame = GVAR(captureFrameNo); + +_firer setVariable [QGVARMAIN(lastFired), currentWeapon _firer]; + +_ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); + +// bullet handling, cut short +if (_ammoSimType isEqualTo "shotBullet") then { + [_projectile, _firer, _frame, _ammoSimType, _ammo] spawn { + params["_projectile", "_firer", "_frame", "_ammoSimType", "_ammo"]; + if (isNull _projectile) then { + _projectile = nearestObject [_firer, _ammo]; + }; + private _lastPos = []; + waitUntil { + _pos = getPosASL _projectile; + if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { + true + }; + _lastPos = _pos; + false; + }; + + if !((count _lastPos) isEqualTo 0) then { + [":FIRED:", [ + (_firer getVariable QGVARMAIN(id)), + _frame, + _lastPos + ]] call EFUNC(extension,sendData); + }; + }; + +} else { + // _ammoSimType + // simulation == "ShotSmokeX"; // M18 Smoke + // "ShotGrenade" // M67 + // "ShotRocket" // S-8 + // "ShotMissile" // R-27 + // "ShotShell" // VOG-17M, HE40mm + // "ShotIlluminating" // 40mm_green Flare + // "ShotMine" // Satchel remote + + _int = random 2000; + _muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName"); + if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayNameShort")}; + if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName")}; + _magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayNameShort"); + if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayName")}; + if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; + if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; + + // non-bullet handling + private ["_markTextLocal"]; + if (!isNull _vehicle) then { + _markTextLocal = format["[%3] %1 - %2", _muzzleDisp, _magDisp, ([configOf _vehicle] call BIS_fnc_displayName)]; + } else { + if (_ammoSimType isEqualTo "shotGrenade") then { + _markTextLocal = format["%1", _magDisp]; + } else { + _markTextLocal = format["%1 - %2", _muzzleDisp, _magDisp]; + }; + }; + + _markName = format["Projectile#%1", _int]; + _markColor = "ColorRed"; + _markerType = ""; + _magPic = (getText(configfile >> "CfgMagazines" >> _magazine >> "picture")); + if (_magPic == "") then { + _markerType = "mil_triangle"; + } else { + _magPicSplit = _magPic splitString "\"; + _magPic = _magPicSplit # ((count _magPicSplit) -1); + _markerType = format["magIcons/%1", _magPic]; + _markColor = "ColorWhite"; + }; + + + // _markStr = format["|%1|%2|%3|%4|%5|%6|%7|%8|%9|%10", + // _markName, + // getPos _firer, + // "mil_triangle", + // "ICON", + // [1, 1], + // 0, + // "Solid", + // "ColorRed", + // 1, + // _markTextLocal + // ]; + + // _markStr call BIS_fnc_stringToMarkerLocal; + + // diag_log text format["detected grenade, created marker %1", _markStr]; + + // _markStr = str _mark; + // _mark = createMarkerLocal [format["Projectile%1", _int],_projectile]; + // _mark setMarkerColorLocal "ColorRed"; + // _mark setMarkerTypeLocal "selector_selectable"; + // _mark setMarkerShapeLocal "ICON"; + // _mark setMarkerTextLocal format["%1 - %2", _firer, _markTextLocal]; + + _firerPos = getPosASL _firer; + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + if (_ammoSimType isEqualTo "shotSubmunitions") then { + private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; + if (count _subTypes > 1) then { + waitUntil {isNull _projectile}; + }; + while {isNull _projectile} do { + { + _projSearch = nearestObject [_firer, _x]; + if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + } forEach _subTypes; + }; + } else { + if (isNull _projectile) then { + _projectile = nearestObject [_firer, _ammo]; + }; + }; + + private _lastPos = []; + private _lastDir = 0; + waitUntil { + _pos = getPosASL _projectile; + _dir = getDirVisual _projectile; + if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { + true + }; + _lastPos = _pos; + _lastDir = _dir; + // params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal"]; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", _dir, "", "", 1]] call CBA_fnc_localEvent; + + // here, monitor fast moving missiles/rockets/shells every 0.3 seconds. monitor smokes, grenades, flares, mines in line with the configured frame capture delay + sleep ([0.3, EGVAR(settings,frameCaptureDelay)] select {_ammoSimType in ["ShotSmokeX","ShotGrenade","ShotIlluminating","ShotMine"]}); + false; + }; + + if !((count _lastPos) isEqualTo 0) then { + // if (count _lastPos == 3) then { + // params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal"]; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _lastPos, "", "", "", _lastDir, "", "", 1]] call CBA_fnc_localEvent; + }; + sleep 10; + // deleteMarkerLocal _markName; + // }; + [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent; +}; diff --git a/x/ocap2/addons/recorder/fnc_eh_hit.sqf b/x/ocap2/addons/recorder/fnc_eh_hit.sqf new file mode 100644 index 0000000..981d0fd --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_eh_hit.sqf @@ -0,0 +1,76 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(eh_hit) + +Description: + Tracks when a unit is hit/takes damage. This is the code triggered by the "MPHit" Event Handler applied to units during . + +Parameters: + _unit - Object the event handler is assigned to. [Object] + _causedBy - Object that caused the damage. Contains the unit itself in case of collisions. [Object] + _damage - Level of damage caused by the hit. [Number] + _instigator - Object - Person who pulled the trigger. [Object] + +Returns: + Nothing + +Examples: + --- Code + --- + +Public: + No + +Author: + IndigoFox, Fank +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_unit", "_causedBy", "_damage", "_instigator"]; + +[_unit, _causedBy, _instigator] spawn { + params ["_unit", "_causedBy", "_instigator"]; + + if (isNull _instigator) then { + _instigator = [_unit, _causedBy] call FUNC(getInstigator); + }; + + _unitID = _unit getVariable [QGVARMAIN(id), -1]; + if (_unitID == -1) exitWith {}; + private _eventData = [GVAR(captureFrameNo), "hit", _unitID, ["null"], -1]; + + if (!isNull _instigator) then { + _causedById = _causedBy getVariable [QGVARMAIN(id), -1]; + _instigatorId = _instigator getVariable [QGVARMAIN(id), -1]; + + private _causedByInfo = []; + private _distanceInfo = 0; + if (_causedBy isKindOf "CAManBase" && _causedById > -1) then { + _causedByInfo = [ + _causedById, + ([_causedBy] call FUNC(getEventWeaponText)) + ]; + _distanceInfo = round (_unit distance _causedBy); + } else { + if (!isNull _instigator && _causedBy != _instigator && _instigator isKindOf "CAManBase" && _instigatorId > -1) then { + _causedByInfo = [ + _instigatorId, + ([_instigator] call FUNC(getEventWeaponText)) + ]; + _distanceInfo = round (_unit distance _instigator); + } else { + _causedByInfo = [_causedById]; + _distanceInfo = round (_unit distance _causedBy); + }; + }; + _eventData = [ + GVAR(captureFrameNo), + "hit", + _unitID, + _causedByInfo, + _distanceInfo + ]; + }; + + OCAPEXTLOG(_eventData); + [":EVENT:", _eventData] call EFUNC(extension,sendData); +}; diff --git a/x/ocap2/addons/recorder/fnc_eh_killed.sqf b/x/ocap2/addons/recorder/fnc_eh_killed.sqf new file mode 100644 index 0000000..8868ee5 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_eh_killed.sqf @@ -0,0 +1,86 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(eh_killed) + +Description: + Tracks when a unit is killed. This is the code triggered by the "MPKilled" Event Handler applied to units during . + +Parameters: + _unit - Object the event handler is assigned to. [Object] + _killer - Object that killed the unit. [Object] + _instigator - Person who pulled the trigger. [Object] + _useEffects - same as useEffects in setDamage alt syntax. [Boolean] + +Returns: + Nothing + +Examples: + --- Code + --- + +Public: + No + +Author: + Dell, IndigoFox, Fank +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_victim", "_killer", "_instigator"]; +if !(_victim getvariable [QGVARMAIN(isKilled),false]) then { + _victim setvariable [QGVARMAIN(isKilled),true]; + + [_victim, _killer, _instigator] spawn { + params ["_victim", "_killer", "_instigator"]; + + private _killedFrame = GVAR(captureFrameNo); + + if (_killer == _victim) then { + private _time = diag_tickTime; + [_victim, { + _this setVariable ["ace_medical_lastDamageSource", (_this getVariable "ace_medical_lastDamageSource"), 2]; + }] remoteExec ["call", _victim]; + waitUntil {diag_tickTime - _time > 10 || !(isNil {_victim getVariable "ace_medical_lastDamageSource"})}; + _killer = _victim getVariable ["ace_medical_lastDamageSource", _killer]; + } else { + _killer + }; + + if (isNull _instigator) then { + _instigator = [_victim, _killer] call FUNC(getInstigator); + }; + + // [GVAR(captureFrameNo), "killed", _victimId, ["null"], -1]; + private _victimId = _victim getVariable [QGVARMAIN(id), -1]; + if (_victimId == -1) exitWith {}; + private _eventData = [_killedFrame, "killed", _victimId, ["null"], -1]; + + if (!isNull _instigator) then { + _killerId = _instigator getVariable [QGVARMAIN(id), -1]; + if (_killerId == -1) exitWith {}; + + private _killerInfo = []; + if (_instigator isKindOf "CAManBase") then { + _killerInfo = [ + _killerId, + ([_instigator] call FUNC(getEventWeaponText)) + ]; + } else { + _killerInfo = [_killerId]; + }; + + _eventData = [ + _killedFrame, + "killed", + _victimId, + _killerInfo, + round(_instigator distance _victim) + ]; + }; + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(_eventData); + }; + + [":EVENT:", _eventData] call EFUNC(extension,sendData); + }; +}; diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf index dca8622..80a3cf1 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -72,6 +72,7 @@ TRACE_7("Save attempted. Elapsed Time =", _elapsedTime," Frame Count * Delay Dur if (_frameTimeDuration < EGVAR(settings,minMissionTime) && !_overrideLimits) exitWith { // if the total duration in minutes is not met based on how many frames have been recorded & the frame capture delay, // then we won't save, but will continue recording in case admins want to save once that threshold is met. + // allow this restriction to be overriden LOG("Save attempted, but the minimum recording duration hasn't been met. Not saving, continuing to record."); { player createDiaryRecord [ @@ -90,7 +91,7 @@ if (_frameTimeDuration < EGVAR(settings,minMissionTime) && !_overrideLimits) exi } remoteExec ["call", 0, false]; }; -GVAR(capturing) = false; +GVAR(recording) = false; GVAR(endFrameNumber) = GVAR(captureFrameNo); publicVariable QGVAR(endFrameNumber); diff --git a/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf new file mode 100644 index 0000000..53380d9 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf @@ -0,0 +1,67 @@ +/* ---------------------------------------------------------------------------- +Script: ocap_fnc_getEventWeaponText + +Description: + Used to identify the current weapon a unit is using that has injured or killed another. Will determine the handheld weapon or vehicle weapon they're using. + + Called during and . + +Parameters: + _instigator - The unit to evaluate [Object] + +Returns: + The description of weapon or vehicle > weapon. [String] + +Examples: + --- Code + [_instigator] call ocap_fnc_getEventWeaponText + --- + +Public: + No + +Author: + IndigoFox +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_instigator"]; + +if (vehicle _instigator isEqualTo _instigator) exitWith { + _instigator getVariable [QGVARMAIN(lastFired), getText (configFile >> "CfgWeapons" >> currentWeapon _instigator >> "displayName")]; +}; + +// pilot/driver doesn't return a value, so check for this +private _turPath = []; +if (count (assignedVehicleRole _instigator) > 1) then { + _turPath = assignedVehicleRole _instigator select 1; +} else { + _turPath = [-1]; +}; + +private _curVic = getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); +(weaponstate [vehicle _instigator, _turPath]) params ["_curWep", "_curMuzzle", "_curFiremode", "_curMag"]; +private _curWepDisplayName = getText(configFile >> "CfgWeapons" >> _curWep >> "displayName"); +private _curMagDisplayName = getText(configFile >> "CfgMagazines" >> _curMag >> "displayName"); +private _text = _curVic; +if (count _curMagDisplayName < 22) then { + if !(_curWepDisplayName isEqualTo "") then { + _text = _text + " [" + _curWepDisplayName; + if !(_curMagDisplayName isEqualTo "") then { + _text = _text + " / " + _curMagDisplayName + "]"; + } else { + _text = _text + "]" + }; + }; +} else { + if !(_curWepDisplayName isEqualTo "") then { + _text = _text + " [" + _curWepDisplayName; + if (_curWep != _curMuzzle && !(_curMuzzle isEqualTo "")) then { + _text = _text + " / " + _curMuzzle + "]"; + } else { + _text = _text + "]"; + }; + }; +}; + +_text; diff --git a/x/ocap2/addons/recorder/fnc_getInstigator.sqf b/x/ocap2/addons/recorder/fnc_getInstigator.sqf new file mode 100644 index 0000000..77d3376 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getInstigator.sqf @@ -0,0 +1,48 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(getInstigator) + +Description: + Attempts to identify who truly pulled the trigger on a kill event. + +Parameters: + _victim - Who was killed. [Object] + _killer - What caused the damage. [Object, default objNull] + _instigator - Who pulled the trigger, as reported by Arma. [Object, default objNull] + +Returns: + The true killer. [Object] + +Examples: + --- Code + [_victim, _killer] call FUNC(getInstigator); + --- + +Public: + No + +Author: + Dell +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +params ["_victim", ["_killer", objNull], ["_instigator", objNull]]; + +if (isNull _instigator) then { + _instigator = UAVControl vehicle _killer select 0; +}; +if ((isNull _instigator) || (_instigator == _victim)) then { + _instigator = _killer; +}; +if (_instigator isKindOf "AllVehicles") then { + _instigator = call { + if(alive(gunner _instigator))exitWith{gunner _instigator}; + if(alive(commander _instigator))exitWith{commander _instigator}; + if(alive(driver _instigator))exitWith{driver _instigator}; + effectiveCommander _instigator + }; +}; +if (isNull _instigator) then { + _instigator = _killer; +}; + +_instigator; diff --git a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf new file mode 100644 index 0000000..2983214 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf @@ -0,0 +1,308 @@ +/* ---------------------------------------------------------------------------- +Script: ocap_fnc_handleMarkers + +Description: + Used for tracking all markers in the vanilla Arma 3 system. + + This function creates a server-side CBA listener as well as local Event Handlers on the server and all clients. It facilitates marker creation, modification, and deletion that occurs across any machine or on the server. + + Delays are integrated into the system to allow for multi-line scripted marker creations during a mission to reflect correctly in the created marker during playback. These delays are accounted for so that playback reflects the true creation time. + + Due to the nature of locality and single-view playback, markers of the same name which exist in different states on different clients may display odd behavior during playback. + + Marker exclusion as configured in userconfig.hpp is handled client-side for performance reasons. + + * Applied during mission event handler application in . + +Parameters: + None + +Returns: + Nothing + +Examples: + --- Code + call ocap_fnc_handleMarkers; + --- + +Public: + Yes + +Author: + IndigoFox, Fank +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +// array: GVAR(trackedMarkers) +// Persistent global variable on server that defines unique marker names currently being tracked. +// Entries are added at marker create events and removed at marker delete events to avoid duplicate processing. +GVAR(trackedMarkers) = []; // Markers which we saves into replay + +// On the dedicated server, the color of the markers is blue +// This overrides it with client data so it's saved properly +{ + _x params ["_name", "_color"]; + profilenamespace setVariable [_name, _color]; +} forEach [ + ["map_blufor_r", 0], + ["map_blufor_g", 0.3], + ["map_blufor_b", 0.6], + ["map_independent_r", 0], + ["map_independent_g", 0.5], + ["map_independent_b", 0], + ["map_civilian_r", 0.4], + ["map_civilian_g", 0], + ["map_civilian_b", 0.5], + ["map_unknown_r", 0.7], + ["map_unknown_g", 0.6], + ["map_unknown_b", 0], + ["map_opfor_r", 0.5], + ["map_opfor_g", 0], + ["map_opfor_b", 0] +]; + +// create CBA event handler to be called on server with key "ocap2_handleMarker" +EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { + params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", ["_forceGlobal", false], ["_creationTime", 0]]; + + switch (_eventType) do { + + case "CREATED":{ + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR2("MARKER:CREATE: Processing marker data -- ", _this)); + }; + + if (_mrk_name in GVAR(trackedMarkers)) exitWith { + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR3("MARKER:CREATE: Marker", _mrk_name, "already tracked, exiting")); + }; + }; + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("MARKER:CREATE: Valid CREATED process of marker from", _mrk_owner, "for", _mrk_name)); + }; + + if (_type isEqualTo "") then {_type = "mil_dot"}; + GVAR(trackedMarkers) pushBackUnique _mrk_name; + + private _mrk_color = ""; + if (_color == "Default") then { + _mrk_color = (configfile >> "CfgMarkers" >> _type >> "color") call BIS_fnc_colorConfigToRGBA call bis_fnc_colorRGBtoHTML; + } else { + _mrk_color = (configfile >> "CfgMarkerColors" >> _color >> "color") call BIS_fnc_colorConfigToRGBA call bis_fnc_colorRGBtoHTML; + }; + + private ["_sideOfMarker"]; + if (_mrk_owner isEqualTo objNull) then { + _forceGlobal = true; + _mrk_owner = -1; + _sideOfMarker = -1; + } else { + _sideOfMarker = (side _mrk_owner) call BIS_fnc_sideID; + _mrk_owner = _mrk_owner getVariable[QGVARMAIN(id), 0]; + }; + + if (_sideOfMarker isEqualTo 4 || + (["Projectile#", _mrk_name] call BIS_fnc_inString) || + (["Detonation#", _mrk_name] call BIS_fnc_inString) || + (["Mine#", _mrk_name] call BIS_fnc_inString) || + (["ObjectMarker", _mrk_name] call BIS_fnc_inString) || + (["moduleCoverMap", _mrk_name] call BIS_fnc_inString) || + _forceGlobal) then {_sideOfMarker = -1}; + + private ["_polylinePos"]; + if (count _pos > 3) then { + _polylinePos = []; + for [{_i = 0}, {_i < ((count _pos) - 1)}, {_i = _i + 1}] do { + _polylinePos pushBack [_pos # (_i), _pos # (_i + 1)]; + _i = _i + 1; + }; + _pos = _polylinePos; + }; + + if (isNil "_dir") then { + _dir = 0; + } else {if (_dir isEqualTo "") then {_dir = 0}}; + + private _captureFrameNo = GVAR(captureFrameNo); + if (_creationTime > 0) then { + private _delta = time - _creationTime; + private _lastFrameTime = (GVAR(captureFrameNo) * EGVAR(settings,frameCaptureDelay)) + GVAR(startTime); + if (_delta > (time - _lastFrameTime)) then { // marker was initially created in some frame(s) before + _captureFrameNo = ceil _lastFrameTime - (_delta / EGVAR(settings,frameCaptureDelay)); + private _logParams = (str [GVAR(captureFrameNo), time, _creationTime, _delta, _lastFrameTime, _captureFrameNo]); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR2("CREATE:MARKER: adjust frame ", _logParams)); + }; + }; + }; + + private _logParams = (str [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]); + + if (GVARMAIN(isDebug)) then { + str ["CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension -- ", _logParams select [0, 5]] remoteExec ["systemChat", [0, -2] select isDedicated]; + OCAPEXTLOG(ARR4("CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension -- ", _logParams)); + }; + + [":MARKER:CREATE:", [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]] call EFUNC(extension,sendData); + }; + + case "UPDATED":{ + + if (_mrk_name in GVAR(trackedMarkers)) then { + if (isNil "_dir") then {_dir = 0}; + [":MARKER:MOVE:", [_mrk_name, GVAR(captureFrameNo), _pos, _dir, _alpha]] call EFUNC(extension,sendData); + }; + }; + + case "DELETED":{ + + if (_mrk_name in GVAR(trackedMarkers)) then { + + if (GVARMAIN(isDebug)) then { + str ["MARKER:DELETE: Marker", _mrk_name, "deleted"] remoteExec ["systemChat", [0, -2] select isDedicated]; + OCAPEXTLOG(ARR3("MARKER:DELETE: Marker", _mrk_name, "deleted")); + }; + + [":MARKER:DELETE:", [_mrk_name, GVAR(captureFrameNo)]] call EFUNC(extension,sendData); + GVAR(trackedMarkers) = GVAR(trackedMarkers) - [_mrk_name]; + }; + }; + }; +}] call CBA_fnc_addEventHandler; + + + + + +// handle created markers +{ + addMissionEventHandler["MarkerCreated", { + params["_marker", "_channelNumber", "_owner", "_local"]; + + if (!_local) exitWith {}; + + // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server + // if value is undefined, then skip + private _isExcluded = false; + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { + { + if ((str _marker) find _x >= 0) exitWith { + _isExcluded = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)); + }; + if (_isExcluded) exitWith {}; + + private _event = _this; + _event pushBack time; + + [{ + params["_marker", "_channelNumber", "_owner", "_local", "_creationTime"]; + _pos = ATLToASL (markerPos [_marker, true]); + _type = markerType _marker; + _shape = markerShape _marker; + _size = markerSize _marker; + _dir = markerDir _marker; + _brush = markerBrush _marker; + _color = markerColor _marker; + _text = markerText _marker; + _alpha = markerAlpha _marker; + _polyline = markerPolyline _marker; + if (count _polyline != 0) then { + _pos = _polyline; + }; + + [QGVARMAIN(handleMarker), ["CREATED", _marker, _owner, _pos, _type, _shape, _size, _dir, _brush, _color, _alpha, _text, false, _creationTime]] call CBA_fnc_serverEvent; + }, _event, 2] call CBA_fnc_waitAndExecute; + }]; + + // handle marker moves/updates + addMissionEventHandler["MarkerUpdated", { + params["_marker", "_local"]; + + if (!_local) exitWith {}; + + // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server + // if value is undefined, then skip + private _isExcluded = false; + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { + { + if ((str _marker) find _x >= -1) exitWith { + _isExcluded = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)); + }; + if (_isExcluded) exitWith {}; + + private _pos = ATLToASL (markerPos [_marker, true]); + + [QGVARMAIN(handleMarker), ["UPDATED", _marker, player, _pos, "", "", "", markerDir _marker, "", "", markerAlpha _marker]] call CBA_fnc_serverEvent; + }]; + + // handle marker deletions + addMissionEventHandler["MarkerDeleted", { + params["_marker", "_local"]; + + if (!_local) exitWith {}; + + // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server + // if value is undefined, then skip + private _isExcluded = false; + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { + { + if ((str _marker) find _x > -1) exitWith { + _isExcluded = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)); + }; + if (_isExcluded) exitWith {}; + + [QGVARMAIN(handleMarker), ["DELETED", _marker, player]] call CBA_fnc_serverEvent; + }]; +} remoteExec["call", 0, true]; + + + +// collect all initial markers & add event handlers to clients +[ + {getClientState > 8 && !isNil QGVAR(startTime)}, + { + { + private _marker = _x; + // "Started polling starting markers" remoteExec ["hint", 0]; + // get intro object markers + _pos = ATLToASL (markerPos [_marker, true]); + _type = markerType _marker; + _shape = markerShape _marker; + _size = markerSize _marker; + _dir = markerDir _marker; + _brush = markerBrush _marker; + _color = markerColor _marker; + _text = markerText _marker; + _alpha = markerAlpha _marker; + _polyline = markerPolyline _marker; + if (count _polyline != 0) then { + _pos = _polyline; + }; + + if (isNil "_dir") then { + _dir = 0; + } else {if (_dir isEqualTo "") then {_dir = 0}}; + + _forceGlobal = true; + + // "_eventType", "_mrk_name", "_mrk_owner","_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal" + [QGVARMAIN(handleMarker), ["CREATED", _marker, objNull, _pos, _type, _shape, _size, _dir, _brush, _color, _alpha, _text, _forceGlobal]] call CBA_fnc_localEvent; + + } forEach (allMapMarkers select {_x find "_USER_DEFINED" == -1}); + + LOG("GETINITIALMARKERS: Successfully parsed init-scripted and editor-placed markers"); + if (GVARMAIN(isDebug)) then { + "GETINITIALMARKERS: Successfully parsed init-scripted and editor-placed markers" SYSCHAT; + OCAPEXTLOG(["GETINITIALMARKERS: Successfully parsed init-scripted and editor-placed markers"]); + }; + } +] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index a02dc75..5e900f0 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -25,8 +25,8 @@ Author: #include "script_component.hpp" -// bool: GVAR(capturing) -GVAR(capturing) = false; +// bool: GVAR(recording) +GVAR(recording) = false; // int: GVAR(captureFrameNo) GVAR(captureFrameNo) = 0; // bool: GVAR(shouldSave) @@ -84,12 +84,15 @@ if (GVAR(missionName) == "") then { GVAR(missionName) = briefingName; }; -// Add event missions -call FUNC(addEventMission); -[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay)]] call EFUNC(extension,sendData); -[] call FUNC(updateTime); -GVAR(nextId) = 0; -0 spawn FUNC(captureLoop); -[":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); +/* + Conditional Start Recording + We'll wait to see if auto-start is enabled and minPlayercount setting is met. This covers scenarios where someone changes the autostart setting during the mission as well, and excludes cases where autostart is disabled. + On execution, we'll also check if recording has already started by other means via whether GVAR(startTime) has been declared or not. + If recording hasn't started already, we'll initialize it here assuming the above conditions are met. +*/ +[ + {((count allPlayers) >= EGVAR(settings,minPlayerCount) && EGVAR(settings,autoStart)) || !isNil QGVAR(startTime)}, + {call FUNC(startRecording)}, +] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/fnc_startRecording.sqf b/x/ocap2/addons/recorder/fnc_startRecording.sqf new file mode 100644 index 0000000..0f3a9cc --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_startRecording.sqf @@ -0,0 +1,29 @@ +/* + Start Recording + + This is the initial recording start function. If it hasn't been called from anywhere already, it'll get everything in order to initiate a session for this mission. +*/ +#include "script_component.hpp" + +// disregard recording attempts while OCAP is disabled. +if (!GVARMAIN(enabled)) exitWith {}; + +// if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. +if (!isNil QGVAR(startTime)) then { + if (GVAR(recording)) exitWith { + OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); + }; + if (!GVAR(recording)) exitWith { + call FUNC(captureLoop); + }; +}; + +[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay)]] call EFUNC(extension,sendData); +[":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); +// Add mission event handlers +call FUNC(addEventMission); +// Track initial times +[] call FUNC(updateTime); + +GVAR(nextId) = 0; +call FUNC(captureLoop); diff --git a/x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf b/x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf deleted file mode 100644 index e58572a..0000000 --- a/x/ocap2/addons/recorder/functions/fn_addEventHandlers.sqf +++ /dev/null @@ -1,41 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_addEventHandlers - -Description: - Used for applying unit-specific event handlers to units during initialization. These event handlers will trigger on the server. - - Applied during initialization of a unit in . - -Parameters: - _entity - Object to apply event handlers to. [Object] - _respawn - Determines if unit is initialized for the first time, or has respawned and does not need certain handlers reapplied. [Boolean, defaults to false] - -Returns: - Nothing - -Examples: - --- Code - [_unit] spawn ocap_fnc_addEventHandlers; - --- - -Public: - No - -Author: - IndigoFox, Fank ----------------------------------------------------------------------------- */ - -params ["_entity", ["_respawn", false]]; - -if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then { - _entity addEventHandler ["FiredMan", { _this spawn ocap_fnc_eh_fired; }]; -}; -_entity addMPEventHandler ["MPHit", { _this spawn ocap_fnc_eh_hit; }]; - -if ( - !_respawn && - (_entity call BIS_fnc_objectType) # 0 == "Soldier" && - isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing") -) then { - ocap_fnc_trackAceThrowing remoteExec ["call", _entity]; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_autoStart.sqf b/x/ocap2/addons/recorder/functions/fn_autoStart.sqf deleted file mode 100644 index 1ab2be6..0000000 --- a/x/ocap2/addons/recorder/functions/fn_autoStart.sqf +++ /dev/null @@ -1,27 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_autoStart - -Description: - Run during preInit to check for auto or manual start. Calls ocap_fnc_init. - -Parameters: - None - -Returns: - Nothing - -Examples: - call ocap_fnc_autoStart; - -Public: - Yes - -Author: - TyroneMF ----------------------------------------------------------------------------- */ - -#include "\userconfig\ocap\config.hpp" - -if (ocap_autoStart) then { - [] call ocap_fnc_init; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_eh_connected.sqf b/x/ocap2/addons/recorder/functions/fn_eh_connected.sqf deleted file mode 100644 index fecb2fd..0000000 --- a/x/ocap2/addons/recorder/functions/fn_eh_connected.sqf +++ /dev/null @@ -1,3 +0,0 @@ -[":EVENT:", - [GVAR(captureFrameNo), "connected", _this select 2] -] call EFUNC(extension,sendData); diff --git a/x/ocap2/addons/recorder/functions/fn_eh_fired.sqf b/x/ocap2/addons/recorder/functions/fn_eh_fired.sqf deleted file mode 100644 index 829d636..0000000 --- a/x/ocap2/addons/recorder/functions/fn_eh_fired.sqf +++ /dev/null @@ -1,179 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_eh_fired - -Description: - Tracks bullet and non-bullet projectiles. This is the code triggered when a unit firing is detected by the "FiredMan" Event Handler applied to units during . - -Parameters: - _firer - Unit the event handler is assigned to (the instigator) [Object] - _weapon - Fired weapon [String] - _muzzle - Muzzle that was used [String] - _mode - Current mode of the fired weapon [String] - _ammo - Ammo used [String] - _magazine - Magazine name which was used [String] - _projectile - Object of the projectile that was shot out [Object] - _vehicle - if weapon is vehicle weapon, otherwise objNull [Object] - -Returns: - Nothing - -Examples: - --- Code - --- - -Public: - No - -Author: - IndigoFox, Dell ----------------------------------------------------------------------------- */ - -params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; - -_frame = GVAR(captureFrameNo); - -// if (!isServer) exitWith {}; - -_ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); - -// bullet handling, cut short -if (_ammoSimType isEqualTo "shotBullet") then { - [_projectile, _firer, _frame, _ammoSimType, _ammo] spawn { - params["_projectile", "_firer", "_frame", "_ammoSimType", "_ammo"]; - if (isNull _projectile) then { - _projectile = nearestObject [_firer, _ammo]; - }; - private _lastPos = []; - waitUntil { - _pos = getPosASL _projectile; - if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { - true - }; - _lastPos = _pos; - false; - }; - - if !((count _lastPos) isEqualTo 0) then { - [":FIRED:", [ - (_firer getVariable QGVARMAIN(id)), - _frame, - _lastPos - ]] call EFUNC(extension,sendData); - }; - }; - -} else { - - // simulation == "ShotSmokeX"; // M18 Smoke - // "ShotGrenade" // M67 - // "ShotRocket" // S-8 - // "ShotMissile" // R-27 - // "ShotShell" // VOG-17M, HE40mm - // "ShotIlluminating" // 40mm_green Flare - // "ShotMine" // Satchel remote - - _int = random 2000; - _muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName"); - if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayNameShort")}; - if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName")}; - _magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayNameShort"); - if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayName")}; - if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; - if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; - - // non-bullet handling - private ["_markTextLocal"]; - if (!isNull _vehicle) then { - _markTextLocal = format["[%3] %1 - %2", _muzzleDisp, _magDisp, ([configOf _vehicle] call BIS_fnc_displayName)]; - } else { - if (_ammoSimType isEqualTo "shotGrenade") then { - _markTextLocal = format["%1", _magDisp]; - } else { - _markTextLocal = format["%1 - %2", _muzzleDisp, _magDisp]; - }; - }; - - _markName = format["Projectile#%1", _int]; - _markColor = "ColorRed"; - _markerType = ""; - _magPic = (getText(configfile >> "CfgMagazines" >> _magazine >> "picture")); - if (_magPic == "") then { - _markerType = "mil_triangle"; - } else { - _magPicSplit = _magPic splitString "\"; - _magPic = _magPicSplit # ((count _magPicSplit) -1); - _markerType = format["magIcons/%1", _magPic]; - _markColor = "ColorWhite"; - }; - - - // _markStr = format["|%1|%2|%3|%4|%5|%6|%7|%8|%9|%10", - // _markName, - // getPos _firer, - // "mil_triangle", - // "ICON", - // [1, 1], - // 0, - // "Solid", - // "ColorRed", - // 1, - // _markTextLocal - // ]; - - // _markStr call BIS_fnc_stringToMarkerLocal; - - // diag_log text format["detected grenade, created marker %1", _markStr]; - - // _markStr = str _mark; - // _mark = createMarkerLocal [format["Projectile%1", _int],_projectile]; - // _mark setMarkerColorLocal "ColorRed"; - // _mark setMarkerTypeLocal "selector_selectable"; - // _mark setMarkerShapeLocal "ICON"; - // _mark setMarkerTextLocal format["%1 - %2", _firer, _markTextLocal]; - - _firerPos = getPosASL _firer; - ["ocap_handleMarker", ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - if (_ammoSimType isEqualTo "shotSubmunitions") then { - private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; - if (count _subTypes > 1) then { - waitUntil {isNull _projectile}; - }; - while {isNull _projectile} do { - { - _projSearch = nearestObject [_firer, _x]; - if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - } forEach _subTypes; - }; - } else { - if (isNull _projectile) then { - _projectile = nearestObject [_firer, _ammo]; - }; - }; - - private _lastPos = []; - private _lastDir = 0; - waitUntil { - _pos = getPosASL _projectile; - _dir = getDirVisual _projectile; - if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { - true - }; - _lastPos = _pos; - _lastDir = _dir; - // params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal"]; - ["ocap_handleMarker", ["UPDATED", _markName, _firer, _pos, "", "", "", _dir, "", "", 1]] call CBA_fnc_localEvent; - sleep 0.3; - false; - }; - - if !((count _lastPos) isEqualTo 0) then { - // if (count _lastPos == 3) then { - // params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal"]; - ["ocap_handleMarker", ["UPDATED", _markName, _firer, _lastPos, "", "", "", _lastDir, "", "", 1]] call CBA_fnc_localEvent; - }; - sleep 10; - // deleteMarkerLocal _markName; - // }; - ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_localEvent; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_eh_hit.sqf b/x/ocap2/addons/recorder/functions/fn_eh_hit.sqf deleted file mode 100644 index 1d72ae6..0000000 --- a/x/ocap2/addons/recorder/functions/fn_eh_hit.sqf +++ /dev/null @@ -1,76 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_eh_hit - -Description: - Tracks when a unit is hit/takes damage. This is the code triggered by the "MPHit" Event Handler applied to units during . - -Parameters: - _unit - Object the event handler is assigned to. [Object] - _causedBy - Object that caused the damage. Contains the unit itself in case of collisions. [Object] - _damage - Level of damage caused by the hit. [Number] - _instigator - Object - Person who pulled the trigger. [Object] - -Returns: - Nothing - -Examples: - --- Code - --- - -Public: - No - -Author: - IndigoFox, Fank ----------------------------------------------------------------------------- */ - -#include "script_macros.hpp"; -params ["_unit", "_causedBy", "_damage", "_instigator"]; - -[_unit, _causedBy, _instigator] spawn { - params ["_unit", "_causedBy", "_instigator"]; - - if (isNull _instigator) then { - _instigator = [_unit, _causedBy] call ocap_fnc_getInstigator; - }; - - _unitID = _unit getVariable [QGVARMAIN(id), -1]; - if (_unitID == -1) exitWith {}; - private _eventData = [GVAR(captureFrameNo), "hit", _unitID, ["null"], -1]; - - if (!isNull _instigator) then { - _causedById = _causedBy getVariable [QGVARMAIN(id), -1]; - _instigatorId = _instigator getVariable [QGVARMAIN(id), -1]; - - private _causedByInfo = []; - private _distanceInfo = 0; - if (_causedBy isKindOf "CAManBase" && _causedById > -1) then { - _causedByInfo = [ - _causedById, - ([_causedBy] call ocap_fnc_getEventWeaponText) - ]; - _distanceInfo = round (_unit distance _causedBy); - } else { - if (!isNull _instigator && _causedBy != _instigator && _instigator isKindOf "CAManBase" && _instigatorId > -1) then { - _causedByInfo = [ - _instigatorId, - ([_instigator] call ocap_fnc_getEventWeaponText) - ]; - _distanceInfo = round (_unit distance _instigator); - } else { - _causedByInfo = [_causedById]; - _distanceInfo = round (_unit distance _causedBy); - }; - }; - _eventData = [ - GVAR(captureFrameNo), - "hit", - _unitID, - _causedByInfo, - _distanceInfo - ]; - }; - - DEBUG(_eventData); - [":EVENT:", _eventData] call EFUNC(extension,sendData); -}; diff --git a/x/ocap2/addons/recorder/functions/fn_eh_killed.sqf b/x/ocap2/addons/recorder/functions/fn_eh_killed.sqf deleted file mode 100644 index 4aab3c9..0000000 --- a/x/ocap2/addons/recorder/functions/fn_eh_killed.sqf +++ /dev/null @@ -1,80 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_eh_killed - -Description: - Tracks when a unit is killed. This is the code triggered by the "MPKilled" Event Handler applied to units during . - -Parameters: - _unit - Object the event handler is assigned to. [Object] - _killer - Object that killed the unit. [Object] - _instigator - Person who pulled the trigger. [Object] - _useEffects - same as useEffects in setDamage alt syntax. [Boolean] - -Returns: - Nothing - -Examples: - --- Code - --- - -Public: - No - -Author: - Dell, IndigoFox, Fank ----------------------------------------------------------------------------- */ - -#include "script_macros.hpp"; -params ["_victim", "_killer", "_instigator"]; -if !(_victim getvariable ["ocapIsKilled",false]) then { - _victim setvariable ["ocapIsKilled",true]; - - [_victim, _killer, _instigator] spawn { - params ["_victim", "_killer", "_instigator"]; - if (_killer == _victim) then { - private _time = diag_tickTime; - [_victim, { - _this setVariable ["ace_medical_lastDamageSource", (_this getVariable "ace_medical_lastDamageSource"), 2]; - }] remoteExec ["call", _victim]; - waitUntil {diag_tickTime - _time > 10 || !(isNil {_victim getVariable "ace_medical_lastDamageSource"})}; - _killer = _victim getVariable ["ace_medical_lastDamageSource", _killer]; - } else { - _killer - }; - - if (isNull _instigator) then { - _instigator = [_victim, _killer] call ocap_fnc_getInstigator; - }; - - // [GVAR(captureFrameNo), "killed", _victimId, ["null"], -1]; - private _victimId = _victim getVariable [QGVARMAIN(id), -1]; - if (_victimId == -1) exitWith {}; - private _eventData = [GVAR(captureFrameNo), "killed", _victimId, ["null"], -1]; - - if (!isNull _instigator) then { - _killerId = _instigator getVariable [QGVARMAIN(id), -1]; - if (_killerId == -1) exitWith {}; - - private _killerInfo = []; - if (_instigator isKindOf "CAManBase") then { - _killerInfo = [ - _killerId, - ([_instigator] call ocap_fnc_getEventWeaponText) - ]; - } else { - _killerInfo = [_killerId]; - }; - - _eventData = [ - GVAR(captureFrameNo), - "killed", - _victimId, - _killerInfo, - round(_instigator distance _victim) - ]; - }; - - DEBUG(_eventData); - [":EVENT:", _eventData] call EFUNC(extension,sendData); - }; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_getEventWeaponText.sqf b/x/ocap2/addons/recorder/functions/fn_getEventWeaponText.sqf deleted file mode 100644 index 0ba8122..0000000 --- a/x/ocap2/addons/recorder/functions/fn_getEventWeaponText.sqf +++ /dev/null @@ -1,66 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_getEventWeaponText - -Description: - Used to identify the current weapon a unit is using that has injured or killed another. Will determine the handheld weapon or vehicle weapon they're using. - - Called during and . - -Parameters: - _instigator - The unit to evaluate [Object] - -Returns: - The description of weapon or vehicle > weapon. [String] - -Examples: - --- Code - [_instigator] call ocap_fnc_getEventWeaponText - --- - -Public: - No - -Author: - IndigoFox ----------------------------------------------------------------------------- */ - -params ["_instigator"]; - -if (vehicle _instigator isEqualTo _instigator) exitWith { - getText (configFile >> "CfgWeapons" >> currentWeapon _instigator >> "displayName"); -}; - -// pilot/driver doesn't return a value, so check for this -private _turPath = []; -if (count (assignedVehicleRole _instigator) > 1) then { - _turPath = assignedVehicleRole _instigator select 1; -} else { - _turPath = [-1]; -}; - -private _curVic = getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); -(weaponstate [vehicle _instigator, _turPath]) params ["_curWep", "_curMuzzle", "_curFiremode", "_curMag"]; -private _curWepDisplayName = getText(configFile >> "CfgWeapons" >> _curWep >> "displayName"); -private _curMagDisplayName = getText(configFile >> "CfgMagazines" >> _curMag >> "displayName"); -private _text = _curVic; -if (count _curMagDisplayName < 22) then { - if !(_curWepDisplayName isEqualTo "") then { - _text = _text + " [" + _curWepDisplayName; - if !(_curMagDisplayName isEqualTo "") then { - _text = _text + " / " + _curMagDisplayName + "]"; - } else { - _text = _text + "]" - }; - }; -} else { - if !(_curWepDisplayName isEqualTo "") then { - _text = _text + " [" + _curWepDisplayName; - if (_curWep != _curMuzzle && !(_curMuzzle isEqualTo "")) then { - _text = _text + " / " + _curMuzzle + "]"; - } else { - _text = _text + "]"; - }; - }; -}; - -_text; diff --git a/x/ocap2/addons/recorder/functions/fn_getInstigator.sqf b/x/ocap2/addons/recorder/functions/fn_getInstigator.sqf deleted file mode 100644 index 0ec5392..0000000 --- a/x/ocap2/addons/recorder/functions/fn_getInstigator.sqf +++ /dev/null @@ -1,47 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_getInstigator - -Description: - Attempts to identify who truly pulled the trigger on a kill event. - -Parameters: - _victim - Who was killed. [Object] - _killer - What caused the damage. [Object, default objNull] - _instigator - Who pulled the trigger, as reported by Arma. [Object, default objNull] - -Returns: - The true killer. [Object] - -Examples: - --- Code - [_victim, _killer] call ocap_fnc_getInstigator; - --- - -Public: - No - -Author: - Dell ----------------------------------------------------------------------------- */ - -params ["_victim", ["_killer", objNull], ["_instigator", objNull]]; - -if (isNull _instigator) then { - _instigator = UAVControl vehicle _killer select 0; -}; -if ((isNull _instigator) || (_instigator == _victim)) then { - _instigator = _killer; -}; -if (_instigator isKindOf "AllVehicles") then { - _instigator = call { - if(alive(gunner _instigator))exitWith{gunner _instigator}; - if(alive(commander _instigator))exitWith{commander _instigator}; - if(alive(driver _instigator))exitWith{driver _instigator}; - effectiveCommander _instigator - }; -}; -if (isNull _instigator) then { - _instigator = _killer; -}; - -_instigator; diff --git a/x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf b/x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf deleted file mode 100644 index 02369ad..0000000 --- a/x/ocap2/addons/recorder/functions/fn_handleMarkers.sqf +++ /dev/null @@ -1,292 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_handleMarkers - -Description: - Used for tracking all markers in the vanilla Arma 3 system. - - This function creates a server-side CBA listener as well as local Event Handlers on the server and all clients. It facilitates marker creation, modification, and deletion that occurs across any machine or on the server. - - Delays are integrated into the system to allow for multi-line scripted marker creations during a mission to reflect correctly in the created marker during playback. These delays are accounted for so that playback reflects the true creation time. - - Due to the nature of locality and single-view playback, markers of the same name which exist in different states on different clients may display odd behavior during playback. - - Marker exclusion as configured in userconfig.hpp is handled client-side for performance reasons. - - * Applied during mission event handler application in . - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - call ocap_fnc_handleMarkers; - --- - -Public: - Yes - -Author: - IndigoFox, Fank ----------------------------------------------------------------------------- */ - -#include "\userconfig\ocap\config.hpp" -#include "script_macros.hpp" - -// array: ocap_markers_tracked -// Persistent global variable on server that defines unique marker names currently being tracked. -// Entries are added at marker create events and removed at marker delete events to avoid duplicate processing. -ocap_markers_tracked = []; // Markers which we saves into replay - -// On the dedicated server, the color of the markers is blue -{ - _x params ["_name", "_color"]; - profilenamespace setVariable [_name, _color]; -} forEach [ - ["map_blufor_r", 0], - ["map_blufor_g", 0.3], - ["map_blufor_b", 0.6], - ["map_independent_r", 0], - ["map_independent_g", 0.5], - ["map_independent_b", 0], - ["map_civilian_r", 0.4], - ["map_civilian_g", 0], - ["map_civilian_b", 0.5], - ["map_unknown_r", 0.7], - ["map_unknown_g", 0.6], - ["map_unknown_b", 0], - ["map_opfor_r", 0.5], - ["map_opfor_g", 0], - ["map_opfor_b", 0] -]; - -// create CBA event handler to be called on server -ocap_markers_handle = ["ocap_handleMarker", { - params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", ["_forceGlobal", false], ["_creationTime", 0]]; - - switch (_eventType) do { - - case "CREATED":{ - DEBUG(ARR2("MARKER:CREATE: Processing marker data -- ", _this)); - - if (_mrk_name in ocap_markers_tracked) exitWith { - DEBUG(ARR3("MARKER:CREATE: Marker", _mrk_name, "already tracked, exiting")); - }; - - DEBUG(ARR4("MARKER:CREATE: Valid CREATED process of marker from", _mrk_owner, "for", _mrk_name)); - - if (_type isEqualTo "") then {_type = "mil_dot"}; - ocap_markers_tracked pushBackUnique _mrk_name; - - private _mrk_color = ""; - if (_color == "Default") then { - _mrk_color = (configfile >> "CfgMarkers" >> _type >> "color") call BIS_fnc_colorConfigToRGBA call bis_fnc_colorRGBtoHTML; - } else { - _mrk_color = (configfile >> "CfgMarkerColors" >> _color >> "color") call BIS_fnc_colorConfigToRGBA call bis_fnc_colorRGBtoHTML; - }; - - private ["_sideOfMarker"]; - if (_mrk_owner isEqualTo objNull) then { - _forceGlobal = true; - _mrk_owner = -1; - _sideOfMarker = -1; - } else { - _sideOfMarker = (side _mrk_owner) call BIS_fnc_sideID; - _mrk_owner = _mrk_owner getVariable[QGVARMAIN(id), 0]; - }; - - if (_sideOfMarker isEqualTo 4 || - (["Projectile#", _mrk_name] call BIS_fnc_inString) || - (["Detonation#", _mrk_name] call BIS_fnc_inString) || - (["Mine#", _mrk_name] call BIS_fnc_inString) || - (["ObjectMarker", _mrk_name] call BIS_fnc_inString) || - (["moduleCoverMap", _mrk_name] call BIS_fnc_inString) || - _forceGlobal) then {_sideOfMarker = -1}; - - private ["_polylinePos"]; - if (count _pos > 3) then { - _polylinePos = []; - for [{_i = 0}, {_i < ((count _pos) - 1)}, {_i = _i + 1}] do { - _polylinePos pushBack [_pos # (_i), _pos # (_i + 1)]; - _i = _i + 1; - }; - _pos = _polylinePos; - }; - - if (isNil "_dir") then { - _dir = 0; - } else {if (_dir isEqualTo "") then {_dir = 0}}; - - private _captureFrameNo = GVAR(captureFrameNo); - if (_creationTime > 0) then { - private _delta = time - _creationTime; - private _lastFrameTime = (GVAR(captureFrameNo) * EGVAR(settings,frameCaptureDelay)) + GVAR(startTime); - if (_delta > (time - _lastFrameTime)) then { // marker was initially created in some frame(s) before - _captureFrameNo = ceil _lastFrameTime - (_delta / EGVAR(settings,frameCaptureDelay)); - private _logParams = (str [GVAR(captureFrameNo), time, _creationTime, _delta, _lastFrameTime, _captureFrameNo]); - DEBUG(ARR2("CREATE:MARKER: adjust frame ", _logParams)); - }; - }; - - private _logParams = (str [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]); - DEBUG(ARR4("CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension -- ", _logParams)); - - [":MARKER:CREATE:", [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]] call EFUNC(extension,sendData); - }; - - case "UPDATED":{ - - if (_mrk_name in ocap_markers_tracked) then { - if (isNil "_dir") then {_dir = 0}; - - if (ocap_isDebug) then { - private _logParams = str [_mrk_name, GVAR(captureFrameNo), _pos, _dir, _alpha]; - DEBUG(ARR4("MARKER:MOVE: Valid UPDATED process of", _mrk_name, ", sending to extension -- ", _logParams)); - }; - - [":MARKER:MOVE:", [_mrk_name, GVAR(captureFrameNo), _pos, _dir, _alpha]] call EFUNC(extension,sendData); - }; - }; - - case "DELETED":{ - - if (_mrk_name in ocap_markers_tracked) then { - DEBUG(ARR3("MARKER:DELETE: Marker", _mrk_name, "deleted")); - [":MARKER:DELETE:", [_mrk_name, GVAR(captureFrameNo)]] call EFUNC(extension,sendData); - ocap_markers_tracked = ocap_markers_tracked - [_mrk_name]; - }; - }; - }; -}] call CBA_fnc_addEventHandler; - - - - - -// handle created markers -{ - addMissionEventHandler["MarkerCreated", { - params["_marker", "_channelNumber", "_owner", "_local"]; - - if (!_local) exitWith {}; - - // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server - // if value is undefined, then skip - private _isExcluded = false; - if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { - { - if ((str _marker) find _x >= 0) exitWith { - _isExcluded = true; - }; - } forEach ocap_excludeMarkerFromRecord; - }; - if (_isExcluded) exitWith {}; - - private _event = _this; - _event pushBack time; - - [{ - params["_marker", "_channelNumber", "_owner", "_local", "_creationTime"]; - _pos = ATLToASL (markerPos [_marker, true]); - _type = markerType _marker; - _shape = markerShape _marker; - _size = markerSize _marker; - _dir = markerDir _marker; - _brush = markerBrush _marker; - _color = markerColor _marker; - _text = markerText _marker; - _alpha = markerAlpha _marker; - _polyline = markerPolyline _marker; - if (count _polyline != 0) then { - _pos = _polyline; - }; - - ["ocap_handleMarker", ["CREATED", _marker, _owner, _pos, _type, _shape, _size, _dir, _brush, _color, _alpha, _text, false, _creationTime]] call CBA_fnc_serverEvent; - }, _event, 2] call CBA_fnc_waitAndExecute; - }]; - - // handle marker moves/updates - addMissionEventHandler["MarkerUpdated", { - params["_marker", "_local"]; - - if (!_local) exitWith {}; - - // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server - // if value is undefined, then skip - private _isExcluded = false; - if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { - { - if ((str _marker) find _x >= 0) exitWith { - _isExcluded = true; - }; - } forEach ocap_excludeMarkerFromRecord; - }; - if (_isExcluded) exitWith {}; - - private _pos = ATLToASL (markerPos [_marker, true]); - - ["ocap_handleMarker", ["UPDATED", _marker, player, _pos, "", "", "", markerDir _marker, "", "", markerAlpha _marker]] call CBA_fnc_serverEvent; - }]; - - // handle marker deletions - addMissionEventHandler["MarkerDeleted", { - params["_marker", "_local"]; - - if (!_local) exitWith {}; - - // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server - // if value is undefined, then skip - private _isExcluded = false; - if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { - { - if ((str _marker) find _x > 0) exitWith { - _isExcluded = true; - }; - } forEach ocap_excludeMarkerFromRecord; - }; - if (_isExcluded) exitWith {}; - - ["ocap_handleMarker", ["DELETED", _marker, player]] call CBA_fnc_serverEvent; - }]; -} remoteExec["call", 0, true]; - - - -// collect all initial markers & add event handlers to clients -[ - {count allPlayers > 0}, - { - { - private _marker = _x; - // "Started polling starting markers" remoteExec ["hint", 0]; - // get intro object markers - _pos = ATLToASL (markerPos [_marker, true]); - _type = markerType _marker; - _shape = markerShape _marker; - _size = markerSize _marker; - _dir = markerDir _marker; - _brush = markerBrush _marker; - _color = markerColor _marker; - _text = markerText _marker; - _alpha = markerAlpha _marker; - _polyline = markerPolyline _marker; - if (count _polyline != 0) then { - _pos = _polyline; - }; - - if (isNil "_dir") then { - _dir = 0; - } else {if (_dir isEqualTo "") then {_dir = 0}}; - - _forceGlobal = true; - - // "_eventType", "_mrk_name", "_mrk_owner","_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal" - ["ocap_handleMarker", ["CREATED", _marker, objNull, _pos, _type, _shape, _size, _dir, _brush, _color, _alpha, _text, _forceGlobal]] call CBA_fnc_localEvent; - - } forEach (allMapMarkers); - - LOG(["GETINITIALMARKERS: Successfully parsed init-scripted and editor-placed markers"]); - } -] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/functions/fn_init.sqf b/x/ocap2/addons/recorder/functions/fn_init.sqf deleted file mode 100644 index 72e011d..0000000 --- a/x/ocap2/addons/recorder/functions/fn_init.sqf +++ /dev/null @@ -1,106 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_init - -Description: - Automatic Start: Called from ocap_fnc_autoStart. - Manual Start: Server execution to begin. - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - call ocap_fnc_init; - --- - -Public: - No - -Author: - Dell, Zealot, IndigoFox ----------------------------------------------------------------------------- */ - -#include "script_macros.hpp" - -// bool: GVAR(capturing) -GVAR(capturing) = false; -// int: GVAR(captureFrameNo) -GVAR(captureFrameNo) = 0; -// bool: GVAR(shouldSave) -GVAR(shouldSave) = [false, true] select (EGVAR(settings,minMissionTime) < 10); - -if (ocap_excludeMarkerFromRecord isEqualType []) then { - publicVariable QEGVAR(settings,excludeMarkerFromRecord); -} else { - LOG(["excludeMarkerFromRecord in config is not an array, skipping exclusions"]); -}; - -// macro: GVARMAIN(version)SION -GVARMAIN(version) = QUOTE(VERSION_STR); -publicVariable QGVARMAIN(version); - -EGVAR(extension,version) = ([":VERSION:", []] call EFUNC(extension,sendData)); -publicVariable QEGVAR(extension,version); - - -{ - [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { - player createDiarySubject ["OCAP2Info", "OCAP2 AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; - - ocap_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; - EGVAR(diary,about) = player createDiaryRecord [ - "OCAP2Info", - [ - "About", - ( - "OCAP2
" + - "Addon version: " + GVARMAIN(version) + - "
" + - "Extension version: " + (EGVAR(extension,version) # 0) + " (built " + (EGVAR(extension,version) # 1) + ")" + - "
" + - "https://github.com/OCAP2/OCAP" + - "

" + - "OCAP2 is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + - "

" + - "Recording status can be found in the Status section." + - "

" + - "DISCLAIMER: This mission may be recorded and made publicly available at the discretion of the server administrators. Please be aware that your actions during this mission will be tracked and attributed to your in-game username." - ) - ] - ]; - - EGVAR(diary,status) = player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - "OCAP2 initialized. Awaiting configured capture conditions." - ] - ]; - }] call CBA_fnc_waitUntilAndExecute; -} remoteExecCall ["call", 0, true]; - -// Support both methods of setting mission name. -GVAR(missionName) = getMissionConfigValue ["onLoadName", ""]; -if (GVAR(missionName) == "") then { - GVAR(missionName) = briefingName; -}; - -// Add event missions -call FUNC(addEventMission); -[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay)]] call EFUNC(extension,sendData); -0 spawn FUNC(captureLoop); - -[":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); - -0 spawn { - if (GVAR(shouldSave)) exitWith {}; - LOG(["Waiting freeze end!"]); - waitUntil {sleep 1.4}; - LOG(["Waiting until EGVAR(settings,minMissionTime) ends"]); - sleep EGVAR(settings,minMissionTime); - LOG(["Recording duration minimum has been exceeded. will save."]); - GVAR(shouldSave) = true; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf b/x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf deleted file mode 100644 index 579602b..0000000 --- a/x/ocap2/addons/recorder/functions/fn_startCaptureLoop.sqf +++ /dev/null @@ -1,212 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: FUNC(captureLoop) - -Description: - Iterates through units, declares they exist, and conditional records their state at an interval defined in userconfig.hpp. - - This is the core processing loop that determines when new units enter the world, all the details about them, classifies which to exclude, and determines their health/life status. It has both unit and vehicle tracking. - - This is spawned during . - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - 0 spawn FUNC(captureLoop); - --- - -Public: - No - -Author: - Dell, Zealot, IndigoFox, Fank ----------------------------------------------------------------------------- */ - -#include "\userconfig\ocap\config.hpp" -#include "script_macros.hpp" - -// Function: _isKindOfApc -// Determines whether the vehicle is an APC by checking class inheritance -private _isKindOfApc = { - _bool = false; - { - if (_this isKindOf _x) exitWith {_bool = true;};false - } count ["Wheeled_APC_F","Tracked_APC","APC_Wheeled_01_base_F","APC_Wheeled_02_base_F", - "APC_Wheeled_03_base_F","APC_Tracked_01_base_F","APC_Tracked_02_base_F","APC_Tracked_03_base_F"]; - _bool -}; - -// Function: _getClass -// Gets generalized 'class' of vehicle to determine what icon to assign during playback -private _getClass = { - if (_this isKindOf "Truck_F") exitWith {"truck"}; // Should be higher than Car - if (_this call _isKindOfApc) exitWith {"apc"}; - if (_this isKindOf "Car") exitWith {"car"}; - if (_this isKindOf "Tank") exitWith {"tank"}; - if (_this isKindOf "StaticMortar") exitWith {"static-mortar"}; - if (_this isKindOf "StaticWeapon") exitWith {"static-weapon"}; - if (_this isKindOf "ParachuteBase") exitWith {"parachute"}; - if (_this isKindOf "Helicopter") exitWith {"heli"}; - if (_this isKindOf "Plane") exitWith {"plane"}; - if (_this isKindOf "Air") exitWith {"plane"}; - if (_this isKindOf "Ship") exitWith {"sea"}; - "unknown" -}; - - - - -waitUntil{(count(allPlayers) >= ocap_minPlayerCount)}; - -// bool: GVAR(capturing) -GVAR(capturing) = true; -GVAR(startTime) = time; -LOG(ARR3(__FILE__, "GVAR(capturing) start, time:", GVAR(startTime))); - -{ - [{!isNull player}, { - player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - "OCAP2 recording conditions met -- beginning capture." - ], taskNull, "", false - ]; - player setDiarySubjectPicture [ - "OCAP2Info", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" - ]; - }] call CBA_fnc_waitUntilAndExecute; -} remoteExecCall ["call", 0, true]; - - -[] call FUNC(updateTime); - -private _id = 0; -while {GVAR(capturing)} do { - isNil { - if (GVAR(captureFrameNo) == 10 || (GVAR(captureFrameNo) > 10 && ocap_trackTimes && GVAR(captureFrameNo) % ocap_trackTimeInterval == 0)) then { - [] call FUNC(updateTime); - }; - - if (GVAR(captureFrameNo) % (60 / EGVAR(settings,frameCaptureDelay)) == 0) then { - publicVariable "GVAR(captureFrameNo)"; - { - player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - ("Capture frame: " + str(GVAR(captureFrameNo)) + "") - ] - ]; - } remoteExecCall ["call", 0, false]; - }; - - { - if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { - if (_x isKindOf "Logic") exitWith { - _x setVariable [QGVARMAIN(exclude), true]; - _x setVariable [QGVARMAIN(isInitialized), true]; - }; - _x setVariable [QGVARMAIN(id), _id]; - [":NEW:UNIT:", [ - GVAR(captureFrameNo), //1 - _id, //2 - name _x, //3 - groupID (group _x), //4 - str side group _x, //5 - BOOL(isPlayer _x), //6 - roleDescription _x // 7 - ]] call EFUNC(extension,sendData); - [_x] spawn ocap_fnc_addEventHandlers; - _id = _id + 1; - _x setVariable [QGVARMAIN(isInitialized), true]; - }; - if !(_x getVariable [QGVARMAIN(exclude), false]) then { - private _unitRole = _x getVariable [QGVARMAIN(unitType), ""]; - if (GVAR(captureFrameNo) % 10 == 0 || _unitRole == "") then { - _unitRole = [_x] call ocap_fnc_getUnitType; - _x setVariable [QGVARMAIN(unitType), _unitRole]; - }; - - private _lifeState = 0; - if (alive _x) then { - if (ocap_preferACEUnconscious && !isNil "ace_common_fnc_isAwake") then { - _lifeState = if ([_x] call ace_common_fnc_isAwake) then {1} else {2}; - } else { - _lifeState = if (lifeState _x isEqualTo "INCAPACITATED") then {2} else {1}; - }; - }; - - _pos = getPosASL _x; - [":UPDATE:UNIT:", [ - (_x getVariable QGVARMAIN(id)), //1 - _pos, //2 - round getDir _x, //3 - _lifeState, //4 - BOOL(!((vehicle _x) isEqualTo _x)), //5 - if (alive _x) then {name _x} else {""}, //6 - BOOL(isPlayer _x), //7 - _unitRole //8 - ]] call EFUNC(extension,sendData); - }; - false - } count (allUnits + allDeadMen); - - { - if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { - _vehType = typeOf _x; - _class = _vehType call _getClass; - _toExcludeKind = false; - if (count ocap_excludeKindFromRecord > 0) then { - private _vic = _x; - { - if (_vic isKindOf _x) exitWith { - _toExcludeKind = true; - }; - } forEach ocap_excludeKindFromRecord; - }; - if ((_class isEqualTo "unknown") || (_vehType in ocap_excludeClassFromRecord) || _toExcludeKind) exitWith { - LOG(ARR2("WARNING: vehicle is defined as 'unknown' or exclude:", _vehType)); - _x setVariable [QGVARMAIN(isInitialized), true]; - _x setVariable [QGVARMAIN(exclude), true]; - }; - - _x setVariable [QGVARMAIN(id), _id]; - [":NEW:VEH:", [ - GVAR(captureFrameNo), //1 - _id, //2 - _class, //3 - getText (configFile >> "CfgVehicles" >> _vehType >> "displayName") //4 - ]] call EFUNC(extension,sendData); - [_x] spawn ocap_fnc_addEventHandlers; - _id = _id + 1; - _x setVariable [QGVARMAIN(isInitialized), true]; - }; - if !(_x getVariable [QGVARMAIN(exclude), false]) then { - private _crew = []; - { - if (_x getVariable [QGVARMAIN(isInitialized), false]) then { - _crew pushBack (_x getVariable QGVARMAIN(id)); - }; false - } count (crew _x); - _pos = getPosASL _x; - [":UPDATE:VEH:", [ - (_x getVariable QGVARMAIN(id)), //1 - _pos, //2 - round getDir _x, //3 - BOOL(alive _x), //4 - _crew, //5 - GVAR(captureFrameNo) // 6 - ]] call EFUNC(extension,sendData); - }; - false - } count vehicles; - }; - sleep (call ocap_fnc_getDelay); - GVAR(captureFrameNo) = GVAR(captureFrameNo) + 1; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_trackAceExplLife.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceExplLife.sqf deleted file mode 100644 index 53f1b32..0000000 --- a/x/ocap2/addons/recorder/functions/fn_trackAceExplLife.sqf +++ /dev/null @@ -1,71 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_trackAceExplLife - -Description: - Triggered in and monitors the status of a mine to ensure markers during playback reflect arm/disarm/detonation of mines. - -Parameters: - _unarmedPlacement - The CfgMagazines item that was placed. [Object] - _placedPos - The position it was placed at. [PositionAGLS] - -Returns: - Nothing - -Examples: - --- Code - [(_this # 0), _placedPos] spawn ocap_fnc_trackAceExplLife; - --- - -Public: - No - -Author: - IndigoFox ----------------------------------------------------------------------------- */ - -waitUntil {isNull (_this # 0)}; -sleep 1; -params ["_unarmedPlacement", "_placedPos"]; - -_nearMines = []; -_nearMines append (_placedPos nearObjects ["TimeBombCore", 5]); -_nearMines append (_placedPos nearObjects ["APERSMine_Range_Ammo", 5]); -_nearMines append (_placedPos nearObjects ["ATMine_Range_Ammo", 5]); - -if (_nearMines isEqualTo []) then { - exit; -} else { - _armedMine = _nearMines # 0; - - _int = random 2000; - - _explType = typeOf _armedMine; - _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); - _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); - - _placedPos = getPosASL _armedMine; - _placer = _placedPos nearestObject "Man"; - _placer addOwnedMine _armedMine; - - _markTextLocal = format["%1", _explosiveDisp]; - _markName = format["Mine#%1/%2", _int, _placedPos]; - _markColor = "ColorRed"; - _markerType = "Minefield"; - - ["ocap_handleMarker", ["CREATED", _markName, _placer, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - waitUntil {isNull _armedMine}; - - ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_localEvent; - - _markerType = "waypoint"; - _markName = format["Detonation#%1", _int]; - - ["ocap_handleMarker", ["CREATED", _markName, _placer, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - [_markName] spawn { - params ["_markName"]; - sleep 10; - ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_localEvent; - }; -}; diff --git a/x/ocap2/addons/recorder/functions/fn_trackAceExplPlace.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceExplPlace.sqf deleted file mode 100644 index 2a99ed6..0000000 --- a/x/ocap2/addons/recorder/functions/fn_trackAceExplPlace.sqf +++ /dev/null @@ -1,34 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_trackAceExplPlace - -Description: - Adds Init code for any ACE_Explosives_Place object that is created, i.e. when a unit uses ACE self-interaction to place down an explosive. - - The Init code will spawn an instance of . - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - call ocap_fnc_trackAceExplPlace; - --- - -Public: - No - -Author: - IndigoFox ----------------------------------------------------------------------------- */ - -// on any ACE explosive or mine *placement* via interaction menu, will execute the code here -["ACE_Explosives_Place", "init", { - - if (!isServer) exitWith {}; - _placedPos = getPosASL (_this # 0); - [(_this # 0), _placedPos] spawn ocap_fnc_trackAceExplLife; - -}] call CBA_fnc_addClassEventHandler; diff --git a/x/ocap2/addons/recorder/functions/fn_trackAceRemoteDet.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceRemoteDet.sqf deleted file mode 100644 index b9aaa75..0000000 --- a/x/ocap2/addons/recorder/functions/fn_trackAceRemoteDet.sqf +++ /dev/null @@ -1,30 +0,0 @@ -// LOG ACE REMOTE DET EVENTS -[{ - params ["_unit", "_range", "_explosive", "_fuzeTime", "_triggerItem"]; - - _int = random 2000; - - // expl is ammo, need to find mag, and display name of mag - _explosiveMag = getText(configFile >> "CfgAmmo" >> (typeOf _explosive) >> "defaultMagazine"); - _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); - _triggerItemDisp = getText(configFile >> "CfgWeapons" >> _triggerItem >> "displayName"); - - _markTextLocal = format["%1", _explosiveDisp]; - _markName = format["Detonation#%1", _int]; - _markColor = "ColorRed"; - _markerType = "waypoint"; - _pos = getPosASL _explosive; - - ["ocap_handleMarker", ["CREATED", _markName, _unit, _pos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true]] call CBA_fnc_serverEvent; - - [_markName] spawn { - params ["_markName"]; - - sleep 10; - // [format['["ocap_handleMarker", ["DELETED", %1]] call CBA_fnc_serverEvent;', _markName]] remoteExec ["hint", 0]; - // systemChat format['["ocap_handleMarker", ["DELETED", %1]] call CBA_fnc_serverEvent;', _markName]; - ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_serverEvent; - }; - true; - -}] call ace_explosives_fnc_addDetonateHandler; diff --git a/x/ocap2/addons/recorder/functions/fn_trackAceThrowing.sqf b/x/ocap2/addons/recorder/functions/fn_trackAceThrowing.sqf deleted file mode 100644 index c61c350..0000000 --- a/x/ocap2/addons/recorder/functions/fn_trackAceThrowing.sqf +++ /dev/null @@ -1,142 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: ocap_fnc_trackAceThrowing - -Description: - Adds a local CBA event listener on units that will trigger when a projectile is thrown using ACE Advanced Throwing and add markers to playback that trace its path. Added to units in . - -Parameters: - None - -Returns: - Nothing - -Examples: - --- Code - ocap_fnc_trackAceThrowing remoteExec ["call", _entity]; - --- - -Public: - No - -Author: - IndigoFox ----------------------------------------------------------------------------- */ - -trackThrows = ["ace_throwableThrown", { - _this spawn { - - params["_unit", "_projectile"]; - - if (isNull _projectile) then { - _projectile = nearestObject [_unit, "CA_Magazine"]; - }; - - // systemChat str _this; - - // note that thrown objects outside of ACE explosives do not include a "default magazine" property in their config. - // this script will attempt to find a matching classname in CfgMagazines, as some chemlights and smokes are built this way. - // if not found, a default magazine value will be assigned (m67 frag, white smoke, green chemlight) - - _projType = typeOf _projectile; - _projConfig = configOf _projectile; - _projName = getText(configFile >> "CfgAmmo" >> _projType >> "displayName"); - - // systemChat format["Config name: %1", configOf _projectile]; - - _ammoSimType = getText(configFile >> "CfgAmmo" >> _projType >> "simulation"); - // systemChat format["Projectile type: %1", _ammoSimType]; - - _markerType = ""; - _markColor = ""; - _magDisp = ""; - _magPic = ""; - - _magType = getText(_projConfig >> "defaultMagazine"); - if (_magType == "") then { - _magType = configName(configfile >> "CfgMagazines" >> _projType) - }; - - if (!(_magType isEqualTo "")) then { - // systemChat format["Mag type: %1", _magType]; - - _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayNameShort"); - if (_magDisp == "") then { - _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayName") - }; - if (_magDisp == "") then { - _magDisp = _projName; - }; - - _magPic = (getText(configfile >> "CfgMagazines" >> _magType >> "picture")); - // hint parseText format["Projectile fired:
", _magPic]; - if (_magPic == "") then { - _markerType = "mil_triangle"; - _markColor = "ColorRed"; - } else { - _magPicSplit = _magPic splitString "\"; - _magPic = _magPicSplit#((count _magPicSplit) - 1); - _markerType = format["magIcons/%1", _magPic]; - _markColor = "ColorWhite"; - }; - } else { - _markerType = "mil_triangle"; - _markColor = "ColorRed"; - // set defaults based on ammo sim type, if no magazine could be matched - switch (_ammoSimType) do { - case "shotGrenade":{ - _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; - _magDisp = "Frag"; - }; - case "shotSmokeX":{ - _magPic = "\A3\Weapons_f\data\ui\gear_smokegrenade_white_ca.paa"; - _magDisp = "Smoke"; - }; - case "shotIlluminating":{ - _magPic = "\A3\Weapons_F\Data\UI\gear_flare_white_ca.paa"; - _magDisp = "Flare"; - }; - default { - _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; - _magDisp = "Frag"; - }; - }; - // hint parseText format["Projectile fired:
", _magPic]; - _magPicSplit = _magPic splitString "\"; - _magPic = _magPicSplit#((count _magPicSplit) - 1); - _markerType = format["magIcons/%1", _magPic]; - _markColor = "ColorWhite"; - }; - - if (!(_ammoSimType isEqualTo "shotBullet")) then { - - _int = random 2000; - - _markTextLocal = format["%1", _magDisp]; - _markName = format["Projectile#%1", _int]; - - _throwerPos = getPosASL _unit; - - ["ocap_handleMarker", ["CREATED", _markName, _unit, _throwerPos, _markerType, "ICON", [1,1], 0, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_serverEvent; - - private _lastPos = []; - waitUntil { - _pos = getPosASL _projectile; - if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { - true - }; - _lastPos = _pos; - ["ocap_handleMarker", ["UPDATED", _markName, _unit, _pos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; - sleep 0.1; - false; - }; - - if !((count _lastPos) isEqualTo 0) then { - // if (count _lastPos == 3) then { - ["ocap_handleMarker", ["UPDATED", _markName, _unit, _lastPos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; - }; - - sleep 10; - ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_serverEvent; - }; - }; -}] call CBA_fnc_addEventHandler; diff --git a/x/ocap2/addons/recorder/functions/markerTestingChecklist.txt b/x/ocap2/addons/recorder/functions/markerTestingChecklist.txt deleted file mode 100644 index 73c4c97..0000000 --- a/x/ocap2/addons/recorder/functions/markerTestingChecklist.txt +++ /dev/null @@ -1,19 +0,0 @@ -Testing Checklist: - -1. Mission should have ellipse and rectangle markers scripted or placed in editor. - - some rotated - - some straight -2. Players should create ON MAP SCREEN: - - 1 icon with text, straight - - 1 icon without text, straight - - 1 icon with text, rotated - - 1 icon without text, rotated - - 1 icon of type "Infantry", "Armor", etc, rotated - - at least one drawn line (CTRL+Click/Drag) -3. Players should create ONCE LOADED IN: - - 1 icon with text, straight - - 1 icon without text, straight - - 1 icon with text, rotated - - 1 icon without text, rotated - - 1 icon of type "Infantry", "Armor", etc, rotated - - at least one drawn line (CTRL+Click/Drag) diff --git a/x/ocap2/addons/recorder/functions/script_macros.hpp b/x/ocap2/addons/recorder/functions/script_macros.hpp deleted file mode 100644 index e430f34..0000000 --- a/x/ocap2/addons/recorder/functions/script_macros.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "\ocap2\script_macros.hpp" From 5606d06912383221d01bf331455927b3c2507973 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Mon, 28 Mar 2022 02:07:11 -0400 Subject: [PATCH 03/26] bunch more changes --- x/ocap2/addons/main/XEH_postInit.sqf | 2 +- x/ocap2/addons/main/XEH_preInit.sqf | 197 +--------------- x/ocap2/addons/main/script_macros.hpp | 5 +- x/ocap2/addons/mod.cpp | 6 +- x/ocap2/addons/recorder/XEH_postInit.sqf | 6 +- x/ocap2/addons/recorder/XEH_preInit.sqf | 220 +++++++++++++++++- x/ocap2/addons/recorder/XEH_prep.sqf | 1 + x/ocap2/addons/recorder/fnc_aceExplosives.sqf | 48 ++-- x/ocap2/addons/recorder/fnc_aceThrowing.sqf | 18 +- .../addons/recorder/fnc_addEventMission.sqf | 15 +- .../recorder/fnc_addUnitEventHandlers.sqf | 23 +- x/ocap2/addons/recorder/fnc_captureLoop.sqf | 36 +-- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 217 +++++++---------- x/ocap2/addons/recorder/fnc_eh_hit.sqf | 17 +- x/ocap2/addons/recorder/fnc_eh_killed.sqf | 13 +- x/ocap2/addons/recorder/fnc_exportData.sqf | 37 ++- x/ocap2/addons/recorder/fnc_getAmmoData.sqf | 62 +++++ x/ocap2/addons/recorder/fnc_getDelay.sqf | 4 +- .../recorder/fnc_getEventWeaponText.sqf | 9 +- .../addons/recorder/fnc_handleCustomEvent.sqf | 2 + x/ocap2/addons/recorder/fnc_handleMarkers.sqf | 21 +- x/ocap2/addons/recorder/fnc_init.sqf | 107 ++++++++- .../addons/recorder/fnc_startRecording.sqf | 16 +- 23 files changed, 649 insertions(+), 433 deletions(-) create mode 100644 x/ocap2/addons/recorder/fnc_getAmmoData.sqf diff --git a/x/ocap2/addons/main/XEH_postInit.sqf b/x/ocap2/addons/main/XEH_postInit.sqf index e3eaf9a..b123aab 100644 --- a/x/ocap2/addons/main/XEH_postInit.sqf +++ b/x/ocap2/addons/main/XEH_postInit.sqf @@ -3,7 +3,7 @@ // During postInit, we'll remoteExec these settings onto clients so vars are synchronized and modifiable during a mission. { _x remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; -} forEach GVARMAIN(allSettings); +} forEach GVAR(allSettings); ADDON = true; diff --git a/x/ocap2/addons/main/XEH_preInit.sqf b/x/ocap2/addons/main/XEH_preInit.sqf index 1a46b65..5de52f5 100644 --- a/x/ocap2/addons/main/XEH_preInit.sqf +++ b/x/ocap2/addons/main/XEH_preInit.sqf @@ -4,201 +4,14 @@ // This PreInit creates the settings on the server, only so that the global vars will be registered and synchronized with clients. -GVARMAIN(allSettings) = [ - // AUTO START SETTINGS - [ - QEGVAR(settings,autoStart), - "CHECKBOX", // setting type - [ - "Auto Start Recording", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Automatically start OCAP recordings at session start. Default: true" - ], - [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - true, // default enabled - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - true // requires restart to apply - ], - - [ - QEGVAR(settings,minPlayerCount), - "SLIDER", // setting type - [ - "Minimum Player Count", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Auto-start will begin once this player count is reached. Default: 15" - ], - [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - [ - 1, // min - 150, // max - 15, // default - 0, // trailing decimals - false // percentage - ], // data for this setting: [min, max, default, number of shown trailing decimals] - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - true // requires restart to apply - ], - - - - // RECORDING SETTINGS - [ - QEGVAR(settings,frameCaptureDelay), - "SLIDER", // setting type - [ - "Frame Capture Delay", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds. Default: 1" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - [ - 0.25, // min - 10, // max - 1, // default - 0, // trailing decimals - false // percentage - ], // data for this setting: [min, max, default, number of shown trailing decimals] - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,minMissionTime), - "SLIDER", // setting type - [ - "Required Duration to Save", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "A recording must be at least this long (in minutes) to save. Default: 20" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - [ - 1, // min - 120, // max - 20, // default - 0, // trailing decimals - false // percentage - ], // data for this setting: [min, max, default, number of shown trailing decimals] - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,preferACEUnconscious), - "CHECKBOX", // setting type - [ - "Use ACE3 Medical", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "If true, will check ACE3 medical status on units. If false, or ACE3 isn't loaded, fall back to vanilla. Default: true" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - true, // default enabled - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,excludeClassFromRecord), - "EDITBOX", // setting type - [ - "Classnames to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - "['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']", // default string value - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,excludeKindFromRecord), - "EDITBOX", // setting type - [ - "Object KindOfs to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - "[]", // default string value - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,excludeMarkerFromRecord), - "EDITBOX", // setting type - [ - "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_']" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - "['SystemMarker_']", // default string value - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,trackTimes), - "CHECKBOX", // setting type - [ - "Enable Mission Time Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Will continuously track in-game world time during a mission. Useful for accelerated/skipped time scenarios. Default: false" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - false, // default enabled - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - [ - QEGVAR(settings,trackTimeInterval), - "SLIDER", // setting type - [ - "Mission Time Tracking Interval", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "If time tracking is enabled, it will be checked every X capture frames. Default: 10" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - [ - 5, // min - 25, // max - 10, // default - 0, // trailing decimals - false // percentage - ], // data for this setting: [min, max, default, number of shown trailing decimals] - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - - - // SAVING SETTINGS - [ - QEGVAR(settings,saveMissionEnded), - "CHECKBOX", // setting type - [ - "Auto-save on MPEnded Event", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "If true, automatically save and export the mission when the MPEnded event fires. Default: true" - ], - [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - true, // default enabled - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - - - - // DEBUG +GVAR(allSettings) = [ + // Core [ QGVARMAIN(enabled), "CHECKBOX", // setting type [ "Recording Enabled", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Turns on or off all recording functionality. Will not reset anything from existing session, will just stop recording any new data. Default: true" + "Turns on or off most recording functionality. Will not reset anything from existing session, will just stop recording most new data. Note: For record/pause switching, use the CBA events! Default: true" ], [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. true, // default enabled @@ -206,7 +19,7 @@ GVARMAIN(allSettings) = [ { params ["_value"]; if (!isServer) exitWith {}; - EFUNC(recorder,init); + if (_value) then {call EFUNC(recorder,init)}; }, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply ], @@ -230,6 +43,6 @@ GVARMAIN(allSettings) = [ { _x call CBA_fnc_addSetting; -} forEach GVARMAIN(allSettings); +} forEach GVAR(allSettings); ADDON = true; diff --git a/x/ocap2/addons/main/script_macros.hpp b/x/ocap2/addons/main/script_macros.hpp index 7adcccc..c40eb31 100644 --- a/x/ocap2/addons/main/script_macros.hpp +++ b/x/ocap2/addons/main/script_macros.hpp @@ -36,7 +36,8 @@ #define ARR6(_arg1, _arg2, _arg3, _arg4, _arg5, _arg6) [_arg1, _arg2, _arg3, _arg4, _arg5, _arg6] - - #include "\x\cba\addons\main\script_macros_common.hpp" #include "\x\cba\addons\xeh\script_xeh.hpp" + +#define SHOULDSAVEEVENTS ((missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] > -1) + diff --git a/x/ocap2/addons/mod.cpp b/x/ocap2/addons/mod.cpp index 4a2a884..6a65751 100644 --- a/x/ocap2/addons/mod.cpp +++ b/x/ocap2/addons/mod.cpp @@ -1,11 +1,11 @@ -name = "OCAP"; // Name of your mod +name = "OCAP2"; // Name of your mod picture = "logo_ocap.paa"; // Picture displayed from the expansions menu. Optimal size is 2048x1024 //logoSmall = "\Samples_F\Data_01\Logos\logo_small.paa"; // Display next to the item added by the mod //logo = "\Samples_F\Data_01\Logos\logo.paa"; // Logo displayed in the main menu //logoOver = "\Samples_F\Data_01\Logos\logoOver.paa"; // When the mouse is over, in the main menu actionName = "Github"; // Text displayed on the "website" button -action = "https://github.com/OCAPv2"; // Website URL, that can accessed from the expansions menu -tooltipOwned = "Operation Capture and Playback v2"; // Tool tip displayed when the mouse is left over, in the main menu +action = "https://github.com/OCAP2/OCAP"; // Website URL, that can accessed from the expansions menu +tooltipOwned = "Operation Capture and Playback 2"; // Tool tip displayed when the mouse is left over, in the main menu // Color used for DLC stripes and backgrounds (RGBA) dlcColor[] = diff --git a/x/ocap2/addons/recorder/XEH_postInit.sqf b/x/ocap2/addons/recorder/XEH_postInit.sqf index 521017f..36b9004 100644 --- a/x/ocap2/addons/recorder/XEH_postInit.sqf +++ b/x/ocap2/addons/recorder/XEH_postInit.sqf @@ -1,8 +1,8 @@ #include "script_component.hpp" #include "XEH_prep.sqf" -if (!EGVAR(settings,autoStart)) then { - call FUNC(init); -}; +{ + _x remoteExec ["CBA_fnc_addSetting", [0, -2] select isDedicated, true]; +} forEach GVAR(allSettings); ADDON = true; diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap2/addons/recorder/XEH_preInit.sqf index d9174b5..d73a536 100644 --- a/x/ocap2/addons/recorder/XEH_preInit.sqf +++ b/x/ocap2/addons/recorder/XEH_preInit.sqf @@ -1,7 +1,225 @@ #include "script_component.hpp" #include "XEH_prep.sqf" -if (EGVAR(settings,autoStart) && !is3DEN) then { +GVAR(allSettings) = [ + // AUTO START SETTINGS + [ + QEGVAR(settings,autoStart), + "CHECKBOX", // setting type + [ + "Auto Start Recording", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Automatically start OCAP recordings at session start. Default: true" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply + ], + + [ + QEGVAR(settings,minPlayerCount), + "SLIDER", // setting type + [ + "Minimum Player Count", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Auto-start will begin once this player count is reached. Default: 15" + ], + [COMPONENT_NAME, "Auto-start Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 1, // min + 150, // max + 15, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + + // RECORDING SETTINGS + [ + QEGVAR(settings,frameCaptureDelay), + "SLIDER", // setting type + [ + "Frame Capture Delay", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds. Default: 1" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 0.25, // min + 10, // max + 1, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply + ], + + [ + QEGVAR(settings,preferACEUnconscious), + "CHECKBOX", // setting type + [ + "Use ACE3 Medical", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If true, will check ACE3 medical status on units. If false, or ACE3 isn't loaded, fall back to vanilla. Default: true" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,excludeClassFromRecord), + "EDITBOX", // setting type + [ + "Classnames to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,excludeKindFromRecord), + "EDITBOX", // setting type + [ + "Object KindOfs to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "[]", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,excludeMarkerFromRecord), + "EDITBOX", // setting type + [ + "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_']" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "['SystemMarker_']", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,pauseOnEmpty), + "CHECKBOX", // setting type + [ + "Auto-Save When No Players", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minumum save duration setting. Default: true" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,trackTimes), + "CHECKBOX", // setting type + [ + "Enable Mission Time Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Will continuously track in-game world time during a mission. Useful for accelerated/skipped time scenarios. Default: false" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + false, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,trackTimeInterval), + "SLIDER", // setting type + [ + "Mission Time Tracking Interval", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If time tracking is enabled, it will be checked every X capture frames. Default: 10" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 5, // min + 25, // max + 10, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + + + // SAVING SETTINGS + [ + QEGVAR(settings,saveTag), + "EDITBOX", // setting type + [ + "Mission Type Tag", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If not overriden by the exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions." + ], + [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "TvT", // default string value + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,saveMissionEnded), + "CHECKBOX", // setting type + [ + "Auto-save on MPEnded Event", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "If true, automatically save and export the mission when the MPEnded event fires. Default: true" + ], + [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + + [ + QEGVAR(settings,minMissionTime), + "SLIDER", // setting type + [ + "Required Duration to Save", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "A recording must be at least this long (in minutes) to auto-save. Calling an 'ocap_exportData' CBA server event will override this restriction. Default: 20" + ], + [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [ + 1, // min + 120, // max + 20, // default + 0, // trailing decimals + false // percentage + ], // data for this setting: [min, max, default, number of shown trailing decimals] + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply + ] +]; + +{ + _x call CBA_fnc_addSetting; +} forEach GVAR(allSettings); + +if (!is3DEN) then { call FUNC(init); }; diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap2/addons/recorder/XEH_prep.sqf index 461c888..a7afbb9 100644 --- a/x/ocap2/addons/recorder/XEH_prep.sqf +++ b/x/ocap2/addons/recorder/XEH_prep.sqf @@ -23,6 +23,7 @@ PREP(eh_hit); PREP(eh_killed); PREP(getInstigator); PREP(getEventWeaponText); +PREP(getAmmoData); PREP(aceThrowing); PREP(aceExplosives); diff --git a/x/ocap2/addons/recorder/fnc_aceExplosives.sqf b/x/ocap2/addons/recorder/fnc_aceExplosives.sqf index 12be079..cdb1af0 100644 --- a/x/ocap2/addons/recorder/fnc_aceExplosives.sqf +++ b/x/ocap2/addons/recorder/fnc_aceExplosives.sqf @@ -31,7 +31,10 @@ Author: #include "script_component.hpp" EGVAR(listener,aceExplosives) = ["ace_explosives_place", { - params [_explosive, _dir, _pitch, _unit]; + + if (!SHOULDSAVEEVENTS) exitWith {}; + + params ["_explosive", "_dir", "_pitch", "_unit"]; private _int = random(2000); @@ -47,50 +50,57 @@ EGVAR(listener,aceExplosives) = ["ace_explosives_place", { _markColor = "ColorRed"; _markerType = "Minefield"; + if (GVARMAIN(isDebug)) then { + format["Created explosive placed marker, %1, %2", _markName, _explosiveDisp] SYSCHAT; + OCAPEXTLOG(ARR3("Created explosive placed marker", _markName, _explosiveDisp)); + }; + [QGVARMAIN(handleMarker), [ "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true ]] call CBA_fnc_localEvent; - TRACE_2("Created explosive placed marker ", _markName); - if (GVARMAIN(isDebug)) then { - ("Created explosive placed marker " + _markName) SYSCHAT; - }; - [{isNull (_this#0)}, { // wait until the mine is null (exploded), and mark this for playback params ["_explosive", "_explosiveDisp", "_unit", "_placedPos", "_markName", "_int"]; - // remove previous marker - ["ocap_handleMarker", ["DELETED", _markName]] call CBA_fnc_localEvent; + // set unit who placed's lastFired var as the explosive so kills are registered to the explosive + _unit setVariable [ + QGVARMAIN(lastFired), + _explosiveDisp + ]; - TRACE_2("Removed explosive placed marker ", _markName); + // remove previous marker if (GVARMAIN(isDebug)) then { - ("Removed explosive placed marker " + _markName) SYSCHAT; + format["Removed explosive placed marker, %1, %2", _markName, _explosiveDisp] SYSCHAT; + OCAPEXTLOG(ARR3("Removed explosive placed marker", _markName, _explosiveDisp)); }; + [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent; + _markTextLocal = format["%1", _explosiveDisp]; _markName = format["Detonation#%1", _int]; _markColor = "ColorRed"; _markerType = "waypoint"; + if (GVARMAIN(isDebug)) then { + format["Created explosive explosion marker, %1, %2", _markName, _explosiveDisp] SYSCHAT; + OCAPEXTLOG(ARR3("Created explosive explosion marker", _markName, _explosiveDisp)); + }; + [QGVARMAIN(handleMarker), [ "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true ]] call CBA_fnc_localEvent; - TRACE_2("Created explosive explosion marker ", _markName); - if (GVARMAIN(isDebug)) then { - ("Created explosive explosion " + _markName) SYSCHAT; - }; [{ - params ["_markName"]; - [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent; - TRACE_2("Removed explosive explosion marker ", _markName); + params ["_markName", "_explosiveDisp"]; if (GVARMAIN(isDebug)) then { - ("Removed explosive explosion marker " + _markName) SYSCHAT; + format["Removed explosive explosion marker, %1, %2", _markName, _explosiveDisp] SYSCHAT; + OCAPEXTLOG(ARR3("Removed explosive explosion marker", _markName, _explosiveDisp)); }; - }, [_markName], 10] call CBA_fnc_waitAndExecute; + [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent; + }, [_markName, _explosiveDisp], 10] call CBA_fnc_waitAndExecute; }, [_explosive, _explosiveDisp, _unit, _placedPos, _markName, _int]] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/fnc_aceThrowing.sqf b/x/ocap2/addons/recorder/fnc_aceThrowing.sqf index ce805e3..4fddecd 100644 --- a/x/ocap2/addons/recorder/fnc_aceThrowing.sqf +++ b/x/ocap2/addons/recorder/fnc_aceThrowing.sqf @@ -25,6 +25,8 @@ Author: EGVAR(listener,aceThrowing) = ["ace_throwableThrown", { + if (!SHOULDSAVEEVENTS) exitWith {}; + _this spawn { params["_unit", "_projectile"]; @@ -43,9 +45,9 @@ EGVAR(listener,aceThrowing) = ["ace_throwableThrown", { _projConfig = configOf _projectile; _projName = getText(configFile >> "CfgAmmo" >> _projType >> "displayName"); - TRACE_2("Detected ACE throwing of ", _projName); if (GVARMAIN(isDebug)) then { - ("Detected ACE throwing of " + _projName) SYSCHAT; + format["Detected ACE throwing of %1", _projName] SYSCHAT; + OCAPEXTLOG(ARR2("Detected ACE throwing of ", _projName)); }; // systemChat format["Config name: %1", configOf _projectile]; @@ -135,12 +137,20 @@ EGVAR(listener,aceThrowing) = ["ace_throwableThrown", { [QGVARMAIN(handleMarker), ["UPDATED", _markName, _unit, _pos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; // here, monitor fast moving missiles/rockets/shells every 0.3 seconds. monitor smokes, grenades, flares, mines in line with the configured frame capture delay - sleep ([0.3, EGVAR(settings,frameCaptureDelay)] select {_ammoSimType in ["ShotSmokeX","ShotGrenade","ShotIlluminating","ShotMine"]}); + sleep (([0.3, GVAR(frameCaptureDelay)] select {_ammoSimType in ["ShotSmokeX","ShotGrenade","ShotIlluminating","ShotMine"]})#0); false; }; if !((count _lastPos) isEqualTo 0) then { - // if (count _lastPos == 3) then { + isNil { + // for non-bullets, set the last fired variable of the soldier so hits/kills are recorded accurately + if (_ammoSimType == "shotGrenade") then { + _unit setVariable [ + QGVARMAIN(lastFired), + _projName + ]; + }; + }; [QGVARMAIN(handleMarker), ["UPDATED", _markName, _unit, _lastPos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; }; diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf index 194a10d..f837861 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -51,12 +51,24 @@ addMissionEventHandler ["EntityRespawned", { }; }]; +// Listen for global ACE Explosive placement events if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then { call FUNC(aceExplosives); }; +// Listen for local ACE Throwing events, for any units owned by the server +if (isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing")) then { + call FUNC(aceThrowing); +}; + addMissionEventHandler ["MPEnded", { - if (EGVAR(settings,saveMissionEnded)) then { + if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then { + ["Mission ended automatically"] call FUNC(exportData); + }; +}]; + +addMissionEventHandler ["Ended", { + if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then { ["Mission ended automatically"] call FUNC(exportData); }; }]; @@ -80,6 +92,7 @@ EGVAR(listener,exportData) = [QGVARMAIN(record), { // This will PAUSE recording EGVAR(listener,exportData) = [QGVARMAIN(pause), { GVAR(recording) = false; + publicVariable QGVAR(recording); }] call CBA_fnc_addEventHandler; // Custom event handler with key "ocap2_exportData" diff --git a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf index 467a828..bd28b62 100644 --- a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf +++ b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf @@ -28,17 +28,32 @@ Author: params ["_entity", ["_respawn", false]]; + +// FIREDMAN if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then { - _entity addEventHandler ["FiredMan", { _this spawn FUNC(eh_firedMan); }]; + _entity addEventHandler ["FiredMan", { _this call FUNC(eh_firedMan); }]; }; + +// MPHIT _entity addMPEventHandler ["MPHit", { _this spawn FUNC(eh_hit); }]; +private _ownerId = owner _entity; if ( - !_respawn && + !_respawn && // exclude re-adding on respawn, as the CBA listener will already be present on the owner's machine (_entity call BIS_fnc_objectType) # 0 == "Soldier" && + _ownerId != 2 && // only add to entities not owned by server, as otherwise server will receive local events already isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing") ) then { // here, we must place a local listener of ACE throw events on the owner of the entity - // the client will then notify the server when such an event happens - FUNC(aceThrowing) remoteExec ["call", _entity]; + // the client will then notify the server when these local events happen + // we'll wait a max of 15 secs until that owner is at least to briefing stage, has completed PostInit, before sending + [ + {(getUserInfo (_this#0)) select 6 > 8}, + { + FUNC(aceThrowing) remoteExec ["call", _this#1]; + OCAPEXTLOG(ARR3("ADD ACE THROWING LISTENER", _this#0, _this#1)); + }, + [_ownerId, _entity], + 15 + ] call CBA_fnc_waitUntilAndExecute; }; diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf index d812cf1..a827f20 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -34,35 +34,27 @@ if (!isNil QGVAR(PFHObject)) then { }; if (isNil QGVAR(startTime)) then { GVAR(startTime) = time; + OCAPEXTLOG(ARR3(__FILE__, QGVAR(recording) + " started, time:", GVAR(startTime))); LOG(ARR3(__FILE__, QGVAR(recording) + " started, time:", GVAR(startTime))); }; GVAR(PFHObject) = [ { - if (!isNil {_private#0}) then { - if (EGVAR(settings,frameCaptureDelay) != _private#0) exitWith { - OCAPEXTLOG(ARR_3("Frame capture delay changed", _private#0, EGVAR(settings,frameCaptureDelay))); - TRACE_3("Frame capture delay changed", _private#0, EGVAR(settings,frameCaptureDelay)); - GVAR(recording) = false; - [{call FUNC(startCaptureLoop)}, [], EGVAR(settings,frameCaptureDelay) + _private#0] call CBA_fnc_waitAndExecute; - }; - }; - _frameCaptureDelay = EGVAR(settings,frameCaptureDelay); + private _loopStart = diag_tickTime; - TRACE_2("Frame", _frameCaptureDelay); if (GVAR(captureFrameNo) == 10 || (GVAR(captureFrameNo) > 10 && EGVAR(settings,trackTimes) && GVAR(captureFrameNo) % EGVAR(settings,trackTimeInterval) == 0)) then { [] call FUNC(updateTime); }; // update diary record every 320 frames - if (GVAR(captureFrameNo) % (320 / EGVAR(settings,frameCaptureDelay)) == 0) then { + if (GVAR(captureFrameNo) % (320 / GVAR(frameCaptureDelay)) == 0) then { publicVariable QGVAR(captureFrameNo); { player createDiaryRecord [ "OCAP2Info", [ "Status", - ("Capture frame: " + QGVAR(captureFrameNo) + "") + ("Capture frame: " + str (missionNamespace getVariable [QGVAR(captureFrameNo), "[not yet received]"]) + "") ] ]; } remoteExecCall ["call", 0, false]; @@ -138,15 +130,15 @@ GVAR(PFHObject) = [ _x setVariable [QGVARMAIN(exclude), true]; }; - _x setVariable [QGVARMAIN(id), _id]; + _x setVariable [QGVARMAIN(id), GVAR(nextId)]; [":NEW:VEH:", [ GVAR(captureFrameNo), //1 - _id, //2 + GVAR(nextId), //2 _class, //3 getText (configFile >> "CfgVehicles" >> _vehType >> "displayName") //4 ]] call EFUNC(extension,sendData); [_x] spawn FUNC(addUnitEventHandlers); - _id = _id + 1; + GVAR(nextId) = GVAR(nextId) + 1; _x setVariable [QGVARMAIN(isInitialized), true]; }; if !(_x getVariable [QGVARMAIN(exclude), false]) then { @@ -170,11 +162,19 @@ GVAR(PFHObject) = [ } count vehicles; GVAR(captureFrameNo) = GVAR(captureFrameNo) + 1; + publicVariable QGVAR(captureFrameNo); + + if (GVARMAIN(isDebug)) then { + private _logStr = format["Frame %1 processed in %2ms", GVAR(captureFrameNo), diag_tickTime - _loopStart]; + OCAPEXTLOG([_logStr]); + _logStr SYSCHAT; + }; }, - EGVAR(settings,frameCaptureDelay), // delay + GVAR(frameCaptureDelay), // delay [], // args { GVAR(recording) = true; + publicVariable QGVAR(recording); { // add diary entry for clients on recording start [{!isNull player}, { @@ -194,6 +194,7 @@ GVAR(PFHObject) = [ }, // code, executed when added { GVAR(recording) = false; + publicVariable QGVAR(recording); { // add diary entry for clients on recording start [{!isNull player}, { @@ -212,6 +213,5 @@ GVAR(PFHObject) = [ } remoteExecCall ["call", 0, true]; }, // code, executed when removed {GVAR(recording)}, // if true, execute PFH cycle - {!GVAR(recording) || !GVARMAIN(enabled)}, // if true, delete object - ["_frameCaptureDelay"] + {!GVAR(recording) || !GVARMAIN(enabled)} // if true, delete object ] call CBA_fnc_createPerFrameHandlerObject; diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 85ac8e1..690efa8 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -29,154 +29,111 @@ Author: ---------------------------------------------------------------------------- */ #include "script_component.hpp" -params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; +if (!SHOULDSAVEEVENTS) exitWith {}; -_frame = GVAR(captureFrameNo); +params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; -_firer setVariable [QGVARMAIN(lastFired), currentWeapon _firer]; +private _frame = GVAR(captureFrameNo); + +private _firerId = (_firer getVariable [QGVARMAIN(id), -1]); +if (_firerId == -1) exitWith {}; + +// set the firer's lastFired var as this weapon, so subsequent kills are logged accurately +isNil { + _firer setVariable [ + QGVARMAIN(lastFired), + format[ + "%1 [%2]", + getText (configFile >> "CfgWeapons" >> _weapon >> "displayName"), + getText (configFile >> "CfgWeapons" >> _muzzle >> "displayName") + ] + ]; +}; _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); - -// bullet handling, cut short -if (_ammoSimType isEqualTo "shotBullet") then { - [_projectile, _firer, _frame, _ammoSimType, _ammo] spawn { - params["_projectile", "_firer", "_frame", "_ammoSimType", "_ammo"]; +// _ammoSimType +// "ShotGrenade" // M67 +// "ShotRocket" // S-8 +// "ShotMissile" // R-27 +// "ShotShell" // VOG-17M, HE40mm +// "ShotMine" // Satchel charge +// "ShotIlluminating" // 40mm_green Flare +// "ShotSmokeX"; // M18 Smoke + + +switch (_ammoSimType) do { + case (_ammoSimType isEqualTo "shotBullet"): { + // [_projectile, _firer, _frame, _ammoSimType, _ammo] spawn { + // params["_projectile", "_firer", "_frame", "_ammoSimType", "_ammo"]; if (isNull _projectile) then { _projectile = nearestObject [_firer, _ammo]; }; - private _lastPos = []; - waitUntil { - _pos = getPosASL _projectile; - if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { - true - }; - _lastPos = _pos; - false; - }; - - if !((count _lastPos) isEqualTo 0) then { - [":FIRED:", [ - (_firer getVariable QGVARMAIN(id)), - _frame, - _lastPos - ]] call EFUNC(extension,sendData); - }; + GVAR(liveBullets) pushBack [_projectile, _firerId, _frame, getPosASL _projectile]; }; -} else { - // _ammoSimType - // simulation == "ShotSmokeX"; // M18 Smoke - // "ShotGrenade" // M67 - // "ShotRocket" // S-8 - // "ShotMissile" // R-27 - // "ShotShell" // VOG-17M, HE40mm - // "ShotIlluminating" // 40mm_green Flare - // "ShotMine" // Satchel remote - - _int = random 2000; - _muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName"); - if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayNameShort")}; - if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName")}; - _magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayNameShort"); - if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayName")}; - if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; - if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; - - // non-bullet handling - private ["_markTextLocal"]; - if (!isNull _vehicle) then { - _markTextLocal = format["[%3] %1 - %2", _muzzleDisp, _magDisp, ([configOf _vehicle] call BIS_fnc_displayName)]; - } else { - if (_ammoSimType isEqualTo "shotGrenade") then { - _markTextLocal = format["%1", _magDisp]; - } else { - _markTextLocal = format["%1 - %2", _muzzleDisp, _magDisp]; - }; - }; - - _markName = format["Projectile#%1", _int]; - _markColor = "ColorRed"; - _markerType = ""; - _magPic = (getText(configfile >> "CfgMagazines" >> _magazine >> "picture")); - if (_magPic == "") then { - _markerType = "mil_triangle"; - } else { - _magPicSplit = _magPic splitString "\"; - _magPic = _magPicSplit # ((count _magPicSplit) -1); - _markerType = format["magIcons/%1", _magPic]; - _markColor = "ColorWhite"; - }; - - - // _markStr = format["|%1|%2|%3|%4|%5|%6|%7|%8|%9|%10", - // _markName, - // getPos _firer, - // "mil_triangle", - // "ICON", - // [1, 1], - // 0, - // "Solid", - // "ColorRed", - // 1, - // _markTextLocal - // ]; - - // _markStr call BIS_fnc_stringToMarkerLocal; - // diag_log text format["detected grenade, created marker %1", _markStr]; + case (_ammoSimType isNotEqualTo "shotSubmunitions"): { - // _markStr = str _mark; - // _mark = createMarkerLocal [format["Projectile%1", _int],_projectile]; - // _mark setMarkerColorLocal "ColorRed"; - // _mark setMarkerTypeLocal "selector_selectable"; - // _mark setMarkerShapeLocal "ICON"; - // _mark setMarkerTextLocal format["%1 - %2", _firer, _markTextLocal]; + // MAKE MARKER FOR PLAYBACK + _firerPos = getPosASL _firer; + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - _firerPos = getPosASL _firer; - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - if (_ammoSimType isEqualTo "shotSubmunitions") then { - private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; - if (count _subTypes > 1) then { - waitUntil {isNull _projectile}; - }; - while {isNull _projectile} do { - { - _projSearch = nearestObject [_firer, _x]; - if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - } forEach _subTypes; - }; - } else { if (isNull _projectile) then { _projectile = nearestObject [_firer, _ammo]; }; - }; - private _lastPos = []; - private _lastDir = 0; - waitUntil { - _pos = getPosASL _projectile; - _dir = getDirVisual _projectile; - if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { - true + switch (true) do { + case (_ammoSimType isEqualTo "shotBullet"): { + GVAR(liveBullets) pushBack [_projectile, _firerId, _frame, getPosASL _projectile]; + }; + case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { + GVAR(liveMissiles) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName]; + }; + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { + GVAR(liveGrenades) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName, _ammoSimType]; + }; + default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _newAmmoSimType))}; }; - _lastPos = _pos; - _lastDir = _dir; - // params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal"]; - [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", _dir, "", "", 1]] call CBA_fnc_localEvent; - - // here, monitor fast moving missiles/rockets/shells every 0.3 seconds. monitor smokes, grenades, flares, mines in line with the configured frame capture delay - sleep ([0.3, EGVAR(settings,frameCaptureDelay)] select {_ammoSimType in ["ShotSmokeX","ShotGrenade","ShotIlluminating","ShotMine"]}); - false; }; - if !((count _lastPos) isEqualTo 0) then { - // if (count _lastPos == 3) then { - // params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", "_forceGlobal"]; - [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _lastPos, "", "", "", _lastDir, "", "", 1]] call CBA_fnc_localEvent; + + case (_ammoSimType isEqualTo "shotSubmunitions"): { + + // for submunitions, we first look at the original ammo and find the classnames of submunition ammo its rounds will turn into + // these are done in a staggered array in format ["classname1", probability of spawn, "classname2", probability of spawn] + // this is usually for vehicles with guns that fire mixed ammo, or for cluster munitions + // we'll wait for the simStep to have elapsed, then start checking for the resulting submunition nearby + // once we have that, we'll add it to the bullet tracking array for positions so a fireline is drawn in playback + private _simDelay = (configFile >> "CfgAmmo" >> _ammo >> "simulationStep") call BIS_fnc_getCfgData; + private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; + [{ + params ["_EHData", "_subTypes", "_magazine", "_firer", "_firerId", "_firerPos", "_frame"]; + private _projectile = objNull; + while {isNull _projectile} do { + { + _projSearch = nearestObject [_firer, _x]; + if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + } forEach _subTypes; + }; + + // get marker details based on original EH data + (_EHData call FUNC(getAmmoData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + private _newAmmoSimType = getText(configFile >> "CfgAmmo" >> _projectile >> "simulation"); + switch (true) do { + case (_newAmmoSimType isEqualTo "shotBullet"): { + GVAR(liveBullets) pushBack [_projectile, _firerId, _frame, getPosASL _projectile]; + }; + case (_newAmmoSimType in ["shotMissile", "shotRocket", "shotShell"]): { + GVAR(liveMissiles) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName]; + }; + case (_newAmmoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { + GVAR(liveGrenades) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName, _newAmmoSimType]; + }; + default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _newAmmoSimType))}; + }; + }, [_this, _subTypes, _magazine, _firer, _firerId, _firerPos, _frame], _simDelay] call CBA_fnc_waitAndExecute; }; - sleep 10; - // deleteMarkerLocal _markName; - // }; - [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent; }; diff --git a/x/ocap2/addons/recorder/fnc_eh_hit.sqf b/x/ocap2/addons/recorder/fnc_eh_hit.sqf index 981d0fd..8ba40b4 100644 --- a/x/ocap2/addons/recorder/fnc_eh_hit.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_hit.sqf @@ -25,6 +25,8 @@ Author: ---------------------------------------------------------------------------- */ #include "script_component.hpp" +if (!SHOULDSAVEEVENTS) exitWith {}; + params ["_unit", "_causedBy", "_damage", "_instigator"]; [_unit, _causedBy, _instigator] spawn { @@ -34,9 +36,11 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; _instigator = [_unit, _causedBy] call FUNC(getInstigator); }; + private _hitFrame = GVAR(captureFrameNo); + _unitID = _unit getVariable [QGVARMAIN(id), -1]; if (_unitID == -1) exitWith {}; - private _eventData = [GVAR(captureFrameNo), "hit", _unitID, ["null"], -1]; + private _eventData = [_hitFrame, "hit", _unitID, ["null"], -1]; if (!isNull _instigator) then { _causedById = _causedBy getVariable [QGVARMAIN(id), -1]; @@ -50,6 +54,10 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; ([_causedBy] call FUNC(getEventWeaponText)) ]; _distanceInfo = round (_unit distance _causedBy); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _causedById)); + }; } else { if (!isNull _instigator && _causedBy != _instigator && _instigator isKindOf "CAManBase" && _instigatorId > -1) then { _causedByInfo = [ @@ -57,13 +65,17 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; ([_instigator] call FUNC(getEventWeaponText)) ]; _distanceInfo = round (_unit distance _instigator); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _instigatorId)); + }; } else { _causedByInfo = [_causedById]; _distanceInfo = round (_unit distance _causedBy); }; }; _eventData = [ - GVAR(captureFrameNo), + _hitFrame, "hit", _unitID, _causedByInfo, @@ -71,6 +83,5 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; ]; }; - OCAPEXTLOG(_eventData); [":EVENT:", _eventData] call EFUNC(extension,sendData); }; diff --git a/x/ocap2/addons/recorder/fnc_eh_killed.sqf b/x/ocap2/addons/recorder/fnc_eh_killed.sqf index 8868ee5..42aff77 100644 --- a/x/ocap2/addons/recorder/fnc_eh_killed.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_killed.sqf @@ -25,7 +25,10 @@ Author: ---------------------------------------------------------------------------- */ #include "script_component.hpp" +if (!SHOULDSAVEEVENTS) exitWith {}; + params ["_victim", "_killer", "_instigator"]; + if !(_victim getvariable [QGVARMAIN(isKilled),false]) then { _victim setvariable [QGVARMAIN(isKilled),true]; @@ -34,7 +37,7 @@ if !(_victim getvariable [QGVARMAIN(isKilled),false]) then { private _killedFrame = GVAR(captureFrameNo); - if (_killer == _victim) then { + if (_killer == _victim && owner _victim != 2 && EGVAR(settings,preferACEUnconscious) && isClass(configFile >> "CfgPatches" >> "ace_medical_status")) then { private _time = diag_tickTime; [_victim, { _this setVariable ["ace_medical_lastDamageSource", (_this getVariable "ace_medical_lastDamageSource"), 2]; @@ -75,12 +78,14 @@ if !(_victim getvariable [QGVARMAIN(isKilled),false]) then { _killerInfo, round(_instigator distance _victim) ]; - }; - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(_eventData); + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("KILLED EVENT", _killedFrame, _victimId, _killerId)); + }; }; + + [":EVENT:", _eventData] call EFUNC(extension,sendData); }; }; diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf index 80a3cf1..2987ed1 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -65,11 +65,11 @@ if (isNil QGVAR(startTime)) exitWith { _elapsedTime = time - GVAR(startTime); -_frameTimeDuration = EGVAR(settings,frameCaptureDelay) * GVAR(captureFrameNo); +_frameTimeDuration = GVAR(frameCaptureDelay) * GVAR(captureFrameNo); TRACE_7("Save attempted. Elapsed Time =", _elapsedTime," Frame Count * Delay Duration =", _frameTimeDuration," delta =", _elapsedTime - _frameTimeDuration); -if (_frameTimeDuration < EGVAR(settings,minMissionTime) && !_overrideLimits) exitWith { +if (_frameTimeDuration < GVAR(minMissionTime) && !_overrideLimits) exitWith { // if the total duration in minutes is not met based on how many frames have been recorded & the frame capture delay, // then we won't save, but will continue recording in case admins want to save once that threshold is met. // allow this restriction to be overriden @@ -92,32 +92,31 @@ if (_frameTimeDuration < EGVAR(settings,minMissionTime) && !_overrideLimits) exi }; GVAR(recording) = false; +publicVariable QGVAR(recording); GVAR(endFrameNumber) = GVAR(captureFrameNo); publicVariable QGVAR(endFrameNumber); -switch (count _this) do { - case 0: { - [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", "Mission ended"]]] call EFUNC(extension,sendData); - }; - case 1: { - [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", _side]]] call EFUNC(extension,sendData); - }; - default { - private _sideString = str(_side); - if (_side == sideUnknown) then { _sideString = "" }; - [":EVENT:", [GVAR(endFrameNumber), "endMission", [_sideString, _message]]] call EFUNC(extension,sendData); - }; +if (isNil "_side") then { + [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", "Mission ended"]]] call EFUNC(extension,sendData); +}; +if (!isNil "_side" && isNil "_message") then { + [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", _side]]] call EFUNC(extension,sendData); +}; +if (!isNil "_side" && !isNil "_message") then { + private _sideString = str(_side); + if (_side == sideUnknown) then { _sideString = "" }; + [":EVENT:", [GVAR(endFrameNumber), "endMission", [_sideString, _message]]] call EFUNC(extension,sendData); }; if (!isNil "_tag") then { - [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay), GVAR(endFrameNumber), _tag]] call EFUNC(extension,sendData); - LOG(ARR4("Saved recording of mission", GVAR(missionName), "with tag", _tag)); -} else { - [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay), GVAR(endFrameNumber)]] call EFUNC(extension,sendData); - LOG(ARR3("Saved recording of mission", GVAR(missionName), "with default tag")); + [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay), GVAR(endFrameNumber), _tag]] call EFUNC(extension,sendData); + OCAPEXTLOG(ARR4("Saved recording of mission", GVAR(missionName), "with tag", _tag)); +} else {// default tag to configured setting + [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay), GVAR(endFrameNumber), EGVAR(settings,saveTag)]] call EFUNC(extension,sendData); + OCAPEXTLOG(ARR3("Saved recording of mission", GVAR(missionName), "with default tag")); }; // briefingName is used here, no need for publicVariable for a simple confirmation log. diff --git a/x/ocap2/addons/recorder/fnc_getAmmoData.sqf b/x/ocap2/addons/recorder/fnc_getAmmoData.sqf new file mode 100644 index 0000000..e22ff7b --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getAmmoData.sqf @@ -0,0 +1,62 @@ +params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; + +_int = random 2000; +_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName"); +if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayNameShort")}; +if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName")}; +_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayNameShort"); +if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayName")}; +if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; +if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; + +// non-bullet handling +private ["_markTextLocal"]; +if (!isNull _vehicle) then { + _markTextLocal = format["[%3] %1 - %2", _muzzleDisp, _magDisp, ([configOf _vehicle] call BIS_fnc_displayName)]; +} else { + if (_ammoSimType isEqualTo "shotGrenade") then { + _markTextLocal = format["%1", _magDisp]; + } else { + _markTextLocal = format["%1 - %2", _muzzleDisp, _magDisp]; + }; +}; + +_markName = format["Projectile#%1", _int]; +_markColor = "ColorRed"; +_markerType = ""; +_magPic = (getText(configfile >> "CfgMagazines" >> _magazine >> "picture")); +if (_magPic == "") then { + _markerType = "mil_triangle"; +} else { + _magPicSplit = _magPic splitString "\"; + _magPic = _magPicSplit # ((count _magPicSplit) -1); + _markerType = format["magIcons/%1", _magPic]; + _markColor = "ColorWhite"; +}; + +[_markTextLocal,_markName,_markColor,_markerType]; + + + // _markStr = format["|%1|%2|%3|%4|%5|%6|%7|%8|%9|%10", + // _markName, + // getPos _firer, + // "mil_triangle", + // "ICON", + // [1, 1], + // 0, + // "Solid", + // "ColorRed", + // 1, + // _markTextLocal + // ]; + + // _markStr call BIS_fnc_stringToMarkerLocal; + + // diag_log text format["detected grenade, created marker %1", _markStr]; + + // _markStr = str _mark; + // _mark = createMarkerLocal [format["Projectile%1", _int],_projectile]; + // _mark setMarkerColorLocal "ColorRed"; + // _mark setMarkerTypeLocal "selector_selectable"; + // _mark setMarkerShapeLocal "ICON"; + // _mark setMarkerTextLocal format["%1 - %2", _firer, _markTextLocal]; diff --git a/x/ocap2/addons/recorder/fnc_getDelay.sqf b/x/ocap2/addons/recorder/fnc_getDelay.sqf index 9b151c2..26e8143 100644 --- a/x/ocap2/addons/recorder/fnc_getDelay.sqf +++ b/x/ocap2/addons/recorder/fnc_getDelay.sqf @@ -28,10 +28,10 @@ Author: private "_sleep"; isNil { _elapsedTime = time - GVAR(startTime); - _sleep = (GVAR(captureFrameNo) + 1) * EGVAR(settings,frameCaptureDelay) - _elapsedTime; + _sleep = (GVAR(captureFrameNo) + 1) * GVAR(frameCaptureDelay) - _elapsedTime; if ((GVAR(captureFrameNo) % 10) isEqualTo 0) then { - LOG(ARR4("DEBUG: Frame", GVAR(captureFrameNo), "is created in ~", EGVAR(settings,frameCaptureDelay) - _sleep)); + LOG(ARR4("DEBUG: Frame", GVAR(captureFrameNo), "is created in ~", GVAR(frameCaptureDelay) - _sleep)); }; if (_sleep < 0) then { LOG(ARR3("ERROR: Frame delay is negative", GVAR(captureFrameNo), _sleep)); diff --git a/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf index 53380d9..04dbc45 100644 --- a/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf +++ b/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf @@ -28,7 +28,14 @@ Author: params ["_instigator"]; if (vehicle _instigator isEqualTo _instigator) exitWith { - _instigator getVariable [QGVARMAIN(lastFired), getText (configFile >> "CfgWeapons" >> currentWeapon _instigator >> "displayName")]; + _instigator getVariable [ + QGVARMAIN(lastFired), + format[ + "%1 [%2]", + getText (configFile >> "CfgWeapons" >> currentWeapon _instigator >> "displayName"), + getText (configFile >> "CfgWeapons" >> currentMuzzle _instigator >> "displayName") + ] + ] }; // pilot/driver doesn't return a value, so check for this diff --git a/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf b/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf index 7efc0b1..278231e 100644 --- a/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf +++ b/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf @@ -48,6 +48,8 @@ Author: ---------------------------------------------------------------------------- */ #include "script_component.hpp" +if (!SHOULDSAVEEVENTS) exitWith {}; + params ["_eventName", "_eventMessage"]; [":EVENT:", [GVAR(captureFrameNo), _eventName, _eventMessage] diff --git a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf index 2983214..012f185 100644 --- a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf +++ b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf @@ -63,6 +63,9 @@ GVAR(trackedMarkers) = []; // Markers which we saves into replay // create CBA event handler to be called on server with key "ocap2_handleMarker" EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { + + if (!SHOULDSAVEEVENTS) exitWith {}; + params["_eventType", "_mrk_name", "_mrk_owner", "_pos", "_type", "_shape", "_size", "_dir", "_brush", "_color", "_alpha", "_text", ["_forceGlobal", false], ["_creationTime", 0]]; switch (_eventType) do { @@ -70,7 +73,7 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { case "CREATED":{ if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR2("MARKER:CREATE: Processing marker data -- ", _this)); + OCAPEXTLOG(ARR2("MARKER:CREATE: Processing marker data -- ", _mrk_name)); }; if (_mrk_name in GVAR(trackedMarkers)) exitWith { @@ -80,7 +83,8 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { }; if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("MARKER:CREATE: Valid CREATED process of marker from", _mrk_owner, "for", _mrk_name)); + format["CREATE:MARKER: Valid CREATED process of %1, sending to extension", _mrk_name] SYSCHAT; + OCAPEXTLOG(ARR3("CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension")); }; if (_type isEqualTo "") then {_type = "mil_dot"}; @@ -128,9 +132,9 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { private _captureFrameNo = GVAR(captureFrameNo); if (_creationTime > 0) then { private _delta = time - _creationTime; - private _lastFrameTime = (GVAR(captureFrameNo) * EGVAR(settings,frameCaptureDelay)) + GVAR(startTime); + private _lastFrameTime = (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) + GVAR(startTime); if (_delta > (time - _lastFrameTime)) then { // marker was initially created in some frame(s) before - _captureFrameNo = ceil _lastFrameTime - (_delta / EGVAR(settings,frameCaptureDelay)); + _captureFrameNo = ceil _lastFrameTime - (_delta / GVAR(frameCaptureDelay)); private _logParams = (str [GVAR(captureFrameNo), time, _creationTime, _delta, _lastFrameTime, _captureFrameNo]); if (GVARMAIN(isDebug)) then { @@ -141,11 +145,6 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { private _logParams = (str [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]); - if (GVARMAIN(isDebug)) then { - str ["CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension -- ", _logParams select [0, 5]] remoteExec ["systemChat", [0, -2] select isDedicated]; - OCAPEXTLOG(ARR4("CREATE:MARKER: Valid CREATED process of", _mrk_name, ", sending to extension -- ", _logParams)); - }; - [":MARKER:CREATE:", [_mrk_name, _dir, _type, _text, _captureFrameNo, -1, _mrk_owner, _mrk_color, _size, _sideOfMarker, _pos, _shape, _alpha, _brush]] call EFUNC(extension,sendData); }; @@ -162,7 +161,7 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { if (_mrk_name in GVAR(trackedMarkers)) then { if (GVARMAIN(isDebug)) then { - str ["MARKER:DELETE: Marker", _mrk_name, "deleted"] remoteExec ["systemChat", [0, -2] select isDedicated]; + format["MARKER:DELETE: Marker %1", _mrk_name] SYSCHAT; OCAPEXTLOG(ARR3("MARKER:DELETE: Marker", _mrk_name, "deleted")); }; @@ -268,7 +267,7 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { // collect all initial markers & add event handlers to clients [ - {getClientState > 8 && !isNil QGVAR(startTime)}, + {getClientStateNumber > 8 && !isNil QGVAR(startTime)}, { { private _marker = _x; diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index 5e900f0..2771aae 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -25,12 +25,24 @@ Author: #include "script_component.hpp" +// exit if in 3DEN editor (when loaded in PreInit XEH +if (is3DEN) exitWith {}; +// if OCAP is disabled do nothing +if (!GVARMAIN(enabled)) exitWith {}; +// if recording has already initialized this session then just start recording, don't re-init +if (!isNil QGVAR(startTime)) exitWith {call FUNC(startRecording)}; + // bool: GVAR(recording) GVAR(recording) = false; +publicVariable QGVAR(recording); // int: GVAR(captureFrameNo) GVAR(captureFrameNo) = 0; -// bool: GVAR(shouldSave) -GVAR(shouldSave) = false; +publicVariable QGVAR(captureFrameNo); + +// save static setting values so changes during a mission don't interrupt timeline +GVAR(frameCaptureDelay) = EGVAR(settings,frameCaptureDelay); +GVAR(autoStart) = EGVAR(settings,autoStart); +GVAR(minMissionTime) = EGVAR(settings,minMissionTime); // macro: GVARMAIN(version)SION GVARMAIN(version) = QUOTE(VERSION_STR); @@ -84,15 +96,96 @@ if (GVAR(missionName) == "") then { GVAR(missionName) = briefingName; }; - - /* Conditional Start Recording We'll wait to see if auto-start is enabled and minPlayercount setting is met. This covers scenarios where someone changes the autostart setting during the mission as well, and excludes cases where autostart is disabled. - On execution, we'll also check if recording has already started by other means via whether GVAR(startTime) has been declared or not. If recording hasn't started already, we'll initialize it here assuming the above conditions are met. + The startRecording function checks internally if recording has already started by other means via whether GVAR(startTime) has been declared or not. */ [ - {((count allPlayers) >= EGVAR(settings,minPlayerCount) && EGVAR(settings,autoStart)) || !isNil QGVAR(startTime)}, - {call FUNC(startRecording)}, + {((count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, + {call FUNC(startRecording)} ] call CBA_fnc_waitUntilAndExecute; + +// When the server progresses past briefing and enters the mission, save an event to the timeline if recording +[{getClientStateNumber > 9}, { + if (!SHOULDSAVEEVENTS) exitWith {}; + [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; +}] call CBA_fnc_waitUntilAndExecute; + + + +// PFH to track bullets +[{ + { + if (isNull (_x#0)) then { + _x params ["_obj", "_firerId", "_frame", "_pos"]; + [":FIRED:", [ + _firerId, + _frame, + _pos + ]] call EFUNC(extension,sendData); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", _frame, _firerId, str _pos)); + }; + GVAR(liveBullets) = GVAR(liveBullets) - [_x]; + } else { + _x set [3, getPosASL (_x#0)]; + }; + } forEach GVAR(liveBullets); +}] call CBA_fnc_addPerFrameHandler; + +// PFH to track missiles, rockets, shells +[{ + { + _x params ["_obj", "_magazine", "_firer", "_pos", "_markName"]; + if (isNull (_x#0)) then { + + _firer setVariable [ + QGVARMAIN(lastFired), + getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") + ]; + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: SHELL-ROCKET-MISSILE", _frame, _firerId, str _pos)); + }; + + [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; + GVAR(liveMissiles) = GVAR(liveMissiles) - [_x]; + + } else { + _nowPos = getPosASL (_x#0); + _x set [3, _nowPos]; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; + }; + } forEach GVAR(liveMissiles); +}, GVAR(frameCaptureDelay) * 0.3] call CBA_fnc_addPerFrameHandler; + +// PFH to track grenades, flares, thrown charges +[{ + { + _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_ammoSimType"]; + if (isNull (_x#0)) then { + + if !(_ammoSimType in ["shotSmokeX", "shotIlluminating"]) then { + _firer setVariable [ + QGVARMAIN(lastFired), + getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") + ]; + }; + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: GRENADE-FLARE-SMOKE", _frame, _firerId, str _pos)); + }; + + [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; + GVAR(liveGrenades) = GVAR(liveGrenades) - [_x]; + + } else { + _nowPos = getPosASL (_x#0); + _x set [3, _nowPos]; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; + }; + } forEach GVAR(liveGrenades); +}, GVAR(frameCaptureDelay)] call CBA_fnc_addPerFrameHandler; diff --git a/x/ocap2/addons/recorder/fnc_startRecording.sqf b/x/ocap2/addons/recorder/fnc_startRecording.sqf index 0f3a9cc..2922dff 100644 --- a/x/ocap2/addons/recorder/fnc_startRecording.sqf +++ b/x/ocap2/addons/recorder/fnc_startRecording.sqf @@ -9,17 +9,17 @@ if (!GVARMAIN(enabled)) exitWith {}; // if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. -if (!isNil QGVAR(startTime)) then { - if (GVAR(recording)) exitWith { - OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); - }; - if (!GVAR(recording)) exitWith { - call FUNC(captureLoop); - }; +if (!isNil QGVAR(startTime) && GVAR(recording)) exitWith { + OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); +}; +if (!isNil QGVAR(startTime) && !GVAR(recording)) exitWith { + call FUNC(captureLoop); }; -[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], EGVAR(settings,frameCaptureDelay)]] call EFUNC(extension,sendData); +// Notify the extension +[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay)]] call EFUNC(extension,sendData); [":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); + // Add mission event handlers call FUNC(addEventMission); // Track initial times From 89659ec65a0057d5521532d5280691361d0d9ab9 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Tue, 29 Mar 2022 17:40:01 -0400 Subject: [PATCH 04/26] ready for playtesting? --- x/ocap2/addons/extension/config.cpp | 2 + x/ocap2/addons/main/config.cpp | 2 + x/ocap2/addons/recorder/XEH_preInit.sqf | 28 +-- x/ocap2/addons/recorder/XEH_prep.sqf | 4 +- x/ocap2/addons/recorder/config.cpp | 2 + x/ocap2/addons/recorder/fnc_aceExplosives.sqf | 13 +- x/ocap2/addons/recorder/fnc_aceThrowing.sqf | 180 +++++++----------- .../addons/recorder/fnc_addEventMission.sqf | 132 ++++++++----- .../recorder/fnc_addUnitEventHandlers.sqf | 33 ++-- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 161 +++++++++++----- x/ocap2/addons/recorder/fnc_eh_hit.sqf | 23 +-- x/ocap2/addons/recorder/fnc_eh_killed.sqf | 12 +- x/ocap2/addons/recorder/fnc_exportData.sqf | 23 ++- ...AmmoData.sqf => fnc_getAmmoMarkerData.sqf} | 12 +- .../recorder/fnc_getEventWeaponText.sqf | 59 ++---- x/ocap2/addons/recorder/fnc_getInstigator.sqf | 12 +- .../recorder/fnc_getWeaponDisplayData.sqf | 11 ++ x/ocap2/addons/recorder/fnc_handleMarkers.sqf | 23 +-- x/ocap2/addons/recorder/fnc_init.sqf | 121 +++++------- .../recorder/fnc_projectileMonitors.sqf | 170 +++++++++++++++++ 20 files changed, 601 insertions(+), 422 deletions(-) rename x/ocap2/addons/recorder/{fnc_getAmmoData.sqf => fnc_getAmmoMarkerData.sqf} (65%) create mode 100644 x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf create mode 100644 x/ocap2/addons/recorder/fnc_projectileMonitors.sqf diff --git a/x/ocap2/addons/extension/config.cpp b/x/ocap2/addons/extension/config.cpp index 9edd0f1..b9abb22 100644 --- a/x/ocap2/addons/extension/config.cpp +++ b/x/ocap2/addons/extension/config.cpp @@ -17,6 +17,8 @@ class CfgPatches }; }; +cba_settings_whitelist[] = {"admin"}; + class Extended_PreInit_EventHandlers { class ADDON { // This will be executed once in 3DEN, main menu and before briefing has started for every mission diff --git a/x/ocap2/addons/main/config.cpp b/x/ocap2/addons/main/config.cpp index f0ff80a..8316bd7 100644 --- a/x/ocap2/addons/main/config.cpp +++ b/x/ocap2/addons/main/config.cpp @@ -17,6 +17,8 @@ class CfgPatches }; }; +cba_settings_whitelist[] = {"admin"}; + class Extended_PreInit_EventHandlers { class ADDON { // This will be executed once in 3DEN, main menu and before briefing has started for every mission diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap2/addons/recorder/XEH_preInit.sqf index d73a536..0d7ffd2 100644 --- a/x/ocap2/addons/recorder/XEH_preInit.sqf +++ b/x/ocap2/addons/recorder/XEH_preInit.sqf @@ -115,20 +115,6 @@ GVAR(allSettings) = [ false // requires restart to apply ], - [ - QEGVAR(settings,pauseOnEmpty), - "CHECKBOX", // setting type - [ - "Auto-Save When No Players", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minumum save duration setting. Default: true" - ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - true, // default enabled - true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer - {}, // function that will be executed once on mission start and every time the setting is changed. - false // requires restart to apply - ], - [ QEGVAR(settings,trackTimes), "CHECKBOX", // setting type @@ -194,6 +180,20 @@ GVAR(allSettings) = [ false // requires restart to apply ], + [ + QEGVAR(settings,saveOnEmpty), + "CHECKBOX", // setting type + [ + "Auto-Save When No Players", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting. Default: true" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply + ], + [ QEGVAR(settings,minMissionTime), "SLIDER", // setting type diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap2/addons/recorder/XEH_prep.sqf index a7afbb9..7232104 100644 --- a/x/ocap2/addons/recorder/XEH_prep.sqf +++ b/x/ocap2/addons/recorder/XEH_prep.sqf @@ -23,8 +23,10 @@ PREP(eh_hit); PREP(eh_killed); PREP(getInstigator); PREP(getEventWeaponText); -PREP(getAmmoData); +PREP(getAmmoMarkerData); +PREP(getWeaponDisplayData); +PREP(projectileMonitors); PREP(aceThrowing); PREP(aceExplosives); diff --git a/x/ocap2/addons/recorder/config.cpp b/x/ocap2/addons/recorder/config.cpp index b43487e..455ebd1 100644 --- a/x/ocap2/addons/recorder/config.cpp +++ b/x/ocap2/addons/recorder/config.cpp @@ -17,6 +17,8 @@ class CfgPatches }; }; +cba_settings_whitelist[] = {"admin"}; + class Extended_PreInit_EventHandlers { class ADDON { // This will be executed once in 3DEN, main menu and before briefing has started for every mission diff --git a/x/ocap2/addons/recorder/fnc_aceExplosives.sqf b/x/ocap2/addons/recorder/fnc_aceExplosives.sqf index cdb1af0..1990e4b 100644 --- a/x/ocap2/addons/recorder/fnc_aceExplosives.sqf +++ b/x/ocap2/addons/recorder/fnc_aceExplosives.sqf @@ -41,6 +41,7 @@ EGVAR(listener,aceExplosives) = ["ace_explosives_place", { _explType = typeOf _explosive; _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); + _explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture"); _placedPos = getPosASL _explosive; _unit addOwnedMine _explosive; @@ -50,15 +51,17 @@ EGVAR(listener,aceExplosives) = ["ace_explosives_place", { _markColor = "ColorRed"; _markerType = "Minefield"; - if (GVARMAIN(isDebug)) then { - format["Created explosive placed marker, %1, %2", _markName, _explosiveDisp] SYSCHAT; - OCAPEXTLOG(ARR3("Created explosive placed marker", _markName, _explosiveDisp)); - }; - [QGVARMAIN(handleMarker), [ "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true ]] call CBA_fnc_localEvent; + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_explosive, _explosivePic, format["%1 %2 - %3", str side group _unit, name _unit, _markTextLocal], [side group _unit] call BIS_fnc_sideColor]; + GVAR(liveDebugMagIcons) pushBack _debugArr; + publicVariable QGVAR(liveDebugMagIcons); + }; + [{isNull (_this#0)}, { // wait until the mine is null (exploded), and mark this for playback diff --git a/x/ocap2/addons/recorder/fnc_aceThrowing.sqf b/x/ocap2/addons/recorder/fnc_aceThrowing.sqf index 4fddecd..bd321d2 100644 --- a/x/ocap2/addons/recorder/fnc_aceThrowing.sqf +++ b/x/ocap2/addons/recorder/fnc_aceThrowing.sqf @@ -27,135 +27,103 @@ EGVAR(listener,aceThrowing) = ["ace_throwableThrown", { if (!SHOULDSAVEEVENTS) exitWith {}; - _this spawn { + params["_unit", "_projectile"]; - params["_unit", "_projectile"]; + if (isNull _projectile) then { + _projectile = nearestObject [_unit, "CA_Magazine"]; + }; - if (isNull _projectile) then { - _projectile = nearestObject [_unit, "CA_Magazine"]; - }; + // systemChat str _this; - // systemChat str _this; + // note that thrown objects outside of ACE explosives do not include a "default magazine" property in their config. + // this script will attempt to find a matching classname in CfgMagazines, as some chemlights and smokes are built this way. + // if not found, a default magazine value will be assigned (m67 frag, white smoke, green chemlight) - // note that thrown objects outside of ACE explosives do not include a "default magazine" property in their config. - // this script will attempt to find a matching classname in CfgMagazines, as some chemlights and smokes are built this way. - // if not found, a default magazine value will be assigned (m67 frag, white smoke, green chemlight) + _projType = typeOf _projectile; + _projConfig = configOf _projectile; + _projName = getText(configFile >> "CfgAmmo" >> _projType >> "displayName"); - _projType = typeOf _projectile; - _projConfig = configOf _projectile; - _projName = getText(configFile >> "CfgAmmo" >> _projType >> "displayName"); + // systemChat format["Config name: %1", configOf _projectile]; - if (GVARMAIN(isDebug)) then { - format["Detected ACE throwing of %1", _projName] SYSCHAT; - OCAPEXTLOG(ARR2("Detected ACE throwing of ", _projName)); - }; + _ammoSimType = getText(configFile >> "CfgAmmo" >> _projType >> "simulation"); + // systemChat format["Projectile type: %1", _ammoSimType]; - // systemChat format["Config name: %1", configOf _projectile]; + _markerType = ""; + _markColor = ""; + _magDisp = ""; + _magPic = ""; - _ammoSimType = getText(configFile >> "CfgAmmo" >> _projType >> "simulation"); - // systemChat format["Projectile type: %1", _ammoSimType]; + _magType = getText(_projConfig >> "defaultMagazine"); + if (_magType == "") then { + _magType = configName(configfile >> "CfgMagazines" >> _projType) + }; - _markerType = ""; - _markColor = ""; - _magDisp = ""; - _magPic = ""; + if (!(_magType isEqualTo "")) then { + // systemChat format["Mag type: %1", _magType]; - _magType = getText(_projConfig >> "defaultMagazine"); - if (_magType == "") then { - _magType = configName(configfile >> "CfgMagazines" >> _projType) + _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayNameShort"); + if (_magDisp == "") then { + _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayName") + }; + if (_magDisp == "") then { + _magDisp = _projName; }; - if (!(_magType isEqualTo "")) then { - // systemChat format["Mag type: %1", _magType]; - - _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayNameShort"); - if (_magDisp == "") then { - _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayName") - }; - if (_magDisp == "") then { - _magDisp = _projName; - }; - - _magPic = (getText(configfile >> "CfgMagazines" >> _magType >> "picture")); - // hint parseText format["Projectile fired:
", _magPic]; - if (_magPic == "") then { - _markerType = "mil_triangle"; - _markColor = "ColorRed"; - } else { - _magPicSplit = _magPic splitString "\"; - _magPic = _magPicSplit#((count _magPicSplit) - 1); - _markerType = format["magIcons/%1", _magPic]; - _markColor = "ColorWhite"; - }; - } else { + _magPic = (getText(configfile >> "CfgMagazines" >> _magType >> "picture")); + // hint parseText format["Projectile fired:
", _magPic]; + if (_magPic == "") then { _markerType = "mil_triangle"; _markColor = "ColorRed"; - // set defaults based on ammo sim type, if no magazine could be matched - switch (_ammoSimType) do { - case "shotGrenade":{ - _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; - _magDisp = "Frag"; - }; - case "shotSmokeX":{ - _magPic = "\A3\Weapons_f\data\ui\gear_smokegrenade_white_ca.paa"; - _magDisp = "Smoke"; - }; - case "shotIlluminating":{ - _magPic = "\A3\Weapons_F\Data\UI\gear_flare_white_ca.paa"; - _magDisp = "Flare"; - }; - default { - _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; - _magDisp = "Frag"; - }; - }; - // hint parseText format["Projectile fired:
", _magPic]; + } else { _magPicSplit = _magPic splitString "\"; _magPic = _magPicSplit#((count _magPicSplit) - 1); _markerType = format["magIcons/%1", _magPic]; _markColor = "ColorWhite"; }; + } else { + _markerType = "mil_triangle"; + _markColor = "ColorRed"; + // set defaults based on ammo sim type, if no magazine could be matched + switch (_ammoSimType) do { + case "shotGrenade":{ + _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; + _magDisp = "Frag"; + }; + case "shotSmokeX":{ + _magPic = "\A3\Weapons_f\data\ui\gear_smokegrenade_white_ca.paa"; + _magDisp = "Smoke"; + }; + case "shotIlluminating":{ + _magPic = "\A3\Weapons_F\Data\UI\gear_flare_white_ca.paa"; + _magDisp = "Flare"; + }; + default { + _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa"; + _magDisp = "Frag"; + }; + }; + // hint parseText format["Projectile fired:
", _magPic]; + _magPicSplit = _magPic splitString "\"; + _magPic = _magPicSplit#((count _magPicSplit) - 1); + _markerType = format["magIcons/%1", _magPic]; + _markColor = "ColorWhite"; + }; - if (!(_ammoSimType isEqualTo "shotBullet")) then { - - _int = random 2000; - - _markTextLocal = format["%1", _magDisp]; - _markName = format["Projectile#%1", _int]; - - _throwerPos = getPosASL _unit; - - [QGVARMAIN(handleMarker), ["CREATED", _markName, _unit, _throwerPos, _markerType, "ICON", [1,1], 0, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_serverEvent; + _int = random 2000; - private _lastPos = []; - waitUntil { - _pos = getPosASL _projectile; - if (((_pos select 0) isEqualTo 0) || isNull _projectile) exitWith { - true - }; - _lastPos = _pos; - [QGVARMAIN(handleMarker), ["UPDATED", _markName, _unit, _pos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; + _markTextLocal = format["%1", _magDisp]; + _markName = format["Projectile#%1", _int]; - // here, monitor fast moving missiles/rockets/shells every 0.3 seconds. monitor smokes, grenades, flares, mines in line with the configured frame capture delay - sleep (([0.3, GVAR(frameCaptureDelay)] select {_ammoSimType in ["ShotSmokeX","ShotGrenade","ShotIlluminating","ShotMine"]})#0); - false; - }; + // MAKE MARKER FOR PLAYBACK + _throwerPos = getPosASL _unit; + [QGVARMAIN(handleMarker), ["CREATED", _markName, _unit, _throwerPos, _markerType, "ICON", [1,1], 0, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_serverEvent; - if !((count _lastPos) isEqualTo 0) then { - isNil { - // for non-bullets, set the last fired variable of the soldier so hits/kills are recorded accurately - if (_ammoSimType == "shotGrenade") then { - _unit setVariable [ - QGVARMAIN(lastFired), - _projName - ]; - }; - }; - [QGVARMAIN(handleMarker), ["UPDATED", _markName, _unit, _lastPos, "", "", "", 0, "", "", 1]] call CBA_fnc_serverEvent; - }; + GVAR(liveGrenades) pushBack [_projectile, _magazine, _unit, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]; - sleep 10; - [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_serverEvent; - }; + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magPic, format["%1 %2 - %3", str side group _unit, name _unit, _markTextLocal], [side group _unit] call BIS_fnc_sideColor]; + GVAR(liveDebugMagIcons) pushBack _debugArr; + publicVariable QGVAR(liveDebugMagIcons); }; }] call CBA_fnc_addEventHandler; diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf index f837861..66349d3 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -25,80 +25,118 @@ Author: ---------------------------------------------------------------------------- */ #include "script_component.hpp" -addMissionEventHandler["HandleDisconnect", { - _this call FUNC(eh_disconnected); -}]; +if (isNil QEGVAR(EH,HandleDisconnect)) then { + addMissionEventHandler["HandleDisconnect", { + _this call FUNC(eh_disconnected); + }]; + OCAPEXTLOG(["Initialized HandleDisconnect EH"]); +}; -addMissionEventHandler["PlayerConnected", { - _this call FUNC(eh_connected); -}]; +if (isNil QEGVAR(EH,PlayerConnected)) then { + addMissionEventHandler["PlayerConnected", { + _this call FUNC(eh_connected); + }]; + OCAPEXTLOG(["Initialized PlayerConnected EH"]); +}; -addMissionEventHandler ["EntityKilled", { - _this call FUNC(eh_killed); -}]; +if (isNil QEGVAR(EH,EntityKilled)) then { + addMissionEventHandler ["EntityKilled", { + _this call FUNC(eh_killed); + }]; + OCAPEXTLOG(["Initialized EntityKilled EH"]); +}; -addMissionEventHandler ["EntityRespawned", { - params ["_entity", "_corpse"]; +if (isNil QEGVAR(EH,EntityRespawned)) then { + EGVAR(EH,EntityRespawned) = addMissionEventHandler ["EntityRespawned", { + params ["_entity", "_corpse"]; - // Reset unit back to normal - _entity setvariable [QGVARMAIN(isKilled), false]; + // Reset unit back to normal + _entity setvariable [QGVARMAIN(isKilled), false]; - // Stop tracking old unit - if (_corpse getVariable [QGVARMAIN(isInitialized), false]) then { - _corpse setVariable [QGVARMAIN(exclude), true]; + // Stop tracking old unit + if (_corpse getVariable [QGVARMAIN(isInitialized), false]) then { + _corpse setVariable [QGVARMAIN(exclude), true]; - [_entity, true] spawn FUNC(addUnitEventHandlers); - }; -}]; + [_entity, true] spawn FUNC(addUnitEventHandlers); + }; + }]; + OCAPEXTLOG(["Initialized EntityRespawned EH"]); +}; // Listen for global ACE Explosive placement events if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then { - call FUNC(aceExplosives); + if (isNil QEGVAR(listener,aceExplosives)) then { + call FUNC(aceExplosives); + OCAPEXTLOG(["Initialized ACE Explosives listener"]); + }; }; -// Listen for local ACE Throwing events, for any units owned by the server +// Listen for global ACE Throwing events that take place when a throwable is primed. use existing firedMan code, since identical args if (isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing")) then { - call FUNC(aceThrowing); + if (isNil QEGVAR(listener,aceThrowing)) then { + ["ace_advanced_throwing_throwFiredXEH", {_this call FUNC(eh_firedMan)}] call CBA_fnc_addEventHandler; + OCAPEXTLOG(["Initialized ACE Throwing listener"]); + }; }; -addMissionEventHandler ["MPEnded", { - if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then { - ["Mission ended automatically"] call FUNC(exportData); - }; -}]; +if (isNil QEGVAR(EH,MPEnded)) then { + EGVAR(EH,MPEnded) = addMissionEventHandler ["MPEnded", { + if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then { + ["Mission ended automatically"] call FUNC(exportData); + }; + }]; + OCAPEXTLOG(["Initialized MPEnded EH"]); +}; -addMissionEventHandler ["Ended", { - if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then { - ["Mission ended automatically"] call FUNC(exportData); - }; -}]; +if (isNil QEGVAR(EH,Ended)) then { + EGVAR(EH,Ended) = addMissionEventHandler ["Ended", { + if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then { + ["Mission ended automatically"] call FUNC(exportData); + }; + }]; + OCAPEXTLOG(["Initialized Ended EH"]); +}; // Add event saving markers -call FUNC(handleMarkers); +if (isNil QEGVAR(listener,markers)) then { + call FUNC(handleMarkers); +}; // Custom event handler with key "ocap2_customEvent" // Used for showing custom events in playback events list -EGVAR(listener,customEvent) = [QGVARMAIN(customEvent), { - _this call FUNC(handleCustomEvent); -}] call CBA_fnc_addEventHandler; +if (isNil QEGVAR(listener,customEvent)) then { + EGVAR(listener,customEvent) = [QGVARMAIN(customEvent), { + _this call FUNC(handleCustomEvent); + }] call CBA_fnc_addEventHandler; + OCAPEXTLOG(["Initialized customEvent listener"]); +}; // Custom event handler with key "ocap2_record" // This will START OR RESUME recording if not already. -EGVAR(listener,exportData) = [QGVARMAIN(record), { - call FUNC(startRecording); -}] call CBA_fnc_addEventHandler; +if (isNil QEGVAR(listener,record)) then { + EGVAR(listener,record) = [QGVARMAIN(record), { + call FUNC(startRecording); + }] call CBA_fnc_addEventHandler; + OCAPEXTLOG(["Initialized record listener"]); +}; // Custom event handler with key "ocap2_pause" // This will PAUSE recording -EGVAR(listener,exportData) = [QGVARMAIN(pause), { - GVAR(recording) = false; - publicVariable QGVAR(recording); -}] call CBA_fnc_addEventHandler; +if (isNil QEGVAR(listener,pause)) then { + EGVAR(listener,pause) = [QGVARMAIN(pause), { + GVAR(recording) = false; + publicVariable QGVAR(recording); + }] call CBA_fnc_addEventHandler; + OCAPEXTLOG(["Initialized pause listener"]); +}; // Custom event handler with key "ocap2_exportData" // This will export the mission immediately regardless of restrictions. // params ["_side", "_message", "_tag"]; -EGVAR(listener,exportData) = [QGVARMAIN(exportData), { - _this set [3, true]; - _this call FUNC(exportData); -}] call CBA_fnc_addEventHandler; +if (isNil QEGVAR(listener,exportData)) then { + EGVAR(listener,exportData) = [QGVARMAIN(exportData), { + _this set [3, true]; + _this call FUNC(exportData); + }] call CBA_fnc_addEventHandler; + OCAPEXTLOG(["Initialized exportData listener"]); +}; diff --git a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf index bd28b62..aafb014 100644 --- a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf +++ b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf @@ -31,29 +31,18 @@ params ["_entity", ["_respawn", false]]; // FIREDMAN if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then { - _entity addEventHandler ["FiredMan", { _this call FUNC(eh_firedMan); }]; + if (isNil {_entity getVariable QGVARMAIN(FiredManEH)}) then { + _entity setVariable [ + QGVARMAIN(FiredManEH), + _entity addEventHandler ["FiredMan", { _this call FUNC(eh_firedMan); }] + ]; + }; }; // MPHIT -_entity addMPEventHandler ["MPHit", { _this spawn FUNC(eh_hit); }]; - -private _ownerId = owner _entity; -if ( - !_respawn && // exclude re-adding on respawn, as the CBA listener will already be present on the owner's machine - (_entity call BIS_fnc_objectType) # 0 == "Soldier" && - _ownerId != 2 && // only add to entities not owned by server, as otherwise server will receive local events already - isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing") -) then { - // here, we must place a local listener of ACE throw events on the owner of the entity - // the client will then notify the server when these local events happen - // we'll wait a max of 15 secs until that owner is at least to briefing stage, has completed PostInit, before sending - [ - {(getUserInfo (_this#0)) select 6 > 8}, - { - FUNC(aceThrowing) remoteExec ["call", _this#1]; - OCAPEXTLOG(ARR3("ADD ACE THROWING LISTENER", _this#0, _this#1)); - }, - [_ownerId, _entity], - 15 - ] call CBA_fnc_waitUntilAndExecute; +if (isNil {_entity getVariable QGVARMAIN(MPHitEH)}) then { + _entity setVariable [ + QGVARMAIN(MPHitEH), + _entity addMPEventHandler ["MPHit", { _this call FUNC(eh_hit); }] + ]; }; diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 690efa8..0450ec2 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -33,22 +33,27 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; +// not sent in ACE Throwing events +if (isNil "_vehicle") then {_vehicle = objNull}; + private _frame = GVAR(captureFrameNo); private _firerId = (_firer getVariable [QGVARMAIN(id), -1]); if (_firerId == -1) exitWith {}; // set the firer's lastFired var as this weapon, so subsequent kills are logged accurately -isNil { - _firer setVariable [ - QGVARMAIN(lastFired), - format[ - "%1 [%2]", - getText (configFile >> "CfgWeapons" >> _weapon >> "displayName"), - getText (configFile >> "CfgWeapons" >> _muzzle >> "displayName") - ] - ]; +([_weapon, _muzzle, _magazine, _ammo] call FUNC(getWeaponDisplayData)) params ["_muzzleDisp", "_magDisp"]; + +private _wepString = ""; +if (_muzzleDisp find _magDisp == 1 && _magDisp isNotEqualTo "") then { + _wepString = format["%1 [%2]", _muzzleDisp, _magDisp]; +} else { + _wepString = _muzzleDisp; +}; +if (vehicle _firer != _firer) then { + _wepString = format["%1 [%2]", (configOf (vehicle _firer)) call BIS_fnc_displayName, _wepString]; }; +_firer setVariable [QGVARMAIN(lastFired), _wepString]; _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); // _ammoSimType @@ -60,39 +65,56 @@ _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); // "ShotIlluminating" // 40mm_green Flare // "ShotSmokeX"; // M18 Smoke +#define LOGBULLET GVAR(liveBullets) pushBack [_projectile, _firerId, _firer, getPosASL _projectile] +#define LOGMISSILE GVAR(liveMissiles) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal] +#define LOGGRENADE GVAR(liveGrenades) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]; -switch (_ammoSimType) do { +switch (true) do { case (_ammoSimType isEqualTo "shotBullet"): { // [_projectile, _firer, _frame, _ammoSimType, _ammo] spawn { // params["_projectile", "_firer", "_frame", "_ammoSimType", "_ammo"]; if (isNull _projectile) then { _projectile = nearestObject [_firer, _ammo]; }; - GVAR(liveBullets) pushBack [_projectile, _firerId, _frame, getPosASL _projectile]; + if (isNil "_projectile") exitWith {}; + LOGBULLET; }; case (_ammoSimType isNotEqualTo "shotSubmunitions"): { - // MAKE MARKER FOR PLAYBACK - _firerPos = getPosASL _firer; - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - if (isNull _projectile) then { _projectile = nearestObject [_firer, _ammo]; + _this set [6, _projectile]; }; + ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; + private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); + + // MAKE MARKER FOR PLAYBACK + _firerPos = getPosASL _firer; + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + switch (true) do { - case (_ammoSimType isEqualTo "shotBullet"): { - GVAR(liveBullets) pushBack [_projectile, _firerId, _frame, getPosASL _projectile]; - }; case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - GVAR(liveMissiles) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName]; + LOGMISSILE; + + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; }; case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { - GVAR(liveGrenades) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName, _ammoSimType]; + LOGGRENADE; + + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; }; - default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _newAmmoSimType))}; + default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; }; }; @@ -106,34 +128,77 @@ switch (_ammoSimType) do { // once we have that, we'll add it to the bullet tracking array for positions so a fireline is drawn in playback private _simDelay = (configFile >> "CfgAmmo" >> _ammo >> "simulationStep") call BIS_fnc_getCfgData; private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; - [{ - params ["_EHData", "_subTypes", "_magazine", "_firer", "_firerId", "_firerPos", "_frame"]; - private _projectile = objNull; - while {isNull _projectile} do { - { - _projSearch = nearestObject [_firer, _x]; - if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - } forEach _subTypes; - }; - - // get marker details based on original EH data - (_EHData call FUNC(getAmmoData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - private _newAmmoSimType = getText(configFile >> "CfgAmmo" >> _projectile >> "simulation"); - switch (true) do { - case (_newAmmoSimType isEqualTo "shotBullet"): { - GVAR(liveBullets) pushBack [_projectile, _firerId, _frame, getPosASL _projectile]; - }; - case (_newAmmoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - GVAR(liveMissiles) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName]; + private _subTypesAmmoSimType = _subTypes apply {(configFile >> "CfgAmmo" >> _x >> "simulation") call BIS_fnc_getCfgData}; + if (count _subTypesAmmoSimType > 0) then { + _subTypesAmmoSimType = selectRandom(_subTypesAmmoSimType); + }; + [ + { + params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_subTypesAmmoSimType"]; + private _projectile = _EHData # 6; + + // if submunitions are NOT bullets, wait until the original projectile deploys then search for submunitions and 're-fire' them to appear in playback + // if !(_subTypesAmmoSimType == "shotBullet") exitWith { + // if (_magazine isKindOf "VehicleMagazine") then { + // [_EHData, _projectile, _subTypes] spawn { + // params ["_EHData", "_projectile", "_subTypes"]; + // private _ogPos = getPos _firer; + // while {!isNull _projectile} do {_ogPos = getPosASL _projectile; sleep 0.1;}; + // isNil { + // _projSearch = nearestObjects [ASLtoAGL _ogPos, _subTypes, 50, false]; + // { + // _EHData set [6, _x]; + // _EHData spawn FUNC(eh_firedMan); + // } forEach _projSearch; + // }; + // }; + // }; + + // if submunitions ARE bullets, process normally and just look for one item + while {isNull _projectile} do { + { + _projSearch = nearestObject [_firer, _x]; + if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + } forEach _subTypes; + sleep 0.1 }; - case (_newAmmoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { - GVAR(liveGrenades) pushBack [_projectile, _magazine, _firer, getPosASL _projectile, _markName, _newAmmoSimType]; + + // get marker details based on original EH data + ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; + private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); + + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + // then get data of submunition to determine how to track it + private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); + switch (true) do { + case (_ammoSimType isEqualTo "shotBullet"): { + LOGBULLET; + }; + case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { + LOGMISSILE; + + if (GVARMAIN(isDebug)) then { + // add to clients' map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; + }; + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { + LOGGRENADE; + + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; + }; + default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; }; - default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _newAmmoSimType))}; - }; - }, [_this, _subTypes, _magazine, _firer, _firerId, _firerPos, _frame], _simDelay] call CBA_fnc_waitAndExecute; + }, + [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _subTypesAmmoSimType], + _simDelay + ] call CBA_fnc_waitAndExecute; }; }; diff --git a/x/ocap2/addons/recorder/fnc_eh_hit.sqf b/x/ocap2/addons/recorder/fnc_eh_hit.sqf index 8ba40b4..3640db8 100644 --- a/x/ocap2/addons/recorder/fnc_eh_hit.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_hit.sqf @@ -48,7 +48,7 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; private _causedByInfo = []; private _distanceInfo = 0; - if (_causedBy isKindOf "CAManBase" && _causedById > -1) then { + if (_causedById > -1 && _causedBy isEqualTo _instigator) then { _causedByInfo = [ _causedById, ([_causedBy] call FUNC(getEventWeaponText)) @@ -59,19 +59,14 @@ params ["_unit", "_causedBy", "_damage", "_instigator"]; OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _causedById)); }; } else { - if (!isNull _instigator && _causedBy != _instigator && _instigator isKindOf "CAManBase" && _instigatorId > -1) then { - _causedByInfo = [ - _instigatorId, - ([_instigator] call FUNC(getEventWeaponText)) - ]; - _distanceInfo = round (_unit distance _instigator); - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _instigatorId)); - }; - } else { - _causedByInfo = [_causedById]; - _distanceInfo = round (_unit distance _causedBy); + _causedByInfo = [ + _instigatorId, + ([_instigator] call FUNC(getEventWeaponText)) + ]; + _distanceInfo = round (_unit distance _instigator); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _instigatorId)); }; }; _eventData = [ diff --git a/x/ocap2/addons/recorder/fnc_eh_killed.sqf b/x/ocap2/addons/recorder/fnc_eh_killed.sqf index 42aff77..755df62 100644 --- a/x/ocap2/addons/recorder/fnc_eh_killed.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_killed.sqf @@ -37,6 +37,10 @@ if !(_victim getvariable [QGVARMAIN(isKilled),false]) then { private _killedFrame = GVAR(captureFrameNo); + // allow some time for last-fired variable on killer to be updated + // namely for explosives, shells, grenades explosions, which are updated on impact + sleep GVAR(frameCaptureDelay); + if (_killer == _victim && owner _victim != 2 && EGVAR(settings,preferACEUnconscious) && isClass(configFile >> "CfgPatches" >> "ace_medical_status")) then { private _time = diag_tickTime; [_victim, { @@ -62,14 +66,14 @@ if !(_victim getvariable [QGVARMAIN(isKilled),false]) then { if (_killerId == -1) exitWith {}; private _killerInfo = []; - if (_instigator isKindOf "CAManBase") then { + // if (_instigator isKindOf "CAManBase") then { _killerInfo = [ _killerId, ([_instigator] call FUNC(getEventWeaponText)) ]; - } else { - _killerInfo = [_killerId]; - }; + // } else { + // _killerInfo = [_killerId]; + // }; _eventData = [ _killedFrame, diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf index 2987ed1..a383f4b 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -65,7 +65,7 @@ if (isNil QGVAR(startTime)) exitWith { _elapsedTime = time - GVAR(startTime); -_frameTimeDuration = GVAR(frameCaptureDelay) * GVAR(captureFrameNo); +_frameTimeDuration = (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) * 60; TRACE_7("Save attempted. Elapsed Time =", _elapsedTime," Frame Count * Delay Duration =", _frameTimeDuration," delta =", _elapsedTime - _frameTimeDuration); @@ -93,29 +93,36 @@ if (_frameTimeDuration < GVAR(minMissionTime) && !_overrideLimits) exitWith { GVAR(recording) = false; publicVariable QGVAR(recording); -GVAR(endFrameNumber) = GVAR(captureFrameNo); +private _endFrameNumber = GVAR(captureFrameNo); -publicVariable QGVAR(endFrameNumber); +// reset vars in case a new recording is started +GVAR(captureFrameNo) = nil; +GVAR(startTime) = nil; + +// TO DO HEREEEEEE if (isNil "_side") then { - [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", "Mission ended"]]] call EFUNC(extension,sendData); + [":EVENT:", [_endFrameNumber, "endMission", ["", "Mission ended"]]] call EFUNC(extension,sendData); +}; +if (isNil "_side" && !isNil "_message") then { + [":EVENT:", [_endFrameNumber, "endMission", ["", _message]]] call EFUNC(extension,sendData); }; if (!isNil "_side" && isNil "_message") then { - [":EVENT:", [GVAR(endFrameNumber), "endMission", ["", _side]]] call EFUNC(extension,sendData); + [":EVENT:", [_endFrameNumber, "endMission", ["", _side]]] call EFUNC(extension,sendData); }; if (!isNil "_side" && !isNil "_message") then { private _sideString = str(_side); if (_side == sideUnknown) then { _sideString = "" }; - [":EVENT:", [GVAR(endFrameNumber), "endMission", [_sideString, _message]]] call EFUNC(extension,sendData); + [":EVENT:", [_endFrameNumber, "endMission", [_sideString, _message]]] call EFUNC(extension,sendData); }; if (!isNil "_tag") then { - [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay), GVAR(endFrameNumber), _tag]] call EFUNC(extension,sendData); + [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay), _endFrameNumber, _tag]] call EFUNC(extension,sendData); OCAPEXTLOG(ARR4("Saved recording of mission", GVAR(missionName), "with tag", _tag)); } else {// default tag to configured setting - [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay), GVAR(endFrameNumber), EGVAR(settings,saveTag)]] call EFUNC(extension,sendData); + [":SAVE:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay), _endFrameNumber, EGVAR(settings,saveTag)]] call EFUNC(extension,sendData); OCAPEXTLOG(ARR3("Saved recording of mission", GVAR(missionName), "with default tag")); }; diff --git a/x/ocap2/addons/recorder/fnc_getAmmoData.sqf b/x/ocap2/addons/recorder/fnc_getAmmoMarkerData.sqf similarity index 65% rename from x/ocap2/addons/recorder/fnc_getAmmoData.sqf rename to x/ocap2/addons/recorder/fnc_getAmmoMarkerData.sqf index e22ff7b..5cadad4 100644 --- a/x/ocap2/addons/recorder/fnc_getAmmoData.sqf +++ b/x/ocap2/addons/recorder/fnc_getAmmoMarkerData.sqf @@ -1,13 +1,9 @@ -params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; +#include "script_component.hpp" + +params ["_weapon", "_muzzle", "_ammo", "_magazine", "_projectile", "_vehicle", "_ammoSimType"]; _int = random 2000; -_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName"); -if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayNameShort")}; -if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName")}; -_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayNameShort"); -if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayName")}; -if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; -if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; +([_weapon, _muzzle, _magazine, _ammo] call FUNC(getWeaponDisplayData)) params ["_muzzleDisp", "_magDisp"]; // non-bullet handling private ["_markTextLocal"]; diff --git a/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf index 04dbc45..1f9678e 100644 --- a/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf +++ b/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf @@ -27,48 +27,29 @@ Author: params ["_instigator"]; -if (vehicle _instigator isEqualTo _instigator) exitWith { +if (isNull _instigator) exitWith {""}; + +if !(_instigator call CBA_fnc_isPerson) then { + _instigator = _instigator call { + if(alive(gunner _this))exitWith{gunner _this}; + if(alive(commander _this))exitWith{commander _this}; + if(alive(driver _this))exitWith{driver _this}; + effectiveCommander _this + }; +}; + +if (_instigator call CBA_fnc_isPerson) then { + (weaponstate _instigator) params ["_weapon", "_muzzle", "_mode", "_magazine"]; + ([_weapon, _muzzle, _magazine] call FUNC(getWeaponDisplayData)) params ["_muzDisp", "_magDisp"]; + _instigator getVariable [ QGVARMAIN(lastFired), - format[ + format [ "%1 [%2]", - getText (configFile >> "CfgWeapons" >> currentWeapon _instigator >> "displayName"), - getText (configFile >> "CfgWeapons" >> currentMuzzle _instigator >> "displayName") + _muzDisp, + _magDisp ] - ] -}; - -// pilot/driver doesn't return a value, so check for this -private _turPath = []; -if (count (assignedVehicleRole _instigator) > 1) then { - _turPath = assignedVehicleRole _instigator select 1; + ]; } else { - _turPath = [-1]; + getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); }; - -private _curVic = getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); -(weaponstate [vehicle _instigator, _turPath]) params ["_curWep", "_curMuzzle", "_curFiremode", "_curMag"]; -private _curWepDisplayName = getText(configFile >> "CfgWeapons" >> _curWep >> "displayName"); -private _curMagDisplayName = getText(configFile >> "CfgMagazines" >> _curMag >> "displayName"); -private _text = _curVic; -if (count _curMagDisplayName < 22) then { - if !(_curWepDisplayName isEqualTo "") then { - _text = _text + " [" + _curWepDisplayName; - if !(_curMagDisplayName isEqualTo "") then { - _text = _text + " / " + _curMagDisplayName + "]"; - } else { - _text = _text + "]" - }; - }; -} else { - if !(_curWepDisplayName isEqualTo "") then { - _text = _text + " [" + _curWepDisplayName; - if (_curWep != _curMuzzle && !(_curMuzzle isEqualTo "")) then { - _text = _text + " / " + _curMuzzle + "]"; - } else { - _text = _text + "]"; - }; - }; -}; - -_text; diff --git a/x/ocap2/addons/recorder/fnc_getInstigator.sqf b/x/ocap2/addons/recorder/fnc_getInstigator.sqf index 77d3376..2d66c7e 100644 --- a/x/ocap2/addons/recorder/fnc_getInstigator.sqf +++ b/x/ocap2/addons/recorder/fnc_getInstigator.sqf @@ -25,7 +25,7 @@ Author: ---------------------------------------------------------------------------- */ #include "script_component.hpp" -params ["_victim", ["_killer", objNull], ["_instigator", objNull]]; +params [["_victim", objNull], ["_killer", objNull], ["_instigator", objNull]]; if (isNull _instigator) then { _instigator = UAVControl vehicle _killer select 0; @@ -34,11 +34,11 @@ if ((isNull _instigator) || (_instigator == _victim)) then { _instigator = _killer; }; if (_instigator isKindOf "AllVehicles") then { - _instigator = call { - if(alive(gunner _instigator))exitWith{gunner _instigator}; - if(alive(commander _instigator))exitWith{commander _instigator}; - if(alive(driver _instigator))exitWith{driver _instigator}; - effectiveCommander _instigator + _instigator = _instigator call { + if(alive(gunner _this))exitWith{gunner _this}; + if(alive(commander _this))exitWith{commander _this}; + if(alive(driver _this))exitWith{driver _this}; + effectiveCommander _this }; }; if (isNull _instigator) then { diff --git a/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf b/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf new file mode 100644 index 0000000..cddafc4 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf @@ -0,0 +1,11 @@ +params ["_weapon", "_muzzle", "_magazine", "_ammo"]; + +_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName"); +if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayNameShort")}; +if (_muzzleDisp == "") then {_muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName")}; +_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayNameShort"); +if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _magazine >> "displayName")}; +if (_magDisp == "" && !isNil "_ammo") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; +if (_magDisp == "" && !isNil "_ammo") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; + +[_muzzleDisp, _magDisp]; diff --git a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf index 012f185..1d6c933 100644 --- a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf +++ b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf @@ -38,28 +38,7 @@ Author: // Entries are added at marker create events and removed at marker delete events to avoid duplicate processing. GVAR(trackedMarkers) = []; // Markers which we saves into replay -// On the dedicated server, the color of the markers is blue -// This overrides it with client data so it's saved properly -{ - _x params ["_name", "_color"]; - profilenamespace setVariable [_name, _color]; -} forEach [ - ["map_blufor_r", 0], - ["map_blufor_g", 0.3], - ["map_blufor_b", 0.6], - ["map_independent_r", 0], - ["map_independent_g", 0.5], - ["map_independent_b", 0], - ["map_civilian_r", 0.4], - ["map_civilian_g", 0], - ["map_civilian_b", 0.5], - ["map_unknown_r", 0.7], - ["map_unknown_g", 0.6], - ["map_unknown_b", 0], - ["map_opfor_r", 0.5], - ["map_opfor_g", 0], - ["map_opfor_b", 0] -]; + // create CBA event handler to be called on server with key "ocap2_handleMarker" EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index 2771aae..c5f0252 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -43,6 +43,7 @@ publicVariable QGVAR(captureFrameNo); GVAR(frameCaptureDelay) = EGVAR(settings,frameCaptureDelay); GVAR(autoStart) = EGVAR(settings,autoStart); GVAR(minMissionTime) = EGVAR(settings,minMissionTime); +GVAR(projectileMonitorMultiplier) = 1; // macro: GVARMAIN(version)SION GVARMAIN(version) = QUOTE(VERSION_STR); @@ -51,7 +52,6 @@ publicVariable QGVARMAIN(version); EGVAR(extension,version) = ([":VERSION:", []] call EFUNC(extension,sendData)); publicVariable QEGVAR(extension,version); - // remoteExec diary creation commands to clients listing version numbers and waiting start state { [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { @@ -87,7 +87,7 @@ publicVariable QEGVAR(extension,version); ] ]; }] call CBA_fnc_waitUntilAndExecute; -} remoteExecCall ["call", 0, true]; +} remoteExecCall ["call", [0, -2] select isDedicated, true]; // Support both methods of setting mission name. @@ -96,6 +96,31 @@ if (GVAR(missionName) == "") then { GVAR(missionName) = briefingName; }; + +// On the dedicated server, the color of the markers is blue +// This overrides it with client data so it's saved properly +{ + _x params ["_name", "_color"]; + profilenamespace setVariable [_name, _color]; +} forEach [ + ["map_blufor_r", 0], + ["map_blufor_g", 0.3], + ["map_blufor_b", 0.6], + ["map_independent_r", 0], + ["map_independent_g", 0.5], + ["map_independent_b", 0], + ["map_civilian_r", 0.4], + ["map_civilian_g", 0], + ["map_civilian_b", 0.5], + ["map_unknown_r", 0.7], + ["map_unknown_g", 0.6], + ["map_unknown_b", 0], + ["map_opfor_r", 0.5], + ["map_opfor_g", 0], + ["map_opfor_b", 0] +]; + + /* Conditional Start Recording We'll wait to see if auto-start is enabled and minPlayercount setting is met. This covers scenarios where someone changes the autostart setting during the mission as well, and excludes cases where autostart is disabled. @@ -110,82 +135,22 @@ if (GVAR(missionName) == "") then { // When the server progresses past briefing and enters the mission, save an event to the timeline if recording [{getClientStateNumber > 9}, { if (!SHOULDSAVEEVENTS) exitWith {}; - [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; + if (time < 20) then { + [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; + } else { + _timeArr = [time, "HH:MM:SS", true] call BIS_fnc_secondsToString; + [QGVARMAIN(customEvent), ["generalEvent", format["Recording began %1H %2M %3S into the mission", _timeArr#0, _timeArr#1, _timeArr#2]]] call CBA_fnc_serverEvent; + } }] call CBA_fnc_waitUntilAndExecute; - - -// PFH to track bullets -[{ - { - if (isNull (_x#0)) then { - _x params ["_obj", "_firerId", "_frame", "_pos"]; - [":FIRED:", [ - _firerId, - _frame, - _pos - ]] call EFUNC(extension,sendData); - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", _frame, _firerId, str _pos)); - }; - GVAR(liveBullets) = GVAR(liveBullets) - [_x]; - } else { - _x set [3, getPosASL (_x#0)]; - }; - } forEach GVAR(liveBullets); -}] call CBA_fnc_addPerFrameHandler; - -// PFH to track missiles, rockets, shells -[{ - { - _x params ["_obj", "_magazine", "_firer", "_pos", "_markName"]; - if (isNull (_x#0)) then { - - _firer setVariable [ - QGVARMAIN(lastFired), - getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") - ]; - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("FIRED EVENT: SHELL-ROCKET-MISSILE", _frame, _firerId, str _pos)); - }; - - [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; - GVAR(liveMissiles) = GVAR(liveMissiles) - [_x]; - - } else { - _nowPos = getPosASL (_x#0); - _x set [3, _nowPos]; - [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; - }; - } forEach GVAR(liveMissiles); -}, GVAR(frameCaptureDelay) * 0.3] call CBA_fnc_addPerFrameHandler; - -// PFH to track grenades, flares, thrown charges +// Auto-save on empty - checked every 30 seconds +// If a recording has been started, exceeds min mission time, and no players are on the server, auto-save [{ - { - _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_ammoSimType"]; - if (isNull (_x#0)) then { - - if !(_ammoSimType in ["shotSmokeX", "shotIlluminating"]) then { - _firer setVariable [ - QGVARMAIN(lastFired), - getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") - ]; - }; - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("FIRED EVENT: GRENADE-FLARE-SMOKE", _frame, _firerId, str _pos)); - }; - - [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; - GVAR(liveGrenades) = GVAR(liveGrenades) - [_x]; - - } else { - _nowPos = getPosASL (_x#0); - _x set [3, _nowPos]; - [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; - }; - } forEach GVAR(liveGrenades); -}, GVAR(frameCaptureDelay)] call CBA_fnc_addPerFrameHandler; + if (!isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count allPlayers == 0) then { + [nil, "Mission ended due to server being empty"] call FUNC(exportData); + }; +}, 30] call CBA_fnc_addPerFrameHandler; + +if (isNil QGVAR(projectileMonitorsInitialized)) then { + call FUNC(projectileMonitors); +}; diff --git a/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf new file mode 100644 index 0000000..ed1d422 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf @@ -0,0 +1,170 @@ +#include "script_component.hpp" + +// PFH to track bullets +GVAR(liveBullets) = []; +[{ + + private _processNow = GVAR(liveBullets) select {isNull (_x#0)}; + GVAR(liveBullets) = GVAR(liveBullets) select {!isNull (_x#0)}; + + // _processNow + // for bullets that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw + { + _x params ["_obj", "_firerId", "_firer", "_pos"]; + + [":FIRED:", [ + _firerId, + GVAR(captureFrameNo), + _pos + ]] call EFUNC(extension,sendData); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _pos)); + + // add to clients' map draw array + private _debugArr = [getPos _firer, _pos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime]; + [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent; + }; + } forEach _processNow; + + // for bullets that still exist, update positions + { + _x set [3, getPosASL (_x#0)]; + } forEach GVAR(liveBullets); +}, 0.1 * GVAR(projectileMonitorMultiplier)] call CBA_fnc_addPerFrameHandler; + +// PFH to track missiles, rockets, shells +GVAR(liveMissiles) = []; +[{ + private _processNow = GVAR(liveMissiles) select {isNull (_x#0)}; + GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x#0)}; + + // _processNow + // for missiles that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw + { + _x params ["_obj", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal"]; + _firer setVariable [ + QGVARMAIN(lastFired), + getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") + ]; + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: SHELL-ROCKET-MISSILE", GVAR(captureFrameNo), _firer getVariable QGVARMAIN(id), str _pos)); + }; + + [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; + } forEach _processNow; + + // for missiles that still exist, update positions + { + _x params ["_obj", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal"]; + _nowPos = getPosASL (_x#0); + _x set [3, _nowPos]; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; + } forEach GVAR(liveMissiles); +}, 0.1 * GVAR(projectileMonitorMultiplier)] call CBA_fnc_addPerFrameHandler; + +// PFH to track grenades, flares, thrown charges +GVAR(liveGrenades) = []; +[{ + private _processNow = GVAR(liveMissiles) select {isNull (_x#0)}; + GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x#0)}; + + // _processNow + // for grenades that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw + { + _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_markTextLocal", "_ammoSimType"]; + + if !(_ammoSimType in ["shotSmokeX", "shotIlluminating"]) then { + _firer setVariable [ + QGVARMAIN(lastFired), + getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") + ]; + }; + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: GRENADE-FLARE-SMOKE", GVAR(captureFrameNo), _firer getVariable QGVARMAIN(id), str _pos)); + }; + + [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; + } forEach _processNow; + + // for grenades that still exist, update positions + { + _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_markTextLocal", "_ammoSimType"]; + _nowPos = getPosASL (_x#0); + _x set [3, _nowPos]; + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; + } forEach GVAR(liveGrenades); +}, GVAR(frameCaptureDelay)] call CBA_fnc_addPerFrameHandler; + + + +// DEBUG draws on clients + +// drawLine for each bullet on map if debug active, to verify rounds are captured +// these are only added once the projectile has hit something +{ + if (!hasInterface) exitWith {}; + [] spawn { + waitUntil {!isNull (findDisplay 12)}; + GVAR(liveDebugBullets) = []; + disableSerialization; + (findDisplay 12 displayCtrl 51) ctrlAddEventHandler ["Draw", { + if (GVARMAIN(isDebug)) then { + // remove bullets from display that have landed > 7 seconds ago + GVAR(liveDebugBullets) = GVAR(liveDebugBullets) select {cba_missionTime < (_x#3) + 7}; + { + // _x params ["_startPos", "_endPos", "_color", "_timeHit"]; + (_this#0) drawLine [ + _x#0, + _x#1, + _x#2 + ]; + } forEach GVAR(liveDebugBullets); + }; + }]; + + [QGVAR(addDebugBullet), { + GVAR(liveDebugBullets) pushBack _this; + }] call CBA_fnc_addEventHandler; + }; +} remoteExec ["call", [0, -2] select isDedicated, true]; + +// drawIcon for magazines/non-bullet projectiles +// these are added when fired and tracked in array +{ + if (!hasInterface) exitWith {}; + [] spawn { + waitUntil {!isNull (findDisplay 12)}; + GVAR(liveDebugMagIcons) = []; + disableSerialization; + (findDisplay 12 displayCtrl 51) ctrlAddEventHandler ["Draw", { + if (GVARMAIN(isDebug)) then { + GVAR(liveDebugMagIcons) = GVAR(liveDebugMagIcons) select {!isNull (_x#0)}; + { + // _x params ["_obj", "_magIcon", "_text", "_sideColor"]; + (_this#0) drawIcon [ + _x#1, // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" + _x#3, + getPos (_x#0), + 25, + 25, + getDir (_x#0), + _x#2, + 0, + 0.03, + "PuristaLight", + "center" + ]; + } forEach GVAR(liveDebugMagIcons); + }; + }]; + + [QGVAR(addDebugMagIcon), { + GVAR(liveDebugMagIcons) pushBack _this; + }] call CBA_fnc_addEventHandler; + }; +} remoteExec ["call", [0, -2] select isDedicated, true]; + +GVAR(projectileMonitorsInitialized) = true; From 0665d5ce52f54503252873e26f9328f3da777457 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Wed, 30 Mar 2022 17:01:21 -0400 Subject: [PATCH 05/26] changes cap loop to persist, prevent duplicate changers of record stat --- x/ocap2/addons/recorder/XEH_prep.sqf | 1 + .../addons/recorder/fnc_addEventMission.sqf | 3 +- x/ocap2/addons/recorder/fnc_captureLoop.sqf | 46 ++---------------- x/ocap2/addons/recorder/fnc_exportData.sqf | 20 ++++++-- x/ocap2/addons/recorder/fnc_init.sqf | 11 ++--- .../addons/recorder/fnc_startRecording.sqf | 45 ++++++++++++----- x/ocap2/addons/recorder/fnc_stopRecording.sqf | 30 ++++++++++++ x/ocap2/addons/recorder/fnc_updateTime.sqf | 14 +++--- x/ocap2/licence.txt | 17 +++++++ x/ocap2/logo_ocap.paa | Bin 0 -> 24169 bytes x/ocap2/mod.cpp | 22 +++++++++ 11 files changed, 135 insertions(+), 74 deletions(-) create mode 100644 x/ocap2/addons/recorder/fnc_stopRecording.sqf create mode 100644 x/ocap2/licence.txt create mode 100644 x/ocap2/logo_ocap.paa create mode 100644 x/ocap2/mod.cpp diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap2/addons/recorder/XEH_prep.sqf index 7232104..35fc9fc 100644 --- a/x/ocap2/addons/recorder/XEH_prep.sqf +++ b/x/ocap2/addons/recorder/XEH_prep.sqf @@ -5,6 +5,7 @@ PREP(init); PREP(updateTime); PREP(startRecording); +PREP(stopRecording); PREP(captureLoop); PREP(isKindOfApc); PREP(getClass); diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf index 66349d3..ebb390c 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -124,8 +124,7 @@ if (isNil QEGVAR(listener,record)) then { // This will PAUSE recording if (isNil QEGVAR(listener,pause)) then { EGVAR(listener,pause) = [QGVARMAIN(pause), { - GVAR(recording) = false; - publicVariable QGVAR(recording); + call FUNC(stopRecording); }] call CBA_fnc_addEventHandler; OCAPEXTLOG(["Initialized pause listener"]); }; diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf index a827f20..5010f06 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -172,46 +172,8 @@ GVAR(PFHObject) = [ }, GVAR(frameCaptureDelay), // delay [], // args - { - GVAR(recording) = true; - publicVariable QGVAR(recording); - - { // add diary entry for clients on recording start - [{!isNull player}, { - player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - "OCAP2 began recording." - ], taskNull, "", false - ]; - player setDiarySubjectPicture [ - "OCAP2Info", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" - ]; - }] call CBA_fnc_waitUntilAndExecute; - } remoteExecCall ["call", 0, true]; - }, // code, executed when added - { - GVAR(recording) = false; - publicVariable QGVAR(recording); - - { // add diary entry for clients on recording start - [{!isNull player}, { - player createDiaryRecord [ - "OCAP2Info", - [ - "Status", - "OCAP2 stopped recording." - ], taskNull, "", false - ]; - player setDiarySubjectPicture [ - "OCAP2Info", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" - ]; - }] call CBA_fnc_waitUntilAndExecute; - } remoteExecCall ["call", 0, true]; - }, // code, executed when removed - {GVAR(recording)}, // if true, execute PFH cycle - {!GVAR(recording) || !GVARMAIN(enabled)} // if true, delete object + {}, // code, executed when added + {}, // code, executed when removed + {SHOULDSAVEEVENTS}, // if true, execute PFH cycle + {false} // if true, delete object ] call CBA_fnc_createPerFrameHandlerObject; diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf index a383f4b..f9c445b 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -91,15 +91,25 @@ if (_frameTimeDuration < GVAR(minMissionTime) && !_overrideLimits) exitWith { } remoteExec ["call", 0, false]; }; -GVAR(recording) = false; -publicVariable QGVAR(recording); + +call FUNC(stopRecording); private _endFrameNumber = GVAR(captureFrameNo); +if (!isNil QGVAR(PFHObject)) then { + [GVAR(PFHObject)] call CBA_fnc_deletePerFrameHandlerObject; + GVAR(PFHObject) = nil; +}; + // reset vars in case a new recording is started -GVAR(captureFrameNo) = nil; +GVAR(captureFrameNo) = 0; GVAR(startTime) = nil; - -// TO DO HEREEEEEE +{ + _x setVariable [QGVARMAIN(isInitialized), nil]; + _x setVariable [QGVARMAIN(exclude), nil]; + _x setVariable [QGVARMAIN(id), nil]; + _x setVariable [QGVARMAIN(unitType), nil]; +} count (allUnits + allDeadMen + vehicles); +GVAR(nextId) = 0; if (isNil "_side") then { diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index c5f0252..3b4c1b5 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -38,6 +38,7 @@ publicVariable QGVAR(recording); // int: GVAR(captureFrameNo) GVAR(captureFrameNo) = 0; publicVariable QGVAR(captureFrameNo); +GVAR(nextId) = 0; // save static setting values so changes during a mission don't interrupt timeline GVAR(frameCaptureDelay) = EGVAR(settings,frameCaptureDelay); @@ -52,6 +53,9 @@ publicVariable QGVARMAIN(version); EGVAR(extension,version) = ([":VERSION:", []] call EFUNC(extension,sendData)); publicVariable QEGVAR(extension,version); +// Add mission event handlers +call FUNC(addEventMission); + // remoteExec diary creation commands to clients listing version numbers and waiting start state { [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { @@ -135,12 +139,7 @@ if (GVAR(missionName) == "") then { // When the server progresses past briefing and enters the mission, save an event to the timeline if recording [{getClientStateNumber > 9}, { if (!SHOULDSAVEEVENTS) exitWith {}; - if (time < 20) then { - [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; - } else { - _timeArr = [time, "HH:MM:SS", true] call BIS_fnc_secondsToString; - [QGVARMAIN(customEvent), ["generalEvent", format["Recording began %1H %2M %3S into the mission", _timeArr#0, _timeArr#1, _timeArr#2]]] call CBA_fnc_serverEvent; - } + [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; }] call CBA_fnc_waitUntilAndExecute; // Auto-save on empty - checked every 30 seconds diff --git a/x/ocap2/addons/recorder/fnc_startRecording.sqf b/x/ocap2/addons/recorder/fnc_startRecording.sqf index 2922dff..2aca51d 100644 --- a/x/ocap2/addons/recorder/fnc_startRecording.sqf +++ b/x/ocap2/addons/recorder/fnc_startRecording.sqf @@ -9,21 +9,42 @@ if (!GVARMAIN(enabled)) exitWith {}; // if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. -if (!isNil QGVAR(startTime) && GVAR(recording)) exitWith { +if (GVAR(recording)) exitWith { OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); }; -if (!isNil QGVAR(startTime) && !GVAR(recording)) exitWith { + +GVAR(recording) = true; +publicVariable QGVAR(recording); + +private _systemTimeFormat = ["%1-%2-%3T%4:%5:%6.%7"]; +_systemTimeFormat append (systemTimeUTC apply {if (_x < 10) then {"0" + str _x} else {str _x}}); +private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; +_missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); + +[QGVARMAIN(customEvent), ["generalEvent", "Recording started."]] call CBA_fnc_serverEvent; + +[[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording start + [{!isNull player}, { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + format["OCAP2 started recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] + ], taskNull, "", false + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ]; + }, _this] call CBA_fnc_waitUntilAndExecute; +}] remoteExecCall ["call", 0, true]; + +if (GVAR(captureFrameNo) == 0) then { + // Notify the extension + [":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay)]] call EFUNC(extension,sendData); + [":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); call FUNC(captureLoop); }; -// Notify the extension -[":START:", [worldName, GVAR(missionName), getMissionConfigValue ["author", ""], GVAR(frameCaptureDelay)]] call EFUNC(extension,sendData); -[":SET:VERSION:", [GVARMAIN(version)]] call EFUNC(extension,sendData); - -// Add mission event handlers -call FUNC(addEventMission); -// Track initial times +// Log times [] call FUNC(updateTime); - -GVAR(nextId) = 0; -call FUNC(captureLoop); diff --git a/x/ocap2/addons/recorder/fnc_stopRecording.sqf b/x/ocap2/addons/recorder/fnc_stopRecording.sqf new file mode 100644 index 0000000..8ee38c8 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_stopRecording.sqf @@ -0,0 +1,30 @@ +#include "script_component.hpp" + +private _systemTimeFormat = ["%1-%2-%3T%4:%5:%6.%7"]; +_systemTimeFormat append (systemTimeUTC apply {if (_x < 10) then {"0" + str _x} else {str _x}}); +private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; +_missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); + +[QGVARMAIN(customEvent), ["generalEvent", "Recording paused."]] call CBA_fnc_serverEvent; + +GVAR(recording) = false; +publicVariable QGVAR(recording); + +[[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording pause + [{!isNull player}, { + player createDiaryRecord [ + "OCAP2Info", + [ + "Status", + format["OCAP2 stopped recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] + ], taskNull, "", false + ]; + player setDiarySubjectPicture [ + "OCAP2Info", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ]; + }, _this] call CBA_fnc_waitUntilAndExecute; +}] remoteExecCall ["call", 0, true]; + +// Log times +[] call FUNC(updateTime); diff --git a/x/ocap2/addons/recorder/fnc_updateTime.sqf b/x/ocap2/addons/recorder/fnc_updateTime.sqf index 62b5113..a8bba74 100644 --- a/x/ocap2/addons/recorder/fnc_updateTime.sqf +++ b/x/ocap2/addons/recorder/fnc_updateTime.sqf @@ -25,21 +25,21 @@ Author: #include "script_component.hpp" params [ - ["_date", []] + ["_date", []] ]; private _systemTimeFormat = ["%1-%2-%3T%4:%5:%6.%7"]; _systemTimeFormat append (systemTimeUTC apply {if (_x < 10) then {"0" + str _x} else {str _x}}); private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; if (_date isEqualTo []) then { - _date = date; + _date = date; }; _missionDateFormat append (_date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); [":TIME:", [ - GVAR(captureFrameNo), - format _systemTimeFormat, - format _missionDateFormat, - timeMultiplier, - time + GVAR(captureFrameNo), + format _systemTimeFormat, + format _missionDateFormat, + timeMultiplier, + time ]] call EFUNC(extension,sendData); diff --git a/x/ocap2/licence.txt b/x/ocap2/licence.txt new file mode 100644 index 0000000..ba93af7 --- /dev/null +++ b/x/ocap2/licence.txt @@ -0,0 +1,17 @@ +Copyright (C) 2016 Jamie Goodson (aka MisterGoodson) (goodsonjamie@yahoo.co.uk) + +References to "this program" include all files, folders, and subfolders +bundled with this license file. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/x/ocap2/logo_ocap.paa b/x/ocap2/logo_ocap.paa new file mode 100644 index 0000000000000000000000000000000000000000..aa72d97b36ef93ecf553b7fc7aefb277f655b849 GIT binary patch literal 24169 zcmb@ueOOah_BMR>2@wJbK`Yf(l;i{ikq@mRD4$|yrqEiEAtZ#8gvxZPAQh!lg`FHk z+X{%b9Z_q;$KaqCBsfm_5JTd}v{rel{Y@KjG!O=dsvyRy5DH1ovv}V3-}k!SKOU|N zV<0&>d+oK?y4QWL1!Dj3hxM5o{*blagCK~}$D{wxbNTv>v(H)hn*IGBfB*YU$?&`g zzIkg2;@MjS;gA!An5J9NshUY1X39E;3X+2R`&`a<&9Dk8`3AwQ#T!}V@VdV5%MiY_o z1P;OkPSP&yxuYa_BI2r1sK!Kjk(L;ueSf%cl;1{pf9ia2>4nZvY#!UP>%!5)#T8~- z`-P*o%XW>XnlU20i}Sa%?mTq3Sb3Hcu=&vKvX_Qk=_?NODSFB;=VBW9L`#*XbW$LC zO+_xU8S|B1x#ZH$4#C+>UWmmb#D||3B`~XQmxbrudN^8)34&btyuH`&z{Z01C60*| z2MP<^f8J=vbIJa8o_PgLl06x!=Qfoxs^48Js1=2{JPxZ4`3IB4&O`jyP-F#foFSfc zcF|%EKW{486b)BIAqg?2orl8n9yw)Fds(02LXU}F;8T@xu*G!Z{vbck-@mq2Bn}`s zeTvY^J67z&^B%XE>}4AZT6wBcEa9oPidyYuV~Vt)@-aM@x2-MMbMv9Xg8jQxGZ-OT zi7FR+667J-j`s@-sxnSC?Jszt8&gC}OEk=9WQTO{pii`jXc-XNbQ7H9bgkW9mZOz^ zd6Zv`!j{!sWilJ5%(R?yyR4L%cL`%uGyG%hmmW2xklE&}wqX>_} zf}x&q)9tctX%+FS4Y$km89u(DuQM-5Ua*L=F6OT|kY1E7f1x{E+&-+aT%bb{?GR4j z`DE{jXH|acFWRn;(64&%^Q;s{bek?l?(pz4%t!J)qqLsO&r9n4O?`im(n_*2MGFE* zEw9w-@f;JRqcpPJK<`Dv{jZ+D48a-yS6uJmVtqzjFD&R&?5L{ZY@`X^j~8lt4i}Fpy4LiK;tF)B`9=!%L}7`8)AjoV(PFL0#)_ij zgGEI05+e3HV*$tS0G%b1VkwHUyM$Oqco&tj?#+kzdFGg;3!4ws6pY>r(Cs?LP7~%HER(1r zIY{)nj)L8j(=@$;&PQP_?QOUjDTkk(#z)9UTZ_j0)V`v`R+DMwbaN%jY1DAUHupeCZT&4i{g}O_$?`Xn&-cp^RZ@*fCJZG4=k z-ty-on-7gC&aiDYkP@;xl30^tV*xihxU@` zRGb|xpyGp(raf4L1&G?^YCsI zrN1$Qmm+u9ggP}dswmiq1B*OFfFVLCz=UcFo$2`37-o}E|w1F9C6J?A-UE}*} zFb8?+pXTFzJhJJ>pThI{6rKvYjNu5Y3uwk;TUwR6tgn!sy!6P+) zoCxk+F0~CxRk`#v*p;U9hmPSL=;KG_VR#qXb8p=f=Chcmg*qD=v@J-atZ$=e|5{6& zdR}sFkq6>zOB;Hob>lqr{6G7K;qGX(l1y6r zLMIXi_ylQER?rw7n6#GSJ-nE(>Zd}?&-?j~!9@kUfs~g-2jN_gdm8rRAT(ahj#I`U zbpMrCO?W@*cPbR{Q(F%esyQWy!}H^p5LFrcm8dc7bfcy?DS0j~C+qE16@~Q2JVU|N zo-svL#>36mwlb4a>4fT#K0~^cFd`>`)I?{8mXv0~M>X};n5h5}Pp%kk+*sh1Qmr>P zP|g+N*M2YObHqt226WG;gcUr|>sC@kyfqu?v^ll@I2V1B6fr{S!qDW>U+7_9Tl=FJg)h{&~B^8HFCTwzc9M^me82F#Z)q zBpE{}oQ&cgesIU=_l8aY=QTZD4(F9uA6Lbex7G!18G)0RM(YS}E z;wcozN=qOTPL-<65I<&mBgRd30awIRA%5PRgxp;0g_2CIHFyhpdA_k6`y=w{U@YaN zc;@08Jzw40Sg`lK_cl-gktn#escP0955K>ef!oj#)lVEucueHraE(1&6cCUL+k;=8 zIf4Ut`yMCx94<~x8|!mg@k*qeY5F&1OhT@QxpsUy-YIkv9DN2Ym!~scnu!syF@+PF za%oA=1N?X1)~!xcpJH2DN|2Dmttep@!hxBEhT`mRlJUP#hfpYzs1v*V`ynormv?W0 ztliq9dw@Se+S1!i+Fud!7!`4xMc5mDQ%*E3!#uQnPdAD0qX!Aojkw&4xjA}sCxq;X zXjXl@rhv0b_ckOKIx{m9BHM0tl8}nugeLUeA>bd6n+QGLhV%{XW7>KC{vx0HfWiVr zmE1tfN%Lug_fB8L%<%qmnC2nb2jAXgj@<}-M878vNQN@R$0d;uXk-M_gFQ3s3AS zxp1^9V}aS+7VOLu`7KV@i=9!tFh%Y`ex6qfVKuhk7f3Z@kEG~y#GGmOf!prw9Uj8x zL|nHJHaRA}2*1j+hj2p=7kj0&B7eU_5O@7n|37hRJfFzZg7SlV?_J1tBu|WIL zg`>WfDHlOd;L?h|d^Hsij#95hSa3QzYh2oa%RTEGs;gP{!cjQ29CzU6L#8Ty2I=a$ z39kLwhxiH=uo7L_doKrKc#W&91iyvWxm8M>wpxY4^Rk&t3A3H$EytClO|ISkA#-rJ z*TSIUA-x)sUo_ax)5;NlyoKc@!!5jlJkA7O0x0y=S4Ap->5oQdmg+MM#d3YV0&fus z08VTykjv>zya@d;)*cNxIwC3ZXDUf7be4*Zn>@!9>pBWuEc-&2&6HElaMX3_(sL?) z0}8J`%*B60^`*m|R6qjCdPZJ>r?*uWHC`rqB_Nk!cRgR0DS`Z`cvc=04ltd${^`nO8~; zJ98;K@A}%XbA}H^1f}(TO%p_aNO(DaZxf-yx|)eq+>ryBj>unbTECuVou??A7& zUOc$%CWVKPzI)5}5L+G{iLS#WCwS(W-S2=9wRc40Kd*BNx%RUBjPB_%7a-}D!JaXQ z0EMGH|A4U2R_S|C%R(IGxug0NP6iGshiuwP(G)tS|3Ls@*mt-#hN4#@eV9or60(7V z4!+7i>kh8vB?v+feR*_SnyPe<8~b~S_MH{Rez&o}(%+$;Z)1~{++8#WF;CA~DcuUR zjN?5GS#4fz36_zEAsW_ip6bS+ z?AfzTYcf0I=yYMif_E~P+nzoQVcCv2L2yT{*-^WSBZ!uSX5bUAYr*f?fNpnIP2Q`HB>+@k`3@Gsd(k= zW`21TdsECNS*BEsb0BbzmR9VibV~GFPt;?k%6sXg*m)54CaiSKC)OSKWD91ffD}}y z$!TQf?``uI(*@$hoHI_bb0569y@u%$37vsXhOv~EO^NCEIH_rzq>gclPC<;~nH0-V z^5n5Zd_=@`3hS07QySe%-dQOgMnj5zDR@V8m+Ln$uy#hh;{Iv5iW_fhYrMfZ2vb564 zC*`m!zcmYdn35MKJH)P}RM8xFU}{=6TO?i$7hU_0k#_7uE}XV0`EUrMYNKoMTJoHp zwS7o0K?-IpQS_SCN2R%3M1SUUt-I>Rw!NpXTAPsC)dmMDhJEo(AsS4D5`Io>%kxj~-l>3{rB^A}a= zr$g@0hLw}_L3Ug*GCrIb^IroWu}9>0$AIw6J5*(ZRvt z+CR$Wd>*yrsOSWs?xy4-B{;Kh>-hVSkxiqn2*x8gI9lXa*T$z0qmcGZlawwB*&bcU zh2JN6Z`g}#P*8hyHS-EDzd)SOzD&e-3MpL@iWzT)(v2Yb)?S%D!z(2$w7~*APRiZs zyLa)c$Sv)+isqVqezKN}bA#}VxUQQgZ2k1&W<=}+(6>Nyo5;T!bdhCjFEHZ$Y~lZy zdbO#WEpsoJ-Fi+(oV+NvT&sz@=M~StyIs0`dhCW~a>Hs#Z1+t%+QYJ;BUr$(X$c`o zbCF|Q+KA_P^p8lV3E~*ND|@F@yGzPYbxer>DYYEw2rlucXDbz>`EN-YBCU;=#8?gp z`>#xT7-igq#@bJk07jm4y?Yhgxh`LUckM{we(gsKpYrGO9`7pCc8;tbsWP283r=#U zr?CafZmwfwqLcOrawY+J6=mv&%~*j{neNT_Vt3D5t8o$sIj&b=iabn>Dh+RMI)eybG%Agk7fg?lJRRbOLdFx_JFYyO<6_*Q8x`@C&ti z*cTSLZ7k|*SR`*decncklxJ&2udsnhX17GwfUN_%^NK00Et%rNe zTyBYA2$LO&0+gsq;!FYrvHC^a!#BW;&;IO zGTxzT@a&nC)f0Q42G!Rb8^MUQmkqLE$ywrnI5H#W2d)iH&kKe84(!b{#jp);CeV44 zq7TTJv?7vht!JwgXV3nX_NM=`zP6qwvKhjcZB+nn5+7A_GK8yEy#io_;C>s*Ih&dm zo>$2`c5F=XaIMeNd|~EDs)~t@1~Nz!+^a633A85Y&=g?8%GQudSXXKO?b}+iV8~-N zJn?BCVF1qfjJ1y9uQ-v?2~E$W`Uq>Yc*J;=jD$&LQj^k;TED4Y)l5v_7A3(B5bXnm zH$DnkBew{O-!G7%aEFk7olNPstDTflf|Ra(ddgUXCTa~$cnS%4yt4yGEKryg73Jnr zi)*HM??9T@qNt?$9$;~~VWCqy!t<7;bxl1R&TLLi3p_rcX4yfiZY3$==~h8zyF6NI z2aN0P6a-U76^iPzxgn&DHX3(hlG|BT^D4`}m6FW}MQFBKDxz!gXGqpUG{TFfuNDn~ zXLwCrf=qyzEv>Co;Hm*vv?wg#JPNufA2JfXY7YVGV2Hw>kjQ8iCI%2 z0m$1)_Oc*)y;v=O(OL}!r>w8N6`Zc)#l0j-w;a81AD+gCQA6yC3MxJlVDXrOaz;_s zFjW7K>LXNA%I=5z@u%wd9i~FM8jbI{Z`OErNL*3bgP-@0n~2Vlpv6(rI%0K5d=QPq z2|(zQ3LrRq^(76T zi6$)fcR~5rZJoLW7JGEQyG>%-7;5VG5Q=ph#Er$w5d-VUBDST?Nxgg-|3d!fex&yp zU4kxD^fAEE-RO~N@OE^~QRu=`ygel)6NgXLvC@{77T4#Qw14lS5&zKG%Xy9Ugl_HZ*p_C2O7Zmoi*_M#$Lc3~9{)e$q}=4( zbPq&$%$HQrj}F$Zdii=$_RNhnne!J_CMY!$I^8S8zt-lsvw>XD_nIRcdnuZk1!W+ND3Yl_<&83_Soa~IxgCmKGe8sc z+6JOk^$i@Rz8o^o32Lej|0fA8+%LDHYGEG7cBFw49`+q&_1W zPHq*3jn6&?r;cC?9kOu@PyB+`8%ce-Ch~3XV0R0lMSDj z?|mvj762Ln65a>evgDGH7uM+uqh*<2k|WESPW}yex_`|aKL(-r(7~xrOz_V9P*a_n z_HeE5$seaN>k(0VsdDv5s)iMK2GNZ`fGWWP}<|(wuR7-mhQb( zm60ivO7+08dApBA;aak2#?jRZuIVuH#&H!IJ=b}LiVqTZzae)0GgG-|LU$H_VJy|Q z_K#DlHHAOb)r5(aE3b-U6<&|;PkLM&%9P2jUtye5g-Pj(SRY^$>!Rf4qN}U1y*e#9Ek7T?o4sm}o5?&?IhOe$;MosGbNZnKQ*}fPF^Ys4Rvhr- z@7HB!stU15?k8SSX1!%1ln<2zu)1O1PL>J?MYg?mC*$3Hj}hZOPHTp=q%8#RK}sca z4VHfXURidmYg3<&#eV3L=%hd*-oK}=IQkW53JKu&mWK&2Q#=B3V}EM|dX+jXZ9_i_y{*KbGT2 z^ur4^!#J2nqvC*EG5*J@_$K4$*nvwZiS_eB^l@F@UjpH-V zXW4XP*IryQr^>(%ys)OG3Fs7Y^dB;CigI6CNB}`=xQ4%Yt#i77!v2yViv|YGJfVMbae_f!mO*hLlERopXnvtKMpN#uZYeCyOK&cJCMfw7KJJIB2f*g|H)OZc5Z*FDP z@qx0=XJz;!K~CqQ)2-)@%;-$h&F!Iv35*JX3cCA%?`{sxRAP>&>sEIo_+Jh}lSQd}x$BWR6`^;I9TMR z|kML=nOLoM`x_ejTd^8|}|xLV9A?dOXzkf36e zmUhFN3W!6cu77k>fh&slIwaQbQH@XgDJr0xvw4?ybDSZ_8O4n==uAuXjU=uVE1hk8 z1b`l(`)4f#NipG;C7(%j)5t8FKu~>qY%Q)f#u-8-?oEsP1p@Dsbk#$y2boOay7?7v zEIOTCCB?4>nc^22j0f&u;P0D3D z1hlwj;rMNdx@{yn39>=X*T)R_7%Dr#K5Y0)a4>frT|)&FLH;&0HJt=E$g|tjlsbKl zLn8r}-t^#ciS|IWKLD{}ZApC>5kb+3s4A@81>RhicV!s_2X~A{1+H*vP;mhSu&6a= z=E;_aYfW_N48*c2M&n@SG>x_C=Ah}vXWs^g8vC}x0&g=#v@}6hvpOvX!0Z);2Om*P za&$0HRRE4dsnw1-5eX0!vg7h=4FbE=!|?_U7YOE4FaqF7=0N{g6xX%vv?B=xH!JnJF7xxDxzK2b{-|BV46f3&);zp(icwpp2GuzI{d(blJ^t&NV>eW~1S!(WQs@0#q)7g9niwyshWjn-RygeFQXjg*x_ z(g{}!K7>x$$MYd6kux0)19azfGjr>iBF=EH&fXrG2>lo2gw5?6XP9mtG!3UOIvr=w zZ{1oBm2Beit~GInK1D7t%eWXl*L! zaaIQRxf>Gq%$F?Hmc9Cpc;QwNl^B$tvDDeXvG^zLGZYJ}t%Ej* z2=}I|fdW~ECU>j1LU9~C(BB&r|3t(C7NH^W>|?XoYJ$AIL#p+H7gO?kqrf8e+^g;a zMbvb!H8|F}$;m=7v*okqV<4-HcR!Oee~N6{CjO>PY_8O|fdo`460lJw$4~>2VAJXd z_Ha(8dXSKo2Pa5I`Dj_MuN8Vbiym{^mOwXb}|!jrkl4(^YvLoTCVu=f0?n8DXWC8LE|%@pMp)w zE{*wxDKcj?(&NI=lvnTkyr-@cI%@Q-XBYQRH&dnqCPyz}rszK`gWGrx85SsTZ}h;` zKL&_@a805aSEJ=!(vk*_x8N*5k3CyQBLS&Kyz=>e#&>f2hZ0=mBOCkaqE|{)Mq}eX z@`aAlL`jw5+mu8!tq~_sKB5h7pVX__L@uQ+Cx~f!5tWecBf1)&Ol!R^=N9$U&X>?? zX$hrM^ZvG^$8dN1%h!_}{pxxS5~w~{EfM)I`6e|D;%{o&4Xswq>@uT$;(%E7OXY9> zYgVLjvjgzZ>$9^6m`PAy4xtJyFlpYO2QHR4E0L3yw^F)r6jiN}VF!vaYs#<_DyAy% zh8^ghCU~6jbkl=kdKWlor+e2hxZ=khj!mMuK=h0$h(?;m^4`+aL6By{0|=#u_qxHh z{0xt|K{x}IcH4L2an#*?Vzyj7&p0NSkiH%LVw@?~{*l8`zi+bdsh^{?nGRWR841*y zmVX)Ir8rRho#7$m(6FfX0SF`CSA`9L>A!w8_eaWU@O)3D?qzJ_P{ZS3)fZA$;*6#S z4w!=KB;~jQIvcmk7OUp^;Q@@tP+sY;Dqdnfx~7kG2N^Qv1tg>N+$oMo+R3r z)ki}LGWFzi2E`wc;Aj=v{IinC_Z40}FZN%R3i(c)-5~`bxmx^7UeHA$Cw=~0v9XxE z*3IEYi+Te5#LfoQ+mjpkGpJy?dlVCbVAJ5S$+zcAREj;BEIVI9#mAB=Tjug;i7p7e ze4QZuBv-xl8Ab;3?0~AMnECa{ghry1q0pAjJCGsPR5Y!jOxhS%$s9jm`53$rJ-;;N z0@-jP*k^p+Dc{QF33G`^2pzJlpWT?ly*6gO%d*Q-R;$srw9H`o9aMDUlAfaL&^6sT z4WP)P+-fbOXl-~Q)|HB}PUP(tR%HY^&EWnoGk@-)Y>^XZhb7hxsH>#dLFs7Zh-%g2 zigj*t&mH=exs6c};r1JjcQEr(MjbTbhNkS?clV^<0nL9cVq+q@KZwY6G|B1gNWNT7 zz-whPwG}@j9~&IwfR0H^j7648gA5&Pe}~y;_$7i~EVdbV0s-icNk}_7{3C=Mg_}D9 zG)VHnu%(RlZ_{HE>!vvqB?5!Zk($ybaW>GOO7NRX!mTCl)n!)`y@wjjq*PCYTIVu^ zjHnN>&aER_@Bt+;Oa(-f=23Pk-gj-bhFIgh=yXqQV~ak2x_ME(9)vQd1f4y3?6i(U zGP^bHr?j56{vH!P)6Ky=lDO7=^xXU)4-dE@sq}a0CPMl<5coZ%4hQ#I_u-$C%SF9# zcr$5v(Ve)Lum8v4Xrzp4p2<+Xpfm2Ld0}DGdoV|=$PBhoszyt+DrPq=61bxKLfE8T z?tXEA3{GBstFRoJ5N6UZy`XReTGt!x51W42;O-RhT*AYz(KZRa z6BUK8y8$uk+Ek{B@t!Fj&6<;%2J*0&E#)1bgjeUR_SpycOl^3ymx^D3LiV?IX(pmY z^Pp1qLsA+0fMFgB+UPp5`n{ow8p`HJUkg%7yT~(;7<8035L$a_J>j}zCXC-nm*Qw0A+Gy<%fKOhFdZ#UUgOzdzjj`x z$Sn{0`yYb5LKkYa>!n8bQce18u}OULsTx@<{-TSlurn@q?r<>kP+ca?nr)eR`TFW_ z_LEqvycwZ!30}Fnyr)*=KlrR=^YI8KZh+(`{iiYQjr zAL|wt7R(z8qFh7ib3<2xTk5^r#^Hz^Dph{~g2P1H*aHb05mF)r@OF4;EE784+ybWd>1bn$lJ)-gE@=z3f0Q19*HXI^~08kkcNc4~YPDKD|j7Xk8 z!(|MnPQLe^9Fh_J?bJ?0P5f(fSkT-4f#--bb|Qmk4OXllIn(gI#7RZn#d@ z;sy*Mj(J&n9~P8w*ZOCyKvPGA%YvM@XAe}Wm7$mvRcVFT`jh&gwBHFS{kZw|8C;KK z%uaQzGfHfG6Hp?+LhFhAEkNh=tiCtf@)Aqwk2Bh=?x_ZC&cAFyYU9spB`m(_>c&2h zbe}UN8r+12ThDZ1HyRszplWcB_Za|c)^(rM!`s&+8lJ$zQoJh_NTHDB)%)WgrUG)T zwTO_@OlhMAmVuV5uJDayzP@=^C((r8A1+$y3=|Oumk|8CrOr(#X^mBm zQ_#6@65DY(Z~FQN-=?PV^YkZ9=0j`RSb_NCuMEYrm;ljt-FxsLdR!_uKo3HF`-7%g zhuR(S-VNy4cWo4cE7nm@j<_9&H=IFcs`2vg41j7U~uOBsElUO4G zl#OkJS5%6;RoI&srZ2rvSa5x9602e7@B4_1iJt@ZIhC)p;EQO^!x9S|gpcuP8$49( zoz^xSd#|})MBH-;!&S`X%fJ_-zNd_o`KUyB0mNP0;&p$Jeg`P@xam6<%BV!TLAN1D zr}bMzz3!DFUi)NLYa8C1@Cis5f_C#s?LKQa6~AuogV<*;{F(IgL%DOe%&RHz6XdQ3 z)Irk{x&qCpv`d)xV!ZcD8XoVOoRK7*{OWd976X!bO*zy&;Gp6GRTeM>ds?O>kjb-4 zqTdH(9n!WuPQunFNo1NM+*LGR?EE^}iz)3bZIJ|OCZxsm3>*X7vIGqiYL98x6_R_{ z`u2P^!EN6~hmqIUKB@qcG^)ONL=sSg)X^Q)zy{0ZAC5ItYR~#GlAP&pXdkhuMsTsg zdl%VFH;d~vv|AFe0v*$_ceODbPHe)fdfEfArt9&$(rG@ZasC`<(c&aBFB;XSY zXJn6vxa$^ncFa%SKJW4xG-$V5!9raTzYRA`mX^6@yIx!m?)6g*Z~MBnG&{FZF7hzG zKlhYZI-P~zMiDbD7Z{nTAxmt07ci~%PDBHv__}npIQ~49xL3VE>zA4JM*U}18FPEH z(z)(F@-()rCtQ>iKeEO`ht#L!VJ=Q0WX2s<7U~1F#o{e0oV`+d@{Fj379ui zrcpxd>uTe5Qvr#Hb?@q#ZH~xR7H))-{_My6A28R?=W1SqgZ7kI)ey6$ zeULV3OCDb0Wi~W_!m_uD5r-6cTThB@A6+glhtMszIZ7BWrPi+d1dX^xy`X*b-)!SD z@X!;cX$HUI_e%Nav$(9i^@x_3dBHD+O5J|vgFNu=>W9*P@JmM85dtph3=D-$cHO|U zHhbXevQpMiG(0_c4@ef(V9WW2A6oYh!!j&)fSY<3=-*aT0qf9*`WtU)$1Jt$=q{>mN3sN1$;4w;by1V&u&|!{UH3vGSeTU_ew6LGTYTFn=ozN%mQ;j;}Ur zU!kW651`hU*_U?WC1||n6JT1~phTB0z&q*P%M!`^gWAsw#hTBQx?QMx4Dx~m9gkif z2dODIvayQN7E3MjP9@dZIU+&Ae;ic&IT-YKv|gJ01{{LgK}!NdL|$4V0nid`XpCb7 z>=P&bcW`|Hmm3@lqSnK;p+cp*9Z}A8d5yPyo zRUsEA{|^CP?Ly`L8D`_7=^rxl)p>1yVVMD2PrM{Rg_;wO4Z;+G zO0~xdwYOy5nJDJxIA0Ban&u#5LBR}cpmX28+q3j?Y{YIo0Ogi7Eqct%Rhm&8ja7tK z%DgRgBx3kWhP!oNpeA;=8;tAdU9}DZWA$kJY4DL#=Yv3_M#GKYB+e=xXIr1aXN5#f zg@elsPu@EL#BQ%^AQQ6JvFW^acqKFAc!m#hXVjOX>g}m%@-J5oCfzEgtZIha74=)g z7Z+nDC6L)KguPHho5#(q2XENuV}8NO72!(=ja}?c&&t#>XuLjWmT0n_IU<9H>8;}< z%${p6_%xT^w{~#D^|c;+G$;K82_L&RC&9wZ5x%&rgD^Z&`5)YX`bURLMSGoU=$H*8 zGMkfw14QDwoixOQUfUE4#F)Ik^c&_)AAw3@-AE2zZe2JlP#nId{e=OL;BMerTr(2u zauj>s&?ZUR`9sG<;`n zIcU;c%V&n80x8HOgOQO2ifua?(iOzq+}dJe$R>_nY~2V)*yzq-A<39x;3WH_Ec0?V zW!n@zcx-Mm0_WMo?%oZ(K`>pU^tWk85xFXW6lZTJVgk-cr4pdUEwX#14clfeCc!rE zf3~FxKNrV;>GuY44hoax9~mAqEzSHw-TKhn00#Wld^#N10M^N$-9AX*wa^WQyc#Rh zm{-Py9rLe7PTkd2qWvkF0eJNujT&q>4Cj6EXi>^O;N{C@G>{YKEqNBEd~DxiiL)Cu z{VhxcV5K*3>m`QFe7Rr3l%BuTMLWewW%}3_yq$N+`|yWeDSe7&y{H@zJ!jl*j?~)c zi7#3tOF!pA1F)8)9~%Fn{xnOT3$FF(v*vLq1IS880VBMrXK4-CXvdLT3J76R=J6@$ zSROr9`T^LKov!3*$WgvXN1&3i#%Fi}Nps{s!ww9$GXso|e=u*YF;N`QBpyZnWIwS6G&>cc8MasKirLd-oPr<0<(9mN{ zcy`oUn!Qq197rxVO5TY75zKd7&Rhbj^>|m_+t3B$lB6!YKqE@=}?2wL*R3!dR-j;1(|G)C?MD1T_ip~|gGQ3pYQt16HM8}`r9EA(b5B=~V zaJ@H59DP7SNhe?=iH+*-7r#t`IQV{7j2cECk`IqFw)%O}VCI!txSj{SWUgyDUH_|G zF43(-aa|)3lr;&RXL7_mAf^cpE?2#~f`wVv`t) zQ0>3Ub})prL?LE>vX_x719BVA4{Mtlue524&V@S6J9dFlY<9SZaJUclg6S#DMV8d}@bO z7GxCZ=4lSF1ODFi$l?>UxE+anmGd(P2&wb)<%z_j^(NxsTEFN#AS#oW)5t}5MFbG6 zaAN_AHj?2^g8pPx00LE)n6L`}ugwm=2q5Od9OAxmVo3P^J(##@36Xs(n@Hv(0;UUz zMisPuZa&l~l}h*Cf~i6f%UJ>P))CM@LU?zmKmb&7QEVbKYQaQe5g&OSE++O!rS%Sw zl65>9>AcY4VzR@=BVdLOX(1#J?re6L>nDPh>Pef7a)QSVQ%OWq&d9$#<@4#8I zN^dVKEO_9T{^u13_<4_>4l;*}Z4P;Exyln7!UMeUTHppvj=};O#9u#}OOWcKvz@bZ zdMTsl%=R*VUbCqy2F9T(I|?RmLEmp|+k*c>@FscO=-7JFP&|jp&E*UD{Jfz!ea-CD zUUd99-EA6D*7>5t*vBiV)!xRkdvC#zQVi`)ESMc6dlFUKIy;W!<=s-S5oY$tq0yX; z1>4dNUg{a2ogCY7e|WC}ntm?ngU^K1$?|}KY>w!{60h53%CI7S$sYnOB}e!O+QFY+ zDBT5YfmCV<2AlxuHDMxTat|MmF-7~ur;WGiSR~wEW`+R6K|Dbi7VuzPjy_!6r+9Ss zksNxK&yO-LCSNM;P=;)TZq-rZ{+-)p+2xu1VqSQj`|H6y@z5aPU#s0QGq_qc7goD0 zJ>7!`pKPc}jeuBwVTpejoRgco%Lec~o`M~Pkvx{of-dzny)X=yC}?`UF6l4~`kkSs zhxXp;Q>gr+zcLfV!td&cB0djrY1nA#7~a53V()*;viv*&h`XYHxIEJZ1# zLw9x;7Vz^@zP)`9K9C0>F;qvu9pnPth1VhBn)tg+>HUAX=yfpE3cw{e%(?(&!aNi9 z;5_O4(?_x2sS;(i>ZOiewA_kd&jtJVr zpL@9YtNbQ?X9x4v$R`GxkG#FkRsm~Y9>k1l=kj>`Q?X7@pQ?=U$u(=k^Qtn$TSpIQ z{QQ%N&<4WMo2(+ttTI{V@g?T+*pK^0Pu|gI{GZD6?@rIUFnL4B1|rsD6OW&_!=O%J z*{^N^1rRuA?=9%Y2iUU5-eU)gx=uI9v>_3}p>EIE@A#bT9Y1^cd-f?>$)~ElFzyhJ zxhxS;kPC157>)b5#sSzyQLir6@(^0#_Ov;;+v()D@dfXgNV1B4SXgl86E1fnEQN@V zh=GzL^dBD^uy=U8cX$AS6UpJSKV5>JRkAWcQ>3Re~^ zUa5r+WKB{bU4WVrmmUQg+WYYyIldM?fCjbx<+1by^s{@nRQt)_jPk?u|M0j10dIcu zwlvnAN#l>ayq=bJudv2O0Rt3l3iuTL+Q=jN#8zr24jQWbUjqBw9_mONdQ8sJ5E-`S9rl_`Jq zosw0P(~rJn*@?H6%8MKm>v!~Qt9I(xh)1voL)5b+BNpWVtqfElLXJDoPj*<3Bl-Qo zOc|eEIp<)-qa`f+WD8IAX1!yMN7m0C0ovcXvjod}LW~HUnOFSodw-gpv$znO*q~!p zv7XQklboEDmg0>pVNms){_f22SR42Hm2`1qC_lgvbTKPSQFQ%^$3?{>n|kT*E^?6` ziBjF3F|<02sEy;2<@HQ$+#F7$ibd-ODv}plvp*SFz-lSXU$;_0dJATk- z)YfX3OQFGRSqUT{!i=B|vK~nZ&(}&uSQRldDL`RD)JQu7p2T{tQfS$7IUoA?!t?So z1~dgzpe|a7moF*5w*g#pQJ3Vo$SOR=a~?F7xx$%bQAv84sVRW2$a`j@+y=#~HL#tclOf zHfP~qxq}O&1EsfGGqV3)tAh^2-gotwP@ym>_5tRRWsTpNt}X7T3DvE7yZPPxjP&G8 z?CY_6Li4M}US+Ke)%l8FTbYbYy<#5QchAl>O?^K@dy(7HVDvT01bvs#)8w-QDBg8E z_a)Qws*KQ-_Y)G`Ac%Wi`(N~I>tN%kz75#yurF!CBQ)i}r+3C#5R8HaJTf$C{>i5{ z9pU-t%E#L;6NmdeE~LpsWjC>42BOCSo8psxZ zi!?d0<_%{EWeq}jdWOVtWOT1~5Qgu9>lyl?hFSQtE9Q0fGKHpxDlAJ#}s?T_| z{w=-xDPAK;st2kYR3ZVlxtV3Bn=|<#RvzY%18oS7F=KnV5khxx35BU!|2aU2i1 zRSZ45vRx1GjxhIvh2Qb#YLg-gdWL&2|`yE_A`@(Dk*ARhQbCl8)eLm3pSx z_%5CHrZ?P0+_|R#z?P2x*JaWyuq1!1Y{CN1Q6bMUdEk$q5!#3Rnw+mdspkvC134{x zPW?v4M)QpGm1!&{l&BRFHP>wa-QNi!U%|51&gUxB&`fx(8wee`o_uhg!xMyoPIXQbnbdMulcS%V|8lX5}rjqVw7Gc!*~F8!G9 ztoL2`9c5AL$2Qw8a4@}n{U`RW}}D5hoS0cb*2QH=`uE%i3jIP>2ZFY3gje!UdzkQ2GfpNoZGJQK`b6O^fYV&7Vr{q=X@wK- zgM{xXYF`3gF^5S{$I)m)yIxP>Al_v|kCtV9a!5emVa8Xpcjn^NC~?f?V1Bnf3ybS> z^V5uf4^??)Wf_w=kH>DUb@bB9B`Wo?j&S#Dp($czlpNrl+wu4&umNqYZ#}16zekdR zY%dijw+JqE{PJJIAQ9+$*b~}0QYrpQnIJWKg)SlF={OGUZm*j9u_jiKtKg<1 z@t<{HmaJn^6*FmbLOYr&GsGLxOes~IsFN~9(U>$y8g;~vqJcG{)!MS~v?Xx&JMZC| zw(1I3RwoN;G4TiZ$biM=En-4!tU)|JWTJ_kABYKGh-}#o5a36Cck3)dQQyFG3W5NY0HR6@e7Dn|mLt zYW|kZu~vwL2lJ7=jlSrC_qP8ku_#<$cp(KsZgqF#EZ8twrTl)u_aEUWe5o<|ZM)I* zvVv4Lbk=T280%WTYjXq>d7sJ9_#(@w zr%Fd^Cd0qOz3tZU!Sv9YxM>$>hYpR*Ure6rvD>Tmt=NSwY=P$APi}vLRaTb4X;9tE z!Uy1z)t{RyXYT!cS=;8vn8?^VY}OGuh-Ve9@?ljE9%dZC_k}!QoWsOtci{tr?+e+m zI1lIX9_C{~pWyCdF`0AZ*g_nM4$I=j{|oi#`#0{@8$rEtx@l=F)^i2$zngPvFNyy< zQLX~q0mya~>lNDjdf;LLiNqur^hoqX5fXK|R2N)-CKWl602>tGg_JpV+HEtW-3pCX zw;lT+BrRuM0sLQbd7v}U)DrC_J{XYWrieF!<9)t{RF^C8zBuwvYKPo6#sAl~;X0_B zqE8VZ`2->ckW57q)iLDE!VlWdGJ{|$*8h6w=lnN4qUiTiRxlZNs}TK~77PXh$-^hR z-Qj#^0J|;=m2s0HxNEq&4eUZWoEIP{S0JNee*ipPfl53Dd{=I|0_3I&=fS6@!_RYG z-3D(3zAMl%?Irr<$!V|N{$O52dq21YKET7qdD4TW=01KFzWb`M;yGNC^k=XKD^G`p z6@DI3zEV=*)d5jNKpwKB&09jCKQxc%KM7J*jYebHCM3B5esPiq$UnXda?;Dow}I{$ z(dF}vV7*z}hGmK^XLWsgeE{M5jV%)K<@v6z)=fFki3=J<`p;*xI0cgyV-d!hb=!vvTL#&uLYim459;Ev!Jz7prwkEKRx zf8ATRqN3P|_LkH}3&JQptsSDc0}kK=joo4B*F3u;!JpKQ>(E}e^%C~emiSEdj}(l< zBwX>J|BxhQxXlTt7o5v~y3E+CEg8_w*q#~A)f+~Qj)&EQ2+d<6?ZM@14Emj}9WxNV z(;lGbr=&ovJl(_j<0x-RrmvAxYSIIJ3jTb!!=v+U=`~Tj6{Wmfe?%-N=k-n)%!o24`rYWY)G?m=+XBJs znP1{9(M9uX(j#d2?Yt6<5uTL@k}U;1d=qq-A6)JQK82nZgZ_y~F7&i9bOYb-&oOjB z{XqUA<$JmM6s|V%x}1QA5(v@w+?4D0SgfR<{T_%vxR^j$(+9oXeh=rr*2aIkvkK#1 z0R@zBrQI1(MQkT*j*Lojt;gp0$1@3q=th0?Po5gL+vcd5jG=$dUx}j{@Y(qG5;(B& zZD7Voua<|CP_&GC`nMC2S7MYrpweRbWS)6=J{0(&!>rnWkN$e+iS0MKA3P`icOsD{yk3$I-U>--^pV#B&MN6-y589Um_=?JJL2$Z zp72ASiwTXchKsZCCE|Zdut)WXexq41C>+;jFz=Im$!T50$L2s*BL52_Zat{~Q@ zA>0X&VEAX$9f6V`PE9HNxwgi%5zF$4u1ZVANitjget(b-SFt>yyg+86Nan5-GMh6n z56zWU>z5D5nx9UHnC5te&os&gBKhhzxmosQ$n1vQRR?ov3Xg*21=gNfff-%Fl91m* zE36}V5dF?xZ3fhqX-uwE&%DHuiooGFRA-4OLyDySQhewv>;1HHoQS5;n5sx+1R{}a<7NjKwbXHW5 zd4py3WW^pb6QtvQKf!07sjtuNMG_)BpQrPS`uptr4U-TDqk9Z7ZJAOyZR22O_xLpU z=K+1$Y8c+5m8DS;P#WcYgH|8JYwthX?xR)S0#_gtKvn@Q&03qlIX(?Ph))}ayY=0g z)OQa;d|EwNJB9C7P+vqluE2!9t>dNF!m7Woudhi>b!|TPraz5lDT_#EG_eWA6@FGli7&xH#Yo@~Z$Pr5C>tXQ<(8qTLJoMc$tp7_o`ww5*t%+edI|WGie@7Efp}r2+u~NNznpGy?+zF-G z)~0t9emTL|ksRj@qd;>T&>i$oP Date: Wed, 30 Mar 2022 19:51:51 -0400 Subject: [PATCH 06/26] removes cba_settings_whitelist --- x/ocap2/addons/extension/config.cpp | 2 -- x/ocap2/addons/main/config.cpp | 2 -- x/ocap2/addons/recorder/config.cpp | 2 -- 3 files changed, 6 deletions(-) diff --git a/x/ocap2/addons/extension/config.cpp b/x/ocap2/addons/extension/config.cpp index b9abb22..9edd0f1 100644 --- a/x/ocap2/addons/extension/config.cpp +++ b/x/ocap2/addons/extension/config.cpp @@ -17,8 +17,6 @@ class CfgPatches }; }; -cba_settings_whitelist[] = {"admin"}; - class Extended_PreInit_EventHandlers { class ADDON { // This will be executed once in 3DEN, main menu and before briefing has started for every mission diff --git a/x/ocap2/addons/main/config.cpp b/x/ocap2/addons/main/config.cpp index 8316bd7..f0ff80a 100644 --- a/x/ocap2/addons/main/config.cpp +++ b/x/ocap2/addons/main/config.cpp @@ -17,8 +17,6 @@ class CfgPatches }; }; -cba_settings_whitelist[] = {"admin"}; - class Extended_PreInit_EventHandlers { class ADDON { // This will be executed once in 3DEN, main menu and before briefing has started for every mission diff --git a/x/ocap2/addons/recorder/config.cpp b/x/ocap2/addons/recorder/config.cpp index 455ebd1..b43487e 100644 --- a/x/ocap2/addons/recorder/config.cpp +++ b/x/ocap2/addons/recorder/config.cpp @@ -17,8 +17,6 @@ class CfgPatches }; }; -cba_settings_whitelist[] = {"admin"}; - class Extended_PreInit_EventHandlers { class ADDON { // This will be executed once in 3DEN, main menu and before briefing has started for every mission From e55ac6d5b4d8e78c2fed61d8acd36ddf7df81fa6 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Fri, 8 Apr 2022 01:04:28 -0400 Subject: [PATCH 07/26] initialmarkers, zeus fired, ace_bft exclude, fix gren dupe - initial markers exclusion - tbd for remote control firedman - exclude ACE_BFT markers by default - fixes grenades moving to 0,0,0 in playback due to misattribution to liveMissiles PFH --- x/ocap2/addons/recorder/XEH_preInit.sqf | 4 ++-- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 8 ++++++++ x/ocap2/addons/recorder/fnc_handleMarkers.sqf | 16 ++++++++++++++++ .../addons/recorder/fnc_projectileMonitors.sqf | 4 ++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap2/addons/recorder/XEH_preInit.sqf index 0d7ffd2..6c459c6 100644 --- a/x/ocap2/addons/recorder/XEH_preInit.sqf +++ b/x/ocap2/addons/recorder/XEH_preInit.sqf @@ -106,10 +106,10 @@ GVAR(allSettings) = [ "EDITBOX", // setting type [ "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_']" + "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_','ACE_BFT_']" ], [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - "['SystemMarker_']", // default string value + "['SystemMarker_','ACE_BFT_']", // default string value true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 0450ec2..7c7f02b 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -33,6 +33,14 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; +private "_who"; +{ + if (_firer == _x getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull]) then { + _who = _x; + }; +} forEach (allUnits select {!isNull (_firer getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull])}); +if (!isNil "_who") then {_firer = _who}; + // not sent in ACE Throwing events if (isNil "_vehicle") then {_vehicle = objNull}; diff --git a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf index 1d6c933..3718cfc 100644 --- a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf +++ b/x/ocap2/addons/recorder/fnc_handleMarkers.sqf @@ -250,6 +250,20 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { { { private _marker = _x; + + // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server + // if value is undefined, then skip + private _isExcluded = false; + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { + { + if ((_marker) find _x >= 0) exitWith { + _isExcluded = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)); + }; + if (_isExcluded) then {continue}; + + // "Started polling starting markers" remoteExec ["hint", 0]; // get intro object markers _pos = ATLToASL (markerPos [_marker, true]); @@ -266,6 +280,8 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { _pos = _polyline; }; + + if (isNil "_dir") then { _dir = 0; } else {if (_dir isEqualTo "") then {_dir = 0}}; diff --git a/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf index ed1d422..7f6222e 100644 --- a/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf +++ b/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf @@ -67,8 +67,8 @@ GVAR(liveMissiles) = []; // PFH to track grenades, flares, thrown charges GVAR(liveGrenades) = []; [{ - private _processNow = GVAR(liveMissiles) select {isNull (_x#0)}; - GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x#0)}; + private _processNow = GVAR(liveGrenades) select {isNull (_x#0)}; + GVAR(liveGrenades) = GVAR(liveGrenades) select {!isNull (_x#0)}; // _processNow // for grenades that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw From 7ff41d0a76ac1ea3c35903503ffc825ae6f2c50c Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Mon, 11 Apr 2022 00:07:06 -0400 Subject: [PATCH 08/26] counter,respawntickets,drawicon for entities https://github.com/OCAP2/OCAP/issues/37 - adds remote-controlled unit detection if firer was >50m away from initial projectile position https://github.com/OCAP2/OCAP/issues/9 - adds setting for optional BIS_fnc_respawnTickets tracking, will automatically track missionNamespace + faction score counts every 30th frame - adds system for optional custom score tracking by a set of sides, a custom counter separate from BIS_fnc_respawnTickets - pending testing of debug drawIcon for units being recorded remove userconfig --- README.md | 1 - userconfig/ocap/config.hpp | 12 ------- x/ocap2/addons/recorder/XEH_preInit.sqf | 14 ++++++++ x/ocap2/addons/recorder/XEH_prep.sqf | 2 ++ .../addons/recorder/fnc_addEventMission.sqf | 28 +++++++++++++++ x/ocap2/addons/recorder/fnc_captureLoop.sqf | 21 +++++++---- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 16 +++++---- .../addons/recorder/fnc_entityMonitors.sqf | 36 +++++++++++++++++++ x/ocap2/addons/recorder/fnc_init.sqf | 4 +++ 9 files changed, 108 insertions(+), 26 deletions(-) delete mode 100644 README.md delete mode 100644 userconfig/ocap/config.hpp create mode 100644 x/ocap2/addons/recorder/fnc_entityMonitors.sqf diff --git a/README.md b/README.md deleted file mode 100644 index 9a2b580..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -OCAP-addon diff --git a/userconfig/ocap/config.hpp b/userconfig/ocap/config.hpp deleted file mode 100644 index af88688..0000000 --- a/userconfig/ocap/config.hpp +++ /dev/null @@ -1,12 +0,0 @@ -ocap_autoStart = true; // Automatically start OCAP recordings at session start. -ocap_minPlayerCount = 15; // recording will only begin if this many players are in the server -ocap_minMissionTime = 20; // missions must last at least this many minutes to be saved -ocap_frameCaptureDelay = 1; -ocap_saveMissionEnded = true; // automatically save mission with default settings on the "MPEnded" event handler trigger -ocap_preferACEUnconscious = true; // if true, ACE3 medical unconscious state will be used. if false, will check vanilla A3 system -ocap_excludeClassFromRecord = ["ACE_friesAnchorBar", "GroundWeaponHolder", "WeaponHolderSimulated"]; // excludes specific class names from recordings -ocap_excludeKindFromRecord = []; // use isKindOf checking to exclude one or more hierarchies of objects from recording -ocap_excludeMarkerFromRecord = ["SystemMarker_"]; // excludes markers with any of these strings in their markerName -ocap_trackTimes = false; // continously track times -- set true for missions with accelerated or skipped time -ocap_trackTimeInterval = 10; // track time every X capture frame -ocap_isDebug = false; // extra logging messages diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap2/addons/recorder/XEH_preInit.sqf index 6c459c6..85c10e7 100644 --- a/x/ocap2/addons/recorder/XEH_preInit.sqf +++ b/x/ocap2/addons/recorder/XEH_preInit.sqf @@ -115,6 +115,20 @@ GVAR(allSettings) = [ false // requires restart to apply ], + [ + QEGVAR(settings,trackTickets), + "CHECKBOX", // setting type + [ + "Enable Ticket Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "Will track respawn ticket counts for missionNamespace and each playable faction every 30th frame. Default: true" + ], + [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + true, // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + true // requires restart to apply + ], + [ QEGVAR(settings,trackTimes), "CHECKBOX", // setting type diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap2/addons/recorder/XEH_prep.sqf index 35fc9fc..656d587 100644 --- a/x/ocap2/addons/recorder/XEH_prep.sqf +++ b/x/ocap2/addons/recorder/XEH_prep.sqf @@ -28,6 +28,8 @@ PREP(getAmmoMarkerData); PREP(getWeaponDisplayData); PREP(projectileMonitors); +PREP(entityMonitors); + PREP(aceThrowing); PREP(aceExplosives); diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf index ebb390c..101dcf1 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -111,6 +111,34 @@ if (isNil QEGVAR(listener,customEvent)) then { OCAPEXTLOG(["Initialized customEvent listener"]); }; +// Custom event handler with key "ocap2_counterInit" +// Used for tracking scores or counts per side +if (isNil QEGVAR(listener,counterInit)) then { + EGVAR(listener,counterInit) = [QGVARMAIN(counterInit), { + EGVAR(counter,sides) = _this apply {_x#0}; + [QGVARMAIN(customEvent), ["counterInit", EGVAR(counter,sides)]] call CBA_fnc_localEvent; + { + [QGVARMAIN(counterEvent), _x] call CBA_fnc_serverEvent; + } forEach _this; + [_thisType, _thisId] call CBA_fnc_removeEventHandler; + }] call CBA_fnc_addEventHandlerArgs; + OCAPEXTLOG(["Initialized counterInit listener"]); +}; +if (isNil QEGVAR(listener,counterEvent)) then { + EGVAR(listener,counterEvent) = [QGVARMAIN(counterEvent), { + if (isNil QEGVAR(counter,sides)) exitWith {}; + if (typeName (_this#0) != "SIDE") exitWith {}; + if !((_this#0) in EGVAR(counter,sides)) exitWith {}; + + private _scores = []; + { + if ((_this#0) isEqualTo _x) then {_scores pushBack (_this#1)} else {_scores pushBack -1}; + } forEach EGVAR(counter,sides); + [QGVARMAIN(customEvent), ["counterSet", _scores]] call CBA_fnc_localEvent; + }] call CBA_fnc_addEventHandler; + OCAPEXTLOG(["Initialized counterEvent listener"]); +}; + // Custom event handler with key "ocap2_record" // This will START OR RESUME recording if not already. if (isNil QEGVAR(listener,record)) then { diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf index 5010f06..cfa174a 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -46,6 +46,15 @@ GVAR(PFHObject) = [ [] call FUNC(updateTime); }; + // every 15 frames of recording check respawn ticket state of each of three sides + if (GVAR(captureFrameNo) % (30 / GVAR(frameCaptureDelay)) == 0 && EGVAR(settings,trackTickets)) then { + private _scores = []; + { + _scores pushBack ([_x] call BIS_fnc_respawnTickets); + } forEach [missionNamespace, east, west, independent]; + ["ocap2_customEvent", ["respawnTickets", _scores]] call CBA_fnc_localEvent; + }; + // update diary record every 320 frames if (GVAR(captureFrameNo) % (320 / GVAR(frameCaptureDelay)) == 0) then { publicVariable QGVAR(captureFrameNo); @@ -63,8 +72,8 @@ GVAR(PFHObject) = [ { if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { if (_x isKindOf "Logic") exitWith { - _x setVariable [QGVARMAIN(exclude), true]; - _x setVariable [QGVARMAIN(isInitialized), true]; + _x setVariable [QGVARMAIN(exclude), true, true]; + _x setVariable [QGVARMAIN(isInitialized), true, true]; }; _x setVariable [QGVARMAIN(id), GVAR(nextId)]; [":NEW:UNIT:", [ @@ -78,7 +87,7 @@ GVAR(PFHObject) = [ ]] call EFUNC(extension,sendData); [_x] spawn FUNC(addUnitEventHandlers); GVAR(nextId) = GVAR(nextId) + 1; - _x setVariable [QGVARMAIN(isInitialized), true]; + _x setVariable [QGVARMAIN(isInitialized), true, true]; }; if !(_x getVariable [QGVARMAIN(exclude), false]) then { private _unitRole = _x getVariable [QGVARMAIN(unitType), ""]; @@ -126,8 +135,8 @@ GVAR(PFHObject) = [ }; if ((_class isEqualTo "unknown") || (_vehType in (parseSimpleArray EGVAR(settings,excludeClassFromRecord))) || _toExcludeKind) exitWith { LOG(ARR2("WARNING: vehicle is defined as 'unknown' or exclude:", _vehType)); - _x setVariable [QGVARMAIN(isInitialized), true]; - _x setVariable [QGVARMAIN(exclude), true]; + _x setVariable [QGVARMAIN(isInitialized), true, true]; + _x setVariable [QGVARMAIN(exclude), true, true]; }; _x setVariable [QGVARMAIN(id), GVAR(nextId)]; @@ -139,7 +148,7 @@ GVAR(PFHObject) = [ ]] call EFUNC(extension,sendData); [_x] spawn FUNC(addUnitEventHandlers); GVAR(nextId) = GVAR(nextId) + 1; - _x setVariable [QGVARMAIN(isInitialized), true]; + _x setVariable [QGVARMAIN(isInitialized), true, true]; }; if !(_x getVariable [QGVARMAIN(exclude), false]) then { private _crew = []; diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 7c7f02b..7794c81 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -33,13 +33,15 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; -private "_who"; -{ - if (_firer == _x getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull]) then { - _who = _x; - }; -} forEach (allUnits select {!isNull (_firer getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull])}); -if (!isNil "_who") then {_firer = _who}; +private _initialProjPos = getPos _projectile; +if (getPos _firer distance _initialProjPos > 50) then { + // if projectile in unscheduled environment is > 50m from FiredMan then likely remote controlled + // we should find the actual firing entity + _nearest = [_initialProjPos, allUnits select {!isPlayer _x}, 5] call CBA_fnc_getNearest; + _firer = _nearest#0; +}; +// missionNamespace getVariable ["bis_fnc_moduleRemoteControl_unit", _firer]; +// _unit getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull]; // not sent in ACE Throwing events if (isNil "_vehicle") then {_vehicle = objNull}; diff --git a/x/ocap2/addons/recorder/fnc_entityMonitors.sqf b/x/ocap2/addons/recorder/fnc_entityMonitors.sqf new file mode 100644 index 0000000..6d124e7 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_entityMonitors.sqf @@ -0,0 +1,36 @@ +#include "script_component.hpp" + +// DEBUG draws on clients +{ + if (!hasInterface) exitWith {}; + [] spawn { + waitUntil {!isNull (findDisplay 12)}; + GVAR(liveDebugBullets) = []; + disableSerialization; + (findDisplay 12 displayCtrl 51) ctrlAddEventHandler ["Draw", { + if (GVARMAIN(isDebug)) then { + { + // _x params ["_startPos", "_endPos", "_color", "_timeHit"]; + (_this#0) drawIcon [ + ["iconMan"] call BIS_fnc_textureVehicleIcon, // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" + [side group _x] call BIS_fnc_sideColor, + getPos _x, + 1, + 1, + getDir _x, + name _x, + 0, + 0.03, + "PuristaLight", + "center" + ]; + } forEach (allUnits + vehicles) select { + _x getVariable [QGVARMAIN(isInitialized), false] && + _x getVariable [QGVARMAIN(exclude), true] + }; + }; + }]; + }; +} remoteExec ["call", [0, -2] select isDedicated, true]; + +GVAR(entityMonitorsInitialized) = true; diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index 3b4c1b5..e59c155 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -153,3 +153,7 @@ if (GVAR(missionName) == "") then { if (isNil QGVAR(projectileMonitorsInitialized)) then { call FUNC(projectileMonitors); }; + +if (isNil QGVAR(entityMonitorsInitialized)) then { + call FUNC(entityMonitors); +}; From 62affe441bd736e10fece115c4da2aee6c302ee5 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Tue, 12 Apr 2022 14:06:03 -0400 Subject: [PATCH 09/26] remotecontrol, settings, wep display text, notifications on start/stop --- x/ocap2/addons/main/script_macros.hpp | 6 +- x/ocap2/addons/recorder/XEH_preInit.sqf | 31 ++-- .../recorder/fnc_addUnitEventHandlers.sqf | 47 ++++++ x/ocap2/addons/recorder/fnc_captureLoop.sqf | 10 +- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 156 ++++++++++-------- .../addons/recorder/fnc_entityMonitors.sqf | 2 +- x/ocap2/addons/recorder/fnc_exportData.sqf | 2 + .../recorder/fnc_getWeaponDisplayData.sqf | 4 + .../addons/recorder/fnc_startRecording.sqf | 2 + x/ocap2/addons/recorder/fnc_stopRecording.sqf | 1 + 10 files changed, 178 insertions(+), 83 deletions(-) diff --git a/x/ocap2/addons/main/script_macros.hpp b/x/ocap2/addons/main/script_macros.hpp index c40eb31..10854a8 100644 --- a/x/ocap2/addons/main/script_macros.hpp +++ b/x/ocap2/addons/main/script_macros.hpp @@ -10,9 +10,9 @@ #endif // The current version of OCAP2. -#define VERSION 1.0 -#define VERSION_STR 1.0.0 -#define VERSION_AR 1,0,0 +#define VERSION 2.0 +#define VERSION_STR 2.0.0-RC2 +#define VERSION_AR 2,0,0 #define VERSION_REQUIRED 2.08 // define: LOG diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap2/addons/recorder/XEH_preInit.sqf index 85c10e7..1bd695f 100644 --- a/x/ocap2/addons/recorder/XEH_preInit.sqf +++ b/x/ocap2/addons/recorder/XEH_preInit.sqf @@ -38,7 +38,7 @@ GVAR(allSettings) = [ ], - // RECORDING SETTINGS + // CORE [ QEGVAR(settings,frameCaptureDelay), "SLIDER", // setting type @@ -46,7 +46,7 @@ GVAR(allSettings) = [ "Frame Capture Delay", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds. Default: 1" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. [ 0.25, // min 10, // max @@ -66,13 +66,18 @@ GVAR(allSettings) = [ "Use ACE3 Medical", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "If true, will check ACE3 medical status on units. If false, or ACE3 isn't loaded, fall back to vanilla. Default: true" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. true, // default enabled true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply ], + + + + + // EXCLUSIONS [ QEGVAR(settings,excludeClassFromRecord), "EDITBOX", // setting type @@ -80,7 +85,7 @@ GVAR(allSettings) = [ "Classnames to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Exclusions"], // Pretty name of the category where the setting can be found. Can be stringtable entry. "['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']", // default string value true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. @@ -94,7 +99,7 @@ GVAR(allSettings) = [ "Object KindOfs to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Exclusions"], // Pretty name of the category where the setting can be found. Can be stringtable entry. "[]", // default string value true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. @@ -108,13 +113,17 @@ GVAR(allSettings) = [ "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_','ACE_BFT_']" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Exclusions"], // Pretty name of the category where the setting can be found. Can be stringtable entry. "['SystemMarker_','ACE_BFT_']", // default string value true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply ], + + + + // TRACKING EXTRA [ QEGVAR(settings,trackTickets), "CHECKBOX", // setting type @@ -122,11 +131,11 @@ GVAR(allSettings) = [ "Enable Ticket Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Will track respawn ticket counts for missionNamespace and each playable faction every 30th frame. Default: true" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Extra Tracking"], // Pretty name of the category where the setting can be found. Can be stringtable entry. true, // default enabled true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. - true // requires restart to apply + false // requires restart to apply ], [ @@ -136,7 +145,7 @@ GVAR(allSettings) = [ "Enable Mission Time Tracking", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Will continuously track in-game world time during a mission. Useful for accelerated/skipped time scenarios. Default: false" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Extra Tracking"], // Pretty name of the category where the setting can be found. Can be stringtable entry. false, // default enabled true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. @@ -150,7 +159,7 @@ GVAR(allSettings) = [ "Mission Time Tracking Interval", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "If time tracking is enabled, it will be checked every X capture frames. Default: 10" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Extra Tracking"], // Pretty name of the category where the setting can be found. Can be stringtable entry. [ 5, // min 25, // max @@ -201,7 +210,7 @@ GVAR(allSettings) = [ "Auto-Save When No Players", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. "Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting. Default: true" ], - [COMPONENT_NAME, "Recording Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + [COMPONENT_NAME, "Save/Export Settings"], // Pretty name of the category where the setting can be found. Can be stringtable entry. true, // default enabled true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. diff --git a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf index aafb014..87dd586 100644 --- a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf +++ b/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf @@ -46,3 +46,50 @@ if (isNil {_entity getVariable QGVARMAIN(MPHitEH)}) then { _entity addMPEventHandler ["MPHit", { _this call FUNC(eh_hit); }] ]; }; + + + +// HITPART +// [_entity, [ +// "HitPart", +// { +// // https://community.bistudio.com/wiki/HitPart_Sample +// (_this select 0) params ["_target", "_shooter", "_projectile", "_position", "_velocity", "_selection", "_ammo", "_vector", "_radius", "_surfaceType", "_isDirect"]; + +// if (isNull _shooter) then { +// // _shooter = [_target, _projectile] call FUNC(getInstigator); +// _shooter = (getShotParents _projectile) select 1; +// }; + +// private _hitFrame = GVAR(captureFrameNo); + +// _targetID = _target getVariable [QGVARMAIN(id), -1]; +// if (_targetID == -1) exitWith {}; +// private _eventData = [_hitFrame, "hit", _targetID, ["null", getText(configOf _projectile >> "displayName")], -1]; + +// if (!isNull _shooter) then { +// // _projectileId = _projectile getVariable [QGVARMAIN(id), -1]; +// _shooterId = _shooter getVariable [QGVARMAIN(id), -1]; + +// _projectileInfo = [ +// _shooterId, +// ([_shooter] call FUNC(getEventWeaponText)) +// ]; +// _distanceInfo = round (_target distance _shooter); + +// if (GVARMAIN(isDebug)) then { +// OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _targetID, _shooterId)); +// }; +// _eventData = [ +// _hitFrame, +// "hit", +// _targetID, +// _projectileInfo, +// _distanceInfo +// ]; +// }; + +// [":EVENT:", _eventData] remoteExecCall [QEFUNC(extension,sendData), 2]; + +// } +// ]] remoteExec ["addEventHandler", [0, -2] select isDedicated, true]; diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf index cfa174a..80f1998 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -106,7 +106,8 @@ GVAR(PFHObject) = [ }; _pos = getPosASL _x; - [":UPDATE:UNIT:", [ + + _unitData = [ (_x getVariable QGVARMAIN(id)), //1 _pos, //2 round getDir _x, //3 @@ -115,7 +116,12 @@ GVAR(PFHObject) = [ if (alive _x) then {name _x} else {""}, //6 BOOL(isPlayer _x), //7 _unitRole //8 - ]] call EFUNC(extension,sendData); + ]; + + if (_x getVariable ["unitData", []] isNotEqualTo _unitData) then { + [":UPDATE:UNIT:", _unitData] call EFUNC(extension,sendData); + }; + _x setVariable [QGVARMAIN(unitData), _unitData]; }; false } count (allUnits + allDeadMen); diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 7794c81..8069bb2 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -34,11 +34,13 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; private _initialProjPos = getPos _projectile; -if (getPos _firer distance _initialProjPos > 50) then { +if (getPos _firer distance _initialProjPos > 50 || vehicle _firer isKindOf "Air") then { // if projectile in unscheduled environment is > 50m from FiredMan then likely remote controlled // we should find the actual firing entity - _nearest = [_initialProjPos, allUnits select {!isPlayer _x}, 5] call CBA_fnc_getNearest; - _firer = _nearest#0; + private _nearest = [_initialProjPos, allUnits select {!isPlayer _x}, 75] call CBA_fnc_getNearest; + if (count _nearest > 0) then { + _firer = _nearest#0; + }; }; // missionNamespace getVariable ["bis_fnc_moduleRemoteControl_unit", _firer]; // _unit getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull]; @@ -55,13 +57,13 @@ if (_firerId == -1) exitWith {}; ([_weapon, _muzzle, _magazine, _ammo] call FUNC(getWeaponDisplayData)) params ["_muzzleDisp", "_magDisp"]; private _wepString = ""; -if (_muzzleDisp find _magDisp == 1 && _magDisp isNotEqualTo "") then { +if (_muzzleDisp find _magDisp == -1 && _magDisp isNotEqualTo "") then { _wepString = format["%1 [%2]", _muzzleDisp, _magDisp]; } else { _wepString = _muzzleDisp; }; -if (vehicle _firer != _firer) then { - _wepString = format["%1 [%2]", (configOf (vehicle _firer)) call BIS_fnc_displayName, _wepString]; +if (!isNull _vehicle) then { + _wepString = format["%1 [%2]", (configOf _vehicle) call BIS_fnc_displayName, _wepString]; }; _firer setVariable [QGVARMAIN(lastFired), _wepString]; @@ -144,70 +146,92 @@ switch (true) do { }; [ { - params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_subTypesAmmoSimType"]; - private _projectile = _EHData # 6; - - // if submunitions are NOT bullets, wait until the original projectile deploys then search for submunitions and 're-fire' them to appear in playback - // if !(_subTypesAmmoSimType == "shotBullet") exitWith { - // if (_magazine isKindOf "VehicleMagazine") then { - // [_EHData, _projectile, _subTypes] spawn { - // params ["_EHData", "_projectile", "_subTypes"]; - // private _ogPos = getPos _firer; - // while {!isNull _projectile} do {_ogPos = getPosASL _projectile; sleep 0.1;}; - // isNil { - // _projSearch = nearestObjects [ASLtoAGL _ogPos, _subTypes, 50, false]; - // { - // _EHData set [6, _x]; - // _EHData spawn FUNC(eh_firedMan); - // } forEach _projSearch; - // }; - // }; - // }; - - // if submunitions ARE bullets, process normally and just look for one item - while {isNull _projectile} do { - { - _projSearch = nearestObject [_firer, _x]; - if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - } forEach _subTypes; - sleep 0.1 - }; - - // get marker details based on original EH data - ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; - private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); - - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - // then get data of submunition to determine how to track it - private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); - switch (true) do { - case (_ammoSimType isEqualTo "shotBullet"): { - LOGBULLET; - }; - case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - LOGMISSILE; - - if (GVARMAIN(isDebug)) then { - // add to clients' map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; + _this spawn { + params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; + // private _projectile = _EHData # 6; + + // if submunitions are NOT bullets, wait until the original projectile deploys then search for submunitions and 're-fire' them to appear in playback + // if !(_subTypesAmmoSimType == "shotBullet") exitWith { + // if (_magazine isKindOf "VehicleMagazine") then { + // [_EHData, _projectile, _subTypes] spawn { + // params ["_EHData", "_projectile", "_subTypes"]; + // private _ogPos = getPos _firer; + // while {!isNull _projectile} do {_ogPos = getPosASL _projectile; sleep 0.1;}; + // isNil { + // _projSearch = nearestObjects [ASLtoAGL _ogPos, _subTypes, 50, false]; + // { + // _EHData set [6, _x]; + // _EHData spawn FUNC(eh_firedMan); + // } forEach _projSearch; + // }; + // }; + // }; + + // if submunitions ARE bullets, process normally and just look for one item + _EHData params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; + private _projectile = objNull; + // while {isNull _projectile} do { + // { + // _projSearch = nearestObject [_firer, _x]; + // if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + // } forEach _subTypes; + // }; + + while {isNull _projectile} do { + { + _projSearch = nearestObject [_firer, _x]; + if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + } forEach _subTypes; }; - case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { - LOGGRENADE; - - if (GVARMAIN(isDebug)) then { - // add to map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; + _newSubs = nearestObjects [_projectile, _subTypes, 50]; + + + isNil { + { + private _projectile = _x; + + // get marker details based on original EH data + ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; + private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); + + // then get data of submunition to determine how to track it + private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); + switch (true) do { + case (_ammoSimType isEqualTo "shotBullet"): { + LOGBULLET; + }; + case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { + LOGMISSILE; + + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + if (GVARMAIN(isDebug)) then { + // add to clients' map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; + }; + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { + LOGGRENADE; + + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; + }; + default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; + }; + } forEach _newSubs; + nil; }; - default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; }; }, - [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _subTypesAmmoSimType], + [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType], _simDelay ] call CBA_fnc_waitAndExecute; }; diff --git a/x/ocap2/addons/recorder/fnc_entityMonitors.sqf b/x/ocap2/addons/recorder/fnc_entityMonitors.sqf index 6d124e7..6e493f3 100644 --- a/x/ocap2/addons/recorder/fnc_entityMonitors.sqf +++ b/x/ocap2/addons/recorder/fnc_entityMonitors.sqf @@ -12,7 +12,7 @@ { // _x params ["_startPos", "_endPos", "_color", "_timeHit"]; (_this#0) drawIcon [ - ["iconMan"] call BIS_fnc_textureVehicleIcon, // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" + [getText((configOf _x) >> "icon")] call BIS_fnc_textureVehicleIcon, // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" [side group _x] call BIS_fnc_sideColor, getPos _x, 1, diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf index f9c445b..5e8e582 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -74,6 +74,7 @@ if (_frameTimeDuration < GVAR(minMissionTime) && !_overrideLimits) exitWith { // then we won't save, but will continue recording in case admins want to save once that threshold is met. // allow this restriction to be overriden LOG("Save attempted, but the minimum recording duration hasn't been met. Not saving, continuing to record."); + ["OCAP2 attempted to save, but the minimum recording duration hasn't been met. Recording will continue.", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; { player createDiaryRecord [ "OCAP2Info", @@ -137,6 +138,7 @@ if (!isNil "_tag") then { }; // briefingName is used here, no need for publicVariable for a simple confirmation log. +[format["OCAP2 saved %1 frames successfully", _endFrameNumber], 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; { player createDiaryRecord [ "OCAP2Info", diff --git a/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf b/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf index cddafc4..2835989 100644 --- a/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf +++ b/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf @@ -8,4 +8,8 @@ if (_magDisp == "") then {_magDisp = getText(configFile >> "CfgMagazines" >> _ma if (_magDisp == "" && !isNil "_ammo") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayNameShort")}; if (_magDisp == "" && !isNil "_ammo") then {_magDisp = getText(configFile >> "CfgAmmo" >> _ammo >> "displayName")}; +if (_muzzleDisp find _magDisp > -1 && _magDisp isNotEqualTo "") then { + _muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> "displayName"); +}; + [_muzzleDisp, _magDisp]; diff --git a/x/ocap2/addons/recorder/fnc_startRecording.sqf b/x/ocap2/addons/recorder/fnc_startRecording.sqf index 2aca51d..032b47d 100644 --- a/x/ocap2/addons/recorder/fnc_startRecording.sqf +++ b/x/ocap2/addons/recorder/fnc_startRecording.sqf @@ -11,6 +11,7 @@ if (!GVARMAIN(enabled)) exitWith {}; // if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. if (GVAR(recording)) exitWith { OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); + ["OCAP2 was asked to record and is already recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; }; GVAR(recording) = true; @@ -22,6 +23,7 @@ private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); [QGVARMAIN(customEvent), ["generalEvent", "Recording started."]] call CBA_fnc_serverEvent; +["OCAP2 began recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; [[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording start [{!isNull player}, { diff --git a/x/ocap2/addons/recorder/fnc_stopRecording.sqf b/x/ocap2/addons/recorder/fnc_stopRecording.sqf index 8ee38c8..8a0eadc 100644 --- a/x/ocap2/addons/recorder/fnc_stopRecording.sqf +++ b/x/ocap2/addons/recorder/fnc_stopRecording.sqf @@ -6,6 +6,7 @@ private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); [QGVARMAIN(customEvent), ["generalEvent", "Recording paused."]] call CBA_fnc_serverEvent; +["OCAP2 stopped recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; GVAR(recording) = false; publicVariable QGVAR(recording); From 64c1aed5ce72c0a46186fc9bafb7854a9bd03327 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Tue, 12 Apr 2022 19:34:52 -0400 Subject: [PATCH 10/26] finalize 2.0.0-RC2 - change MPHit to HitPart - fire tracking, setShotParents - fix debug entityDraw - exclude objects w empty models - fix duplicate fnc_init calls - fix remoteExec diary entries - init at PostInit --- x/ocap2/addons/licence.txt | 17 --- x/ocap2/addons/logo_ocap.paa | Bin 24169 -> 0 bytes x/ocap2/addons/main/CfgSettings.hpp | 0 x/ocap2/addons/main/XEH_postInit.sqf | 2 +- x/ocap2/addons/main/XEH_preInit.sqf | 6 +- x/ocap2/addons/mod.cpp | 22 ---- x/ocap2/addons/recorder/XEH_postInit.sqf | 7 +- x/ocap2/addons/recorder/XEH_preInit.sqf | 4 - .../recorder/fnc_addUnitEventHandlers.sqf | 118 ++++++++++-------- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 7 ++ .../addons/recorder/fnc_entityMonitors.sqf | 16 +-- x/ocap2/addons/recorder/fnc_getClass.sqf | 2 + x/ocap2/addons/recorder/fnc_init.sqf | 19 ++- .../addons/recorder/fnc_startRecording.sqf | 7 +- x/ocap2/addons/recorder/fnc_stopRecording.sqf | 2 +- x/ocap2/licence.txt | 17 --- x/ocap2/logo_ocap.paa | Bin 24169 -> 0 bytes x/ocap2/mod.cpp | 22 ---- 18 files changed, 113 insertions(+), 155 deletions(-) delete mode 100644 x/ocap2/addons/licence.txt delete mode 100644 x/ocap2/addons/logo_ocap.paa delete mode 100644 x/ocap2/addons/main/CfgSettings.hpp delete mode 100644 x/ocap2/addons/mod.cpp delete mode 100644 x/ocap2/licence.txt delete mode 100644 x/ocap2/logo_ocap.paa delete mode 100644 x/ocap2/mod.cpp diff --git a/x/ocap2/addons/licence.txt b/x/ocap2/addons/licence.txt deleted file mode 100644 index ba93af7..0000000 --- a/x/ocap2/addons/licence.txt +++ /dev/null @@ -1,17 +0,0 @@ -Copyright (C) 2016 Jamie Goodson (aka MisterGoodson) (goodsonjamie@yahoo.co.uk) - -References to "this program" include all files, folders, and subfolders -bundled with this license file. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . diff --git a/x/ocap2/addons/logo_ocap.paa b/x/ocap2/addons/logo_ocap.paa deleted file mode 100644 index aa72d97b36ef93ecf553b7fc7aefb277f655b849..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24169 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 "displayName")], -1]; - -// if (!isNull _shooter) then { -// // _projectileId = _projectile getVariable [QGVARMAIN(id), -1]; -// _shooterId = _shooter getVariable [QGVARMAIN(id), -1]; - -// _projectileInfo = [ -// _shooterId, -// ([_shooter] call FUNC(getEventWeaponText)) -// ]; -// _distanceInfo = round (_target distance _shooter); - -// if (GVARMAIN(isDebug)) then { -// OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _targetID, _shooterId)); -// }; -// _eventData = [ -// _hitFrame, -// "hit", -// _targetID, -// _projectileInfo, -// _distanceInfo -// ]; -// }; - -// [":EVENT:", _eventData] remoteExecCall [QEFUNC(extension,sendData), 2]; - -// } -// ]] remoteExec ["addEventHandler", [0, -2] select isDedicated, true]; +[_entity, [ + "HitPart", + { + // https://community.bistudio.com/wiki/HitPart_Sample + (_this select 0) params ["_target", "_shooter", "_projectile", "_position", "_velocity", "_selection", "_ammo", "_vector", "_radius", "_surfaceType", "_isDirect"]; + + if (isNull _shooter) then { + // _shooter = [_target, _projectile] call FUNC(getInstigator); + private _shotParents = getShotParents _projectile; + if (count _shotParents == 1) then { + _shooter = [_target, _shotParents#0, objNull] call FUNC(getInstigator); + }; + if (count _shotParents == 2) then { + _shooter = [_target, _shotParents#0, _shotParents#1] call FUNC(getInstigator); + }; + }; + if (isNull _shooter) exitWith {}; + + private _hitFrame = GVAR(captureFrameNo); + + _targetID = _target getVariable [QGVARMAIN(id), -1]; + if (_targetID == -1) exitWith {}; + _shooterId = _shooter getVariable [QGVARMAIN(id), -1]; + if (_shooterId == -1) exitWith {}; + + private _eventData = [_hitFrame, "hit", _targetID, [_shooterId, getText(configOf _projectile >> "displayName")], -1]; + + + + _wepText = format["%1x", count _this]; + + private _allSelections = flatten(_this apply {_x#5}); + if (count _allSelections > 0) then { + private _uniqueSelections = _allSelections arrayIntersect _allSelections; + _wepText = format["%1 - %2", _wepText, _uniqueSelections joinString ","]; + }; + + _wepText = format["%1 - %2", _wepText, ([_shooter] call FUNC(getEventWeaponText))]; + + + + + _projectileInfo = [ + _shooterId, + _wepText + ]; + _distanceInfo = round (_target distance _shooter); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _targetID, _shooterId)); + }; + _eventData = [ + _hitFrame, + "hit", + _targetID, + _projectileInfo, + _distanceInfo + ]; + + [":EVENT:", _eventData] remoteExecCall [QEFUNC(extension,sendData), 2]; + + } +]] remoteExec ["addEventHandler", [0, -2] select isDedicated, true]; diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 8069bb2..6ffeea3 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -42,11 +42,17 @@ if (getPos _firer distance _initialProjPos > 50 || vehicle _firer isKindOf "Air" _firer = _nearest#0; }; }; + // missionNamespace getVariable ["bis_fnc_moduleRemoteControl_unit", _firer]; // _unit getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull]; // not sent in ACE Throwing events if (isNil "_vehicle") then {_vehicle = objNull}; +if (!isNull _vehicle) then { + _projectile setShotParents [_vehicle, _firer]; +} else { + _projectile setShotParents [_firer, _firer]; +}; private _frame = GVAR(captureFrameNo); @@ -66,6 +72,7 @@ if (!isNull _vehicle) then { _wepString = format["%1 [%2]", (configOf _vehicle) call BIS_fnc_displayName, _wepString]; }; _firer setVariable [QGVARMAIN(lastFired), _wepString]; +(vehicle _firer) setVariable [QGVARMAIN(lastFired), _wepString]; _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); // _ammoSimType diff --git a/x/ocap2/addons/recorder/fnc_entityMonitors.sqf b/x/ocap2/addons/recorder/fnc_entityMonitors.sqf index 6e493f3..d8665e4 100644 --- a/x/ocap2/addons/recorder/fnc_entityMonitors.sqf +++ b/x/ocap2/addons/recorder/fnc_entityMonitors.sqf @@ -5,23 +5,25 @@ if (!hasInterface) exitWith {}; [] spawn { waitUntil {!isNull (findDisplay 12)}; - GVAR(liveDebugBullets) = []; disableSerialization; (findDisplay 12 displayCtrl 51) ctrlAddEventHandler ["Draw", { if (GVARMAIN(isDebug)) then { + _sizeInMeters = 2.5; + _iconSize = (_sizeInMeters * 0.15) * 10^(abs log (ctrlMapScale (_this#0))); { // _x params ["_startPos", "_endPos", "_color", "_timeHit"]; + if (!alive _x || isNull _x) then {continue}; (_this#0) drawIcon [ - [getText((configOf _x) >> "icon")] call BIS_fnc_textureVehicleIcon, // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" + getText(configFile >> "CfgVehicleIcons" >> (getText((configOf _x) >> "icon"))), // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" [side group _x] call BIS_fnc_sideColor, getPos _x, - 1, - 1, + _iconSize, + _iconSize, getDir _x, name _x, - 0, - 0.03, - "PuristaLight", + 1, + 0.02, + "TahomaB", "center" ]; } forEach (allUnits + vehicles) select { diff --git a/x/ocap2/addons/recorder/fnc_getClass.sqf b/x/ocap2/addons/recorder/fnc_getClass.sqf index 4568e34..d388dc6 100644 --- a/x/ocap2/addons/recorder/fnc_getClass.sqf +++ b/x/ocap2/addons/recorder/fnc_getClass.sqf @@ -1,5 +1,7 @@ #include "script_component.hpp" +if (getText(configFile >> "CfgVehicles" >> _this >> "model") isEqualTo "\A3\Weapons_f\empty") exitWith {"unknown"}; + if (_this isKindOf "Truck_F") exitWith {"truck"}; // Should be higher than Car if (_this call FUNC(isKindOfApc)) exitWith {"apc"}; if (_this isKindOf "Car") exitWith {"car"}; diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index e59c155..86f6849 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -30,7 +30,12 @@ if (is3DEN) exitWith {}; // if OCAP is disabled do nothing if (!GVARMAIN(enabled)) exitWith {}; // if recording has already initialized this session then just start recording, don't re-init -if (!isNil QGVAR(startTime)) exitWith {call FUNC(startRecording)}; +if (!isNil QGVAR(startTime)) exitWith { + if (!SHOULDSAVEEVENTS) exitWith {}; + call FUNC(startRecording) +}; + +// "debug_console" callExtension format["clientState: %1 (%2) | %3", getClientState, getClientStateNumber, __FILE__]; // bool: GVAR(recording) GVAR(recording) = false; @@ -61,7 +66,7 @@ call FUNC(addEventMission); [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { player createDiarySubject ["OCAP2Info", "OCAP2 AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; - ocap_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; + ocap2_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; EGVAR(diary,about) = player createDiaryRecord [ "OCAP2Info", [ @@ -72,7 +77,7 @@ call FUNC(addEventMission); "
" + "Extension version: " + (EGVAR(extension,version) # 0) + " (built " + (EGVAR(extension,version) # 1) + ")" + "
" + - "https://github.com/OCAP2/OCAP" + + "https://github.com/OCAP2/OCAP" + "

" + "OCAP2 is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + "

" + @@ -132,8 +137,10 @@ if (GVAR(missionName) == "") then { The startRecording function checks internally if recording has already started by other means via whether GVAR(startTime) has been declared or not. */ [ - {((count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, - {call FUNC(startRecording)} + {(getClientStateNumber > 8 && (count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, + { + call FUNC(startRecording) + } ] call CBA_fnc_waitUntilAndExecute; // When the server progresses past briefing and enters the mission, save an event to the timeline if recording @@ -146,7 +153,7 @@ if (GVAR(missionName) == "") then { // If a recording has been started, exceeds min mission time, and no players are on the server, auto-save [{ if (!isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count allPlayers == 0) then { - [nil, "Mission ended due to server being empty"] call FUNC(exportData); + [nil, "Recording ended due to server being empty"] call FUNC(exportData); }; }, 30] call CBA_fnc_addPerFrameHandler; diff --git a/x/ocap2/addons/recorder/fnc_startRecording.sqf b/x/ocap2/addons/recorder/fnc_startRecording.sqf index 032b47d..d1f1e01 100644 --- a/x/ocap2/addons/recorder/fnc_startRecording.sqf +++ b/x/ocap2/addons/recorder/fnc_startRecording.sqf @@ -11,7 +11,10 @@ if (!GVARMAIN(enabled)) exitWith {}; // if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. if (GVAR(recording)) exitWith { OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); - ["OCAP2 was asked to record and is already recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; + [ + ["OCAP2 was asked to record", 1, [1, 1, 1, 1]], + ["and is already recording", 1, [1, 1, 1, 1]] + ] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; }; GVAR(recording) = true; @@ -39,7 +42,7 @@ _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {st "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" ]; }, _this] call CBA_fnc_waitUntilAndExecute; -}] remoteExecCall ["call", 0, true]; +}] remoteExecCall ["call", [0, -2] select isDedicated, true]; if (GVAR(captureFrameNo) == 0) then { // Notify the extension diff --git a/x/ocap2/addons/recorder/fnc_stopRecording.sqf b/x/ocap2/addons/recorder/fnc_stopRecording.sqf index 8a0eadc..907223a 100644 --- a/x/ocap2/addons/recorder/fnc_stopRecording.sqf +++ b/x/ocap2/addons/recorder/fnc_stopRecording.sqf @@ -25,7 +25,7 @@ publicVariable QGVAR(recording); "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" ]; }, _this] call CBA_fnc_waitUntilAndExecute; -}] remoteExecCall ["call", 0, true]; +}] remoteExecCall ["call", [0, -2] select isDedicated, true]; // Log times [] call FUNC(updateTime); diff --git a/x/ocap2/licence.txt b/x/ocap2/licence.txt deleted file mode 100644 index ba93af7..0000000 --- a/x/ocap2/licence.txt +++ /dev/null @@ -1,17 +0,0 @@ -Copyright (C) 2016 Jamie Goodson (aka MisterGoodson) (goodsonjamie@yahoo.co.uk) - -References to "this program" include all files, folders, and subfolders -bundled with this license file. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . diff --git a/x/ocap2/logo_ocap.paa b/x/ocap2/logo_ocap.paa deleted file mode 100644 index aa72d97b36ef93ecf553b7fc7aefb277f655b849..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24169 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 Date: Thu, 14 Apr 2022 16:38:16 -0400 Subject: [PATCH 11/26] admin diary controls, some fixes - add diary record/pause/save controls for whitelisted admins in CBA setting, or when a user logs in as admin - ensure auto-save respects the saveOnEmpty setting - start recording on autoStart at clientStateNumber 10, or once the mission has actually begun to avoid dead time in recording during briefing screen - don't run init if not multiplayer - fixes duplication EH checking - fixes HandleDisconnect override to prevent unintentional AI taking over DCed players and disrespecting description.ext preferences - skip "__SERVER__ has connected" event --- x/ocap2/addons/main/XEH_preInit.sqf | 16 ++++++- x/ocap2/addons/main/script_macros.hpp | 3 +- x/ocap2/addons/recorder/XEH_prep.sqf | 2 + .../addons/recorder/fnc_addEventMission.sqf | 12 ++++- .../addons/recorder/fnc_adminUIcontrol.sqf | 46 +++++++++++++++++++ x/ocap2/addons/recorder/fnc_captureLoop.sqf | 5 +- x/ocap2/addons/recorder/fnc_eh_connected.sqf | 8 ++++ .../fnc_eh_onUserAdminStateChanged.sqf | 8 ++++ x/ocap2/addons/recorder/fnc_init.sqf | 19 ++++---- 9 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 x/ocap2/addons/recorder/fnc_adminUIcontrol.sqf create mode 100644 x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf diff --git a/x/ocap2/addons/main/XEH_preInit.sqf b/x/ocap2/addons/main/XEH_preInit.sqf index 8d8e92c..79152c7 100644 --- a/x/ocap2/addons/main/XEH_preInit.sqf +++ b/x/ocap2/addons/main/XEH_preInit.sqf @@ -32,11 +32,23 @@ GVAR(allSettings) = [ true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply + ], + + [ + QGVARMAIN(administratorList), + "EDITBOX", // setting type + [ + "Administrators", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. + "An array or server-visible variable referencing one that is a list of playerUIDs. Additional briefing diary or UI elements may be available for more accessible control over OCAP2's features. Takes effect on player server connection. Format: [] OR myAdminPUIDs | Default: []" + ], + [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. + "[]", // default enabled + true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer + {}, // function that will be executed once on mission start and every time the setting is changed. + false // requires restart to apply ] ]; - - { _x call CBA_fnc_addSetting; } forEach GVAR(allSettings); diff --git a/x/ocap2/addons/main/script_macros.hpp b/x/ocap2/addons/main/script_macros.hpp index 10854a8..f931fe6 100644 --- a/x/ocap2/addons/main/script_macros.hpp +++ b/x/ocap2/addons/main/script_macros.hpp @@ -20,6 +20,8 @@ #define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData) #define SYSCHAT remoteExec ["systemChat", [0, -2] select isDedicated] +#define SHOULDSAVEEVENTS ((missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] > -1) + // #define DEBUG_MODE_NORMAL #define DEBUG_MODE_FULL @@ -39,5 +41,4 @@ #include "\x\cba\addons\main\script_macros_common.hpp" #include "\x\cba\addons\xeh\script_xeh.hpp" -#define SHOULDSAVEEVENTS ((missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] > -1) diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap2/addons/recorder/XEH_prep.sqf index 656d587..abb652d 100644 --- a/x/ocap2/addons/recorder/XEH_prep.sqf +++ b/x/ocap2/addons/recorder/XEH_prep.sqf @@ -18,6 +18,8 @@ PREP(addUnitEventHandlers); PREP(eh_connected); PREP(eh_disconnected); +PREP(eh_onUserAdminStateChanged); +PREP(adminUIcontrol); PREP(eh_firedMan); PREP(eh_hit); diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap2/addons/recorder/fnc_addEventMission.sqf index 101dcf1..4dcfcae 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap2/addons/recorder/fnc_addEventMission.sqf @@ -26,19 +26,27 @@ Author: #include "script_component.hpp" if (isNil QEGVAR(EH,HandleDisconnect)) then { - addMissionEventHandler["HandleDisconnect", { + EGVAR(EH,HandleDisconnect) = addMissionEventHandler["HandleDisconnect", { _this call FUNC(eh_disconnected); + false; // ensure we're not overriding disabledAI and persisting an AI unit to replace the player's }]; OCAPEXTLOG(["Initialized HandleDisconnect EH"]); }; if (isNil QEGVAR(EH,PlayerConnected)) then { - addMissionEventHandler["PlayerConnected", { + EGVAR(EH,PlayerConnected) = addMissionEventHandler["PlayerConnected", { _this call FUNC(eh_connected); }]; OCAPEXTLOG(["Initialized PlayerConnected EH"]); }; +if (isNil QEGVAR(EH,OnUserAdminStateChanged)) then { + EGVAR(EH,OnUserAdminStateChanged) = addMissionEventHandler ["OnUserAdminStateChanged", { + _this call FUNC(eh_onUserAdminStateChanged); + }]; + OCAPEXTLOG(["Initialized OnUserAdminStateChanged EH"]); +}; + if (isNil QEGVAR(EH,EntityKilled)) then { addMissionEventHandler ["EntityKilled", { _this call FUNC(eh_killed); diff --git a/x/ocap2/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap2/addons/recorder/fnc_adminUIcontrol.sqf new file mode 100644 index 0000000..7a35918 --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_adminUIcontrol.sqf @@ -0,0 +1,46 @@ +params ["_PID"]; + +if (isNil "_PID") exitWith {}; + +(getUserInfo _PID) params ["_playerID", "_owner", "_playerUID"]; + +// check if admin +private _adminUIDs = missionNamespace getVariable [QGVARMAIN(administratorList), nil]; + +if (isNil "_adminUIDs") then { + _adminUIDs = parseSimpleArray QGVARMAIN(administratorList); +}; +if (isNil "_adminUIDs") exitWith { + WARNING("Failed to parse administrator list setting. Please check its value!"); +}; + +if !(_playerUID in _adminUIDs) exitWith {}; + + +// add controls to diary entry +{ + [{getClientStateNumber > 9 && !isNull player}, { + + if (player getVariable [QGVARMAIN(hasAdminControls), false]) exitWith {}; + + QEGVAR(diary,adminControls) = player createDiarySubject [ + QEGVAR(diary,adminControls_subject), + PREFIX + " Admin", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa" + ]; + + player createDiaryRecord [ + QEGVAR(diary,adminControls_subject), + [ + PREFIX + " Controls", + "
+ Start/Resume Recording
+ Pause Recording
+ Stop and Export Recording + " + ] + ]; + + player setVariable [QGVARMAIN(hasAdminControls), true, 2]; + }] call CBA_fnc_waitUntilAndExecute; +} remoteExec ["call", _owner]; diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap2/addons/recorder/fnc_captureLoop.sqf index 80f1998..7447414 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap2/addons/recorder/fnc_captureLoop.sqf @@ -52,7 +52,7 @@ GVAR(PFHObject) = [ { _scores pushBack ([_x] call BIS_fnc_respawnTickets); } forEach [missionNamespace, east, west, independent]; - ["ocap2_customEvent", ["respawnTickets", _scores]] call CBA_fnc_localEvent; + [QGVARMAIN(customEvent), ["respawnTickets", _scores]] call CBA_fnc_localEvent; }; // update diary record every 320 frames @@ -139,7 +139,7 @@ GVAR(PFHObject) = [ }; } forEach (parseSimpleArray EGVAR(settings,excludeKindFromRecord)); }; - if ((_class isEqualTo "unknown") || (_vehType in (parseSimpleArray EGVAR(settings,excludeClassFromRecord))) || _toExcludeKind) exitWith { + if ((_class isEqualTo "unknown") || _toExcludeKind) exitWith { LOG(ARR2("WARNING: vehicle is defined as 'unknown' or exclude:", _vehType)); _x setVariable [QGVARMAIN(isInitialized), true, true]; _x setVariable [QGVARMAIN(exclude), true, true]; @@ -154,6 +154,7 @@ GVAR(PFHObject) = [ ]] call EFUNC(extension,sendData); [_x] spawn FUNC(addUnitEventHandlers); GVAR(nextId) = GVAR(nextId) + 1; + _x setVariable [QGVARMAIN(vehicleClass), _class]; _x setVariable [QGVARMAIN(isInitialized), true, true]; }; if !(_x getVariable [QGVARMAIN(exclude), false]) then { diff --git a/x/ocap2/addons/recorder/fnc_eh_connected.sqf b/x/ocap2/addons/recorder/fnc_eh_connected.sqf index 1302f2c..f3a2ebf 100644 --- a/x/ocap2/addons/recorder/fnc_eh_connected.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_connected.sqf @@ -1,5 +1,13 @@ #include "script_component.hpp" +params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; + +// skip for server 'connected' message +if ((_this#0) isEqualTo 2) exitWith {}; + +// log to timeline [":EVENT:", [GVAR(captureFrameNo), "connected", _this select 2] ] call EFUNC(extension,sendData); + +[_id] call FUNC(adminUIcontrol); diff --git a/x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf new file mode 100644 index 0000000..1d90c8f --- /dev/null +++ b/x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf @@ -0,0 +1,8 @@ +params ["_networkId", "_loggedIn", "_votedIn"]; + +_object = (getUserInfo _networkId) select 10; +if (isNull _object) exitWith {}; +if (_loggedIn && !_votedIn && !(_object getVariable [QGVARMAIN(hasAdminControls), true])) then { + // if user has become admin by logging, not voting, and has not yet received adminControls per OCAP2 - Main > Administrators setting, add controls + [_id] call FUNC(adminUIcontrol); +}; diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index 86f6849..fad09ae 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -25,8 +25,8 @@ Author: #include "script_component.hpp" -// exit if in 3DEN editor (when loaded in PreInit XEH -if (is3DEN) exitWith {}; +// exit if in 3DEN editor (when loaded in PreInit XEH) +if (is3DEN || !isMultiplayer) exitWith {}; // if OCAP is disabled do nothing if (!GVARMAIN(enabled)) exitWith {}; // if recording has already initialized this session then just start recording, don't re-init @@ -135,24 +135,23 @@ if (GVAR(missionName) == "") then { We'll wait to see if auto-start is enabled and minPlayercount setting is met. This covers scenarios where someone changes the autostart setting during the mission as well, and excludes cases where autostart is disabled. If recording hasn't started already, we'll initialize it here assuming the above conditions are met. The startRecording function checks internally if recording has already started by other means via whether GVAR(startTime) has been declared or not. + Start recording AFTER Briefing screen, so the beginning of the recording matches the start of the actual mission session. */ [ - {(getClientStateNumber > 8 && (count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, + {(getClientStateNumber > 9 && (count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, { call FUNC(startRecording) + [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; } ] call CBA_fnc_waitUntilAndExecute; -// When the server progresses past briefing and enters the mission, save an event to the timeline if recording -[{getClientStateNumber > 9}, { - if (!SHOULDSAVEEVENTS) exitWith {}; - [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; -}] call CBA_fnc_waitUntilAndExecute; - // Auto-save on empty - checked every 30 seconds // If a recording has been started, exceeds min mission time, and no players are on the server, auto-save [{ - if (!isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count allPlayers == 0) then { + if ( + QEGVAR(settings,saveOnEmpty) && + !isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count allPlayers == 0 + ) then { [nil, "Recording ended due to server being empty"] call FUNC(exportData); }; }, 30] call CBA_fnc_addPerFrameHandler; From d6549e44bb245091375e2a7f9920423ce4412e28 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Fri, 15 Apr 2022 13:18:21 -0400 Subject: [PATCH 12/26] fixes exportData minMissionTime parse; excludes HC for saveOnEmpty --- x/ocap2/addons/recorder/fnc_exportData.sqf | 2 +- x/ocap2/addons/recorder/fnc_init.sqf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap2/addons/recorder/fnc_exportData.sqf index 5e8e582..1b86338 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap2/addons/recorder/fnc_exportData.sqf @@ -65,7 +65,7 @@ if (isNil QGVAR(startTime)) exitWith { _elapsedTime = time - GVAR(startTime); -_frameTimeDuration = (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) * 60; +_frameTimeDuration = (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60; TRACE_7("Save attempted. Elapsed Time =", _elapsedTime," Frame Count * Delay Duration =", _frameTimeDuration," delta =", _elapsedTime - _frameTimeDuration); diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index fad09ae..af2c6a4 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -150,7 +150,7 @@ if (GVAR(missionName) == "") then { [{ if ( QEGVAR(settings,saveOnEmpty) && - !isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count allPlayers == 0 + !isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count (call BIS_fnc_listPlayers) == 0 ) then { [nil, "Recording ended due to server being empty"] call FUNC(exportData); }; From 286774e0e7f2d258016cb9ab7b440195eed8a7c6 Mon Sep 17 00:00:00 2001 From: Indigo Date: Fri, 15 Apr 2022 15:16:46 -0400 Subject: [PATCH 13/26] Update x/ocap2/addons/recorder/fnc_init.sqf Co-authored-by: jonpas --- x/ocap2/addons/recorder/fnc_init.sqf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap2/addons/recorder/fnc_init.sqf index af2c6a4..0ff6935 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap2/addons/recorder/fnc_init.sqf @@ -150,7 +150,7 @@ if (GVAR(missionName) == "") then { [{ if ( QEGVAR(settings,saveOnEmpty) && - !isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count (call BIS_fnc_listPlayers) == 0 + !isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count (call CBA_fnc_players) == 0 ) then { [nil, "Recording ended due to server being empty"] call FUNC(exportData); }; From e397415de51b069ef89142150b0b019eaa70148b Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Thu, 21 Apr 2022 14:09:08 -0400 Subject: [PATCH 14/26] account for countermeasure shots (flares) in tracking/deletion --- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index 6ffeea3..df01b37 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -124,7 +124,7 @@ switch (true) do { [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; }; }; - case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { LOGGRENADE; if (GVARMAIN(isDebug)) then { @@ -219,7 +219,7 @@ switch (true) do { [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; }; }; - case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX"]): { + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { LOGGRENADE; // create our marker record in the timeline From b5a820cc464848c537c7a8b4eae32c0c70df62f7 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Tue, 27 Sep 2022 19:16:20 -0400 Subject: [PATCH 15/26] start fire EH rework for bullets --- x/ocap2/addons/recorder/fnc_eh_firedMan.sqf | 279 +++++++++++++------- 1 file changed, 179 insertions(+), 100 deletions(-) diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf index df01b37..448aa38 100644 --- a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf @@ -75,6 +75,9 @@ _firer setVariable [QGVARMAIN(lastFired), _wepString]; (vehicle _firer) setVariable [QGVARMAIN(lastFired), _wepString]; _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); + +_projectile setVariable [QGVAR(firerId), _firerId]; + // _ammoSimType // "ShotGrenade" // M67 // "ShotRocket" // S-8 @@ -96,7 +99,16 @@ switch (true) do { _projectile = nearestObject [_firer, _ammo]; }; if (isNil "_projectile") exitWith {}; - LOGBULLET; + + _projectile addEventHandler ["Deleted", { + params ["_projectile"]; + [":FIRED:", [ + _projectile getVariable [QGVAR(firerId), -1], + GVAR(captureFrameNo), + _pos + ]] call EFUNC(extension, sendData); + }; + // LOGBULLET; }; @@ -114,6 +126,9 @@ switch (true) do { _firerPos = getPosASL _firer; [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + + switch (true) do { case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { LOGMISSILE; @@ -125,7 +140,14 @@ switch (true) do { }; }; case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { - LOGGRENADE; + // LOGGRENADE; + + _projectile setVariable [GVAR(ocapdata), [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]]; + + _projectile addEventHandler ["Deleted", { + params ["_projectile"]; + [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable [GVAR(ocapdata)]]] call CBA_fnc_localEvent; + }]; if (GVARMAIN(isDebug)) then { // add to map draw array @@ -140,106 +162,163 @@ switch (true) do { case (_ammoSimType isEqualTo "shotSubmunitions"): { - // for submunitions, we first look at the original ammo and find the classnames of submunition ammo its rounds will turn into - // these are done in a staggered array in format ["classname1", probability of spawn, "classname2", probability of spawn] - // this is usually for vehicles with guns that fire mixed ammo, or for cluster munitions - // we'll wait for the simStep to have elapsed, then start checking for the resulting submunition nearby - // once we have that, we'll add it to the bullet tracking array for positions so a fireline is drawn in playback - private _simDelay = (configFile >> "CfgAmmo" >> _ammo >> "simulationStep") call BIS_fnc_getCfgData; - private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; - private _subTypesAmmoSimType = _subTypes apply {(configFile >> "CfgAmmo" >> _x >> "simulation") call BIS_fnc_getCfgData}; - if (count _subTypesAmmoSimType > 0) then { - _subTypesAmmoSimType = selectRandom(_subTypesAmmoSimType); - }; - [ - { - _this spawn { - params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; - // private _projectile = _EHData # 6; - - // if submunitions are NOT bullets, wait until the original projectile deploys then search for submunitions and 're-fire' them to appear in playback - // if !(_subTypesAmmoSimType == "shotBullet") exitWith { - // if (_magazine isKindOf "VehicleMagazine") then { - // [_EHData, _projectile, _subTypes] spawn { - // params ["_EHData", "_projectile", "_subTypes"]; - // private _ogPos = getPos _firer; - // while {!isNull _projectile} do {_ogPos = getPosASL _projectile; sleep 0.1;}; - // isNil { - // _projSearch = nearestObjects [ASLtoAGL _ogPos, _subTypes, 50, false]; - // { - // _EHData set [6, _x]; - // _EHData spawn FUNC(eh_firedMan); - // } forEach _projSearch; - // }; - // }; - // }; - - // if submunitions ARE bullets, process normally and just look for one item - _EHData params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; - private _projectile = objNull; - // while {isNull _projectile} do { - // { - // _projSearch = nearestObject [_firer, _x]; - // if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - // } forEach _subTypes; - // }; - - while {isNull _projectile} do { - { - _projSearch = nearestObject [_firer, _x]; - if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - } forEach _subTypes; + + _projectile setVariable [GVAR(data), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))]; + + + _projectile addEventHandler ["SubmunitionCreated", { + params ["_projectile", "_submunitionProjectile", "_pos", "_velocity"]; + (_projectile getVariable [GVAR(data), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"]; + _submunitionProjectile setVariable [QGVAR(firerId), _projectile getVariable [QGVAR(firerId), -1]]; + + private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); + + // then get data of submunition to determine how to track it + private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); + + + switch (true) do { + case (_ammoSimType isEqualTo "shotBullet"): { + // LOGBULLET; + _submunitionProjectile addEventHandler ["Deleted", { + params ["_projectile"]; + [":FIRED:", [ + _projectile getVariable [QGVAR(firerId), -1], + GVAR(captureFrameNo), + _pos + ]] call EFUNC(extension, sendData); }; - _newSubs = nearestObjects [_projectile, _subTypes, 50]; - - - isNil { - { - private _projectile = _x; - - // get marker details based on original EH data - ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; - private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); - - // then get data of submunition to determine how to track it - private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); - switch (true) do { - case (_ammoSimType isEqualTo "shotBullet"): { - LOGBULLET; - }; - case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - LOGMISSILE; - - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - if (GVARMAIN(isDebug)) then { - // add to clients' map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; - }; - case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { - LOGGRENADE; - - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - if (GVARMAIN(isDebug)) then { - // add to map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; - }; - default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; - }; - } forEach _newSubs; - nil; + }; + case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { + LOGMISSILE; + + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + if (GVARMAIN(isDebug)) then { + // add to clients' map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; }; }; - }, - [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType], - _simDelay - ] call CBA_fnc_waitAndExecute; + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { + LOGGRENADE; + + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; + }; + default { + OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType)) + }; + }; + }]; + + // // for submunitions, we first look at the original ammo and find the classnames of submunition ammo its rounds will turn into + // // these are done in a staggered array in format ["classname1", probability of spawn, "classname2", probability of spawn] + // // this is usually for vehicles with guns that fire mixed ammo, or for cluster munitions + // // we'll wait for the simStep to have elapsed, then start checking for the resulting submunition nearby + // // once we have that, we'll add it to the bullet tracking array for positions so a fireline is drawn in playback + // private _simDelay = (configFile >> "CfgAmmo" >> _ammo >> "simulationStep") call BIS_fnc_getCfgData; + // private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; + // private _subTypesAmmoSimType = _subTypes apply {(configFile >> "CfgAmmo" >> _x >> "simulation") call BIS_fnc_getCfgData}; + // if (count _subTypesAmmoSimType > 0) then { + // _subTypesAmmoSimType = selectRandom(_subTypesAmmoSimType); + // }; + // [ + // { + // _this spawn { + // params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; + // // private _projectile = _EHData # 6; + + // // if submunitions are NOT bullets, wait until the original projectile deploys then search for submunitions and 're-fire' them to appear in playback + // // if !(_subTypesAmmoSimType == "shotBullet") exitWith { + // // if (_magazine isKindOf "VehicleMagazine") then { + // // [_EHData, _projectile, _subTypes] spawn { + // // params ["_EHData", "_projectile", "_subTypes"]; + // // private _ogPos = getPos _firer; + // // while {!isNull _projectile} do {_ogPos = getPosASL _projectile; sleep 0.1;}; + // // isNil { + // // _projSearch = nearestObjects [ASLtoAGL _ogPos, _subTypes, 50, false]; + // // { + // // _EHData set [6, _x]; + // // _EHData spawn FUNC(eh_firedMan); + // // } forEach _projSearch; + // // }; + // // }; + // // }; + + // // if submunitions ARE bullets, process normally and just look for one item + // _EHData params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; + // private _projectile = objNull; + // // while {isNull _projectile} do { + // // { + // // _projSearch = nearestObject [_firer, _x]; + // // if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + // // } forEach _subTypes; + // // }; + + // while {isNull _projectile} do { + // { + // _projSearch = nearestObject [_firer, _x]; + // if !(isNull _projSearch) exitWith {_projectile = _projSearch}; + // } forEach _subTypes; + // }; + // _newSubs = nearestObjects [_projectile, _subTypes, 50]; + + + // isNil { + // { + // private _projectile = _x; + + // // get marker details based on original EH data + // ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; + // private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); + + // // then get data of submunition to determine how to track it + // private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); + // switch (true) do { + // case (_ammoSimType isEqualTo "shotBullet"): { + // LOGBULLET; + // }; + // case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { + // LOGMISSILE; + + // // create our marker record in the timeline + // [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + // if (GVARMAIN(isDebug)) then { + // // add to clients' map draw array + // private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + // [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + // }; + // }; + // case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { + // LOGGRENADE; + + // // create our marker record in the timeline + // [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + // if (GVARMAIN(isDebug)) then { + // // add to map draw array + // private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + // [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + // }; + // }; + // default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; + // }; + // } forEach _newSubs; + // nil; + // }; + // }; + // }, + // [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType], + // _simDelay + // ] call CBA_fnc_waitAndExecute; }; }; From c5e6ac29c67c805e297498d963d9f0ca24aad625 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Thu, 29 Sep 2022 13:52:18 -0400 Subject: [PATCH 16/26] change addon back to OCAP instead of OCAP2 --- x/{ocap2 => ocap}/.editorconfig | 0 x/{ocap2 => ocap}/.gitignore | 0 .../addons/extension/XEH_postInit.sqf | 0 .../addons/extension/XEH_preInit.sqf | 0 .../addons/extension/XEH_prep.sqf | 0 x/{ocap2 => ocap}/addons/extension/config.cpp | 2 +- .../addons/extension/fnc_sendData.sqf | 0 .../addons/extension/script_component.hpp | 2 +- .../addons/main/XEH_postInit.sqf | 0 x/{ocap2 => ocap}/addons/main/XEH_preInit.sqf | 2 +- x/{ocap2 => ocap}/addons/main/XEH_prep.sqf | 0 x/{ocap2 => ocap}/addons/main/config.cpp | 0 .../addons/main/script_component.hpp | 0 .../addons/main/script_macros.hpp | 4 ++-- .../addons/recorder/XEH_postInit.sqf | 0 .../addons/recorder/XEH_preInit.sqf | 0 .../addons/recorder/XEH_prep.sqf | 0 x/{ocap2 => ocap}/addons/recorder/config.cpp | 2 +- .../addons/recorder/fnc_aceExplosives.sqf | 0 .../addons/recorder/fnc_aceThrowing.sqf | 0 .../addons/recorder/fnc_addEventMission.sqf | 12 +++++----- .../recorder/fnc_addUnitEventHandlers.sqf | 0 .../addons/recorder/fnc_adminUIcontrol.sqf | 0 .../addons/recorder/fnc_captureLoop.sqf | 2 +- .../addons/recorder/fnc_eh_connected.sqf | 0 .../addons/recorder/fnc_eh_disconnected.sqf | 0 .../addons/recorder/fnc_eh_firedMan.sqf | 0 .../addons/recorder/fnc_eh_hit.sqf | 0 .../addons/recorder/fnc_eh_killed.sqf | 0 .../fnc_eh_onUserAdminStateChanged.sqf | 2 +- .../addons/recorder/fnc_entityMonitors.sqf | 0 .../addons/recorder/fnc_exportData.sqf | 22 +++++++++---------- .../addons/recorder/fnc_getAmmoMarkerData.sqf | 0 .../addons/recorder/fnc_getClass.sqf | 0 .../addons/recorder/fnc_getDelay.sqf | 0 .../recorder/fnc_getEventWeaponText.sqf | 0 .../addons/recorder/fnc_getInstigator.sqf | 0 .../addons/recorder/fnc_getUnitType.sqf | 0 .../recorder/fnc_getWeaponDisplayData.sqf | 0 .../addons/recorder/fnc_handleCustomEvent.sqf | 2 +- .../addons/recorder/fnc_handleMarkers.sqf | 2 +- .../addons/recorder/fnc_init.sqf | 14 ++++++------ .../addons/recorder/fnc_isKindOfApc.sqf | 0 .../recorder/fnc_projectileMonitors.sqf | 0 .../addons/recorder/fnc_startRecording.sqf | 12 +++++----- .../addons/recorder/fnc_stopRecording.sqf | 8 +++---- .../addons/recorder/fnc_updateTime.sqf | 0 .../addons/recorder/script_component.hpp | 2 +- 48 files changed, 45 insertions(+), 45 deletions(-) rename x/{ocap2 => ocap}/.editorconfig (100%) rename x/{ocap2 => ocap}/.gitignore (100%) rename x/{ocap2 => ocap}/addons/extension/XEH_postInit.sqf (100%) rename x/{ocap2 => ocap}/addons/extension/XEH_preInit.sqf (100%) rename x/{ocap2 => ocap}/addons/extension/XEH_prep.sqf (100%) rename x/{ocap2 => ocap}/addons/extension/config.cpp (98%) rename x/{ocap2 => ocap}/addons/extension/fnc_sendData.sqf (100%) rename x/{ocap2 => ocap}/addons/extension/script_component.hpp (57%) rename x/{ocap2 => ocap}/addons/main/XEH_postInit.sqf (100%) rename x/{ocap2 => ocap}/addons/main/XEH_preInit.sqf (94%) rename x/{ocap2 => ocap}/addons/main/XEH_prep.sqf (100%) rename x/{ocap2 => ocap}/addons/main/config.cpp (100%) rename x/{ocap2 => ocap}/addons/main/script_component.hpp (100%) rename x/{ocap2 => ocap}/addons/main/script_macros.hpp (96%) rename x/{ocap2 => ocap}/addons/recorder/XEH_postInit.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/XEH_preInit.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/XEH_prep.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/config.cpp (96%) rename x/{ocap2 => ocap}/addons/recorder/fnc_aceExplosives.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_aceThrowing.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_addEventMission.sqf (94%) rename x/{ocap2 => ocap}/addons/recorder/fnc_addUnitEventHandlers.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_adminUIcontrol.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_captureLoop.sqf (99%) rename x/{ocap2 => ocap}/addons/recorder/fnc_eh_connected.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_eh_disconnected.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_eh_firedMan.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_eh_hit.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_eh_killed.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf (79%) rename x/{ocap2 => ocap}/addons/recorder/fnc_entityMonitors.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_exportData.sqf (86%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getAmmoMarkerData.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getClass.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getDelay.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getEventWeaponText.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getInstigator.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getUnitType.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_getWeaponDisplayData.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_handleCustomEvent.sqf (96%) rename x/{ocap2 => ocap}/addons/recorder/fnc_handleMarkers.sqf (99%) rename x/{ocap2 => ocap}/addons/recorder/fnc_init.sqf (89%) rename x/{ocap2 => ocap}/addons/recorder/fnc_isKindOfApc.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_projectileMonitors.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/fnc_startRecording.sqf (80%) rename x/{ocap2 => ocap}/addons/recorder/fnc_stopRecording.sqf (74%) rename x/{ocap2 => ocap}/addons/recorder/fnc_updateTime.sqf (100%) rename x/{ocap2 => ocap}/addons/recorder/script_component.hpp (56%) diff --git a/x/ocap2/.editorconfig b/x/ocap/.editorconfig similarity index 100% rename from x/ocap2/.editorconfig rename to x/ocap/.editorconfig diff --git a/x/ocap2/.gitignore b/x/ocap/.gitignore similarity index 100% rename from x/ocap2/.gitignore rename to x/ocap/.gitignore diff --git a/x/ocap2/addons/extension/XEH_postInit.sqf b/x/ocap/addons/extension/XEH_postInit.sqf similarity index 100% rename from x/ocap2/addons/extension/XEH_postInit.sqf rename to x/ocap/addons/extension/XEH_postInit.sqf diff --git a/x/ocap2/addons/extension/XEH_preInit.sqf b/x/ocap/addons/extension/XEH_preInit.sqf similarity index 100% rename from x/ocap2/addons/extension/XEH_preInit.sqf rename to x/ocap/addons/extension/XEH_preInit.sqf diff --git a/x/ocap2/addons/extension/XEH_prep.sqf b/x/ocap/addons/extension/XEH_prep.sqf similarity index 100% rename from x/ocap2/addons/extension/XEH_prep.sqf rename to x/ocap/addons/extension/XEH_prep.sqf diff --git a/x/ocap2/addons/extension/config.cpp b/x/ocap/addons/extension/config.cpp similarity index 98% rename from x/ocap2/addons/extension/config.cpp rename to x/ocap/addons/extension/config.cpp index 9edd0f1..bab61ad 100644 --- a/x/ocap2/addons/extension/config.cpp +++ b/x/ocap/addons/extension/config.cpp @@ -11,7 +11,7 @@ class CfgPatches authors[] = {"Dell", "Zealot", "Kurt", "IndigoFox", "Fank"}; url = "https://github.com/OCAP2/OCAP"; VERSION_CONFIG; - requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh","ocap2_main"}; + requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh","ocap_main"}; units[] = {}; weapons[] = {}; }; diff --git a/x/ocap2/addons/extension/fnc_sendData.sqf b/x/ocap/addons/extension/fnc_sendData.sqf similarity index 100% rename from x/ocap2/addons/extension/fnc_sendData.sqf rename to x/ocap/addons/extension/fnc_sendData.sqf diff --git a/x/ocap2/addons/extension/script_component.hpp b/x/ocap/addons/extension/script_component.hpp similarity index 57% rename from x/ocap2/addons/extension/script_component.hpp rename to x/ocap/addons/extension/script_component.hpp index 5220b67..755d233 100644 --- a/x/ocap2/addons/extension/script_component.hpp +++ b/x/ocap/addons/extension/script_component.hpp @@ -1,4 +1,4 @@ #define COMPONENT extension #define COMPONENT_BEAUTIFIED Extension -#include "\x\ocap2\addons\main\script_macros.hpp" +#include "\x\ocap\addons\main\script_macros.hpp" diff --git a/x/ocap2/addons/main/XEH_postInit.sqf b/x/ocap/addons/main/XEH_postInit.sqf similarity index 100% rename from x/ocap2/addons/main/XEH_postInit.sqf rename to x/ocap/addons/main/XEH_postInit.sqf diff --git a/x/ocap2/addons/main/XEH_preInit.sqf b/x/ocap/addons/main/XEH_preInit.sqf similarity index 94% rename from x/ocap2/addons/main/XEH_preInit.sqf rename to x/ocap/addons/main/XEH_preInit.sqf index 79152c7..4655732 100644 --- a/x/ocap2/addons/main/XEH_preInit.sqf +++ b/x/ocap/addons/main/XEH_preInit.sqf @@ -39,7 +39,7 @@ GVAR(allSettings) = [ "EDITBOX", // setting type [ "Administrators", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "An array or server-visible variable referencing one that is a list of playerUIDs. Additional briefing diary or UI elements may be available for more accessible control over OCAP2's features. Takes effect on player server connection. Format: [] OR myAdminPUIDs | Default: []" + "An array or server-visible variable referencing one that is a list of playerUIDs. Additional briefing diary or UI elements may be available for more accessible control over OCAP's features. Takes effect on player server connection. Format: [] OR myAdminPUIDs | Default: []" ], [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. "[]", // default enabled diff --git a/x/ocap2/addons/main/XEH_prep.sqf b/x/ocap/addons/main/XEH_prep.sqf similarity index 100% rename from x/ocap2/addons/main/XEH_prep.sqf rename to x/ocap/addons/main/XEH_prep.sqf diff --git a/x/ocap2/addons/main/config.cpp b/x/ocap/addons/main/config.cpp similarity index 100% rename from x/ocap2/addons/main/config.cpp rename to x/ocap/addons/main/config.cpp diff --git a/x/ocap2/addons/main/script_component.hpp b/x/ocap/addons/main/script_component.hpp similarity index 100% rename from x/ocap2/addons/main/script_component.hpp rename to x/ocap/addons/main/script_component.hpp diff --git a/x/ocap2/addons/main/script_macros.hpp b/x/ocap/addons/main/script_macros.hpp similarity index 96% rename from x/ocap2/addons/main/script_macros.hpp rename to x/ocap/addons/main/script_macros.hpp index f931fe6..46e1e5b 100644 --- a/x/ocap2/addons/main/script_macros.hpp +++ b/x/ocap/addons/main/script_macros.hpp @@ -1,7 +1,7 @@ // Header: script_macros.hpp // Defines macros imported to other functions -#define PREFIX OCAP2 +#define PREFIX OCAP #ifdef COMPONENT_BEAUTIFIED #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT_BEAUTIFIED) @@ -9,7 +9,7 @@ #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT) #endif -// The current version of OCAP2. +// The current version of OCAP. #define VERSION 2.0 #define VERSION_STR 2.0.0-RC2 #define VERSION_AR 2,0,0 diff --git a/x/ocap2/addons/recorder/XEH_postInit.sqf b/x/ocap/addons/recorder/XEH_postInit.sqf similarity index 100% rename from x/ocap2/addons/recorder/XEH_postInit.sqf rename to x/ocap/addons/recorder/XEH_postInit.sqf diff --git a/x/ocap2/addons/recorder/XEH_preInit.sqf b/x/ocap/addons/recorder/XEH_preInit.sqf similarity index 100% rename from x/ocap2/addons/recorder/XEH_preInit.sqf rename to x/ocap/addons/recorder/XEH_preInit.sqf diff --git a/x/ocap2/addons/recorder/XEH_prep.sqf b/x/ocap/addons/recorder/XEH_prep.sqf similarity index 100% rename from x/ocap2/addons/recorder/XEH_prep.sqf rename to x/ocap/addons/recorder/XEH_prep.sqf diff --git a/x/ocap2/addons/recorder/config.cpp b/x/ocap/addons/recorder/config.cpp similarity index 96% rename from x/ocap2/addons/recorder/config.cpp rename to x/ocap/addons/recorder/config.cpp index b43487e..dffc8b3 100644 --- a/x/ocap2/addons/recorder/config.cpp +++ b/x/ocap/addons/recorder/config.cpp @@ -11,7 +11,7 @@ class CfgPatches authors[] = {"Dell", "Zealot", "Kurt", "IndigoFox", "Fank"}; url = "https://github.com/OCAP2/OCAP"; VERSION_CONFIG; - requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh","ocap2_main","ocap2_extension"}; + requiredAddons[] = {"A3_Functions_F","cba_main","cba_xeh","ocap_main","ocap_extension"}; units[] = {}; weapons[] = {}; }; diff --git a/x/ocap2/addons/recorder/fnc_aceExplosives.sqf b/x/ocap/addons/recorder/fnc_aceExplosives.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_aceExplosives.sqf rename to x/ocap/addons/recorder/fnc_aceExplosives.sqf diff --git a/x/ocap2/addons/recorder/fnc_aceThrowing.sqf b/x/ocap/addons/recorder/fnc_aceThrowing.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_aceThrowing.sqf rename to x/ocap/addons/recorder/fnc_aceThrowing.sqf diff --git a/x/ocap2/addons/recorder/fnc_addEventMission.sqf b/x/ocap/addons/recorder/fnc_addEventMission.sqf similarity index 94% rename from x/ocap2/addons/recorder/fnc_addEventMission.sqf rename to x/ocap/addons/recorder/fnc_addEventMission.sqf index 4dcfcae..65ea5d5 100644 --- a/x/ocap2/addons/recorder/fnc_addEventMission.sqf +++ b/x/ocap/addons/recorder/fnc_addEventMission.sqf @@ -4,7 +4,7 @@ Script: ocap_fnc_addEventMission Description: Used for applying mission event handlers. - * Applied during initialization of OCAP2 in . + * Applied during initialization of OCAP in . Parameters: None @@ -110,7 +110,7 @@ if (isNil QEGVAR(listener,markers)) then { call FUNC(handleMarkers); }; -// Custom event handler with key "ocap2_customEvent" +// Custom event handler with key "ocap_customEvent" // Used for showing custom events in playback events list if (isNil QEGVAR(listener,customEvent)) then { EGVAR(listener,customEvent) = [QGVARMAIN(customEvent), { @@ -119,7 +119,7 @@ if (isNil QEGVAR(listener,customEvent)) then { OCAPEXTLOG(["Initialized customEvent listener"]); }; -// Custom event handler with key "ocap2_counterInit" +// Custom event handler with key "ocap_counterInit" // Used for tracking scores or counts per side if (isNil QEGVAR(listener,counterInit)) then { EGVAR(listener,counterInit) = [QGVARMAIN(counterInit), { @@ -147,7 +147,7 @@ if (isNil QEGVAR(listener,counterEvent)) then { OCAPEXTLOG(["Initialized counterEvent listener"]); }; -// Custom event handler with key "ocap2_record" +// Custom event handler with key "ocap_record" // This will START OR RESUME recording if not already. if (isNil QEGVAR(listener,record)) then { EGVAR(listener,record) = [QGVARMAIN(record), { @@ -156,7 +156,7 @@ if (isNil QEGVAR(listener,record)) then { OCAPEXTLOG(["Initialized record listener"]); }; -// Custom event handler with key "ocap2_pause" +// Custom event handler with key "ocap_pause" // This will PAUSE recording if (isNil QEGVAR(listener,pause)) then { EGVAR(listener,pause) = [QGVARMAIN(pause), { @@ -165,7 +165,7 @@ if (isNil QEGVAR(listener,pause)) then { OCAPEXTLOG(["Initialized pause listener"]); }; -// Custom event handler with key "ocap2_exportData" +// Custom event handler with key "ocap_exportData" // This will export the mission immediately regardless of restrictions. // params ["_side", "_message", "_tag"]; if (isNil QEGVAR(listener,exportData)) then { diff --git a/x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_addUnitEventHandlers.sqf rename to x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf diff --git a/x/ocap2/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_adminUIcontrol.sqf rename to x/ocap/addons/recorder/fnc_adminUIcontrol.sqf diff --git a/x/ocap2/addons/recorder/fnc_captureLoop.sqf b/x/ocap/addons/recorder/fnc_captureLoop.sqf similarity index 99% rename from x/ocap2/addons/recorder/fnc_captureLoop.sqf rename to x/ocap/addons/recorder/fnc_captureLoop.sqf index 7447414..d1e8dea 100644 --- a/x/ocap2/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap/addons/recorder/fnc_captureLoop.sqf @@ -60,7 +60,7 @@ GVAR(PFHObject) = [ publicVariable QGVAR(captureFrameNo); { player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "Status", ("Capture frame: " + str (missionNamespace getVariable [QGVAR(captureFrameNo), "[not yet received]"]) + "") diff --git a/x/ocap2/addons/recorder/fnc_eh_connected.sqf b/x/ocap/addons/recorder/fnc_eh_connected.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_eh_connected.sqf rename to x/ocap/addons/recorder/fnc_eh_connected.sqf diff --git a/x/ocap2/addons/recorder/fnc_eh_disconnected.sqf b/x/ocap/addons/recorder/fnc_eh_disconnected.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_eh_disconnected.sqf rename to x/ocap/addons/recorder/fnc_eh_disconnected.sqf diff --git a/x/ocap2/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_eh_firedMan.sqf rename to x/ocap/addons/recorder/fnc_eh_firedMan.sqf diff --git a/x/ocap2/addons/recorder/fnc_eh_hit.sqf b/x/ocap/addons/recorder/fnc_eh_hit.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_eh_hit.sqf rename to x/ocap/addons/recorder/fnc_eh_hit.sqf diff --git a/x/ocap2/addons/recorder/fnc_eh_killed.sqf b/x/ocap/addons/recorder/fnc_eh_killed.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_eh_killed.sqf rename to x/ocap/addons/recorder/fnc_eh_killed.sqf diff --git a/x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf similarity index 79% rename from x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf rename to x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf index 1d90c8f..14b8589 100644 --- a/x/ocap2/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf @@ -3,6 +3,6 @@ params ["_networkId", "_loggedIn", "_votedIn"]; _object = (getUserInfo _networkId) select 10; if (isNull _object) exitWith {}; if (_loggedIn && !_votedIn && !(_object getVariable [QGVARMAIN(hasAdminControls), true])) then { - // if user has become admin by logging, not voting, and has not yet received adminControls per OCAP2 - Main > Administrators setting, add controls + // if user has become admin by logging, not voting, and has not yet received adminControls per OCAP - Main > Administrators setting, add controls [_id] call FUNC(adminUIcontrol); }; diff --git a/x/ocap2/addons/recorder/fnc_entityMonitors.sqf b/x/ocap/addons/recorder/fnc_entityMonitors.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_entityMonitors.sqf rename to x/ocap/addons/recorder/fnc_entityMonitors.sqf diff --git a/x/ocap2/addons/recorder/fnc_exportData.sqf b/x/ocap/addons/recorder/fnc_exportData.sqf similarity index 86% rename from x/ocap2/addons/recorder/fnc_exportData.sqf rename to x/ocap/addons/recorder/fnc_exportData.sqf index 1b86338..4d9e81b 100644 --- a/x/ocap2/addons/recorder/fnc_exportData.sqf +++ b/x/ocap/addons/recorder/fnc_exportData.sqf @@ -49,14 +49,14 @@ if (isNil QGVAR(startTime)) exitWith { { [{!isNull player}, { player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "Status", - "OCAP2 was asked to save, but recording hasn't started yet." + "OCAP was asked to save, but recording hasn't started yet." ], taskNull, "", false ]; player setDiarySubjectPicture [ - "OCAP2Info", + "OCAPInfo", "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" ]; }] call CBA_fnc_waitUntilAndExecute; @@ -74,19 +74,19 @@ if (_frameTimeDuration < GVAR(minMissionTime) && !_overrideLimits) exitWith { // then we won't save, but will continue recording in case admins want to save once that threshold is met. // allow this restriction to be overriden LOG("Save attempted, but the minimum recording duration hasn't been met. Not saving, continuing to record."); - ["OCAP2 attempted to save, but the minimum recording duration hasn't been met. Recording will continue.", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; + ["OCAP attempted to save, but the minimum recording duration hasn't been met. Recording will continue.", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; { player createDiaryRecord [ - "OCAP2Info", + "OCA2Info", [ "Status", ( - "OCAP2 capture of " + briefingName + " has not yet reached the minimum duration to save it. Recording will continue." + "OCAP capture of " + briefingName + " has not yet reached the minimum duration to save it. Recording will continue." ) ] ]; player setDiarySubjectPicture [ - "OCAP2Info", + "OCAPInfo", "\A3\ui_f\data\igui\cfg\simpleTasks\types\danger_ca.paa" ]; } remoteExec ["call", 0, false]; @@ -138,21 +138,21 @@ if (!isNil "_tag") then { }; // briefingName is used here, no need for publicVariable for a simple confirmation log. -[format["OCAP2 saved %1 frames successfully", _endFrameNumber], 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; +[format["OCAP saved %1 frames successfully", _endFrameNumber], 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; { player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "Status", ( - "OCAP2 capture of " + briefingName + " has been exported with " + str(GVAR(endFrameNumber)) + " frames saved." + + "OCAP capture of " + briefingName + " has been exported with " + str(GVAR(endFrameNumber)) + " frames saved." + "

" + "Upload results have been logged." ) ] ]; player setDiarySubjectPicture [ - "OCAP2Info", + "OCAPInfo", "\A3\ui_f\data\igui\cfg\simpleTasks\types\upload_ca.paa" ]; } remoteExec ["call", 0, false]; diff --git a/x/ocap2/addons/recorder/fnc_getAmmoMarkerData.sqf b/x/ocap/addons/recorder/fnc_getAmmoMarkerData.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getAmmoMarkerData.sqf rename to x/ocap/addons/recorder/fnc_getAmmoMarkerData.sqf diff --git a/x/ocap2/addons/recorder/fnc_getClass.sqf b/x/ocap/addons/recorder/fnc_getClass.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getClass.sqf rename to x/ocap/addons/recorder/fnc_getClass.sqf diff --git a/x/ocap2/addons/recorder/fnc_getDelay.sqf b/x/ocap/addons/recorder/fnc_getDelay.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getDelay.sqf rename to x/ocap/addons/recorder/fnc_getDelay.sqf diff --git a/x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getEventWeaponText.sqf rename to x/ocap/addons/recorder/fnc_getEventWeaponText.sqf diff --git a/x/ocap2/addons/recorder/fnc_getInstigator.sqf b/x/ocap/addons/recorder/fnc_getInstigator.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getInstigator.sqf rename to x/ocap/addons/recorder/fnc_getInstigator.sqf diff --git a/x/ocap2/addons/recorder/fnc_getUnitType.sqf b/x/ocap/addons/recorder/fnc_getUnitType.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getUnitType.sqf rename to x/ocap/addons/recorder/fnc_getUnitType.sqf diff --git a/x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf b/x/ocap/addons/recorder/fnc_getWeaponDisplayData.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_getWeaponDisplayData.sqf rename to x/ocap/addons/recorder/fnc_getWeaponDisplayData.sqf diff --git a/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf b/x/ocap/addons/recorder/fnc_handleCustomEvent.sqf similarity index 96% rename from x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf rename to x/ocap/addons/recorder/fnc_handleCustomEvent.sqf index 278231e..9824619 100644 --- a/x/ocap2/addons/recorder/fnc_handleCustomEvent.sqf +++ b/x/ocap/addons/recorder/fnc_handleCustomEvent.sqf @@ -4,7 +4,7 @@ Script: FUNC(handleCustomEvent) Description: Used for applying global event handlers. - * Applied during initialization of OCAP2 in . + * Applied during initialization of OCAP in . Parameters: _type - objective type that will define the text & icon [String, one of: "flag"] diff --git a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf b/x/ocap/addons/recorder/fnc_handleMarkers.sqf similarity index 99% rename from x/ocap2/addons/recorder/fnc_handleMarkers.sqf rename to x/ocap/addons/recorder/fnc_handleMarkers.sqf index 3718cfc..0716c94 100644 --- a/x/ocap2/addons/recorder/fnc_handleMarkers.sqf +++ b/x/ocap/addons/recorder/fnc_handleMarkers.sqf @@ -40,7 +40,7 @@ GVAR(trackedMarkers) = []; // Markers which we saves into replay -// create CBA event handler to be called on server with key "ocap2_handleMarker" +// create CBA event handler to be called on server with key "ocap_handleMarker" EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { if (!SHOULDSAVEEVENTS) exitWith {}; diff --git a/x/ocap2/addons/recorder/fnc_init.sqf b/x/ocap/addons/recorder/fnc_init.sqf similarity index 89% rename from x/ocap2/addons/recorder/fnc_init.sqf rename to x/ocap/addons/recorder/fnc_init.sqf index 0ff6935..3d4c5e5 100644 --- a/x/ocap2/addons/recorder/fnc_init.sqf +++ b/x/ocap/addons/recorder/fnc_init.sqf @@ -64,11 +64,11 @@ call FUNC(addEventMission); // remoteExec diary creation commands to clients listing version numbers and waiting start state { [{!isNil QGVARMAIN(version) && !isNil QEGVAR(extension,version)}, { - player createDiarySubject ["OCAP2Info", "OCAP2 AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; + player createDiarySubject ["OCAPInfo", "OCAP AAR", "\A3\ui_f\data\igui\cfg\simpleTasks\types\whiteboard_ca.paa"]; - ocap2_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP2 GitHub link copied to clipboard";}; + ocap_fnc_copyGitHubToClipboard = {copyToClipboard "https://github.com/OCAP2/OCAP"; systemChat "OCAP GitHub link copied to clipboard";}; EGVAR(diary,about) = player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "About", ( @@ -77,9 +77,9 @@ call FUNC(addEventMission); "
" + "Extension version: " + (EGVAR(extension,version) # 0) + " (built " + (EGVAR(extension,version) # 1) + ")" + "
" + - "https://github.com/OCAP2/OCAP" + + "https://github.com/OCAP2/OCAP" + "

" + - "OCAP2 is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + + "OCAP is a server-side Arma 3 recording suite that provides web-based playback of all units, vehicles, markers, and projectiles present, placed, and fired during a mission." + "

" + "Recording status can be found in the Status section." + "

" + @@ -89,10 +89,10 @@ call FUNC(addEventMission); ]; EGVAR(diary,status) = player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "Status", - "OCAP2 initialized." + "OCAP initialized." ] ]; }] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/fnc_isKindOfApc.sqf b/x/ocap/addons/recorder/fnc_isKindOfApc.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_isKindOfApc.sqf rename to x/ocap/addons/recorder/fnc_isKindOfApc.sqf diff --git a/x/ocap2/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_projectileMonitors.sqf rename to x/ocap/addons/recorder/fnc_projectileMonitors.sqf diff --git a/x/ocap2/addons/recorder/fnc_startRecording.sqf b/x/ocap/addons/recorder/fnc_startRecording.sqf similarity index 80% rename from x/ocap2/addons/recorder/fnc_startRecording.sqf rename to x/ocap/addons/recorder/fnc_startRecording.sqf index d1f1e01..d96689a 100644 --- a/x/ocap2/addons/recorder/fnc_startRecording.sqf +++ b/x/ocap/addons/recorder/fnc_startRecording.sqf @@ -10,9 +10,9 @@ if (!GVARMAIN(enabled)) exitWith {}; // if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. if (GVAR(recording)) exitWith { - OCAPEXTLOG(["OCAP2 was asked to record and is already recording!"]); + OCAPEXTLOG(["OCAP was asked to record and is already recording!"]); [ - ["OCAP2 was asked to record", 1, [1, 1, 1, 1]], + ["OCAP was asked to record", 1, [1, 1, 1, 1]], ["and is already recording", 1, [1, 1, 1, 1]] ] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; }; @@ -26,19 +26,19 @@ private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); [QGVARMAIN(customEvent), ["generalEvent", "Recording started."]] call CBA_fnc_serverEvent; -["OCAP2 began recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; +["OCAP began recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; [[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording start [{!isNull player}, { player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "Status", - format["OCAP2 started recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] + format["OCAP started recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] ], taskNull, "", false ]; player setDiarySubjectPicture [ - "OCAP2Info", + "OCAPInfo", "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" ]; }, _this] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/fnc_stopRecording.sqf b/x/ocap/addons/recorder/fnc_stopRecording.sqf similarity index 74% rename from x/ocap2/addons/recorder/fnc_stopRecording.sqf rename to x/ocap/addons/recorder/fnc_stopRecording.sqf index 907223a..5510f75 100644 --- a/x/ocap2/addons/recorder/fnc_stopRecording.sqf +++ b/x/ocap/addons/recorder/fnc_stopRecording.sqf @@ -6,7 +6,7 @@ private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); [QGVARMAIN(customEvent), ["generalEvent", "Recording paused."]] call CBA_fnc_serverEvent; -["OCAP2 stopped recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; +["OCAP stopped recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; GVAR(recording) = false; publicVariable QGVAR(recording); @@ -14,14 +14,14 @@ publicVariable QGVAR(recording); [[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording pause [{!isNull player}, { player createDiaryRecord [ - "OCAP2Info", + "OCAPInfo", [ "Status", - format["OCAP2 stopped recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] + format["OCAP stopped recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] ], taskNull, "", false ]; player setDiarySubjectPicture [ - "OCAP2Info", + "OCAPInfo", "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" ]; }, _this] call CBA_fnc_waitUntilAndExecute; diff --git a/x/ocap2/addons/recorder/fnc_updateTime.sqf b/x/ocap/addons/recorder/fnc_updateTime.sqf similarity index 100% rename from x/ocap2/addons/recorder/fnc_updateTime.sqf rename to x/ocap/addons/recorder/fnc_updateTime.sqf diff --git a/x/ocap2/addons/recorder/script_component.hpp b/x/ocap/addons/recorder/script_component.hpp similarity index 56% rename from x/ocap2/addons/recorder/script_component.hpp rename to x/ocap/addons/recorder/script_component.hpp index 05bac07..cfcd3a9 100644 --- a/x/ocap2/addons/recorder/script_component.hpp +++ b/x/ocap/addons/recorder/script_component.hpp @@ -1,4 +1,4 @@ #define COMPONENT recorder #define COMPONENT_BEAUTIFIED Recorder -#include "\x\ocap2\addons\main\script_macros.hpp" +#include "\x\ocap\addons\main\script_macros.hpp" From 834a1cba95d2d40ce9aa77143412f85dec8dc3af Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Fri, 30 Sep 2022 11:44:24 -0400 Subject: [PATCH 17/26] utilize new deleted EH for projectiles --- x/ocap/addons/recorder/fnc_eh_firedMan.sqf | 22 ++++++++++++------- .../recorder/fnc_projectileMonitors.sqf | 14 ++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf index 448aa38..2c38829 100644 --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf @@ -100,6 +100,9 @@ switch (true) do { }; if (isNil "_projectile") exitWith {}; + + LOGBULLET; + _projectile addEventHandler ["Deleted", { params ["_projectile"]; [":FIRED:", [ @@ -108,7 +111,6 @@ switch (true) do { _pos ]] call EFUNC(extension, sendData); }; - // LOGBULLET; }; @@ -140,7 +142,7 @@ switch (true) do { }; }; case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { - // LOGGRENADE; + LOGGRENADE; _projectile setVariable [GVAR(ocapdata), [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]]; @@ -163,18 +165,22 @@ switch (true) do { case (_ammoSimType isEqualTo "shotSubmunitions"): { - _projectile setVariable [GVAR(data), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))]; + _projectile setVariable [GVAR(markerData), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))]; + _projectile setVariable [GVAR(EHData), [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType]]; + _projectile addEventHandler ["SubmunitionCreated", { params ["_projectile", "_submunitionProjectile", "_pos", "_velocity"]; - (_projectile getVariable [GVAR(data), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"]; + (_projectile getVariable [GVAR(markerData), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"]; + (_projectile getVariable [GVAR(EHData), []]) params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; + _submunitionProjectile setVariable [QGVAR(firerId), _projectile getVariable [QGVAR(firerId), -1]]; private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); // then get data of submunition to determine how to track it - private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); + private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _submunitionProjectile) >> "simulation"); switch (true) do { @@ -197,7 +203,7 @@ switch (true) do { if (GVARMAIN(isDebug)) then { // add to clients' map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + private _debugArr = [_submunitionProjectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; }; }; @@ -209,12 +215,12 @@ switch (true) do { if (GVARMAIN(isDebug)) then { // add to map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + private _debugArr = [_submunitionProjectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; }; }; default { - OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType)) + OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _submunitionProjectile, _ammoSimType)) }; }; }]; diff --git a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf index 7f6222e..fba6e4a 100644 --- a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf +++ b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf @@ -12,11 +12,11 @@ GVAR(liveBullets) = []; { _x params ["_obj", "_firerId", "_firer", "_pos"]; - [":FIRED:", [ - _firerId, - GVAR(captureFrameNo), - _pos - ]] call EFUNC(extension,sendData); + // [":FIRED:", [ + // _firerId, + // GVAR(captureFrameNo), + // _pos + // ]] call EFUNC(extension,sendData); if (GVARMAIN(isDebug)) then { OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _pos)); @@ -52,7 +52,7 @@ GVAR(liveMissiles) = []; OCAPEXTLOG(ARR4("FIRED EVENT: SHELL-ROCKET-MISSILE", GVAR(captureFrameNo), _firer getVariable QGVARMAIN(id), str _pos)); }; - [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; + // [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; } forEach _processNow; // for missiles that still exist, update positions @@ -86,7 +86,7 @@ GVAR(liveGrenades) = []; OCAPEXTLOG(ARR4("FIRED EVENT: GRENADE-FLARE-SMOKE", GVAR(captureFrameNo), _firer getVariable QGVARMAIN(id), str _pos)); }; - [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; + // [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; } forEach _processNow; // for grenades that still exist, update positions From cb3178e08889fc9dc1d83ff35ae9f0f68f661f54 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Sun, 2 Oct 2022 16:38:19 -0400 Subject: [PATCH 18/26] fix bugs, ready for testing --- x/ocap/addons/recorder/XEH_preInit.sqf | 6 +-- x/ocap/addons/recorder/fnc_adminUIcontrol.sqf | 10 +++-- x/ocap/addons/recorder/fnc_captureLoop.sqf | 16 +++++-- x/ocap/addons/recorder/fnc_eh_firedMan.sqf | 43 +++++++++++++++---- .../fnc_eh_onUserAdminStateChanged.sqf | 2 + x/ocap/addons/recorder/fnc_entityMonitors.sqf | 21 ++++++--- x/ocap/addons/recorder/fnc_init.sqf | 4 +- 7 files changed, 75 insertions(+), 27 deletions(-) diff --git a/x/ocap/addons/recorder/XEH_preInit.sqf b/x/ocap/addons/recorder/XEH_preInit.sqf index 3128f8d..239b793 100644 --- a/x/ocap/addons/recorder/XEH_preInit.sqf +++ b/x/ocap/addons/recorder/XEH_preInit.sqf @@ -83,10 +83,10 @@ GVAR(allSettings) = [ "EDITBOX", // setting type [ "Classnames to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry. - "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']" + "Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'WeaponHolderSimulated']" ], [COMPONENT_NAME, "Exclusions"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - "['ACE_friesAnchorBar', 'GroundWeaponHolder', 'WeaponHolderSimulated']", // default string value + "['ACE_friesAnchorBar']", // default string value true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply @@ -100,7 +100,7 @@ GVAR(allSettings) = [ "Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []" ], [COMPONENT_NAME, "Exclusions"], // Pretty name of the category where the setting can be found. Can be stringtable entry. - "[]", // default string value + "['WeaponHolder']", // default string value true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer {}, // function that will be executed once on mission start and every time the setting is changed. false // requires restart to apply diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf index 7a35918..b865e50 100644 --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf @@ -1,3 +1,5 @@ +#include "script_component.hpp" + params ["_PID"]; if (isNil "_PID") exitWith {}; @@ -23,7 +25,7 @@ if !(_playerUID in _adminUIDs) exitWith {}; if (player getVariable [QGVARMAIN(hasAdminControls), false]) exitWith {}; - QEGVAR(diary,adminControls) = player createDiarySubject [ + EGVAR(diary,adminControls) = player createDiarySubject [ QEGVAR(diary,adminControls_subject), PREFIX + " Admin", "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa" @@ -34,9 +36,9 @@ if !(_playerUID in _adminUIDs) exitWith {}; [ PREFIX + " Controls", "
- Start/Resume Recording
- Pause Recording
- Stop and Export Recording +Start/Resume Recording
+Pause Recording
+Stop and Export Recording " ] ]; diff --git a/x/ocap/addons/recorder/fnc_captureLoop.sqf b/x/ocap/addons/recorder/fnc_captureLoop.sqf index d1e8dea..e90eb0a 100644 --- a/x/ocap/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap/addons/recorder/fnc_captureLoop.sqf @@ -71,7 +71,9 @@ GVAR(PFHObject) = [ { if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { - if (_x isKindOf "Logic") exitWith { + if ( + _x isKindOf "Logic" + ) exitWith { _x setVariable [QGVARMAIN(exclude), true, true]; _x setVariable [QGVARMAIN(isInitialized), true, true]; }; @@ -130,7 +132,7 @@ GVAR(PFHObject) = [ if !(_x getVariable [QGVARMAIN(isInitialized), false]) then { _vehType = typeOf _x; _class = _vehType call FUNC(getClass); - _toExcludeKind = false; + private _toExcludeKind = false; if (count (parseSimpleArray EGVAR(settings,excludeKindFromRecord)) > 0) then { private _vic = _x; { @@ -139,7 +141,15 @@ GVAR(PFHObject) = [ }; } forEach (parseSimpleArray EGVAR(settings,excludeKindFromRecord)); }; - if ((_class isEqualTo "unknown") || _toExcludeKind) exitWith { + private _toExcludeClass = false; + if (count (parseSimpleArray EGVAR(settings,excludeClassFromRecord)) > 0) then { + { + if (typeOf _vic == _x) exitWith { + _toExcludeClass = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeClassFromRecord)); + }; + if ((_class isEqualTo "unknown") || _toExcludeKind || _toExcludeClass) exitWith { LOG(ARR2("WARNING: vehicle is defined as 'unknown' or exclude:", _vehType)); _x setVariable [QGVARMAIN(isInitialized), true, true]; _x setVariable [QGVARMAIN(exclude), true, true]; diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf index 2c38829..a74870f 100644 --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf @@ -108,9 +108,9 @@ switch (true) do { [":FIRED:", [ _projectile getVariable [QGVAR(firerId), -1], GVAR(captureFrameNo), - _pos - ]] call EFUNC(extension, sendData); - }; + getPosASL _projectile + ]] call EFUNC(extension,sendData); + }]; }; @@ -135,6 +135,16 @@ switch (true) do { case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { LOGMISSILE; + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + _projectile setVariable [QGVAR(markName), _markName]; + + _projectile addEventHandler ["Deleted", { + params ["_projectile"]; + [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; + }]; + if (GVARMAIN(isDebug)) then { // add to map draw array private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; @@ -144,11 +154,14 @@ switch (true) do { case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { LOGGRENADE; - _projectile setVariable [GVAR(ocapdata), [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]]; + // create our marker record in the timeline + [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + + _projectile setVariable [QGVAR(markName), _markName]; _projectile addEventHandler ["Deleted", { params ["_projectile"]; - [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable [GVAR(ocapdata)]]] call CBA_fnc_localEvent; + [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; }]; if (GVARMAIN(isDebug)) then { @@ -191,9 +204,9 @@ switch (true) do { [":FIRED:", [ _projectile getVariable [QGVAR(firerId), -1], GVAR(captureFrameNo), - _pos - ]] call EFUNC(extension, sendData); - }; + getPosASL _projectile + ]] call EFUNC(extension,sendData); + }]; }; case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { LOGMISSILE; @@ -201,6 +214,13 @@ switch (true) do { // create our marker record in the timeline [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + _projectile setVariable [QGVAR(markName), _markName]; + + _projectile addEventHandler ["Deleted", { + params ["_projectile"]; + [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; + }]; + if (GVARMAIN(isDebug)) then { // add to clients' map draw array private _debugArr = [_submunitionProjectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; @@ -213,6 +233,13 @@ switch (true) do { // create our marker record in the timeline [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + _projectile setVariable [QGVAR(markName), _markName]; + + _projectile addEventHandler ["Deleted", { + params ["_projectile"]; + [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; + }]; + if (GVARMAIN(isDebug)) then { // add to map draw array private _debugArr = [_submunitionProjectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; diff --git a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf index 14b8589..e229b9f 100644 --- a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf @@ -1,3 +1,5 @@ +#include "script_component.hpp" + params ["_networkId", "_loggedIn", "_votedIn"]; _object = (getUserInfo _networkId) select 10; diff --git a/x/ocap/addons/recorder/fnc_entityMonitors.sqf b/x/ocap/addons/recorder/fnc_entityMonitors.sqf index d8665e4..95e47a7 100644 --- a/x/ocap/addons/recorder/fnc_entityMonitors.sqf +++ b/x/ocap/addons/recorder/fnc_entityMonitors.sqf @@ -11,25 +11,32 @@ _sizeInMeters = 2.5; _iconSize = (_sizeInMeters * 0.15) * 10^(abs log (ctrlMapScale (_this#0))); { - // _x params ["_startPos", "_endPos", "_color", "_timeHit"]; - if (!alive _x || isNull _x) then {continue}; (_this#0) drawIcon [ - getText(configFile >> "CfgVehicleIcons" >> (getText((configOf _x) >> "icon"))), // Custom images can also be used: getMissionPath "\myFolder\myIcon.paa" + (_x call { + if (_this call CBA_fnc_isPerson) then { + getText(configFile >> "CfgVehicleIcons" >> (getText((configOf _this) >> "icon"))) + } else { + getText((configOf _this) >> "icon") + }; + }), [side group _x] call BIS_fnc_sideColor, getPos _x, _iconSize, _iconSize, getDir _x, - name _x, + (_x call { + if (_this call CBA_fnc_isPerson) then {name _this} else {getText(configOf _this >> "displayName")} + }), 1, 0.02, "TahomaB", "center" ]; - } forEach (allUnits + vehicles) select { + } forEach ((allUnits + vehicles) select { _x getVariable [QGVARMAIN(isInitialized), false] && - _x getVariable [QGVARMAIN(exclude), true] - }; + !(_x getVariable [QGVARMAIN(exclude), false]) && + not (!alive _x || isNull _x || objectParent _x isNotEqualTo objNull) + }); }; }]; }; diff --git a/x/ocap/addons/recorder/fnc_init.sqf b/x/ocap/addons/recorder/fnc_init.sqf index 3d4c5e5..d96806b 100644 --- a/x/ocap/addons/recorder/fnc_init.sqf +++ b/x/ocap/addons/recorder/fnc_init.sqf @@ -140,7 +140,7 @@ if (GVAR(missionName) == "") then { [ {(getClientStateNumber > 9 && (count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, { - call FUNC(startRecording) + call FUNC(startRecording); [QGVARMAIN(customEvent), ["generalEvent", "Mission has started!"]] call CBA_fnc_serverEvent; } ] call CBA_fnc_waitUntilAndExecute; @@ -149,7 +149,7 @@ if (GVAR(missionName) == "") then { // If a recording has been started, exceeds min mission time, and no players are on the server, auto-save [{ if ( - QEGVAR(settings,saveOnEmpty) && + EGVAR(settings,saveOnEmpty) && !isNil QGVAR(startTime) && (GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime) && count (call CBA_fnc_players) == 0 ) then { [nil, "Recording ended due to server being empty"] call FUNC(exportData); From fabd8a251caf07fdb71dafcd9c24833b5eac8acc Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Sun, 9 Oct 2022 15:11:13 -0400 Subject: [PATCH 19/26] fix projectile and submunitions rework, admin controls --- x/ocap/addons/recorder/XEH_prep.sqf | 3 +- .../recorder/fnc_addUnitEventHandlers.sqf | 67 ---- x/ocap/addons/recorder/fnc_adminUIcontrol.sqf | 12 +- x/ocap/addons/recorder/fnc_eh_connected.sqf | 2 +- x/ocap/addons/recorder/fnc_eh_firedMan.sqf | 364 +++++++----------- x/ocap/addons/recorder/fnc_eh_hit.sqf | 82 ---- .../fnc_eh_onUserAdminStateChanged.sqf | 11 +- .../addons/recorder/fnc_eh_projectileHit.sqf | 59 +++ .../recorder/fnc_getEventWeaponText.sqf | 26 +- .../recorder/fnc_projectileMonitors.sqf | 74 +--- 10 files changed, 232 insertions(+), 468 deletions(-) delete mode 100644 x/ocap/addons/recorder/fnc_eh_hit.sqf create mode 100644 x/ocap/addons/recorder/fnc_eh_projectileHit.sqf diff --git a/x/ocap/addons/recorder/XEH_prep.sqf b/x/ocap/addons/recorder/XEH_prep.sqf index abb652d..7ddd5ca 100644 --- a/x/ocap/addons/recorder/XEH_prep.sqf +++ b/x/ocap/addons/recorder/XEH_prep.sqf @@ -22,7 +22,8 @@ PREP(eh_onUserAdminStateChanged); PREP(adminUIcontrol); PREP(eh_firedMan); -PREP(eh_hit); +// PREP(eh_hit); +PREP(eh_projectileHit); PREP(eh_killed); PREP(getInstigator); PREP(getEventWeaponText); diff --git a/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf index d3d6bfa..e977e2c 100644 --- a/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf +++ b/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf @@ -46,70 +46,3 @@ if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then { // _entity addMPEventHandler ["MPHit", { _this call FUNC(eh_hit); }] // ]; // }; - - - -// HITPART -[_entity, [ - "HitPart", - { - // https://community.bistudio.com/wiki/HitPart_Sample - (_this select 0) params ["_target", "_shooter", "_projectile", "_position", "_velocity", "_selection", "_ammo", "_vector", "_radius", "_surfaceType", "_isDirect"]; - - if (isNull _shooter) then { - // _shooter = [_target, _projectile] call FUNC(getInstigator); - private _shotParents = getShotParents _projectile; - if (count _shotParents == 1) then { - _shooter = [_target, _shotParents#0, objNull] call FUNC(getInstigator); - }; - if (count _shotParents == 2) then { - _shooter = [_target, _shotParents#0, _shotParents#1] call FUNC(getInstigator); - }; - }; - if (isNull _shooter) exitWith {}; - - private _hitFrame = GVAR(captureFrameNo); - - _targetID = _target getVariable [QGVARMAIN(id), -1]; - if (_targetID == -1) exitWith {}; - _shooterId = _shooter getVariable [QGVARMAIN(id), -1]; - if (_shooterId == -1) exitWith {}; - - private _eventData = [_hitFrame, "hit", _targetID, [_shooterId, getText(configOf _projectile >> "displayName")], -1]; - - - - _wepText = format["%1x", count _this]; - - private _allSelections = flatten(_this apply {_x#5}); - if (count _allSelections > 0) then { - private _uniqueSelections = _allSelections arrayIntersect _allSelections; - _wepText = format["%1 - %2", _wepText, _uniqueSelections joinString ","]; - }; - - _wepText = format["%1 - %2", _wepText, ([_shooter] call FUNC(getEventWeaponText))]; - - - - - _projectileInfo = [ - _shooterId, - _wepText - ]; - _distanceInfo = round (_target distance _shooter); - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _targetID, _shooterId)); - }; - _eventData = [ - _hitFrame, - "hit", - _targetID, - _projectileInfo, - _distanceInfo - ]; - - [":EVENT:", _eventData] remoteExecCall [QEFUNC(extension,sendData), 2]; - - } -]] remoteExec ["addEventHandler", [0, -2] select isDedicated, true]; diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf index b865e50..9c670c6 100644 --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf @@ -1,11 +1,21 @@ #include "script_component.hpp" -params ["_PID"]; +params [ + "_PID", + ["_grantControls", true, [true]] +]; if (isNil "_PID") exitWith {}; (getUserInfo _PID) params ["_playerID", "_owner", "_playerUID"]; +// If parameter was false, remove the diary entry +if (!_grantControls) exitWith { + { + player removeDiarySubject QEGVAR(diary,adminControls_subject); + } remoteExec ["call", _owner]; +}; + // check if admin private _adminUIDs = missionNamespace getVariable [QGVARMAIN(administratorList), nil]; diff --git a/x/ocap/addons/recorder/fnc_eh_connected.sqf b/x/ocap/addons/recorder/fnc_eh_connected.sqf index f3a2ebf..67e8bab 100644 --- a/x/ocap/addons/recorder/fnc_eh_connected.sqf +++ b/x/ocap/addons/recorder/fnc_eh_connected.sqf @@ -10,4 +10,4 @@ if ((_this#0) isEqualTo 2) exitWith {}; [GVAR(captureFrameNo), "connected", _this select 2] ] call EFUNC(extension,sendData); -[_id] call FUNC(adminUIcontrol); +[_idstr] call FUNC(adminUIcontrol); diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf index a74870f..c337a06 100644 --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf @@ -76,8 +76,23 @@ _firer setVariable [QGVARMAIN(lastFired), _wepString]; _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); +// Save marker data to projectile namespace for EH later +_projectile setVariable [QGVAR(firer), _firer]; _projectile setVariable [QGVAR(firerId), _firerId]; +// Track hit events for all projectile types +_projectile addEventHandler ["HitPart", { + params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius" ,"_surfaceType"]; + LOG(ARR2("HitPart", _hitEntity)); + [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); +}]; + +_projectile addEventHandler ["HitExplosion", { + params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"]; + LOG(ARR2("HitExplosion", _hitEntity)); + [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); +}]; + // _ammoSimType // "ShotGrenade" // M67 // "ShotRocket" // S-8 @@ -86,272 +101,163 @@ _projectile setVariable [QGVAR(firerId), _firerId]; // "ShotMine" // Satchel charge // "ShotIlluminating" // 40mm_green Flare // "ShotSmokeX"; // M18 Smoke - -#define LOGBULLET GVAR(liveBullets) pushBack [_projectile, _firerId, _firer, getPosASL _projectile] -#define LOGMISSILE GVAR(liveMissiles) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal] -#define LOGGRENADE GVAR(liveGrenades) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]; - -switch (true) do { - case (_ammoSimType isEqualTo "shotBullet"): { - // [_projectile, _firer, _frame, _ammoSimType, _ammo] spawn { - // params["_projectile", "_firer", "_frame", "_ammoSimType", "_ammo"]; - if (isNull _projectile) then { - _projectile = nearestObject [_firer, _ammo]; +// "ShotCM" // Plane flares +// "ShotSubmunition" // Hind minigun, cluster artillery + + +if (_ammoSimType isEqualTo "shotBullet") exitWith { + // Bullet projectiles + _projectile addEventHandler ["Deleted", { + params ["_projectile"]; + _firer = _projectile getVariable [QGVAR(firer), objNull]; + _firerId = _projectile getVariable [QGVAR(firerId), -1]; + _projectilePos = getPosASL _projectile; + + [":FIRED:", [ + _firerId, + GVAR(captureFrameNo), + _projectilePos + ]] call EFUNC(extension,sendData); + + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos)); + + // add to clients' map draw array + private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime]; + [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent; }; - if (isNil "_projectile") exitWith {}; - - - LOGBULLET; - - _projectile addEventHandler ["Deleted", { - params ["_projectile"]; - [":FIRED:", [ - _projectile getVariable [QGVAR(firerId), -1], - GVAR(captureFrameNo), - getPosASL _projectile - ]] call EFUNC(extension,sendData); - }]; - }; - - - case (_ammoSimType isNotEqualTo "shotSubmunitions"): { - - if (isNull _projectile) then { - _projectile = nearestObject [_firer, _ammo]; - _this set [6, _projectile]; - }; - - ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; - private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); - - // MAKE MARKER FOR PLAYBACK - _firerPos = getPosASL _firer; - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - + }]; +}; +// ALL OTHER PROJECTILES - switch (true) do { - case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - LOGMISSILE; +// Get data for marker +([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; +private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); +_projectile setVariable [QGVAR(markName), _markName]; - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; +// MAKE MARKER FOR PLAYBACK +_firerPos = getPosASL _firer; +[QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - _projectile setVariable [QGVAR(markName), _markName]; - _projectile addEventHandler ["Deleted", { - params ["_projectile"]; - [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; - }]; - - if (GVARMAIN(isDebug)) then { - // add to map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; - }; - case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { - LOGGRENADE; - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; +// Move marker, then delete marker, when projectile is deleted +_projectile addEventHandler ["Deleted", { + params ["_projectile"]; + _markName = _projectile getVariable QGVAR(markName); + _firer = _projectile getVariable QGVAR(firer); + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent; + [{ + [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent; + }, _markName, GVAR(frameCaptureDelay)] call CBA_fnc_waitAndExecute; +}]; - _projectile setVariable [QGVAR(markName), _markName]; +// Add to debug +if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; +}; - _projectile addEventHandler ["Deleted", { - params ["_projectile"]; - [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; - }]; - if (GVARMAIN(isDebug)) then { - // add to map draw array - private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; - }; - default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; - }; +switch (true) do { + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { + GVAR(liveGrenades) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType]; }; + // case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell", "shotSubmunitions"]): { + default { + GVAR(liveMissiles) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal]; - case (_ammoSimType isEqualTo "shotSubmunitions"): { + if (_ammoSimType isEqualTo "shotSubmunitions") then { + _projectile setVariable [QGVAR(markerData), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))]; + _projectile setVariable [QGVAR(EHData), [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType]]; - _projectile setVariable [GVAR(markerData), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))]; - _projectile setVariable [GVAR(EHData), [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType]]; + // for every submunition split process here + _projectile addEventHandler ["SubmunitionCreated", { + params ["_projectile", "_submunitionProjectile", "_pos", "_velocity"]; + (_projectile getVariable [QGVAR(markerData), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"]; + (_projectile getVariable [QGVAR(EHData), []]) params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; + // Save marker data to projectile namespace for EH later + _submunitionProjectile setVariable [QGVAR(firer), _firer]; + _submunitionProjectile setVariable [QGVAR(firerId), _firerId]; + _submunitionProjectile setVariable [QGVAR(markName), _markName]; + private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); - _projectile addEventHandler ["SubmunitionCreated", { - params ["_projectile", "_submunitionProjectile", "_pos", "_velocity"]; - (_projectile getVariable [GVAR(markerData), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"]; - (_projectile getVariable [GVAR(EHData), []]) params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; + // then get data of submunition to determine how to track it + private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _submunitionProjectile) >> "simulation"); - _submunitionProjectile setVariable [QGVAR(firerId), _projectile getVariable [QGVAR(firerId), -1]]; - private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); - - // then get data of submunition to determine how to track it - private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _submunitionProjectile) >> "simulation"); + // Track hit events for all projectile types + _submunitionProjectile addEventHandler ["HitPart", { + params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius" ,"_surfaceType"]; + [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); + }]; + _submunitionProjectile addEventHandler ["HitExplosion", { + params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"]; + [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); + }]; - switch (true) do { - case (_ammoSimType isEqualTo "shotBullet"): { - // LOGBULLET; + if (_ammoSimType isEqualTo "shotBullet") exitWith { + // Bullet projectiles _submunitionProjectile addEventHandler ["Deleted", { params ["_projectile"]; + _firer = _projectile getVariable [QGVAR(firer), objNull]; + _firerId = _projectile getVariable [QGVAR(firerId), -1]; + _projectilePos = getPosASL _projectile; + [":FIRED:", [ - _projectile getVariable [QGVAR(firerId), -1], + _firerId, GVAR(captureFrameNo), - getPosASL _projectile + _projectilePos ]] call EFUNC(extension,sendData); - }]; - }; - case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - LOGMISSILE; - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos)); - _projectile setVariable [QGVAR(markName), _markName]; - - _projectile addEventHandler ["Deleted", { - params ["_projectile"]; - [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; + // add to clients' map draw array + private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime]; + [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent; + }; }]; - - if (GVARMAIN(isDebug)) then { - // add to clients' map draw array - private _debugArr = [_submunitionProjectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - }; }; - case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { - LOGGRENADE; - // create our marker record in the timeline - [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1, 1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; + // Move marker, then delete marker, when projectile is deleted + _submunitionProjectile addEventHandler ["Deleted", { + params ["_projectile"]; + _markName = _projectile getVariable QGVAR(markName); + _firer = _projectile getVariable QGVAR(firer); + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent; + [{ + [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent; + }, _markName, GVAR(frameCaptureDelay)] call CBA_fnc_waitAndExecute; + }]; + + // Add to debug + if (GVARMAIN(isDebug)) then { + // add to map draw array + private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; + [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + }; - _projectile setVariable [QGVAR(markName), _markName]; - _projectile addEventHandler ["Deleted", { - params ["_projectile"]; - [QGVARMAIN(handleMarker), ["DELETED", _projectile getVariable QGVAR(markName)]] call CBA_fnc_localEvent; - }]; - if (GVARMAIN(isDebug)) then { - // add to map draw array - private _debugArr = [_submunitionProjectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; + switch (true) do { + case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { + GVAR(liveGrenades) pushBack [_submunitionProjectile, _wepString, _firer, getPosASL _submunitionProjectile, _markName, _markTextLocal, _ammoSimType]; + }; + // case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell", "shotSubmunitions"]): { + default { + GVAR(liveMissiles) pushBack [_submunitionProjectile, _wepString, _firer, getPosASL _submunitionProjectile, _markName, _markTextLocal]; }; }; - default { - OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _submunitionProjectile, _ammoSimType)) - }; - }; - }]; - - // // for submunitions, we first look at the original ammo and find the classnames of submunition ammo its rounds will turn into - // // these are done in a staggered array in format ["classname1", probability of spawn, "classname2", probability of spawn] - // // this is usually for vehicles with guns that fire mixed ammo, or for cluster munitions - // // we'll wait for the simStep to have elapsed, then start checking for the resulting submunition nearby - // // once we have that, we'll add it to the bullet tracking array for positions so a fireline is drawn in playback - // private _simDelay = (configFile >> "CfgAmmo" >> _ammo >> "simulationStep") call BIS_fnc_getCfgData; - // private _subTypes = ((configFile >> "CfgAmmo" >> _ammo >> "submunitionAmmo") call BIS_fnc_getCfgDataArray) select {_x isEqualType ""}; - // private _subTypesAmmoSimType = _subTypes apply {(configFile >> "CfgAmmo" >> _x >> "simulation") call BIS_fnc_getCfgData}; - // if (count _subTypesAmmoSimType > 0) then { - // _subTypesAmmoSimType = selectRandom(_subTypesAmmoSimType); - // }; - // [ - // { - // _this spawn { - // params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"]; - // // private _projectile = _EHData # 6; - - // // if submunitions are NOT bullets, wait until the original projectile deploys then search for submunitions and 're-fire' them to appear in playback - // // if !(_subTypesAmmoSimType == "shotBullet") exitWith { - // // if (_magazine isKindOf "VehicleMagazine") then { - // // [_EHData, _projectile, _subTypes] spawn { - // // params ["_EHData", "_projectile", "_subTypes"]; - // // private _ogPos = getPos _firer; - // // while {!isNull _projectile} do {_ogPos = getPosASL _projectile; sleep 0.1;}; - // // isNil { - // // _projSearch = nearestObjects [ASLtoAGL _ogPos, _subTypes, 50, false]; - // // { - // // _EHData set [6, _x]; - // // _EHData spawn FUNC(eh_firedMan); - // // } forEach _projSearch; - // // }; - // // }; - // // }; - - // // if submunitions ARE bullets, process normally and just look for one item - // _EHData params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; - // private _projectile = objNull; - // // while {isNull _projectile} do { - // // { - // // _projSearch = nearestObject [_firer, _x]; - // // if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - // // } forEach _subTypes; - // // }; - - // while {isNull _projectile} do { - // { - // _projSearch = nearestObject [_firer, _x]; - // if !(isNull _projSearch) exitWith {_projectile = _projSearch}; - // } forEach _subTypes; - // }; - // _newSubs = nearestObjects [_projectile, _subTypes, 50]; - - - // isNil { - // { - // private _projectile = _x; - - // // get marker details based on original EH data - // ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"]; - // private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture"); - - // // then get data of submunition to determine how to track it - // private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _projectile) >> "simulation"); - // switch (true) do { - // case (_ammoSimType isEqualTo "shotBullet"): { - // LOGBULLET; - // }; - // case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell"]): { - // LOGMISSILE; - - // // create our marker record in the timeline - // [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - // if (GVARMAIN(isDebug)) then { - // // add to clients' map draw array - // private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - // [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - // }; - // }; - // case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): { - // LOGGRENADE; - - // // create our marker record in the timeline - // [QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, getPosASL _firer, _markerType, "ICON", [1,1], getDir _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent; - - // if (GVARMAIN(isDebug)) then { - // // add to map draw array - // private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor]; - // [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent; - // }; - // }; - // default {OCAPEXTLOG(ARR3("Invalid ammo sim type, check it", _projectile, _ammoSimType))}; - // }; - // } forEach _newSubs; - // nil; - // }; - // }; - // }, - // [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType], - // _simDelay - // ] call CBA_fnc_waitAndExecute; + }]; + }; }; }; diff --git a/x/ocap/addons/recorder/fnc_eh_hit.sqf b/x/ocap/addons/recorder/fnc_eh_hit.sqf deleted file mode 100644 index 3640db8..0000000 --- a/x/ocap/addons/recorder/fnc_eh_hit.sqf +++ /dev/null @@ -1,82 +0,0 @@ -/* ---------------------------------------------------------------------------- -Script: FUNC(eh_hit) - -Description: - Tracks when a unit is hit/takes damage. This is the code triggered by the "MPHit" Event Handler applied to units during . - -Parameters: - _unit - Object the event handler is assigned to. [Object] - _causedBy - Object that caused the damage. Contains the unit itself in case of collisions. [Object] - _damage - Level of damage caused by the hit. [Number] - _instigator - Object - Person who pulled the trigger. [Object] - -Returns: - Nothing - -Examples: - --- Code - --- - -Public: - No - -Author: - IndigoFox, Fank ----------------------------------------------------------------------------- */ -#include "script_component.hpp" - -if (!SHOULDSAVEEVENTS) exitWith {}; - -params ["_unit", "_causedBy", "_damage", "_instigator"]; - -[_unit, _causedBy, _instigator] spawn { - params ["_unit", "_causedBy", "_instigator"]; - - if (isNull _instigator) then { - _instigator = [_unit, _causedBy] call FUNC(getInstigator); - }; - - private _hitFrame = GVAR(captureFrameNo); - - _unitID = _unit getVariable [QGVARMAIN(id), -1]; - if (_unitID == -1) exitWith {}; - private _eventData = [_hitFrame, "hit", _unitID, ["null"], -1]; - - if (!isNull _instigator) then { - _causedById = _causedBy getVariable [QGVARMAIN(id), -1]; - _instigatorId = _instigator getVariable [QGVARMAIN(id), -1]; - - private _causedByInfo = []; - private _distanceInfo = 0; - if (_causedById > -1 && _causedBy isEqualTo _instigator) then { - _causedByInfo = [ - _causedById, - ([_causedBy] call FUNC(getEventWeaponText)) - ]; - _distanceInfo = round (_unit distance _causedBy); - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _causedById)); - }; - } else { - _causedByInfo = [ - _instigatorId, - ([_instigator] call FUNC(getEventWeaponText)) - ]; - _distanceInfo = round (_unit distance _instigator); - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _instigatorId)); - }; - }; - _eventData = [ - _hitFrame, - "hit", - _unitID, - _causedByInfo, - _distanceInfo - ]; - }; - - [":EVENT:", _eventData] call EFUNC(extension,sendData); -}; diff --git a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf index e229b9f..edc00ac 100644 --- a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf @@ -4,7 +4,14 @@ params ["_networkId", "_loggedIn", "_votedIn"]; _object = (getUserInfo _networkId) select 10; if (isNull _object) exitWith {}; -if (_loggedIn && !_votedIn && !(_object getVariable [QGVARMAIN(hasAdminControls), true])) then { + +_id = getPlayerId _object; + +if (_loggedIn && !_votedIn && !(_object getVariable [QGVARMAIN(hasAdminControls), true])) exitWith { // if user has become admin by logging, not voting, and has not yet received adminControls per OCAP - Main > Administrators setting, add controls - [_id] call FUNC(adminUIcontrol); + [_id, true] call FUNC(adminUIcontrol); +}; +if (!_loggedIn && _object getVariable [QGVARMAIN(hasAdminControls), false]) then { + // if user has logged out, remove adminControls + [_id, false] call FUNC(adminUIcontrol); }; diff --git a/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf b/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf new file mode 100644 index 0000000..035ff7e --- /dev/null +++ b/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf @@ -0,0 +1,59 @@ +/* ---------------------------------------------------------------------------- +Script: FUNC(eh_hit) + +Description: + Tracks when a unit is hit/takes damage. This is the code triggered by the "MPHit" Event Handler applied to units during . + +Parameters: + _unit - Object the event handler is assigned to. [Object] + _causedBy - Object that caused the damage. Contains the unit itself in case of collisions. [Object] + _damage - Level of damage caused by the hit. [Number] + _instigator - Object - Person who pulled the trigger. [Object] + +Returns: + Nothing + +Examples: + --- Code + --- + +Public: + No + +Author: + IndigoFox, Fank +---------------------------------------------------------------------------- */ +#include "script_component.hpp" + +if (!SHOULDSAVEEVENTS) exitWith {}; + +params ["_unit", "_shooter"]; + +private _hitFrame = GVAR(captureFrameNo); + +_unitID = _unit getVariable [QGVARMAIN(id), -1]; +if (_unitID == -1) exitWith {}; +_shooterID = _shooter getVariable [QGVARMAIN(id), -1]; +if (_shooterID == -1) exitWith {}; + +private _distanceInfo = 0; +_distanceInfo = round (_shooter distance _unit); + +_causedByInfo = [ + _shooterID, + ([_shooter] call FUNC(getEventWeaponText)) +]; + +private _eventData = [ + _hitFrame, // Frame number + "hit", // event type + _unitID, // hit unit ID + _causedByInfo, // event info + _distanceInfo // distance +]; + +[":EVENT:", _eventData] call EFUNC(extension,sendData); + +if (GVARMAIN(isDebug)) then { + OCAPEXTLOG(ARR4("HIT EVENT", _hitFrame, _unitID, _causedById)); +}; diff --git a/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf index 1f9678e..8ccab7b 100644 --- a/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf +++ b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf @@ -29,16 +29,16 @@ params ["_instigator"]; if (isNull _instigator) exitWith {""}; -if !(_instigator call CBA_fnc_isPerson) then { - _instigator = _instigator call { - if(alive(gunner _this))exitWith{gunner _this}; - if(alive(commander _this))exitWith{commander _this}; - if(alive(driver _this))exitWith{driver _this}; - effectiveCommander _this - }; -}; - -if (_instigator call CBA_fnc_isPerson) then { +// if !(_instigator call CBA_fnc_isPerson) then { +// _instigator = _instigator call { +// if(alive(gunner _this))exitWith{gunner _this}; +// if(alive(commander _this))exitWith{commander _this}; +// if(alive(driver _this))exitWith{driver _this}; +// effectiveCommander _this +// }; +// }; + +// if (_instigator call CBA_fnc_isPerson) then { (weaponstate _instigator) params ["_weapon", "_muzzle", "_mode", "_magazine"]; ([_weapon, _muzzle, _magazine] call FUNC(getWeaponDisplayData)) params ["_muzDisp", "_magDisp"]; @@ -50,6 +50,6 @@ if (_instigator call CBA_fnc_isPerson) then { _magDisp ] ]; -} else { - getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); -}; +// } else { + // getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); +// }; diff --git a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf index fba6e4a..6c4b15a 100644 --- a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf +++ b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf @@ -1,60 +1,10 @@ #include "script_component.hpp" -// PFH to track bullets -GVAR(liveBullets) = []; -[{ - - private _processNow = GVAR(liveBullets) select {isNull (_x#0)}; - GVAR(liveBullets) = GVAR(liveBullets) select {!isNull (_x#0)}; - - // _processNow - // for bullets that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw - { - _x params ["_obj", "_firerId", "_firer", "_pos"]; - - // [":FIRED:", [ - // _firerId, - // GVAR(captureFrameNo), - // _pos - // ]] call EFUNC(extension,sendData); - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _pos)); - - // add to clients' map draw array - private _debugArr = [getPos _firer, _pos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime]; - [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent; - }; - } forEach _processNow; - - // for bullets that still exist, update positions - { - _x set [3, getPosASL (_x#0)]; - } forEach GVAR(liveBullets); -}, 0.1 * GVAR(projectileMonitorMultiplier)] call CBA_fnc_addPerFrameHandler; - // PFH to track missiles, rockets, shells GVAR(liveMissiles) = []; [{ - private _processNow = GVAR(liveMissiles) select {isNull (_x#0)}; GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x#0)}; - // _processNow - // for missiles that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw - { - _x params ["_obj", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal"]; - _firer setVariable [ - QGVARMAIN(lastFired), - getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") - ]; - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("FIRED EVENT: SHELL-ROCKET-MISSILE", GVAR(captureFrameNo), _firer getVariable QGVARMAIN(id), str _pos)); - }; - - // [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; - } forEach _processNow; - // for missiles that still exist, update positions { _x params ["_obj", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal"]; @@ -62,33 +12,13 @@ GVAR(liveMissiles) = []; _x set [3, _nowPos]; [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; } forEach GVAR(liveMissiles); -}, 0.1 * GVAR(projectileMonitorMultiplier)] call CBA_fnc_addPerFrameHandler; +}, 0.7] call CBA_fnc_addPerFrameHandler; // PFH to track grenades, flares, thrown charges GVAR(liveGrenades) = []; [{ - private _processNow = GVAR(liveGrenades) select {isNull (_x#0)}; GVAR(liveGrenades) = GVAR(liveGrenades) select {!isNull (_x#0)}; - // _processNow - // for grenades that have hit something and become null, trigger FIRED events in timeline and add to clients for debug draw - { - _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_markTextLocal", "_ammoSimType"]; - - if !(_ammoSimType in ["shotSmokeX", "shotIlluminating"]) then { - _firer setVariable [ - QGVARMAIN(lastFired), - getText(configFile >> "CfgMagazines" >> _magazine >> "displayName") - ]; - }; - - if (GVARMAIN(isDebug)) then { - OCAPEXTLOG(ARR4("FIRED EVENT: GRENADE-FLARE-SMOKE", GVAR(captureFrameNo), _firer getVariable QGVARMAIN(id), str _pos)); - }; - - // [{[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent}, _markName, 10] call CBA_fnc_waitAndExecute; - } forEach _processNow; - // for grenades that still exist, update positions { _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_markTextLocal", "_ammoSimType"]; @@ -96,7 +26,7 @@ GVAR(liveGrenades) = []; _x set [3, _nowPos]; [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent; } forEach GVAR(liveGrenades); -}, GVAR(frameCaptureDelay)] call CBA_fnc_addPerFrameHandler; +}, 0.7] call CBA_fnc_addPerFrameHandler; From 21a2057dd7cebf061a8f89f74b7f693ef83b9447 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Wed, 12 Oct 2022 14:42:59 -0400 Subject: [PATCH 20/26] many tweaks --- x/ocap/addons/recorder/XEH_preInit.sqf | 4 +- x/ocap/addons/recorder/fnc_adminUIcontrol.sqf | 137 +++++++++++++----- x/ocap/addons/recorder/fnc_captureLoop.sqf | 8 +- x/ocap/addons/recorder/fnc_eh_connected.sqf | 7 +- x/ocap/addons/recorder/fnc_eh_firedMan.sqf | 2 - .../fnc_eh_onUserAdminStateChanged.sqf | 14 +- x/ocap/addons/recorder/fnc_exportData.sqf | 53 +++---- x/ocap/addons/recorder/fnc_startRecording.sqf | 20 +-- x/ocap/addons/recorder/fnc_stopRecording.sqf | 10 +- 9 files changed, 162 insertions(+), 93 deletions(-) diff --git a/x/ocap/addons/recorder/XEH_preInit.sqf b/x/ocap/addons/recorder/XEH_preInit.sqf index 239b793..6feb5de 100644 --- a/x/ocap/addons/recorder/XEH_preInit.sqf +++ b/x/ocap/addons/recorder/XEH_preInit.sqf @@ -48,10 +48,10 @@ GVAR(allSettings) = [ ], [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry. [ - 0.25, // min + 0.30, // min 10, // max 1, // default - 0, // trailing decimals + 2, // trailing decimals false // percentage ], // data for this setting: [min, max, default, number of shown trailing decimals] true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf index 9c670c6..a5f9ad0 100644 --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf @@ -2,57 +2,126 @@ params [ "_PID", - ["_grantControls", true, [true]] + ["_event", "", [""]] ]; if (isNil "_PID") exitWith {}; -(getUserInfo _PID) params ["_playerID", "_owner", "_playerUID"]; +private _userInfo = (getUserInfo _PID); +_userInfo params ["_playerID", "_owner", "_playerUID"]; +_unit = _userInfo select 10; -// If parameter was false, remove the diary entry -if (!_grantControls) exitWith { + +_fnc_addControls = { + params ["_owner","_unit"]; + // add controls to diary entry + { + [{getClientStateNumber > 9 && !isNull player}, { + + player createDiarySubject [ + QEGVAR(diary,adminControls_subject), + "OCAP Admin", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa" + ]; + + EGVAR(diary,adminControls_record) = player createDiaryRecord [ + QEGVAR(diary,adminControls_subject), + [ + "Controls", + format[ + "
These controls can be used to Start Recording, Pause Recording, and Save/Export the Recording. On the backend, these use the corresponding CBA server events that can be found in the documentation. Because of this, they override the default minimum duration required to save, so be aware that clicking ""Stop and Export Recording"" will save and upload your current recording regardless of its duration.

Start/Resume Recording
Pause Recording
Stop and Export Recording", + QGVARMAIN(record), + QGVARMAIN(pause), + QGVARMAIN(exportData) + ] + ] + ]; + }] call CBA_fnc_waitUntilAndExecute; + } remoteExec ["call", _owner]; + + // set variable on unit + _unit setVariable [QGVARMAIN(hasAdminControls), true]; +}; + +_fnc_removeControls = { + params ["_owner","_unit"]; { player removeDiarySubject QEGVAR(diary,adminControls_subject); + player setVariable [QGVARMAIN(hasAdminControls), false, 2]; } remoteExec ["call", _owner]; + + // set variable on unit + _unit setVariable [QGVARMAIN(hasAdminControls), false]; }; + + // check if admin private _adminUIDs = missionNamespace getVariable [QGVARMAIN(administratorList), nil]; -if (isNil "_adminUIDs") then { - _adminUIDs = parseSimpleArray QGVARMAIN(administratorList); -}; if (isNil "_adminUIDs") exitWith { + // At this point, no adminUIDs are defined in missionNamespace or in CBA settings WARNING("Failed to parse administrator list setting. Please check its value!"); -}; -if !(_playerUID in _adminUIDs) exitWith {}; + switch (_event) do { + case "connect": { + // A player just joined the mission and no admin list exists - skip + }; + case "login": { + // A player just logged in so add controls if they don't already have them + if !(_unit getVariable [QGVARMAIN(hasAdminControls), false]) then { + [_owner, _unit] call _fnc_addControls; + if (GVARMAIN(isDebug)) then { + format["%1 was granted OCAP control by logging in as admin", name _unit] SYSCHAT; + }; + }; + }; + case "logout": { + // A player just logged out so remove controls if they have them + if (_unit getVariable [QGVARMAIN(hasAdminControls), false]) then { + [_owner, _unit] call _fnc_removeControls; + if (GVARMAIN(isDebug)) then { + format["%1 had their admin controls removed due to logging out from admin", name _unit] SYSCHAT; + }; + }; + }; + default {}; + }; +}; -// add controls to diary entry -{ - [{getClientStateNumber > 9 && !isNull player}, { - - if (player getVariable [QGVARMAIN(hasAdminControls), false]) exitWith {}; - - EGVAR(diary,adminControls) = player createDiarySubject [ - QEGVAR(diary,adminControls_subject), - PREFIX + " Admin", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa" - ]; - player createDiaryRecord [ - QEGVAR(diary,adminControls_subject), - [ - PREFIX + " Controls", - "
-Start/Resume Recording
-Pause Recording
-Stop and Export Recording - " - ] - ]; +// Admin list is defined, so we check if the player is listed by playerUID +private _inAdminList = _playerUID in _adminUIDs; - player setVariable [QGVARMAIN(hasAdminControls), true, 2]; - }] call CBA_fnc_waitUntilAndExecute; -} remoteExec ["call", _owner]; +switch (_event) do { + case "connect": { + // A player just joined the mission + // If they are an admin, we add the diary entry + if (_inAdminList) then { + [_owner, _unit] call _fnc_addControls; + if (GVARMAIN(isDebug)) then { + format["%1 was granted OCAP control due to being in the administratorList", name _unit] SYSCHAT; + }; + }; + }; + case "login": { + // A player just logged in so add controls if they don't already have them + if !(_unit getVariable [QGVARMAIN(hasAdminControls), false]) then { + [_owner, _unit] call _fnc_addControls; + if (GVARMAIN(isDebug)) then { + format["%1 was granted OCAP control by logging in as admin", name _unit] SYSCHAT; + }; + }; + }; + case "logout": { + // A player just logged out so remove controls if they have them + if (_unit getVariable [QGVARMAIN(hasAdminControls), false]) then { + [_owner, _unit] call _fnc_removeControls; + if (GVARMAIN(isDebug)) then { + format["%1 had their admin controls removed due to logging out from admin", name _unit] SYSCHAT; + }; + }; + }; + default {}; +}; diff --git a/x/ocap/addons/recorder/fnc_captureLoop.sqf b/x/ocap/addons/recorder/fnc_captureLoop.sqf index e90eb0a..5f25c9a 100644 --- a/x/ocap/addons/recorder/fnc_captureLoop.sqf +++ b/x/ocap/addons/recorder/fnc_captureLoop.sqf @@ -66,7 +66,7 @@ GVAR(PFHObject) = [ ("Capture frame: " + str (missionNamespace getVariable [QGVAR(captureFrameNo), "[not yet received]"]) + "") ] ]; - } remoteExecCall ["call", 0, false]; + } remoteExec ["call", 0, false]; }; { @@ -187,14 +187,14 @@ GVAR(PFHObject) = [ false } count vehicles; - GVAR(captureFrameNo) = GVAR(captureFrameNo) + 1; - publicVariable QGVAR(captureFrameNo); - if (GVARMAIN(isDebug)) then { private _logStr = format["Frame %1 processed in %2ms", GVAR(captureFrameNo), diag_tickTime - _loopStart]; OCAPEXTLOG([_logStr]); _logStr SYSCHAT; }; + + GVAR(captureFrameNo) = GVAR(captureFrameNo) + 1; + publicVariable QGVAR(captureFrameNo); }, GVAR(frameCaptureDelay), // delay [], // args diff --git a/x/ocap/addons/recorder/fnc_eh_connected.sqf b/x/ocap/addons/recorder/fnc_eh_connected.sqf index 67e8bab..e70719d 100644 --- a/x/ocap/addons/recorder/fnc_eh_connected.sqf +++ b/x/ocap/addons/recorder/fnc_eh_connected.sqf @@ -1,13 +1,14 @@ #include "script_component.hpp" - +// PlayerConnected EH params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; // skip for server 'connected' message -if ((_this#0) isEqualTo 2) exitWith {}; +if (_owner isEqualTo 2) exitWith {}; // log to timeline [":EVENT:", [GVAR(captureFrameNo), "connected", _this select 2] ] call EFUNC(extension,sendData); -[_idstr] call FUNC(adminUIcontrol); +// trigger admin control check for all connecting players +[_idstr, "connect"] call FUNC(adminUIcontrol); diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf index c337a06..70ba693 100644 --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf @@ -83,13 +83,11 @@ _projectile setVariable [QGVAR(firerId), _firerId]; // Track hit events for all projectile types _projectile addEventHandler ["HitPart", { params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius" ,"_surfaceType"]; - LOG(ARR2("HitPart", _hitEntity)); [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); }]; _projectile addEventHandler ["HitExplosion", { params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"]; - LOG(ARR2("HitExplosion", _hitEntity)); [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); }]; diff --git a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf index edc00ac..b797d70 100644 --- a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf @@ -5,13 +5,11 @@ params ["_networkId", "_loggedIn", "_votedIn"]; _object = (getUserInfo _networkId) select 10; if (isNull _object) exitWith {}; -_id = getPlayerId _object; - -if (_loggedIn && !_votedIn && !(_object getVariable [QGVARMAIN(hasAdminControls), true])) exitWith { - // if user has become admin by logging, not voting, and has not yet received adminControls per OCAP - Main > Administrators setting, add controls - [_id, true] call FUNC(adminUIcontrol); +if (_loggedIn && !_votedIn) exitWith { + // if user has become admin by logging, not voting, trigger control addition check + [_networkId, "login"] call FUNC(adminUIcontrol); }; -if (!_loggedIn && _object getVariable [QGVARMAIN(hasAdminControls), false]) then { - // if user has logged out, remove adminControls - [_id, false] call FUNC(adminUIcontrol); +if (!_loggedIn) then { + // if user has logged out, trigger admin control removal + [_networkId, "logout"] call FUNC(adminUIcontrol); }; diff --git a/x/ocap/addons/recorder/fnc_exportData.sqf b/x/ocap/addons/recorder/fnc_exportData.sqf index 4d9e81b..943ef44 100644 --- a/x/ocap/addons/recorder/fnc_exportData.sqf +++ b/x/ocap/addons/recorder/fnc_exportData.sqf @@ -101,18 +101,6 @@ if (!isNil QGVAR(PFHObject)) then { GVAR(PFHObject) = nil; }; -// reset vars in case a new recording is started -GVAR(captureFrameNo) = 0; -GVAR(startTime) = nil; -{ - _x setVariable [QGVARMAIN(isInitialized), nil]; - _x setVariable [QGVARMAIN(exclude), nil]; - _x setVariable [QGVARMAIN(id), nil]; - _x setVariable [QGVARMAIN(unitType), nil]; -} count (allUnits + allDeadMen + vehicles); -GVAR(nextId) = 0; - - if (isNil "_side") then { [":EVENT:", [_endFrameNumber, "endMission", ["", "Mission ended"]]] call EFUNC(extension,sendData); }; @@ -137,22 +125,37 @@ if (!isNil "_tag") then { OCAPEXTLOG(ARR3("Saved recording of mission", GVAR(missionName), "with default tag")); }; -// briefingName is used here, no need for publicVariable for a simple confirmation log. -[format["OCAP saved %1 frames successfully", _endFrameNumber], 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; -{ + +// notify players that the recording was saved with a 2 second delay to ensure the "stopped recording" entries populate first +[format["OCAP saved %1 frames successfully", _endFrameNumber], 1, [1, 1, 1, 1]] remoteExec ["CBA_fnc_notify", [0, -2] select isDedicated]; +[[GVAR(missionName), GVAR(captureFrameNo)], { + params ["_missionName", "_endFrame"]; + + player setDiarySubjectPicture [ + "OCAPInfo", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\upload_ca.paa" + ]; player createDiaryRecord [ "OCAPInfo", [ "Status", - ( - "OCAP capture of " + briefingName + " has been exported with " + str(GVAR(endFrameNumber)) + " frames saved." + - "

" + - "Upload results have been logged." - ) + format[ + "OCAP capture of %1 has been exported with %2 frames saved.

Upload results have been logged.", + _missionName, + _endFrame + ] ] ]; - player setDiarySubjectPicture [ - "OCAPInfo", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\upload_ca.paa" - ]; -} remoteExec ["call", 0, false]; +}] remoteExec ["call", [0, -2] select isDedicated, true]; + +// reset vars in case a new recording is started +GVAR(captureFrameNo) = 0; +publicVariable QGVAR(captureFrameNo); +GVAR(startTime) = nil; +{ + _x setVariable [QGVARMAIN(isInitialized), nil]; + _x setVariable [QGVARMAIN(exclude), nil]; + _x setVariable [QGVARMAIN(id), nil]; + _x setVariable [QGVARMAIN(unitType), nil]; +} count (allUnits + allDeadMen + vehicles); +GVAR(nextId) = 0; diff --git a/x/ocap/addons/recorder/fnc_startRecording.sqf b/x/ocap/addons/recorder/fnc_startRecording.sqf index d96689a..796d272 100644 --- a/x/ocap/addons/recorder/fnc_startRecording.sqf +++ b/x/ocap/addons/recorder/fnc_startRecording.sqf @@ -9,7 +9,7 @@ if (!GVARMAIN(enabled)) exitWith {}; // if recording started earlier and startTime has been noted, only restart the capture loop with any updated settings. -if (GVAR(recording)) exitWith { +if (GVAR(recording) && GVAR(captureFrameNo) > 10) exitWith { OCAPEXTLOG(["OCAP was asked to record and is already recording!"]); [ ["OCAP was asked to record", 1, [1, 1, 1, 1]], @@ -25,24 +25,21 @@ _systemTimeFormat append (systemTimeUTC apply {if (_x < 10) then {"0" + str _x} private _missionDateFormat = ["%1-%2-%3T%4:%5:00"]; _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {str _x}}); -[QGVARMAIN(customEvent), ["generalEvent", "Recording started."]] call CBA_fnc_serverEvent; -["OCAP began recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; - [[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording start [{!isNull player}, { + player setDiarySubjectPicture [ + "OCAPInfo", + "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ]; player createDiaryRecord [ "OCAPInfo", [ "Status", format["OCAP started recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] - ], taskNull, "", false - ]; - player setDiarySubjectPicture [ - "OCAPInfo", - "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" + ] ]; }, _this] call CBA_fnc_waitUntilAndExecute; -}] remoteExecCall ["call", [0, -2] select isDedicated, true]; +}] remoteExec ["call", [0, -2] select isDedicated, true]; if (GVAR(captureFrameNo) == 0) then { // Notify the extension @@ -51,5 +48,8 @@ if (GVAR(captureFrameNo) == 0) then { call FUNC(captureLoop); }; +[QGVARMAIN(customEvent), ["generalEvent", "Recording started."]] call CBA_fnc_serverEvent; +["OCAP began recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; + // Log times [] call FUNC(updateTime); diff --git a/x/ocap/addons/recorder/fnc_stopRecording.sqf b/x/ocap/addons/recorder/fnc_stopRecording.sqf index 5510f75..7e0e992 100644 --- a/x/ocap/addons/recorder/fnc_stopRecording.sqf +++ b/x/ocap/addons/recorder/fnc_stopRecording.sqf @@ -8,9 +8,6 @@ _missionDateFormat append (date apply {if (_x < 10) then {"0" + str _x} else {st [QGVARMAIN(customEvent), ["generalEvent", "Recording paused."]] call CBA_fnc_serverEvent; ["OCAP stopped recording", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; -GVAR(recording) = false; -publicVariable QGVAR(recording); - [[cba_missionTime, format _missionDateFormat, format _systemTimeFormat], { // add diary entry for clients on recording pause [{!isNull player}, { player createDiaryRecord [ @@ -18,14 +15,17 @@ publicVariable QGVAR(recording); [ "Status", format["OCAP stopped recording.
In-Mission Time Elapsed: %1
Mission World Time: %2
System Time UTC: %3
", _this#0, _this#1, _this#2] - ], taskNull, "", false + ] ]; player setDiarySubjectPicture [ "OCAPInfo", "\A3\ui_f\data\igui\cfg\simpleTasks\types\use_ca.paa" ]; }, _this] call CBA_fnc_waitUntilAndExecute; -}] remoteExecCall ["call", [0, -2] select isDedicated, true]; +}] remoteExec ["call", [0, -2] select isDedicated, true]; // Log times [] call FUNC(updateTime); + +GVAR(recording) = false; +publicVariable QGVAR(recording); From bef670d0ac2a92586a471664c741e97fb2200d2b Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Mon, 17 Oct 2022 20:33:14 -0400 Subject: [PATCH 21/26] minor fixes to eventWeaponText, weaponState command misbehaving remotely --- x/ocap/addons/main/script_macros.hpp | 4 +- x/ocap/addons/recorder/fnc_eh_firedMan.sqf | 64 +++++++++++++------ .../recorder/fnc_getEventWeaponText.sqf | 28 ++++---- x/ocap/addons/recorder/fnc_handleMarkers.sqf | 22 +++---- 4 files changed, 71 insertions(+), 47 deletions(-) diff --git a/x/ocap/addons/main/script_macros.hpp b/x/ocap/addons/main/script_macros.hpp index 46e1e5b..e8c743a 100644 --- a/x/ocap/addons/main/script_macros.hpp +++ b/x/ocap/addons/main/script_macros.hpp @@ -11,9 +11,9 @@ // The current version of OCAP. #define VERSION 2.0 -#define VERSION_STR 2.0.0-RC2 +#define VERSION_STR 2.0.0 #define VERSION_AR 2,0,0 -#define VERSION_REQUIRED 2.08 +#define VERSION_REQUIRED 2.10 // define: LOG // Used for logging messages via the extension. diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf index 70ba693..b6386c1 100644 --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf @@ -33,7 +33,7 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_projectile", "_vehicle"]; -private _initialProjPos = getPos _projectile; +private _initialProjPos = getPosASL _projectile; if (getPos _firer distance _initialProjPos > 50 || vehicle _firer isKindOf "Air") then { // if projectile in unscheduled environment is > 50m from FiredMan then likely remote controlled // we should find the actual firing entity @@ -63,19 +63,28 @@ if (_firerId == -1) exitWith {}; ([_weapon, _muzzle, _magazine, _ammo] call FUNC(getWeaponDisplayData)) params ["_muzzleDisp", "_magDisp"]; private _wepString = ""; -if (_muzzleDisp find _magDisp == -1 && _magDisp isNotEqualTo "") then { - _wepString = format["%1 [%2]", _muzzleDisp, _magDisp]; -} else { - _wepString = _muzzleDisp; -}; if (!isNull _vehicle) then { _wepString = format["%1 [%2]", (configOf _vehicle) call BIS_fnc_displayName, _wepString]; +} else { + _wepString = format["%1 [%2]", _muzzleDisp, _magDisp]; }; _firer setVariable [QGVARMAIN(lastFired), _wepString]; (vehicle _firer) setVariable [QGVARMAIN(lastFired), _wepString]; + +// _ammoSimType +// "ShotGrenade" // M67 +// "ShotRocket" // S-8 +// "ShotMissile" // R-27 +// "ShotShell" // VOG-17M, HE40mm +// "ShotMine" // Satchel charge +// "ShotIlluminating" // 40mm_green Flare +// "ShotSmokeX"; // M18 Smoke +// "ShotCM" // Plane flares +// "ShotSubmunition" // Hind minigun, cluster artillery _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation"); + // Save marker data to projectile namespace for EH later _projectile setVariable [QGVAR(firer), _firer]; _projectile setVariable [QGVAR(firerId), _firerId]; @@ -91,18 +100,10 @@ _projectile addEventHandler ["HitExplosion", { [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit); }]; -// _ammoSimType -// "ShotGrenade" // M67 -// "ShotRocket" // S-8 -// "ShotMissile" // R-27 -// "ShotShell" // VOG-17M, HE40mm -// "ShotMine" // Satchel charge -// "ShotIlluminating" // 40mm_green Flare -// "ShotSmokeX"; // M18 Smoke -// "ShotCM" // Plane flares -// "ShotSubmunition" // Hind minigun, cluster artillery +// BULLET PROJECTILES + if (_ammoSimType isEqualTo "shotBullet") exitWith { // Bullet projectiles _projectile addEventHandler ["Deleted", { @@ -141,7 +142,7 @@ _firerPos = getPosASL _firer; -// Move marker, then delete marker, when projectile is deleted +// Move marker, then delete marker, when projectile is deleted or explodes _projectile addEventHandler ["Deleted", { params ["_projectile"]; _markName = _projectile getVariable QGVAR(markName); @@ -149,9 +150,22 @@ _projectile addEventHandler ["Deleted", { [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent; [{ [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent; - }, _markName, GVAR(frameCaptureDelay)] call CBA_fnc_waitAndExecute; + }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute; }]; +_projectile addEventHandler ["Explode", { + params ["_projectile", "_pos", "_velocity"]; + _markName = _projectile getVariable QGVAR(markName); + _firer = _projectile getVariable QGVAR(firer); + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent; + [{ + [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent; + }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute; +}]; + + + + // Add to debug if (GVARMAIN(isDebug)) then { // add to map draw array @@ -226,7 +240,7 @@ switch (true) do { }]; }; - // Move marker, then delete marker, when projectile is deleted + // Move marker, then delete marker, when projectile is deleted or explodes _submunitionProjectile addEventHandler ["Deleted", { params ["_projectile"]; _markName = _projectile getVariable QGVAR(markName); @@ -234,7 +248,17 @@ switch (true) do { [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent; [{ [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent; - }, _markName, GVAR(frameCaptureDelay)] call CBA_fnc_waitAndExecute; + }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute; + }]; + + _projectile addEventHandler ["Explode", { + params ["_projectile", "_pos", "_velocity"]; + _markName = _projectile getVariable QGVAR(markName); + _firer = _projectile getVariable QGVAR(firer); + [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent; + [{ + [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent; + }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute; }]; // Add to debug diff --git a/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf index 8ccab7b..904488d 100644 --- a/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf +++ b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf @@ -29,17 +29,17 @@ params ["_instigator"]; if (isNull _instigator) exitWith {""}; -// if !(_instigator call CBA_fnc_isPerson) then { -// _instigator = _instigator call { -// if(alive(gunner _this))exitWith{gunner _this}; -// if(alive(commander _this))exitWith{commander _this}; -// if(alive(driver _this))exitWith{driver _this}; -// effectiveCommander _this -// }; -// }; - -// if (_instigator call CBA_fnc_isPerson) then { - (weaponstate _instigator) params ["_weapon", "_muzzle", "_mode", "_magazine"]; +if !(_instigator call CBA_fnc_isPerson) then { + _instigator = _instigator call { + if(alive(gunner _this))exitWith{gunner _this}; + if(alive(commander _this))exitWith{commander _this}; + if(alive(driver _this))exitWith{driver _this}; + effectiveCommander _this + }; +}; + +if (_instigator call CBA_fnc_isPerson) then { + (_instigator weaponstate (currentWeapon _instigator)) params ["_weapon", "_muzzle", "_mode", "_magazine"]; ([_weapon, _muzzle, _magazine] call FUNC(getWeaponDisplayData)) params ["_muzDisp", "_magDisp"]; _instigator getVariable [ @@ -50,6 +50,6 @@ if (isNull _instigator) exitWith {""}; _magDisp ] ]; -// } else { - // getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); -// }; +} else { + getText(configFile >> "CfgVehicles" >> (typeOf vehicle _instigator) >> "displayName"); +}; diff --git a/x/ocap/addons/recorder/fnc_handleMarkers.sqf b/x/ocap/addons/recorder/fnc_handleMarkers.sqf index 0716c94..82b65dd 100644 --- a/x/ocap/addons/recorder/fnc_handleMarkers.sqf +++ b/x/ocap/addons/recorder/fnc_handleMarkers.sqf @@ -251,17 +251,17 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), { { private _marker = _x; - // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server - // if value is undefined, then skip - private _isExcluded = false; - if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { - { - if ((_marker) find _x >= 0) exitWith { - _isExcluded = true; - }; - } forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)); - }; - if (_isExcluded) then {continue}; + // check for excluded values in marker name. if name contains at least one value, skip sending traffic to server + // if value is undefined, then skip + private _isExcluded = false; + if (!isNil QEGVAR(settings,excludeMarkerFromRecord)) then { + { + if ((_marker) find _x >= 0) exitWith { + _isExcluded = true; + }; + } forEach (parseSimpleArray EGVAR(settings,excludeMarkerFromRecord)); + }; + if (_isExcluded) then {continue}; // "Started polling starting markers" remoteExec ["hint", 0]; From 5d379dc1c09e0f17a601461f34cee6b7aa734919 Mon Sep 17 00:00:00 2001 From: IndigoFox Date: Wed, 19 Oct 2022 16:31:35 -0400 Subject: [PATCH 22/26] massive documentation write-up and cleanup --- docs/files/extension/fnc_sendData-sqf.html | 43 + .../files/extension/script_component-hpp.html | 45 + docs/files/main/XEH_preInit-sqf.html | 49 + docs/files/main/script_component-hpp.html | 45 + docs/files/main/script_macros-hpp.html | 65 + docs/files/recorder/XEH_preInit-sqf.html | 87 + .../files/recorder/fnc_aceExplosives-sqf.html | 43 + .../recorder/fnc_addEventMission-sqf.html | 94 + .../fnc_addUnitEventHandlers-sqf.html | 43 + .../recorder/fnc_adminUIcontrol-sqf.html | 47 + docs/files/recorder/fnc_captureLoop-sqf.html | 47 + docs/files/recorder/fnc_eh_connected-sqf.html | 43 + .../recorder/fnc_eh_disconnected-sqf.html | 43 + docs/files/recorder/fnc_eh_firedMan-sqf.html | 57 + docs/files/recorder/fnc_eh_killed-sqf.html | 43 + .../fnc_eh_onUserAdminStateChanged-sqf.html | 43 + .../recorder/fnc_eh_projectileHit-sqf.html | 43 + .../recorder/fnc_entityMonitors-sqf.html | 47 + docs/files/recorder/fnc_exportData-sqf.html | 56 + .../recorder/fnc_getAmmoMarkerData-sqf.html | 43 + docs/files/recorder/fnc_getClass-sqf.html | 43 + .../recorder/fnc_getEventWeaponText-sqf.html | 43 + .../files/recorder/fnc_getInstigator-sqf.html | 43 + docs/files/recorder/fnc_getUnitType-sqf.html | 43 + .../fnc_getWeaponDisplayData-sqf.html | 43 + .../recorder/fnc_handleCustomEvent-sqf.html | 65 + .../files/recorder/fnc_handleMarkers-sqf.html | 61 + docs/files/recorder/fnc_init-sqf.html | 61 + docs/files/recorder/fnc_isKindOfApc-sqf.html | 43 + .../recorder/fnc_projectileMonitors-sqf.html | 49 + .../recorder/fnc_startRecording-sqf.html | 43 + .../files/recorder/fnc_stopRecording-sqf.html | 43 + docs/files/recorder/fnc_updateTime-sqf.html | 43 + docs/files/recorder/script_component-hpp.html | 45 + docs/index.html | 1 + docs/index/CBAEvents.html | 54 + docs/index/CBASettings.html | 62 + docs/index/EventHandlers.html | 54 + docs/index/Files.html | 42 + docs/index/Functions.html | 66 + docs/index/General.html | 82 + docs/index/General2.html | 54 + docs/index/Macros.html | 58 + docs/index/Variables.html | 78 + docs/javascript/main.js | 841 +++++++++ docs/javascript/prettify.js | 1526 +++++++++++++++++ docs/javascript/searchdata.js | 242 +++ docs/search/CBAEventsA.html | 20 + docs/search/CBAEventsC.html | 20 + docs/search/CBAEventsE.html | 20 + docs/search/CBAEventsH.html | 20 + docs/search/CBAEventsP.html | 20 + docs/search/CBAEventsR.html | 20 + docs/search/CBASettingsA.html | 20 + docs/search/CBASettingsE.html | 20 + docs/search/CBASettingsF.html | 20 + docs/search/CBASettingsI.html | 20 + docs/search/CBASettingsM.html | 20 + docs/search/CBASettingsP.html | 20 + docs/search/CBASettingsS.html | 20 + docs/search/CBASettingsT.html | 20 + docs/search/EventHandlersD.html | 20 + docs/search/EventHandlersE.html | 20 + docs/search/EventHandlersH.html | 20 + docs/search/EventHandlersM.html | 20 + docs/search/EventHandlersO.html | 20 + docs/search/EventHandlersP.html | 20 + docs/search/FilesC.html | 20 + docs/search/FilesF.html | 20 + docs/search/FilesS.html | 20 + docs/search/FunctionsA.html | 20 + docs/search/FunctionsC.html | 20 + docs/search/FunctionsE.html | 20 + docs/search/FunctionsG.html | 20 + docs/search/FunctionsH.html | 20 + docs/search/FunctionsI.html | 20 + docs/search/FunctionsP.html | 20 + docs/search/FunctionsS.html | 20 + docs/search/FunctionsU.html | 20 + docs/search/GeneralA.html | 20 + docs/search/GeneralB.html | 20 + docs/search/GeneralC.html | 20 + docs/search/GeneralD.html | 20 + docs/search/GeneralE.html | 20 + docs/search/GeneralF.html | 20 + docs/search/GeneralG.html | 20 + docs/search/GeneralH.html | 20 + docs/search/GeneralI.html | 20 + docs/search/GeneralL.html | 20 + docs/search/GeneralM.html | 20 + docs/search/GeneralN.html | 20 + docs/search/GeneralO.html | 20 + docs/search/GeneralP.html | 20 + docs/search/GeneralR.html | 20 + docs/search/GeneralS.html | 20 + docs/search/GeneralT.html | 20 + docs/search/GeneralU.html | 20 + docs/search/GeneralV.html | 20 + docs/search/MacrosA.html | 20 + docs/search/MacrosB.html | 20 + docs/search/MacrosC.html | 20 + docs/search/MacrosL.html | 20 + docs/search/MacrosP.html | 20 + docs/search/MacrosS.html | 20 + docs/search/MacrosV.html | 20 + docs/search/NoResults.html | 15 + docs/search/VariablesA.html | 20 + docs/search/VariablesC.html | 20 + docs/search/VariablesE.html | 20 + docs/search/VariablesF.html | 20 + docs/search/VariablesH.html | 20 + docs/search/VariablesL.html | 20 + docs/search/VariablesM.html | 20 + docs/search/VariablesN.html | 20 + docs/search/VariablesP.html | 20 + docs/search/VariablesR.html | 20 + docs/search/VariablesT.html | 20 + docs/search/VariablesV.html | 20 + docs/styles/main.css | 828 +++++++++ x/ocap/addons/extension/fnc_sendData.sqf | 8 +- x/ocap/addons/extension/script_component.hpp | 4 + x/ocap/addons/main/XEH_preInit.sqf | 41 +- x/ocap/addons/main/script_component.hpp | 4 + x/ocap/addons/main/script_macros.hpp | 38 +- x/ocap/addons/recorder/XEH_preInit.sqf | 182 +- x/ocap/addons/recorder/fnc_aceExplosives.sqf | 134 +- x/ocap/addons/recorder/fnc_aceThrowing.sqf | 129 -- .../addons/recorder/fnc_addEventMission.sqf | 191 ++- .../recorder/fnc_addUnitEventHandlers.sqf | 23 +- x/ocap/addons/recorder/fnc_adminUIcontrol.sqf | 31 +- x/ocap/addons/recorder/fnc_captureLoop.sqf | 17 +- x/ocap/addons/recorder/fnc_eh_connected.sqf | 28 +- .../addons/recorder/fnc_eh_disconnected.sqf | 27 + x/ocap/addons/recorder/fnc_eh_firedMan.sqf | 83 +- x/ocap/addons/recorder/fnc_eh_killed.sqf | 11 +- .../fnc_eh_onUserAdminStateChanged.sqf | 25 + .../addons/recorder/fnc_eh_projectileHit.sqf | 15 +- x/ocap/addons/recorder/fnc_entityMonitors.sqf | 27 + x/ocap/addons/recorder/fnc_exportData.sqf | 22 +- .../addons/recorder/fnc_getAmmoMarkerData.sqf | 29 + x/ocap/addons/recorder/fnc_getClass.sqf | 23 + x/ocap/addons/recorder/fnc_getDelay.sqf | 41 - .../recorder/fnc_getEventWeaponText.sqf | 12 +- x/ocap/addons/recorder/fnc_getInstigator.sqf | 10 +- x/ocap/addons/recorder/fnc_getUnitType.sqf | 10 +- .../recorder/fnc_getWeaponDisplayData.sqf | 29 + .../addons/recorder/fnc_handleCustomEvent.sqf | 101 +- x/ocap/addons/recorder/fnc_handleMarkers.sqf | 53 +- x/ocap/addons/recorder/fnc_init.sqf | 55 +- x/ocap/addons/recorder/fnc_isKindOfApc.sqf | 23 + .../recorder/fnc_projectileMonitors.sqf | 37 + x/ocap/addons/recorder/fnc_startRecording.sqf | 30 +- x/ocap/addons/recorder/fnc_stopRecording.sqf | 25 + x/ocap/addons/recorder/fnc_updateTime.sqf | 9 +- x/ocap/addons/recorder/script_component.hpp | 4 + 155 files changed, 8196 insertions(+), 432 deletions(-) create mode 100644 docs/files/extension/fnc_sendData-sqf.html create mode 100644 docs/files/extension/script_component-hpp.html create mode 100644 docs/files/main/XEH_preInit-sqf.html create mode 100644 docs/files/main/script_component-hpp.html create mode 100644 docs/files/main/script_macros-hpp.html create mode 100644 docs/files/recorder/XEH_preInit-sqf.html create mode 100644 docs/files/recorder/fnc_aceExplosives-sqf.html create mode 100644 docs/files/recorder/fnc_addEventMission-sqf.html create mode 100644 docs/files/recorder/fnc_addUnitEventHandlers-sqf.html create mode 100644 docs/files/recorder/fnc_adminUIcontrol-sqf.html create mode 100644 docs/files/recorder/fnc_captureLoop-sqf.html create mode 100644 docs/files/recorder/fnc_eh_connected-sqf.html create mode 100644 docs/files/recorder/fnc_eh_disconnected-sqf.html create mode 100644 docs/files/recorder/fnc_eh_firedMan-sqf.html create mode 100644 docs/files/recorder/fnc_eh_killed-sqf.html create mode 100644 docs/files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html create mode 100644 docs/files/recorder/fnc_eh_projectileHit-sqf.html create mode 100644 docs/files/recorder/fnc_entityMonitors-sqf.html create mode 100644 docs/files/recorder/fnc_exportData-sqf.html create mode 100644 docs/files/recorder/fnc_getAmmoMarkerData-sqf.html create mode 100644 docs/files/recorder/fnc_getClass-sqf.html create mode 100644 docs/files/recorder/fnc_getEventWeaponText-sqf.html create mode 100644 docs/files/recorder/fnc_getInstigator-sqf.html create mode 100644 docs/files/recorder/fnc_getUnitType-sqf.html create mode 100644 docs/files/recorder/fnc_getWeaponDisplayData-sqf.html create mode 100644 docs/files/recorder/fnc_handleCustomEvent-sqf.html create mode 100644 docs/files/recorder/fnc_handleMarkers-sqf.html create mode 100644 docs/files/recorder/fnc_init-sqf.html create mode 100644 docs/files/recorder/fnc_isKindOfApc-sqf.html create mode 100644 docs/files/recorder/fnc_projectileMonitors-sqf.html create mode 100644 docs/files/recorder/fnc_startRecording-sqf.html create mode 100644 docs/files/recorder/fnc_stopRecording-sqf.html create mode 100644 docs/files/recorder/fnc_updateTime-sqf.html create mode 100644 docs/files/recorder/script_component-hpp.html create mode 100644 docs/index.html create mode 100644 docs/index/CBAEvents.html create mode 100644 docs/index/CBASettings.html create mode 100644 docs/index/EventHandlers.html create mode 100644 docs/index/Files.html create mode 100644 docs/index/Functions.html create mode 100644 docs/index/General.html create mode 100644 docs/index/General2.html create mode 100644 docs/index/Macros.html create mode 100644 docs/index/Variables.html create mode 100644 docs/javascript/main.js create mode 100644 docs/javascript/prettify.js create mode 100644 docs/javascript/searchdata.js create mode 100644 docs/search/CBAEventsA.html create mode 100644 docs/search/CBAEventsC.html create mode 100644 docs/search/CBAEventsE.html create mode 100644 docs/search/CBAEventsH.html create mode 100644 docs/search/CBAEventsP.html create mode 100644 docs/search/CBAEventsR.html create mode 100644 docs/search/CBASettingsA.html create mode 100644 docs/search/CBASettingsE.html create mode 100644 docs/search/CBASettingsF.html create mode 100644 docs/search/CBASettingsI.html create mode 100644 docs/search/CBASettingsM.html create mode 100644 docs/search/CBASettingsP.html create mode 100644 docs/search/CBASettingsS.html create mode 100644 docs/search/CBASettingsT.html create mode 100644 docs/search/EventHandlersD.html create mode 100644 docs/search/EventHandlersE.html create mode 100644 docs/search/EventHandlersH.html create mode 100644 docs/search/EventHandlersM.html create mode 100644 docs/search/EventHandlersO.html create mode 100644 docs/search/EventHandlersP.html create mode 100644 docs/search/FilesC.html create mode 100644 docs/search/FilesF.html create mode 100644 docs/search/FilesS.html create mode 100644 docs/search/FunctionsA.html create mode 100644 docs/search/FunctionsC.html create mode 100644 docs/search/FunctionsE.html create mode 100644 docs/search/FunctionsG.html create mode 100644 docs/search/FunctionsH.html create mode 100644 docs/search/FunctionsI.html create mode 100644 docs/search/FunctionsP.html create mode 100644 docs/search/FunctionsS.html create mode 100644 docs/search/FunctionsU.html create mode 100644 docs/search/GeneralA.html create mode 100644 docs/search/GeneralB.html create mode 100644 docs/search/GeneralC.html create mode 100644 docs/search/GeneralD.html create mode 100644 docs/search/GeneralE.html create mode 100644 docs/search/GeneralF.html create mode 100644 docs/search/GeneralG.html create mode 100644 docs/search/GeneralH.html create mode 100644 docs/search/GeneralI.html create mode 100644 docs/search/GeneralL.html create mode 100644 docs/search/GeneralM.html create mode 100644 docs/search/GeneralN.html create mode 100644 docs/search/GeneralO.html create mode 100644 docs/search/GeneralP.html create mode 100644 docs/search/GeneralR.html create mode 100644 docs/search/GeneralS.html create mode 100644 docs/search/GeneralT.html create mode 100644 docs/search/GeneralU.html create mode 100644 docs/search/GeneralV.html create mode 100644 docs/search/MacrosA.html create mode 100644 docs/search/MacrosB.html create mode 100644 docs/search/MacrosC.html create mode 100644 docs/search/MacrosL.html create mode 100644 docs/search/MacrosP.html create mode 100644 docs/search/MacrosS.html create mode 100644 docs/search/MacrosV.html create mode 100644 docs/search/NoResults.html create mode 100644 docs/search/VariablesA.html create mode 100644 docs/search/VariablesC.html create mode 100644 docs/search/VariablesE.html create mode 100644 docs/search/VariablesF.html create mode 100644 docs/search/VariablesH.html create mode 100644 docs/search/VariablesL.html create mode 100644 docs/search/VariablesM.html create mode 100644 docs/search/VariablesN.html create mode 100644 docs/search/VariablesP.html create mode 100644 docs/search/VariablesR.html create mode 100644 docs/search/VariablesT.html create mode 100644 docs/search/VariablesV.html create mode 100644 docs/styles/main.css delete mode 100644 x/ocap/addons/recorder/fnc_aceThrowing.sqf delete mode 100644 x/ocap/addons/recorder/fnc_getDelay.sqf diff --git a/docs/files/extension/fnc_sendData-sqf.html b/docs/files/extension/fnc_sendData-sqf.html new file mode 100644 index 0000000..9e29049 --- /dev/null +++ b/docs/files/extension/fnc_sendData-sqf.html @@ -0,0 +1,43 @@ + + +fnc_sendData.sqf - OCAP2 + + + + + + + + + +

fnc_sendData.sqf

Summary
fnc_sendData.sqf
Functions
OCAP_extension_fnc_sendDataManages raw extension calls and returns values / logs errors where relevant.
+ +

Functions

+ +

OCAP_extension_fnc_sendData

Description

Manages raw extension calls and returns values / logs errors where relevant.

Parameters

_commandThe extension command to call [String]
_argsThe arguments to send [Array]

Returns

Depends

Examples

[":VERSION", []] call EFUNC(extension,sendData);

Public

No

Author

Dell, Zealot

+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/files/extension/script_component-hpp.html b/docs/files/extension/script_component-hpp.html new file mode 100644 index 0000000..64c3cb7 --- /dev/null +++ b/docs/files/extension/script_component-hpp.html @@ -0,0 +1,45 @@ + + +script_component.hpp - OCAP2 + + + + + + + + + +
+ +

Macros

+ +

COMPONENT

#define COMPONENT extension
+ +

COMPONENT_BEAUTIFIED

#define COMPONENT_BEAUTIFIED Extension
+ +
+ + + + + + + + + + +
#define COMPONENT extension
#define COMPONENT_BEAUTIFIED Extension
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/main/XEH_preInit-sqf.html b/docs/files/main/XEH_preInit-sqf.html new file mode 100644 index 0000000..a51415b --- /dev/null +++ b/docs/files/main/XEH_preInit-sqf.html @@ -0,0 +1,49 @@ + + +CBA Settings - OCAP2 + + + + + + + + + +

CBA Settings

Summary
CBA Settings
Core
CBA Settings
OCAP_enabledTurns on or off most recording functionality.
OCAP_isDebugEnables increased logging of addon actions.
OCAP_administratorListAn array or server-visible variable referencing one that is a list of playerUIDs.
+ +

Core

Summary
CBA Settings
OCAP_enabledTurns on or off most recording functionality.
OCAP_isDebugEnables increased logging of addon actions.
OCAP_administratorListAn array or server-visible variable referencing one that is a list of playerUIDs.
+ +

CBA Settings

+ +

OCAP_enabled

Description

Turns on or off most recording functionality.  Will not reset anything from existing session, will just stop recording most new data.  Note: For record/pause switching, use the CBA events!  Default: true

Setting Name

Recording Enabled

Value Type

Boolean

+ +

OCAP_isDebug

Description

Enables increased logging of addon actions.  Default: false

Setting Name

Debug Mode

Value Type

Boolean

+ +

OCAP_administratorList

Description

An array or server-visible variable referencing one that is a list of playerUIDs.  Additional briefing diary or UI elements may be available for more accessible control over OCAP’s features.  Takes effect on player server connection.  Format: [] OR myAdminPUIDs | Default: []

Setting Name

Administrators

Value Type

Stringified Array

Example

"['76561198000000000', '76561198000000001']"
+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/files/main/script_component-hpp.html b/docs/files/main/script_component-hpp.html new file mode 100644 index 0000000..2cec9fc --- /dev/null +++ b/docs/files/main/script_component-hpp.html @@ -0,0 +1,45 @@ + + +script_component.hpp - OCAP2 + + + + + + + + + +
+ +

Macros

+ +

COMPONENT

#define COMPONENT main
+ +

COMPONENT_BEAUTIFIED

#define COMPONENT_BEAUTIFIED Main
+ +
+ + + + + + + + + + +
#define COMPONENT main
#define COMPONENT_BEAUTIFIED Main
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/main/script_macros-hpp.html b/docs/files/main/script_macros-hpp.html new file mode 100644 index 0000000..3eecaa0 --- /dev/null +++ b/docs/files/main/script_macros-hpp.html @@ -0,0 +1,65 @@ + + +script_macros.hpp - OCAP2 + + + + + + + + + +

script_macros.hpp

Defines macros imported to other functions

Summary
script_macros.hppDefines macros imported to other functions
Macros
PREFIX
COMPONENT_NAME
ADDONPREFIX_COMPONENT
VERSION
VERSION_STR
VERSION_AR
VERSION_REQUIRED
LOGUsed for logging messages to the extension (ocap-ext log file).
SYSCHATUsed for debug purposes to send a string to all clients with interfaces.
SHOULDSAVEEVENTSUsed to determine if events should currently be saved based on OCAP_recorder_recording and <OCAP_recorder_startTime>.
BOOLForces a true/false return of input.
ARR2Resolves arguments to array, used for entries to LOG that requires array input.
+ +

Macros

+ +

PREFIX

#define PREFIX OCAP
+ +

COMPONENT_NAME

#define COMPONENT_NAME QUOTE(PREFIX COMPONENT_BEAUTIFIED)
+ + + +

VERSION

#define VERSION 2.0
+ +

VERSION_STR

#define VERSION_STR 2.0.0
+ +

VERSION_AR

#define VERSION_AR 2,0,0
+ +

VERSION_REQUIRED

#define VERSION_REQUIRED 2.10
+ +

LOG

#define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData)

Used for logging messages to the extension (ocap-ext log file).

+ +

SYSCHAT

#define SYSCHAT remoteExec ["systemChat", [0, -2] select isDedicated]

Used for debug purposes to send a string to all clients with interfaces.

+ +

SHOULDSAVEEVENTS

#define SHOULDSAVEEVENTS (
   (missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] -1
)

Used to determine if events should currently be saved based on OCAP_recorder_recording and <OCAP_recorder_startTime>.

+ +

BOOL

#define BOOL(_cond) ([0,1] select (_cond))

Forces a true/false return of input.

+ +

ARR2

#define ARR2(_arg1,
_arg2) [_arg1, _arg2]

Resolves arguments to array, used for entries to LOG that requires array input.

+ +
+ + + + + + + + + + +
#define PREFIX OCAP
#define COMPONENT_NAME QUOTE(PREFIX COMPONENT_BEAUTIFIED)
#define COMPONENT main
#define VERSION 2.0
#define VERSION_STR 2.0.0
#define VERSION_AR 2,0,0
#define VERSION_REQUIRED 2.10
#define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData)
Used for logging messages to the extension (ocap-ext log file).
#define SYSCHAT remoteExec ["systemChat", [0, -2] select isDedicated]
Used for debug purposes to send a string to all clients with interfaces.
#define SHOULDSAVEEVENTS (
   (missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] -1
)
Used to determine if events should currently be saved based on OCAP_recorder_recording and OCAP_recorder_startTime.
Global variable that represents whether or not recording is active [Bool]
#define BOOL(_cond) ([0,1] select (_cond))
Forces a true/false return of input.
#define ARR2(_arg1,
_arg2) [_arg1, _arg2]
Resolves arguments to array, used for entries to LOG that requires array input.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/XEH_preInit-sqf.html b/docs/files/recorder/XEH_preInit-sqf.html new file mode 100644 index 0000000..28d43d2 --- /dev/null +++ b/docs/files/recorder/XEH_preInit-sqf.html @@ -0,0 +1,87 @@ + + +CBA Settings - OCAP2 + + + + + + + + + +

CBA Settings

Summary
CBA Settings
Auto-Start Settings
CBA Settings
OCAP_settings_autoStartAutomatically start OCAP recordings at session start.
OCAP_settings_minPlayerCountAuto-start will begin once this player count is reached.
Core
CBA Settings
OCAP_settings_frameCaptureDelayPositioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds.
OCAP_settings_preferACEUnconsciousIf true, will check ACE3 medical status on units.
Exclusions
CBA Settings
OCAP_settings_excludeClassFromRecordArray of object classnames that should be excluded from recording.
OCAP_settings_excludeKindFromRecordArray of classnames which, along with all child classes, should be excluded from recording.
OCAP_settings_excludeMarkerFromRecordArray of prefixes.
Extra Tracking
CBA Settings
OCAP_settings_trackTicketsWill track respawn ticket counts for missionNamespace and each playable faction every 30th frame.
OCAP_settings_trackTimesWill continuously track in-game world time during a mission.
OCAP_settings_trackTimeIntervalIf OCAP_settings_trackTimes is enabled, it will be checked every X capture frames.
Save/Export Settings
CBA Settings
OCAP_settings_saveTagIf not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
OCAP_settings_saveMissionEndedIf true, automatically save and export the mission when the MPEnded event fires.
OCAP_settings_saveOnEmptyWill automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting.
OCAP_settings_minMissionTimeA recording must be at least this long (in minutes) to auto-save.
+ +

Auto-Start Settings

Summary
CBA Settings
OCAP_settings_autoStartAutomatically start OCAP recordings at session start.
OCAP_settings_minPlayerCountAuto-start will begin once this player count is reached.
+ +

CBA Settings

+ +

OCAP_settings_autoStart

Description

Automatically start OCAP recordings at session start.  Default: true

Setting Name

Auto Start Recording

Value Type

Boolean

+ +

OCAP_settings_minPlayerCount

Description

Auto-start will begin once this player count is reached.  Default: 15

Setting Name

Minimum Player Count

Value Type

Number

+ +

Core

Summary
CBA Settings
OCAP_settings_frameCaptureDelayPositioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds.
OCAP_settings_preferACEUnconsciousIf true, will check ACE3 medical status on units.
+ +

CBA Settings

+ +

OCAP_settings_frameCaptureDelay

Description

Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds.  Default: 1

Setting Name

Frame Capture Delay

Value Type

Number

+ +

OCAP_settings_preferACEUnconscious

Description

If true, will check ACE3 medical status on units.  If false, or ACE3 isn’t loaded, fall back to vanilla.  Default: true

Setting Name

Use ACE3 Medical

Value Type

Boolean

+ +

Exclusions

Summary
CBA Settings
OCAP_settings_excludeClassFromRecordArray of object classnames that should be excluded from recording.
OCAP_settings_excludeKindFromRecordArray of classnames which, along with all child classes, should be excluded from recording.
OCAP_settings_excludeMarkerFromRecordArray of prefixes.
+ +

CBA Settings

+ +

OCAP_settings_excludeClassFromRecord

Description

Array of object classnames that should be excluded from recording.  Use single quotes!  Default: [‘ACE_friesAnchorBar’, ‘WeaponHolderSimulated’]

Setting Name

Classnames to Exclude

Value Type

Stringified Array

Example

"['ACE_friesAnchorBar']"
+ +

OCAP_settings_excludeKindFromRecord

Description

Array of classnames which, along with all child classes, should be excluded from recording.  Use single quotes!  Default: []

Setting Name

Object KindOfs to Exclude

Value Type

Stringified Array

Example

"['WeaponHolder']"
+ +

OCAP_settings_excludeMarkerFromRecord

Description

Array of prefixes.  Any markers matching these prefixes will be excluded from recording.  Use single quotes!  Default: [‘SystemMarker_’,’ACE_BFT_’]

Setting Name

Marker Prefixes To Exclude

Value Type

Stringified Array

Example

"['SystemMarker_','ACE_BFT_']"
+ +

Extra Tracking

Summary
CBA Settings
OCAP_settings_trackTicketsWill track respawn ticket counts for missionNamespace and each playable faction every 30th frame.
OCAP_settings_trackTimesWill continuously track in-game world time during a mission.
OCAP_settings_trackTimeIntervalIf OCAP_settings_trackTimes is enabled, it will be checked every X capture frames.
+ +

CBA Settings

+ +

OCAP_settings_trackTickets

Description

Will track respawn ticket counts for missionNamespace and each playable faction every 30th frame.  Default: true

Setting Name

Enable Ticket Tracking

Value Type

Boolean

+ +

OCAP_settings_trackTimes

Description

Will continuously track in-game world time during a mission.  Useful for accelerated/skipped time scenarios.  Default: false

Setting Name

Enable Mission Time Tracking

Value Type

Boolean

+ +

OCAP_settings_trackTimeInterval

Description

If OCAP_settings_trackTimes is enabled, it will be checked every X capture frames.  Default: 10

Setting Name

Mission Time Tracking Interval

Value Type

Number

+ +

Save/Export Settings

Summary
CBA Settings
OCAP_settings_saveTagIf not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
OCAP_settings_saveMissionEndedIf true, automatically save and export the mission when the MPEnded event fires.
OCAP_settings_saveOnEmptyWill automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting.
OCAP_settings_minMissionTimeA recording must be at least this long (in minutes) to auto-save.
+ +

CBA Settings

+ +

OCAP_settings_saveTag

Description

If not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.

Setting Name

Mission Type Tag

Value Type

String

+ +

OCAP_settings_saveMissionEnded

Description

If true, automatically save and export the mission when the MPEnded event fires.  Default: true

Setting Name

Auto-save on MPEnded Event

Value Type

Boolean

+ +

OCAP_settings_saveOnEmpty

Description

Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting.  Default: true

Setting Name

Auto-Save When No Players

Value Type

Boolean

+ +

OCAP_settings_minMissionTime

Description

A recording must be at least this long (in minutes) to auto-save.  Calling an OCAP_exportData CBA server event will override this restriction.  Default: 20

Setting Name

Required Duration to Sav

Value Type

Boolean

+ +
+ + + + + + + + + + +
Will continuously track in-game world time during a mission.
Used to stop recording & signal the extension to save and upload it to the web component.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_aceExplosives-sqf.html b/docs/files/recorder/fnc_aceExplosives-sqf.html new file mode 100644 index 0000000..2a08c66 --- /dev/null +++ b/docs/files/recorder/fnc_aceExplosives-sqf.html @@ -0,0 +1,43 @@ + + +fnc_aceExplosives.sqf - OCAP2 + + + + + + + + + +

fnc_aceExplosives.sqf

Summary
fnc_aceExplosives.sqf
Functions
OCAP_recorder_fnc_aceExplosivesAdds marker on the mine’s position to the recording timeline.
+ +

Functions

+ +

OCAP_recorder_fnc_aceExplosives

Description

Adds marker on the mine’s position to the recording timeline.  Then waits until the explosive is null (exploded) and indicates it with a 10-frame long red X before removing the marker.

Called by ace_explosives_place CBA listener.

Parameters

None

Returns

Nothing

Examples

call FUNC(aceExplosives);

Notes

Example of emitting event from ACE3 code:

[QGVAR(place), [_explosive, _dir, _pitch, _unit]] call CBA_fnc_globalEvent;

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + +
EGVAR(
   listener,
   aceExplosives
) = ["ace_explosives_place", { call FUNC(aceExplosives); }] call CBA_fnc_addEventHandler
Event listener for ACE3 global event indicating a mine has been placed and armed.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_addEventMission-sqf.html b/docs/files/recorder/fnc_addEventMission-sqf.html new file mode 100644 index 0000000..0285f34 --- /dev/null +++ b/docs/files/recorder/fnc_addEventMission-sqf.html @@ -0,0 +1,94 @@ + + +fnc_addEventMission.sqf - OCAP2 + + + + + + + + + +

fnc_addEventMission.sqf

Summary
fnc_addEventMission.sqf
Functions
OCAP_recorder_fnc_addEventMissionUsed for applying mission event handlers.
Event Handlers
Event Handlers
OCAP_EH_HandleDisconnectFired when a player leaves the mission by returning to lobby or disconnecting.
OCAP_EH_PlayerConnectedHandle for the “PlayerConnected” mission event handler.
OCAP_EH_OnUserAdminStateChangedHandle for the “OnUserAdminStateChange” mission event handler.
OCAP_EH_EntityKilledHandle for the “EntityKilled” mission event handler.
OCAP_EH_EntityRespawnedHandle for the “EntityRespawned” mission event handler.
OCAP_EH_MPEndedHandle for the “MPEnded” mission event handler.
OCAP_EH_EndedHandle for the “Ended” mission event handler.
CBA Events
Variables
CBA Listener Handles
CBA Events
ace_advanced_throwing_throwFiredXEHFired when a throwable is primed.
ace_explosives_placeEvent listener for ACE3 global event indicating a mine has been placed and armed.
OCAP_customEventEvent listener for custom event text to be added to the timeline.
OCAP_counterInitMeant for use in custom tracking of points or score between two sides.
OCAP_counterEventMeant for use in custom tracking of points or score between two sides.
OCAP_recordUsed to start or resume recording.
OCAP_pauseUsed to pause recording.
OCAP_exportDataUsed to stop recording & signal the extension to save and upload it to the web component.
+ +

Functions

+ +

OCAP_recorder_fnc_addEventMission

Description

Used for applying mission event handlers.  Applied during initialization of OCAP in OCAP_recorder_fnc_init.

Parameters

None

Returns

Nothing

Examples

call FUNC(addEventMission);

Public

No

Author

IndigoFox, Dell

+ +

Event Handlers

Summary
Event Handlers
OCAP_EH_HandleDisconnectFired when a player leaves the mission by returning to lobby or disconnecting.
OCAP_EH_PlayerConnectedHandle for the “PlayerConnected” mission event handler.
OCAP_EH_OnUserAdminStateChangedHandle for the “OnUserAdminStateChange” mission event handler.
OCAP_EH_EntityKilledHandle for the “EntityKilled” mission event handler.
OCAP_EH_EntityRespawnedHandle for the “EntityRespawned” mission event handler.
OCAP_EH_MPEndedHandle for the “MPEnded” mission event handler.
OCAP_EH_EndedHandle for the “Ended” mission event handler.
+ +

Event Handlers

+ +

OCAP_EH_HandleDisconnect

Fired when a player leaves the mission by returning to lobby or disconnecting.  Calls OCAP_recorder_fnc_eh_disconnected.

+ +

OCAP_EH_PlayerConnected

Handle for the “PlayerConnected” mission event handler.  Fired when a player joins the mission from lobby and appears in the world.  Calls OCAP_recorder_fnc_eh_connected.

+ +

OCAP_EH_OnUserAdminStateChanged

Handle for the “OnUserAdminStateChange” mission event handler.  Fired when a player’s admin status changes.  Calls OCAP_recorder_fnc_eh_onUserAdminStateChanged.

+ +

OCAP_EH_EntityKilled

Handle for the “EntityKilled” mission event handler.  Fired when an entity is killed.  Calls OCAP_recorder_fnc_eh_killed.

+ +

OCAP_EH_EntityRespawned

Handle for the “EntityRespawned” mission event handler.  Fired when an entity is respawned.  Sets new body to not-killed and calls OCAP_recorder_fnc_addUnitEventHandlers on it.  Then excludes corpse from further capture.

+ +

OCAP_EH_MPEnded

Handle for the “MPEnded” mission event handler.  Fired on the MPEnded mission event.  This is used to automatically save and export if OCAP_settings_saveMissionEnded is true and OCAP_settings_minMissionTime was reached.

+ +

OCAP_EH_Ended

Handle for the “Ended” mission event handler.  Fired on the singleplayer Ended mission event.  This is used to automatically save and export if OCAP_settings_saveMissionEnded is true and OCAP_settings_minMissionTime was reached.  Kept in just in case this event triggers.

+ +

CBA Events

Summary
Variables
CBA Listener Handles
CBA Events
ace_advanced_throwing_throwFiredXEHFired when a throwable is primed.
ace_explosives_placeEvent listener for ACE3 global event indicating a mine has been placed and armed.
OCAP_customEventEvent listener for custom event text to be added to the timeline.
OCAP_counterInitMeant for use in custom tracking of points or score between two sides.
OCAP_counterEventMeant for use in custom tracking of points or score between two sides.
OCAP_recordUsed to start or resume recording.
OCAP_pauseUsed to pause recording.
OCAP_exportDataUsed to stop recording & signal the extension to save and upload it to the web component.
+ +

Variables

+ +

CBA Listener Handles

OCAP_listener_aceThrowingHandle for ace_advanced_throwing_throwFiredXEH listener.
OCAP_listener_aceExplosivesHandle for ace_explosives_place listener.
OCAP_listener_customEventHandle for OCAP_customEvent listener.
OCAP_listener_counterInitHandle for OCAP_counterInit listener.
OCAP_listener_counterEventHandle for OCAP_counterEvent listener.
OCAP_counter_sidesSides that are tracked by the custom counter system.  [Array]
OCAP_listener_recordHandle for OCAP_record listener.
OCAP_listener_pauseHandle for OCAP_pause listener.
OCAP_listener_exportDataHandle for OCAP_exportData listener.
+ +

CBA Events

+ +

ace_advanced_throwing_throwFiredXEH

EGVAR(
   listener,
   aceThrowing
) = ["ace_advanced_throwing_throwFiredXEH", { _this call FUNC(eh_firedMan) }] call CBA_fnc_addEventHandler

Fired when a throwable is primed.  This is a global event the server will handle and forward to OCAP_recorder_fnc_eh_firedMan.  Created only if PBO “ace_advanced_throwing” is loaded.

+ +

ace_explosives_place

EGVAR(
   listener,
   aceExplosives
) = ["ace_explosives_place", { call FUNC(aceExplosives); }] call CBA_fnc_addEventHandler

Event listener for ACE3 global event indicating a mine has been placed and armed.  Calls OCAP_recorder_fnc_aceExplosives when triggered.  Created only if PBO “ace_explosives” is loaded.

+ +

OCAP_customEvent

Description

Event listener for custom event text to be added to the timeline.  Calls OCAP_recorder_fnc_handleCustomEvent when triggered.

Parameters

0Event name [String]
1Event data [Array]
1.0Always “generalEvent” [String]
1.1Custom event text [String]

Example

["OCAP_customEvent", ["generalEvent", "The warehouse has been secured!"]] call CBA_fnc_serverEvent;
+[QGVARMAIN(customEvent), ["generalEvent", "The warehouse has been secured!"]] call CBA_fnc_serverEvent;
+ +

OCAP_counterInit

Description

Meant for use in custom tracking of points or score between two sides.  Separate from BIS_fnc_respawnTickets.  Initializes the system.  Calls <OCAP_recorder_fnc_counterInit> when triggered.

Parameters

0Event name [String]
1Key/value for one or more sides [Array]
1.0Pair [Array]
1.0.0Side <SIDE>
1.0.1Initial value [Number]

Example

["OCAP_counterInit", [
+  [west, 0],
+  [east, 0]
+]] call CBA_fnc_serverEvent;
+
+[QGVARMAIN(counterInit), [
+  [west, 0],
+  [east, 0]
+]] call CBA_fnc_serverEvent;
+ +

OCAP_counterEvent

Description

Meant for use in custom tracking of points or score between two sides.  Separate from BIS_fnc_respawnTickets.  Updates the system.  Calls <OCAP_recorder_fnc_counterEvent> when triggered.

Parameters

0Event name [String]
1Event data [Array]
1.0Side <SIDE>
1.1Value to set [Number]

Example

["OCAP_counterEvent", [west, 1]] call CBA_fnc_serverEvent;
+ +

OCAP_record

Description

Used to start or resume recording.  Calls OCAP_recorder_fnc_startRecording when triggered.

Example

["OCAP_record"] call CBA_fnc_serverEvent;
+ +

OCAP_pause

Description

Used to pause recording.  Calls OCAP_recorder_fnc_stopRecording when triggered.

Example

["OCAP_pause"] call CBA_fnc_serverEvent;
+ +

OCAP_exportData

Description

Used to stop recording & signal the extension to save and upload it to the web component.  Calls OCAP_recorder_fnc_exportData when triggered.

Will always bypass OCAP_settings_minMissionTime.

Parameters

0Event name [String]
1Event data [Array]
1.0(optional) Winning side <SIDE>
1.1(optional) Message describing mission end [String]
1.2(optional) Custom save tag (overrides OCAP_settings_saveTag) [String]
+ +
+ + + + + + + + + + +
EGVAR(
   listener,
   aceThrowing
) = ["ace_advanced_throwing_throwFiredXEH", { _this call FUNC(eh_firedMan) }] call CBA_fnc_addEventHandler
Fired when a throwable is primed.
EGVAR(
   listener,
   aceExplosives
) = ["ace_explosives_place", { call FUNC(aceExplosives); }] call CBA_fnc_addEventHandler
Event listener for ACE3 global event indicating a mine has been placed and armed.
Initializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
This function uses the OCAP_EH_HandleDisconnect event handler to log “disconnected” events to the timeline.
This function uses the OCAP_EH_Connected event handler to log “connected” events to the timeline.
Uses OCAP_EH_OnUserAdminStateChanged to detect when someone is has logged in or out of the server and calls OCAP_recorder_fnc_adminUIControl to update the admin UI.
Tracks when a unit is killed.
Used for applying unit-specific event handlers to units during initialization.
If true, automatically save and export the mission when the MPEnded event fires.
A recording must be at least this long (in minutes) to auto-save.
Event listener for custom event text to be added to the timeline.
Meant for use in custom tracking of points or score between two sides.
Meant for use in custom tracking of points or score between two sides.
Used to start or resume recording.
Used to pause recording.
Used to stop recording & signal the extension to save and upload it to the web component.
Tracks bullet and non-bullet projectiles.
Adds marker on the mine’s position to the recording timeline.
Sends custom event data to the extension to save it to the timeline.
Begins recording the current mission.
Stops recording the current mission.
This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.
If not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_addUnitEventHandlers-sqf.html b/docs/files/recorder/fnc_addUnitEventHandlers-sqf.html new file mode 100644 index 0000000..217a2a9 --- /dev/null +++ b/docs/files/recorder/fnc_addUnitEventHandlers-sqf.html @@ -0,0 +1,43 @@ + + +fnc_addUnitEventHandlers.sqf - OCAP2 + + + + + + + + + +

fnc_addUnitEventHandlers.sqf

Summary
fnc_addUnitEventHandlers.sqf
Functions
OCAP_recorder_fnc_addUnitEventHandlersUsed for applying unit-specific event handlers to units during initialization.
+ +

Functions

+ +

OCAP_recorder_fnc_addUnitEventHandlers

Description

Used for applying unit-specific event handlers to units during initialization.  These event handlers will trigger on the server.

Applied during initialization of a unit in OCAP_recorder_fnc_captureLoop.

Note: Hit tracking moved to projectile EHs in OCAP_recorder_fnc_eh_firedMan

Parameters

_entityObject to apply event handlers to.  [Object]
_respawnDetermines if unit is initialized for the first time, or has respawned and does not need certain handlers reapplied.  [[Bool], default: false]

Returns

Nothing

Examples

[_unit] spawn FUNC(addUnitEventHandlers);

Public

No

Author

IndigoFox, Fank

+ +
+ + + + + + + + + + +
This function is run unscheduled and creates a CBA PerFrameHandler object, a logic object which executes code every specified interval (OCAP_settings_frameCaptureDelay) while a condition (SHOULDSAVEEVENTS) is true.
Tracks bullet and non-bullet projectiles.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_adminUIcontrol-sqf.html b/docs/files/recorder/fnc_adminUIcontrol-sqf.html new file mode 100644 index 0000000..94b9f04 --- /dev/null +++ b/docs/files/recorder/fnc_adminUIcontrol-sqf.html @@ -0,0 +1,47 @@ + + +fnc_adminUIControl.sqf - OCAP2 + + + + + + + + + +

fnc_adminUIControl.sqf

Summary
fnc_adminUIControl.sqf
Functions
OCAP_recorder_fnc_adminUIControlRuns checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
Variables
OCAP_hasAdminControlsApplied on units processed in OCAP_recorder_fnc_adminUIControl.
+ +

Functions

+ +

OCAP_recorder_fnc_adminUIControl

Description

Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.

Parameters

_PIDPlayerID indicating unique network client on the server [String]
_eventEvent that triggered this call [[String], one of: “connect”, “login”, “logout”]

Returns

Nothing

Examples

["1234567890", "connect"] call FUNC(adminUIControl);

Public

No

Author

IndigoFox

+ +

Variables

+ +

OCAP_hasAdminControls

Applied on units processed in OCAP_recorder_fnc_adminUIControl.  Indicates whether or not they have the administrative diary entry available.  Server missionNamespace only.

+ +
+ + + + + + + + + + +
Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
This function uses the OCAP_EH_Connected event handler to log “connected” events to the timeline.
An array or server-visible variable referencing one that is a list of playerUIDs.
Uses OCAP_EH_OnUserAdminStateChanged to detect when someone is has logged in or out of the server and calls OCAP_recorder_fnc_adminUIControl to update the admin UI.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_captureLoop-sqf.html b/docs/files/recorder/fnc_captureLoop-sqf.html new file mode 100644 index 0000000..4ddfc1a --- /dev/null +++ b/docs/files/recorder/fnc_captureLoop-sqf.html @@ -0,0 +1,47 @@ + + +fnc_captureLoop.sqf - OCAP2 + + + + + + + + + +

fnc_captureLoop.sqf

Summary
fnc_captureLoop.sqf
Functions
OCAP_recorder_fnc_captureLoopThis function is run unscheduled and creates a CBA PerFrameHandler object, a logic object which executes code every specified interval (OCAP_settings_frameCaptureDelay) while a condition (SHOULDSAVEEVENTS) is true.
Variables
OCAP_PFHObjectThe CBA PerFrameHandler object that is created and used to run the capture loop.
+ +

Functions

+ +

OCAP_recorder_fnc_captureLoop

Description

This function is run unscheduled and creates a CBA PerFrameHandler object, a logic object which executes code every specified interval (OCAP_settings_frameCaptureDelay) while a condition (SHOULDSAVEEVENTS) is true.

Iterates through units and vehicles, declares they exist, and conditionally sends their information to the extension to populate recording data.

This is the core processing loop that determines when new units enter the world, all the details about them, classifies which to exclude, and determines their health/life status.  It has both unit and vehicle tracking.

Parameters

None

Returns

Nothing

Examples

call FUNC(captureLoop);

Public

No

Author

Dell, Zealot, IndigoFox, Fank

+ +

Variables

+ +

OCAP_PFHObject

The CBA PerFrameHandler object that is created and used to run the capture loop.

+ +
+ + + + + + + + + + +
Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds.
#define SHOULDSAVEEVENTS (
   (missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] -1
)
Used to determine if events should currently be saved based on OCAP_recorder_recording and OCAP_recorder_startTime.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_eh_connected-sqf.html b/docs/files/recorder/fnc_eh_connected-sqf.html new file mode 100644 index 0000000..cc40877 --- /dev/null +++ b/docs/files/recorder/fnc_eh_connected-sqf.html @@ -0,0 +1,43 @@ + + +fnc_eh_connected.sqf - OCAP2 + + + + + + + + + +

fnc_eh_connected.sqf

Summary
fnc_eh_connected.sqf
Functions
OCAP_recorder_fnc_eh_connectedThis function uses the <OCAP_EH_Connected> event handler to log “connected” events to the timeline.
+ +

Functions

+ +

OCAP_recorder_fnc_eh_connected

Description

This function uses the <OCAP_EH_Connected> event handler to log “connected” events to the timeline.

It also calls OCAP_recorder_fnc_adminUIControl to apply the admin UI if the player is in OCAP_administratorList.

Parameters

See the wiki for details.  https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#PlayerConnected

Returns

Nothing

Examples

call FUNC(eh_connected);

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + +
Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
An array or server-visible variable referencing one that is a list of playerUIDs.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_eh_disconnected-sqf.html b/docs/files/recorder/fnc_eh_disconnected-sqf.html new file mode 100644 index 0000000..13093ce --- /dev/null +++ b/docs/files/recorder/fnc_eh_disconnected-sqf.html @@ -0,0 +1,43 @@ + + +fnc_eh_disconnected.sqf - OCAP2 + + + + + + + + + +

fnc_eh_disconnected.sqf

Summary
fnc_eh_disconnected.sqf
Functions
OCAP_recorder_fnc_eh_disconnectedThis function uses the OCAP_EH_HandleDisconnect event handler to log “disconnected” events to the timeline.
+ +

Functions

+ +

OCAP_recorder_fnc_eh_disconnected

Description

This function uses the OCAP_EH_HandleDisconnect event handler to log “disconnected” events to the timeline.  It will exclude any body left over from further recording.

Parameters

See the wiki for details.  https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#HandleDisconnect

Returns

False [Bool]

Examples

call FUNC(eh_disconnected);

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + +
Fired when a player leaves the mission by returning to lobby or disconnecting.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_eh_firedMan-sqf.html b/docs/files/recorder/fnc_eh_firedMan-sqf.html new file mode 100644 index 0000000..7335b91 --- /dev/null +++ b/docs/files/recorder/fnc_eh_firedMan-sqf.html @@ -0,0 +1,57 @@ + + +fnc_eh_firedMan.sqf - OCAP2 + + + + + + + + + +

fnc_eh_firedMan.sqf

Summary
fnc_eh_firedMan.sqf
Functions
OCAP_recorder_fnc_eh_firedManTracks bullet and non-bullet projectiles.
Variables
OCAP_lastFiredIndicates a formatted string of the last weapon and magazine type fired by the unit.
Event Handlers
Projectiles (Bullets)
Projectiles (Non-Bullets)
CBA Events
Projectiles
+ +

Functions

+ +

OCAP_recorder_fnc_eh_firedMan

Description

Tracks bullet and non-bullet projectiles.  This is the code triggered when a unit firing is detected by the “FiredMan” Event Handler applied to units during OCAP_recorder_fnc_addUnitEventHandlers.

Parameters

_firerUnit the event handler is assigned to (the instigator) [Object]
_weaponFired weapon [String]
_muzzleMuzzle that was used [String]
_modeCurrent mode of the fired weapon [String]
_ammoClassname of ammo used [String]
_magazineClassname of magazine used [String]
_projectileObject of the projectile that was shot out [Object]
_vehicleif weapon is vehicle weapon, otherwise objNull [Object]

Returns

Nothing

Examples

[_firer, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile, _vehicle] call FUNC(eh_firedMan);

Public

No

Author

IndigoFox, Dell

+ +

Variables

+ +

OCAP_lastFired

Indicates a formatted string of the last weapon and magazine type fired by the unit.  Used for logging hits/kills.  Applied to a firing unit.

+ +

Event Handlers

+ +

Projectiles (Bullets)

DeletedMakes extension call to draw a fire-line between the firer and the final destination.
ExplodeMakes extension call to draw a fire-line between the firer and the final destination.
HitPartTriggered when a projectile hits a part of a unit.  Calls OCAP_recorder_fnc_eh_projectileHit.
HitExplosionTriggered when a projectile explodes and damages a part of a unit.  Calls OCAP_recorder_fnc_eh_projectileHit.
+ +

Projectiles (Non-Bullets)

DeletedTriggered when a non-bullet projectile is deleted.  Updates marker position, then removes it 3 frames later.
ExplodeTriggered when a non-bullet projectile explodes.  Updates marker position, then removes it 3 frames later.
HitPartTriggered when a projectile hits a part of a unit.  Calls OCAP_recorder_fnc_eh_projectileHit.
HitExplosionTriggered when a projectile explodes and damages a part of a unit.  Calls OCAP_recorder_fnc_eh_projectileHit.
+ +

CBA Events

+ +

Projectiles

OCAP_recorder_addDebugBulletTriggered when a bullet is fired and the debug mode is enabled.  Shares recent bullet data to all clients.
OCAP_recorder_addDebugMagIconTriggered when a non-bullet projectile is fired and the debug mode is enabled.  Shares recent data to all clients.
+ +
+ + + + + + + + + + +
Used for applying unit-specific event handlers to units during initialization.
Tracks when a unit is hit/takes damage and saves to the timeline.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_eh_killed-sqf.html b/docs/files/recorder/fnc_eh_killed-sqf.html new file mode 100644 index 0000000..9ab0aff --- /dev/null +++ b/docs/files/recorder/fnc_eh_killed-sqf.html @@ -0,0 +1,43 @@ + + +fnc_eh_killed.sqf - OCAP2 + + + + + + + + + +

fnc_eh_killed.sqf

Summary
+ +

Functions

+ +

OCAP_recorder_fnc_eh_killed

Description

Tracks when a unit is killed.  This is the code triggered by the OCAP_EH_EntityKilled mission event handler.

Parameters

_unitObject the event handler is assigned to.  [Object]
_killerObject that killed the unit.  [Object]
_instigatorPerson who pulled the trigger.  [Object]
_useEffectssame as useEffects in setDamage alt syntax.  [Bool]

Returns

Nothing

Examples

call FUNC(eh_killed);

Public

No

Author

Dell, IndigoFox, Fank

+ +
+ + + + + + + + + + +
Handle for the “EntityKilled” mission event handler.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html b/docs/files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html new file mode 100644 index 0000000..251b308 --- /dev/null +++ b/docs/files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html @@ -0,0 +1,43 @@ + + +fnc_eh_onUserAdminStateChanged.sqf - OCAP2 + + + + + + + + + +

fnc_eh_onUserAdminStateChanged.sqf

Summary
fnc_eh_onUserAdminStateChanged.sqf
Functions
OCAP_recorder_fnc_eh_onUserAdminStateChangedUses OCAP_EH_OnUserAdminStateChanged to detect when someone is has logged in or out of the server and calls OCAP_recorder_fnc_adminUIControl to update the admin UI.
+ +

Functions

+ +

OCAP_recorder_fnc_eh_onUserAdminStateChanged

Description

Uses OCAP_EH_OnUserAdminStateChanged to detect when someone is has logged in or out of the server and calls OCAP_recorder_fnc_adminUIControl to update the admin UI.

Parameters

_networkIdThe network ID of the player who has logged in or out of the server [String]
_loggedInWhether the player has logged in or out as admin [Boolean]
_votedInWhether the player has been voted in or out of admin [Boolean]

Returns

Nothing

Examples

call FUNC(eh_onUserAdminStateChanged);

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + +
Handle for the “OnUserAdminStateChange” mission event handler.
Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_eh_projectileHit-sqf.html b/docs/files/recorder/fnc_eh_projectileHit-sqf.html new file mode 100644 index 0000000..723ca96 --- /dev/null +++ b/docs/files/recorder/fnc_eh_projectileHit-sqf.html @@ -0,0 +1,43 @@ + + +fnc_eh_projectileHit.sqf - OCAP2 + + + + + + + + + +

fnc_eh_projectileHit.sqf

Summary
fnc_eh_projectileHit.sqf
Functions
OCAP_recorder_fnc_eh_projectileHitTracks when a unit is hit/takes damage and saves to the timeline.
+ +

Functions

+ +

OCAP_recorder_fnc_eh_projectileHit

Description

Tracks when a unit is hit/takes damage and saves to the timeline.  This is called by projectile event handlers in OCAP_recorder_fnc_eh_firedMan.

Parameters

_unitObject that took damage [Object]
_shooterObject that caused the damage [Object]

Returns

Nothing

Examples

[_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);

Public

No

Author

IndigoFox, Fank

+ +
+ + + + + + + + + + +
Tracks bullet and non-bullet projectiles.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_entityMonitors-sqf.html b/docs/files/recorder/fnc_entityMonitors-sqf.html new file mode 100644 index 0000000..5c1d33b --- /dev/null +++ b/docs/files/recorder/fnc_entityMonitors-sqf.html @@ -0,0 +1,47 @@ + + +fnc_entityMonitors.sqf - OCAP2 + + + + + + + + + +

fnc_entityMonitors.sqf

Summary
fnc_entityMonitors.sqf
Functions
OCAP_recorder_fnc_entityMonitorsWhile debug mode is enabled, this function will render 2D icons and text representing all entities that have been initialized by OCAP and are not being excluded from the recording.
Variables
OCAP_entityMonitorsInitializedThis variable on the server indicates whether or not the entity monitors have been initialized for all clients + JIP.
+ +

Functions

+ +

OCAP_recorder_fnc_entityMonitors

Description

While debug mode is enabled, this function will render 2D icons and text representing all entities that have been initialized by OCAP and are not being excluded from the recording.

This is useful for debugging and verifying that the correct entities are being recorded (see OCAP_settings_excludeClassFromRecord and OCAP_settings_excludeKindFromRecord.

Parameters

None

Returns

Nothing

Examples

[_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);

Public

No

Author

IndigoFox

+ +

Variables

+ +

OCAP_entityMonitorsInitialized

This variable on the server indicates whether or not the entity monitors have been initialized for all clients + JIP.

+ +
+ + + + + + + + + + +
Array of object classnames that should be excluded from recording.
Array of classnames which, along with all child classes, should be excluded from recording.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_exportData-sqf.html b/docs/files/recorder/fnc_exportData-sqf.html new file mode 100644 index 0000000..388c45d --- /dev/null +++ b/docs/files/recorder/fnc_exportData-sqf.html @@ -0,0 +1,56 @@ + + +fnc_exportData.sqf - OCAP2 + + + + + + + + + +

fnc_exportData.sqf

Summary
fnc_exportData.sqf
Functions
OCAP_recorder_fnc_exportDataThis function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.
+ +

Functions

+ +

OCAP_recorder_fnc_exportData

Description

This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.

Called directly, it is subject to the OCAP_settings_minMissionTime setting, and will not export if the mission is not long enough.  It can also be called using OCAP_listener_exportData to bypass this check.

When OCAP_settings_saveMissionEnded is true, this function will be called automatically when the mission ends.

When OCAP_settings_saveOnEmpty is true, this function will execute when the last player leaves the mission (to lobby or when disconnecting).

OCAP_settings_saveTag is used to tag the mission with a custom string, which can be used to identify the mission in the web component.

Parameters

_sideThe winning side [optional, Side]
_messageA custom description of how the victory was achieved [optional, String]
_tagA custom tag to override that which is defined in userconfig.hpp that will make it filterable in web [optional, String]

Returns

Nothing

Examples

// "Mission ended"
+[] call FUNC(exportData);
+
+// "BLUFOR Win."
+[west] call FUNC(exportData);
+
+// "OPFOR Win. OPFOR controlled all sectors!
+[east, "OPFOR controlled all sectors!"] call FUNC(exportData);
+
+// "Independent Win. INDFOR stole the intel!"
+// Mission is saved under filterable "SnatchAndGrab" tag on web
+[independent, "INDFOR stole the intel!", "SnatchAndGrab"] call FUNC(exportData);
+
+["OCAP_exportData", west] call CBA_fnc_serverEvent;

Public

No

Author

Dell, Zealot, IndigoFox, TyroneMF

+ +
+ + + + + + + + + + +
A recording must be at least this long (in minutes) to auto-save.
Handle for OCAP_exportData listener.
If true, automatically save and export the mission when the MPEnded event fires.
Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting.
If not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_getAmmoMarkerData-sqf.html b/docs/files/recorder/fnc_getAmmoMarkerData-sqf.html new file mode 100644 index 0000000..cbb4f37 --- /dev/null +++ b/docs/files/recorder/fnc_getAmmoMarkerData-sqf.html @@ -0,0 +1,43 @@ + + +fnc_getAmmoMarkerData.sqf - OCAP2 + + + + + + + + + +

fnc_getAmmoMarkerData.sqf

Summary
fnc_getAmmoMarkerData.sqf
Functions
OCAP_recorder_fnc_getAmmoMarkerDataThis function will intake information from <OCAP_EH_FiredMan> and return data used to format a marker for entry into the timeline.
+ +

Functions

+ +

OCAP_recorder_fnc_getAmmoMarkerData

Description

This function will intake information from <OCAP_EH_FiredMan> and return data used to format a marker for entry into the timeline.

Parameters

_weaponThe weapon used [String]
_muzzleThe muzzle used [String]
_ammoThe ammo used [String]
_magazineThe magazine used [String]
_projectileThe projectile object [Object]
_vehicleThe vehicle the weapon is mounted on, if any [Object]
_ammoSimTypeThe ammo simulation type [String]

Returns

[_markTextLocal,_markName,_markColor,_markerType]The marker data [Array]

Examples

([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"];

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_getClass-sqf.html b/docs/files/recorder/fnc_getClass-sqf.html new file mode 100644 index 0000000..cac387b --- /dev/null +++ b/docs/files/recorder/fnc_getClass-sqf.html @@ -0,0 +1,43 @@ + + +fnc_getClass.sqf - OCAP2 + + + + + + + + + +

fnc_getClass.sqf

Summary
fnc_getClass.sqf
Functions
OCAP_recorder_fnc_getClassDetermines what type of vehicle is being recorded to match with the more limited icon set preloaded in the OCAP playback UI.
+ +

Functions

+ +

OCAP_recorder_fnc_getClass

Description

Determines what type of vehicle is being recorded to match with the more limited icon set preloaded in the OCAP playback UI.

Parameters

_thisThe vehicle being queried [Object]

Returns

[String]The icon name that should be used to represent the vehicle in the playback UI

Examples

_class = _vehType call FUNC(getClass);

Public

No

Author

Zealot, Dell

+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_getEventWeaponText-sqf.html b/docs/files/recorder/fnc_getEventWeaponText-sqf.html new file mode 100644 index 0000000..35cfcf7 --- /dev/null +++ b/docs/files/recorder/fnc_getEventWeaponText-sqf.html @@ -0,0 +1,43 @@ + + +fnc_getEventWeaponText.sqf - OCAP2 + + + + + + + + + +

fnc_getEventWeaponText.sqf

Summary
fnc_getEventWeaponText.sqf
Functions
OCAP_recorder_fnc_getEventWeaponTextUsed to identify the current weapon a unit is using that has injured or killed another.
+ +

Functions

+ +

OCAP_recorder_fnc_getEventWeaponText

Description

Used to identify the current weapon a unit is using that has injured or killed another.  Will determine the handheld weapon or vehicle weapon they’re using.

Attempts to reference OCAP_lastFired but will fall back to current value if not available.

Called during <OCAP_recorder_fnc_projectileHit> and OCAP_recorder_fnc_eh_killed.

Parameters

_instigatorThe unit to evaluate [Object]

Returns

The description of weapon or vehicle > weapon.  [String]

Examples

[_shooter] call FUNC(getEventWeaponText)

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + +
Indicates a formatted string of the last weapon and magazine type fired by the unit.
Tracks when a unit is killed.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_getInstigator-sqf.html b/docs/files/recorder/fnc_getInstigator-sqf.html new file mode 100644 index 0000000..d371ded --- /dev/null +++ b/docs/files/recorder/fnc_getInstigator-sqf.html @@ -0,0 +1,43 @@ + + +fnc_getInstigator.sqf - OCAP2 + + + + + + + + + +

fnc_getInstigator.sqf

Summary
fnc_getInstigator.sqf
Functions
OCAP_recorder_fnc_getInstigatorAttempts to identify who truly pulled the trigger on a kill event.
+ +

Functions

+ +

OCAP_recorder_fnc_getInstigator

Description

Attempts to identify who truly pulled the trigger on a kill event.

Called in OCAP_recorder_fnc_eh_killed.

Parameters

_victimWho was killed.  [Object]
_killerWhat caused the damage.  [Object, default objNull]
_instigatorWho pulled the trigger, as reported by Arma.  [Object, default objNull]

Returns

The true killer.  [Object]

Examples

[_victim, _killer] call FUNC(getInstigator);

Public

No

Author

Dell

+ +
+ + + + + + + + + + +
Tracks when a unit is killed.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_getUnitType-sqf.html b/docs/files/recorder/fnc_getUnitType-sqf.html new file mode 100644 index 0000000..d787381 --- /dev/null +++ b/docs/files/recorder/fnc_getUnitType-sqf.html @@ -0,0 +1,43 @@ + + +fnc_getUnitType.sqf - OCAP2 + + + + + + + + + +

fnc_getUnitType.sqf

Summary
fnc_getUnitType.sqf
Functions
OCAP_recorder_fnc_getUnitTypeIdentifies the role of a unit using similar methodology to Arma 3’s.
+ +

Functions

+ +

OCAP_recorder_fnc_getUnitType

Description

Identifies the role of a unit using similar methodology to Arma 3’s.  Used in <FUNC(captureLoop)>.

Parameters

_unitToCheckUnit to evaluate.  [Object]

Returns

The role text.  [String]

Examples

[_x] call ocap_fnc_getUnitType;

Public

No

Author

IndigoFox, veteran29

+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_getWeaponDisplayData-sqf.html b/docs/files/recorder/fnc_getWeaponDisplayData-sqf.html new file mode 100644 index 0000000..2200c15 --- /dev/null +++ b/docs/files/recorder/fnc_getWeaponDisplayData-sqf.html @@ -0,0 +1,43 @@ + + +fnc_getWeaponDisplayData.sqf - OCAP2 + + + + + + + + + +
+ +

Functions

+ +

OCAP_recorder_fnc_getWeaponDisplayData

Description

Used to populate OCAP_lastFired on units in OCAP_recorder_fnc_eh_firedMan.

Parameters

_weaponWeapon class name [String]
_muzzleMuzzle class name [String]
_magazineMagazine class name [String]
_ammoAmmo class name [String]

Returns

[Array]

0Muzzle display name [String]
1Magazine display name [String]

Examples

([_weapon, _muzzle, _magazine, _ammo] call FUNC(getWeaponDisplayData)) params ["_muzzleDisp", "_magDisp"];

Public

No

Author

IndigoFox

+ +
+ + + + + + + + + + +
Indicates a formatted string of the last weapon and magazine type fired by the unit.
Tracks bullet and non-bullet projectiles.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_handleCustomEvent-sqf.html b/docs/files/recorder/fnc_handleCustomEvent-sqf.html new file mode 100644 index 0000000..5fa9feb --- /dev/null +++ b/docs/files/recorder/fnc_handleCustomEvent-sqf.html @@ -0,0 +1,65 @@ + + +fnc_handleCustomEvent.sqf - OCAP2 + + + + + + + + + +

fnc_handleCustomEvent.sqf

Summary
fnc_handleCustomEvent.sqf
Functions
OCAP_recorder_fnc_handleCustomEventSends custom event data to the extension to save it to the timeline.
+ +

Functions

+ +

OCAP_recorder_fnc_handleCustomEvent

Description

Sends custom event data to the extension to save it to the timeline.  This custom event data is later read by Javascript in the web component to determine how it should be displayed.

Applied during initialization of OCAP in OCAP_recorder_fnc_init.

Parameters

_typeclassifier for the type of event. used to determine text & icon [[String], one of: “flag”, “generalEvent”]
_unitname of the unit that performed the action [String]
_unitColor(optional) color for the unit’s name shown in Events list and for the pulse on the map [[String], Hex RGB, defaults “” and will show as white]
_objectiveColor(optional) color representing the icon in Events list [[String], Hex RGB, defaults “” and will show as white]
_position(optional) the location to pulse on the map [<PositionATL>, default nil]

Returns

Nothing

Examples

["ocap_handleCustomEvent", ["eventType", "eventMessage"]] call CBA_fnc_serverEvent;
+
+// saves a general event to the timeline
+["ocap_handleCustomEvent", ["generalEvent", "eventText"]] call CBA_fnc_serverEvent;
+
+// indicates a flag has been captured
+["ocap_handleCustomEvent", ["captured", [
+  "flag",
+  name _unit,
+  str side group _unit,
+  "#FF0000",
+  getPosAtl _flag
+]]] call call CBA_fnc_serverEvent;
+
+
+// Not yet implemented
+["ocap_handleCustomEvent", ["captured", [
+  "sector",
+  name _unit,
+  str side group _unit,
+  "#FF0000",
+  getPosAtl _sectorObject
+]]] call call CBA_fnc_serverEvent;

Public

Yes

Author

Fank, Zealot

+ +
+ + + + + + + + + + +
Initializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_handleMarkers-sqf.html b/docs/files/recorder/fnc_handleMarkers-sqf.html new file mode 100644 index 0000000..ae82c66 --- /dev/null +++ b/docs/files/recorder/fnc_handleMarkers-sqf.html @@ -0,0 +1,61 @@ + + +fnc_handleMarkers.sqf - OCAP2 + + + + + + + + + +

fnc_handleMarkers.sqf

Summary
fnc_handleMarkers.sqf
Functions
OCAP_recorder_fnc_handleMarkersUsed for tracking all markers in the vanilla Arma 3 system.
Variables
OCAP_recorder_trackedMarkersPersistent global variable on server that defines unique marker names currently being tracked.
OCAP_listener_markersContains handle for OCAP_handleMarker CBA event handler.
CBA Events
OCAP_handleMarkerHandles marker creation, modification, and deletion events.
Event Handlers
MarkerCreatedTracks marker creations.
MarkerUpdatedTracks marker updates (moves).
MarkerDeletedTracks marker deletions.
+ +

Functions

+ +

OCAP_recorder_fnc_handleMarkers

Description

Used for tracking all markers in the vanilla Arma 3 system.

This function creates a server-side CBA listener as well as local Event Handlers on the server and all clients.  It facilitates marker creation, modification, and deletion that occurs across any machine or on the server.

Delays are integrated into the system to allow for multi-line scripted marker creations during a mission to reflect correctly in the created marker during playback.  These delays are accounted for so that playback reflects the true creation time.

Due to the nature of locality and single-view playback, markers of the same name which exist in different states on different clients may display odd behavior during playback.

Marker exclusions as configured in OCAP_settings_excludeMarkerFromRecord are handled client-side to avoid unnecessary network traffic.

This will also wait until the mission proceeds past the briefing screen, then gather all existing markers and send them to the server for entry onto the Editor/Briefing Markers layer during playback.

Applied during mission event handler application in OCAP_recorder_fnc_addEventMission.

Parameters

None

Returns

Nothing

Examples

call FUNC(handleMarkers);

Public

No

Author

IndigoFox, Fank

+ +

Variables

+ +

OCAP_recorder_trackedMarkers

Persistent global variable on server that defines unique marker names currently being tracked.  Entries are added at marker create events and removed at marker delete events to avoid duplicate processing.

+ +

OCAP_listener_markers

Contains handle for OCAP_handleMarker CBA event handler.

+ +

CBA Events

+ +

OCAP_handleMarker

Handles marker creation, modification, and deletion events.

+ +

Event Handlers

+ +

MarkerCreated

Description

Tracks marker creations.  Present on server and all clients.  Ignores remotely-owned markers.

References OCAP_settings_excludeMarkerFromRecord on each local machine to determine if a marker should be recorded.

If so, sends marker data to OCAP_handleMarker on the server for processing.

+ +

MarkerUpdated

Description

Tracks marker updates (moves).  Present on server and all clients.  Ignores remotely-owned markers.

References OCAP_settings_excludeMarkerFromRecord on each local machine to determine if a marker should be recorded.

If so, sends marker data to OCAP_handleMarker on the server for processing.

+ +

MarkerDeleted

Description

Tracks marker deletions.  Present on server and all clients.  Ignores remotely-owned markers.

References OCAP_settings_excludeMarkerFromRecord on each local machine to determine if a marker should be recorded.

If so, sends marker data to OCAP_handleMarker on the server for processing.

+ +
+ + + + + + + + + + +
Handles marker creation, modification, and deletion events.
Array of prefixes.
Used for applying mission event handlers.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_init-sqf.html b/docs/files/recorder/fnc_init-sqf.html new file mode 100644 index 0000000..2059184 --- /dev/null +++ b/docs/files/recorder/fnc_init-sqf.html @@ -0,0 +1,61 @@ + + +fnc_init.sqf - OCAP2 + + + + + + + + + +

fnc_init.sqf

Summary
fnc_init.sqf
Functions
OCAP_recorder_fnc_initInitializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
Variables
OCAP_recorder_recordingGlobal variable that represents whether or not recording is active [Bool]
OCAP_recorder_captureFrameNoGlobal variable that represents the current frame number [Number]
OCAP_recorder_nextIdGlobal variable that represents the next available id to assign to a unit or vehicle [Number]
OCAP_recorder_frameCaptureDelayGlobal variable that represents the delay between frame captures in seconds.
OCAP_recorder_autoStartGlobal variable that represents whether or not recording should automatically start.
OCAP_recorder_minMissionTimeGlobal variable that represents the minimum mission time in seconds to qualify for saving.
OCAP_versionGlobal variable that represents the version of OCAP addon being used [String]
OCAP_extension_versionGlobal variable that represents the version of OCAP extension being used [String]
+ +

Functions

+ +

OCAP_recorder_fnc_init

Description

Initializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.

Parameters

None

Returns

Nothing

Example

call OCAP_recorder_fnc_init

Public

No

Author

Dell, Zealot, IndigoFox

+ +

Variables

+ +

OCAP_recorder_recording

Global variable that represents whether or not recording is active [Bool]

+ +

OCAP_recorder_captureFrameNo

Global variable that represents the current frame number [Number]

+ +

OCAP_recorder_nextId

Global variable that represents the next available id to assign to a unit or vehicle [Number]

+ +

OCAP_recorder_frameCaptureDelay

Global variable that represents the delay between frame captures in seconds.  Gathered from CBA settings at init.  [Number]

+ +

OCAP_recorder_autoStart

Global variable that represents whether or not recording should automatically start.  Gathered from CBA settings at init.  [Bool]

+ +

OCAP_recorder_minMissionTime

Global variable that represents the minimum mission time in seconds to qualify for saving.  Can be overridden by using the <ocap_exportData> CBA event.  Gathered from CBA settings at init.  [Number]

+ +

OCAP_version

Global variable that represents the version of OCAP addon being used [String]

+ +

OCAP_extension_version

Global variable that represents the version of OCAP extension being used [String]

+ +
+ + + + + + + + + + +
Global variable that represents the version of OCAP addon being used [String]
Global variable that represents the version of OCAP extension being used [String]
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_isKindOfApc-sqf.html b/docs/files/recorder/fnc_isKindOfApc-sqf.html new file mode 100644 index 0000000..0033cb7 --- /dev/null +++ b/docs/files/recorder/fnc_isKindOfApc-sqf.html @@ -0,0 +1,43 @@ + + +fnc_isKindOfApc.sqf - OCAP2 + + + + + + + + + +

fnc_isKindOfApc.sqf

Summary
fnc_isKindOfApc.sqf
Functions
OCAP_recorder_fnc_isKindOfApcHelper function for OCAP_recorder_fnc_getClass to prevent APCs from being classified as Cars or Trucks.
+ +

Functions

+ +

OCAP_recorder_fnc_isKindOfApc

Description

Helper function for OCAP_recorder_fnc_getClass to prevent APCs from being classified as Cars or Trucks.

Parameters

_thisThe vehicle to check [Object]

Returns

[Bool]True if the vehicle is an APC, false otherwise

Examples

if (_this call FUNC(isKindOfApc)) exitWith {"apc"};

Public

No

Author

Dell, Zealot

+ +
+ + + + + + + + + + +
Determines what type of vehicle is being recorded to match with the more limited icon set preloaded in the OCAP playback UI.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_projectileMonitors-sqf.html b/docs/files/recorder/fnc_projectileMonitors-sqf.html new file mode 100644 index 0000000..7faa168 --- /dev/null +++ b/docs/files/recorder/fnc_projectileMonitors-sqf.html @@ -0,0 +1,49 @@ + + +fnc_projectileMonitors.sqf - OCAP2 + + + + + + + + + +

fnc_projectileMonitors.sqf

Summary
fnc_projectileMonitors.sqf
Functions
OCAP_recorder_fnc_projectileMonitorsThis initializes projectile monitoring for the purposes of moving non-bullet projectile markers across the map during playback as well as to display them on the in-game map while OCAP_isDebug is true.
Variables
OCAP_recorder_liveDebugBulletsUsed by clients to draw bullet lines.
OCAP_recorder_liveDebugMagIconsUsed by clients to draw magazine icons of non-bullet projectiles.
+ +

Functions

+ +

OCAP_recorder_fnc_projectileMonitors

Description

This initializes projectile monitoring for the purposes of moving non-bullet projectile markers across the map during playback as well as to display them on the in-game map while OCAP_isDebug is true.

On clients, it will create a “Draw” UI event handler to display both fire-lines representing bullets and markers representing non-bullet projectiles.  It will also create an event handler used by the server in OCAP_recorder_fnc_eh_firedMan to integrate new projectiles to the array being procesed by the “Draw” handler.

On the server, it will initialize <OCAP_recorder_liveMissiles> and <OCAP_recorder_liveGrenades>.  These are watch arrays that are used to track the position of non-bullet projectiles and update the extension with their positions as they travel.  This causes the effect of a ‘moving marker’ during playback.

Parameters

None

Returns

Nothing

Example

call FUNC(projectileMonitors);

Public

No

Author

IndigoFox

+ +

Variables

+ +

OCAP_recorder_liveDebugBullets

Used by clients to draw bullet lines.  Entered via OCAP_recorder_fnc_eh_firedMan and managed in ??

+ +

OCAP_recorder_liveDebugMagIcons

Used by clients to draw magazine icons of non-bullet projectiles.  Entered via OCAP_recorder_fnc_eh_firedMan and managed in ??

+ +
+ + + + + + + + + + +
Enables increased logging of addon actions.
Tracks bullet and non-bullet projectiles.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_startRecording-sqf.html b/docs/files/recorder/fnc_startRecording-sqf.html new file mode 100644 index 0000000..b066e75 --- /dev/null +++ b/docs/files/recorder/fnc_startRecording-sqf.html @@ -0,0 +1,43 @@ + + +fnc_startRecording.sqf - OCAP2 + + + + + + + + + +

fnc_startRecording.sqf

Summary
+ +

Functions

+ +

OCAP_recorder_fnc_startRecording

Description

Begins recording the current mission.

Called via OCAP_record via direct CBA event or the administrative diary entry, or by a waiter in OCAP_recorder_fnc_init (see OCAP_settings_autoStart).

Will not start recording if OCAP_recorder_recording is true and will notify players.

Parameters

None

Returns

Nothing

Examples

call FUNC(startRecording);

Public

No

Author

Dell, Zealot, IndigoFox

+ +
+ + + + + + + + + + +
Used to start or resume recording.
Initializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
Automatically start OCAP recordings at session start.
Global variable that represents whether or not recording is active [Bool]
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_stopRecording-sqf.html b/docs/files/recorder/fnc_stopRecording-sqf.html new file mode 100644 index 0000000..81ba12a --- /dev/null +++ b/docs/files/recorder/fnc_stopRecording-sqf.html @@ -0,0 +1,43 @@ + + +fnc_stopRecording.sqf - OCAP2 + + + + + + + + + +

fnc_stopRecording.sqf

Summary
+ +

Functions

+ +

OCAP_recorder_fnc_stopRecording

Description

Stops recording the current mission.  Can be used to pause the recording for later resumption.  Also called automatically as part of OCAP_recorder_fnc_exportData.

Called via OCAP_pause via direct CBA event or the administrative diary entry.

Parameters

None

Returns

Nothing

Examples

call FUNC(stopRecording);

Public

No

Author

Dell, Zealot, IndigoFox

+ +
+ + + + + + + + + + +
This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.
Used to pause recording.
+ + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/fnc_updateTime-sqf.html b/docs/files/recorder/fnc_updateTime-sqf.html new file mode 100644 index 0000000..195641f --- /dev/null +++ b/docs/files/recorder/fnc_updateTime-sqf.html @@ -0,0 +1,43 @@ + + +fnc_updateTime.sqf - OCAP2 + + + + + + + + + +

fnc_updateTime.sqf

Summary
fnc_updateTime.sqf
Functions
OCAP_recorder_fnc_updateTimeSends server’s system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension.
+ +

Functions

+ +

OCAP_recorder_fnc_updateTime

Description

Sends server’s system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension.  Will run on a recurring basis as part of <FUNC(captureLoop)> if the setting in userconfig.hpp is configured to do so.  This is required in missions that utilize time acceleration or have time skips as part of mission flow.

Parameters

_dateA manual in-game time to check.  [optional, Array]

Returns

Nothing

Examples

[] call FUNC(updateTime);

Public

No

Author

Fank

+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/files/recorder/script_component-hpp.html b/docs/files/recorder/script_component-hpp.html new file mode 100644 index 0000000..ecbe98c --- /dev/null +++ b/docs/files/recorder/script_component-hpp.html @@ -0,0 +1,45 @@ + + +script_component.hpp - OCAP2 + + + + + + + + + +
+ +

Macros

+ +

COMPONENT

#define COMPONENT recorder
+ +

COMPONENT_BEAUTIFIED

#define COMPONENT_BEAUTIFIED Recorder
+ +
+ + + + + + + + + + +
#define COMPONENT recorder
#define COMPONENT_BEAUTIFIED Recorder
+ + + + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..d15bd03 --- /dev/null +++ b/docs/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/index/CBAEvents.html b/docs/index/CBAEvents.html new file mode 100644 index 0000000..5f52a58 --- /dev/null +++ b/docs/index/CBAEvents.html @@ -0,0 +1,54 @@ + + +CBA Event Index - OCAP2 + + + + + + + + + +
CBA Event Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
 ace_advanced_throwing_throwFiredXEH
 ace_explosives_place
OCAP_recorder_addDebugBullet
OCAP_recorder_addDebugMagIcon
C
OCAP_counterEvent
OCAP_counterInit
OCAP_customEvent
E
OCAP_exportData
H
OCAP_handleMarker
P
OCAP_pause
 Projectiles
R
OCAP_record
+ +
EGVAR(
   listener,
   aceThrowing
) = ["ace_advanced_throwing_throwFiredXEH", { _this call FUNC(eh_firedMan) }] call CBA_fnc_addEventHandler
Fired when a throwable is primed.
EGVAR(
   listener,
   aceExplosives
) = ["ace_explosives_place", { call FUNC(aceExplosives); }] call CBA_fnc_addEventHandler
Event listener for ACE3 global event indicating a mine has been placed and armed.
Triggered when a bullet is fired and the debug mode is enabled.
Triggered when a non-bullet projectile is fired and the debug mode is enabled.
+ + + +
Meant for use in custom tracking of points or score between two sides.
Meant for use in custom tracking of points or score between two sides.
Event listener for custom event text to be added to the timeline.
+ + + +
Used to stop recording & signal the extension to save and upload it to the web component.
+ + + +
Handles marker creation, modification, and deletion events.
+ + + +
Used to pause recording.
+ + + +
Used to start or resume recording.
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/CBASettings.html b/docs/index/CBASettings.html new file mode 100644 index 0000000..da70e7d --- /dev/null +++ b/docs/index/CBASettings.html @@ -0,0 +1,62 @@ + + +CBA Setting Index - OCAP2 + + + + + + + + + +
CBA Setting Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
OCAP_administratorList
OCAP_settings_autoStart
E
OCAP_enabled
OCAP_settings_excludeClassFromRecord
OCAP_settings_excludeKindFromRecord
OCAP_settings_excludeMarkerFromRecord
F
OCAP_settings_frameCaptureDelay
I
OCAP_isDebug
M
OCAP_settings_minMissionTime
OCAP_settings_minPlayerCount
P
OCAP_settings_preferACEUnconscious
S
OCAP_settings_saveMissionEnded
OCAP_settings_saveOnEmpty
OCAP_settings_saveTag
T
OCAP_settings_trackTickets
OCAP_settings_trackTimeInterval
OCAP_settings_trackTimes
+ +
An array or server-visible variable referencing one that is a list of playerUIDs.
Automatically start OCAP recordings at session start.
+ + + +
Turns on or off most recording functionality.
Array of object classnames that should be excluded from recording.
Array of classnames which, along with all child classes, should be excluded from recording.
Array of prefixes.
+ + + +
Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds.
+ + + +
Enables increased logging of addon actions.
+ + + +
A recording must be at least this long (in minutes) to auto-save.
Auto-start will begin once this player count is reached.
+ + + +
If true, will check ACE3 medical status on units.
+ + + +
If true, automatically save and export the mission when the MPEnded event fires.
Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting.
If not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
+ + + +
Will track respawn ticket counts for missionNamespace and each playable faction every 30th frame.
If OCAP_settings_trackTimes is enabled, it will be checked every X capture frames.
Will continuously track in-game world time during a mission.
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/EventHandlers.html b/docs/index/EventHandlers.html new file mode 100644 index 0000000..33534db --- /dev/null +++ b/docs/index/EventHandlers.html @@ -0,0 +1,54 @@ + + +Event Handler Index - OCAP2 + + + + + + + + + +
Event Handler Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
D
 Deleted
E
OCAP_EH_Ended
OCAP_EH_EntityKilled
OCAP_EH_EntityRespawned
 Explode
H
OCAP_EH_HandleDisconnect
 HitExplosion
 HitPart
M
 MarkerCreated
 MarkerDeleted
 MarkerUpdated
OCAP_EH_MPEnded
O
OCAP_EH_OnUserAdminStateChanged
P
OCAP_EH_PlayerConnected
 Projectiles(Bullets)
 Projectiles(Non-Bullets)
+ +
Makes extension call to draw a fire-line between the firer and the final destination.
+ + + +
Handle for the “Ended” mission event handler.
Handle for the “EntityKilled” mission event handler.
Handle for the “EntityRespawned” mission event handler.
Makes extension call to draw a fire-line between the firer and the final destination.
+ + + +
Fired when a player leaves the mission by returning to lobby or disconnecting.
Triggered when a projectile explodes and damages a part of a unit.
Triggered when a projectile hits a part of a unit.
+ + + +
Tracks marker creations.
Tracks marker deletions.
Tracks marker updates (moves).
Handle for the “MPEnded” mission event handler.
+ + + +
Handle for the “OnUserAdminStateChange” mission event handler.
+ + + +
Handle for the “PlayerConnected” mission event handler.
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/Files.html b/docs/index/Files.html new file mode 100644 index 0000000..01e6987 --- /dev/null +++ b/docs/index/Files.html @@ -0,0 +1,42 @@ + + +File Index - OCAP2 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/Functions.html b/docs/index/Functions.html new file mode 100644 index 0000000..73b3014 --- /dev/null +++ b/docs/index/Functions.html @@ -0,0 +1,66 @@ + + +Function Index - OCAP2 + + + + + + + + + +
Function Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
OCAP_recorder_fnc_aceExplosives
OCAP_recorder_fnc_addEventMission
OCAP_recorder_fnc_addUnitEventHandlers
OCAP_recorder_fnc_adminUIControl
C
OCAP_recorder_fnc_captureLoop
E
OCAP_recorder_fnc_eh_connected
OCAP_recorder_fnc_eh_disconnected
OCAP_recorder_fnc_eh_firedMan
OCAP_recorder_fnc_eh_killed
OCAP_recorder_fnc_eh_onUserAdminStateChanged
OCAP_recorder_fnc_eh_projectileHit
OCAP_recorder_fnc_entityMonitors
OCAP_recorder_fnc_exportData
G
OCAP_recorder_fnc_getAmmoMarkerData
OCAP_recorder_fnc_getClass
OCAP_recorder_fnc_getEventWeaponText
OCAP_recorder_fnc_getInstigator
OCAP_recorder_fnc_getUnitType
OCAP_recorder_fnc_getWeaponDisplayData
H
OCAP_recorder_fnc_handleCustomEvent
OCAP_recorder_fnc_handleMarkers
I
OCAP_recorder_fnc_init
OCAP_recorder_fnc_isKindOfApc
P
OCAP_recorder_fnc_projectileMonitors
S
OCAP_extension_fnc_sendData
OCAP_recorder_fnc_startRecording
OCAP_recorder_fnc_stopRecording
U
OCAP_recorder_fnc_updateTime
+ +
Adds marker on the mine’s position to the recording timeline.
Used for applying mission event handlers.
Used for applying unit-specific event handlers to units during initialization.
Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
+ + + +
This function is run unscheduled and creates a CBA PerFrameHandler object, a logic object which executes code every specified interval (OCAP_settings_frameCaptureDelay) while a condition (SHOULDSAVEEVENTS) is true.
+ + + +
This function uses the OCAP_EH_Connected event handler to log “connected” events to the timeline.
This function uses the OCAP_EH_HandleDisconnect event handler to log “disconnected” events to the timeline.
Tracks bullet and non-bullet projectiles.
Tracks when a unit is killed.
Uses OCAP_EH_OnUserAdminStateChanged to detect when someone is has logged in or out of the server and calls OCAP_recorder_fnc_adminUIControl to update the admin UI.
Tracks when a unit is hit/takes damage and saves to the timeline.
While debug mode is enabled, this function will render 2D icons and text representing all entities that have been initialized by OCAP and are not being excluded from the recording.
This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.
+ + + +
This function will intake information from OCAP_EH_FiredMan and return data used to format a marker for entry into the timeline.
Determines what type of vehicle is being recorded to match with the more limited icon set preloaded in the OCAP playback UI.
Used to identify the current weapon a unit is using that has injured or killed another.
Attempts to identify who truly pulled the trigger on a kill event.
Identifies the role of a unit using similar methodology to Arma 3’s.
Used to populate OCAP_lastFired on units in OCAP_recorder_fnc_eh_firedMan.
+ + + +
Sends custom event data to the extension to save it to the timeline.
Used for tracking all markers in the vanilla Arma 3 system.
+ + + +
Initializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
Helper function for OCAP_recorder_fnc_getClass to prevent APCs from being classified as Cars or Trucks.
+ + + +
This initializes projectile monitoring for the purposes of moving non-bullet projectile markers across the map during playback as well as to display them on the in-game map while OCAP_isDebug is true.
+ + + +
Manages raw extension calls and returns values / logs errors where relevant.
Begins recording the current mission.
Stops recording the current mission.
+ + + +
Sends server’s system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension.
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/General.html b/docs/index/General.html new file mode 100644 index 0000000..624e7f6 --- /dev/null +++ b/docs/index/General.html @@ -0,0 +1,82 @@ + + +Index - OCAP2 + + + + + + + + + +
Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
 ace_advanced_throwing_throwFiredXEH
 ace_explosives_place
OCAP_recorder_fnc_aceExplosives
OCAP_recorder_addDebugBullet
OCAP_recorder_addDebugMagIcon
OCAP_recorder_fnc_addEventMission
 ADDON
OCAP_recorder_fnc_addUnitEventHandlers
OCAP_administratorList
OCAP_recorder_fnc_adminUIControl
 ARR2
 Auto-Start Settings
OCAP_recorder_autoStart
OCAP_settings_autoStart
B
 BOOL
C
OCAP_recorder_captureFrameNo
OCAP_recorder_fnc_captureLoop
 CBA Events
 CBA Listener Handles
 CBA Settings
 COMPONENT
 COMPONENT_BEAUTIFIED
 COMPONENT_NAME
 Core
OCAP_counter_sides
OCAP_counterEvent
OCAP_counterInit
OCAP_customEvent
D
 Deleted
E
OCAP_recorder_fnc_eh_connected
OCAP_recorder_fnc_eh_disconnected
OCAP_recorder_fnc_eh_firedMan
OCAP_recorder_fnc_eh_killed
OCAP_recorder_fnc_eh_onUserAdminStateChanged
OCAP_recorder_fnc_eh_projectileHit
OCAP_enabled
OCAP_EH_Ended
OCAP_EH_EntityKilled
OCAP_recorder_fnc_entityMonitors
OCAP_entityMonitorsInitialized
OCAP_EH_EntityRespawned
 Event Handlers
OCAP_settings_excludeClassFromRecord
OCAP_settings_excludeKindFromRecord
OCAP_settings_excludeMarkerFromRecord
 Exclusions
 Explode
OCAP_exportData
OCAP_recorder_fnc_exportData
 Extra Tracking
F
 fnc_aceExplosives.sqf
 fnc_addEventMission.sqf
 fnc_addUnitEventHandlers.sqf
 fnc_adminUIControl.sqf
 fnc_captureLoop.sqf
 fnc_eh_connected.sqf
 fnc_eh_disconnected.sqf
 fnc_eh_firedMan.sqf
 fnc_eh_killed.sqf
 fnc_eh_onUserAdminStateChanged.sqf
 fnc_eh_projectileHit.sqf
 fnc_entityMonitors.sqf
 fnc_exportData.sqf
 fnc_getAmmoMarkerData.sqf
 fnc_getClass.sqf
 fnc_getEventWeaponText.sqf
 fnc_getInstigator.sqf
 fnc_getUnitType.sqf
 fnc_getWeaponDisplayData.sqf
 fnc_handleCustomEvent.sqf
 fnc_handleMarkers.sqf
 fnc_init.sqf
 fnc_isKindOfApc.sqf
 fnc_projectileMonitors.sqf
 fnc_sendData.sqf
 fnc_startRecording.sqf
 fnc_stopRecording.sqf
 fnc_updateTime.sqf
OCAP_recorder_frameCaptureDelay
OCAP_settings_frameCaptureDelay
 Functions
G
OCAP_recorder_fnc_getAmmoMarkerData
OCAP_recorder_fnc_getClass
OCAP_recorder_fnc_getEventWeaponText
OCAP_recorder_fnc_getInstigator
OCAP_recorder_fnc_getUnitType
OCAP_recorder_fnc_getWeaponDisplayData
H
OCAP_recorder_fnc_handleCustomEvent
OCAP_EH_HandleDisconnect
OCAP_handleMarker
OCAP_recorder_fnc_handleMarkers
OCAP_hasAdminControls
 HitExplosion
 HitPart
I
OCAP_recorder_fnc_init
OCAP_isDebug
OCAP_recorder_fnc_isKindOfApc
L
OCAP_lastFired
OCAP_listener_aceExplosives
OCAP_listener_aceThrowing
OCAP_listener_counterEvent
OCAP_listener_counterInit
OCAP_listener_customEvent
OCAP_listener_exportData
OCAP_listener_markers
OCAP_listener_pause
OCAP_listener_record
OCAP_recorder_liveDebugBullets
OCAP_recorder_liveDebugMagIcons
 LOG
M
 Macros
 MarkerCreated
 MarkerDeleted
 MarkerUpdated
OCAP_recorder_minMissionTime
OCAP_settings_minMissionTime
OCAP_settings_minPlayerCount
OCAP_EH_MPEnded
N
OCAP_recorder_nextId
O
OCAP_EH_OnUserAdminStateChanged
+ +
EGVAR(
   listener,
   aceThrowing
) = ["ace_advanced_throwing_throwFiredXEH", { _this call FUNC(eh_firedMan) }] call CBA_fnc_addEventHandler
Fired when a throwable is primed.
EGVAR(
   listener,
   aceExplosives
) = ["ace_explosives_place", { call FUNC(aceExplosives); }] call CBA_fnc_addEventHandler
Event listener for ACE3 global event indicating a mine has been placed and armed.
Adds marker on the mine’s position to the recording timeline.
Triggered when a bullet is fired and the debug mode is enabled.
Triggered when a non-bullet projectile is fired and the debug mode is enabled.
Used for applying mission event handlers.
PREFIX_COMPONENT
Used for applying unit-specific event handlers to units during initialization.
An array or server-visible variable referencing one that is a list of playerUIDs.
Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
#define ARR2(_arg1,
_arg2) [_arg1, _arg2]
Resolves arguments to array, used for entries to LOG that requires array input.
Global variable that represents whether or not recording should automatically start.
Automatically start OCAP recordings at session start.
+ + + +
#define BOOL(_cond) ([0,1] select (_cond))
Forces a true/false return of input.
+ + + +
Global variable that represents the current frame number [Number]
This function is run unscheduled and creates a CBA PerFrameHandler object, a logic object which executes code every specified interval (OCAP_settings_frameCaptureDelay) while a condition (SHOULDSAVEEVENTS) is true.
#define COMPONENT extension
#define COMPONENT_BEAUTIFIED Extension
#define COMPONENT_NAME QUOTE(PREFIX COMPONENT_BEAUTIFIED)
Sides that are tracked by the custom counter system.
Meant for use in custom tracking of points or score between two sides.
Meant for use in custom tracking of points or score between two sides.
Event listener for custom event text to be added to the timeline.
+ + + +
Makes extension call to draw a fire-line between the firer and the final destination.
+ + + +
This function uses the OCAP_EH_Connected event handler to log “connected” events to the timeline.
This function uses the OCAP_EH_HandleDisconnect event handler to log “disconnected” events to the timeline.
Tracks bullet and non-bullet projectiles.
Tracks when a unit is killed.
Uses OCAP_EH_OnUserAdminStateChanged to detect when someone is has logged in or out of the server and calls OCAP_recorder_fnc_adminUIControl to update the admin UI.
Tracks when a unit is hit/takes damage and saves to the timeline.
Turns on or off most recording functionality.
Handle for the “Ended” mission event handler.
Handle for the “EntityKilled” mission event handler.
While debug mode is enabled, this function will render 2D icons and text representing all entities that have been initialized by OCAP and are not being excluded from the recording.
This variable on the server indicates whether or not the entity monitors have been initialized for all clients + JIP.
Handle for the “EntityRespawned” mission event handler.
Array of object classnames that should be excluded from recording.
Array of classnames which, along with all child classes, should be excluded from recording.
Array of prefixes.
Makes extension call to draw a fire-line between the firer and the final destination.
Used to stop recording & signal the extension to save and upload it to the web component.
This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.
+ + + +
Global variable that represents the delay between frame captures in seconds.
Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds.
+ + + +
This function will intake information from OCAP_EH_FiredMan and return data used to format a marker for entry into the timeline.
Determines what type of vehicle is being recorded to match with the more limited icon set preloaded in the OCAP playback UI.
Used to identify the current weapon a unit is using that has injured or killed another.
Attempts to identify who truly pulled the trigger on a kill event.
Identifies the role of a unit using similar methodology to Arma 3’s.
Used to populate OCAP_lastFired on units in OCAP_recorder_fnc_eh_firedMan.
+ + + +
Sends custom event data to the extension to save it to the timeline.
Fired when a player leaves the mission by returning to lobby or disconnecting.
Handles marker creation, modification, and deletion events.
Used for tracking all markers in the vanilla Arma 3 system.
Applied on units processed in OCAP_recorder_fnc_adminUIControl.
Triggered when a projectile explodes and damages a part of a unit.
Triggered when a projectile hits a part of a unit.
+ + + +
Initializes event listeners, event handlers, gathers OCAP_version and OCAP_extension_version, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
Enables increased logging of addon actions.
Helper function for OCAP_recorder_fnc_getClass to prevent APCs from being classified as Cars or Trucks.
+ + + +
Indicates a formatted string of the last weapon and magazine type fired by the unit.
Handle for ace_explosives_place listener.
Handle for ace_advanced_throwing_throwFiredXEH listener.
Handle for OCAP_counterEvent listener.
Handle for OCAP_counterInit listener.
Handle for OCAP_customEvent listener.
Handle for OCAP_exportData listener.
Contains handle for OCAP_handleMarker CBA event handler.
Handle for OCAP_pause listener.
Handle for OCAP_record listener.
Used by clients to draw bullet lines.
Used by clients to draw magazine icons of non-bullet projectiles.
#define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData)
Used for logging messages to the extension (ocap-ext log file).
+ + + +
Tracks marker creations.
Tracks marker deletions.
Tracks marker updates (moves).
Global variable that represents the minimum mission time in seconds to qualify for saving.
A recording must be at least this long (in minutes) to auto-save.
Auto-start will begin once this player count is reached.
Handle for the “MPEnded” mission event handler.
+ + + +
Global variable that represents the next available id to assign to a unit or vehicle [Number]
+ + + +
Handle for the “OnUserAdminStateChange” mission event handler.
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/General2.html b/docs/index/General2.html new file mode 100644 index 0000000..89a36c9 --- /dev/null +++ b/docs/index/General2.html @@ -0,0 +1,54 @@ + + +Index - OCAP2 + + + + + + + + + +
Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
P
OCAP_pause
OCAP_PFHObject
OCAP_EH_PlayerConnected
OCAP_settings_preferACEUnconscious
 PREFIX
OCAP_recorder_fnc_projectileMonitors
 Projectiles
 Projectiles(Bullets)
 Projectiles(Non-Bullets)
R
OCAP_record
OCAP_recorder_recording
S
 Save/Export Settings
OCAP_settings_saveMissionEnded
OCAP_settings_saveOnEmpty
OCAP_settings_saveTag
 script_component.hpp
 script_macros.hpp
OCAP_extension_fnc_sendData
 SHOULDSAVEEVENTS
OCAP_recorder_fnc_startRecording
OCAP_recorder_fnc_stopRecording
 SYSCHAT
T
OCAP_recorder_trackedMarkers
OCAP_settings_trackTickets
OCAP_settings_trackTimeInterval
OCAP_settings_trackTimes
U
OCAP_recorder_fnc_updateTime
V
 Variables
OCAP_version
OCAP_extension_version
 VERSION
 VERSION_AR
 VERSION_REQUIRED
 VERSION_STR
+ +
Used to pause recording.
The CBA PerFrameHandler object that is created and used to run the capture loop.
Handle for the “PlayerConnected” mission event handler.
If true, will check ACE3 medical status on units.
#define PREFIX OCAP
This initializes projectile monitoring for the purposes of moving non-bullet projectile markers across the map during playback as well as to display them on the in-game map while OCAP_isDebug is true.
+ + + +
Used to start or resume recording.
Global variable that represents whether or not recording is active [Bool]
+ + + +
If true, automatically save and export the mission when the MPEnded event fires.
Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting.
If not overriden by the OCAP_exportData CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
Defines macros imported to other functions
Manages raw extension calls and returns values / logs errors where relevant.
#define SHOULDSAVEEVENTS (
   (missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] -1
)
Used to determine if events should currently be saved based on OCAP_recorder_recording and OCAP_recorder_startTime.
Begins recording the current mission.
Stops recording the current mission.
#define SYSCHAT remoteExec ["systemChat", [0, -2] select isDedicated]
Used for debug purposes to send a string to all clients with interfaces.
+ + + +
Persistent global variable on server that defines unique marker names currently being tracked.
Will track respawn ticket counts for missionNamespace and each playable faction every 30th frame.
If OCAP_settings_trackTimes is enabled, it will be checked every X capture frames.
Will continuously track in-game world time during a mission.
+ + + +
Sends server’s system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension.
+ + + +
Global variable that represents the version of OCAP addon being used [String]
Global variable that represents the version of OCAP extension being used [String]
#define VERSION 2.0
#define VERSION_AR 2,0,0
#define VERSION_REQUIRED 2.10
#define VERSION_STR 2.0.0
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/Macros.html b/docs/index/Macros.html new file mode 100644 index 0000000..3b9b603 --- /dev/null +++ b/docs/index/Macros.html @@ -0,0 +1,58 @@ + + +Macro Index - OCAP2 + + + + + + + + + +
Macro Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
 ADDON
 ARR2
B
 BOOL
C
 COMPONENT
 COMPONENT_BEAUTIFIED
 COMPONENT_NAME
L
 LOG
P
 PREFIX
S
 SHOULDSAVEEVENTS
 SYSCHAT
V
 VERSION
 VERSION_AR
 VERSION_REQUIRED
 VERSION_STR
+ +
PREFIX_COMPONENT
#define ARR2(_arg1,
_arg2) [_arg1, _arg2]
Resolves arguments to array, used for entries to LOG that requires array input.
+ + + +
#define BOOL(_cond) ([0,1] select (_cond))
Forces a true/false return of input.
+ + + +
#define COMPONENT extension
#define COMPONENT_BEAUTIFIED Extension
#define COMPONENT_NAME QUOTE(PREFIX COMPONENT_BEAUTIFIED)
+ + + +
#define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData)
Used for logging messages to the extension (ocap-ext log file).
+ + + +
#define PREFIX OCAP
+ + + +
#define SHOULDSAVEEVENTS (
   (missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] -1
)
Used to determine if events should currently be saved based on OCAP_recorder_recording and OCAP_recorder_startTime.
#define SYSCHAT remoteExec ["systemChat", [0, -2] select isDedicated]
Used for debug purposes to send a string to all clients with interfaces.
+ + + +
#define VERSION 2.0
#define VERSION_AR 2,0,0
#define VERSION_REQUIRED 2.10
#define VERSION_STR 2.0.0
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index/Variables.html b/docs/index/Variables.html new file mode 100644 index 0000000..a3859f8 --- /dev/null +++ b/docs/index/Variables.html @@ -0,0 +1,78 @@ + + +Variable Index - OCAP2 + + + + + + + + + +
Variable Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
OCAP_recorder_autoStart
C
OCAP_recorder_captureFrameNo
 CBA Listener Handles
OCAP_counter_sides
E
OCAP_entityMonitorsInitialized
F
OCAP_recorder_frameCaptureDelay
H
OCAP_hasAdminControls
L
OCAP_lastFired
OCAP_listener_aceExplosives
OCAP_listener_aceThrowing
OCAP_listener_counterEvent
OCAP_listener_counterInit
OCAP_listener_customEvent
OCAP_listener_exportData
OCAP_listener_markers
OCAP_listener_pause
OCAP_listener_record
OCAP_recorder_liveDebugBullets
OCAP_recorder_liveDebugMagIcons
M
OCAP_recorder_minMissionTime
N
OCAP_recorder_nextId
P
OCAP_PFHObject
R
OCAP_recorder_recording
T
OCAP_recorder_trackedMarkers
V
OCAP_version
OCAP_extension_version
+ +
Global variable that represents whether or not recording should automatically start.
+ + + +
Global variable that represents the current frame number [Number]
Sides that are tracked by the custom counter system.
+ + + +
This variable on the server indicates whether or not the entity monitors have been initialized for all clients + JIP.
+ + + +
Global variable that represents the delay between frame captures in seconds.
+ + + +
Applied on units processed in OCAP_recorder_fnc_adminUIControl.
+ + + +
Indicates a formatted string of the last weapon and magazine type fired by the unit.
Handle for ace_explosives_place listener.
Handle for ace_advanced_throwing_throwFiredXEH listener.
Handle for OCAP_counterEvent listener.
Handle for OCAP_counterInit listener.
Handle for OCAP_customEvent listener.
Handle for OCAP_exportData listener.
Contains handle for OCAP_handleMarker CBA event handler.
Handle for OCAP_pause listener.
Handle for OCAP_record listener.
Used by clients to draw bullet lines.
Used by clients to draw magazine icons of non-bullet projectiles.
+ + + +
Global variable that represents the minimum mission time in seconds to qualify for saving.
+ + + +
Global variable that represents the next available id to assign to a unit or vehicle [Number]
+ + + +
The CBA PerFrameHandler object that is created and used to run the capture loop.
+ + + +
Global variable that represents whether or not recording is active [Bool]
+ + + +
Persistent global variable on server that defines unique marker names currently being tracked.
+ + + +
Global variable that represents the version of OCAP addon being used [String]
Global variable that represents the version of OCAP extension being used [String]
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/javascript/main.js b/docs/javascript/main.js new file mode 100644 index 0000000..b008be9 --- /dev/null +++ b/docs/javascript/main.js @@ -0,0 +1,841 @@ +// This file is part of Natural Docs, which is Copyright 2003-2010 Greg Valure +// Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL) +// Refer to License.txt for the complete details + +// This file may be distributed with documentation files generated by Natural Docs. +// Such documentation is not covered by Natural Docs' copyright and licensing, +// and may have its own copyright and distribution terms as decided by its author. + + +// +// Browser Styles +// ____________________________________________________________________________ + +var agt=navigator.userAgent.toLowerCase(); +var browserType; +var browserVer; + +if (agt.indexOf("opera") != -1) + { + browserType = "Opera"; + + if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1) + { browserVer = "Opera7"; } + else if (agt.indexOf("opera 8") != -1 || agt.indexOf("opera/8") != -1) + { browserVer = "Opera8"; } + else if (agt.indexOf("opera 9") != -1 || agt.indexOf("opera/9") != -1) + { browserVer = "Opera9"; } + } + +else if (agt.indexOf("applewebkit") != -1) + { + browserType = "Safari"; + + if (agt.indexOf("version/3") != -1) + { browserVer = "Safari3"; } + else if (agt.indexOf("safari/4") != -1) + { browserVer = "Safari2"; } + } + +else if (agt.indexOf("khtml") != -1) + { + browserType = "Konqueror"; + } + +else if (agt.indexOf("msie") != -1) + { + browserType = "IE"; + + if (agt.indexOf("msie 6") != -1) + { browserVer = "IE6"; } + else if (agt.indexOf("msie 7") != -1) + { browserVer = "IE7"; } + } + +else if (agt.indexOf("gecko") != -1) + { + browserType = "Firefox"; + + if (agt.indexOf("rv:1.7") != -1) + { browserVer = "Firefox1"; } + else if (agt.indexOf("rv:1.8)") != -1 || agt.indexOf("rv:1.8.0") != -1) + { browserVer = "Firefox15"; } + else if (agt.indexOf("rv:1.8.1") != -1) + { browserVer = "Firefox2"; } + } + + +// +// Support Functions +// ____________________________________________________________________________ + + +function GetXPosition(item) + { + var position = 0; + + if (item.offsetWidth != null) + { + while (item != document.body && item != null) + { + position += item.offsetLeft; + item = item.offsetParent; + }; + }; + + return position; + }; + + +function GetYPosition(item) + { + var position = 0; + + if (item.offsetWidth != null) + { + while (item != document.body && item != null) + { + position += item.offsetTop; + item = item.offsetParent; + }; + }; + + return position; + }; + + +function MoveToPosition(item, x, y) + { + // Opera 5 chokes on the px extension, so it can use the Microsoft one instead. + + if (item.style.left != null) + { + item.style.left = x + "px"; + item.style.top = y + "px"; + } + else if (item.style.pixelLeft != null) + { + item.style.pixelLeft = x; + item.style.pixelTop = y; + }; + }; + + +// +// Menu +// ____________________________________________________________________________ + + +function ToggleMenu(id) + { + if (!window.document.getElementById) + { return; }; + + var display = window.document.getElementById(id).style.display; + + if (display == "none") + { display = "block"; } + else + { display = "none"; } + + window.document.getElementById(id).style.display = display; + } + +function HideAllBut(ids, max) + { + if (document.getElementById) + { + ids.sort( function(a,b) { return a - b; } ); + var number = 1; + + while (number < max) + { + if (ids.length > 0 && number == ids[0]) + { ids.shift(); } + else + { + document.getElementById("MGroupContent" + number).style.display = "none"; + }; + + number++; + }; + }; + } + + +// +// Tooltips +// ____________________________________________________________________________ + + +var tooltipTimer = 0; + +function ShowTip(event, tooltipID, linkID) + { + if (tooltipTimer) + { clearTimeout(tooltipTimer); }; + + var docX = event.clientX + window.pageXOffset; + var docY = event.clientY + window.pageYOffset; + + var showCommand = "ReallyShowTip('" + tooltipID + "', '" + linkID + "', " + docX + ", " + docY + ")"; + + tooltipTimer = setTimeout(showCommand, 1000); + } + +function ReallyShowTip(tooltipID, linkID, docX, docY) + { + tooltipTimer = 0; + + var tooltip; + var link; + + if (document.getElementById) + { + tooltip = document.getElementById(tooltipID); + link = document.getElementById(linkID); + } +/* else if (document.all) + { + tooltip = eval("document.all['" + tooltipID + "']"); + link = eval("document.all['" + linkID + "']"); + } +*/ + if (tooltip) + { + var left = GetXPosition(link); + var top = GetYPosition(link); + top += link.offsetHeight; + + + // The fallback method is to use the mouse X and Y relative to the document. We use a separate if and test if its a number + // in case some browser snuck through the above if statement but didn't support everything. + + if (!isFinite(top) || top == 0) + { + left = docX; + top = docY; + } + + // Some spacing to get it out from under the cursor. + + top += 10; + + // Make sure the tooltip doesnt get smushed by being too close to the edge, or in some browsers, go off the edge of the + // page. We do it here because Konqueror does get offsetWidth right even if it doesnt get the positioning right. + + if (tooltip.offsetWidth != null) + { + var width = tooltip.offsetWidth; + var docWidth = document.body.clientWidth; + + if (left + width > docWidth) + { left = docWidth - width - 1; } + + // If there's a horizontal scroll bar we could go past zero because it's using the page width, not the window width. + if (left < 0) + { left = 0; }; + } + + MoveToPosition(tooltip, left, top); + tooltip.style.visibility = "visible"; + } + } + +function HideTip(tooltipID) + { + if (tooltipTimer) + { + clearTimeout(tooltipTimer); + tooltipTimer = 0; + } + + var tooltip; + + if (document.getElementById) + { tooltip = document.getElementById(tooltipID); } + else if (document.all) + { tooltip = eval("document.all['" + tooltipID + "']"); } + + if (tooltip) + { tooltip.style.visibility = "hidden"; } + } + + +// +// Blockquote fix for IE +// ____________________________________________________________________________ + + +function NDOnLoad() + { + if (browserVer == "IE6") + { + var scrollboxes = document.getElementsByTagName('blockquote'); + + if (scrollboxes.item(0)) + { + NDDoResize(); + window.onresize=NDOnResize; + }; + }; + }; + + +var resizeTimer = 0; + +function NDOnResize() + { + if (resizeTimer != 0) + { clearTimeout(resizeTimer); }; + + resizeTimer = setTimeout(NDDoResize, 250); + }; + + +function NDDoResize() + { + var scrollboxes = document.getElementsByTagName('blockquote'); + + var i; + var item; + + i = 0; + while (item = scrollboxes.item(i)) + { + item.style.width = 100; + i++; + }; + + i = 0; + while (item = scrollboxes.item(i)) + { + item.style.width = item.parentNode.offsetWidth; + i++; + }; + + clearTimeout(resizeTimer); + resizeTimer = 0; + } + + + +/* ________________________________________________________________________________________________________ + + Class: SearchPanel + ________________________________________________________________________________________________________ + + A class handling everything associated with the search panel. + + Parameters: + + name - The name of the global variable that will be storing this instance. Is needed to be able to set timeouts. + mode - The mode the search is going to work in. Pass CommandLineOption()>, so the + value will be something like "HTML" or "FramedHTML". + + ________________________________________________________________________________________________________ +*/ + + +function SearchPanel(name, mode, resultsPath) + { + if (!name || !mode || !resultsPath) + { alert("Incorrect parameters to SearchPanel."); }; + + + // Group: Variables + // ________________________________________________________________________ + + /* + var: name + The name of the global variable that will be storing this instance of the class. + */ + this.name = name; + + /* + var: mode + The mode the search is going to work in, such as "HTML" or "FramedHTML". + */ + this.mode = mode; + + /* + var: resultsPath + The relative path from the current HTML page to the results page directory. + */ + this.resultsPath = resultsPath; + + /* + var: keyTimeout + The timeout used between a keystroke and when a search is performed. + */ + this.keyTimeout = 0; + + /* + var: keyTimeoutLength + The length of in thousandths of a second. + */ + this.keyTimeoutLength = 500; + + /* + var: lastSearchValue + The last search string executed, or an empty string if none. + */ + this.lastSearchValue = ""; + + /* + var: lastResultsPage + The last results page. The value is only relevant if is set. + */ + this.lastResultsPage = ""; + + /* + var: deactivateTimeout + + The timeout used between when a control is deactivated and when the entire panel is deactivated. Is necessary + because a control may be deactivated in favor of another control in the same panel, in which case it should stay + active. + */ + this.deactivateTimout = 0; + + /* + var: deactivateTimeoutLength + The length of in thousandths of a second. + */ + this.deactivateTimeoutLength = 200; + + + + + // Group: DOM Elements + // ________________________________________________________________________ + + + // Function: DOMSearchField + this.DOMSearchField = function() + { return document.getElementById("MSearchField"); }; + + // Function: DOMSearchType + this.DOMSearchType = function() + { return document.getElementById("MSearchType"); }; + + // Function: DOMPopupSearchResults + this.DOMPopupSearchResults = function() + { return document.getElementById("MSearchResults"); }; + + // Function: DOMPopupSearchResultsWindow + this.DOMPopupSearchResultsWindow = function() + { return document.getElementById("MSearchResultsWindow"); }; + + // Function: DOMSearchPanel + this.DOMSearchPanel = function() + { return document.getElementById("MSearchPanel"); }; + + + + + // Group: Event Handlers + // ________________________________________________________________________ + + + /* + Function: OnSearchFieldFocus + Called when focus is added or removed from the search field. + */ + this.OnSearchFieldFocus = function(isActive) + { + this.Activate(isActive); + }; + + + /* + Function: OnSearchFieldChange + Called when the content of the search field is changed. + */ + this.OnSearchFieldChange = function() + { + if (this.keyTimeout) + { + clearTimeout(this.keyTimeout); + this.keyTimeout = 0; + }; + + var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); + + if (searchValue != this.lastSearchValue) + { + if (searchValue != "") + { + this.keyTimeout = setTimeout(this.name + ".Search()", this.keyTimeoutLength); + } + else + { + if (this.mode == "HTML") + { this.DOMPopupSearchResultsWindow().style.display = "none"; }; + this.lastSearchValue = ""; + }; + }; + }; + + + /* + Function: OnSearchTypeFocus + Called when focus is added or removed from the search type. + */ + this.OnSearchTypeFocus = function(isActive) + { + this.Activate(isActive); + }; + + + /* + Function: OnSearchTypeChange + Called when the search type is changed. + */ + this.OnSearchTypeChange = function() + { + var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); + + if (searchValue != "") + { + this.Search(); + }; + }; + + + + // Group: Action Functions + // ________________________________________________________________________ + + + /* + Function: CloseResultsWindow + Closes the results window. + */ + this.CloseResultsWindow = function() + { + this.DOMPopupSearchResultsWindow().style.display = "none"; + this.Activate(false, true); + }; + + + /* + Function: Search + Performs a search. + */ + this.Search = function() + { + this.keyTimeout = 0; + + var searchValue = this.DOMSearchField().value.replace(/^ +/, ""); + var searchTopic = this.DOMSearchType().value; + + var pageExtension = searchValue.substr(0,1); + + if (pageExtension.match(/^[a-z]/i)) + { pageExtension = pageExtension.toUpperCase(); } + else if (pageExtension.match(/^[0-9]/)) + { pageExtension = 'Numbers'; } + else + { pageExtension = "Symbols"; }; + + var resultsPage; + var resultsPageWithSearch; + var hasResultsPage; + + // indexSectionsWithContent is defined in searchdata.js + if (indexSectionsWithContent[searchTopic][pageExtension] == true) + { + resultsPage = this.resultsPath + '/' + searchTopic + pageExtension + '.html'; + resultsPageWithSearch = resultsPage+'?'+escape(searchValue); + hasResultsPage = true; + } + else + { + resultsPage = this.resultsPath + '/NoResults.html'; + resultsPageWithSearch = resultsPage; + hasResultsPage = false; + }; + + var resultsFrame; + if (this.mode == "HTML") + { resultsFrame = window.frames.MSearchResults; } + else if (this.mode == "FramedHTML") + { resultsFrame = window.top.frames['Content']; }; + + + if (resultsPage != this.lastResultsPage || + + // Bug in IE. If everything becomes hidden in a run, none of them will be able to be reshown in the next for some + // reason. It counts the right number of results, and you can even read the display as "block" after setting it, but it + // just doesn't work in IE 6 or IE 7. So if we're on the right page but the previous search had no results, reload the + // page anyway to get around the bug. + (browserType == "IE" && hasResultsPage && + (!resultsFrame.searchResults || resultsFrame.searchResults.lastMatchCount == 0)) ) + + { + resultsFrame.location.href = resultsPageWithSearch; + } + + // So if the results page is right and there's no IE bug, reperform the search on the existing page. We have to check if there + // are results because NoResults.html doesn't have any JavaScript, and it would be useless to do anything on that page even + // if it did. + else if (hasResultsPage) + { + // We need to check if this exists in case the frame is present but didn't finish loading. + if (resultsFrame.searchResults) + { resultsFrame.searchResults.Search(searchValue); } + + // Otherwise just reload instead of waiting. + else + { resultsFrame.location.href = resultsPageWithSearch; }; + }; + + + var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow(); + + if (this.mode == "HTML" && domPopupSearchResultsWindow.style.display != "block") + { + var domSearchType = this.DOMSearchType(); + + var left = GetXPosition(domSearchType); + var top = GetYPosition(domSearchType) + domSearchType.offsetHeight; + + MoveToPosition(domPopupSearchResultsWindow, left, top); + domPopupSearchResultsWindow.style.display = 'block'; + }; + + + this.lastSearchValue = searchValue; + this.lastResultsPage = resultsPage; + }; + + + + // Group: Activation Functions + // Functions that handle whether the entire panel is active or not. + // ________________________________________________________________________ + + + /* + Function: Activate + + Activates or deactivates the search panel, resetting things to their default values if necessary. You can call this on every + control's OnBlur() and it will handle not deactivating the entire panel when focus is just switching between them transparently. + + Parameters: + + isActive - Whether you're activating or deactivating the panel. + ignoreDeactivateDelay - Set if you're positive the action will deactivate the panel and thus want to skip the delay. + */ + this.Activate = function(isActive, ignoreDeactivateDelay) + { + // We want to ignore isActive being false while the results window is open. + if (isActive || (this.mode == "HTML" && this.DOMPopupSearchResultsWindow().style.display == "block")) + { + if (this.inactivateTimeout) + { + clearTimeout(this.inactivateTimeout); + this.inactivateTimeout = 0; + }; + + this.DOMSearchPanel().className = 'MSearchPanelActive'; + + var searchField = this.DOMSearchField(); + + if (searchField.value == 'Search') + { searchField.value = ""; } + } + else if (!ignoreDeactivateDelay) + { + this.inactivateTimeout = setTimeout(this.name + ".InactivateAfterTimeout()", this.inactivateTimeoutLength); + } + else + { + this.InactivateAfterTimeout(); + }; + }; + + + /* + Function: InactivateAfterTimeout + + Called by , which is set by . Inactivation occurs on a timeout because a control may + receive OnBlur() when focus is really transferring to another control in the search panel. In this case we don't want to + actually deactivate the panel because not only would that cause a visible flicker but it could also reset the search value. + So by doing it on a timeout instead, there's a short period where the second control's OnFocus() can cancel the deactivation. + */ + this.InactivateAfterTimeout = function() + { + this.inactivateTimeout = 0; + + this.DOMSearchPanel().className = 'MSearchPanelInactive'; + this.DOMSearchField().value = "Search"; + + this.lastSearchValue = ""; + this.lastResultsPage = ""; + }; + }; + + + + +/* ________________________________________________________________________________________________________ + + Class: SearchResults + _________________________________________________________________________________________________________ + + The class that handles everything on the search results page. + _________________________________________________________________________________________________________ +*/ + + +function SearchResults(name, mode) + { + /* + var: mode + The mode the search is going to work in, such as "HTML" or "FramedHTML". + */ + this.mode = mode; + + /* + var: lastMatchCount + The number of matches from the last run of . + */ + this.lastMatchCount = 0; + + + /* + Function: Toggle + Toggles the visibility of the passed element ID. + */ + this.Toggle = function(id) + { + if (this.mode == "FramedHTML") + { return; }; + + var parentElement = document.getElementById(id); + + var element = parentElement.firstChild; + + while (element && element != parentElement) + { + if (element.nodeName == 'DIV' && element.className == 'ISubIndex') + { + if (element.style.display == 'block') + { element.style.display = "none"; } + else + { element.style.display = 'block'; } + }; + + if (element.nodeName == 'DIV' && element.hasChildNodes()) + { element = element.firstChild; } + else if (element.nextSibling) + { element = element.nextSibling; } + else + { + do + { + element = element.parentNode; + } + while (element && element != parentElement && !element.nextSibling); + + if (element && element != parentElement) + { element = element.nextSibling; }; + }; + }; + }; + + + /* + Function: Search + + Searches for the passed string. If there is no parameter, it takes it from the URL query. + + Always returns true, since other documents may try to call it and that may or may not be possible. + */ + this.Search = function(search) + { + if (!search) + { + search = window.location.search; + search = search.substring(1); // Remove the leading ? + search = unescape(search); + }; + + search = search.replace(/^ +/, ""); + search = search.replace(/ +$/, ""); + search = search.toLowerCase(); + + if (search.match(/[^a-z0-9]/)) // Just a little speedup so it doesn't have to go through the below unnecessarily. + { + search = search.replace(/\_/g, "_und"); + search = search.replace(/\ +/gi, "_spc"); + search = search.replace(/\~/g, "_til"); + search = search.replace(/\!/g, "_exc"); + search = search.replace(/\@/g, "_att"); + search = search.replace(/\#/g, "_num"); + search = search.replace(/\$/g, "_dol"); + search = search.replace(/\%/g, "_pct"); + search = search.replace(/\^/g, "_car"); + search = search.replace(/\&/g, "_amp"); + search = search.replace(/\*/g, "_ast"); + search = search.replace(/\(/g, "_lpa"); + search = search.replace(/\)/g, "_rpa"); + search = search.replace(/\-/g, "_min"); + search = search.replace(/\+/g, "_plu"); + search = search.replace(/\=/g, "_equ"); + search = search.replace(/\{/g, "_lbc"); + search = search.replace(/\}/g, "_rbc"); + search = search.replace(/\[/g, "_lbk"); + search = search.replace(/\]/g, "_rbk"); + search = search.replace(/\:/g, "_col"); + search = search.replace(/\;/g, "_sco"); + search = search.replace(/\"/g, "_quo"); + search = search.replace(/\'/g, "_apo"); + search = search.replace(/\/g, "_ran"); + search = search.replace(/\,/g, "_com"); + search = search.replace(/\./g, "_per"); + search = search.replace(/\?/g, "_que"); + search = search.replace(/\//g, "_sla"); + search = search.replace(/[^a-z0-9\_]i/gi, "_zzz"); + }; + + var resultRows = document.getElementsByTagName("div"); + var matches = 0; + + var i = 0; + while (i < resultRows.length) + { + var row = resultRows.item(i); + + if (row.className == "SRResult") + { + var rowMatchName = row.id.toLowerCase(); + rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); + + if (search.length <= rowMatchName.length && rowMatchName.substr(0, search.length) == search) + { + row.style.display = "block"; + matches++; + } + else + { row.style.display = "none"; }; + }; + + i++; + }; + + document.getElementById("Searching").style.display="none"; + + if (matches == 0) + { document.getElementById("NoMatches").style.display="block"; } + else + { document.getElementById("NoMatches").style.display="none"; } + + this.lastMatchCount = matches; + + return true; + }; + }; + diff --git a/docs/javascript/prettify.js b/docs/javascript/prettify.js new file mode 100644 index 0000000..fda4bf1 --- /dev/null +++ b/docs/javascript/prettify.js @@ -0,0 +1,1526 @@ + +// This code comes from the December 2009 release of Google Prettify, which is Copyright 2006 Google Inc. +// Minor modifications are marked with "ND Change" comments. +// As part of Natural Docs, this code is licensed under version 3 of the GNU Affero General Public License (AGPL.) +// However, it may also be obtained separately under version 2.0 of the Apache License. +// Refer to License.txt for the complete details + + +// Main code +// ____________________________________________________________________________ + +// Copyright (C) 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview + * some functions for browser-side pretty printing of code contained in html. + *

+ * + * For a fairly comprehensive set of languages see the + * README + * file that came with this source. At a minimum, the lexer should work on a + * number of languages including C and friends, Java, Python, Bash, SQL, HTML, + * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk + * and a subset of Perl, but, because of commenting conventions, doesn't work on + * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. + *

+ * Usage:

    + *
  1. include this source file in an html page via + * {@code } + *
  2. define style rules. See the example page for examples. + *
  3. mark the {@code
    } and {@code } tags in your source with
    + *    {@code class=prettyprint.}
    + *    You can also use the (html deprecated) {@code } tag, but the pretty
    + *    printer needs to do more substantial DOM manipulations to support that, so
    + *    some css styles may not be preserved.
    + * </ol>
    + * That's it.  I wanted to keep the API as simple as possible, so there's no
    + * need to specify which language the code is in, but if you wish, you can add
    + * another class to the {@code <pre>} or {@code <code>} element to specify the
    + * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
    + * starts with "lang-" followed by a file extension, specifies the file type.
    + * See the "lang-*.js" files in this directory for code that implements
    + * per-language file handlers.
    + * <p>
    + * Change log:<br>
    + * cbeust, 2006/08/22
    + * <blockquote>
    + *   Java annotations (start with "@") are now captured as literals ("lit")
    + * </blockquote>
    + * @requires console
    + * @overrides window
    + */
    +
    +// JSLint declarations
    +/*global console, document, navigator, setTimeout, window */
    +
    +/**
    + * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
    + * UI events.
    + * If set to {@code false}, {@code prettyPrint()} is synchronous.
    + */
    +window['PR_SHOULD_USE_CONTINUATION'] = true;
    +
    +/** the number of characters between tab columns */
    +window['PR_TAB_WIDTH'] = 8;
    +
    +/** Walks the DOM returning a properly escaped version of innerHTML.
    +  * @param {Node} node
    +  * @param {Array.<string>} out output buffer that receives chunks of HTML.
    +  */
    +window['PR_normalizedHtml']
    +
    +/** Contains functions for creating and registering new language handlers.
    +  * @type {Object}
    +  */
    +  = window['PR']
    +
    +/** Pretty print a chunk of code.
    +  *
    +  * @param {string} sourceCodeHtml code as html
    +  * @return {string} code as html, but prettier
    +  */
    +  = window['prettyPrintOne']
    +/** Find all the {@code <pre>} and {@code <code>} tags in the DOM with
    +  * {@code class=prettyprint} and prettify them.
    +  * @param {Function?} opt_whenDone if specified, called when the last entry
    +  *     has been finished.
    +  */
    +  = window['prettyPrint'] = void 0;
    +
    +/** browser detection. @extern @returns false if not IE, otherwise the major version. */
    +window['_pr_isIE6'] = function () {
    +  var ieVersion = navigator && navigator.userAgent &&
    +      navigator.userAgent.match(/\bMSIE ([678])\./);
    +  ieVersion = ieVersion ? +ieVersion[1] : false;
    +  window['_pr_isIE6'] = function () { return ieVersion; };
    +  return ieVersion;
    +};
    +
    +
    +(function () {
    +  // Keyword lists for various languages.
    +  var FLOW_CONTROL_KEYWORDS =
    +      "break continue do else for if return while ";
    +  var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
    +      "double enum extern float goto int long register short signed sizeof " +
    +      "static struct switch typedef union unsigned void volatile ";
    +  var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
    +      "new operator private protected public this throw true try typeof ";
    +  var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
    +      "concept concept_map const_cast constexpr decltype " +
    +      "dynamic_cast explicit export friend inline late_check " +
    +      "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
    +      "template typeid typename using virtual wchar_t where ";
    +  var JAVA_KEYWORDS = COMMON_KEYWORDS +
    +      "abstract boolean byte extends final finally implements import " +
    +      "instanceof null native package strictfp super synchronized throws " +
    +      "transient ";
    +  var CSHARP_KEYWORDS = JAVA_KEYWORDS +
    +      "as base by checked decimal delegate descending event " +
    +      "fixed foreach from group implicit in interface internal into is lock " +
    +      "object out override orderby params partial readonly ref sbyte sealed " +
    +      "stackalloc string select uint ulong unchecked unsafe ushort var ";
    +  var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
    +      "debugger eval export function get null set undefined var with " +
    +      "Infinity NaN ";
    +  var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
    +      "goto if import last local my next no our print package redo require " +
    +      "sub undef unless until use wantarray while BEGIN END ";
    +  var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
    +      "elif except exec finally from global import in is lambda " +
    +      "nonlocal not or pass print raise try with yield " +
    +      "False True None ";
    +  var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
    +      " defined elsif end ensure false in module next nil not or redo rescue " +
    +      "retry self super then true undef unless until when yield BEGIN END ";
    +  var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
    +      "function in local set then until ";
    +  var ALL_KEYWORDS = (
    +      CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
    +      PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
    +
    +  // token style names.  correspond to css classes
    +  /** token style for a string literal */
    +  var PR_STRING = 'str';
    +  /** token style for a keyword */
    +  var PR_KEYWORD = 'kwd';
    +  /** token style for a comment */
    +  var PR_COMMENT = 'com';
    +  /** token style for a type */
    +  var PR_TYPE = 'typ';
    +  /** token style for a literal value.  e.g. 1, null, true. */
    +  var PR_LITERAL = 'lit';
    +  /** token style for a punctuation string. */
    +  var PR_PUNCTUATION = 'pun';
    +  /** token style for a punctuation string. */
    +  var PR_PLAIN = 'pln';
    +
    +  /** token style for an sgml tag. */
    +  var PR_TAG = 'tag';
    +  /** token style for a markup declaration such as a DOCTYPE. */
    +  var PR_DECLARATION = 'dec';
    +  /** token style for embedded source. */
    +  var PR_SOURCE = 'src';
    +  /** token style for an sgml attribute name. */
    +  var PR_ATTRIB_NAME = 'atn';
    +  /** token style for an sgml attribute value. */
    +  var PR_ATTRIB_VALUE = 'atv';
    +
    +  /**
    +   * A class that indicates a section of markup that is not code, e.g. to allow
    +   * embedding of line numbers within code listings.
    +   */
    +  var PR_NOCODE = 'nocode';
    +
    +  /** A set of tokens that can precede a regular expression literal in
    +    * javascript.
    +    * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
    +    * list, but I've removed ones that might be problematic when seen in
    +    * languages that don't support regular expression literals.
    +    *
    +    * <p>Specifically, I've removed any keywords that can't precede a regexp
    +    * literal in a syntactically legal javascript program, and I've removed the
    +    * "in" keyword since it's not a keyword in many languages, and might be used
    +    * as a count of inches.
    +    *
    +    * <p>The link a above does not accurately describe EcmaScript rules since
    +    * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
    +    * very well in practice.
    +    *
    +    * @private
    +    */
    +  var REGEXP_PRECEDER_PATTERN = function () {
    +      var preceders = [
    +          "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
    +          "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
    +          "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
    +          "<", "<<", "<<=", "<=", "=", "==", "===", ">",
    +          ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
    +          "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
    +          "||=", "~" /* handles =~ and !~ */,
    +          "break", "case", "continue", "delete",
    +          "do", "else", "finally", "instanceof",
    +          "return", "throw", "try", "typeof"
    +          ];
    +      var pattern = '(?:^^|[+-]';
    +      for (var i = 0; i < preceders.length; ++i) {
    +        pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
    +      }
    +      pattern += ')\\s*';  // matches at end, and matches empty string
    +      return pattern;
    +      // CAVEAT: this does not properly handle the case where a regular
    +      // expression immediately follows another since a regular expression may
    +      // have flags for case-sensitivity and the like.  Having regexp tokens
    +      // adjacent is not valid in any language I'm aware of, so I'm punting.
    +      // TODO: maybe style special characters inside a regexp as punctuation.
    +    }();
    +
    +  // Define regexps here so that the interpreter doesn't have to create an
    +  // object each time the function containing them is called.
    +  // The language spec requires a new object created even if you don't access
    +  // the $1 members.
    +  var pr_amp = /&/g;
    +  var pr_lt = /</g;
    +  var pr_gt = />/g;
    +  var pr_quot = /\"/g;
    +  /** like textToHtml but escapes double quotes to be attribute safe. */
    +  function attribToHtml(str) {
    +    return str.replace(pr_amp, '&amp;')
    +        .replace(pr_lt, '&lt;')
    +        .replace(pr_gt, '&gt;')
    +        .replace(pr_quot, '&quot;');
    +  }
    +
    +  /** escapest html special characters to html. */
    +  function textToHtml(str) {
    +    return str.replace(pr_amp, '&amp;')
    +        .replace(pr_lt, '&lt;')
    +        .replace(pr_gt, '&gt;');
    +  }
    +
    +
    +  var pr_ltEnt = /&lt;/g;
    +  var pr_gtEnt = /&gt;/g;
    +  var pr_aposEnt = /&apos;/g;
    +  var pr_quotEnt = /&quot;/g;
    +  var pr_ampEnt = /&amp;/g;
    +  var pr_nbspEnt = /&nbsp;/g;
    +  /** unescapes html to plain text. */
    +  function htmlToText(html) {
    +    var pos = html.indexOf('&');
    +    if (pos < 0) { return html; }
    +    // Handle numeric entities specially.  We can't use functional substitution
    +    // since that doesn't work in older versions of Safari.
    +    // These should be rare since most browsers convert them to normal chars.
    +    for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0;) {
    +      var end = html.indexOf(';', pos);
    +      if (end >= 0) {
    +        var num = html.substring(pos + 3, end);
    +        var radix = 10;
    +        if (num && num.charAt(0) === 'x') {
    +          num = num.substring(1);
    +          radix = 16;
    +        }
    +        var codePoint = parseInt(num, radix);
    +        if (!isNaN(codePoint)) {
    +          html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
    +                  html.substring(end + 1));
    +        }
    +      }
    +    }
    +
    +    return html.replace(pr_ltEnt, '<')
    +        .replace(pr_gtEnt, '>')
    +        .replace(pr_aposEnt, "'")
    +        .replace(pr_quotEnt, '"')
    +        .replace(pr_nbspEnt, ' ')
    +        .replace(pr_ampEnt, '&');
    +  }
    +
    +  /** is the given node's innerHTML normally unescaped? */
    +  function isRawContent(node) {
    +    return 'XMP' === node.tagName;
    +  }
    +
    +  var newlineRe = /[\r\n]/g;
    +  /**
    +   * Are newlines and adjacent spaces significant in the given node's innerHTML?
    +   */
    +  function isPreformatted(node, content) {
    +    // PRE means preformatted, and is a very common case, so don't create
    +    // unnecessary computed style objects.
    +    if ('PRE' === node.tagName) { return true; }
    +    if (!newlineRe.test(content)) { return true; }  // Don't care
    +    var whitespace = '';
    +    // For disconnected nodes, IE has no currentStyle.
    +    if (node.currentStyle) {
    +      whitespace = node.currentStyle.whiteSpace;
    +    } else if (window.getComputedStyle) {
    +      // Firefox makes a best guess if node is disconnected whereas Safari
    +      // returns the empty string.
    +      whitespace = window.getComputedStyle(node, null).whiteSpace;
    +    }
    +    return !whitespace || whitespace === 'pre';
    +  }
    +
    +  function normalizedHtml(node, out) {
    +    switch (node.nodeType) {
    +      case 1:  // an element
    +        var name = node.tagName.toLowerCase();
    +        out.push('<', name);
    +        for (var i = 0; i < node.attributes.length; ++i) {
    +          var attr = node.attributes[i];
    +          if (!attr.specified) { continue; }
    +          out.push(' ');
    +          normalizedHtml(attr, out);
    +        }
    +        out.push('>');
    +        for (var child = node.firstChild; child; child = child.nextSibling) {
    +          normalizedHtml(child, out);
    +        }
    +        if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
    +          out.push('<\/', name, '>');
    +        }
    +        break;
    +      case 2: // an attribute
    +        out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"');
    +        break;
    +      case 3: case 4: // text
    +        out.push(textToHtml(node.nodeValue));
    +        break;
    +    }
    +  }
    +
    +  /**
    +   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
    +   * matches the union o the sets o strings matched d by the input RegExp.
    +   * Since it matches globally, if the input strings have a start-of-input
    +   * anchor (/^.../), it is ignored for the purposes of unioning.
    +   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
    +   * @return {RegExp} a global regex.
    +   */
    +  function combinePrefixPatterns(regexs) {
    +    var capturedGroupIndex = 0;
    +
    +    var needToFoldCase = false;
    +    var ignoreCase = false;
    +    for (var i = 0, n = regexs.length; i < n; ++i) {
    +      var regex = regexs[i];
    +      if (regex.ignoreCase) {
    +        ignoreCase = true;
    +      } else if (/[a-z]/i.test(regex.source.replace(
    +                     /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
    +        needToFoldCase = true;
    +        ignoreCase = false;
    +        break;
    +      }
    +    }
    +
    +    function decodeEscape(charsetPart) {
    +      if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
    +      switch (charsetPart.charAt(1)) {
    +        case 'b': return 8;
    +        case 't': return 9;
    +        case 'n': return 0xa;
    +        case 'v': return 0xb;
    +        case 'f': return 0xc;
    +        case 'r': return 0xd;
    +        case 'u': case 'x':
    +          return parseInt(charsetPart.substring(2), 16)
    +              || charsetPart.charCodeAt(1);
    +        case '0': case '1': case '2': case '3': case '4':
    +        case '5': case '6': case '7':
    +          return parseInt(charsetPart.substring(1), 8);
    +        default: return charsetPart.charCodeAt(1);
    +      }
    +    }
    +
    +    function encodeEscape(charCode) {
    +      if (charCode < 0x20) {
    +        return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
    +      }
    +      var ch = String.fromCharCode(charCode);
    +      if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
    +        ch = '\\' + ch;
    +      }
    +      return ch;
    +    }
    +
    +    function caseFoldCharset(charSet) {
    +      var charsetParts = charSet.substring(1, charSet.length - 1).match(
    +          new RegExp(
    +              '\\\\u[0-9A-Fa-f]{4}'
    +              + '|\\\\x[0-9A-Fa-f]{2}'
    +              + '|\\\\[0-3][0-7]{0,2}'
    +              + '|\\\\[0-7]{1,2}'
    +              + '|\\\\[\\s\\S]'
    +              + '|-'
    +              + '|[^-\\\\]',
    +              'g'));
    +      var groups = [];
    +      var ranges = [];
    +      var inverse = charsetParts[0] === '^';
    +      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
    +        var p = charsetParts[i];
    +        switch (p) {
    +          case '\\B': case '\\b':
    +          case '\\D': case '\\d':
    +          case '\\S': case '\\s':
    +          case '\\W': case '\\w':
    +            groups.push(p);
    +            continue;
    +        }
    +        var start = decodeEscape(p);
    +        var end;
    +        if (i + 2 < n && '-' === charsetParts[i + 1]) {
    +          end = decodeEscape(charsetParts[i + 2]);
    +          i += 2;
    +        } else {
    +          end = start;
    +        }
    +        ranges.push([start, end]);
    +        // If the range might intersect letters, then expand it.
    +        if (!(end < 65 || start > 122)) {
    +          if (!(end < 65 || start > 90)) {
    +            ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
    +          }
    +          if (!(end < 97 || start > 122)) {
    +            ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
    +          }
    +        }
    +      }
    +
    +      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
    +      // -> [[1, 12], [14, 14], [16, 17]]
    +      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
    +      var consolidatedRanges = [];
    +      var lastRange = [NaN, NaN];
    +      for (var i = 0; i < ranges.length; ++i) {
    +        var range = ranges[i];
    +        if (range[0] <= lastRange[1] + 1) {
    +          lastRange[1] = Math.max(lastRange[1], range[1]);
    +        } else {
    +          consolidatedRanges.push(lastRange = range);
    +        }
    +      }
    +
    +      var out = ['['];
    +      if (inverse) { out.push('^'); }
    +      out.push.apply(out, groups);
    +      for (var i = 0; i < consolidatedRanges.length; ++i) {
    +        var range = consolidatedRanges[i];
    +        out.push(encodeEscape(range[0]));
    +        if (range[1] > range[0]) {
    +          if (range[1] + 1 > range[0]) { out.push('-'); }
    +          out.push(encodeEscape(range[1]));
    +        }
    +      }
    +      out.push(']');
    +      return out.join('');
    +    }
    +
    +    function allowAnywhereFoldCaseAndRenumberGroups(regex) {
    +      // Split into character sets, escape sequences, punctuation strings
    +      // like ('(', '(?:', ')', '^'), and runs of characters that do not
    +      // include any of the above.
    +      var parts = regex.source.match(
    +          new RegExp(
    +              '(?:'
    +              + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
    +              + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
    +              + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
    +              + '|\\\\[0-9]+'  // a back-reference or octal escape
    +              + '|\\\\[^ux0-9]'  // other escape sequence
    +              + '|\\(\\?[:!=]'  // start of a non-capturing group
    +              + '|[\\(\\)\\^]'  // start/emd of a group, or line start
    +              + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
    +              + ')',
    +              'g'));
    +      var n = parts.length;
    +
    +      // Maps captured group numbers to the number they will occupy in
    +      // the output or to -1 if that has not been determined, or to
    +      // undefined if they need not be capturing in the output.
    +      var capturedGroups = [];
    +
    +      // Walk over and identify back references to build the capturedGroups
    +      // mapping.
    +      for (var i = 0, groupIndex = 0; i < n; ++i) {
    +        var p = parts[i];
    +        if (p === '(') {
    +          // groups are 1-indexed, so max group index is count of '('
    +          ++groupIndex;
    +        } else if ('\\' === p.charAt(0)) {
    +          var decimalValue = +p.substring(1);
    +          if (decimalValue && decimalValue <= groupIndex) {
    +            capturedGroups[decimalValue] = -1;
    +          }
    +        }
    +      }
    +
    +      // Renumber groups and reduce capturing groups to non-capturing groups
    +      // where possible.
    +      for (var i = 1; i < capturedGroups.length; ++i) {
    +        if (-1 === capturedGroups[i]) {
    +          capturedGroups[i] = ++capturedGroupIndex;
    +        }
    +      }
    +      for (var i = 0, groupIndex = 0; i < n; ++i) {
    +        var p = parts[i];
    +        if (p === '(') {
    +          ++groupIndex;
    +          if (capturedGroups[groupIndex] === undefined) {
    +            parts[i] = '(?:';
    +          }
    +        } else if ('\\' === p.charAt(0)) {
    +          var decimalValue = +p.substring(1);
    +          if (decimalValue && decimalValue <= groupIndex) {
    +            parts[i] = '\\' + capturedGroups[groupIndex];
    +          }
    +        }
    +      }
    +
    +      // Remove any prefix anchors so that the output will match anywhere.
    +      // ^^ really does mean an anchored match though.
    +      for (var i = 0, groupIndex = 0; i < n; ++i) {
    +        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
    +      }
    +
    +      // Expand letters to groupts to handle mixing of case-sensitive and
    +      // case-insensitive patterns if necessary.
    +      if (regex.ignoreCase && needToFoldCase) {
    +        for (var i = 0; i < n; ++i) {
    +          var p = parts[i];
    +          var ch0 = p.charAt(0);
    +          if (p.length >= 2 && ch0 === '[') {
    +            parts[i] = caseFoldCharset(p);
    +          } else if (ch0 !== '\\') {
    +            // TODO: handle letters in numeric escapes.
    +            parts[i] = p.replace(
    +                /[a-zA-Z]/g,
    +                function (ch) {
    +                  var cc = ch.charCodeAt(0);
    +                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
    +                });
    +          }
    +        }
    +      }
    +
    +      return parts.join('');
    +    }
    +
    +    var rewritten = [];
    +    for (var i = 0, n = regexs.length; i < n; ++i) {
    +      var regex = regexs[i];
    +      if (regex.global || regex.multiline) { throw new Error('' + regex); }
    +      rewritten.push(
    +          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
    +    }
    +
    +    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
    +  }
    +
    +  var PR_innerHtmlWorks = null;
    +  function getInnerHtml(node) {
    +    // inner html is hopelessly broken in Safari 2.0.4 when the content is
    +    // an html description of well formed XML and the containing tag is a PRE
    +    // tag, so we detect that case and emulate innerHTML.
    +    if (null === PR_innerHtmlWorks) {
    +      var testNode = document.createElement('PRE');
    +      testNode.appendChild(
    +          document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));
    +      PR_innerHtmlWorks = !/</.test(testNode.innerHTML);
    +    }
    +
    +    if (PR_innerHtmlWorks) {
    +      var content = node.innerHTML;
    +      // XMP tags contain unescaped entities so require special handling.
    +      if (isRawContent(node)) {
    +        content = textToHtml(content);
    +      } else if (!isPreformatted(node, content)) {
    +        content = content.replace(/(<br\s*\/?>)[\r\n]+/g, '$1')
    +            .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
    +      }
    +      return content;
    +    }
    +
    +    var out = [];
    +    for (var child = node.firstChild; child; child = child.nextSibling) {
    +      normalizedHtml(child, out);
    +    }
    +    return out.join('');
    +  }
    +
    +  /** returns a function that expand tabs to spaces.  This function can be fed
    +    * successive chunks of text, and will maintain its own internal state to
    +    * keep track of how tabs are expanded.
    +    * @return {function (string) : string} a function that takes
    +    *   plain text and return the text with tabs expanded.
    +    * @private
    +    */
    +  function makeTabExpander(tabWidth) {
    +    var SPACES = '                ';
    +    var charInLine = 0;
    +
    +    return function (plainText) {
    +      // walk over each character looking for tabs and newlines.
    +      // On tabs, expand them.  On newlines, reset charInLine.
    +      // Otherwise increment charInLine
    +      var out = null;
    +      var pos = 0;
    +      for (var i = 0, n = plainText.length; i < n; ++i) {
    +        var ch = plainText.charAt(i);
    +
    +        switch (ch) {
    +          case '\t':
    +            if (!out) { out = []; }
    +            out.push(plainText.substring(pos, i));
    +            // calculate how much space we need in front of this part
    +            // nSpaces is the amount of padding -- the number of spaces needed
    +            // to move us to the next column, where columns occur at factors of
    +            // tabWidth.
    +            var nSpaces = tabWidth - (charInLine % tabWidth);
    +            charInLine += nSpaces;
    +            for (; nSpaces >= 0; nSpaces -= SPACES.length) {
    +              out.push(SPACES.substring(0, nSpaces));
    +            }
    +            pos = i + 1;
    +            break;
    +          case '\n':
    +            charInLine = 0;
    +            break;
    +          default:
    +            ++charInLine;
    +        }
    +      }
    +      if (!out) { return plainText; }
    +      out.push(plainText.substring(pos));
    +      return out.join('');
    +    };
    +  }
    +
    +  var pr_chunkPattern = new RegExp(
    +      '[^<]+'  // A run of characters other than '<'
    +      + '|<\!--[\\s\\S]*?--\>'  // an HTML comment
    +      + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'  // a CDATA section
    +      // a probable tag that should not be highlighted
    +      + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
    +      + '|<',  // A '<' that does not begin a larger chunk
    +      'g');
    +  var pr_commentPrefix = /^<\!--/;
    +  var pr_cdataPrefix = /^<!\[CDATA\[/;
    +  var pr_brPrefix = /^<br\b/i;
    +  var pr_tagNameRe = /^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/;
    +
    +  /** split markup into chunks of html tags (style null) and
    +    * plain text (style {@link #PR_PLAIN}), converting tags which are
    +    * significant for tokenization (<br>) into their textual equivalent.
    +    *
    +    * @param {string} s html where whitespace is considered significant.
    +    * @return {Object} source code and extracted tags.
    +    * @private
    +    */
    +  function extractTags(s) {
    +    // since the pattern has the 'g' modifier and defines no capturing groups,
    +    // this will return a list of all chunks which we then classify and wrap as
    +    // PR_Tokens
    +    var matches = s.match(pr_chunkPattern);
    +    var sourceBuf = [];
    +    var sourceBufLen = 0;
    +    var extractedTags = [];
    +    if (matches) {
    +      for (var i = 0, n = matches.length; i < n; ++i) {
    +        var match = matches[i];
    +        if (match.length > 1 && match.charAt(0) === '<') {
    +          if (pr_commentPrefix.test(match)) { continue; }
    +          if (pr_cdataPrefix.test(match)) {
    +            // strip CDATA prefix and suffix.  Don't unescape since it's CDATA
    +            sourceBuf.push(match.substring(9, match.length - 3));
    +            sourceBufLen += match.length - 12;
    +          } else if (pr_brPrefix.test(match)) {
    +            // <br> tags are lexically significant so convert them to text.
    +            // This is undone later.
    +            sourceBuf.push('\n');
    +            ++sourceBufLen;
    +          } else {
    +            if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
    +              // A <span class="nocode"> will start a section that should be
    +              // ignored.  Continue walking the list until we see a matching end
    +              // tag.
    +              var name = match.match(pr_tagNameRe)[2];
    +              var depth = 1;
    +              var j;
    +              end_tag_loop:
    +              for (j = i + 1; j < n; ++j) {
    +                var name2 = matches[j].match(pr_tagNameRe);
    +                if (name2 && name2[2] === name) {
    +                  if (name2[1] === '/') {
    +                    if (--depth === 0) { break end_tag_loop; }
    +                  } else {
    +                    ++depth;
    +                  }
    +                }
    +              }
    +              if (j < n) {
    +                extractedTags.push(
    +                    sourceBufLen, matches.slice(i, j + 1).join(''));
    +                i = j;
    +              } else {  // Ignore unclosed sections.
    +                extractedTags.push(sourceBufLen, match);
    +              }
    +            } else {
    +              extractedTags.push(sourceBufLen, match);
    +            }
    +          }
    +        } else {
    +          var literalText = htmlToText(match);
    +          sourceBuf.push(literalText);
    +          sourceBufLen += literalText.length;
    +        }
    +      }
    +    }
    +    return { source: sourceBuf.join(''), tags: extractedTags };
    +  }
    +
    +  /** True if the given tag contains a class attribute with the nocode class. */
    +  function isNoCodeTag(tag) {
    +    return !!tag
    +        // First canonicalize the representation of attributes
    +        .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
    +                 ' $1="$2$3$4"')
    +        // Then look for the attribute we want.
    +        .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
    +  }
    +
    +  /**
    +   * Apply the given language handler to sourceCode and add the resulting
    +   * decorations to out.
    +   * @param {number} basePos the index of sourceCode within the chunk of source
    +   *    whose decorations are already present on out.
    +   */
    +  function appendDecorations(basePos, sourceCode, langHandler, out) {
    +    if (!sourceCode) { return; }
    +    var job = {
    +      source: sourceCode,
    +      basePos: basePos
    +    };
    +    langHandler(job);
    +    out.push.apply(out, job.decorations);
    +  }
    +
    +  /** Given triples of [style, pattern, context] returns a lexing function,
    +    * The lexing function interprets the patterns to find token boundaries and
    +    * returns a decoration list of the form
    +    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
    +    * where index_n is an index into the sourceCode, and style_n is a style
    +    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
    +    * all characters in sourceCode[index_n-1:index_n].
    +    *
    +    * The stylePatterns is a list whose elements have the form
    +    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
    +    *
    +    * Style is a style constant like PR_PLAIN, or can be a string of the
    +    * form 'lang-FOO', where FOO is a language extension describing the
    +    * language of the portion of the token in $1 after pattern executes.
    +    * E.g., if style is 'lang-lisp', and group 1 contains the text
    +    * '(hello (world))', then that portion of the token will be passed to the
    +    * registered lisp handler for formatting.
    +    * The text before and after group 1 will be restyled using this decorator
    +    * so decorators should take care that this doesn't result in infinite
    +    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
    +    * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
    +    * '<script>foo()<\/script>', which would cause the current decorator to
    +    * be called with '<script>' which would not match the same rule since
    +    * group 1 must not be empty, so it would be instead styled as PR_TAG by
    +    * the generic tag rule.  The handler registered for the 'js' extension would
    +    * then be called with 'foo()', and finally, the current decorator would
    +    * be called with '<\/script>' which would not match the original rule and
    +    * so the generic tag rule would identify it as a tag.
    +    *
    +    * Pattern must only match prefixes, and if it matches a prefix, then that
    +    * match is considered a token with the same style.
    +    *
    +    * Context is applied to the last non-whitespace, non-comment token
    +    * recognized.
    +    *
    +    * Shortcut is an optional string of characters, any of which, if the first
    +    * character, gurantee that this pattern and only this pattern matches.
    +    *
    +    * @param {Array} shortcutStylePatterns patterns that always start with
    +    *   a known character.  Must have a shortcut string.
    +    * @param {Array} fallthroughStylePatterns patterns that will be tried in
    +    *   order if the shortcut ones fail.  May have shortcuts.
    +    *
    +    * @return {function (Object)} a
    +    *   function that takes source code and returns a list of decorations.
    +    */
    +  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
    +    var shortcuts = {};
    +    var tokenizer;
    +    (function () {
    +      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
    +      var allRegexs = [];
    +      var regexKeys = {};
    +      for (var i = 0, n = allPatterns.length; i < n; ++i) {
    +        var patternParts = allPatterns[i];
    +        var shortcutChars = patternParts[3];
    +        if (shortcutChars) {
    +          for (var c = shortcutChars.length; --c >= 0;) {
    +            shortcuts[shortcutChars.charAt(c)] = patternParts;
    +          }
    +        }
    +        var regex = patternParts[1];
    +        var k = '' + regex;
    +        if (!regexKeys.hasOwnProperty(k)) {
    +          allRegexs.push(regex);
    +          regexKeys[k] = null;
    +        }
    +      }
    +      allRegexs.push(/[\0-\uffff]/);
    +      tokenizer = combinePrefixPatterns(allRegexs);
    +    })();
    +
    +    var nPatterns = fallthroughStylePatterns.length;
    +    var notWs = /\S/;
    +
    +    /**
    +     * Lexes job.source and produces an output array job.decorations of style
    +     * classes preceded by the position at which they start in job.source in
    +     * order.
    +     *
    +     * @param {Object} job an object like {@code
    +     *    source: {string} sourceText plain text,
    +     *    basePos: {int} position of job.source in the larger chunk of
    +     *        sourceCode.
    +     * }
    +     */
    +    var decorate = function (job) {
    +      var sourceCode = job.source, basePos = job.basePos;
    +      /** Even entries are positions in source in ascending order.  Odd enties
    +        * are style markers (e.g., PR_COMMENT) that run from that position until
    +        * the end.
    +        * @type {Array.<number|string>}
    +        */
    +      var decorations = [basePos, PR_PLAIN];
    +      var pos = 0;  // index into sourceCode
    +      var tokens = sourceCode.match(tokenizer) || [];
    +      var styleCache = {};
    +
    +      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
    +        var token = tokens[ti];
    +        var style = styleCache[token];
    +        var match = void 0;
    +
    +        var isEmbedded;
    +        if (typeof style === 'string') {
    +          isEmbedded = false;
    +        } else {
    +          var patternParts = shortcuts[token.charAt(0)];
    +          if (patternParts) {
    +            match = token.match(patternParts[1]);
    +            style = patternParts[0];
    +          } else {
    +            for (var i = 0; i < nPatterns; ++i) {
    +              patternParts = fallthroughStylePatterns[i];
    +              match = token.match(patternParts[1]);
    +              if (match) {
    +                style = patternParts[0];
    +                break;
    +              }
    +            }
    +
    +            if (!match) {  // make sure that we make progress
    +              style = PR_PLAIN;
    +            }
    +          }
    +
    +          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
    +          if (isEmbedded && !(match && typeof match[1] === 'string')) {
    +            isEmbedded = false;
    +            style = PR_SOURCE;
    +          }
    +
    +          if (!isEmbedded) { styleCache[token] = style; }
    +        }
    +
    +        var tokenStart = pos;
    +        pos += token.length;
    +
    +        if (!isEmbedded) {
    +          decorations.push(basePos + tokenStart, style);
    +        } else {  // Treat group 1 as an embedded block of source code.
    +          var embeddedSource = match[1];
    +          var embeddedSourceStart = token.indexOf(embeddedSource);
    +          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
    +          if (match[2]) {
    +            // If embeddedSource can be blank, then it would match at the
    +            // beginning which would cause us to infinitely recurse on the
    +            // entire token, so we catch the right context in match[2].
    +            embeddedSourceEnd = token.length - match[2].length;
    +            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
    +          }
    +          var lang = style.substring(5);
    +          // Decorate the left of the embedded source
    +          appendDecorations(
    +              basePos + tokenStart,
    +              token.substring(0, embeddedSourceStart),
    +              decorate, decorations);
    +          // Decorate the embedded source
    +          appendDecorations(
    +              basePos + tokenStart + embeddedSourceStart,
    +              embeddedSource,
    +              langHandlerForExtension(lang, embeddedSource),
    +              decorations);
    +          // Decorate the right of the embedded section
    +          appendDecorations(
    +              basePos + tokenStart + embeddedSourceEnd,
    +              token.substring(embeddedSourceEnd),
    +              decorate, decorations);
    +        }
    +      }
    +      job.decorations = decorations;
    +    };
    +    return decorate;
    +  }
    +
    +  /** returns a function that produces a list of decorations from source text.
    +    *
    +    * This code treats ", ', and ` as string delimiters, and \ as a string
    +    * escape.  It does not recognize perl's qq() style strings.
    +    * It has no special handling for double delimiter escapes as in basic, or
    +    * the tripled delimiters used in python, but should work on those regardless
    +    * although in those cases a single string literal may be broken up into
    +    * multiple adjacent string literals.
    +    *
    +    * It recognizes C, C++, and shell style comments.
    +    *
    +    * @param {Object} options a set of optional parameters.
    +    * @return {function (Object)} a function that examines the source code
    +    *     in the input job and builds the decoration list.
    +    */
    +  function sourceDecorator(options) {
    +    var shortcutStylePatterns = [], fallthroughStylePatterns = [];
    +    if (options['tripleQuotedStrings']) {
    +      // '''multi-line-string''', 'single-line-string', and double-quoted
    +      shortcutStylePatterns.push(
    +          [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
    +           null, '\'"']);
    +    } else if (options['multiLineStrings']) {
    +      // 'multi-line-string', "multi-line-string"
    +      shortcutStylePatterns.push(
    +          [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
    +           null, '\'"`']);
    +    } else {
    +      // 'single-line-string', "single-line-string"
    +      shortcutStylePatterns.push(
    +          [PR_STRING,
    +           /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
    +           null, '"\'']);
    +    }
    +    if (options['verbatimStrings']) {
    +      // verbatim-string-literal production from the C# grammar.  See issue 93.
    +      fallthroughStylePatterns.push(
    +          [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
    +    }
    +    if (options['hashComments']) {
    +      if (options['cStyleComments']) {
    +        // Stop C preprocessor declarations at an unclosed open comment
    +        shortcutStylePatterns.push(
    +            [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
    +             null, '#']);
    +        fallthroughStylePatterns.push(
    +            [PR_STRING,
    +             /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
    +             null]);
    +      } else {
    +        shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
    +      }
    +    }
    +    if (options['cStyleComments']) {
    +      fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
    +      fallthroughStylePatterns.push(
    +          [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
    +    }
    +    if (options['regexLiterals']) {
    +      var REGEX_LITERAL = (
    +          // A regular expression literal starts with a slash that is
    +          // not followed by * or / so that it is not confused with
    +          // comments.
    +          '/(?=[^/*])'
    +          // and then contains any number of raw characters,
    +          + '(?:[^/\\x5B\\x5C]'
    +          // escape sequences (\x5C),
    +          +    '|\\x5C[\\s\\S]'
    +          // or non-nesting character sets (\x5B\x5D);
    +          +    '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
    +          // finally closed by a /.
    +          + '/');
    +      fallthroughStylePatterns.push(
    +          ['lang-regex',
    +           new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
    +           ]);
    +    }
    +
    +    var keywords = options['keywords'].replace(/^\s+|\s+$/g, '');
    +    if (keywords.length) {
    +      fallthroughStylePatterns.push(
    +          [PR_KEYWORD,
    +           new RegExp('^(?:' + keywords.replace(/\s+/g, '|') + ')\\b'), null]);
    +    }
    +
    +    shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
    +    fallthroughStylePatterns.push(
    +        // TODO(mikesamuel): recognize non-latin letters and numerals in idents
    +        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
    +        [PR_TYPE,        /^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, null],
    +        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
    +        [PR_LITERAL,
    +         new RegExp(
    +             '^(?:'
    +             // A hex number
    +             + '0x[a-f0-9]+'
    +             // or an octal or decimal number,
    +             + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
    +             // possibly in scientific notation
    +             + '(?:e[+\\-]?\\d+)?'
    +             + ')'
    +             // with an optional modifier like UL for unsigned long
    +             + '[a-z]*', 'i'),
    +         null, '0123456789'],
    +        [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#]*/, null]);
    +
    +    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
    +  }
    +
    +  var decorateSource = sourceDecorator({
    +        'keywords': ALL_KEYWORDS,
    +        'hashComments': true,
    +        'cStyleComments': true,
    +        'multiLineStrings': true,
    +        'regexLiterals': true
    +      });
    +
    +  /** Breaks {@code job.source} around style boundaries in
    +    * {@code job.decorations} while re-interleaving {@code job.extractedTags},
    +    * and leaves the result in {@code job.prettyPrintedHtml}.
    +    * @param {Object} job like {
    +    *    source: {string} source as plain text,
    +    *    extractedTags: {Array.<number|string>} extractedTags chunks of raw
    +    *                   html preceded by their position in {@code job.source}
    +    *                   in order
    +    *    decorations: {Array.<number|string} an array of style classes preceded
    +    *                 by the position at which they start in job.source in order
    +    * }
    +    * @private
    +    */
    +  function recombineTagsAndDecorations(job) {
    +    var sourceText = job.source;
    +    var extractedTags = job.extractedTags;
    +    var decorations = job.decorations;
    +
    +    var html = [];
    +    // index past the last char in sourceText written to html
    +    var outputIdx = 0;
    +
    +    var openDecoration = null;
    +    var currentDecoration = null;
    +    var tagPos = 0;  // index into extractedTags
    +    var decPos = 0;  // index into decorations
    +    var tabExpander = makeTabExpander(window['PR_TAB_WIDTH']);
    +
    +    var adjacentSpaceRe = /([\r\n ]) /g;
    +    var startOrSpaceRe = /(^| ) /gm;
    +    var newlineRe = /\r\n?|\n/g;
    +    var trailingSpaceRe = /[ \r\n]$/;
    +    var lastWasSpace = true;  // the last text chunk emitted ended with a space.
    +
    +    // A helper function that is responsible for opening sections of decoration
    +    // and outputing properly escaped chunks of source
    +    function emitTextUpTo(sourceIdx) {
    +      if (sourceIdx > outputIdx) {
    +        if (openDecoration && openDecoration !== currentDecoration) {
    +          // Close the current decoration
    +          html.push('</span>');
    +          openDecoration = null;
    +        }
    +        if (!openDecoration && currentDecoration) {
    +          openDecoration = currentDecoration;
    +          html.push('<span class="', openDecoration, '">');
    +        }
    +        // This interacts badly with some wikis which introduces paragraph tags
    +        // into pre blocks for some strange reason.
    +        // It's necessary for IE though which seems to lose the preformattedness
    +        // of <pre> tags when their innerHTML is assigned.
    +        // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
    +        // and it serves to undo the conversion of <br>s to newlines done in
    +        // chunkify.
    +        var htmlChunk = textToHtml(
    +            tabExpander(sourceText.substring(outputIdx, sourceIdx)))
    +            .replace(lastWasSpace
    +                     ? startOrSpaceRe
    +                     : adjacentSpaceRe, '$1&nbsp;');
    +        // Keep track of whether we need to escape space at the beginning of the
    +        // next chunk.
    +        lastWasSpace = trailingSpaceRe.test(htmlChunk);
    +        // IE collapses multiple adjacient <br>s into 1 line break.
    +        // Prefix every <br> with '&nbsp;' can prevent such IE's behavior.
    +        var lineBreakHtml = window['_pr_isIE6']() ? '&nbsp;<br />' : '<br />';
    +        html.push(htmlChunk.replace(newlineRe, lineBreakHtml));
    +        outputIdx = sourceIdx;
    +      }
    +    }
    +
    +    while (true) {
    +      // Determine if we're going to consume a tag this time around.  Otherwise
    +      // we consume a decoration or exit.
    +      var outputTag;
    +      if (tagPos < extractedTags.length) {
    +        if (decPos < decorations.length) {
    +          // Pick one giving preference to extractedTags since we shouldn't open
    +          // a new style that we're going to have to immediately close in order
    +          // to output a tag.
    +          outputTag = extractedTags[tagPos] <= decorations[decPos];
    +        } else {
    +          outputTag = true;
    +        }
    +      } else {
    +        outputTag = false;
    +      }
    +      // Consume either a decoration or a tag or exit.
    +      if (outputTag) {
    +        emitTextUpTo(extractedTags[tagPos]);
    +        if (openDecoration) {
    +          // Close the current decoration
    +          html.push('</span>');
    +          openDecoration = null;
    +        }
    +        html.push(extractedTags[tagPos + 1]);
    +        tagPos += 2;
    +      } else if (decPos < decorations.length) {
    +        emitTextUpTo(decorations[decPos]);
    +        currentDecoration = decorations[decPos + 1];
    +        decPos += 2;
    +      } else {
    +        break;
    +      }
    +    }
    +    emitTextUpTo(sourceText.length);
    +    if (openDecoration) {
    +      html.push('</span>');
    +    }
    +    job.prettyPrintedHtml = html.join('');
    +  }
    +
    +  /** Maps language-specific file extensions to handlers. */
    +  var langHandlerRegistry = {};
    +  /** Register a language handler for the given file extensions.
    +    * @param {function (Object)} handler a function from source code to a list
    +    *      of decorations.  Takes a single argument job which describes the
    +    *      state of the computation.   The single parameter has the form
    +    *      {@code {
    +    *        source: {string} as plain text.
    +    *        decorations: {Array.<number|string>} an array of style classes
    +    *                     preceded by the position at which they start in
    +    *                     job.source in order.
    +    *                     The language handler should assigned this field.
    +    *        basePos: {int} the position of source in the larger source chunk.
    +    *                 All positions in the output decorations array are relative
    +    *                 to the larger source chunk.
    +    *      } }
    +    * @param {Array.<string>} fileExtensions
    +    */
    +  function registerLangHandler(handler, fileExtensions) {
    +    for (var i = fileExtensions.length; --i >= 0;) {
    +      var ext = fileExtensions[i];
    +      if (!langHandlerRegistry.hasOwnProperty(ext)) {
    +        langHandlerRegistry[ext] = handler;
    +      } else if ('console' in window) {
    +        console.warn('cannot override language handler %s', ext);
    +      }
    +    }
    +  }
    +  function langHandlerForExtension(extension, source) {
    +    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
    +      // Treat it as markup if the first non whitespace character is a < and
    +      // the last non-whitespace character is a >.
    +      extension = /^\s*</.test(source)
    +          ? 'default-markup'
    +          : 'default-code';
    +    }
    +    return langHandlerRegistry[extension];
    +  }
    +  registerLangHandler(decorateSource, ['default-code']);
    +  registerLangHandler(
    +      createSimpleLexer(
    +          [],
    +          [
    +           [PR_PLAIN,       /^[^<?]+/],
    +           [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
    +           [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
    +           // Unescaped content in an unknown language
    +           ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
    +           ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
    +           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
    +           ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
    +           // Unescaped content in javascript.  (Or possibly vbscript).
    +           ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
    +           // Contains unescaped stylesheet content
    +           ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
    +           ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
    +          ]),
    +      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
    +  registerLangHandler(
    +      createSimpleLexer(
    +          [
    +           [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
    +           [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
    +           ],
    +          [
    +           [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
    +           [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
    +           ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
    +           [PR_PUNCTUATION,  /^[=<>\/]+/],
    +           ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
    +           ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
    +           ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
    +           ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
    +           ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
    +           ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
    +           ]),
    +      ['in.tag']);
    +  registerLangHandler(
    +      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': CPP_KEYWORDS,
    +          'hashComments': true,
    +          'cStyleComments': true
    +        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': 'null true false'
    +        }), ['json']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': CSHARP_KEYWORDS,
    +          'hashComments': true,
    +          'cStyleComments': true,
    +          'verbatimStrings': true
    +        }), ['cs']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': JAVA_KEYWORDS,
    +          'cStyleComments': true
    +        }), ['java']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': SH_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true
    +        }), ['bsh', 'csh', 'sh']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': PYTHON_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true,
    +          'tripleQuotedStrings': true
    +        }), ['cv', 'py']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': PERL_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true,
    +          'regexLiterals': true
    +        }), ['perl', 'pl', 'pm']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': RUBY_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true,
    +          'regexLiterals': true
    +        }), ['rb']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': JSCRIPT_KEYWORDS,
    +          'cStyleComments': true,
    +          'regexLiterals': true
    +        }), ['js']);
    +  registerLangHandler(
    +      createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
    +
    +  function applyDecorator(job) {
    +    var sourceCodeHtml = job.sourceCodeHtml;
    +    var opt_langExtension = job.langExtension;
    +
    +    // Prepopulate output in case processing fails with an exception.
    +    job.prettyPrintedHtml = sourceCodeHtml;
    +
    +    try {
    +      // Extract tags, and convert the source code to plain text.
    +      var sourceAndExtractedTags = extractTags(sourceCodeHtml);
    +      /** Plain text. @type {string} */
    +      var source = sourceAndExtractedTags.source;
    +      job.source = source;
    +      job.basePos = 0;
    +
    +      /** Even entries are positions in source in ascending order.  Odd entries
    +        * are tags that were extracted at that position.
    +        * @type {Array.<number|string>}
    +        */
    +      job.extractedTags = sourceAndExtractedTags.tags;
    +
    +      // Apply the appropriate language handler
    +      langHandlerForExtension(opt_langExtension, source)(job);
    +      // Integrate the decorations and tags back into the source code to produce
    +      // a decorated html string which is left in job.prettyPrintedHtml.
    +      recombineTagsAndDecorations(job);
    +    } catch (e) {
    +      if ('console' in window) {
    +        console.log(e);
    +        console.trace();
    +      }
    +    }
    +  }
    +
    +  function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
    +    var job = {
    +      sourceCodeHtml: sourceCodeHtml,
    +      langExtension: opt_langExtension
    +    };
    +    applyDecorator(job);
    +    return job.prettyPrintedHtml;
    +  }
    +
    +  function prettyPrint(opt_whenDone) {
    +    var isIE678 = window['_pr_isIE6']();
    +    var ieNewline = isIE678 === 6 ? '\r\n' : '\r';
    +    // See bug 71 and http://stackoverflow.com/questions/136443/why-doesnt-ie7-
    +
    +    // fetch a list of nodes to rewrite
    +    var codeSegments = [
    +        document.getElementsByTagName('pre'),
    +        document.getElementsByTagName('code'),
    +        document.getElementsByTagName('td'),  /* ND Change: Add tables to support prototypes. */
    +        document.getElementsByTagName('xmp') ];
    +    var elements = [];
    +    for (var i = 0; i < codeSegments.length; ++i) {
    +      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
    +        elements.push(codeSegments[i][j]);
    +      }
    +    }
    +    codeSegments = null;
    +
    +    var clock = Date;
    +    if (!clock['now']) {
    +      clock = { 'now': function () { return (new Date).getTime(); } };
    +    }
    +
    +    // The loop is broken into a series of continuations to make sure that we
    +    // don't make the browser unresponsive when rewriting a large page.
    +    var k = 0;
    +    var prettyPrintingJob;
    +
    +    function doWork() {
    +      var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
    +                     clock.now() + 250 /* ms */ :
    +                     Infinity);
    +      for (; k < elements.length && clock.now() < endTime; k++) {
    +        var cs = elements[k];
    +        if (cs.className && cs.className.indexOf('prettyprint') >= 0) {
    +          // If the classes includes a language extensions, use it.
    +          // Language extensions can be specified like
    +          //     <pre class="prettyprint lang-cpp">
    +          // the language extension "cpp" is used to find a language handler as
    +          // passed to PR_registerLangHandler.
    +          var langExtension = cs.className.match(/\blang-(\w+)\b/);
    +          if (langExtension) { langExtension = langExtension[1]; }
    +
    +          // make sure this is not nested in an already prettified element
    +          var nested = false;
    +          for (var p = cs.parentNode; p; p = p.parentNode) {
    +            if ((p.tagName === 'pre' || p.tagName === 'code' ||
    +                 p.tagName === 'xmp' || p.tagName === 'td') &&  /* ND Change: Add tables to support prototypes */
    +                p.className && p.className.indexOf('prettyprint') >= 0) {
    +              nested = true;
    +              break;
    +            }
    +          }
    +          if (!nested) {
    +            // fetch the content as a snippet of properly escaped HTML.
    +            // Firefox adds newlines at the end.
    +            var content = getInnerHtml(cs);
    +            content = content.replace(/(?:\r\n?|\n)$/, '');
    +
    +	  		/* ND Change: we need to preserve &nbsp;s so change them to a special character instead of a space. */
    +			content = content.replace(/&nbsp;/g, '\x11');
    +
    +            // do the pretty printing
    +            prettyPrintingJob = {
    +              sourceCodeHtml: content,
    +              langExtension: langExtension,
    +              sourceNode: cs
    +            };
    +            applyDecorator(prettyPrintingJob);
    +            replaceWithPrettyPrintedHtml();
    +          }
    +        }
    +      }
    +      if (k < elements.length) {
    +        // finish up in a continuation
    +        setTimeout(doWork, 250);
    +      } else if (opt_whenDone) {
    +        opt_whenDone();
    +      }
    +    }
    +
    +    function replaceWithPrettyPrintedHtml() {
    +      var newContent = prettyPrintingJob.prettyPrintedHtml;
    +      if (!newContent) { return; }
    +
    +      /* ND Change: Restore the preserved &nbsp;s.  */
    +	  newContent = newContent.replace(/\x11/g, '&nbsp;');
    +
    +      var cs = prettyPrintingJob.sourceNode;
    +
    +      // push the prettified html back into the tag.
    +      if (!isRawContent(cs)) {
    +        // just replace the old html with the new
    +        cs.innerHTML = newContent;
    +      } else {
    +        // we need to change the tag to a <pre> since <xmp>s do not allow
    +        // embedded tags such as the span tags used to attach styles to
    +        // sections of source code.
    +        var pre = document.createElement('PRE');
    +        for (var i = 0; i < cs.attributes.length; ++i) {
    +          var a = cs.attributes[i];
    +          if (a.specified) {
    +            var aname = a.name.toLowerCase();
    +            if (aname === 'class') {
    +              pre.className = a.value;  // For IE 6
    +            } else {
    +              pre.setAttribute(a.name, a.value);
    +            }
    +          }
    +        }
    +        pre.innerHTML = newContent;
    +
    +        // remove the old
    +        cs.parentNode.replaceChild(pre, cs);
    +        cs = pre;
    +      }
    +
    +      // Replace <br>s with line-feeds so that copying and pasting works
    +      // on IE 6.
    +      // Doing this on other browsers breaks lots of stuff since \r\n is
    +      // treated as two newlines on Firefox, and doing this also slows
    +      // down rendering.
    +      if (isIE678 && cs.tagName === 'PRE') {
    +        var lineBreaks = cs.getElementsByTagName('br');
    +        for (var j = lineBreaks.length; --j >= 0;) {
    +          var lineBreak = lineBreaks[j];
    +          lineBreak.parentNode.replaceChild(
    +              document.createTextNode(ieNewline), lineBreak);
    +        }
    +      }
    +    }
    +
    +    doWork();
    +  }
    +
    +  window['PR_normalizedHtml'] = normalizedHtml;
    +  window['prettyPrintOne'] = prettyPrintOne;
    +  window['prettyPrint'] = prettyPrint;
    +  window['PR'] = {
    +        'combinePrefixPatterns': combinePrefixPatterns,
    +        'createSimpleLexer': createSimpleLexer,
    +        'registerLangHandler': registerLangHandler,
    +        'sourceDecorator': sourceDecorator,
    +        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
    +        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
    +        'PR_COMMENT': PR_COMMENT,
    +        'PR_DECLARATION': PR_DECLARATION,
    +        'PR_KEYWORD': PR_KEYWORD,
    +        'PR_LITERAL': PR_LITERAL,
    +        'PR_NOCODE': PR_NOCODE,
    +        'PR_PLAIN': PR_PLAIN,
    +        'PR_PUNCTUATION': PR_PUNCTUATION,
    +        'PR_SOURCE': PR_SOURCE,
    +        'PR_STRING': PR_STRING,
    +        'PR_TAG': PR_TAG,
    +        'PR_TYPE': PR_TYPE
    +      };
    +})();
    +
    +
    +// ____________________________________________________________________________
    +
    +
    +
    +// Lua extension
    +
    +PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0]+/,null,'	\n\r \xa0'],[PR.PR_STRING,/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,'\"\'']],[[PR.PR_COMMENT,/^--(?:\[(=*)\[[\s\S]*?(?:\]\1\]|$)|[^\r\n]*)/],[PR.PR_STRING,/^\[(=*)\[[\s\S]*?(?:\]\1\]|$)/],[PR.PR_KEYWORD,/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],[PR.PR_LITERAL,/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],[PR.PR_PLAIN,/^[a-z_]\w*/i],[PR.PR_PUNCTUATION,/^[^\w\t\n\r \xA0][^\w\t\n\r \xA0\"\'\-\+=]*/]]),['lua'])
    +
    +
    +// Haskell extension
    +
    +PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\x0B\x0C\r ]+/,null,'	\n\r '],[PR.PR_STRING,/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'\"'],[PR.PR_STRING,/^\'(?:[^\'\\\n\x0C\r]|\\[^&])\'?/,null,'\''],[PR.PR_LITERAL,/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,'0123456789']],[[PR.PR_COMMENT,/^(?:(?:--+(?:[^\r\n\x0C]*)?)|(?:\{-(?:[^-]|-+[^-\}])*-\}))/],[PR.PR_KEYWORD,/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^a-zA-Z0-9\']|$)/,null],[PR.PR_PLAIN,/^(?:[A-Z][\w\']*\.)*[a-zA-Z][\w\']*/],[PR.PR_PUNCTUATION,/^[^\t\n\x0B\x0C\r a-zA-Z0-9\'\"]+/]]),['hs'])
    +
    +
    +// ML extension
    +
    +PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0]+/,null,'	\n\r \xa0'],[PR.PR_COMMENT,/^#(?:if[\t\n\r \xA0]+(?:[a-z_$][\w\']*|``[^\r\n\t`]*(?:``|$))|else|endif|light)/i,null,'#'],[PR.PR_STRING,/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,'\"\'']],[[PR.PR_COMMENT,/^(?:\/\/[^\r\n]*|\(\*[\s\S]*?\*\))/],[PR.PR_KEYWORD,/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/],[PR.PR_LITERAL,/^[+\-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],[PR.PR_PLAIN,/^(?:[a-z_]\w*[!?#]?|``[^\r\n\t`]*(?:``|$))/i],[PR.PR_PUNCTUATION,/^[^\t\n\r \xA0\"\'\w]+/]]),['fs','ml'])
    +
    +
    +// SQL extension
    +
    +PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0]+/,null,'	\n\r \xa0'],[PR.PR_STRING,/^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/,null,'\"\'']],[[PR.PR_COMMENT,/^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/],[PR.PR_KEYWORD,/^(?:ADD|ALL|ALTER|AND|ANY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\w-]|$)/i,null],[PR.PR_LITERAL,/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],[PR.PR_PLAIN,/^[a-z_][\w-]*/i],[PR.PR_PUNCTUATION,/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0+\-\"\']*/]]),['sql'])
    +
    +
    +// VB extension
    +
    +PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[\t\n\r \xA0\u2028\u2029]+/,null,'	\n\r \xa0\u2028\u2029'],[PR.PR_STRING,/^(?:[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})(?:[\"\u201C\u201D]c|$)|[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})*(?:[\"\u201C\u201D]|$))/i,null,'\"\u201c\u201d'],[PR.PR_COMMENT,/^[\'\u2018\u2019][^\r\n\u2028\u2029]*/,null,'\'\u2018\u2019']],[[PR.PR_KEYWORD,/^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\b/i,null],[PR.PR_COMMENT,/^REM[^\r\n\u2028\u2029]*/i],[PR.PR_LITERAL,/^(?:True\b|False\b|Nothing\b|\d+(?:E[+\-]?\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\d*\.\d+(?:E[+\-]?\d+)?[FRD]?|#\s+(?:\d+[\-\/]\d+[\-\/]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)?|\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)\s+#)/i],[PR.PR_PLAIN,/^(?:(?:[a-z]|_\w)\w*|\[(?:[a-z]|_\w)\w*\])/i],[PR.PR_PUNCTUATION,/^[^\w\t\n\r \"\'\[\]\xA0\u2018\u2019\u201C\u201D\u2028\u2029]+/],[PR.PR_PUNCTUATION,/^(?:\[|\])/]]),['vb','vbs'])
    diff --git a/docs/javascript/searchdata.js b/docs/javascript/searchdata.js
    new file mode 100644
    index 0000000..ca41a89
    --- /dev/null
    +++ b/docs/javascript/searchdata.js
    @@ -0,0 +1,242 @@
    +var indexSectionsWithContent = {
    +   "Functions": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": true,
    +      "B": false,
    +      "C": true,
    +      "D": false,
    +      "E": true,
    +      "F": false,
    +      "G": true,
    +      "H": true,
    +      "I": true,
    +      "J": false,
    +      "K": false,
    +      "L": false,
    +      "M": false,
    +      "N": false,
    +      "O": false,
    +      "P": true,
    +      "Q": false,
    +      "R": false,
    +      "S": true,
    +      "T": false,
    +      "U": true,
    +      "V": false,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "Files": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": false,
    +      "B": false,
    +      "C": true,
    +      "D": false,
    +      "E": false,
    +      "F": true,
    +      "G": false,
    +      "H": false,
    +      "I": false,
    +      "J": false,
    +      "K": false,
    +      "L": false,
    +      "M": false,
    +      "N": false,
    +      "O": false,
    +      "P": false,
    +      "Q": false,
    +      "R": false,
    +      "S": true,
    +      "T": false,
    +      "U": false,
    +      "V": false,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "CBAEvents": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": true,
    +      "B": false,
    +      "C": true,
    +      "D": false,
    +      "E": true,
    +      "F": false,
    +      "G": false,
    +      "H": true,
    +      "I": false,
    +      "J": false,
    +      "K": false,
    +      "L": false,
    +      "M": false,
    +      "N": false,
    +      "O": false,
    +      "P": true,
    +      "Q": false,
    +      "R": true,
    +      "S": false,
    +      "T": false,
    +      "U": false,
    +      "V": false,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "Variables": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": true,
    +      "B": false,
    +      "C": true,
    +      "D": false,
    +      "E": true,
    +      "F": true,
    +      "G": false,
    +      "H": true,
    +      "I": false,
    +      "J": false,
    +      "K": false,
    +      "L": true,
    +      "M": true,
    +      "N": true,
    +      "O": false,
    +      "P": true,
    +      "Q": false,
    +      "R": true,
    +      "S": false,
    +      "T": true,
    +      "U": false,
    +      "V": true,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "General": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": true,
    +      "B": true,
    +      "C": true,
    +      "D": false,
    +      "E": true,
    +      "F": true,
    +      "G": true,
    +      "H": false,
    +      "I": false,
    +      "J": false,
    +      "K": false,
    +      "L": true,
    +      "M": true,
    +      "N": false,
    +      "O": true,
    +      "P": false,
    +      "Q": false,
    +      "R": false,
    +      "S": true,
    +      "T": false,
    +      "U": false,
    +      "V": true,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "Macros": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": true,
    +      "B": true,
    +      "C": true,
    +      "D": false,
    +      "E": false,
    +      "F": false,
    +      "G": false,
    +      "H": false,
    +      "I": false,
    +      "J": false,
    +      "K": false,
    +      "L": true,
    +      "M": false,
    +      "N": false,
    +      "O": false,
    +      "P": true,
    +      "Q": false,
    +      "R": false,
    +      "S": true,
    +      "T": false,
    +      "U": false,
    +      "V": true,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "CBASettings": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": true,
    +      "B": false,
    +      "C": false,
    +      "D": false,
    +      "E": true,
    +      "F": true,
    +      "G": false,
    +      "H": false,
    +      "I": true,
    +      "J": false,
    +      "K": false,
    +      "L": false,
    +      "M": true,
    +      "N": false,
    +      "O": false,
    +      "P": true,
    +      "Q": false,
    +      "R": false,
    +      "S": true,
    +      "T": true,
    +      "U": false,
    +      "V": false,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      },
    +   "EventHandlers": {
    +      "Symbols": false,
    +      "Numbers": false,
    +      "A": false,
    +      "B": false,
    +      "C": false,
    +      "D": true,
    +      "E": true,
    +      "F": false,
    +      "G": false,
    +      "H": true,
    +      "I": false,
    +      "J": false,
    +      "K": false,
    +      "L": false,
    +      "M": true,
    +      "N": false,
    +      "O": true,
    +      "P": true,
    +      "Q": false,
    +      "R": false,
    +      "S": false,
    +      "T": false,
    +      "U": false,
    +      "V": false,
    +      "W": false,
    +      "X": false,
    +      "Y": false,
    +      "Z": false
    +      }
    +   }
    \ No newline at end of file
    diff --git a/docs/search/CBAEventsA.html b/docs/search/CBAEventsA.html
    new file mode 100644
    index 0000000..0b236d1
    --- /dev/null
    +++ b/docs/search/CBAEventsA.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_ace_undadvanced_undthrowing_undthrowFiredXEH><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#ace_advanced_throwing_throwFiredXEH" target=_parent class=ISymbol>ace_advanced_throwing_throwFiredXEH</a></div></div><div class=SRResult id=SR_ace_undexplosives_undplace><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#ace_explosives_place" target=_parent class=ISymbol>ace_explosives_place</a></div></div><div class=SRResult id=SR_addDebugBullet><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_recorder_addDebugBullet" target=_parent class=ISymbol>addDebugBullet</a></div></div><div class=SRResult id=SR_addDebugMagIcon><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_recorder_addDebugMagIcon" target=_parent class=ISymbol>addDebugMagIcon</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBAEventsC.html b/docs/search/CBAEventsC.html
    new file mode 100644
    index 0000000..5b5199f
    --- /dev/null
    +++ b/docs/search/CBAEventsC.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_counterEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_counterEvent" target=_parent class=ISymbol>counterEvent</a></div></div><div class=SRResult id=SR_counterInit><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_counterInit" target=_parent class=ISymbol>counterInit</a></div></div><div class=SRResult id=SR_customEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_customEvent" target=_parent class=ISymbol>customEvent</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBAEventsE.html b/docs/search/CBAEventsE.html
    new file mode 100644
    index 0000000..0e5ffa0
    --- /dev/null
    +++ b/docs/search/CBAEventsE.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_exportData><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_exportData" target=_parent class=ISymbol>exportData</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBAEventsH.html b/docs/search/CBAEventsH.html
    new file mode 100644
    index 0000000..569f11b
    --- /dev/null
    +++ b/docs/search/CBAEventsH.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_handleMarker><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_handleMarker" target=_parent class=ISymbol>handleMarker</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBAEventsP.html b/docs/search/CBAEventsP.html
    new file mode 100644
    index 0000000..ce5abe3
    --- /dev/null
    +++ b/docs/search/CBAEventsP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_pause><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_pause" target=_parent class=ISymbol>pause</a></div></div><div class=SRResult id=SR_Projectiles><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Projectiles" target=_parent class=ISymbol>Projectiles</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBAEventsR.html b/docs/search/CBAEventsR.html
    new file mode 100644
    index 0000000..e925913
    --- /dev/null
    +++ b/docs/search/CBAEventsR.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_record><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_record" target=_parent class=ISymbol>record</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsA.html b/docs/search/CBASettingsA.html
    new file mode 100644
    index 0000000..089a878
    --- /dev/null
    +++ b/docs/search/CBASettingsA.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_administratorList><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/main/XEH_preInit-sqf.html#OCAP_administratorList" target=_parent class=ISymbol>administratorList</a></div></div><div class=SRResult id=SR_autoStart><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_autoStart" target=_parent class=ISymbol>autoStart</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsE.html b/docs/search/CBASettingsE.html
    new file mode 100644
    index 0000000..73d6777
    --- /dev/null
    +++ b/docs/search/CBASettingsE.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_enabled><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/main/XEH_preInit-sqf.html#OCAP_enabled" target=_parent class=ISymbol>enabled</a></div></div><div class=SRResult id=SR_excludeClassFromRecord><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_excludeClassFromRecord" target=_parent class=ISymbol>excludeClassFromRecord</a></div></div><div class=SRResult id=SR_excludeKindFromRecord><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_excludeKindFromRecord" target=_parent class=ISymbol>excludeKindFromRecord</a></div></div><div class=SRResult id=SR_excludeMarkerFromRecord><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_excludeMarkerFromRecord" target=_parent class=ISymbol>excludeMarkerFromRecord</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsF.html b/docs/search/CBASettingsF.html
    new file mode 100644
    index 0000000..8165696
    --- /dev/null
    +++ b/docs/search/CBASettingsF.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_frameCaptureDelay><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_frameCaptureDelay" target=_parent class=ISymbol>frameCaptureDelay</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsI.html b/docs/search/CBASettingsI.html
    new file mode 100644
    index 0000000..97ebc23
    --- /dev/null
    +++ b/docs/search/CBASettingsI.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_isDebug><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/main/XEH_preInit-sqf.html#OCAP_isDebug" target=_parent class=ISymbol>isDebug</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsM.html b/docs/search/CBASettingsM.html
    new file mode 100644
    index 0000000..562ed13
    --- /dev/null
    +++ b/docs/search/CBASettingsM.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_minMissionTime><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_minMissionTime" target=_parent class=ISymbol>minMissionTime</a></div></div><div class=SRResult id=SR_minPlayerCount><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_minPlayerCount" target=_parent class=ISymbol>minPlayerCount</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsP.html b/docs/search/CBASettingsP.html
    new file mode 100644
    index 0000000..4ea9354
    --- /dev/null
    +++ b/docs/search/CBASettingsP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_preferACEUnconscious><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_preferACEUnconscious" target=_parent class=ISymbol>preferACEUnconscious</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsS.html b/docs/search/CBASettingsS.html
    new file mode 100644
    index 0000000..aa9f3d9
    --- /dev/null
    +++ b/docs/search/CBASettingsS.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_saveMissionEnded><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_saveMissionEnded" target=_parent class=ISymbol>saveMissionEnded</a></div></div><div class=SRResult id=SR_saveOnEmpty><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_saveOnEmpty" target=_parent class=ISymbol>saveOnEmpty</a></div></div><div class=SRResult id=SR_saveTag><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_saveTag" target=_parent class=ISymbol>saveTag</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/CBASettingsT.html b/docs/search/CBASettingsT.html
    new file mode 100644
    index 0000000..5e38c01
    --- /dev/null
    +++ b/docs/search/CBASettingsT.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_trackTickets><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_trackTickets" target=_parent class=ISymbol>trackTickets</a></div></div><div class=SRResult id=SR_trackTimeInterval><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_trackTimeInterval" target=_parent class=ISymbol>trackTimeInterval</a></div></div><div class=SRResult id=SR_trackTimes><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_trackTimes" target=_parent class=ISymbol>trackTimes</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/EventHandlersD.html b/docs/search/EventHandlersD.html
    new file mode 100644
    index 0000000..b191455
    --- /dev/null
    +++ b/docs/search/EventHandlersD.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Deleted><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Deleted" target=_parent class=ISymbol>Deleted</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/EventHandlersE.html b/docs/search/EventHandlersE.html
    new file mode 100644
    index 0000000..a88db21
    --- /dev/null
    +++ b/docs/search/EventHandlersE.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Ended><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_Ended" target=_parent class=ISymbol>Ended</a></div></div><div class=SRResult id=SR_EntityKilled><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_EntityKilled" target=_parent class=ISymbol>EntityKilled</a></div></div><div class=SRResult id=SR_EntityRespawned><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_EntityRespawned" target=_parent class=ISymbol>EntityRespawned</a></div></div><div class=SRResult id=SR_Explode><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Explode" target=_parent class=ISymbol>Explode</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/EventHandlersH.html b/docs/search/EventHandlersH.html
    new file mode 100644
    index 0000000..a7b1026
    --- /dev/null
    +++ b/docs/search/EventHandlersH.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_HandleDisconnect><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_HandleDisconnect" target=_parent class=ISymbol>HandleDisconnect</a></div></div><div class=SRResult id=SR_HitExplosion><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#HitExplosion" target=_parent class=ISymbol>HitExplosion</a></div></div><div class=SRResult id=SR_HitPart><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#HitPart" target=_parent class=ISymbol>HitPart</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/EventHandlersM.html b/docs/search/EventHandlersM.html
    new file mode 100644
    index 0000000..68af557
    --- /dev/null
    +++ b/docs/search/EventHandlersM.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_MarkerCreated><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#MarkerCreated" target=_parent class=ISymbol>MarkerCreated</a></div></div><div class=SRResult id=SR_MarkerDeleted><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#MarkerDeleted" target=_parent class=ISymbol>MarkerDeleted</a></div></div><div class=SRResult id=SR_MarkerUpdated><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#MarkerUpdated" target=_parent class=ISymbol>MarkerUpdated</a></div></div><div class=SRResult id=SR_MPEnded><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_MPEnded" target=_parent class=ISymbol>MPEnded</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/EventHandlersO.html b/docs/search/EventHandlersO.html
    new file mode 100644
    index 0000000..ca3ecb3
    --- /dev/null
    +++ b/docs/search/EventHandlersO.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_OnUserAdminStateChanged><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_OnUserAdminStateChanged" target=_parent class=ISymbol>OnUserAdminStateChanged</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/EventHandlersP.html b/docs/search/EventHandlersP.html
    new file mode 100644
    index 0000000..347a296
    --- /dev/null
    +++ b/docs/search/EventHandlersP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_PlayerConnected><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_PlayerConnected" target=_parent class=ISymbol>PlayerConnected</a></div></div><div class=SRResult id=SR_Projectiles_lpaBullets_rpa><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Projectiles(Bullets)" target=_parent class=ISymbol>Projectiles(Bullets)</a></div></div><div class=SRResult id=SR_Projectiles_lpaNon_minBullets_rpa><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Projectiles(Non-Bullets)" target=_parent class=ISymbol>Projectiles(Non-Bullets)</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FilesC.html b/docs/search/FilesC.html
    new file mode 100644
    index 0000000..443a96b
    --- /dev/null
    +++ b/docs/search/FilesC.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_CBA_spcSettings><div class=IEntry><a href="javascript:searchResults.Toggle('SR_CBA_spcSettings')" class=ISymbol>CBA Settings</a><div class=ISubIndex><a href="../files/main/XEH_preInit-sqf.html#CBA_Settings" target=_parent class=IFile>main\<wbr>XEH_preInit.sqf</a><a href="../files/recorder/XEH_preInit-sqf.html#CBA_Settings" target=_parent class=IFile>recorder\<wbr>XEH_preInit.sqf</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FilesF.html b/docs/search/FilesF.html
    new file mode 100644
    index 0000000..79394a7
    --- /dev/null
    +++ b/docs/search/FilesF.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_fnc_undaceExplosives_persqf><div class=IEntry><a href="../files/recorder/fnc_aceExplosives-sqf.html#fnc_aceExplosives.sqf" target=_parent class=ISymbol>fnc_aceExplosives.sqf</a></div></div><div class=SRResult id=SR_fnc_undaddEventMission_persqf><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#fnc_addEventMission.sqf" target=_parent class=ISymbol>fnc_addEventMission.sqf</a></div></div><div class=SRResult id=SR_fnc_undaddUnitEventHandlers_persqf><div class=IEntry><a href="../files/recorder/fnc_addUnitEventHandlers-sqf.html#fnc_addUnitEventHandlers.sqf" target=_parent class=ISymbol>fnc_addUnitEventHandlers.sqf</a></div></div><div class=SRResult id=SR_fnc_undadminUIControl_persqf><div class=IEntry><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#fnc_adminUIControl.sqf" target=_parent class=ISymbol>fnc_adminUIControl.sqf</a></div></div><div class=SRResult id=SR_fnc_undcaptureLoop_persqf><div class=IEntry><a href="../files/recorder/fnc_captureLoop-sqf.html#fnc_captureLoop.sqf" target=_parent class=ISymbol>fnc_captureLoop.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undconnected_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_connected-sqf.html#fnc_eh_connected.sqf" target=_parent class=ISymbol>fnc_eh_connected.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_unddisconnected_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_disconnected-sqf.html#fnc_eh_disconnected.sqf" target=_parent class=ISymbol>fnc_eh_disconnected.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undfiredMan_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#fnc_eh_firedMan.sqf" target=_parent class=ISymbol>fnc_eh_firedMan.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undkilled_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_killed-sqf.html#fnc_eh_killed.sqf" target=_parent class=ISymbol>fnc_eh_killed.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undonUserAdminStateChanged_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html#fnc_eh_onUserAdminStateChanged.sqf" target=_parent class=ISymbol>fnc_eh_onUserAdminStateChanged.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undprojectileHit_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_projectileHit-sqf.html#fnc_eh_projectileHit.sqf" target=_parent class=ISymbol>fnc_eh_projectileHit.sqf</a></div></div><div class=SRResult id=SR_fnc_undentityMonitors_persqf><div class=IEntry><a href="../files/recorder/fnc_entityMonitors-sqf.html#fnc_entityMonitors.sqf" target=_parent class=ISymbol>fnc_entityMonitors.sqf</a></div></div><div class=SRResult id=SR_fnc_undexportData_persqf><div class=IEntry><a href="../files/recorder/fnc_exportData-sqf.html#fnc_exportData.sqf" target=_parent class=ISymbol>fnc_exportData.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetAmmoMarkerData_persqf><div class=IEntry><a href="../files/recorder/fnc_getAmmoMarkerData-sqf.html#fnc_getAmmoMarkerData.sqf" target=_parent class=ISymbol>fnc_getAmmoMarkerData.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetClass_persqf><div class=IEntry><a href="../files/recorder/fnc_getClass-sqf.html#fnc_getClass.sqf" target=_parent class=ISymbol>fnc_getClass.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetEventWeaponText_persqf><div class=IEntry><a href="../files/recorder/fnc_getEventWeaponText-sqf.html#fnc_getEventWeaponText.sqf" target=_parent class=ISymbol>fnc_getEventWeaponText.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetInstigator_persqf><div class=IEntry><a href="../files/recorder/fnc_getInstigator-sqf.html#fnc_getInstigator.sqf" target=_parent class=ISymbol>fnc_getInstigator.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetUnitType_persqf><div class=IEntry><a href="../files/recorder/fnc_getUnitType-sqf.html#fnc_getUnitType.sqf" target=_parent class=ISymbol>fnc_getUnitType.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetWeaponDisplayData_persqf><div class=IEntry><a href="../files/recorder/fnc_getWeaponDisplayData-sqf.html#fnc_getWeaponDisplayData.sqf" target=_parent class=ISymbol>fnc_getWeaponDisplayData.sqf</a></div></div><div class=SRResult id=SR_fnc_undhandleCustomEvent_persqf><div class=IEntry><a href="../files/recorder/fnc_handleCustomEvent-sqf.html#fnc_handleCustomEvent.sqf" target=_parent class=ISymbol>fnc_handleCustomEvent.sqf</a></div></div><div class=SRResult id=SR_fnc_undhandleMarkers_persqf><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#fnc_handleMarkers.sqf" target=_parent class=ISymbol>fnc_handleMarkers.sqf</a></div></div><div class=SRResult id=SR_fnc_undinit_persqf><div class=IEntry><a href="../files/recorder/fnc_init-sqf.html#fnc_init.sqf" target=_parent class=ISymbol>fnc_init.sqf</a></div></div><div class=SRResult id=SR_fnc_undisKindOfApc_persqf><div class=IEntry><a href="../files/recorder/fnc_isKindOfApc-sqf.html#fnc_isKindOfApc.sqf" target=_parent class=ISymbol>fnc_isKindOfApc.sqf</a></div></div><div class=SRResult id=SR_fnc_undprojectileMonitors_persqf><div class=IEntry><a href="../files/recorder/fnc_projectileMonitors-sqf.html#fnc_projectileMonitors.sqf" target=_parent class=ISymbol>fnc_projectileMonitors.sqf</a></div></div><div class=SRResult id=SR_fnc_undsendData_persqf><div class=IEntry><a href="../files/extension/fnc_sendData-sqf.html#fnc_sendData.sqf" target=_parent class=ISymbol>fnc_sendData.sqf</a></div></div><div class=SRResult id=SR_fnc_undstartRecording_persqf><div class=IEntry><a href="../files/recorder/fnc_startRecording-sqf.html#fnc_startRecording.sqf" target=_parent class=ISymbol>fnc_startRecording.sqf</a></div></div><div class=SRResult id=SR_fnc_undstopRecording_persqf><div class=IEntry><a href="../files/recorder/fnc_stopRecording-sqf.html#fnc_stopRecording.sqf" target=_parent class=ISymbol>fnc_stopRecording.sqf</a></div></div><div class=SRResult id=SR_fnc_undupdateTime_persqf><div class=IEntry><a href="../files/recorder/fnc_updateTime-sqf.html#fnc_updateTime.sqf" target=_parent class=ISymbol>fnc_updateTime.sqf</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FilesS.html b/docs/search/FilesS.html
    new file mode 100644
    index 0000000..c972dbd
    --- /dev/null
    +++ b/docs/search/FilesS.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_script_undcomponent_perhpp><div class=IEntry><a href="javascript:searchResults.Toggle('SR_script_undcomponent_perhpp')" class=ISymbol>script_component.hpp</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#script_component.hpp" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#script_component.hpp" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/recorder/script_component-hpp.html#script_component.hpp" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_script_undmacros_perhpp><div class=IEntry><a href="../files/main/script_macros-hpp.html#script_macros.hpp" target=_parent class=ISymbol>script_macros.hpp</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsA.html b/docs/search/FunctionsA.html
    new file mode 100644
    index 0000000..6e3274f
    --- /dev/null
    +++ b/docs/search/FunctionsA.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_aceExplosives><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_aceExplosives-sqf.html#OCAP_recorder_fnc_aceExplosives" target=_parent class=ISymbol>aceExplosives</a></div></div><div class=SRResult id=SR_addEventMission><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_recorder_fnc_addEventMission" target=_parent class=ISymbol>addEventMission</a></div></div><div class=SRResult id=SR_addUnitEventHandlers><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_addUnitEventHandlers-sqf.html#OCAP_recorder_fnc_addUnitEventHandlers" target=_parent class=ISymbol>addUnitEventHandlers</a></div></div><div class=SRResult id=SR_adminUIControl><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#OCAP_recorder_fnc_adminUIControl" target=_parent class=ISymbol>adminUIControl</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsC.html b/docs/search/FunctionsC.html
    new file mode 100644
    index 0000000..8469c35
    --- /dev/null
    +++ b/docs/search/FunctionsC.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_captureLoop><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_captureLoop-sqf.html#OCAP_recorder_fnc_captureLoop" target=_parent class=ISymbol>captureLoop</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsE.html b/docs/search/FunctionsE.html
    new file mode 100644
    index 0000000..5f796b0
    --- /dev/null
    +++ b/docs/search/FunctionsE.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_eh_undconnected><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_connected-sqf.html#OCAP_recorder_fnc_eh_connected" target=_parent class=ISymbol>eh_connected</a></div></div><div class=SRResult id=SR_eh_unddisconnected><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_disconnected-sqf.html#OCAP_recorder_fnc_eh_disconnected" target=_parent class=ISymbol>eh_disconnected</a></div></div><div class=SRResult id=SR_eh_undfiredMan><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_recorder_fnc_eh_firedMan" target=_parent class=ISymbol>eh_firedMan</a></div></div><div class=SRResult id=SR_eh_undkilled><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_killed-sqf.html#OCAP_recorder_fnc_eh_killed" target=_parent class=ISymbol>eh_killed</a></div></div><div class=SRResult id=SR_eh_undonUserAdminStateChanged><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html#OCAP_recorder_fnc_eh_onUserAdminStateChanged" target=_parent class=ISymbol>eh_onUserAdminStateChanged</a></div></div><div class=SRResult id=SR_eh_undprojectileHit><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_projectileHit-sqf.html#OCAP_recorder_fnc_eh_projectileHit" target=_parent class=ISymbol>eh_projectileHit</a></div></div><div class=SRResult id=SR_entityMonitors><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_entityMonitors-sqf.html#OCAP_recorder_fnc_entityMonitors" target=_parent class=ISymbol>entityMonitors</a></div></div><div class=SRResult id=SR_exportData><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_exportData-sqf.html#OCAP_recorder_fnc_exportData" target=_parent class=ISymbol>exportData</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsG.html b/docs/search/FunctionsG.html
    new file mode 100644
    index 0000000..491a410
    --- /dev/null
    +++ b/docs/search/FunctionsG.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_getAmmoMarkerData><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getAmmoMarkerData-sqf.html#OCAP_recorder_fnc_getAmmoMarkerData" target=_parent class=ISymbol>getAmmoMarkerData</a></div></div><div class=SRResult id=SR_getClass><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getClass-sqf.html#OCAP_recorder_fnc_getClass" target=_parent class=ISymbol>getClass</a></div></div><div class=SRResult id=SR_getEventWeaponText><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getEventWeaponText-sqf.html#OCAP_recorder_fnc_getEventWeaponText" target=_parent class=ISymbol>getEventWeaponText</a></div></div><div class=SRResult id=SR_getInstigator><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getInstigator-sqf.html#OCAP_recorder_fnc_getInstigator" target=_parent class=ISymbol>getInstigator</a></div></div><div class=SRResult id=SR_getUnitType><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getUnitType-sqf.html#OCAP_recorder_fnc_getUnitType" target=_parent class=ISymbol>getUnitType</a></div></div><div class=SRResult id=SR_getWeaponDisplayData><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getWeaponDisplayData-sqf.html#OCAP_recorder_fnc_getWeaponDisplayData" target=_parent class=ISymbol>getWeaponDisplayData</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsH.html b/docs/search/FunctionsH.html
    new file mode 100644
    index 0000000..ff7059d
    --- /dev/null
    +++ b/docs/search/FunctionsH.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_handleCustomEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_handleCustomEvent-sqf.html#OCAP_recorder_fnc_handleCustomEvent" target=_parent class=ISymbol>handleCustomEvent</a></div></div><div class=SRResult id=SR_handleMarkers><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_recorder_fnc_handleMarkers" target=_parent class=ISymbol>handleMarkers</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsI.html b/docs/search/FunctionsI.html
    new file mode 100644
    index 0000000..2da2ac7
    --- /dev/null
    +++ b/docs/search/FunctionsI.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_init><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_fnc_init" target=_parent class=ISymbol>init</a></div></div><div class=SRResult id=SR_isKindOfApc><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_isKindOfApc-sqf.html#OCAP_recorder_fnc_isKindOfApc" target=_parent class=ISymbol>isKindOfApc</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsP.html b/docs/search/FunctionsP.html
    new file mode 100644
    index 0000000..6b33e6c
    --- /dev/null
    +++ b/docs/search/FunctionsP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_projectileMonitors><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_projectileMonitors-sqf.html#OCAP_recorder_fnc_projectileMonitors" target=_parent class=ISymbol>projectileMonitors</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsS.html b/docs/search/FunctionsS.html
    new file mode 100644
    index 0000000..86bf492
    --- /dev/null
    +++ b/docs/search/FunctionsS.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_sendData><div class=IEntry><span class=ISymbolPrefix>OCAP_extension_fnc_</span><a href="../files/extension/fnc_sendData-sqf.html#OCAP_extension_fnc_sendData" target=_parent class=ISymbol>sendData</a></div></div><div class=SRResult id=SR_startRecording><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_startRecording-sqf.html#OCAP_recorder_fnc_startRecording" target=_parent class=ISymbol>startRecording</a></div></div><div class=SRResult id=SR_stopRecording><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_stopRecording-sqf.html#OCAP_recorder_fnc_stopRecording" target=_parent class=ISymbol>stopRecording</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/FunctionsU.html b/docs/search/FunctionsU.html
    new file mode 100644
    index 0000000..27bf0df
    --- /dev/null
    +++ b/docs/search/FunctionsU.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_updateTime><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_updateTime-sqf.html#OCAP_recorder_fnc_updateTime" target=_parent class=ISymbol>updateTime</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralA.html b/docs/search/GeneralA.html
    new file mode 100644
    index 0000000..6ceb9a6
    --- /dev/null
    +++ b/docs/search/GeneralA.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_ace_undadvanced_undthrowing_undthrowFiredXEH><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#ace_advanced_throwing_throwFiredXEH" target=_parent class=ISymbol>ace_advanced_throwing_throwFiredXEH</a></div></div><div class=SRResult id=SR_ace_undexplosives_undplace><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#ace_explosives_place" target=_parent class=ISymbol>ace_explosives_place</a></div></div><div class=SRResult id=SR_aceExplosives><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_aceExplosives-sqf.html#OCAP_recorder_fnc_aceExplosives" target=_parent class=ISymbol>aceExplosives</a></div></div><div class=SRResult id=SR_addDebugBullet><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_recorder_addDebugBullet" target=_parent class=ISymbol>addDebugBullet</a></div></div><div class=SRResult id=SR_addDebugMagIcon><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_recorder_addDebugMagIcon" target=_parent class=ISymbol>addDebugMagIcon</a></div></div><div class=SRResult id=SR_addEventMission><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_recorder_fnc_addEventMission" target=_parent class=ISymbol>addEventMission</a></div></div><div class=SRResult id=SR_ADDON><div class=IEntry><a href="../files/main/script_macros-hpp.html#ADDON" target=_parent class=ISymbol>ADDON</a></div></div><div class=SRResult id=SR_addUnitEventHandlers><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_addUnitEventHandlers-sqf.html#OCAP_recorder_fnc_addUnitEventHandlers" target=_parent class=ISymbol>addUnitEventHandlers</a></div></div><div class=SRResult id=SR_administratorList><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/main/XEH_preInit-sqf.html#OCAP_administratorList" target=_parent class=ISymbol>administratorList</a></div></div><div class=SRResult id=SR_adminUIControl><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#OCAP_recorder_fnc_adminUIControl" target=_parent class=ISymbol>adminUIControl</a></div></div><div class=SRResult id=SR_ARR2><div class=IEntry><a href="../files/main/script_macros-hpp.html#ARR2" target=_parent class=ISymbol>ARR2</a></div></div><div class=SRResult id=SR_Auto_minStart_spcSettings><div class=IEntry><a href="../files/recorder/XEH_preInit-sqf.html#Auto-Start_Settings" target=_parent class=ISymbol>Auto-Start Settings</a></div></div><div class=SRResult id=SR_autoStart><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_autoStart" target=_parent class=ISymbol>autoStart</a></div></div><div class=SRResult id=SR2_autoStart><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_autoStart" target=_parent class=ISymbol>autoStart</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralB.html b/docs/search/GeneralB.html
    new file mode 100644
    index 0000000..5e160dc
    --- /dev/null
    +++ b/docs/search/GeneralB.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_BOOL><div class=IEntry><a href="../files/main/script_macros-hpp.html#BOOL" target=_parent class=ISymbol>BOOL</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralC.html b/docs/search/GeneralC.html
    new file mode 100644
    index 0000000..ab1e054
    --- /dev/null
    +++ b/docs/search/GeneralC.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_captureFrameNo><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_captureFrameNo" target=_parent class=ISymbol>captureFrameNo</a></div></div><div class=SRResult id=SR_captureLoop><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_captureLoop-sqf.html#OCAP_recorder_fnc_captureLoop" target=_parent class=ISymbol>captureLoop</a></div></div><div class=SRResult id=SR_CBA_spcEvents><div class=IEntry><a href="javascript:searchResults.Toggle('SR_CBA_spcEvents')" class=ISymbol>CBA Events</a><div class=ISubIndex><a href="../files/recorder/fnc_addEventMission-sqf.html#CBA_Events" target=_parent class=IFile>recorder\<wbr>fnc_addEventMission.sqf</a><a href="../files/recorder/fnc_eh_firedMan-sqf.html#CBA_Events" target=_parent class=IFile>recorder\<wbr>fnc_eh_firedMan.sqf</a><a href="../files/recorder/fnc_handleMarkers-sqf.html#CBA_Events" target=_parent class=IFile>recorder\<wbr>fnc_handleMarkers.sqf</a></div></div></div><div class=SRResult id=SR_CBA_spcListener_spcHandles><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#CBA_Listener_Handles" target=_parent class=ISymbol>CBA Listener Handles</a></div></div><div class=SRResult id=SR_CBA_spcSettings><div class=IEntry><a href="javascript:searchResults.Toggle('SR_CBA_spcSettings')" class=ISymbol>CBA Settings</a><div class=ISubIndex><a href="../files/main/XEH_preInit-sqf.html#CBA_Settings" target=_parent class=IFile>main\<wbr>XEH_preInit.sqf</a><a href="../files/recorder/XEH_preInit-sqf.html#CBA_Settings" target=_parent class=IFile>recorder\<wbr>XEH_preInit.sqf</a></div></div></div><div class=SRResult id=SR_COMPONENT><div class=IEntry><a href="javascript:searchResults.Toggle('SR_COMPONENT')" class=ISymbol>COMPONENT</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#COMPONENT" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#COMPONENT" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/recorder/script_component-hpp.html#COMPONENT" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_COMPONENT_undBEAUTIFIED><div class=IEntry><a href="javascript:searchResults.Toggle('SR_COMPONENT_undBEAUTIFIED')" class=ISymbol>COMPONENT_BEAUTIFIED</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#COMPONENT_BEAUTIFIED" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#COMPONENT_BEAUTIFIED" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/recorder/script_component-hpp.html#COMPONENT_BEAUTIFIED" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_COMPONENT_undNAME><div class=IEntry><a href="../files/main/script_macros-hpp.html#COMPONENT_NAME" target=_parent class=ISymbol>COMPONENT_NAME</a></div></div><div class=SRResult id=SR_Core><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Core')" class=ISymbol>Core</a><div class=ISubIndex><a href="../files/main/XEH_preInit-sqf.html#Core" target=_parent class=IFile>main\<wbr>XEH_preInit.sqf</a><a href="../files/recorder/XEH_preInit-sqf.html#Core" target=_parent class=IFile>recorder\<wbr>XEH_preInit.sqf</a></div></div></div><div class=SRResult id=SR_counter_undsides><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_counter_sides" target=_parent class=ISymbol>counter_sides</a></div></div><div class=SRResult id=SR_counterEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_counterEvent" target=_parent class=ISymbol>counterEvent</a></div></div><div class=SRResult id=SR_counterInit><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_counterInit" target=_parent class=ISymbol>counterInit</a></div></div><div class=SRResult id=SR_customEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_customEvent" target=_parent class=ISymbol>customEvent</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralD.html b/docs/search/GeneralD.html
    new file mode 100644
    index 0000000..b191455
    --- /dev/null
    +++ b/docs/search/GeneralD.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Deleted><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Deleted" target=_parent class=ISymbol>Deleted</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralE.html b/docs/search/GeneralE.html
    new file mode 100644
    index 0000000..f685d9b
    --- /dev/null
    +++ b/docs/search/GeneralE.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_eh_undconnected><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_connected-sqf.html#OCAP_recorder_fnc_eh_connected" target=_parent class=ISymbol>eh_connected</a></div></div><div class=SRResult id=SR_eh_unddisconnected><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_disconnected-sqf.html#OCAP_recorder_fnc_eh_disconnected" target=_parent class=ISymbol>eh_disconnected</a></div></div><div class=SRResult id=SR_eh_undfiredMan><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_recorder_fnc_eh_firedMan" target=_parent class=ISymbol>eh_firedMan</a></div></div><div class=SRResult id=SR_eh_undkilled><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_killed-sqf.html#OCAP_recorder_fnc_eh_killed" target=_parent class=ISymbol>eh_killed</a></div></div><div class=SRResult id=SR_eh_undonUserAdminStateChanged><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html#OCAP_recorder_fnc_eh_onUserAdminStateChanged" target=_parent class=ISymbol>eh_onUserAdminStateChanged</a></div></div><div class=SRResult id=SR_eh_undprojectileHit><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_eh_projectileHit-sqf.html#OCAP_recorder_fnc_eh_projectileHit" target=_parent class=ISymbol>eh_projectileHit</a></div></div><div class=SRResult id=SR_enabled><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/main/XEH_preInit-sqf.html#OCAP_enabled" target=_parent class=ISymbol>enabled</a></div></div><div class=SRResult id=SR_Ended><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_Ended" target=_parent class=ISymbol>Ended</a></div></div><div class=SRResult id=SR_EntityKilled><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_EntityKilled" target=_parent class=ISymbol>EntityKilled</a></div></div><div class=SRResult id=SR_entityMonitors><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_entityMonitors-sqf.html#OCAP_recorder_fnc_entityMonitors" target=_parent class=ISymbol>entityMonitors</a></div></div><div class=SRResult id=SR_entityMonitorsInitialized><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_entityMonitors-sqf.html#OCAP_entityMonitorsInitialized" target=_parent class=ISymbol>entityMonitorsInitialized</a></div></div><div class=SRResult id=SR_EntityRespawned><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_EntityRespawned" target=_parent class=ISymbol>EntityRespawned</a></div></div><div class=SRResult id=SR_Event_spcHandlers><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Event_spcHandlers')" class=ISymbol>Event Handlers</a><div class=ISubIndex><a href="../files/recorder/fnc_addEventMission-sqf.html#Event_Handlers" target=_parent class=IFile>recorder\<wbr>fnc_addEventMission.sqf</a><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Event_Handlers" target=_parent class=IFile>recorder\<wbr>fnc_eh_firedMan.sqf</a><a href="../files/recorder/fnc_handleMarkers-sqf.html#Event_Handlers" target=_parent class=IFile>recorder\<wbr>fnc_handleMarkers.sqf</a></div></div></div><div class=SRResult id=SR_excludeClassFromRecord><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_excludeClassFromRecord" target=_parent class=ISymbol>excludeClassFromRecord</a></div></div><div class=SRResult id=SR_excludeKindFromRecord><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_excludeKindFromRecord" target=_parent class=ISymbol>excludeKindFromRecord</a></div></div><div class=SRResult id=SR_excludeMarkerFromRecord><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_excludeMarkerFromRecord" target=_parent class=ISymbol>excludeMarkerFromRecord</a></div></div><div class=SRResult id=SR_Exclusions><div class=IEntry><a href="../files/recorder/XEH_preInit-sqf.html#Exclusions" target=_parent class=ISymbol>Exclusions</a></div></div><div class=SRResult id=SR_Explode><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Explode" target=_parent class=ISymbol>Explode</a></div></div><div class=SRResult id=SR_exportData><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_exportData" target=_parent class=ISymbol>exportData</a></div></div><div class=SRResult id=SR2_exportData><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_exportData-sqf.html#OCAP_recorder_fnc_exportData" target=_parent class=ISymbol>exportData</a></div></div><div class=SRResult id=SR_Extra_spcTracking><div class=IEntry><a href="../files/recorder/XEH_preInit-sqf.html#Extra_Tracking" target=_parent class=ISymbol>Extra Tracking</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralF.html b/docs/search/GeneralF.html
    new file mode 100644
    index 0000000..5669192
    --- /dev/null
    +++ b/docs/search/GeneralF.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_fnc_undaceExplosives_persqf><div class=IEntry><a href="../files/recorder/fnc_aceExplosives-sqf.html#fnc_aceExplosives.sqf" target=_parent class=ISymbol>fnc_aceExplosives.sqf</a></div></div><div class=SRResult id=SR_fnc_undaddEventMission_persqf><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#fnc_addEventMission.sqf" target=_parent class=ISymbol>fnc_addEventMission.sqf</a></div></div><div class=SRResult id=SR_fnc_undaddUnitEventHandlers_persqf><div class=IEntry><a href="../files/recorder/fnc_addUnitEventHandlers-sqf.html#fnc_addUnitEventHandlers.sqf" target=_parent class=ISymbol>fnc_addUnitEventHandlers.sqf</a></div></div><div class=SRResult id=SR_fnc_undadminUIControl_persqf><div class=IEntry><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#fnc_adminUIControl.sqf" target=_parent class=ISymbol>fnc_adminUIControl.sqf</a></div></div><div class=SRResult id=SR_fnc_undcaptureLoop_persqf><div class=IEntry><a href="../files/recorder/fnc_captureLoop-sqf.html#fnc_captureLoop.sqf" target=_parent class=ISymbol>fnc_captureLoop.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undconnected_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_connected-sqf.html#fnc_eh_connected.sqf" target=_parent class=ISymbol>fnc_eh_connected.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_unddisconnected_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_disconnected-sqf.html#fnc_eh_disconnected.sqf" target=_parent class=ISymbol>fnc_eh_disconnected.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undfiredMan_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#fnc_eh_firedMan.sqf" target=_parent class=ISymbol>fnc_eh_firedMan.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undkilled_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_killed-sqf.html#fnc_eh_killed.sqf" target=_parent class=ISymbol>fnc_eh_killed.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undonUserAdminStateChanged_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html#fnc_eh_onUserAdminStateChanged.sqf" target=_parent class=ISymbol>fnc_eh_onUserAdminStateChanged.sqf</a></div></div><div class=SRResult id=SR_fnc_undeh_undprojectileHit_persqf><div class=IEntry><a href="../files/recorder/fnc_eh_projectileHit-sqf.html#fnc_eh_projectileHit.sqf" target=_parent class=ISymbol>fnc_eh_projectileHit.sqf</a></div></div><div class=SRResult id=SR_fnc_undentityMonitors_persqf><div class=IEntry><a href="../files/recorder/fnc_entityMonitors-sqf.html#fnc_entityMonitors.sqf" target=_parent class=ISymbol>fnc_entityMonitors.sqf</a></div></div><div class=SRResult id=SR_fnc_undexportData_persqf><div class=IEntry><a href="../files/recorder/fnc_exportData-sqf.html#fnc_exportData.sqf" target=_parent class=ISymbol>fnc_exportData.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetAmmoMarkerData_persqf><div class=IEntry><a href="../files/recorder/fnc_getAmmoMarkerData-sqf.html#fnc_getAmmoMarkerData.sqf" target=_parent class=ISymbol>fnc_getAmmoMarkerData.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetClass_persqf><div class=IEntry><a href="../files/recorder/fnc_getClass-sqf.html#fnc_getClass.sqf" target=_parent class=ISymbol>fnc_getClass.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetEventWeaponText_persqf><div class=IEntry><a href="../files/recorder/fnc_getEventWeaponText-sqf.html#fnc_getEventWeaponText.sqf" target=_parent class=ISymbol>fnc_getEventWeaponText.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetInstigator_persqf><div class=IEntry><a href="../files/recorder/fnc_getInstigator-sqf.html#fnc_getInstigator.sqf" target=_parent class=ISymbol>fnc_getInstigator.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetUnitType_persqf><div class=IEntry><a href="../files/recorder/fnc_getUnitType-sqf.html#fnc_getUnitType.sqf" target=_parent class=ISymbol>fnc_getUnitType.sqf</a></div></div><div class=SRResult id=SR_fnc_undgetWeaponDisplayData_persqf><div class=IEntry><a href="../files/recorder/fnc_getWeaponDisplayData-sqf.html#fnc_getWeaponDisplayData.sqf" target=_parent class=ISymbol>fnc_getWeaponDisplayData.sqf</a></div></div><div class=SRResult id=SR_fnc_undhandleCustomEvent_persqf><div class=IEntry><a href="../files/recorder/fnc_handleCustomEvent-sqf.html#fnc_handleCustomEvent.sqf" target=_parent class=ISymbol>fnc_handleCustomEvent.sqf</a></div></div><div class=SRResult id=SR_fnc_undhandleMarkers_persqf><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#fnc_handleMarkers.sqf" target=_parent class=ISymbol>fnc_handleMarkers.sqf</a></div></div><div class=SRResult id=SR_fnc_undinit_persqf><div class=IEntry><a href="../files/recorder/fnc_init-sqf.html#fnc_init.sqf" target=_parent class=ISymbol>fnc_init.sqf</a></div></div><div class=SRResult id=SR_fnc_undisKindOfApc_persqf><div class=IEntry><a href="../files/recorder/fnc_isKindOfApc-sqf.html#fnc_isKindOfApc.sqf" target=_parent class=ISymbol>fnc_isKindOfApc.sqf</a></div></div><div class=SRResult id=SR_fnc_undprojectileMonitors_persqf><div class=IEntry><a href="../files/recorder/fnc_projectileMonitors-sqf.html#fnc_projectileMonitors.sqf" target=_parent class=ISymbol>fnc_projectileMonitors.sqf</a></div></div><div class=SRResult id=SR_fnc_undsendData_persqf><div class=IEntry><a href="../files/extension/fnc_sendData-sqf.html#fnc_sendData.sqf" target=_parent class=ISymbol>fnc_sendData.sqf</a></div></div><div class=SRResult id=SR_fnc_undstartRecording_persqf><div class=IEntry><a href="../files/recorder/fnc_startRecording-sqf.html#fnc_startRecording.sqf" target=_parent class=ISymbol>fnc_startRecording.sqf</a></div></div><div class=SRResult id=SR_fnc_undstopRecording_persqf><div class=IEntry><a href="../files/recorder/fnc_stopRecording-sqf.html#fnc_stopRecording.sqf" target=_parent class=ISymbol>fnc_stopRecording.sqf</a></div></div><div class=SRResult id=SR_fnc_undupdateTime_persqf><div class=IEntry><a href="../files/recorder/fnc_updateTime-sqf.html#fnc_updateTime.sqf" target=_parent class=ISymbol>fnc_updateTime.sqf</a></div></div><div class=SRResult id=SR_frameCaptureDelay><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_frameCaptureDelay" target=_parent class=ISymbol>frameCaptureDelay</a></div></div><div class=SRResult id=SR2_frameCaptureDelay><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_frameCaptureDelay" target=_parent class=ISymbol>frameCaptureDelay</a></div></div><div class=SRResult id=SR_Functions><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Functions')" class=ISymbol>Functions</a><div class=ISubIndex><a href="../files/extension/fnc_sendData-sqf.html#Functions" target=_parent class=IFile>extension\<wbr>fnc_sendData.sqf</a><a href="../files/recorder/fnc_aceExplosives-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_aceExplosives.sqf</a><a href="../files/recorder/fnc_addEventMission-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_addEventMission.sqf</a><a href="../files/recorder/fnc_addUnitEventHandlers-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_addUnitEventHandlers.sqf</a><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_adminUIcontrol.sqf</a><a href="../files/recorder/fnc_captureLoop-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_captureLoop.sqf</a><a href="../files/recorder/fnc_eh_connected-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_eh_connected.sqf</a><a href="../files/recorder/fnc_eh_disconnected-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_eh_disconnected.sqf</a><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_eh_firedMan.sqf</a><a href="../files/recorder/fnc_eh_killed-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_eh_killed.sqf</a><a href="../files/recorder/fnc_eh_onUserAdminStateChanged-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_eh_onUserAdminStateChanged.sqf</a><a href="../files/recorder/fnc_eh_projectileHit-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_eh_projectileHit.sqf</a><a href="../files/recorder/fnc_entityMonitors-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_entityMonitors.sqf</a><a href="../files/recorder/fnc_exportData-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_exportData.sqf</a><a href="../files/recorder/fnc_getAmmoMarkerData-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_getAmmoMarkerData.sqf</a><a href="../files/recorder/fnc_getClass-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_getClass.sqf</a><a href="../files/recorder/fnc_getEventWeaponText-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_getEventWeaponText.sqf</a><a href="../files/recorder/fnc_getInstigator-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_getInstigator.sqf</a><a href="../files/recorder/fnc_getUnitType-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_getUnitType.sqf</a><a href="../files/recorder/fnc_getWeaponDisplayData-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_getWeaponDisplayData.sqf</a><a href="../files/recorder/fnc_handleCustomEvent-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_handleCustomEvent.sqf</a><a href="../files/recorder/fnc_handleMarkers-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_handleMarkers.sqf</a><a href="../files/recorder/fnc_init-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_init.sqf</a><a href="../files/recorder/fnc_isKindOfApc-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_isKindOfApc.sqf</a><a href="../files/recorder/fnc_projectileMonitors-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_projectileMonitors.sqf</a><a href="../files/recorder/fnc_startRecording-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_startRecording.sqf</a><a href="../files/recorder/fnc_stopRecording-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_stopRecording.sqf</a><a href="../files/recorder/fnc_updateTime-sqf.html#Functions" target=_parent class=IFile>recorder\<wbr>fnc_updateTime.sqf</a></div></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralG.html b/docs/search/GeneralG.html
    new file mode 100644
    index 0000000..491a410
    --- /dev/null
    +++ b/docs/search/GeneralG.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_getAmmoMarkerData><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getAmmoMarkerData-sqf.html#OCAP_recorder_fnc_getAmmoMarkerData" target=_parent class=ISymbol>getAmmoMarkerData</a></div></div><div class=SRResult id=SR_getClass><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getClass-sqf.html#OCAP_recorder_fnc_getClass" target=_parent class=ISymbol>getClass</a></div></div><div class=SRResult id=SR_getEventWeaponText><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getEventWeaponText-sqf.html#OCAP_recorder_fnc_getEventWeaponText" target=_parent class=ISymbol>getEventWeaponText</a></div></div><div class=SRResult id=SR_getInstigator><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getInstigator-sqf.html#OCAP_recorder_fnc_getInstigator" target=_parent class=ISymbol>getInstigator</a></div></div><div class=SRResult id=SR_getUnitType><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getUnitType-sqf.html#OCAP_recorder_fnc_getUnitType" target=_parent class=ISymbol>getUnitType</a></div></div><div class=SRResult id=SR_getWeaponDisplayData><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_getWeaponDisplayData-sqf.html#OCAP_recorder_fnc_getWeaponDisplayData" target=_parent class=ISymbol>getWeaponDisplayData</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralH.html b/docs/search/GeneralH.html
    new file mode 100644
    index 0000000..0f75ce0
    --- /dev/null
    +++ b/docs/search/GeneralH.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_handleCustomEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_handleCustomEvent-sqf.html#OCAP_recorder_fnc_handleCustomEvent" target=_parent class=ISymbol>handleCustomEvent</a></div></div><div class=SRResult id=SR_HandleDisconnect><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_HandleDisconnect" target=_parent class=ISymbol>HandleDisconnect</a></div></div><div class=SRResult id=SR_handleMarker><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_handleMarker" target=_parent class=ISymbol>handleMarker</a></div></div><div class=SRResult id=SR_handleMarkers><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_recorder_fnc_handleMarkers" target=_parent class=ISymbol>handleMarkers</a></div></div><div class=SRResult id=SR_hasAdminControls><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#OCAP_hasAdminControls" target=_parent class=ISymbol>hasAdminControls</a></div></div><div class=SRResult id=SR_HitExplosion><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#HitExplosion" target=_parent class=ISymbol>HitExplosion</a></div></div><div class=SRResult id=SR_HitPart><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#HitPart" target=_parent class=ISymbol>HitPart</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralI.html b/docs/search/GeneralI.html
    new file mode 100644
    index 0000000..5c4261b
    --- /dev/null
    +++ b/docs/search/GeneralI.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_init><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_fnc_init" target=_parent class=ISymbol>init</a></div></div><div class=SRResult id=SR_isDebug><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/main/XEH_preInit-sqf.html#OCAP_isDebug" target=_parent class=ISymbol>isDebug</a></div></div><div class=SRResult id=SR_isKindOfApc><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_isKindOfApc-sqf.html#OCAP_recorder_fnc_isKindOfApc" target=_parent class=ISymbol>isKindOfApc</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralL.html b/docs/search/GeneralL.html
    new file mode 100644
    index 0000000..8248985
    --- /dev/null
    +++ b/docs/search/GeneralL.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_lastFired><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_lastFired" target=_parent class=ISymbol>lastFired</a></div></div><div class=SRResult id=SR_listener_undaceExplosives><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_aceExplosives" target=_parent class=ISymbol>listener_aceExplosives</a></div></div><div class=SRResult id=SR_listener_undaceThrowing><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_aceThrowing" target=_parent class=ISymbol>listener_aceThrowing</a></div></div><div class=SRResult id=SR_listener_undcounterEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_counterEvent" target=_parent class=ISymbol>listener_counterEvent</a></div></div><div class=SRResult id=SR_listener_undcounterInit><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_counterInit" target=_parent class=ISymbol>listener_counterInit</a></div></div><div class=SRResult id=SR_listener_undcustomEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_customEvent" target=_parent class=ISymbol>listener_customEvent</a></div></div><div class=SRResult id=SR_listener_undexportData><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_exportData" target=_parent class=ISymbol>listener_exportData</a></div></div><div class=SRResult id=SR_listener_undmarkers><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_listener_markers" target=_parent class=ISymbol>listener_markers</a></div></div><div class=SRResult id=SR_listener_undpause><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_pause" target=_parent class=ISymbol>listener_pause</a></div></div><div class=SRResult id=SR_listener_undrecord><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_record" target=_parent class=ISymbol>listener_record</a></div></div><div class=SRResult id=SR_liveDebugBullets><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_projectileMonitors-sqf.html#OCAP_recorder_liveDebugBullets" target=_parent class=ISymbol>liveDebugBullets</a></div></div><div class=SRResult id=SR_liveDebugMagIcons><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_projectileMonitors-sqf.html#OCAP_recorder_liveDebugMagIcons" target=_parent class=ISymbol>liveDebugMagIcons</a></div></div><div class=SRResult id=SR_LOG><div class=IEntry><a href="../files/main/script_macros-hpp.html#LOG" target=_parent class=ISymbol>LOG</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralM.html b/docs/search/GeneralM.html
    new file mode 100644
    index 0000000..0dc7bbd
    --- /dev/null
    +++ b/docs/search/GeneralM.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Macros><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Macros')" class=ISymbol>Macros</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#Macros" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#Macros" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/main/script_macros-hpp.html#Macros" target=_parent class=IFile>main\<wbr>script_macros.hpp</a><a href="../files/recorder/script_component-hpp.html#Macros" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_MarkerCreated><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#MarkerCreated" target=_parent class=ISymbol>MarkerCreated</a></div></div><div class=SRResult id=SR_MarkerDeleted><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#MarkerDeleted" target=_parent class=ISymbol>MarkerDeleted</a></div></div><div class=SRResult id=SR_MarkerUpdated><div class=IEntry><a href="../files/recorder/fnc_handleMarkers-sqf.html#MarkerUpdated" target=_parent class=ISymbol>MarkerUpdated</a></div></div><div class=SRResult id=SR_minMissionTime><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_minMissionTime" target=_parent class=ISymbol>minMissionTime</a></div></div><div class=SRResult id=SR2_minMissionTime><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_minMissionTime" target=_parent class=ISymbol>minMissionTime</a></div></div><div class=SRResult id=SR_minPlayerCount><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_minPlayerCount" target=_parent class=ISymbol>minPlayerCount</a></div></div><div class=SRResult id=SR_MPEnded><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_MPEnded" target=_parent class=ISymbol>MPEnded</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralN.html b/docs/search/GeneralN.html
    new file mode 100644
    index 0000000..e5e8dbd
    --- /dev/null
    +++ b/docs/search/GeneralN.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_nextId><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_nextId" target=_parent class=ISymbol>nextId</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralO.html b/docs/search/GeneralO.html
    new file mode 100644
    index 0000000..ca3ecb3
    --- /dev/null
    +++ b/docs/search/GeneralO.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_OnUserAdminStateChanged><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_OnUserAdminStateChanged" target=_parent class=ISymbol>OnUserAdminStateChanged</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralP.html b/docs/search/GeneralP.html
    new file mode 100644
    index 0000000..27d90a0
    --- /dev/null
    +++ b/docs/search/GeneralP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_pause><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_pause" target=_parent class=ISymbol>pause</a></div></div><div class=SRResult id=SR_PFHObject><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_captureLoop-sqf.html#OCAP_PFHObject" target=_parent class=ISymbol>PFHObject</a></div></div><div class=SRResult id=SR_PlayerConnected><div class=IEntry><span class=ISymbolPrefix>OCAP_EH_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_EH_PlayerConnected" target=_parent class=ISymbol>PlayerConnected</a></div></div><div class=SRResult id=SR_preferACEUnconscious><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_preferACEUnconscious" target=_parent class=ISymbol>preferACEUnconscious</a></div></div><div class=SRResult id=SR_PREFIX><div class=IEntry><a href="../files/main/script_macros-hpp.html#PREFIX" target=_parent class=ISymbol>PREFIX</a></div></div><div class=SRResult id=SR_projectileMonitors><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_projectileMonitors-sqf.html#OCAP_recorder_fnc_projectileMonitors" target=_parent class=ISymbol>projectileMonitors</a></div></div><div class=SRResult id=SR_Projectiles><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Projectiles" target=_parent class=ISymbol>Projectiles</a></div></div><div class=SRResult id=SR_Projectiles_lpaBullets_rpa><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Projectiles(Bullets)" target=_parent class=ISymbol>Projectiles(Bullets)</a></div></div><div class=SRResult id=SR_Projectiles_lpaNon_minBullets_rpa><div class=IEntry><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Projectiles(Non-Bullets)" target=_parent class=ISymbol>Projectiles(Non-Bullets)</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralR.html b/docs/search/GeneralR.html
    new file mode 100644
    index 0000000..726fc9d
    --- /dev/null
    +++ b/docs/search/GeneralR.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_record><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_record" target=_parent class=ISymbol>record</a></div></div><div class=SRResult id=SR_recording><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_recording" target=_parent class=ISymbol>recording</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralS.html b/docs/search/GeneralS.html
    new file mode 100644
    index 0000000..a646058
    --- /dev/null
    +++ b/docs/search/GeneralS.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Save_slaExport_spcSettings><div class=IEntry><a href="../files/recorder/XEH_preInit-sqf.html#Save/Export_Settings" target=_parent class=ISymbol>Save/<wbr>Export Settings</a></div></div><div class=SRResult id=SR_saveMissionEnded><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_saveMissionEnded" target=_parent class=ISymbol>saveMissionEnded</a></div></div><div class=SRResult id=SR_saveOnEmpty><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_saveOnEmpty" target=_parent class=ISymbol>saveOnEmpty</a></div></div><div class=SRResult id=SR_saveTag><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_saveTag" target=_parent class=ISymbol>saveTag</a></div></div><div class=SRResult id=SR_script_undcomponent_perhpp><div class=IEntry><a href="javascript:searchResults.Toggle('SR_script_undcomponent_perhpp')" class=ISymbol>script_component.hpp</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#script_component.hpp" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#script_component.hpp" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/recorder/script_component-hpp.html#script_component.hpp" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_script_undmacros_perhpp><div class=IEntry><a href="../files/main/script_macros-hpp.html#script_macros.hpp" target=_parent class=ISymbol>script_macros.hpp</a></div></div><div class=SRResult id=SR_sendData><div class=IEntry><span class=ISymbolPrefix>OCAP_extension_fnc_</span><a href="../files/extension/fnc_sendData-sqf.html#OCAP_extension_fnc_sendData" target=_parent class=ISymbol>sendData</a></div></div><div class=SRResult id=SR_SHOULDSAVEEVENTS><div class=IEntry><a href="../files/main/script_macros-hpp.html#SHOULDSAVEEVENTS" target=_parent class=ISymbol>SHOULDSAVEEVENTS</a></div></div><div class=SRResult id=SR_startRecording><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_startRecording-sqf.html#OCAP_recorder_fnc_startRecording" target=_parent class=ISymbol>startRecording</a></div></div><div class=SRResult id=SR_stopRecording><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_stopRecording-sqf.html#OCAP_recorder_fnc_stopRecording" target=_parent class=ISymbol>stopRecording</a></div></div><div class=SRResult id=SR_SYSCHAT><div class=IEntry><a href="../files/main/script_macros-hpp.html#SYSCHAT" target=_parent class=ISymbol>SYSCHAT</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralT.html b/docs/search/GeneralT.html
    new file mode 100644
    index 0000000..70ee5ca
    --- /dev/null
    +++ b/docs/search/GeneralT.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_trackedMarkers><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_recorder_trackedMarkers" target=_parent class=ISymbol>trackedMarkers</a></div></div><div class=SRResult id=SR_trackTickets><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_trackTickets" target=_parent class=ISymbol>trackTickets</a></div></div><div class=SRResult id=SR_trackTimeInterval><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_trackTimeInterval" target=_parent class=ISymbol>trackTimeInterval</a></div></div><div class=SRResult id=SR_trackTimes><div class=IEntry><span class=ISymbolPrefix>OCAP_settings_</span><a href="../files/recorder/XEH_preInit-sqf.html#OCAP_settings_trackTimes" target=_parent class=ISymbol>trackTimes</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralU.html b/docs/search/GeneralU.html
    new file mode 100644
    index 0000000..27bf0df
    --- /dev/null
    +++ b/docs/search/GeneralU.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_updateTime><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_fnc_</span><a href="../files/recorder/fnc_updateTime-sqf.html#OCAP_recorder_fnc_updateTime" target=_parent class=ISymbol>updateTime</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/GeneralV.html b/docs/search/GeneralV.html
    new file mode 100644
    index 0000000..30ce1a1
    --- /dev/null
    +++ b/docs/search/GeneralV.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_Variables><div class=IEntry><a href="javascript:searchResults.Toggle('SR_Variables')" class=ISymbol>Variables</a><div class=ISubIndex><a href="../files/recorder/fnc_addEventMission-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_addEventMission.sqf</a><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_adminUIcontrol.sqf</a><a href="../files/recorder/fnc_captureLoop-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_captureLoop.sqf</a><a href="../files/recorder/fnc_eh_firedMan-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_eh_firedMan.sqf</a><a href="../files/recorder/fnc_entityMonitors-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_entityMonitors.sqf</a><a href="../files/recorder/fnc_handleMarkers-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_handleMarkers.sqf</a><a href="../files/recorder/fnc_init-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_init.sqf</a><a href="../files/recorder/fnc_projectileMonitors-sqf.html#Variables" target=_parent class=IFile>recorder\<wbr>fnc_projectileMonitors.sqf</a></div></div></div><div class=SRResult id=SR_version><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_version" target=_parent class=ISymbol>version</a></div></div><div class=SRResult id=SR2_version><div class=IEntry><span class=ISymbolPrefix>OCAP_extension_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_extension_version" target=_parent class=ISymbol>version</a></div></div><div class=SRResult id=SR3_VERSION><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION" target=_parent class=ISymbol>VERSION</a></div></div><div class=SRResult id=SR_VERSION_undAR><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION_AR" target=_parent class=ISymbol>VERSION_AR</a></div></div><div class=SRResult id=SR_VERSION_undREQUIRED><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION_REQUIRED" target=_parent class=ISymbol>VERSION_REQUIRED</a></div></div><div class=SRResult id=SR_VERSION_undSTR><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION_STR" target=_parent class=ISymbol>VERSION_STR</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosA.html b/docs/search/MacrosA.html
    new file mode 100644
    index 0000000..e55ee53
    --- /dev/null
    +++ b/docs/search/MacrosA.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_ADDON><div class=IEntry><a href="../files/main/script_macros-hpp.html#ADDON" target=_parent class=ISymbol>ADDON</a></div></div><div class=SRResult id=SR_ARR2><div class=IEntry><a href="../files/main/script_macros-hpp.html#ARR2" target=_parent class=ISymbol>ARR2</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosB.html b/docs/search/MacrosB.html
    new file mode 100644
    index 0000000..5e160dc
    --- /dev/null
    +++ b/docs/search/MacrosB.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_BOOL><div class=IEntry><a href="../files/main/script_macros-hpp.html#BOOL" target=_parent class=ISymbol>BOOL</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosC.html b/docs/search/MacrosC.html
    new file mode 100644
    index 0000000..f11af39
    --- /dev/null
    +++ b/docs/search/MacrosC.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_COMPONENT><div class=IEntry><a href="javascript:searchResults.Toggle('SR_COMPONENT')" class=ISymbol>COMPONENT</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#COMPONENT" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#COMPONENT" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/recorder/script_component-hpp.html#COMPONENT" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_COMPONENT_undBEAUTIFIED><div class=IEntry><a href="javascript:searchResults.Toggle('SR_COMPONENT_undBEAUTIFIED')" class=ISymbol>COMPONENT_BEAUTIFIED</a><div class=ISubIndex><a href="../files/extension/script_component-hpp.html#COMPONENT_BEAUTIFIED" target=_parent class=IFile>extension\<wbr>script_component.hpp</a><a href="../files/main/script_component-hpp.html#COMPONENT_BEAUTIFIED" target=_parent class=IFile>main\<wbr>script_component.hpp</a><a href="../files/recorder/script_component-hpp.html#COMPONENT_BEAUTIFIED" target=_parent class=IFile>recorder\<wbr>script_component.hpp</a></div></div></div><div class=SRResult id=SR_COMPONENT_undNAME><div class=IEntry><a href="../files/main/script_macros-hpp.html#COMPONENT_NAME" target=_parent class=ISymbol>COMPONENT_NAME</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosL.html b/docs/search/MacrosL.html
    new file mode 100644
    index 0000000..8b1106b
    --- /dev/null
    +++ b/docs/search/MacrosL.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_LOG><div class=IEntry><a href="../files/main/script_macros-hpp.html#LOG" target=_parent class=ISymbol>LOG</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosP.html b/docs/search/MacrosP.html
    new file mode 100644
    index 0000000..5455079
    --- /dev/null
    +++ b/docs/search/MacrosP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_PREFIX><div class=IEntry><a href="../files/main/script_macros-hpp.html#PREFIX" target=_parent class=ISymbol>PREFIX</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosS.html b/docs/search/MacrosS.html
    new file mode 100644
    index 0000000..b190758
    --- /dev/null
    +++ b/docs/search/MacrosS.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_SHOULDSAVEEVENTS><div class=IEntry><a href="../files/main/script_macros-hpp.html#SHOULDSAVEEVENTS" target=_parent class=ISymbol>SHOULDSAVEEVENTS</a></div></div><div class=SRResult id=SR_SYSCHAT><div class=IEntry><a href="../files/main/script_macros-hpp.html#SYSCHAT" target=_parent class=ISymbol>SYSCHAT</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/MacrosV.html b/docs/search/MacrosV.html
    new file mode 100644
    index 0000000..8545d0c
    --- /dev/null
    +++ b/docs/search/MacrosV.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_VERSION><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION" target=_parent class=ISymbol>VERSION</a></div></div><div class=SRResult id=SR_VERSION_undAR><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION_AR" target=_parent class=ISymbol>VERSION_AR</a></div></div><div class=SRResult id=SR_VERSION_undREQUIRED><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION_REQUIRED" target=_parent class=ISymbol>VERSION_REQUIRED</a></div></div><div class=SRResult id=SR_VERSION_undSTR><div class=IEntry><a href="../files/main/script_macros-hpp.html#VERSION_STR" target=_parent class=ISymbol>VERSION_STR</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/NoResults.html b/docs/search/NoResults.html
    new file mode 100644
    index 0000000..8c72496
    --- /dev/null
    +++ b/docs/search/NoResults.html
    @@ -0,0 +1,15 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=NoMatches>No Matches</div></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesA.html b/docs/search/VariablesA.html
    new file mode 100644
    index 0000000..c2d4197
    --- /dev/null
    +++ b/docs/search/VariablesA.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_autoStart><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_autoStart" target=_parent class=ISymbol>autoStart</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesC.html b/docs/search/VariablesC.html
    new file mode 100644
    index 0000000..a8cf1b8
    --- /dev/null
    +++ b/docs/search/VariablesC.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_captureFrameNo><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_captureFrameNo" target=_parent class=ISymbol>captureFrameNo</a></div></div><div class=SRResult id=SR_CBA_spcListener_spcHandles><div class=IEntry><a href="../files/recorder/fnc_addEventMission-sqf.html#CBA_Listener_Handles" target=_parent class=ISymbol>CBA Listener Handles</a></div></div><div class=SRResult id=SR_counter_undsides><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_counter_sides" target=_parent class=ISymbol>counter_sides</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesE.html b/docs/search/VariablesE.html
    new file mode 100644
    index 0000000..95f50d7
    --- /dev/null
    +++ b/docs/search/VariablesE.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_entityMonitorsInitialized><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_entityMonitors-sqf.html#OCAP_entityMonitorsInitialized" target=_parent class=ISymbol>entityMonitorsInitialized</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesF.html b/docs/search/VariablesF.html
    new file mode 100644
    index 0000000..296f619
    --- /dev/null
    +++ b/docs/search/VariablesF.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_frameCaptureDelay><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_frameCaptureDelay" target=_parent class=ISymbol>frameCaptureDelay</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesH.html b/docs/search/VariablesH.html
    new file mode 100644
    index 0000000..2f627fa
    --- /dev/null
    +++ b/docs/search/VariablesH.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_hasAdminControls><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_adminUIcontrol-sqf.html#OCAP_hasAdminControls" target=_parent class=ISymbol>hasAdminControls</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesL.html b/docs/search/VariablesL.html
    new file mode 100644
    index 0000000..e4738aa
    --- /dev/null
    +++ b/docs/search/VariablesL.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_lastFired><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_eh_firedMan-sqf.html#OCAP_lastFired" target=_parent class=ISymbol>lastFired</a></div></div><div class=SRResult id=SR_listener_undaceExplosives><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_aceExplosives" target=_parent class=ISymbol>listener_aceExplosives</a></div></div><div class=SRResult id=SR_listener_undaceThrowing><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_aceThrowing" target=_parent class=ISymbol>listener_aceThrowing</a></div></div><div class=SRResult id=SR_listener_undcounterEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_counterEvent" target=_parent class=ISymbol>listener_counterEvent</a></div></div><div class=SRResult id=SR_listener_undcounterInit><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_counterInit" target=_parent class=ISymbol>listener_counterInit</a></div></div><div class=SRResult id=SR_listener_undcustomEvent><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_customEvent" target=_parent class=ISymbol>listener_customEvent</a></div></div><div class=SRResult id=SR_listener_undexportData><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_exportData" target=_parent class=ISymbol>listener_exportData</a></div></div><div class=SRResult id=SR_listener_undmarkers><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_listener_markers" target=_parent class=ISymbol>listener_markers</a></div></div><div class=SRResult id=SR_listener_undpause><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_pause" target=_parent class=ISymbol>listener_pause</a></div></div><div class=SRResult id=SR_listener_undrecord><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_addEventMission-sqf.html#OCAP_listener_record" target=_parent class=ISymbol>listener_record</a></div></div><div class=SRResult id=SR_liveDebugBullets><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_projectileMonitors-sqf.html#OCAP_recorder_liveDebugBullets" target=_parent class=ISymbol>liveDebugBullets</a></div></div><div class=SRResult id=SR_liveDebugMagIcons><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_projectileMonitors-sqf.html#OCAP_recorder_liveDebugMagIcons" target=_parent class=ISymbol>liveDebugMagIcons</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesM.html b/docs/search/VariablesM.html
    new file mode 100644
    index 0000000..359cdbc
    --- /dev/null
    +++ b/docs/search/VariablesM.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_minMissionTime><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_minMissionTime" target=_parent class=ISymbol>minMissionTime</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesN.html b/docs/search/VariablesN.html
    new file mode 100644
    index 0000000..e5e8dbd
    --- /dev/null
    +++ b/docs/search/VariablesN.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_nextId><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_nextId" target=_parent class=ISymbol>nextId</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesP.html b/docs/search/VariablesP.html
    new file mode 100644
    index 0000000..03e9a32
    --- /dev/null
    +++ b/docs/search/VariablesP.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_PFHObject><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_captureLoop-sqf.html#OCAP_PFHObject" target=_parent class=ISymbol>PFHObject</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesR.html b/docs/search/VariablesR.html
    new file mode 100644
    index 0000000..1d01ef7
    --- /dev/null
    +++ b/docs/search/VariablesR.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_recording><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_recorder_recording" target=_parent class=ISymbol>recording</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesT.html b/docs/search/VariablesT.html
    new file mode 100644
    index 0000000..c71baa6
    --- /dev/null
    +++ b/docs/search/VariablesT.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_trackedMarkers><div class=IEntry><span class=ISymbolPrefix>OCAP_recorder_</span><a href="../files/recorder/fnc_handleMarkers-sqf.html#OCAP_recorder_trackedMarkers" target=_parent class=ISymbol>trackedMarkers</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/search/VariablesV.html b/docs/search/VariablesV.html
    new file mode 100644
    index 0000000..465cc3f
    --- /dev/null
    +++ b/docs/search/VariablesV.html
    @@ -0,0 +1,20 @@
    +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
    +
    +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel="stylesheet" type="text/css" href="../styles/main.css"><script language=JavaScript src="../javascript/main.js"></script></head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()"><script language=JavaScript><!--
    +if (browserType) {document.write("<div class=" + browserType + ">");if (browserVer) {document.write("<div class=" + browserVer + ">"); }}// --></script>
    +
    +<!--  Generated by Natural Docs, version 1.52 -->
    +<!--  http://www.naturaldocs.org  -->
    +
    +<!-- saved from url=(0026)http://www.naturaldocs.org -->
    +
    +
    +
    +
    +<div id=Index><div class=SRStatus id=Loading>Loading...</div><table border=0 cellspacing=0 cellpadding=0><div class=SRResult id=SR_version><div class=IEntry><span class=ISymbolPrefix>OCAP_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_version" target=_parent class=ISymbol>version</a></div></div><div class=SRResult id=SR2_version><div class=IEntry><span class=ISymbolPrefix>OCAP_extension_</span><a href="../files/recorder/fnc_init-sqf.html#OCAP_extension_version" target=_parent class=ISymbol>version</a></div></div></table><div class=SRStatus id=Searching>Searching...</div><div class=SRStatus id=NoMatches>No Matches</div><script type="text/javascript"><!--
    +document.getElementById("Loading").style.display="none";
    +document.getElementById("NoMatches").style.display="none";
    +var searchResults = new SearchResults("searchResults", "HTML");
    +searchResults.Search();
    +--></script></div><script language=JavaScript><!--
    +if (browserType) {if (browserVer) {document.write("</div>"); }document.write("</div>");}// --></script></body></html>
    \ No newline at end of file
    diff --git a/docs/styles/main.css b/docs/styles/main.css
    new file mode 100644
    index 0000000..b769e5f
    --- /dev/null
    +++ b/docs/styles/main.css
    @@ -0,0 +1,828 @@
    +/*
    +   IMPORTANT: If you're editing this file in the output directory of one of
    +   your projects, your changes will be overwritten the next time you run
    +   Natural Docs.  Instead, copy this file to your project directory, make your
    +   changes, and you can use it with -s.  Even better would be to make a CSS
    +   file in your project directory with only your changes, which you can then
    +   use with -s [original style] [your changes].
    +
    +   On the other hand, if you're editing this file in the Natural Docs styles
    +   directory, the changes will automatically be applied to all your projects
    +   that use this style the next time Natural Docs is run on them.
    +
    +   This file is part of Natural Docs, which is Copyright  2003-2010 Greg Valure.
    +   Natural Docs is licensed under version 3 of the GNU Affero General Public
    +   License (AGPL).  Refer to License.txt for the complete details.
    +
    +   This file may be distributed with documentation files generated by Natural Docs.
    +   Such documentation is not covered by Natural Docs' copyright and licensing,
    +   and may have its own copyright and distribution terms as decided by its author.
    +*/
    +
    +body {
    +    font: 10pt Verdana, Arial, sans-serif;
    +    color: #000000;
    +    margin: 0; padding: 0;
    +    }
    +
    +.ContentPage,
    +.IndexPage,
    +.FramedMenuPage {
    +    background-color: #E8E8E8;
    +    }
    +.FramedContentPage,
    +.FramedIndexPage,
    +.FramedSearchResultsPage,
    +.PopupSearchResultsPage {
    +    background-color: #FFFFFF;
    +    }
    +
    +
    +a:link,
    +a:visited { color: #900000; text-decoration: none }
    +a:hover { color: #900000; text-decoration: underline }
    +a:active { color: #FF0000; text-decoration: underline }
    +
    +td {
    +    vertical-align: top }
    +
    +img { border: 0;  }
    +
    +
    +/*
    +    Comment out this line to use web-style paragraphs (blank line between
    +    paragraphs, no indent) instead of print-style paragraphs (no blank line,
    +    indented.)
    +*/
    +p {
    +    text-indent: 5ex; margin: 0 }
    +
    +
    +/*  Opera doesn't break with just wbr, but will if you add this.  */
    +.Opera wbr:after {
    +	content: "\00200B";
    +	}
    +
    +
    +/*  Blockquotes are used as containers for things that may need to scroll.  */
    +blockquote {
    +    padding: 0;
    +    margin: 0;
    +    overflow: auto;
    +    }
    +
    +
    +.Firefox1 blockquote {
    +    padding-bottom: .5em;
    +    }
    +
    +/*  Turn off scrolling when printing.  */
    +@media print {
    +    blockquote {
    +        overflow: visible;
    +        }
    +    .IE blockquote {
    +        width: auto;
    +        }
    +    }
    +
    +
    +
    +#Menu {
    +    font-size: 9pt;
    +    padding: 10px 0 0 0;
    +    }
    +.ContentPage #Menu,
    +.IndexPage #Menu {
    +    position: absolute;
    +    top: 0;
    +    left: 0;
    +    width: 31ex;
    +    overflow: hidden;
    +    }
    +.ContentPage .Firefox #Menu,
    +.IndexPage .Firefox #Menu {
    +    width: 27ex;
    +    }
    +
    +
    +    .MTitle {
    +        font-size: 16pt; font-weight: bold; font-variant: small-caps;
    +        text-align: center;
    +        padding: 5px 10px 15px 10px;
    +        border-bottom: 1px dotted #000000;
    +        margin-bottom: 15px }
    +
    +    .MSubTitle {
    +        font-size: 9pt; font-weight: normal; font-variant: normal;
    +        margin-top: 1ex; margin-bottom: 5px }
    +
    +
    +    .MEntry a:link,
    +    .MEntry a:hover,
    +    .MEntry a:visited { color: #606060; margin-right: 0 }
    +    .MEntry a:active { color: #A00000; margin-right: 0 }
    +
    +
    +    .MGroup {
    +        font-variant: small-caps; font-weight: bold;
    +        margin: 1em 0 1em 10px;
    +        }
    +
    +    .MGroupContent {
    +        font-variant: normal; font-weight: normal }
    +
    +    .MGroup a:link,
    +    .MGroup a:hover,
    +    .MGroup a:visited { color: #545454; margin-right: 10px }
    +    .MGroup a:active { color: #A00000; margin-right: 10px }
    +
    +
    +    .MFile,
    +    .MText,
    +    .MLink,
    +    .MIndex {
    +        padding: 1px 17px 2px 10px;
    +        margin: .25em 0 .25em 0;
    +        }
    +
    +    .MText {
    +        font-size: 8pt; font-style: italic }
    +
    +    .MLink {
    +        font-style: italic }
    +
    +    #MSelected {
    +        color: #000000; background-color: #FFFFFF;
    +        /*  Replace padding with border.  */
    +        padding: 0 10px 0 10px;
    +        border-width: 1px 2px 2px 0; border-style: solid; border-color: #000000;
    +        margin-right: 5px;
    +        }
    +
    +    /*  Close off the left side when its in a group.  */
    +    .MGroup #MSelected {
    +        padding-left: 9px; border-left-width: 1px }
    +
    +    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
    +    .Firefox #MSelected {
    +        -moz-border-radius-topright: 10px;
    +        -moz-border-radius-bottomright: 10px }
    +    .Firefox .MGroup #MSelected {
    +        -moz-border-radius-topleft: 10px;
    +        -moz-border-radius-bottomleft: 10px }
    +
    +
    +    #MSearchPanel {
    +        padding: 0px 6px;
    +        margin: .25em 0;
    +        }
    +
    +
    +    #MSearchField {
    +        font: italic 9pt Verdana, sans-serif;
    +        color: #606060;
    +        background-color: #E8E8E8;
    +        border: none;
    +        padding: 2px 4px;
    +        width: 100%;
    +        }
    +    /* Only Opera gets it right. */
    +    .Firefox #MSearchField,
    +    .IE #MSearchField,
    +    .Safari #MSearchField {
    +        width: 94%;
    +        }
    +    .Opera9 #MSearchField,
    +    .Konqueror #MSearchField {
    +        width: 97%;
    +        }
    +    .FramedMenuPage .Firefox #MSearchField,
    +    .FramedMenuPage .Safari #MSearchField,
    +    .FramedMenuPage .Konqueror #MSearchField {
    +        width: 98%;
    +        }
    +
    +    /* Firefox doesn't do this right in frames without #MSearchPanel added on.
    +        It's presence doesn't hurt anything other browsers. */
    +    #MSearchPanel.MSearchPanelInactive:hover #MSearchField {
    +        background-color: #FFFFFF;
    +        border: 1px solid #C0C0C0;
    +        padding: 1px 3px;
    +        }
    +    .MSearchPanelActive #MSearchField {
    +        background-color: #FFFFFF;
    +        border: 1px solid #C0C0C0;
    +        font-style: normal;
    +        padding: 1px 3px;
    +        }
    +
    +    #MSearchType {
    +        visibility: hidden;
    +        font: 8pt Verdana, sans-serif;
    +        width: 98%;
    +        padding: 0;
    +        border: 1px solid #C0C0C0;
    +        }
    +    .MSearchPanelActive #MSearchType,
    +    /*  As mentioned above, Firefox doesn't do this right in frames without #MSearchPanel added on. */
    +    #MSearchPanel.MSearchPanelInactive:hover #MSearchType,
    +    #MSearchType:focus {
    +        visibility: visible;
    +        color: #606060;
    +        }
    +    #MSearchType option#MSearchEverything {
    +        font-weight: bold;
    +        }
    +
    +    .Opera8 .MSearchPanelInactive:hover,
    +    .Opera8 .MSearchPanelActive {
    +        margin-left: -1px;
    +        }
    +
    +
    +    iframe#MSearchResults {
    +        width: 60ex;
    +        height: 15em;
    +        }
    +    #MSearchResultsWindow {
    +        display: none;
    +        position: absolute;
    +        left: 0; top: 0;
    +        border: 1px solid #000000;
    +        background-color: #E8E8E8;
    +        }
    +    #MSearchResultsWindowClose {
    +        font-weight: bold;
    +        font-size: 8pt;
    +        display: block;
    +        padding: 2px 5px;
    +        }
    +    #MSearchResultsWindowClose:link,
    +    #MSearchResultsWindowClose:visited {
    +        color: #000000;
    +        text-decoration: none;
    +        }
    +    #MSearchResultsWindowClose:active,
    +    #MSearchResultsWindowClose:hover {
    +        color: #800000;
    +        text-decoration: none;
    +        background-color: #F4F4F4;
    +        }
    +
    +
    +
    +
    +#Content {
    +    padding-bottom: 15px;
    +    }
    +
    +.ContentPage #Content {
    +    border-width: 0 0 1px 1px;
    +    border-style: solid;
    +    border-color: #000000;
    +    background-color: #FFFFFF;
    +    font-size: 9pt;  /* To make 31ex match the menu's 31ex. */
    +    margin-left: 31ex;
    +    }
    +.ContentPage .Firefox #Content {
    +    margin-left: 27ex;
    +    }
    +
    +
    +
    +    .CTopic {
    +        font-size: 10pt;
    +        margin-bottom: 3em;
    +        }
    +
    +
    +    .CTitle {
    +        font-size: 12pt; font-weight: bold;
    +        border-width: 0 0 1px 0; border-style: solid; border-color: #A0A0A0;
    +        margin: 0 15px .5em 15px }
    +
    +    .CGroup .CTitle {
    +        font-size: 16pt; font-variant: small-caps;
    +        padding-left: 15px; padding-right: 15px;
    +        border-width: 0 0 2px 0; border-color: #000000;
    +        margin-left: 0; margin-right: 0 }
    +
    +    .CClass .CTitle,
    +    .CInterface .CTitle,
    +    .CDatabase .CTitle,
    +    .CDatabaseTable .CTitle,
    +    .CSection .CTitle {
    +        font-size: 18pt;
    +        color: #FFFFFF; background-color: #A0A0A0;
    +        padding: 10px 15px 10px 15px;
    +        border-width: 2px 0; border-color: #000000;
    +        margin-left: 0; margin-right: 0 }
    +
    +    #MainTopic .CTitle {
    +        font-size: 20pt;
    +        color: #FFFFFF; background-color: #7070C0;
    +        padding: 10px 15px 10px 15px;
    +        border-width: 0 0 3px 0; border-color: #000000;
    +        margin-left: 0; margin-right: 0 }
    +
    +    .CBody {
    +        margin-left: 15px; margin-right: 15px }
    +
    +
    +    .CToolTip {
    +        position: absolute; visibility: hidden;
    +        left: 0; top: 0;
    +        background-color: #FFFFE0;
    +        padding: 5px;
    +        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #000000;
    +        font-size: 8pt;
    +        }
    +
    +    .Opera .CToolTip {
    +        max-width: 98%;
    +        }
    +
    +    /*  Scrollbars would be useless.  */
    +    .CToolTip blockquote {
    +        overflow: hidden;
    +        }
    +    .IE6 .CToolTip blockquote {
    +        overflow: visible;
    +        }
    +
    +    .CHeading {
    +        font-weight: bold; font-size: 10pt;
    +        margin: 1.5em 0 .5em 0;
    +        }
    +
    +    .CBody pre {
    +        font: 10pt "Courier New", Courier, monospace;
    +	    background-color: #FCFCFC;
    +	    margin: 1em 35px;
    +	    padding: 10px 15px 10px 10px;
    +	    border-color: #E0E0E0 #E0E0E0 #E0E0E0 #E4E4E4;
    +	    border-width: 1px 1px 1px 6px;
    +	    border-style: dashed dashed dashed solid;
    +        }
    +
    +    .CBody ul {
    +        /*  I don't know why CBody's margin doesn't apply, but it's consistent across browsers so whatever.
    +             Reapply it here as padding.  */
    +        padding-left: 15px; padding-right: 15px;
    +        margin: .5em 5ex .5em 5ex;
    +        }
    +
    +    .CDescriptionList {
    +        margin: .5em 5ex 0 5ex }
    +
    +        .CDLEntry {
    +            font: 10pt "Courier New", Courier, monospace; color: #808080;
    +            padding-bottom: .25em;
    +            white-space: nowrap }
    +
    +        .CDLDescription {
    +            font-size: 10pt;  /*  For browsers that don't inherit correctly, like Opera 5.  */
    +            padding-bottom: .5em; padding-left: 5ex }
    +
    +
    +    .CTopic img {
    +        text-align: center;
    +        display: block;
    +        margin: 1em auto;
    +        }
    +    .CImageCaption {
    +        font-variant: small-caps;
    +        font-size: 8pt;
    +        color: #808080;
    +        text-align: center;
    +        position: relative;
    +        top: 1em;
    +        }
    +
    +    .CImageLink {
    +        color: #808080;
    +        font-style: italic;
    +        }
    +    a.CImageLink:link,
    +    a.CImageLink:visited,
    +    a.CImageLink:hover { color: #808080 }
    +
    +
    +
    +
    +
    +.Prototype {
    +    font: 10pt "Courier New", Courier, monospace;
    +    padding: 5px 3ex;
    +    border-width: 1px; border-style: solid;
    +    margin: 0 5ex 1.5em 5ex;
    +    }
    +
    +    .Prototype td {
    +        font-size: 10pt;
    +        }
    +
    +    .PDefaultValue,
    +    .PDefaultValuePrefix,
    +    .PTypePrefix {
    +        color: #8F8F8F;
    +        }
    +    .PTypePrefix {
    +        text-align: right;
    +        }
    +    .PAfterParameters {
    +        vertical-align: bottom;
    +        }
    +
    +    .IE .Prototype table {
    +        padding: 0;
    +        }
    +
    +    .CFunction .Prototype {
    +        background-color: #F4F4F4; border-color: #D0D0D0 }
    +    .CProperty .Prototype {
    +        background-color: #F4F4FF; border-color: #C0C0E8 }
    +    .CVariable .Prototype {
    +        background-color: #FFFFF0; border-color: #E0E0A0 }
    +
    +    .CClass .Prototype {
    +        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
    +        background-color: #F4F4F4;
    +        }
    +    .CInterface .Prototype {
    +        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0D0;
    +        background-color: #F4F4FF;
    +        }
    +
    +    .CDatabaseIndex .Prototype,
    +    .CConstant .Prototype {
    +        background-color: #D0D0D0; border-color: #000000 }
    +    .CType .Prototype,
    +    .CEnumeration .Prototype {
    +        background-color: #FAF0F0; border-color: #E0B0B0;
    +        }
    +    .CDatabaseTrigger .Prototype,
    +    .CEvent .Prototype,
    +    .CDelegate .Prototype {
    +        background-color: #F0FCF0; border-color: #B8E4B8 }
    +
    +    .CToolTip .Prototype {
    +        margin: 0 0 .5em 0;
    +        white-space: nowrap;
    +        }
    +
    +
    +
    +
    +
    +.Summary {
    +    margin: 1.5em 5ex 0 5ex }
    +
    +    .STitle {
    +        font-size: 12pt; font-weight: bold;
    +        margin-bottom: .5em }
    +
    +
    +    .SBorder {
    +        background-color: #FFFFF0;
    +        padding: 15px;
    +        border: 1px solid #C0C060 }
    +
    +    /* In a frame IE 6 will make them too long unless you set the width to 100%.  Without frames it will be correct without a width
    +        or slightly too long (but not enough to scroll) with a width.  This arbitrary weirdness simply astounds me.  IE 7 has the same
    +        problem with frames, haven't tested it without.  */
    +    .FramedContentPage .IE .SBorder {
    +        width: 100% }
    +
    +    /*  A treat for Mozilla users.  Blatantly non-standard.  Will be replaced with CSS 3 attributes when finalized/supported.  */
    +    .Firefox .SBorder {
    +        -moz-border-radius: 20px }
    +
    +
    +    .STable {
    +        font-size: 9pt; width: 100% }
    +
    +    .SEntry {
    +        width: 30% }
    +    .SDescription {
    +        width: 70% }
    +
    +
    +    .SMarked {
    +        background-color: #F8F8D8 }
    +
    +    .SDescription { padding-left: 2ex }
    +    .SIndent1 .SEntry { padding-left: 1.5ex }   .SIndent1 .SDescription { padding-left: 3.5ex }
    +    .SIndent2 .SEntry { padding-left: 3.0ex }   .SIndent2 .SDescription { padding-left: 5.0ex }
    +    .SIndent3 .SEntry { padding-left: 4.5ex }   .SIndent3 .SDescription { padding-left: 6.5ex }
    +    .SIndent4 .SEntry { padding-left: 6.0ex }   .SIndent4 .SDescription { padding-left: 8.0ex }
    +    .SIndent5 .SEntry { padding-left: 7.5ex }   .SIndent5 .SDescription { padding-left: 9.5ex }
    +
    +    .SDescription a { color: #800000}
    +    .SDescription a:active { color: #A00000 }
    +
    +    .SGroup td {
    +        padding-top: .5em; padding-bottom: .25em }
    +
    +    .SGroup .SEntry {
    +        font-weight: bold; font-variant: small-caps }
    +
    +    .SGroup .SEntry a { color: #800000 }
    +    .SGroup .SEntry a:active { color: #F00000 }
    +
    +
    +    .SMain td,
    +    .SClass td,
    +    .SDatabase td,
    +    .SDatabaseTable td,
    +    .SSection td {
    +        font-size: 10pt;
    +        padding-bottom: .25em }
    +
    +    .SClass td,
    +    .SDatabase td,
    +    .SDatabaseTable td,
    +    .SSection td {
    +        padding-top: 1em }
    +
    +    .SMain .SEntry,
    +    .SClass .SEntry,
    +    .SDatabase .SEntry,
    +    .SDatabaseTable .SEntry,
    +    .SSection .SEntry {
    +        font-weight: bold;
    +        }
    +
    +    .SMain .SEntry a,
    +    .SClass .SEntry a,
    +    .SDatabase .SEntry a,
    +    .SDatabaseTable .SEntry a,
    +    .SSection .SEntry a { color: #000000 }
    +
    +    .SMain .SEntry a:active,
    +    .SClass .SEntry a:active,
    +    .SDatabase .SEntry a:active,
    +    .SDatabaseTable .SEntry a:active,
    +    .SSection .SEntry a:active { color: #A00000 }
    +
    +
    +
    +
    +
    +.ClassHierarchy {
    +    margin: 0 15px 1em 15px }
    +
    +    .CHEntry {
    +        border-width: 1px 2px 2px 1px; border-style: solid; border-color: #A0A0A0;
    +        margin-bottom: 3px;
    +        padding: 2px 2ex;
    +        font-size: 10pt;
    +        background-color: #F4F4F4; color: #606060;
    +        }
    +
    +    .Firefox .CHEntry {
    +        -moz-border-radius: 4px;
    +        }
    +
    +    .CHCurrent .CHEntry {
    +        font-weight: bold;
    +        border-color: #000000;
    +        color: #000000;
    +        }
    +
    +    .CHChildNote .CHEntry {
    +        font-style: italic;
    +        font-size: 8pt;
    +        }
    +
    +    .CHIndent {
    +        margin-left: 3ex;
    +        }
    +
    +    .CHEntry a:link,
    +    .CHEntry a:visited,
    +    .CHEntry a:hover {
    +        color: #606060;
    +        }
    +    .CHEntry a:active {
    +        color: #800000;
    +        }
    +
    +
    +
    +
    +
    +#Index {
    +    background-color: #FFFFFF;
    +    }
    +
    +/*  As opposed to .PopupSearchResultsPage #Index  */
    +.IndexPage #Index,
    +.FramedIndexPage #Index,
    +.FramedSearchResultsPage #Index {
    +    padding: 15px;
    +    }
    +
    +.IndexPage #Index {
    +    border-width: 0 0 1px 1px;
    +    border-style: solid;
    +    border-color: #000000;
    +    font-size: 9pt;  /* To make 27ex match the menu's 27ex. */
    +    margin-left: 27ex;
    +    }
    +
    +
    +    .IPageTitle {
    +        font-size: 20pt; font-weight: bold;
    +        color: #FFFFFF; background-color: #7070C0;
    +        padding: 10px 15px 10px 15px;
    +        border-width: 0 0 3px 0; border-color: #000000; border-style: solid;
    +        margin: -15px -15px 0 -15px }
    +
    +    .FramedSearchResultsPage .IPageTitle {
    +        margin-bottom: 15px;
    +        }
    +
    +    .INavigationBar {
    +        font-size: 10pt;
    +        text-align: center;
    +        background-color: #FFFFF0;
    +        padding: 5px;
    +        border-bottom: solid 1px black;
    +        margin: 0 -15px 15px -15px;
    +        }
    +
    +    .INavigationBar a {
    +        font-weight: bold }
    +
    +    .IHeading {
    +        font-size: 16pt; font-weight: bold;
    +        padding: 2.5em 0 .5em 0;
    +        text-align: center;
    +        width: 3.5ex;
    +        }
    +    #IFirstHeading {
    +        padding-top: 0;
    +        }
    +
    +    .IEntry {
    +        font-size: 10pt;
    +        padding-left: 1ex;
    +        }
    +    .PopupSearchResultsPage .IEntry {
    +        font-size: 8pt;
    +        padding: 1px 5px;
    +        }
    +    .PopupSearchResultsPage .Opera9 .IEntry,
    +    .FramedSearchResultsPage .Opera9 .IEntry {
    +        text-align: left;
    +        }
    +    .FramedSearchResultsPage .IEntry {
    +        padding: 0;
    +        }
    +
    +    .ISubIndex {
    +        padding-left: 3ex; padding-bottom: .5em }
    +    .PopupSearchResultsPage .ISubIndex {
    +        display: none;
    +        }
    +
    +    /*  While it may cause some entries to look like links when they aren't, I found it's much easier to read the
    +         index if everything's the same color.  */
    +    .ISymbol {
    +        font-weight: bold; color: #900000  }
    +
    +    .IndexPage .ISymbolPrefix,
    +    .FramedIndexPage .ISymbolPrefix {
    +        font-size: 10pt;
    +        text-align: right;
    +        color: #C47C7C;
    +        background-color: #F8F8F8;
    +        border-right: 3px solid #E0E0E0;
    +        border-left: 1px solid #E0E0E0;
    +        padding: 0 1px 0 2px;
    +        }
    +    .PopupSearchResultsPage .ISymbolPrefix,
    +    .FramedSearchResultsPage .ISymbolPrefix {
    +        color: #900000;
    +        }
    +    .PopupSearchResultsPage .ISymbolPrefix {
    +        font-size: 8pt;
    +        }
    +
    +    .IndexPage #IFirstSymbolPrefix,
    +    .FramedIndexPage #IFirstSymbolPrefix {
    +        border-top: 1px solid #E0E0E0;
    +        }
    +    .IndexPage #ILastSymbolPrefix,
    +    .FramedIndexPage #ILastSymbolPrefix {
    +        border-bottom: 1px solid #E0E0E0;
    +        }
    +    .IndexPage #IOnlySymbolPrefix,
    +    .FramedIndexPage #IOnlySymbolPrefix {
    +        border-top: 1px solid #E0E0E0;
    +        border-bottom: 1px solid #E0E0E0;
    +        }
    +
    +    a.IParent,
    +    a.IFile {
    +        display: block;
    +        }
    +
    +    .PopupSearchResultsPage .SRStatus {
    +        padding: 2px 5px;
    +        font-size: 8pt;
    +        font-style: italic;
    +        }
    +    .FramedSearchResultsPage .SRStatus {
    +        font-size: 10pt;
    +        font-style: italic;
    +        }
    +
    +    .SRResult {
    +        display: none;
    +        }
    +
    +
    +
    +#Footer {
    +    font-size: 8pt;
    +    color: #989898;
    +    text-align: right;
    +    }
    +
    +#Footer p {
    +    text-indent: 0;
    +    margin-bottom: .5em;
    +    }
    +
    +.ContentPage #Footer,
    +.IndexPage #Footer {
    +    text-align: right;
    +    margin: 2px;
    +    }
    +
    +.FramedMenuPage #Footer {
    +    text-align: center;
    +    margin: 5em 10px 10px 10px;
    +    padding-top: 1em;
    +    border-top: 1px solid #C8C8C8;
    +    }
    +
    +    #Footer a:link,
    +    #Footer a:hover,
    +    #Footer a:visited { color: #989898 }
    +    #Footer a:active { color: #A00000 }
    +
    +
    +
    +.prettyprint .kwd { color: #800000; }  /* keywords */
    +
    +    .prettyprint.PDefaultValue .kwd,
    +    .prettyprint.PDefaultValuePrefix .kwd,
    +    .prettyprint.PTypePrefix .kwd {
    +        color: #C88F8F;
    +        }
    +
    +.prettyprint .com { color: #008000; }  /* comments */
    +
    +    .prettyprint.PDefaultValue .com,
    +    .prettyprint.PDefaultValuePrefix .com,
    +    .prettyprint.PTypePrefix .com {
    +        color: #8FC88F;
    +        }
    +
    +.prettyprint .str { color: #0000B0; }  /* strings */
    +.prettyprint .lit { color: #0000B0; }  /* literals */
    +
    +    .prettyprint.PDefaultValue .str,
    +    .prettyprint.PDefaultValuePrefix .str,
    +    .prettyprint.PTypePrefix .str,
    +    .prettyprint.PDefaultValue .lit,
    +    .prettyprint.PDefaultValuePrefix .lit,
    +    .prettyprint.PTypePrefix .lit {
    +        color: #8F8FC0;
    +        }
    +
    +.prettyprint .typ { color: #000000; }  /* types */
    +.prettyprint .pun { color: #000000; }  /* punctuation */
    +.prettyprint .pln { color: #000000; }  /* punctuation */
    +
    +    .prettyprint.PDefaultValue .typ,
    +    .prettyprint.PDefaultValuePrefix .typ,
    +    .prettyprint.PTypePrefix .typ,
    +    .prettyprint.PDefaultValue .pun,
    +    .prettyprint.PDefaultValuePrefix .pun,
    +    .prettyprint.PTypePrefix .pun,
    +    .prettyprint.PDefaultValue .pln,
    +    .prettyprint.PDefaultValuePrefix .pln,
    +    .prettyprint.PTypePrefix .pln {
    +        color: #8F8F8F;
    +        }
    +
    +.prettyprint .tag { color: #008; }
    +.prettyprint .atn { color: #606; }
    +.prettyprint .atv { color: #080; }
    +.prettyprint .dec { color: #606; }
    +
    diff --git a/x/ocap/addons/extension/fnc_sendData.sqf b/x/ocap/addons/extension/fnc_sendData.sqf
    index dc0d8b9..bb096b7 100644
    --- a/x/ocap/addons/extension/fnc_sendData.sqf
    +++ b/x/ocap/addons/extension/fnc_sendData.sqf
    @@ -1,5 +1,7 @@
     /* ----------------------------------------------------------------------------
    -Script: EFUNC(extension,sendData)
    +FILE: fnc_sendData.sqf
    +
    +FUNCTION: OCAP_extension_fnc_sendData
     
     Description:
     	Manages raw extension calls and returns values / logs errors where relevant.
    @@ -12,9 +14,7 @@ Returns:
     	Depends
     
     Examples:
    -	--- Code
    -	[":VERSION", []] call EFUNC(extension,sendData);
    -	---
    +	> [":VERSION", []] call EFUNC(extension,sendData);
     
     Public:
     	No
    diff --git a/x/ocap/addons/extension/script_component.hpp b/x/ocap/addons/extension/script_component.hpp
    index 755d233..648d608 100644
    --- a/x/ocap/addons/extension/script_component.hpp
    +++ b/x/ocap/addons/extension/script_component.hpp
    @@ -1,4 +1,8 @@
    +// HEADER: script_component.hpp
    +
    +// DEFINE: COMPONENT
     #define COMPONENT extension
    +// DEFINE: COMPONENT_BEAUTIFIED
     #define COMPONENT_BEAUTIFIED Extension
     
     #include "\x\ocap\addons\main\script_macros.hpp"
    diff --git a/x/ocap/addons/main/XEH_preInit.sqf b/x/ocap/addons/main/XEH_preInit.sqf
    index 4655732..95eccbe 100644
    --- a/x/ocap/addons/main/XEH_preInit.sqf
    +++ b/x/ocap/addons/main/XEH_preInit.sqf
    @@ -1,3 +1,5 @@
    +// FILE: CBA Settings
    +
     #include "script_component.hpp"
     #include "XEH_prep.sqf"
     
    @@ -5,7 +7,19 @@
     // This PreInit creates the settings on the server, only so that the global vars will be registered and synchronized with clients.
     
     GVAR(allSettings) = [
    -  // Core
    +  // Section: Core
    +
    +  /*
    +    CBA Setting: OCAP_enabled
    +    Description:
    +      Turns on or off most recording functionality. Will not reset anything from existing session, will just stop recording most new data. Note: For record/pause switching, use the CBA events! Default: true
    +
    +    Setting Name:
    +      Recording Enabled
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QGVARMAIN(enabled),
         "CHECKBOX", // setting type
    @@ -20,6 +34,17 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_isDebug
    +    Description:
    +      Enables increased logging of addon actions. Default: false
    +
    +    Setting Name:
    +      Debug Mode
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QGVARMAIN(isDebug),
         "CHECKBOX", // setting type
    @@ -34,6 +59,20 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_administratorList
    +    Description:
    +      An array or server-visible variable referencing one that is a list of playerUIDs. Additional briefing diary or UI elements may be available for more accessible control over OCAP's features. Takes effect on player server connection. Format: [] OR myAdminPUIDs | Default: []
    +
    +    Setting Name:
    +      Administrators
    +
    +    Value Type:
    +      Stringified Array
    +
    +    Example:
    +      > "['76561198000000000', '76561198000000001']"
    +  */
       [
         QGVARMAIN(administratorList),
         "EDITBOX", // setting type
    diff --git a/x/ocap/addons/main/script_component.hpp b/x/ocap/addons/main/script_component.hpp
    index 9f444aa..a277cac 100644
    --- a/x/ocap/addons/main/script_component.hpp
    +++ b/x/ocap/addons/main/script_component.hpp
    @@ -1,4 +1,8 @@
    +// HEADER: script_component.hpp
    +
    +// DEFINE: COMPONENT
     #define COMPONENT main
    +// DEFINE: COMPONENT_BEAUTIFIED
     #define COMPONENT_BEAUTIFIED Main
     
     #include "script_macros.hpp"
    diff --git a/x/ocap/addons/main/script_macros.hpp b/x/ocap/addons/main/script_macros.hpp
    index e8c743a..75bbdc8 100644
    --- a/x/ocap/addons/main/script_macros.hpp
    +++ b/x/ocap/addons/main/script_macros.hpp
    @@ -1,36 +1,54 @@
    -// Header: script_macros.hpp
    +// HEADER: script_macros.hpp
     // Defines macros imported to other functions
     
    +// DEFINE: PREFIX
     #define PREFIX OCAP
     
     #ifdef COMPONENT_BEAUTIFIED
    -    #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT_BEAUTIFIED)
    +  // DEFINE: COMPONENT_NAME
    +  #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT_BEAUTIFIED)
     #else
    -    #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT)
    +  #define COMPONENT_NAME QUOTE(PREFIX - COMPONENT)
     #endif
     
    +// DEFINE: ADDON
    +// <PREFIX>_<COMPONENT>
    +
     // The current version of OCAP.
    +
    +// DEFINE: VERSION
     #define VERSION 2.0
    +
    +// DEFINE: VERSION_STR
     #define VERSION_STR 2.0.0
    +
    +// DEFINE: VERSION_AR
     #define VERSION_AR 2,0,0
    +
    +// DEFINE: VERSION_REQUIRED
     #define VERSION_REQUIRED 2.10
     
    -// define: LOG
    -// Used for logging messages via the extension.
    +// MACRO: LOG
    +// Used for logging messages to the extension (ocap-ext log file).
     #define OCAPEXTLOG(_args) [":LOG:", _args] call EFUNC(extension,sendData)
    +
    +// MACRO: SYSCHAT
    +// Used for debug purposes to send a string to all clients with interfaces.
     #define SYSCHAT remoteExec ["systemChat", [0, -2] select isDedicated]
     
    +// MACRO: SHOULDSAVEEVENTS
    +// Used to determine if events should currently be saved based on <OCAP_recorder_recording> and <OCAP_recorder_startTime>.
     #define SHOULDSAVEEVENTS ((missionNamespace getVariable [QGVAR(recording), false]) && missionNamespace getVariable [QGVAR(startTime), -1] > -1)
     
    -// #define DEBUG_MODE_NORMAL
    -#define DEBUG_MODE_FULL
    +#define DEBUG_MODE_NORMAL
    +// #define DEBUG_MODE_FULL
     
    -// define: BOOL
    +// DEFINE: BOOL
     // Forces a true/false return of input.
     #define BOOL(_cond) ([0,1] select (_cond))
     
    -// define: ARR2
    -// Resolves to array, used for entry to <LOG> that requires array input.
    +// DEFINE: ARR2
    +// Resolves arguments to array, used for entries to <LOG> that requires array input.
     #define ARR2(_arg1, _arg2) [_arg1, _arg2]
     #define ARR3(_arg1, _arg2, _arg3) [_arg1, _arg2, _arg3]
     #define ARR4(_arg1, _arg2, _arg3, _arg4) [_arg1, _arg2, _arg3, _arg4]
    diff --git a/x/ocap/addons/recorder/XEH_preInit.sqf b/x/ocap/addons/recorder/XEH_preInit.sqf
    index 6feb5de..78a6e31 100644
    --- a/x/ocap/addons/recorder/XEH_preInit.sqf
    +++ b/x/ocap/addons/recorder/XEH_preInit.sqf
    @@ -1,8 +1,22 @@
    +// FILE: CBA Settings
    +
     #include "script_component.hpp"
     #include "XEH_prep.sqf"
     
     GVAR(allSettings) = [
    -  // AUTO START SETTINGS
    +  // Section: Auto-Start Settings
    +
    +  /*
    +    CBA Setting: OCAP_settings_autoStart
    +    Description:
    +      Automatically start OCAP recordings at session start. Default: true
    +
    +    Setting Name:
    +      Auto Start Recording
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,autoStart),
         "CHECKBOX", // setting type
    @@ -17,6 +31,17 @@ GVAR(allSettings) = [
         true // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_minPlayerCount
    +    Description:
    +      Auto-start will begin once this player count is reached. Default: 15
    +
    +    Setting Name:
    +      Minimum Player Count
    +
    +    Value Type:
    +      Number
    +  */
       [
         QEGVAR(settings,minPlayerCount),
         "SLIDER", // setting type
    @@ -38,7 +63,19 @@ GVAR(allSettings) = [
       ],
     
     
    -  // CORE
    +  // Section: Core
    +
    +  /*
    +    CBA Setting: OCAP_settings_frameCaptureDelay
    +    Description:
    +      Positioning, medical status, and crew states of units and vehicles will be captured every X amount of seconds. Default: 1
    +
    +    Setting Name:
    +      Frame Capture Delay
    +
    +    Value Type:
    +      Number
    +  */
       [
         QEGVAR(settings,frameCaptureDelay),
         "SLIDER", // setting type
    @@ -59,6 +96,17 @@ GVAR(allSettings) = [
         true // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_preferACEUnconscious
    +    Description:
    +      If true, will check ACE3 medical status on units. If false, or ACE3 isn't loaded, fall back to vanilla. Default: true
    +
    +    Setting Name:
    +      Use ACE3 Medical
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,preferACEUnconscious),
         "CHECKBOX", // setting type
    @@ -77,7 +125,22 @@ GVAR(allSettings) = [
     
     
     
    -  // EXCLUSIONS
    +  // Section: Exclusions
    +
    +  /*
    +    CBA Setting: OCAP_settings_excludeClassFromRecord
    +    Description:
    +      Array of object classnames that should be excluded from recording. Use single quotes! Default: ['ACE_friesAnchorBar', 'WeaponHolderSimulated']
    +
    +    Setting Name:
    +      Classnames to Exclude
    +
    +    Value Type:
    +      Stringified Array
    +
    +    Example:
    +      > "['ACE_friesAnchorBar']"
    +  */
       [
         QEGVAR(settings,excludeClassFromRecord),
         "EDITBOX", // setting type
    @@ -92,6 +155,20 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_excludeKindFromRecord
    +    Description:
    +      Array of classnames which, along with all child classes, should be excluded from recording. Use single quotes! Default: []
    +
    +    Setting Name:
    +      Object KindOfs to Exclude
    +
    +    Value Type:
    +      Stringified Array
    +
    +    Example:
    +      > "['WeaponHolder']"
    +  */
       [
         QEGVAR(settings,excludeKindFromRecord),
         "EDITBOX", // setting type
    @@ -106,12 +183,26 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_excludeMarkerFromRecord
    +    Description:
    +      Array of prefixes. Any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_','ACE_BFT_']
    +
    +    Setting Name:
    +      Marker Prefixes To Exclude
    +
    +    Value Type:
    +      Stringified Array
    +
    +    Example:
    +      > "['SystemMarker_','ACE_BFT_']"
    +  */
       [
         QEGVAR(settings,excludeMarkerFromRecord),
         "EDITBOX", // setting type
         [
           "Marker Prefixes to Exclude", // Pretty name shown inside the ingame settings menu. Can be stringtable entry.
    -      "Array of prefixes - any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_','ACE_BFT_']"
    +      "Array of prefixes. Any markers matching these prefixes will be excluded from recording. Use single quotes! Default: ['SystemMarker_','ACE_BFT_']"
         ],
         [COMPONENT_NAME, "Exclusions"], // Pretty name of the category where the setting can be found. Can be stringtable entry.
         "['SystemMarker_','ACE_BFT_']", // default string value
    @@ -123,7 +214,19 @@ GVAR(allSettings) = [
     
     
     
    -  // TRACKING EXTRA
    +  // Section: Extra Tracking
    +
    +  /*
    +    CBA Setting: OCAP_settings_trackTickets
    +    Description:
    +      Will track respawn ticket counts for missionNamespace and each playable faction every 30th frame. Default: true
    +
    +    Setting Name:
    +      Enable Ticket Tracking
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,trackTickets),
         "CHECKBOX", // setting type
    @@ -138,6 +241,17 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_trackTimes
    +    Description:
    +      Will continuously track in-game world time during a mission. Useful for accelerated/skipped time scenarios. Default: false
    +
    +    Setting Name:
    +      Enable Mission Time Tracking
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,trackTimes),
         "CHECKBOX", // setting type
    @@ -152,6 +266,17 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_trackTimeInterval
    +    Description:
    +      If <OCAP_settings_trackTimes> is enabled, it will be checked every X capture frames. Default: 10
    +
    +    Setting Name:
    +      Mission Time Tracking Interval
    +
    +    Value Type:
    +      Number
    +  */
       [
         QEGVAR(settings,trackTimeInterval),
         "SLIDER", // setting type
    @@ -174,7 +299,19 @@ GVAR(allSettings) = [
     
     
     
    -  // SAVING SETTINGS
    +  // Section: Save/Export Settings
    +
    +  /*
    +    CBA Setting: OCAP_settings_saveTag
    +    Description:
    +      If not overriden by the <OCAP_exportData> CBA event or if a mission is auto-saved, this will be used to categorize and filter the recording in the database and web list of missions.
    +
    +    Setting Name:
    +      Mission Type Tag
    +
    +    Value Type:
    +      String
    +  */
       [
         QEGVAR(settings,saveTag),
         "EDITBOX", // setting type
    @@ -189,6 +326,17 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_saveMissionEnded
    +    Description:
    +      If true, automatically save and export the mission when the MPEnded event fires. Default: true
    +
    +    Setting Name:
    +      Auto-save on MPEnded Event
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,saveMissionEnded),
         "CHECKBOX", // setting type
    @@ -203,6 +351,17 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_saveOnEmpty
    +    Description:
    +      Will automatically save recording when there are 0 players on the server and existing data accounts for more time than the minimum save duration setting. Default: true
    +
    +    Setting Name:
    +      Auto-Save When No Players
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,saveOnEmpty),
         "CHECKBOX", // setting type
    @@ -217,6 +376,17 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_settings_minMissionTime
    +    Description:
    +      A recording must be at least this long (in minutes) to auto-save. Calling an <OCAP_exportData> CBA server event will override this restriction. Default: 20
    +
    +    Setting Name:
    +      Required Duration to Sav
    +
    +    Value Type:
    +      Boolean
    +  */
       [
         QEGVAR(settings,minMissionTime),
         "SLIDER", // setting type
    diff --git a/x/ocap/addons/recorder/fnc_aceExplosives.sqf b/x/ocap/addons/recorder/fnc_aceExplosives.sqf
    index 1990e4b..1d2d500 100644
    --- a/x/ocap/addons/recorder/fnc_aceExplosives.sqf
    +++ b/x/ocap/addons/recorder/fnc_aceExplosives.sqf
    @@ -1,11 +1,13 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(aceExplosives)
    +FILE: fnc_aceExplosives.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_aceExplosives
     
     Description:
    -  Listener for ACE3 global event indicating an armed mine has been placed.
    -  Adds marker on its position to the recording, not in-game.
    -  Then waits until the explosive is null (exploded) and indicates it with a temporary new marker in the recording.
    +  Adds marker on the mine's position to the recording timeline.
    +  Then waits until the explosive is null (exploded) and indicates it with a 10-frame long red X before removing the marker.
     
    +  Called by <ace_explosives_place> CBA listener.
     
     Parameters:
       None
    @@ -14,13 +16,11 @@ Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  call FUNC(aceExplosives);
    -  ---
    +  > call FUNC(aceExplosives);
     
     Notes:
    -  ACE3 call
    -  [QGVAR(place), [_explosive, _dir, _pitch, _unit]] call CBA_fnc_globalEvent;
    +  Example of emitting event from ACE3 code:
    +  > [QGVAR(place), [_explosive, _dir, _pitch, _unit]] call CBA_fnc_globalEvent;
     
     Public:
       No
    @@ -30,81 +30,83 @@ Author:
     ---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
    -EGVAR(listener,aceExplosives) = ["ace_explosives_place", {
    +if (!SHOULDSAVEEVENTS) exitWith {};
     
    -  if (!SHOULDSAVEEVENTS) exitWith {};
    +params ["_explosive", "_dir", "_pitch", "_unit"];
     
    -  params ["_explosive", "_dir", "_pitch", "_unit"];
    +private _int = random(2000);
     
    -  private _int = random(2000);
    +_explType = typeOf _explosive;
    +_explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine");
    +_explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName");
    +_explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture");
     
    -  _explType = typeOf _explosive;
    -  _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine");
    -  _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName");
    -  _explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture");
    +_placedPos = getPosASL _explosive;
    +_unit addOwnedMine _explosive;
     
    -  _placedPos = getPosASL _explosive;
    -  _unit addOwnedMine _explosive;
    +_markTextLocal = format["%1", _explosiveDisp];
    +_markName = format["%1#%2/%3", QGVARMAIN(mine), _int, _placedPos];
    +_markColor = "ColorRed";
    +_markerType = "Minefield";
     
    -  _markTextLocal = format["%1", _explosiveDisp];
    -  _markName = format["%1#%2/%3", QGVARMAIN(mine), _int, _placedPos];
    -  _markColor = "ColorRed";
    -  _markerType = "Minefield";
     
    -  [QGVARMAIN(handleMarker), [
    -    "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true
    -  ]] call CBA_fnc_localEvent;
    +// Signals creation of a Minefield (triangle) marker on the timeline at the location the explosive was armed.
    +[QGVARMAIN(handleMarker), [
    +  "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true
    +]] call CBA_fnc_localEvent;
     
    -  if (GVARMAIN(isDebug)) then {
    -    // add to map draw array
    -    private _debugArr = [_explosive, _explosivePic, format["%1 %2 - %3", str side group _unit, name _unit, _markTextLocal], [side group _unit] call BIS_fnc_sideColor];
    -    GVAR(liveDebugMagIcons) pushBack _debugArr;
    -    publicVariable QGVAR(liveDebugMagIcons);
    -  };
    +if (GVARMAIN(isDebug)) then {
    +  // add to map draw array
    +  private _debugArr = [_explosive, _explosivePic, format["%1 %2 - %3", str side group _unit, name _unit, _markTextLocal], [side group _unit] call BIS_fnc_sideColor];
    +  GVAR(liveDebugMagIcons) pushBack _debugArr;
    +  publicVariable QGVAR(liveDebugMagIcons);
    +};
     
     
    -  [{isNull (_this#0)}, { // wait until the mine is null (exploded), and mark this for playback
    +[{isNull (_this#0)}, { // wait until the mine is null (exploded), and mark this for playback
     
    -    params ["_explosive", "_explosiveDisp", "_unit", "_placedPos", "_markName", "_int"];
    +  params ["_explosive", "_explosiveDisp", "_unit", "_placedPos", "_markName", "_int"];
     
    -    // set unit who placed's lastFired var as the explosive so kills are registered to the explosive
    -    _unit setVariable [
    -      QGVARMAIN(lastFired),
    -      _explosiveDisp
    -    ];
    +  // set unit who placed's lastFired var as the explosive so kills are registered to the explosive
    +  _unit setVariable [
    +    QGVARMAIN(lastFired),
    +    _explosiveDisp
    +  ];
     
    -    // remove previous marker
    -    if (GVARMAIN(isDebug)) then {
    -      format["Removed explosive placed marker, %1, %2", _markName, _explosiveDisp] SYSCHAT;
    -      OCAPEXTLOG(ARR3("Removed explosive placed marker", _markName, _explosiveDisp));
    -    };
    +  // remove previous marker
    +  if (GVARMAIN(isDebug)) then {
    +    format["Removed explosive placed marker, %1, %2", _markName, _explosiveDisp] SYSCHAT;
    +    OCAPEXTLOG(ARR3("Removed explosive placed marker", _markName, _explosiveDisp));
    +  };
     
    -    [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent;
     
    -    _markTextLocal = format["%1", _explosiveDisp];
    -    _markName = format["Detonation#%1", _int];
    -    _markColor = "ColorRed";
    -    _markerType = "waypoint";
    +  // Signals removal of the Minefield (triangle) marker when the explosive is null (exploded).
    +  [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent;
     
    -    if (GVARMAIN(isDebug)) then {
    -      format["Created explosive explosion marker, %1, %2", _markName, _explosiveDisp] SYSCHAT;
    -      OCAPEXTLOG(ARR3("Created explosive explosion marker", _markName, _explosiveDisp));
    -    };
    +  _markTextLocal = format["%1", _explosiveDisp];
    +  _markName = format["Detonation#%1", _int];
    +  _markColor = "ColorRed";
    +  _markerType = "waypoint";
     
    -    [QGVARMAIN(handleMarker), [
    -      "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true
    -    ]] call CBA_fnc_localEvent;
    +  if (GVARMAIN(isDebug)) then {
    +    format["Created explosive explosion marker, %1, %2", _markName, _explosiveDisp] SYSCHAT;
    +    OCAPEXTLOG(ARR3("Created explosive explosion marker", _markName, _explosiveDisp));
    +  };
     
     
    -    [{
    -      params ["_markName", "_explosiveDisp"];
    -      if (GVARMAIN(isDebug)) then {
    -        format["Removed explosive explosion marker, %1, %2", _markName, _explosiveDisp] SYSCHAT;
    -        OCAPEXTLOG(ARR3("Removed explosive explosion marker", _markName, _explosiveDisp));
    -      };
    -      [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent;
    -    }, [_markName, _explosiveDisp], 10] call CBA_fnc_waitAndExecute;
    +  // Signals creation of a Waypoint (X) marker on the timeline at the location the explosive detonated.
    +  [QGVARMAIN(handleMarker), [
    +    "CREATED", _markName, _unit, _placedPos, _markerType, "ICON", [1,1], 0, "Solid", "ColorRed", 1, _markTextLocal, true
    +  ]] call CBA_fnc_localEvent;
    +
     
    -  }, [_explosive, _explosiveDisp, _unit, _placedPos, _markName, _int]] call CBA_fnc_waitUntilAndExecute;
    +  [{
    +    params ["_markName", "_explosiveDisp"];
    +    if (GVARMAIN(isDebug)) then {
    +      format["Removed explosive explosion marker, %1, %2", _markName, _explosiveDisp] SYSCHAT;
    +      OCAPEXTLOG(ARR3("Removed explosive explosion marker", _markName, _explosiveDisp));
    +    };
    +    [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent;
    +  }, [_markName, _explosiveDisp], GVAR(captureFrameNo) * 10] call CBA_fnc_waitAndExecute;
     
    -}] call CBA_fnc_addEventHandler;
    +}, [_explosive, _explosiveDisp, _unit, _placedPos, _markName, _int]] call CBA_fnc_waitUntilAndExecute;
    diff --git a/x/ocap/addons/recorder/fnc_aceThrowing.sqf b/x/ocap/addons/recorder/fnc_aceThrowing.sqf
    deleted file mode 100644
    index bd321d2..0000000
    --- a/x/ocap/addons/recorder/fnc_aceThrowing.sqf
    +++ /dev/null
    @@ -1,129 +0,0 @@
    -/* ----------------------------------------------------------------------------
    -Script: FUNC(aceThrowing)
    -
    -Description:
    -  Adds a local CBA event listener on units that will trigger when a projectile is thrown using ACE Advanced Throwing and add markers to playback that trace its path. Added to units in <FUNC(addUnitEventHandlers)>.
    -
    -Parameters:
    -  None
    -
    -Returns:
    -  Nothing
    -
    -Examples:
    -  --- Code
    -  FUNC(aceThrowing) remoteExec ["call", _entity];
    -  ---
    -
    -Public:
    -  No
    -
    -Author:
    -  IndigoFox
    ----------------------------------------------------------------------------- */
    -#include "script_component.hpp"
    -
    -EGVAR(listener,aceThrowing) = ["ace_throwableThrown", {
    -
    -  if (!SHOULDSAVEEVENTS) exitWith {};
    -
    -  params["_unit", "_projectile"];
    -
    -  if (isNull _projectile) then {
    -    _projectile = nearestObject [_unit, "CA_Magazine"];
    -  };
    -
    -  // systemChat str _this;
    -
    -  // note that thrown objects outside of ACE explosives do not include a "default magazine" property in their config.
    -  // this script will attempt to find a matching classname in CfgMagazines, as some chemlights and smokes are built this way.
    -  // if not found, a default magazine value will be assigned (m67 frag, white smoke, green chemlight)
    -
    -  _projType = typeOf _projectile;
    -  _projConfig = configOf _projectile;
    -  _projName = getText(configFile >> "CfgAmmo" >> _projType >> "displayName");
    -
    -  // systemChat format["Config name: %1", configOf _projectile];
    -
    -  _ammoSimType = getText(configFile >> "CfgAmmo" >> _projType >> "simulation");
    -  // systemChat format["Projectile type: %1", _ammoSimType];
    -
    -  _markerType = "";
    -  _markColor = "";
    -  _magDisp = "";
    -  _magPic = "";
    -
    -  _magType = getText(_projConfig >> "defaultMagazine");
    -  if (_magType == "") then {
    -    _magType = configName(configfile >> "CfgMagazines" >> _projType)
    -  };
    -
    -  if (!(_magType isEqualTo "")) then {
    -    // systemChat format["Mag type: %1", _magType];
    -
    -    _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayNameShort");
    -    if (_magDisp == "") then {
    -      _magDisp = getText(configFile >> "CfgMagazines" >> _magType >> "displayName")
    -    };
    -    if (_magDisp == "") then {
    -      _magDisp = _projName;
    -    };
    -
    -    _magPic = (getText(configfile >> "CfgMagazines" >> _magType >> "picture"));
    -    // hint parseText format["Projectile fired:<br/><img image='%1'/>", _magPic];
    -    if (_magPic == "") then {
    -      _markerType = "mil_triangle";
    -      _markColor = "ColorRed";
    -    } else {
    -      _magPicSplit = _magPic splitString "\";
    -      _magPic = _magPicSplit#((count _magPicSplit) - 1);
    -      _markerType = format["magIcons/%1", _magPic];
    -      _markColor = "ColorWhite";
    -    };
    -  } else {
    -    _markerType = "mil_triangle";
    -    _markColor = "ColorRed";
    -    // set defaults based on ammo sim type, if no magazine could be matched
    -    switch (_ammoSimType) do {
    -      case "shotGrenade":{
    -          _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa";
    -          _magDisp = "Frag";
    -        };
    -      case "shotSmokeX":{
    -          _magPic = "\A3\Weapons_f\data\ui\gear_smokegrenade_white_ca.paa";
    -          _magDisp = "Smoke";
    -        };
    -      case "shotIlluminating":{
    -          _magPic = "\A3\Weapons_F\Data\UI\gear_flare_white_ca.paa";
    -          _magDisp = "Flare";
    -        };
    -      default {
    -        _magPic = "\A3\Weapons_F\Data\UI\gear_M67_CA.paa";
    -        _magDisp = "Frag";
    -      };
    -    };
    -    // hint parseText format["Projectile fired:<br/><img image='%1'/>", _magPic];
    -    _magPicSplit = _magPic splitString "\";
    -    _magPic = _magPicSplit#((count _magPicSplit) - 1);
    -    _markerType = format["magIcons/%1", _magPic];
    -    _markColor = "ColorWhite";
    -  };
    -
    -  _int = random 2000;
    -
    -  _markTextLocal = format["%1", _magDisp];
    -  _markName = format["Projectile#%1", _int];
    -
    -  // MAKE MARKER FOR PLAYBACK
    -  _throwerPos = getPosASL _unit;
    -  [QGVARMAIN(handleMarker), ["CREATED", _markName, _unit, _throwerPos, _markerType, "ICON", [1,1], 0, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_serverEvent;
    -
    -  GVAR(liveGrenades) pushBack [_projectile, _magazine, _unit, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType];
    -
    -  if (GVARMAIN(isDebug)) then {
    -    // add to map draw array
    -    private _debugArr = [_projectile, _magPic, format["%1 %2 - %3", str side group _unit, name _unit, _markTextLocal], [side group _unit] call BIS_fnc_sideColor];
    -    GVAR(liveDebugMagIcons) pushBack _debugArr;
    -    publicVariable QGVAR(liveDebugMagIcons);
    -  };
    -}] call CBA_fnc_addEventHandler;
    diff --git a/x/ocap/addons/recorder/fnc_addEventMission.sqf b/x/ocap/addons/recorder/fnc_addEventMission.sqf
    index 65ea5d5..e245dac 100644
    --- a/x/ocap/addons/recorder/fnc_addEventMission.sqf
    +++ b/x/ocap/addons/recorder/fnc_addEventMission.sqf
    @@ -1,10 +1,10 @@
     /* ----------------------------------------------------------------------------
    -Script: ocap_fnc_addEventMission
    +FILE: fnc_addEventMission.sqf
     
    -Description:
    -  Used for applying mission event handlers.
    +FUNCTION: OCAP_recorder_fnc_addEventMission
     
    -  * Applied during initialization of OCAP in <ocap_fnc_init>.
    +Description:
    +  Used for applying mission event handlers. Applied during initialization of OCAP in <OCAP_recorder_fnc_init>.
     
     Parameters:
       None
    @@ -13,9 +13,7 @@ Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  call ocap_fnc_addEventMission;
    -  ---
    +  > call FUNC(addEventMission);
     
     Public:
       No
    @@ -25,7 +23,11 @@ Author:
     ---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
    +// Section: Event Handlers
    +
     if (isNil QEGVAR(EH,HandleDisconnect)) then {
    +  // Event Handler: OCAP_EH_HandleDisconnect
    +  // Fired when a player leaves the mission by returning to lobby or disconnecting. Calls <OCAP_recorder_fnc_eh_disconnected>.
       EGVAR(EH,HandleDisconnect) = addMissionEventHandler["HandleDisconnect", {
         _this call FUNC(eh_disconnected);
         false; // ensure we're not overriding disabledAI and persisting an AI unit to replace the player's
    @@ -34,6 +36,8 @@ if (isNil QEGVAR(EH,HandleDisconnect)) then {
     };
     
     if (isNil QEGVAR(EH,PlayerConnected)) then {
    +  // Event Handler: OCAP_EH_PlayerConnected
    +  // Handle for the "PlayerConnected" mission event handler. Fired when a player joins the mission from lobby and appears in the world. Calls <OCAP_recorder_fnc_eh_connected>.
       EGVAR(EH,PlayerConnected) = addMissionEventHandler["PlayerConnected", {
         _this call FUNC(eh_connected);
       }];
    @@ -41,6 +45,8 @@ if (isNil QEGVAR(EH,PlayerConnected)) then {
     };
     
     if (isNil QEGVAR(EH,OnUserAdminStateChanged)) then {
    +  // Event Handler: OCAP_EH_OnUserAdminStateChanged
    +  // Handle for the "OnUserAdminStateChange" mission event handler. Fired when a player's admin status changes. Calls <OCAP_recorder_fnc_eh_onUserAdminStateChanged>.
       EGVAR(EH,OnUserAdminStateChanged) = addMissionEventHandler ["OnUserAdminStateChanged", {
         _this call FUNC(eh_onUserAdminStateChanged);
       }];
    @@ -48,13 +54,17 @@ if (isNil QEGVAR(EH,OnUserAdminStateChanged)) then {
     };
     
     if (isNil QEGVAR(EH,EntityKilled)) then {
    -  addMissionEventHandler ["EntityKilled", {
    +  // Event Handler: OCAP_EH_EntityKilled
    +  // Handle for the "EntityKilled" mission event handler. Fired when an entity is killed. Calls <OCAP_recorder_fnc_eh_killed>.
    +  EGVAR(EH,EntityKilled) = addMissionEventHandler ["EntityKilled", {
         _this call FUNC(eh_killed);
       }];
       OCAPEXTLOG(["Initialized EntityKilled EH"]);
     };
     
     if (isNil QEGVAR(EH,EntityRespawned)) then {
    +  // Event Handler: OCAP_EH_EntityRespawned
    +  // Handle for the "EntityRespawned" mission event handler. Fired when an entity is respawned. Sets new body to not-killed and calls <OCAP_recorder_fnc_addUnitEventHandlers> on it. Then excludes corpse from further capture.
       EGVAR(EH,EntityRespawned) = addMissionEventHandler ["EntityRespawned", {
         params ["_entity", "_corpse"];
     
    @@ -71,23 +81,10 @@ if (isNil QEGVAR(EH,EntityRespawned)) then {
       OCAPEXTLOG(["Initialized EntityRespawned EH"]);
     };
     
    -// Listen for global ACE Explosive placement events
    -if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then {
    -  if (isNil QEGVAR(listener,aceExplosives)) then {
    -    call FUNC(aceExplosives);
    -    OCAPEXTLOG(["Initialized ACE Explosives listener"]);
    -  };
    -};
    -
    -// Listen for global ACE Throwing events that take place when a throwable is primed. use existing firedMan code, since identical args
    -if (isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing")) then {
    -  if (isNil QEGVAR(listener,aceThrowing)) then {
    -    ["ace_advanced_throwing_throwFiredXEH", {_this call FUNC(eh_firedMan)}] call CBA_fnc_addEventHandler;
    -    OCAPEXTLOG(["Initialized ACE Throwing listener"]);
    -  };
    -};
     
     if (isNil QEGVAR(EH,MPEnded)) then {
    +  // Event Handler: OCAP_EH_MPEnded
    +  // Handle for the "MPEnded" mission event handler. Fired on the MPEnded mission event. This is used to automatically save and export if <OCAP_settings_saveMissionEnded> is true and <OCAP_settings_minMissionTime> was reached.
       EGVAR(EH,MPEnded) = addMissionEventHandler ["MPEnded", {
         if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then {
           ["Mission ended automatically"] call FUNC(exportData);
    @@ -97,6 +94,8 @@ if (isNil QEGVAR(EH,MPEnded)) then {
     };
     
     if (isNil QEGVAR(EH,Ended)) then {
    +  // Event Handler: OCAP_EH_Ended
    +  // Handle for the "Ended" mission event handler. Fired on the singleplayer Ended mission event. This is used to automatically save and export if <OCAP_settings_saveMissionEnded> is true and <OCAP_settings_minMissionTime> was reached. Kept in just in case this event triggers.
       EGVAR(EH,Ended) = addMissionEventHandler ["Ended", {
         if (EGVAR(settings,saveMissionEnded) && (GVAR(captureFrameNo) * GVAR(frameCaptureDelay)) >= GVAR(minMissionTime)) then {
           ["Mission ended automatically"] call FUNC(exportData);
    @@ -105,13 +104,74 @@ if (isNil QEGVAR(EH,Ended)) then {
       OCAPEXTLOG(["Initialized Ended EH"]);
     };
     
    +
     // Add event saving markers
     if (isNil QEGVAR(listener,markers)) then {
       call FUNC(handleMarkers);
     };
     
    -// Custom event handler with key "ocap_customEvent"
    -// Used for showing custom events in playback events list
    +
    +
    +// Section: CBA Events
    +
    +/*
    +  Variables: CBA Listener Handles
    +
    +  OCAP_listener_aceThrowing - Handle for <ace_advanced_throwing_throwFiredXEH> listener.
    +  OCAP_listener_aceExplosives - Handle for <ace_explosives_place> listener.
    +  OCAP_listener_customEvent - Handle for <OCAP_customEvent> listener.
    +  OCAP_listener_counterInit - Handle for <OCAP_counterInit> listener.
    +  OCAP_listener_counterEvent - Handle for <OCAP_counterEvent> listener.
    +  OCAP_counter_sides - Sides that are tracked by the custom counter system. [Array]
    +  OCAP_listener_record - Handle for <OCAP_record> listener.
    +  OCAP_listener_pause - Handle for <OCAP_pause> listener.
    +  OCAP_listener_exportData - Handle for <OCAP_exportData> listener.
    +*/
    +
    +
    +if (isClass (configFile >> "CfgPatches" >> "ace_advanced_throwing")) then {
    +  if (isNil QEGVAR(listener,aceThrowing)) then {
    +    /*
    +      CBA Event: ace_advanced_throwing_throwFiredXEH
    +      Fired when a throwable is primed. This is a global event the server will handle and forward to <OCAP_recorder_fnc_eh_firedMan>. Created only if PBO "ace_advanced_throwing" is loaded.
    +    */
    +    EGVAR(listener,aceThrowing) = ["ace_advanced_throwing_throwFiredXEH", {
    +      _this call FUNC(eh_firedMan)
    +    }] call CBA_fnc_addEventHandler;
    +    OCAPEXTLOG(["Initialized ACE Throwing listener"]);
    +  };
    +};
    +
    +if (isClass (configFile >> "CfgPatches" >> "ace_explosives")) then {
    +  if (isNil QEGVAR(listener,aceExplosives)) then {
    +    /*
    +      CBA Event: ace_explosives_place
    +      Event listener for ACE3 global event indicating a mine has been placed and armed. Calls <OCAP_recorder_fnc_aceExplosives> when triggered. Created only if PBO "ace_explosives" is loaded.
    +    */
    +    EGVAR(listener,aceExplosives) = ["ace_explosives_place", {
    +      call FUNC(aceExplosives);
    +    }] call CBA_fnc_addEventHandler;
    +    OCAPEXTLOG(["Initialized ACE Explosives listener"]);
    +  };
    +};
    +
    +
    +/*
    +  CBA Event: OCAP_customEvent
    +  Description:
    +    Event listener for custom event text to be added to the timeline. Calls <OCAP_recorder_fnc_handleCustomEvent> when triggered.
    +
    +  Parameters:
    +    0 - Event name [String]
    +    1 - Event data [Array]
    +      1.0 - Always "generalEvent" [String]
    +      1.1 - Custom event text [String]
    +
    +  Example:
    +    > ["OCAP_customEvent", ["generalEvent", "The warehouse has been secured!"]] call CBA_fnc_serverEvent;
    +    > [QGVARMAIN(customEvent), ["generalEvent", "The warehouse has been secured!"]] call CBA_fnc_serverEvent;
    +
    +*/
     if (isNil QEGVAR(listener,customEvent)) then {
       EGVAR(listener,customEvent) = [QGVARMAIN(customEvent), {
         _this call FUNC(handleCustomEvent);
    @@ -119,8 +179,31 @@ if (isNil QEGVAR(listener,customEvent)) then {
       OCAPEXTLOG(["Initialized customEvent listener"]);
     };
     
    -// Custom event handler with key "ocap_counterInit"
    -// Used for tracking scores or counts per side
    +/*
    +  CBA Event: OCAP_counterInit
    +  Description:
    +    Meant for use in custom tracking of points or score between two sides. Separate from BIS_fnc_respawnTickets. Initializes the system. Calls <OCAP_recorder_fnc_counterInit> when triggered.
    +
    +  Parameters:
    +    0 - Event name [String]
    +    1 - Key/value for one or more sides [Array]
    +      1.0 - Pair [Array]
    +        1.0.0 - Side <SIDE>
    +        1.0.1 - Initial value [Number]
    +
    +  Example:
    +    (start code)
    +    ["OCAP_counterInit", [
    +      [west, 0],
    +      [east, 0]
    +    ]] call CBA_fnc_serverEvent;
    +
    +    [QGVARMAIN(counterInit), [
    +      [west, 0],
    +      [east, 0]
    +    ]] call CBA_fnc_serverEvent;
    +    (end code)
    +*/
     if (isNil QEGVAR(listener,counterInit)) then {
       EGVAR(listener,counterInit) = [QGVARMAIN(counterInit), {
         EGVAR(counter,sides) = _this apply {_x#0};
    @@ -132,6 +215,21 @@ if (isNil QEGVAR(listener,counterInit)) then {
       }] call CBA_fnc_addEventHandlerArgs;
       OCAPEXTLOG(["Initialized counterInit listener"]);
     };
    +
    +/*
    +  CBA Event: OCAP_counterEvent
    +  Description:
    +    Meant for use in custom tracking of points or score between two sides. Separate from BIS_fnc_respawnTickets. Updates the system. Calls <OCAP_recorder_fnc_counterEvent> when triggered.
    +
    +  Parameters:
    +    0 - Event name [String]
    +    1 - Event data [Array]
    +      1.0 - Side <SIDE>
    +      1.1 - Value to set [Number]
    +
    +  Example:
    +    > ["OCAP_counterEvent", [west, 1]] call CBA_fnc_serverEvent;
    +*/
     if (isNil QEGVAR(listener,counterEvent)) then {
       EGVAR(listener,counterEvent) = [QGVARMAIN(counterEvent), {
         if (isNil QEGVAR(counter,sides)) exitWith {};
    @@ -140,15 +238,21 @@ if (isNil QEGVAR(listener,counterEvent)) then {
     
         private _scores = [];
         {
    -      if ((_this#0) isEqualTo _x) then {_scores pushBack (_this#1)} else {_scores pushBack -1};
    +      if ((_this#0) isEqualTo _x) then {_scores pushBack (_this#1)};
         } forEach EGVAR(counter,sides);
         [QGVARMAIN(customEvent), ["counterSet", _scores]] call CBA_fnc_localEvent;
       }] call CBA_fnc_addEventHandler;
       OCAPEXTLOG(["Initialized counterEvent listener"]);
     };
     
    -// Custom event handler with key "ocap_record"
    -// This will START OR RESUME recording if not already.
    +/*
    +  CBA Event: OCAP_record
    +  Description:
    +    Used to start or resume recording. Calls <OCAP_recorder_fnc_startRecording> when triggered.
    +
    +  Example:
    +    > ["OCAP_record"] call CBA_fnc_serverEvent;
    +*/
     if (isNil QEGVAR(listener,record)) then {
       EGVAR(listener,record) = [QGVARMAIN(record), {
         call FUNC(startRecording);
    @@ -156,8 +260,14 @@ if (isNil QEGVAR(listener,record)) then {
       OCAPEXTLOG(["Initialized record listener"]);
     };
     
    -// Custom event handler with key "ocap_pause"
    -// This will PAUSE recording
    +/*
    +  CBA Event: OCAP_pause
    +  Description:
    +    Used to pause recording. Calls <OCAP_recorder_fnc_stopRecording> when triggered.
    +
    +  Example:
    +    > ["OCAP_pause"] call CBA_fnc_serverEvent;
    +*/
     if (isNil QEGVAR(listener,pause)) then {
       EGVAR(listener,pause) = [QGVARMAIN(pause), {
         call FUNC(stopRecording);
    @@ -165,9 +275,20 @@ if (isNil QEGVAR(listener,pause)) then {
       OCAPEXTLOG(["Initialized pause listener"]);
     };
     
    -// Custom event handler with key "ocap_exportData"
    -// This will export the mission immediately regardless of restrictions.
    -// params ["_side", "_message", "_tag"];
    +/*
    +  CBA Event: OCAP_exportData
    +  Description:
    +    Used to stop recording & signal the extension to save and upload it to the web component. Calls <OCAP_recorder_fnc_exportData> when triggered.
    +
    +    *Will always bypass <OCAP_settings_minMissionTime>*.
    +
    +  Parameters:
    +    0 - Event name [String]
    +    1 - Event data [Array]
    +      1.0 - (optional) Winning side <SIDE>
    +      1.1 - (optional) Message describing mission end [String]
    +      1.2 - (optional) Custom save tag (overrides <OCAP_settings_saveTag>) [String]
    +*/
     if (isNil QEGVAR(listener,exportData)) then {
       EGVAR(listener,exportData) = [QGVARMAIN(exportData), {
         _this set [3, true];
    diff --git a/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf b/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf
    index e977e2c..0b7199e 100644
    --- a/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf
    +++ b/x/ocap/addons/recorder/fnc_addUnitEventHandlers.sqf
    @@ -1,22 +1,24 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(addUnitEventHandlers)
    +FILE: fnc_addUnitEventHandlers.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_addUnitEventHandlers
     
     Description:
       Used for applying unit-specific event handlers to units during initialization. These event handlers will trigger on the server.
     
    -  Applied during initialization of a unit in <FUNC(captureLoop)>.
    +  Applied during initialization of a unit in <OCAP_recorder_fnc_captureLoop>.
    +
    +  Note: Hit tracking moved to projectile EHs in <OCAP_recorder_fnc_eh_firedMan>
     
     Parameters:
       _entity - Object to apply event handlers to. [Object]
    -  _respawn - Determines if unit is initialized for the first time, or has respawned and does not need certain handlers reapplied. [Boolean, defaults to false]
    +  _respawn - Determines if unit is initialized for the first time, or has respawned and does not need certain handlers reapplied. [[Bool], default: false]
     
     Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  [_unit] spawn FUNC(addUnitEventHandlers);
    -  ---
    +  > [_unit] spawn FUNC(addUnitEventHandlers);
     
     Public:
       No
    @@ -30,6 +32,7 @@ params ["_entity", ["_respawn", false]];
     
     
     // FIREDMAN
    +// Set a serverside variable on the unit with a value of the handle of the FiredMan EH placed on that unit.
     if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then {
       if (isNil {_entity getVariable QGVARMAIN(FiredManEH)}) then {
         _entity setVariable [
    @@ -38,11 +41,3 @@ if ((_entity call BIS_fnc_objectType) # 0 == "Soldier") then {
         ];
       };
     };
    -
    -// MPHIT
    -// if (isNil {_entity getVariable QGVARMAIN(MPHitEH)}) then {
    -//   _entity setVariable [
    -//     QGVARMAIN(MPHitEH),
    -//     _entity addMPEventHandler ["MPHit", { _this call FUNC(eh_hit); }]
    -//   ];
    -// };
    diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    index a5f9ad0..bfa23ba 100644
    --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    @@ -1,3 +1,31 @@
    +/*
    +  FILE: fnc_adminUIControl.sqf
    +
    +  FUNCTION: OCAP_recorder_fnc_adminUIControl
    +
    +  Description:
    +    Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
    +
    +    - <OCAP_recorder_fnc_eh_connected> at mission start to determine if a player is in <OCAP_administratorList>
    +    - <OCAP_recorder_fnc_eh_onUserAdminStateChanged> to add/remove when a player logs in or out as admin on the server
    +
    +  Parameters:
    +    _PID - PlayerID indicating unique network client on the server [String]
    +    _event - Event that triggered this call [[String], one of: "connect", "login", "logout"]
    +
    +  Returns:
    +    Nothing
    +
    +  Examples:
    +    > ["1234567890", "connect"] call FUNC(adminUIControl);
    +
    +  Public:
    +    No
    +
    +  Author:
    +    IndigoFox
    +*/
    +
     #include "script_component.hpp"
     
     params [
    @@ -50,7 +78,8 @@ _fnc_removeControls = {
         player setVariable [QGVARMAIN(hasAdminControls), false, 2];
       } remoteExec ["call", _owner];
     
    -  // set variable on unit
    +  // Variable: OCAP_hasAdminControls
    +  // Applied on units processed in <OCAP_recorder_fnc_adminUIControl>. Indicates whether or not they have the administrative diary entry available. Server missionNamespace only.
       _unit setVariable [QGVARMAIN(hasAdminControls), false];
     };
     
    diff --git a/x/ocap/addons/recorder/fnc_captureLoop.sqf b/x/ocap/addons/recorder/fnc_captureLoop.sqf
    index 5f25c9a..05086a9 100644
    --- a/x/ocap/addons/recorder/fnc_captureLoop.sqf
    +++ b/x/ocap/addons/recorder/fnc_captureLoop.sqf
    @@ -1,12 +1,15 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(captureLoop)
    +FILE: fnc_captureLoop.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_captureLoop
     
     Description:
    -  Iterates through units, declares they exist, and conditional records their state at an interval defined in userconfig.hpp.
     
    -  This is the core processing loop that determines when new units enter the world, all the details about them, classifies which to exclude, and determines their health/life status. It has both unit and vehicle tracking.
    +  This function is run unscheduled and creates a CBA PerFrameHandler object, a logic object which executes code every specified interval (<OCAP_settings_frameCaptureDelay>) while a condition (<SHOULDSAVEEVENTS>) is true.
     
    -  This is spawned during <ocap_fnc_init>.
    +  Iterates through units and vehicles, declares they exist, and conditionally sends their information to the extension to populate recording data.
    +
    +  This is the core processing loop that determines when new units enter the world, all the details about them, classifies which to exclude, and determines their health/life status. It has both unit and vehicle tracking.
     
     Parameters:
       None
    @@ -15,9 +18,7 @@ Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  0 spawn FUNC(captureLoop);
    -  ---
    +  >  call FUNC(captureLoop);
     
     Public:
       No
    @@ -38,6 +39,8 @@ if (isNil QGVAR(startTime)) then {
       LOG(ARR3(__FILE__, QGVAR(recording) + " started, time:", GVAR(startTime)));
     };
     
    +// Variable: OCAP_PFHObject
    +// The CBA PerFrameHandler object that is created and used to run the capture loop.
     GVAR(PFHObject) = [
       {
         private _loopStart = diag_tickTime;
    diff --git a/x/ocap/addons/recorder/fnc_eh_connected.sqf b/x/ocap/addons/recorder/fnc_eh_connected.sqf
    index e70719d..a24aeb1 100644
    --- a/x/ocap/addons/recorder/fnc_eh_connected.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_connected.sqf
    @@ -1,5 +1,31 @@
    +/*
    +  FILE: fnc_eh_connected.sqf
    +
    +  FUNCTION: OCAP_recorder_fnc_eh_connected
    +
    +  Description:
    +
    +    This function uses the <OCAP_EH_Connected> event handler to log "connected" events to the timeline.
    +
    +    It also calls <OCAP_recorder_fnc_adminUIControl> to apply the admin UI if the player is in <OCAP_administratorList>.
    +
    +  Parameters:
    +    See the wiki for details. <https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#PlayerConnected>
    +
    +  Returns:
    +    Nothing
    +
    +  Examples:
    +    > call FUNC(eh_connected);
    +
    +  Public:
    +    No
    +
    +  Author:
    +    IndigoFox
    +*/
    +
     #include "script_component.hpp"
    -// PlayerConnected EH
     params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
     
     // skip for server 'connected' message
    diff --git a/x/ocap/addons/recorder/fnc_eh_disconnected.sqf b/x/ocap/addons/recorder/fnc_eh_disconnected.sqf
    index 810220e..7bf993f 100644
    --- a/x/ocap/addons/recorder/fnc_eh_disconnected.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_disconnected.sqf
    @@ -1,3 +1,28 @@
    +/*
    +  FILE: fnc_eh_disconnected.sqf
    +
    +  FUNCTION: OCAP_recorder_fnc_eh_disconnected
    +
    +  Description:
    +
    +    This function uses the <OCAP_EH_HandleDisconnect> event handler to log "disconnected" events to the timeline. It will exclude any body left over from further recording.
    +
    +  Parameters:
    +    See the wiki for details. <https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#HandleDisconnect>
    +
    +  Returns:
    +    False [Bool]
    +
    +  Examples:
    +    > call FUNC(eh_disconnected);
    +
    +  Public:
    +    No
    +
    +  Author:
    +    IndigoFox
    +*/
    +
     #include "script_component.hpp"
     
     params ["_unit", "_id", "_uid", "_name"];
    @@ -9,3 +34,5 @@ params ["_unit", "_id", "_uid", "_name"];
     if (_unit getVariable [QGVARMAIN(isInitialized), false]) then {
     	_unit setVariable [QGVARMAIN(exclude), true];
     };
    +
    +false;
    diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    index b6386c1..8825915 100644
    --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    @@ -1,16 +1,18 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(eh_firedMan)
    +FILE: fnc_eh_firedMan.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_eh_firedMan
     
     Description:
    -  Tracks bullet and non-bullet projectiles. This is the code triggered when a unit firing is detected by the "FiredMan" Event Handler applied to units during <FUNC(addUnitEventHandlers)>.
    +  Tracks bullet and non-bullet projectiles. This is the code triggered when a unit firing is detected by the "FiredMan" Event Handler applied to units during <OCAP_recorder_fnc_addUnitEventHandlers>.
     
     Parameters:
       _firer - Unit the event handler is assigned to (the instigator) [Object]
       _weapon - Fired weapon [String]
       _muzzle - Muzzle that was used [String]
       _mode - Current mode of the fired weapon [String]
    -  _ammo - Ammo used [String]
    -  _magazine - Magazine name which was used [String]
    +  _ammo - Classname of ammo used [String]
    +  _magazine - Classname of magazine used [String]
       _projectile - Object of the projectile that was shot out [Object]
       _vehicle - if weapon is vehicle weapon, otherwise objNull [Object]
     
    @@ -18,15 +20,42 @@ Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  ---
    +  > [_firer, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile, _vehicle] call FUNC(eh_firedMan);
     
     Public:
       No
     
     Author:
       IndigoFox, Dell
    +
     ---------------------------------------------------------------------------- */
    +
    +
    +/*
    +  Variable: OCAP_lastFired
    +  Indicates a formatted string of the last weapon and magazine type fired by the unit. Used for logging hits/kills. Applied to a firing unit.
    +*/
    +
    +/*
    +  Event Handlers: Projectiles (Bullets)
    +  Deleted - Makes extension call to draw a fire-line between the firer and the final destination.
    +  Explode - Makes extension call to draw a fire-line between the firer and the final destination.
    +  HitPart - Triggered when a projectile hits a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +  HitExplosion - Triggered when a projectile explodes and damages a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +
    +  Event Handlers: Projectiles (Non-Bullets)
    +  Deleted - Triggered when a non-bullet projectile is deleted. Updates marker position, then removes it 3 frames later.
    +  Explode - Triggered when a non-bullet projectile explodes. Updates marker position, then removes it 3 frames later.
    +  HitPart - Triggered when a projectile hits a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +  HitExplosion - Triggered when a projectile explodes and damages a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +*/
    +
    +/*
    +  CBA Events: Projectiles
    +  OCAP_recorder_addDebugBullet - Triggered when a bullet is fired and the debug mode is enabled. Shares recent bullet data to all clients.
    +  OCAP_recorder_addDebugMagIcon - Triggered when a non-bullet projectile is fired and the debug mode is enabled. Shares recent data to all clients.
    +*/
    +
     #include "script_component.hpp"
     
     if (!SHOULDSAVEEVENTS) exitWith {};
    @@ -68,6 +97,7 @@ if (!isNull _vehicle) then {
     } else {
       _wepString = format["%1 [%2]", _muzzleDisp, _magDisp];
     };
    +
     _firer setVariable [QGVARMAIN(lastFired), _wepString];
     (vehicle _firer) setVariable [QGVARMAIN(lastFired), _wepString];
     
    @@ -89,6 +119,7 @@ _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation");
     _projectile setVariable [QGVAR(firer), _firer];
     _projectile setVariable [QGVAR(firerId), _firerId];
     
    +
     // Track hit events for all projectile types
     _projectile addEventHandler ["HitPart", {
       params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius" ,"_surfaceType"];
    @@ -118,6 +149,26 @@ if (_ammoSimType isEqualTo "shotBullet") exitWith {
           _projectilePos
         ]] call EFUNC(extension,sendData);
     
    +    if (GVARMAIN(isDebug)) then {
    +      OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    +
    +      // add to clients' map draw array
    +      private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    +      [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    +    };
    +  }];
    +  _projectile addEventHandler ["Explode", {
    +    params ["_projectile", "_pos", "_velocity"];
    +    _firer = _projectile getVariable [QGVAR(firer), objNull];
    +    _firerId = _projectile getVariable [QGVAR(firerId), -1];
    +    _projectilePos = getPosASL _projectile;
    +
    +    [":FIRED:", [
    +      _firerId,
    +      GVAR(captureFrameNo),
    +      _projectilePos
    +    ]] call EFUNC(extension,sendData);
    +
         if (GVARMAIN(isDebug)) then {
           OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
     
    @@ -230,6 +281,26 @@ switch (true) do {
                   _projectilePos
                 ]] call EFUNC(extension,sendData);
     
    +            if (GVARMAIN(isDebug)) then {
    +              OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    +
    +              // add to clients' map draw array
    +              private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    +              [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    +            };
    +          }];
    +          _submunitionProjectile addEventHandler ["Explode", {
    +            params ["_projectile"];
    +            _firer = _projectile getVariable [QGVAR(firer), objNull];
    +            _firerId = _projectile getVariable [QGVAR(firerId), -1];
    +            _projectilePos = getPosASL _projectile;
    +
    +            [":FIRED:", [
    +              _firerId,
    +              GVAR(captureFrameNo),
    +              _projectilePos
    +            ]] call EFUNC(extension,sendData);
    +
                 if (GVARMAIN(isDebug)) then {
                   OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
     
    diff --git a/x/ocap/addons/recorder/fnc_eh_killed.sqf b/x/ocap/addons/recorder/fnc_eh_killed.sqf
    index 755df62..5c88806 100644
    --- a/x/ocap/addons/recorder/fnc_eh_killed.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_killed.sqf
    @@ -1,21 +1,22 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(eh_killed)
    +FILE: fnc_eh_killed.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_eh_killed
     
     Description:
    -  Tracks when a unit is killed. This is the code triggered by the "MPKilled" Event Handler applied to units during <FUNC(addUnitEventHandlers)>.
    +  Tracks when a unit is killed. This is the code triggered by the <OCAP_EH_EntityKilled> mission event handler.
     
     Parameters:
       _unit - Object the event handler is assigned to. [Object]
       _killer - Object that killed the unit. [Object]
       _instigator - Person who pulled the trigger. [Object]
    -  _useEffects - same as useEffects in setDamage alt syntax. [Boolean]
    +  _useEffects - same as useEffects in setDamage alt syntax. [Bool]
     
     Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  ---
    +  > call FUNC(eh_killed);
     
     Public:
       No
    diff --git a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    index b797d70..78a2f3b 100644
    --- a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    @@ -1,3 +1,28 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_eh_onUserAdminStateChanged.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_eh_onUserAdminStateChanged
    +
    +Description:
    +  Uses <OCAP_EH_OnUserAdminStateChanged> to detect when someone is has logged in or out of the server and calls <OCAP_recorder_fnc_adminUIControl> to update the admin UI.
    +
    +Parameters:
    +  _networkId - The network ID of the player who has logged in or out of the server [String]
    +  _loggedIn - Whether the player has logged in or out as admin [Boolean]
    +  _votedIn - Whether the player has been voted in or out of admin [Boolean]
    +
    +Returns:
    +  Nothing
    +
    +Examples:
    +  > call FUNC(eh_onUserAdminStateChanged);
    +
    +Public:
    +  No
    +
    +Author:
    +  IndigoFox
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     params ["_networkId", "_loggedIn", "_votedIn"];
    diff --git a/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf b/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf
    index 035ff7e..7706346 100644
    --- a/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_projectileHit.sqf
    @@ -1,21 +1,20 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(eh_hit)
    +FILE: fnc_eh_projectileHit.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_eh_projectileHit
     
     Description:
    -  Tracks when a unit is hit/takes damage. This is the code triggered by the "MPHit" Event Handler applied to units during <FUNC(addUnitEventHandlers)>.
    +  Tracks when a unit is hit/takes damage and saves to the timeline. This is called by projectile event handlers in <OCAP_recorder_fnc_eh_firedMan>.
     
     Parameters:
    -  _unit - Object the event handler is assigned to. [Object]
    -  _causedBy - Object that caused the damage. Contains the unit itself in case of collisions. [Object]
    -  _damage - Level of damage caused by the hit. [Number]
    -  _instigator - Object - Person who pulled the trigger. [Object]
    +  _unit - Object that took damage [Object]
    +  _shooter - Object that caused the damage [Object]
     
     Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  ---
    +  > [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
     
     Public:
       No
    diff --git a/x/ocap/addons/recorder/fnc_entityMonitors.sqf b/x/ocap/addons/recorder/fnc_entityMonitors.sqf
    index 95e47a7..3d87ab1 100644
    --- a/x/ocap/addons/recorder/fnc_entityMonitors.sqf
    +++ b/x/ocap/addons/recorder/fnc_entityMonitors.sqf
    @@ -1,3 +1,28 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_entityMonitors.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_entityMonitors
    +
    +Description:
    +  While debug mode is enabled, this function will render 2D icons and text representing all entities that have been initialized by OCAP and are not being excluded from the recording.
    +
    +  This is useful for debugging and verifying that the correct entities are being recorded (see <OCAP_settings_excludeClassFromRecord> and <OCAP_settings_excludeKindFromRecord>.
    +
    +Parameters:
    +  None
    +
    +Returns:
    +  Nothing
    +
    +Examples:
    +  > [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    +
    +Public:
    +  No
    +
    +Author:
    +  IndigoFox
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     // DEBUG draws on clients
    @@ -42,4 +67,6 @@
       };
     } remoteExec ["call", [0, -2] select isDedicated, true];
     
    +// Variable: OCAP_entityMonitorsInitialized
    +// This variable on the server indicates whether or not the entity monitors have been initialized for all clients + JIP.
     GVAR(entityMonitorsInitialized) = true;
    diff --git a/x/ocap/addons/recorder/fnc_exportData.sqf b/x/ocap/addons/recorder/fnc_exportData.sqf
    index 943ef44..8eeac10 100644
    --- a/x/ocap/addons/recorder/fnc_exportData.sqf
    +++ b/x/ocap/addons/recorder/fnc_exportData.sqf
    @@ -1,10 +1,18 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(exportData)
    +FILE: fnc_exportData.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_exportData
     
     Description:
       This function facilitates the actual endMission and save events in the extension, prompting it to pack the mission and upload it to the web component.
     
    -  This function MUST be called in order to save a mission recording. A boolean true in the correct option of userconfig.hpp will automatically execute this function when the "MPEnded" Event Handler triggers.
    +  Called directly, it is subject to the <OCAP_settings_minMissionTime> setting, and will not export if the mission is not long enough. It can also be called using <OCAP_listener_exportData> to bypass this check.
    +
    +  When <OCAP_settings_saveMissionEnded> is true, this function will be called automatically when the mission ends.
    +
    +  When <OCAP_settings_saveOnEmpty> is true, this function will execute when the last player leaves the mission (to lobby or when disconnecting).
    +
    +  <OCAP_settings_saveTag> is used to tag the mission with a custom string, which can be used to identify the mission in the web component.
     
     Parameters:
       _side - The winning side [optional, Side]
    @@ -15,7 +23,7 @@ Returns:
       Nothing
     
     Examples:
    -  --- Code
    +  (start code)
       // "Mission ended"
       [] call FUNC(exportData);
     
    @@ -28,10 +36,12 @@ Examples:
       // "Independent Win. INDFOR stole the intel!"
       // Mission is saved under filterable "SnatchAndGrab" tag on web
       [independent, "INDFOR stole the intel!", "SnatchAndGrab"] call FUNC(exportData);
    -  ---
    +
    +  ["OCAP_exportData", west] call CBA_fnc_serverEvent;
    +  (end code)
     
     Public:
    -  Yes
    +  No
     
     Author:
       Dell, Zealot, IndigoFox, TyroneMF
    @@ -77,7 +87,7 @@ if (_frameTimeDuration < GVAR(minMissionTime) && !_overrideLimits) exitWith {
       ["OCAP attempted to save, but the minimum recording duration hasn't been met. Recording will continue.", 1, [1, 1, 1, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated];
       {
         player createDiaryRecord [
    -      "OCA2Info",
    +      "OCAPInfo",
           [
             "Status",
             (
    diff --git a/x/ocap/addons/recorder/fnc_getAmmoMarkerData.sqf b/x/ocap/addons/recorder/fnc_getAmmoMarkerData.sqf
    index 5cadad4..361ee4c 100644
    --- a/x/ocap/addons/recorder/fnc_getAmmoMarkerData.sqf
    +++ b/x/ocap/addons/recorder/fnc_getAmmoMarkerData.sqf
    @@ -1,3 +1,32 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_getAmmoMarkerData.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_getAmmoMarkerData
    +
    +Description:
    +  This function will intake information from <OCAP_EH_FiredMan> and return data used to format a marker for entry into the timeline.
    +
    +Parameters:
    +  _weapon - The weapon used [String]
    +  _muzzle - The muzzle used [String]
    +  _ammo - The ammo used [String]
    +  _magazine - The magazine used [String]
    +  _projectile - The projectile object [Object]
    +  _vehicle - The vehicle the weapon is mounted on, if any [Object]
    +  _ammoSimType - The ammo simulation type [String]
    +
    +Returns:
    +  [_markTextLocal,_markName,_markColor,_markerType] - The marker data [Array]
    +
    +Examples:
    +  > ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"];
    +
    +Public:
    +  No
    +
    +Author:
    +  IndigoFox
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     params ["_weapon", "_muzzle", "_ammo", "_magazine", "_projectile", "_vehicle", "_ammoSimType"];
    diff --git a/x/ocap/addons/recorder/fnc_getClass.sqf b/x/ocap/addons/recorder/fnc_getClass.sqf
    index d388dc6..8a774be 100644
    --- a/x/ocap/addons/recorder/fnc_getClass.sqf
    +++ b/x/ocap/addons/recorder/fnc_getClass.sqf
    @@ -1,3 +1,26 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_getClass.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_getClass
    +
    +Description:
    +  Determines what type of vehicle is being recorded to match with the more limited icon set preloaded in the OCAP playback UI.
    +
    +Parameters:
    +  _this - The vehicle being queried [Object]
    +
    +Returns:
    +  [String] - The icon name that should be used to represent the vehicle in the playback UI
    +
    +Examples:
    +  > _class = _vehType call FUNC(getClass);
    +
    +Public:
    +  No
    +
    +Author:
    +  Zealot, Dell
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     if (getText(configFile >> "CfgVehicles" >> _this >> "model") isEqualTo "\A3\Weapons_f\empty") exitWith {"unknown"};
    diff --git a/x/ocap/addons/recorder/fnc_getDelay.sqf b/x/ocap/addons/recorder/fnc_getDelay.sqf
    deleted file mode 100644
    index 26e8143..0000000
    --- a/x/ocap/addons/recorder/fnc_getDelay.sqf
    +++ /dev/null
    @@ -1,41 +0,0 @@
    -/* ----------------------------------------------------------------------------
    -Script: FUNC(exportData)
    -
    -Description:
    -  Determines the the appropriate interval at which to loop the <FUNC(captureLoop)> function.
    -
    -  Устанавливает точную задержку между кадрами
    -
    -Parameters:
    -  None
    -
    -Returns:
    -  Sleep duration [Number]
    -
    -Examples:
    -  --- Code
    -  call ocap_fnc_getDelay;
    -  ---
    -
    -Public:
    -  No
    -
    -Author:
    -  Dell
    ----------------------------------------------------------------------------- */
    -#include "script_component.hpp"
    -
    -private "_sleep";
    -isNil {
    -  _elapsedTime = time - GVAR(startTime);
    -  _sleep = (GVAR(captureFrameNo) + 1) * GVAR(frameCaptureDelay) - _elapsedTime;
    -
    -  if ((GVAR(captureFrameNo) % 10) isEqualTo 0) then {
    -    LOG(ARR4("DEBUG: Frame", GVAR(captureFrameNo), "is created in ~", GVAR(frameCaptureDelay) - _sleep));
    -  };
    -  if (_sleep < 0) then {
    -    LOG(ARR3("ERROR: Frame delay is negative", GVAR(captureFrameNo), _sleep));
    -    _sleep = 0.4;
    -  };
    -};
    -_sleep
    diff --git a/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf
    index 904488d..c1e4a97 100644
    --- a/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf
    +++ b/x/ocap/addons/recorder/fnc_getEventWeaponText.sqf
    @@ -1,10 +1,14 @@
     /* ----------------------------------------------------------------------------
    -Script: ocap_fnc_getEventWeaponText
    +FILE: fnc_getEventWeaponText.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_getEventWeaponText
     
     Description:
       Used to identify the current weapon a unit is using that has injured or killed another. Will determine the handheld weapon or vehicle weapon they're using.
     
    -  Called during <FUNC(eh_hit)> and <FUNC(eh_killed)>.
    +  Attempts to reference <OCAP_lastFired> but will fall back to current value if not available.
    +
    +  Called during <OCAP_recorder_fnc_projectileHit> and <OCAP_recorder_fnc_eh_killed>.
     
     Parameters:
       _instigator - The unit to evaluate [Object]
    @@ -13,9 +17,7 @@ Returns:
       The description of weapon or vehicle > weapon. [String]
     
     Examples:
    -  --- Code
    -  [_instigator] call ocap_fnc_getEventWeaponText
    -  ---
    +  > [_shooter] call FUNC(getEventWeaponText)
     
     Public:
       No
    diff --git a/x/ocap/addons/recorder/fnc_getInstigator.sqf b/x/ocap/addons/recorder/fnc_getInstigator.sqf
    index 2d66c7e..d107a83 100644
    --- a/x/ocap/addons/recorder/fnc_getInstigator.sqf
    +++ b/x/ocap/addons/recorder/fnc_getInstigator.sqf
    @@ -1,9 +1,13 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(getInstigator)
    +FILE: fnc_getInstigator.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_getInstigator
     
     Description:
       Attempts to identify who truly pulled the trigger on a kill event.
     
    +  Called in <OCAP_recorder_fnc_eh_killed>.
    +
     Parameters:
       _victim - Who was killed. [Object]
       _killer - What caused the damage. [Object, default objNull]
    @@ -13,9 +17,7 @@ Returns:
       The true killer. [Object]
     
     Examples:
    -  --- Code
    -  [_victim, _killer] call FUNC(getInstigator);
    -  ---
    +  > [_victim, _killer] call FUNC(getInstigator);
     
     Public:
       No
    diff --git a/x/ocap/addons/recorder/fnc_getUnitType.sqf b/x/ocap/addons/recorder/fnc_getUnitType.sqf
    index 8004c6b..dd35f08 100644
    --- a/x/ocap/addons/recorder/fnc_getUnitType.sqf
    +++ b/x/ocap/addons/recorder/fnc_getUnitType.sqf
    @@ -1,5 +1,7 @@
     /* ----------------------------------------------------------------------------
    -Script: ocap_fnc_getUnitType
    +FILE: fnc_getUnitType.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_getUnitType
     
     Description:
       Identifies the role of a unit using similar methodology to Arma 3's. Used in <FUNC(captureLoop)>.
    @@ -11,15 +13,13 @@ Returns:
       The role text. [String]
     
     Examples:
    -  --- Code
    -  [_x] call ocap_fnc_getUnitType;
    -  ---
    +  > [_x] call ocap_fnc_getUnitType;
     
     Public:
       No
     
     Author:
    -  IndigoFox
    +  IndigoFox, veteran29
     ---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
    diff --git a/x/ocap/addons/recorder/fnc_getWeaponDisplayData.sqf b/x/ocap/addons/recorder/fnc_getWeaponDisplayData.sqf
    index 2835989..3e66387 100644
    --- a/x/ocap/addons/recorder/fnc_getWeaponDisplayData.sqf
    +++ b/x/ocap/addons/recorder/fnc_getWeaponDisplayData.sqf
    @@ -1,3 +1,32 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_getWeaponDisplayData.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_getWeaponDisplayData
    +
    +Description:
    +  Used to populate <OCAP_lastFired> on units in <OCAP_recorder_fnc_eh_firedMan>.
    +
    +Parameters:
    +  _weapon - Weapon class name [String]
    +  _muzzle - Muzzle class name [String]
    +  _magazine - Magazine class name [String]
    +  _ammo - Ammo class name [String]
    +
    +Returns:
    +  [Array]
    +
    +    0 - Muzzle display name [String]
    +    1 - Magazine display name [String]
    +
    +Examples:
    +  > ([_weapon, _muzzle, _magazine, _ammo] call FUNC(getWeaponDisplayData)) params ["_muzzleDisp", "_magDisp"];
    +
    +Public:
    +  No
    +
    +Author:
    +  IndigoFox
    +---------------------------------------------------------------------------- */
     params ["_weapon", "_muzzle", "_magazine", "_ammo"];
     
     _muzzleDisp = getText(configFile >> "CfgWeapons" >> _weapon >> _muzzle >> "displayName");
    diff --git a/x/ocap/addons/recorder/fnc_handleCustomEvent.sqf b/x/ocap/addons/recorder/fnc_handleCustomEvent.sqf
    index 9824619..2cfb2cc 100644
    --- a/x/ocap/addons/recorder/fnc_handleCustomEvent.sqf
    +++ b/x/ocap/addons/recorder/fnc_handleCustomEvent.sqf
    @@ -1,51 +1,56 @@
    -/* ----------------------------------------------------------------------------
    -Script: FUNC(handleCustomEvent)
    -
    -Description:
    -  Used for applying global event handlers.
    -
    -  * Applied during initialization of OCAP in <ocap_fnc_init>.
    -
    -Parameters:
    -  _type - objective type that will define the text & icon [String, one of: "flag"]
    -  _unit - name of the unit that performed the action [String]
    -  _unitColor - color for the unit's name shown in Events list and for the pulse on the map [String, Hex RGB, defaults "" and will show as white]
    -  _objectiveColor - color representing the icon in Events list [String, Hex RGB, defaults "" and will show as white]
    -  _position - the location to pulse on the map [optional, PositionATL, default nil]
    -
    -Returns:
    -  Nothing
    -
    -Examples:
    -  --- Code
    -  ["ocap_handleCustomEvent", ["eventType", "eventMessage"]] call CBA_fnc_serverEvent;
    -
    -  // indicates a flag has been captured
    -  ["ocap_handleCustomEvent", ["captured", [
    -    "flag",
    -    name _unit,
    -    str side group _unit,
    -    "#FF0000",
    -    getPosAtl _flag
    -  ]]] call call CBA_fnc_serverEvent;
    -
    -
    -  // Not yet implemented
    -  ["ocap_handleCustomEvent", ["captured", [
    -    "sector",
    -    name _unit,
    -    str side group _unit,
    -    "#FF0000",
    -    getPosAtl _sectorObject
    -  ]]] call call CBA_fnc_serverEvent;
    -  ---
    -
    -Public:
    -  Yes
    -
    -Author:
    -  Fank, Zealot
    ----------------------------------------------------------------------------- */
    +/*
    +  FILE: fnc_handleCustomEvent.sqf
    +
    +  FUNCTION: OCAP_recorder_fnc_handleCustomEvent
    +
    +  Description:
    +    Sends custom event data to the extension to save it to the timeline. This custom event data is later read by Javascript in the web component to determine how it should be displayed.
    +
    +    Applied during initialization of OCAP in <OCAP_recorder_fnc_init>.
    +
    +  Parameters:
    +    _type - classifier for the type of event. used to determine text & icon [[String], one of: "flag", "generalEvent"]
    +    _unit - name of the unit that performed the action [String]
    +    _unitColor - (optional) color for the unit's name shown in Events list and for the pulse on the map [[String], Hex RGB, defaults "" and will show as white]
    +    _objectiveColor - (optional) color representing the icon in Events list [[String], Hex RGB, defaults "" and will show as white]
    +    _position - (optional) the location to pulse on the map [<PositionATL>, default nil]
    +
    +  Returns:
    +    Nothing
    +
    +  Examples:
    +    (start code)
    +    ["ocap_handleCustomEvent", ["eventType", "eventMessage"]] call CBA_fnc_serverEvent;
    +
    +    // saves a general event to the timeline
    +    ["ocap_handleCustomEvent", ["generalEvent", "eventText"]] call CBA_fnc_serverEvent;
    +
    +    // indicates a flag has been captured
    +    ["ocap_handleCustomEvent", ["captured", [
    +      "flag",
    +      name _unit,
    +      str side group _unit,
    +      "#FF0000",
    +      getPosAtl _flag
    +    ]]] call call CBA_fnc_serverEvent;
    +
    +
    +    // Not yet implemented
    +    ["ocap_handleCustomEvent", ["captured", [
    +      "sector",
    +      name _unit,
    +      str side group _unit,
    +      "#FF0000",
    +      getPosAtl _sectorObject
    +    ]]] call call CBA_fnc_serverEvent;
    +    (end code)
    +
    +  Public:
    +    Yes
    +
    +  Author:
    +    Fank, Zealot
    +*/
     #include "script_component.hpp"
     
     if (!SHOULDSAVEEVENTS) exitWith {};
    diff --git a/x/ocap/addons/recorder/fnc_handleMarkers.sqf b/x/ocap/addons/recorder/fnc_handleMarkers.sqf
    index 82b65dd..3a9ab33 100644
    --- a/x/ocap/addons/recorder/fnc_handleMarkers.sqf
    +++ b/x/ocap/addons/recorder/fnc_handleMarkers.sqf
    @@ -1,5 +1,7 @@
     /* ----------------------------------------------------------------------------
    -Script: ocap_fnc_handleMarkers
    +FILE: fnc_handleMarkers.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_handleMarkers
     
     Description:
       Used for tracking all markers in the vanilla Arma 3 system.
    @@ -10,9 +12,11 @@ Description:
     
       Due to the nature of locality and single-view playback, markers of the same name which exist in different states on different clients may display odd behavior during playback.
     
    -  Marker exclusion as configured in userconfig.hpp is handled client-side for performance reasons.
    +  Marker exclusions as configured in <OCAP_settings_excludeMarkerFromRecord> are handled client-side to avoid unnecessary network traffic.
    +
    +  This will also wait until the mission proceeds past the briefing screen, then gather all existing markers and send them to the server for entry onto the Editor/Briefing Markers layer during playback.
     
    -  * Applied during mission event handler application in <ocap_fnc_addEventMission>.
    +  Applied during mission event handler application in <OCAP_recorder_fnc_addEventMission>.
     
     Parameters:
       None
    @@ -21,26 +25,26 @@ Returns:
       Nothing
     
     Examples:
    -  --- Code
    -  call ocap_fnc_handleMarkers;
    -  ---
    +  > call FUNC(handleMarkers);
     
     Public:
    -  Yes
    +  No
     
     Author:
       IndigoFox, Fank
     ---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
    -// array: GVAR(trackedMarkers)
    +// VARIABLE: OCAP_recorder_trackedMarkers
     // Persistent global variable on server that defines unique marker names currently being tracked.
     // Entries are added at marker create events and removed at marker delete events to avoid duplicate processing.
    -GVAR(trackedMarkers) = []; // Markers which we saves into replay
    -
    +GVAR(trackedMarkers) = []; // Markers which we save into replay
     
    +// VARIABLE: OCAP_listener_markers
    +// Contains handle for <OCAP_handleMarker> CBA event handler.
     
    -// create CBA event handler to be called on server with key "ocap_handleMarker"
    +// CBA Event: OCAP_handleMarker
    +// Handles marker creation, modification, and deletion events.
     EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
     
       if (!SHOULDSAVEEVENTS) exitWith {};
    @@ -157,6 +161,15 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
     
     // handle created markers
     {
    +  /*
    +    Event Handler: MarkerCreated
    +    Description:
    +      Tracks marker creations. Present on server and all clients. Ignores remotely-owned markers.
    +
    +      References <OCAP_settings_excludeMarkerFromRecord> on each local machine to determine if a marker should be recorded.
    +
    +      If so, sends marker data to <OCAP_handleMarker> on the server for processing.
    + */
       addMissionEventHandler["MarkerCreated", {
         params["_marker", "_channelNumber", "_owner", "_local"];
     
    @@ -198,6 +211,15 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
       }];
     
       // handle marker moves/updates
    +  /*
    +    Event Handler: MarkerUpdated
    +    Description:
    +      Tracks marker updates (moves). Present on server and all clients. Ignores remotely-owned markers.
    +
    +      References <OCAP_settings_excludeMarkerFromRecord> on each local machine to determine if a marker should be recorded.
    +
    +      If so, sends marker data to <OCAP_handleMarker> on the server for processing.
    +  */
       addMissionEventHandler["MarkerUpdated", {
         params["_marker", "_local"];
     
    @@ -221,6 +243,15 @@ EGVAR(listener,markers) = [QGVARMAIN(handleMarker), {
       }];
     
       // handle marker deletions
    +  /*
    +    Event Handler: MarkerDeleted
    +    Description:
    +      Tracks marker deletions. Present on server and all clients. Ignores remotely-owned markers.
    +
    +      References <OCAP_settings_excludeMarkerFromRecord> on each local machine to determine if a marker should be recorded.
    +
    +      If so, sends marker data to <OCAP_handleMarker> on the server for processing.
    +  */
       addMissionEventHandler["MarkerDeleted", {
         params["_marker", "_local"];
     
    diff --git a/x/ocap/addons/recorder/fnc_init.sqf b/x/ocap/addons/recorder/fnc_init.sqf
    index d96806b..8e924f9 100644
    --- a/x/ocap/addons/recorder/fnc_init.sqf
    +++ b/x/ocap/addons/recorder/fnc_init.sqf
    @@ -1,9 +1,10 @@
     /* ----------------------------------------------------------------------------
    -Script: ocap_fnc_init
    +FILE: fnc_init.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_init
     
     Description:
    -  Automatic Start: Called from ocap_fnc_autoStart.
    -  Manual Start: Server execution to begin.
    +  Initializes event listeners, event handlers, gathers <OCAP_version> and <OCAP_extension_version>, and kicks off waiters for the auto-start conditions if settings are configured to enable it.
     
     Parameters:
       None
    @@ -11,10 +12,8 @@ Parameters:
     Returns:
       Nothing
     
    -Examples:
    -  --- Code
    -  call ocap_fnc_init;
    -  ---
    +Example:
    +  > call OCAP_recorder_fnc_init
     
     Public:
       No
    @@ -37,24 +36,60 @@ if (!isNil QGVAR(startTime)) exitWith {
     
     // "debug_console" callExtension format["clientState: %1 (%2) | %3", getClientState, getClientStateNumber, __FILE__];
     
    -// bool: GVAR(recording)
    +// VARIABLE: OCAP_recorder_recording
    +// Global variable that represents whether or not recording is active [Bool]
     GVAR(recording) = false;
     publicVariable QGVAR(recording);
    -// int: GVAR(captureFrameNo)
    +
    +/*
    +  VARIABLE: OCAP_recorder_captureFrameNo
    +  Global variable that represents the current frame number [Number]
    +*/
     GVAR(captureFrameNo) = 0;
     publicVariable QGVAR(captureFrameNo);
    +
    +/*
    +  VARIABLE: OCAP_recorder_nextId
    +  Global variable that represents the next available id to assign to a unit or vehicle [Number]
    +*/
     GVAR(nextId) = 0;
     
    +
    +
     // save static setting values so changes during a mission don't interrupt timeline
    +
    +/*
    +  VARIABLE: OCAP_recorder_frameCaptureDelay
    +  Global variable that represents the delay between frame captures in seconds. Gathered from CBA settings at init. [Number]
    +*/
     GVAR(frameCaptureDelay) = EGVAR(settings,frameCaptureDelay);
    +
    +/*
    +  VARIABLE: OCAP_recorder_autoStart
    +  Global variable that represents whether or not recording should automatically start. Gathered from CBA settings at init. [Bool]
    +*/
     GVAR(autoStart) = EGVAR(settings,autoStart);
    +
    +
    +/*
    +  VARIABLE: OCAP_recorder_minMissionTime
    +  Global variable that represents the minimum mission time in seconds to qualify for saving. Can be overridden by using the <ocap_exportData> CBA event. Gathered from CBA settings at init. [Number]
    +*/
     GVAR(minMissionTime) = EGVAR(settings,minMissionTime);
    +
     GVAR(projectileMonitorMultiplier) = 1;
     
    -// macro: GVARMAIN(version)SION
    +/*
    +  VARIABLE: OCAP_version
    +  Global variable that represents the version of OCAP addon being used [String]
    +*/
     GVARMAIN(version) = QUOTE(VERSION_STR);
     publicVariable QGVARMAIN(version);
     
    +/*
    +  VARIABLE: OCAP_extension_version
    +  Global variable that represents the version of OCAP extension being used [String]
    +*/
     EGVAR(extension,version) = ([":VERSION:", []] call EFUNC(extension,sendData));
     publicVariable QEGVAR(extension,version);
     
    diff --git a/x/ocap/addons/recorder/fnc_isKindOfApc.sqf b/x/ocap/addons/recorder/fnc_isKindOfApc.sqf
    index 189377c..4a86356 100644
    --- a/x/ocap/addons/recorder/fnc_isKindOfApc.sqf
    +++ b/x/ocap/addons/recorder/fnc_isKindOfApc.sqf
    @@ -1,3 +1,26 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_isKindOfApc.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_isKindOfApc
    +
    +Description:
    +  Helper function for <OCAP_recorder_fnc_getClass> to prevent APCs from being classified as Cars or Trucks.
    +
    +Parameters:
    +  _this - The vehicle to check [Object]
    +
    +Returns:
    +  [Bool] - True if the vehicle is an APC, false otherwise
    +
    +Examples:
    +  > if (_this call FUNC(isKindOfApc)) exitWith {"apc"};
    +
    +Public:
    +  No
    +
    +Author:
    +  Dell, Zealot
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     _bool = false;
    diff --git a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf
    index 6c4b15a..1079e91 100644
    --- a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf
    +++ b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf
    @@ -1,6 +1,35 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_projectileMonitors.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_projectileMonitors
    +
    +Description:
    +  This initializes projectile monitoring for the purposes of moving non-bullet projectile markers across the map during playback as well as to display them on the in-game map while <OCAP_isDebug> is true.
    +
    +  On clients, it will create a "Draw" UI event handler to display both fire-lines representing bullets and markers representing non-bullet projectiles. It will also create an event handler used by the server in <OCAP_recorder_fnc_eh_firedMan> to integrate new projectiles to the array being procesed by the "Draw" handler.
    +
    +  On the server, it will initialize <OCAP_recorder_liveMissiles> and <OCAP_recorder_liveGrenades>. These are watch arrays that are used to track the position of non-bullet projectiles and update the extension with their positions as they travel. This causes the effect of a 'moving marker' during playback.
    +
    +Parameters:
    +  None
    +
    +Returns:
    +  Nothing
    +
    +Example:
    +  > call FUNC(projectileMonitors);
    +
    +Public:
    +  No
    +
    +Author:
    +  IndigoFox
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     // PFH to track missiles, rockets, shells
    +// Variable: OCAP_recorder_liveMissiles
    +// Watched array of missiles, rockets, shells, and any other unaccounted projectile. Every 0.7 seconds, the position of each object in the array is updated and sent to the extension.
     GVAR(liveMissiles) = [];
     [{
       GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x#0)};
    @@ -15,6 +44,8 @@ GVAR(liveMissiles) = [];
     }, 0.7] call CBA_fnc_addPerFrameHandler;
     
     // PFH to track grenades, flares, thrown charges
    +// Variable: OCAP_recorder_liveGrenades
    +// Watched array of grenades, flares, and thrown charges. Every 0.7 seconds, the position of each object in the array is updated and sent to the extension.
     GVAR(liveGrenades) = [];
     [{
       GVAR(liveGrenades) = GVAR(liveGrenades) select {!isNull (_x#0)};
    @@ -38,6 +69,9 @@ GVAR(liveGrenades) = [];
       if (!hasInterface) exitWith {};
       [] spawn {
         waitUntil {!isNull (findDisplay 12)};
    +
    +    // Variable: OCAP_recorder_liveDebugBullets
    +    // Used by clients to draw bullet lines. Entered via <OCAP_recorder_fnc_eh_firedMan> and managed in ??
         GVAR(liveDebugBullets) = [];
         disableSerialization;
         (findDisplay 12 displayCtrl 51) ctrlAddEventHandler ["Draw", {
    @@ -67,6 +101,9 @@ GVAR(liveGrenades) = [];
       if (!hasInterface) exitWith {};
       [] spawn {
         waitUntil {!isNull (findDisplay 12)};
    +
    +    // Variable: OCAP_recorder_liveDebugMagIcons
    +    // Used by clients to draw magazine icons of non-bullet projectiles. Entered via <OCAP_recorder_fnc_eh_firedMan> and managed in ??
         GVAR(liveDebugMagIcons) = [];
         disableSerialization;
         (findDisplay 12 displayCtrl 51) ctrlAddEventHandler ["Draw", {
    diff --git a/x/ocap/addons/recorder/fnc_startRecording.sqf b/x/ocap/addons/recorder/fnc_startRecording.sqf
    index 796d272..6f80f79 100644
    --- a/x/ocap/addons/recorder/fnc_startRecording.sqf
    +++ b/x/ocap/addons/recorder/fnc_startRecording.sqf
    @@ -1,8 +1,30 @@
    -/*
    -  Start Recording
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_startRecording.sqf
     
    -  This is the initial recording start function. If it hasn't been called from anywhere already, it'll get everything in order to initiate a session for this mission.
    -*/
    +FUNCTION: OCAP_recorder_fnc_startRecording
    +
    +Description:
    +  Begins recording the current mission.
    +
    +  Called via <OCAP_record> via direct CBA event or the administrative diary entry, or by a waiter in <OCAP_recorder_fnc_init> (see <OCAP_settings_autoStart>).
    +
    +  Will not start recording if <OCAP_recorder_recording> is true and will notify players.
    +
    +Parameters:
    +  None
    +
    +Returns:
    +  Nothing
    +
    +Examples:
    +  > call FUNC(startRecording);
    +
    +Public:
    +  No
    +
    +Author:
    +  Dell, Zealot, IndigoFox
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     // disregard recording attempts while OCAP is disabled.
    diff --git a/x/ocap/addons/recorder/fnc_stopRecording.sqf b/x/ocap/addons/recorder/fnc_stopRecording.sqf
    index 7e0e992..f553370 100644
    --- a/x/ocap/addons/recorder/fnc_stopRecording.sqf
    +++ b/x/ocap/addons/recorder/fnc_stopRecording.sqf
    @@ -1,3 +1,28 @@
    +/* ----------------------------------------------------------------------------
    +FILE: fnc_stopRecording.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_stopRecording
    +
    +Description:
    +  Stops recording the current mission. Can be used to pause the recording for later resumption. Also called automatically as part of <OCAP_recorder_fnc_exportData>.
    +
    +  Called via <OCAP_pause> via direct CBA event or the administrative diary entry.
    +
    +Parameters:
    +  None
    +
    +Returns:
    +  Nothing
    +
    +Examples:
    +  > call FUNC(stopRecording);
    +
    +Public:
    +  No
    +
    +Author:
    +  Dell, Zealot, IndigoFox
    +---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     private _systemTimeFormat = ["%1-%2-%3T%4:%5:%6.%7"];
    diff --git a/x/ocap/addons/recorder/fnc_updateTime.sqf b/x/ocap/addons/recorder/fnc_updateTime.sqf
    index a8bba74..3cc3c41 100644
    --- a/x/ocap/addons/recorder/fnc_updateTime.sqf
    +++ b/x/ocap/addons/recorder/fnc_updateTime.sqf
    @@ -1,5 +1,7 @@
     /* ----------------------------------------------------------------------------
    -Script: FUNC(updateTime)
    +FILE: fnc_updateTime.sqf
    +
    +FUNCTION: OCAP_recorder_fnc_updateTime
     
     Description:
     	Sends server's system time, mission environment date/time, time multiplier setting, and time since mission start (post-briefing) to the extension. Will run on a recurring basis as part of <FUNC(captureLoop)> if the setting in userconfig.hpp is configured to do so. This is required in missions that utilize time acceleration or have time skips as part of mission flow.
    @@ -11,9 +13,7 @@ Returns:
     	Nothing
     
     Examples:
    -	--- Code
    -	[] call FUNC(updateTime);
    -	---
    +	> [] call FUNC(updateTime);
     
     Public:
     	No
    @@ -21,7 +21,6 @@ Public:
     Author:
     	Fank
     ---------------------------------------------------------------------------- */
    -
     #include "script_component.hpp"
     
     params [
    diff --git a/x/ocap/addons/recorder/script_component.hpp b/x/ocap/addons/recorder/script_component.hpp
    index cfcd3a9..ac61b0f 100644
    --- a/x/ocap/addons/recorder/script_component.hpp
    +++ b/x/ocap/addons/recorder/script_component.hpp
    @@ -1,4 +1,8 @@
    +// HEADER: script_component.hpp
    +
    +// DEFINE: COMPONENT
     #define COMPONENT recorder
    +// DEFINE: COMPONENT_BEAUTIFIED
     #define COMPONENT_BEAUTIFIED Recorder
     
     #include "\x\ocap\addons\main\script_macros.hpp"
    
    From a46b2b30a54792e9e6095f02c4c09fa843dae438 Mon Sep 17 00:00:00 2001
    From: IndigoFox <indifox926@gmail.com>
    Date: Wed, 19 Oct 2022 21:48:06 -0400
    Subject: [PATCH 23/26] move marker deletions back to projectileMonitors
    
    ---
     x/ocap/addons/recorder/fnc_eh_firedMan.sqf    | 24 ++++++------
     .../recorder/fnc_projectileMonitors.sqf       | 38 +++++++++++--------
     2 files changed, 34 insertions(+), 28 deletions(-)
    
    diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    index 8825915..4082112 100644
    --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    @@ -199,9 +199,9 @@ _projectile addEventHandler ["Deleted", {
       _markName = _projectile getVariable QGVAR(markName);
       _firer = _projectile getVariable QGVAR(firer);
       [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -  [{
    -    [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -  }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +  // [{
    +  //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +  // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
     }];
     
     _projectile addEventHandler ["Explode", {
    @@ -209,9 +209,9 @@ _projectile addEventHandler ["Explode", {
       _markName = _projectile getVariable QGVAR(markName);
       _firer = _projectile getVariable QGVAR(firer);
       [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -  [{
    -    [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -  }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +  // [{
    +  //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +  // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
     }];
     
     
    @@ -317,9 +317,9 @@ switch (true) do {
               _markName = _projectile getVariable QGVAR(markName);
               _firer = _projectile getVariable QGVAR(firer);
               [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -          [{
    -            [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -          }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +          // [{
    +          //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +          // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
             }];
     
             _projectile addEventHandler ["Explode", {
    @@ -327,9 +327,9 @@ switch (true) do {
               _markName = _projectile getVariable QGVAR(markName);
               _firer = _projectile getVariable QGVAR(firer);
               [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -          [{
    -            [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -          }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +          // [{
    +          //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +          // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
             }];
     
             // Add to debug
    diff --git a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf
    index 1079e91..73d4f57 100644
    --- a/x/ocap/addons/recorder/fnc_projectileMonitors.sqf
    +++ b/x/ocap/addons/recorder/fnc_projectileMonitors.sqf
    @@ -32,32 +32,38 @@ Author:
     // Watched array of missiles, rockets, shells, and any other unaccounted projectile. Every 0.7 seconds, the position of each object in the array is updated and sent to the extension.
     GVAR(liveMissiles) = [];
     [{
    -  GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x#0)};
    -
    -  // for missiles that still exist, update positions
       {
    -    _x params ["_obj", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal"];
    -    _nowPos = getPosASL (_x#0);
    -    _x set [3, _nowPos];
    -    [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent;
    +    _x params ["_projectile", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal"];
    +    if (isNull _projectile) then {
    +      [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent;
    +    } else {
    +      _pos = getPosASL _projectile;
    +      GVAR(liveMissiles) set [_forEachIndex, [_projectile, _wepString, _firer, _pos, _markName, _markTextLocal]];
    +      [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent;
    +    };
       } forEach GVAR(liveMissiles);
    -}, 0.7] call CBA_fnc_addPerFrameHandler;
    +
    +  GVAR(liveMissiles) = GVAR(liveMissiles) select {!isNull (_x select 0)};
    +}, GVAR(frameCaptureDelay)] call CBA_fnc_addPerFrameHandler;
     
     // PFH to track grenades, flares, thrown charges
     // Variable: OCAP_recorder_liveGrenades
     // Watched array of grenades, flares, and thrown charges. Every 0.7 seconds, the position of each object in the array is updated and sent to the extension.
     GVAR(liveGrenades) = [];
     [{
    -  GVAR(liveGrenades) = GVAR(liveGrenades) select {!isNull (_x#0)};
    -
    -  // for grenades that still exist, update positions
       {
    -    _x params ["_obj", "_magazine", "_firer", "_pos", "_markName", "_markTextLocal", "_ammoSimType"];
    -    _nowPos = getPosASL (_x#0);
    -    _x set [3, _nowPos];
    -    [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _nowPos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent;
    +    _x params ["_projectile", "_wepString", "_firer", "_pos", "_markName", "_markTextLocal", "_ammoSimType"];
    +    if (isNull _projectile) then {
    +      [QGVARMAIN(handleMarker), ["DELETED", _markName]] call CBA_fnc_localEvent;
    +    } else {
    +      _pos = getPosASL _projectile;
    +      GVAR(liveGrenades) set [_forEachIndex, [_projectile, _wepString, _firer, _pos, _markName, _markTextLocal, _ammoSimType]];
    +      [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir (_x#0), "", "", 1]] call CBA_fnc_localEvent;
    +    };
       } forEach GVAR(liveGrenades);
    -}, 0.7] call CBA_fnc_addPerFrameHandler;
    +
    +  GVAR(liveGrenades) = GVAR(liveGrenades) select {!isNull (_x select 0)};
    +}, GVAR(frameCaptureDelay)] call CBA_fnc_addPerFrameHandler;
     
     
     
    
    From 436f0ccaf9417149f129c72e4a5f612c54ac15f9 Mon Sep 17 00:00:00 2001
    From: IndigoFox <indifox926@gmail.com>
    Date: Wed, 22 Feb 2023 13:23:26 -0800
    Subject: [PATCH 24/26] fix non-deleting UGL rounds - testing on dedi w AI
    
    ---
     x/ocap/addons/recorder/XEH_prep.sqf           |   1 -
     x/ocap/addons/recorder/fnc_adminUIcontrol.sqf | 234 ++++-----
     x/ocap/addons/recorder/fnc_eh_firedMan.sqf    | 495 +++++++++---------
     .../fnc_eh_onUserAdminStateChanged.sqf        |  45 +-
     x/ocap/addons/recorder/fnc_init.sqf           |   2 +
     5 files changed, 382 insertions(+), 395 deletions(-)
    
    diff --git a/x/ocap/addons/recorder/XEH_prep.sqf b/x/ocap/addons/recorder/XEH_prep.sqf
    index 7ddd5ca..6019b7d 100644
    --- a/x/ocap/addons/recorder/XEH_prep.sqf
    +++ b/x/ocap/addons/recorder/XEH_prep.sqf
    @@ -33,7 +33,6 @@ PREP(getWeaponDisplayData);
     PREP(projectileMonitors);
     PREP(entityMonitors);
     
    -PREP(aceThrowing);
     PREP(aceExplosives);
     
     PREP(exportData);
    diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    index bfa23ba..3032f54 100644
    --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    @@ -1,156 +1,154 @@
     /*
    -  FILE: fnc_adminUIControl.sqf
    +	  FILE: fnc_adminUIControl.sqf
     
    -  FUNCTION: OCAP_recorder_fnc_adminUIControl
    +	  FUNCTION: OCAP_recorder_fnc_adminUIControl
     
    -  Description:
    -    Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
    +	  Description:
    +	    Runs checks to determine if a player should have the administrative diary entry added or removed upon joining the mission or logging in/out as admin.
     
    -    - <OCAP_recorder_fnc_eh_connected> at mission start to determine if a player is in <OCAP_administratorList>
    -    - <OCAP_recorder_fnc_eh_onUserAdminStateChanged> to add/remove when a player logs in or out as admin on the server
    +	    - <OCAP_recorder_fnc_eh_connected> at mission start to determine if a player is in <OCAP_administratorList>
    +	    - <OCAP_recorder_fnc_eh_onUserAdminStateChanged> to add/remove when a player logs in or out as admin on the server
     
    -  Parameters:
    -    _PID - PlayerID indicating unique network client on the server [String]
    -    _event - Event that triggered this call [[String], one of: "connect", "login", "logout"]
    +	  Parameters:
    +	    _PID - PlayerID indicating unique network client on the server [String]
    +	    _event - Event that triggered this call [[String], one of: "connect", "login", "logout"]
     
    -  Returns:
    -    Nothing
    +	  Returns:
    +	    Nothing
     
    -  Examples:
    -    > ["1234567890", "connect"] call FUNC(adminUIControl);
    +	  Examples:
    +	    > ["1234567890", "connect"] call FUNC(adminUIControl);
     
    -  Public:
    -    No
    +	  Public:
    +	    No
     
    -  Author:
    -    IndigoFox
    +	  Author:
    +	    IndigoFox
     */
     
     #include "script_component.hpp"
     
     params [
    -  "_PID",
    -  ["_event", "", [""]]
    +	"_PID",
    +	["_event", "", [""]]
     ];
     
     if (isNil "_PID") exitWith {};
     
     private _userInfo = (getUserInfo _PID);
    +if (isNil "_userInfo") exitWith {};
     _userInfo params ["_playerID", "_owner", "_playerUID"];
     _unit = _userInfo select 10;
     
    -
     _fnc_addControls = {
    -  params ["_owner","_unit"];
    -  // add controls to diary entry
    -  {
    -    [{getClientStateNumber > 9 && !isNull player}, {
    -
    -      player createDiarySubject [
    -        QEGVAR(diary,adminControls_subject),
    -        "OCAP Admin",
    -        "\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa"
    -      ];
    -
    -      EGVAR(diary,adminControls_record) = player createDiaryRecord [
    -        QEGVAR(diary,adminControls_subject),
    -        [
    -          "Controls",
    -          format[
    -            "<br/>These controls can be used to Start Recording, Pause Recording, and Save/Export the Recording. On the backend, these use the corresponding CBA server events that can be found in the documentation. Because of this, they override the default minimum duration required to save, so be aware that clicking ""Stop and Export Recording"" will save and upload your current recording regardless of its duration.<br/><br/><execute expression='[""%1""] call CBA_fnc_serverEvent;'>Start/Resume Recording</execute><br/><execute expression='[""%2""] call CBA_fnc_serverEvent;'>Pause Recording</execute><br/><execute expression='[""%3""] call CBA_fnc_serverEvent;'>Stop and Export Recording</execute>",
    -            QGVARMAIN(record),
    -            QGVARMAIN(pause),
    -            QGVARMAIN(exportData)
    -          ]
    -        ]
    -      ];
    -    }] call CBA_fnc_waitUntilAndExecute;
    -  } remoteExec ["call", _owner];
    -
    -  // set variable on unit
    -  _unit setVariable [QGVARMAIN(hasAdminControls), true];
    +	params ["_owner", "_unit"];
    +	  // add controls to diary entry
    +	{
    +		[{
    +			getClientStateNumber > 9 && !isNull player
    +		}, {
    +			player createDiarySubject [
    +				QEGVAR(diary,adminControls_subject),
    +				"OCAP Admin",
    +				"\A3\ui_f\data\igui\cfg\simpleTasks\types\interact_ca.paa"
    +			];
    +
    +			EGVAR(diary,adminControls_record) = player createDiaryRecord [
    +				QEGVAR(diary,adminControls_subject),
    +				[
    +					"Controls",
    +					format[
    +						"<br/>These controls can be used to Start Recording, Pause Recording, and Save/Export the Recording. On the backend, these use the corresponding CBA server events that can be found in the documentation. Because of this, they override the default minimum duration required to save, so be aware that clicking ""Stop and Export Recording"" will save and upload your current recording regardless of its duration.<br/><br/><execute expression='[""%1""] call CBA_fnc_serverEvent;
    +						'>Start/Resume Recording</execute><br/><execute expression='[""%2""] call CBA_fnc_serverEvent;'>Pause Recording</execute><br/><execute expression='[""%3""] call CBA_fnc_serverEvent;'>Stop and Export Recording</execute>",
    +						QGVARMAIN(record),
    +						QGVARMAIN(pause),
    +						QGVARMAIN(exportData)
    +					]
    +				]
    +			];
    +		}] call CBA_fnc_waitUntilAndExecute;
    +	} remoteExec ["call", _owner];
    +
    +	  // set variable on unit
    +	_unit setVariable [QGVARMAIN(hasAdminControls), true];
     };
     
     _fnc_removeControls = {
    -  params ["_owner","_unit"];
    -  {
    -    player removeDiarySubject QEGVAR(diary,adminControls_subject);
    -    player setVariable [QGVARMAIN(hasAdminControls), false, 2];
    -  } remoteExec ["call", _owner];
    -
    -  // Variable: OCAP_hasAdminControls
    -  // Applied on units processed in <OCAP_recorder_fnc_adminUIControl>. Indicates whether or not they have the administrative diary entry available. Server missionNamespace only.
    -  _unit setVariable [QGVARMAIN(hasAdminControls), false];
    +	params ["_owner", "_unit"];
    +	{
    +		player removeDiarySubject QEGVAR(diary,adminControls_subject);
    +		player setVariable [QGVARMAIN(hasAdminControls), false, 2];
    +	} remoteExec ["call", _owner];
    +
    +	  // Variable: OCAP_hasAdminControls
    +	  // Applied on units processed in <OCAP_recorder_fnc_adminUIControl>. Indicates whether or not they have the administrative diary entry available. Server missionNamespace only.
    +	_unit setVariable [QGVARMAIN(hasAdminControls), false];
     };
     
    -
    -
     // check if admin
     private _adminUIDs = missionNamespace getVariable [QGVARMAIN(administratorList), nil];
     
     if (isNil "_adminUIDs") exitWith {
    -  // At this point, no adminUIDs are defined in missionNamespace or in CBA settings
    -  WARNING("Failed to parse administrator list setting. Please check its value!");
    -
    -
    -  switch (_event) do {
    -    case "connect": {
    -      // A player just joined the mission and no admin list exists - skip
    -    };
    -    case "login": {
    -      // A player just logged in so add controls if they don't already have them
    -      if !(_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    -        [_owner, _unit] call _fnc_addControls;
    -        if (GVARMAIN(isDebug)) then {
    -          format["%1 was granted OCAP control by logging in as admin", name _unit] SYSCHAT;
    -        };
    -      };
    -    };
    -    case "logout": {
    -      // A player just logged out so remove controls if they have them
    -      if (_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    -        [_owner, _unit] call _fnc_removeControls;
    -        if (GVARMAIN(isDebug)) then {
    -          format["%1 had their admin controls removed due to logging out from admin", name _unit] SYSCHAT;
    -        };
    -      };
    -    };
    -    default {};
    -  };
    +	// At this point, no adminUIDs are defined in missionNamespace or in CBA settings
    +	WARNING("Failed to parse administrator list setting. Please check its value!");
    +
    +	switch (_event) do {
    +		case "connect": {
    +			// A player just joined the mission and no admin list exists - skip
    +		};
    +		case "login": {
    +			// A player just logged in so add controls if they don't already have them
    +			if !(_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    +				[_owner, _unit] call _fnc_addControls;
    +				if (GVARMAIN(isDebug)) then {
    +					format["%1 was granted OCAP control by logging in as admin", name _unit] SYSCHAT;
    +				};
    +			};
    +		};
    +		case "logout": {
    +			// A player just logged out so remove controls if they have them
    +			if (_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    +				[_owner, _unit] call _fnc_removeControls;
    +				if (GVARMAIN(isDebug)) then {
    +					format["%1 had their admin controls removed due to logging out from admin", name _unit] SYSCHAT;
    +				};
    +			};
    +		};
    +		default {};
    +	};
     };
     
    -
    -// Admin list is defined, so we check if the player is listed by playerUID
    +// admin list is defined, so we check if the player is listed by playerUID
     private _inAdminList = _playerUID in _adminUIDs;
     
     switch (_event) do {
    -  case "connect": {
    -    // A player just joined the mission
    -    // If they are an admin, we add the diary entry
    -    if (_inAdminList) then {
    -      [_owner, _unit] call _fnc_addControls;
    -      if (GVARMAIN(isDebug)) then {
    -        format["%1 was granted OCAP control due to being in the administratorList", name _unit] SYSCHAT;
    -      };
    -    };
    -  };
    -  case "login": {
    -    // A player just logged in so add controls if they don't already have them
    -    if !(_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    -      [_owner, _unit] call _fnc_addControls;
    -      if (GVARMAIN(isDebug)) then {
    -        format["%1 was granted OCAP control by logging in as admin", name _unit] SYSCHAT;
    -      };
    -    };
    -  };
    -  case "logout": {
    -    // A player just logged out so remove controls if they have them
    -    if (_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    -      [_owner, _unit] call _fnc_removeControls;
    -      if (GVARMAIN(isDebug)) then {
    -        format["%1 had their admin controls removed due to logging out from admin", name _unit] SYSCHAT;
    -      };
    -    };
    -  };
    -  default {};
    +	case "connect": {
    +		// A player just joined the mission
    +		    // if they are an admin, we add the diary entry
    +		if (_inAdminList) then {
    +			[_owner, _unit] call _fnc_addControls;
    +			if (GVARMAIN(isDebug)) then {
    +				format["%1 was granted OCAP control due to being in the administratorList", name _unit] SYSCHAT;
    +			};
    +		};
    +	};
    +	case "login": {
    +		// A player just logged in so add controls if they don't already have them
    +		if !(_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    +			[_owner, _unit] call _fnc_addControls;
    +			if (GVARMAIN(isDebug)) then {
    +				format["%1 was granted OCAP control by logging in as admin", name _unit] SYSCHAT;
    +			};
    +		};
    +	};
    +	case "logout": {
    +		// A player just logged out so remove controls if they have them
    +		if (_unit getVariable [QGVARMAIN(hasAdminControls), false]) then {
    +			[_owner, _unit] call _fnc_removeControls;
    +			if (GVARMAIN(isDebug)) then {
    +				format["%1 had their admin controls removed due to logging out from admin", name _unit] SYSCHAT;
    +			};
    +		};
    +	};
    +	default {};
     };
    diff --git a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    index 4082112..03ad786 100644
    --- a/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_firedMan.sqf
    @@ -1,59 +1,58 @@
     /* ----------------------------------------------------------------------------
    -FILE: fnc_eh_firedMan.sqf
    +	FILE: fnc_eh_firedMan.sqf
     
    -FUNCTION: OCAP_recorder_fnc_eh_firedMan
    +	FUNCTION: OCAP_recorder_fnc_eh_firedMan
     
    -Description:
    -  Tracks bullet and non-bullet projectiles. This is the code triggered when a unit firing is detected by the "FiredMan" Event Handler applied to units during <OCAP_recorder_fnc_addUnitEventHandlers>.
    +	Description:
    +	  Tracks bullet and non-bullet projectiles. This is the code triggered when a unit firing is detected by the "FiredMan" Event Handler applied to units during <OCAP_recorder_fnc_addUnitEventHandlers>.
     
    -Parameters:
    -  _firer - Unit the event handler is assigned to (the instigator) [Object]
    -  _weapon - Fired weapon [String]
    -  _muzzle - Muzzle that was used [String]
    -  _mode - Current mode of the fired weapon [String]
    -  _ammo - Classname of ammo used [String]
    -  _magazine - Classname of magazine used [String]
    -  _projectile - Object of the projectile that was shot out [Object]
    -  _vehicle - if weapon is vehicle weapon, otherwise objNull [Object]
    +	Parameters:
    +	  _firer - Unit the event handler is assigned to (the instigator) [Object]
    +	  _weapon - Fired weapon [String]
    +	  _muzzle - Muzzle that was used [String]
    +	  _mode - Current mode of the fired weapon [String]
    +	  _ammo - className of ammo used [String]
    +	  _magazine - className of magazine used [String]
    +	  _projectile - Object of the projectile that was shot out [Object]
    +	  _vehicle - if weapon is vehicle weapon, otherwise objNull [Object]
     
    -Returns:
    -  Nothing
    +	Returns:
    +	  Nothing
     
    -Examples:
    -  > [_firer, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile, _vehicle] call FUNC(eh_firedMan);
    +	Examples:
    +	  > [_firer, _weapon, _muzzle, _mode, _ammo, _magazine, _projectile, _vehicle] call FUNC(eh_firedMan);
     
    -Public:
    -  No
    +	Public:
    +	  No
     
    -Author:
    -  IndigoFox, Dell
    +	Author:
    +	  IndigoFox, Dell
     
     ---------------------------------------------------------------------------- */
     
    -
     /*
    -  Variable: OCAP_lastFired
    -  Indicates a formatted string of the last weapon and magazine type fired by the unit. Used for logging hits/kills. Applied to a firing unit.
    +	  Variable: OCAP_lastFired
    +	  Indicates a formatted string of the last weapon and magazine type fired by the unit. Used for logging hits/kills. Applied to a firing unit.
     */
     
     /*
    -  Event Handlers: Projectiles (Bullets)
    -  Deleted - Makes extension call to draw a fire-line between the firer and the final destination.
    -  Explode - Makes extension call to draw a fire-line between the firer and the final destination.
    -  HitPart - Triggered when a projectile hits a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    -  HitExplosion - Triggered when a projectile explodes and damages a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    -
    -  Event Handlers: Projectiles (Non-Bullets)
    -  Deleted - Triggered when a non-bullet projectile is deleted. Updates marker position, then removes it 3 frames later.
    -  Explode - Triggered when a non-bullet projectile explodes. Updates marker position, then removes it 3 frames later.
    -  HitPart - Triggered when a projectile hits a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    -  HitExplosion - Triggered when a projectile explodes and damages a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +	  Event Handlers: Projectiles (Bullets)
    +	  Deleted - Makes extension call to draw a fire-line between the firer and the final destination.
    +	  Explode - Makes extension call to draw a fire-line between the firer and the final destination.
    +	  HitPart - Triggered when a projectile hits a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +	  HitExplosion - Triggered when a projectile explodes and damages a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +
    +	  Event Handlers: Projectiles (Non-Bullets)
    +	  Deleted - Triggered when a non-bullet projectile is deleted. Updates marker position, then removes it 3 frames later.
    +	  Explode - Triggered when a non-bullet projectile explodes. Updates marker position, then removes it 3 frames later.
    +	  HitPart - Triggered when a projectile hits a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
    +	  HitExplosion - Triggered when a projectile explodes and damages a part of a unit. Calls <OCAP_recorder_fnc_eh_projectileHit>.
     */
     
     /*
    -  CBA Events: Projectiles
    -  OCAP_recorder_addDebugBullet - Triggered when a bullet is fired and the debug mode is enabled. Shares recent bullet data to all clients.
    -  OCAP_recorder_addDebugMagIcon - Triggered when a non-bullet projectile is fired and the debug mode is enabled. Shares recent data to all clients.
    +	  CBA Events: Projectiles
    +	  OCAP_recorder_addDebugBullet - Triggered when a bullet is fired and the debug mode is enabled. Shares recent bullet data to all clients.
    +	  OCAP_recorder_addDebugMagIcon - Triggered when a non-bullet projectile is fired and the debug mode is enabled. Shares recent data to all clients.
     */
     
     #include "script_component.hpp"
    @@ -64,23 +63,27 @@ params ["_firer", "_weapon", "_muzzle", "_mode", "_ammo", "_magazine", "_project
     
     private _initialProjPos = getPosASL _projectile;
     if (getPos _firer distance _initialProjPos > 50 || vehicle _firer isKindOf "Air") then {
    -  // if projectile in unscheduled environment is > 50m from FiredMan then likely remote controlled
    -  // we should find the actual firing entity
    -  private _nearest = [_initialProjPos, allUnits select {!isPlayer _x}, 75] call CBA_fnc_getNearest;
    -  if (count _nearest > 0) then {
    -    _firer = _nearest#0;
    -  };
    +	// if projectile in unscheduled environment is > 50m from FiredMan then likely remote controlled
    +	  // we should find the actual firing entity
    +	private _nearest = [_initialProjPos, allUnits select {
    +		!isPlayer _x
    +	}, 75] call CBA_fnc_getNearest;
    +	if (count _nearest > 0) then {
    +		_firer = _nearest#0;
    +	};
     };
     
     // missionNamespace getVariable ["bis_fnc_moduleRemoteControl_unit", _firer];
     // _unit getVariable ["BIS_fnc_moduleRemoteControl_owner", objNull];
     
     // not sent in ACE Throwing events
    -if (isNil "_vehicle") then {_vehicle = objNull};
    +if (isNil "_vehicle") then {
    +	_vehicle = objNull
    +};
     if (!isNull _vehicle) then {
    -  _projectile setShotParents [_vehicle, _firer];
    +	_projectile setShotParents [_vehicle, _firer];
     } else {
    -  _projectile setShotParents [_firer, _firer];
    +	_projectile setShotParents [_firer, _firer];
     };
     
     private _frame = GVAR(captureFrameNo);
    @@ -93,15 +96,14 @@ if (_firerId == -1) exitWith {};
     
     private _wepString = "";
     if (!isNull _vehicle) then {
    -  _wepString = format["%1 [%2]", (configOf _vehicle) call BIS_fnc_displayName, _wepString];
    +	_wepString = format["%1 [%2]", (configOf _vehicle) call BIS_fnc_displayName, _wepString];
     } else {
    -  _wepString = format["%1 [%2]", _muzzleDisp, _magDisp];
    +	_wepString = format["%1 [%2]", _muzzleDisp, _magDisp];
     };
     
     _firer setVariable [QGVARMAIN(lastFired), _wepString];
     (vehicle _firer) setVariable [QGVARMAIN(lastFired), _wepString];
     
    -
     // _ammoSimType
     // "ShotGrenade" // M67
     // "ShotRocket" // S-8
    @@ -114,243 +116,228 @@ _firer setVariable [QGVARMAIN(lastFired), _wepString];
     // "ShotSubmunition" // Hind minigun, cluster artillery
     _ammoSimType = getText(configFile >> "CfgAmmo" >> _ammo >> "simulation");
     
    -
     // Save marker data to projectile namespace for EH later
     _projectile setVariable [QGVAR(firer), _firer];
     _projectile setVariable [QGVAR(firerId), _firerId];
     
    -
     // Track hit events for all projectile types
     _projectile addEventHandler ["HitPart", {
    -  params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius" ,"_surfaceType"];
    -  [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    +	params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius", "_surfaceType"];
    +	[_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
     }];
     
     _projectile addEventHandler ["HitExplosion", {
    -  params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"];
    -  [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    +	params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"];
    +	[_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
     }];
     
    -
    -
     // BULLET PROJECTILES
     
     if (_ammoSimType isEqualTo "shotBullet") exitWith {
    -  // Bullet projectiles
    -  _projectile addEventHandler ["Deleted", {
    -    params ["_projectile"];
    -    _firer = _projectile getVariable [QGVAR(firer), objNull];
    -    _firerId = _projectile getVariable [QGVAR(firerId), -1];
    -    _projectilePos = getPosASL _projectile;
    -
    -    [":FIRED:", [
    -      _firerId,
    -      GVAR(captureFrameNo),
    -      _projectilePos
    -    ]] call EFUNC(extension,sendData);
    -
    -    if (GVARMAIN(isDebug)) then {
    -      OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    -
    -      // add to clients' map draw array
    -      private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    -      [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    -    };
    -  }];
    -  _projectile addEventHandler ["Explode", {
    -    params ["_projectile", "_pos", "_velocity"];
    -    _firer = _projectile getVariable [QGVAR(firer), objNull];
    -    _firerId = _projectile getVariable [QGVAR(firerId), -1];
    -    _projectilePos = getPosASL _projectile;
    -
    -    [":FIRED:", [
    -      _firerId,
    -      GVAR(captureFrameNo),
    -      _projectilePos
    -    ]] call EFUNC(extension,sendData);
    -
    -    if (GVARMAIN(isDebug)) then {
    -      OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    -
    -      // add to clients' map draw array
    -      private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    -      [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    -    };
    -  }];
    +	// Bullet projectiles
    +	_projectile addEventHandler ["Deleted", {
    +		params ["_projectile"];
    +		_firer = _projectile getVariable [QGVAR(firer), objNull];
    +		_firerId = _projectile getVariable [QGVAR(firerId), -1];
    +		_projectilePos = getPosASL _projectile;
    +
    +		[":FIRED:", [
    +			_firerId,
    +			GVAR(captureFrameNo),
    +			_projectilePos
    +		]] call EFUNC(extension,sendData);
    +
    +		if (GVARMAIN(isDebug)) then {
    +			OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    +
    +			      // add to clients' map draw array
    +			private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    +			[QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    +		};
    +	}];
    +	_projectile addEventHandler ["Explode", {
    +		params ["_projectile", "_pos", "_velocity"];
    +		_firer = _projectile getVariable [QGVAR(firer), objNull];
    +		_firerId = _projectile getVariable [QGVAR(firerId), -1];
    +		_projectilePos = getPosASL _projectile;
    +
    +		[":FIRED:", [
    +			_firerId,
    +			GVAR(captureFrameNo),
    +			_projectilePos
    +		]] call EFUNC(extension,sendData);
    +
    +		if (GVARMAIN(isDebug)) then {
    +			OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    +
    +			      // add to clients' map draw array
    +			private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    +			[QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    +		};
    +	}];
     };
     
    -
     // ALL OTHER PROJECTILES
     
    -// Get data for marker
    -([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal","_markName","_markColor","_markerType"];
    +// get data for marker
    +([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData)) params ["_markTextLocal", "_markName", "_markColor", "_markerType"];
     private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture");
     _projectile setVariable [QGVAR(markName), _markName];
     
    -// MAKE MARKER FOR PLAYBACK
    +// MAKE MARKER for PLAYBACK
     _firerPos = getPosASL _firer;
    -[QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1,1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent;
    -
    +[QGVARMAIN(handleMarker), ["CREATED", _markName, _firer, _firerPos, _markerType, "ICON", [1, 1], getDirVisual _firer, "Solid", _markColor, 1, _markTextLocal, true]] call CBA_fnc_localEvent;
     
    -
    -// Move marker, then delete marker, when projectile is deleted or explodes
    +// move marker, then delete marker, when projectile is deleted or explodes
     _projectile addEventHandler ["Deleted", {
    -  params ["_projectile"];
    -  _markName = _projectile getVariable QGVAR(markName);
    -  _firer = _projectile getVariable QGVAR(firer);
    -  [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -  // [{
    -  //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -  // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +	params ["_projectile"];
    +	_markName = _projectile getVariable QGVAR(markName);
    +	_firer = _projectile getVariable QGVAR(firer);
    +	[QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    +	[{
    +		[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +	}, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
     }];
     
     _projectile addEventHandler ["Explode", {
     	params ["_projectile", "_pos", "_velocity"];
    -  _markName = _projectile getVariable QGVAR(markName);
    -  _firer = _projectile getVariable QGVAR(firer);
    -  [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -  // [{
    -  //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -  // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +	_markName = _projectile getVariable QGVAR(markName);
    +	_firer = _projectile getVariable QGVAR(firer);
    +	[QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    +	[{
    +		[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +	}, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
     }];
     
    -
    -
    -
     // Add to debug
     if (GVARMAIN(isDebug)) then {
    -  // add to map draw array
    -  private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor];
    -  [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent;
    +	// add to map draw array
    +	private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor];
    +	[QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent;
     };
     
    -
     switch (true) do {
    -  case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): {
    -    GVAR(liveGrenades) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType];
    -  };
    -
    -  // case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell", "shotSubmunitions"]): {
    -  default {
    -    GVAR(liveMissiles) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal];
    -
    -    if (_ammoSimType isEqualTo "shotSubmunitions") then {
    -
    -      _projectile setVariable [QGVAR(markerData), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))];
    -      _projectile setVariable [QGVAR(EHData), [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType]];
    -
    -      // for every submunition split process here
    -      _projectile addEventHandler ["SubmunitionCreated", {
    -        params ["_projectile", "_submunitionProjectile", "_pos", "_velocity"];
    -        (_projectile getVariable [QGVAR(markerData), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"];
    -        (_projectile getVariable [QGVAR(EHData), []]) params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"];
    -
    -        // Save marker data to projectile namespace for EH later
    -        _submunitionProjectile setVariable [QGVAR(firer), _firer];
    -        _submunitionProjectile setVariable [QGVAR(firerId), _firerId];
    -        _submunitionProjectile setVariable [QGVAR(markName), _markName];
    -
    -        private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture");
    -
    -        // then get data of submunition to determine how to track it
    -        private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _submunitionProjectile) >> "simulation");
    -
    -
    -        // Track hit events for all projectile types
    -        _submunitionProjectile addEventHandler ["HitPart", {
    -          params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius" ,"_surfaceType"];
    -          [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    -        }];
    -
    -        _submunitionProjectile addEventHandler ["HitExplosion", {
    -          params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"];
    -          [_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    -        }];
    -
    -        if (_ammoSimType isEqualTo "shotBullet") exitWith {
    -          // Bullet projectiles
    -          _submunitionProjectile addEventHandler ["Deleted", {
    -            params ["_projectile"];
    -            _firer = _projectile getVariable [QGVAR(firer), objNull];
    -            _firerId = _projectile getVariable [QGVAR(firerId), -1];
    -            _projectilePos = getPosASL _projectile;
    -
    -            [":FIRED:", [
    -              _firerId,
    -              GVAR(captureFrameNo),
    -              _projectilePos
    -            ]] call EFUNC(extension,sendData);
    -
    -            if (GVARMAIN(isDebug)) then {
    -              OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    -
    -              // add to clients' map draw array
    -              private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    -              [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    -            };
    -          }];
    -          _submunitionProjectile addEventHandler ["Explode", {
    -            params ["_projectile"];
    -            _firer = _projectile getVariable [QGVAR(firer), objNull];
    -            _firerId = _projectile getVariable [QGVAR(firerId), -1];
    -            _projectilePos = getPosASL _projectile;
    -
    -            [":FIRED:", [
    -              _firerId,
    -              GVAR(captureFrameNo),
    -              _projectilePos
    -            ]] call EFUNC(extension,sendData);
    -
    -            if (GVARMAIN(isDebug)) then {
    -              OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    -
    -              // add to clients' map draw array
    -              private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    -              [QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    -            };
    -          }];
    -        };
    -
    -        // Move marker, then delete marker, when projectile is deleted or explodes
    -        _submunitionProjectile addEventHandler ["Deleted", {
    -          params ["_projectile"];
    -          _markName = _projectile getVariable QGVAR(markName);
    -          _firer = _projectile getVariable QGVAR(firer);
    -          [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -          // [{
    -          //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -          // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    -        }];
    -
    -        _projectile addEventHandler ["Explode", {
    -          params ["_projectile", "_pos", "_velocity"];
    -          _markName = _projectile getVariable QGVAR(markName);
    -          _firer = _projectile getVariable QGVAR(firer);
    -          [QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    -          // [{
    -          //   [QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    -          // }, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    -        }];
    -
    -        // Add to debug
    -        if (GVARMAIN(isDebug)) then {
    -          // add to map draw array
    -          private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor];
    -          [QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent;
    -        };
    -
    -
    -
    -        switch (true) do {
    -          case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): {
    -            GVAR(liveGrenades) pushBack [_submunitionProjectile, _wepString, _firer, getPosASL _submunitionProjectile, _markName, _markTextLocal, _ammoSimType];
    -          };
    -          // case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell", "shotSubmunitions"]): {
    -          default {
    -            GVAR(liveMissiles) pushBack [_submunitionProjectile, _wepString, _firer, getPosASL _submunitionProjectile, _markName, _markTextLocal];
    -          };
    -        };
    -      }];
    -    };
    -  };
    +	case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): {
    +		GVAR(liveGrenades) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal, _ammoSimType];
    +	};
    +
    +	default {
    +		// case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell", "shotSubmunitions"]):
    +		GVAR(liveMissiles) pushBack [_projectile, _wepString, _firer, getPosASL _projectile, _markName, _markTextLocal];
    +
    +		if (_ammoSimType isEqualTo "shotSubmunitions") then {
    +			_projectile setVariable [QGVAR(markerData), ([_weapon, _muzzle, _ammo, _magazine, _projectile, _vehicle, _ammoSimType] call FUNC(getAmmoMarkerData))];
    +			_projectile setVariable [QGVAR(EHData), [_this, _subTypes, _magazine, _wepString, _firer, _firerId, _firerPos, _frame, _ammoSimType, _subTypesAmmoSimType]];
    +
    +			            // for every submunition split process here
    +			_projectile addEventHandler ["SubmunitionCreated", {
    +				params ["_projectile", "_submunitionProjectile", "_pos", "_velocity"];
    +				(_projectile getVariable [QGVAR(markerData), []]) params ["_markTextLocal", "_markName", "_markColor", "_markerType"];
    +				(_projectile getVariable [QGVAR(EHData), []]) params ["_EHData", "_subTypes", "_magazine", "_wepString", "_firer", "_firerId", "_firerPos", "_frame", "_ammoSimType", "_subTypesAmmoSimType"];
    +
    +				                // Save marker data to projectile namespace for EH later
    +				_submunitionProjectile setVariable [QGVAR(firer), _firer];
    +				_submunitionProjectile setVariable [QGVAR(firerId), _firerId];
    +				_submunitionProjectile setVariable [QGVAR(markName), _markName];
    +
    +				private _magIcon = getText(configFile >> "CfgMagazines" >> _magazine >> "picture");
    +
    +				                // then get data of submunition to determine how to track it
    +				private _ammoSimType = getText(configFile >> "CfgAmmo" >> (typeOf _submunitionProjectile) >> "simulation");
    +
    +				_submunitionProjectile addEventHandler ["HitPart", {
    +					// Track hit events for all projectile types
    +					params ["_projectile", "_hitEntity", "_projectileOwner", "_pos", "_velocity", "_normal", "_component", "_radius", "_surfaceType"];
    +					[_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    +				}];
    +
    +				_submunitionProjectile addEventHandler ["HitExplosion", {
    +					params ["_projectile", "_hitEntity", "_projectileOwner", "_hitThings"];
    +					[_hitEntity, _projectileOwner] call FUNC(eh_projectileHit);
    +				}];
    +
    +				if (_ammoSimType isEqualTo "shotBullet") exitWith {
    +					// Bullet projectiles
    +					_submunitionProjectile addEventHandler ["Deleted", {
    +						params ["_projectile"];
    +						_firer = _projectile getVariable [QGVAR(firer), objNull];
    +						_firerId = _projectile getVariable [QGVAR(firerId), -1];
    +						_projectilePos = getPosASL _projectile;
    +
    +						[":FIRED:", [
    +							_firerId,
    +							GVAR(captureFrameNo),
    +							_projectilePos
    +						]] call EFUNC(extension,sendData);
    +
    +						if (GVARMAIN(isDebug)) then {
    +							// add to clients' map draw array
    +							OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    +
    +							private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    +							[QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    +						};
    +					}];
    +					_submunitionProjectile addEventHandler ["Explode", {
    +						params ["_projectile"];
    +						_firer = _projectile getVariable [QGVAR(firer), objNull];
    +						_firerId = _projectile getVariable [QGVAR(firerId), -1];
    +						_projectilePos = getPosASL _projectile;
    +
    +						[":FIRED:", [
    +							_firerId,
    +							GVAR(captureFrameNo),
    +							_projectilePos
    +						]] call EFUNC(extension,sendData);
    +
    +						if (GVARMAIN(isDebug)) then {
    +							// add to clients' map draw array
    +							OCAPEXTLOG(ARR4("FIRED EVENT: BULLET", GVAR(captureFrameNo), _firerId, str _projectilePos));
    +							private _debugArr = [getPosASL _firer, _projectilePos, [side group _firer] call BIS_fnc_sideColor, cba_missionTime];
    +							[QGVAR(addDebugBullet), _debugArr] call CBA_fnc_globalEvent;
    +						};
    +					}];
    +				};
    +
    +				_submunitionProjectile addEventHandler ["Deleted", {
    +					// move marker, then delete marker, when projectile is deleted or explodes
    +					params ["_projectile"];
    +					_markName = _projectile getVariable QGVAR(markName);
    +					_firer = _projectile getVariable QGVAR(firer);
    +					[QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, getPosASL _projectile, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    +					[{
    +						[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +					}, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +				}];
    +
    +				_projectile addEventHandler ["Explode", {
    +					params ["_projectile", "_pos", "_velocity"];
    +					_markName = _projectile getVariable QGVAR(markName);
    +					_firer = _projectile getVariable QGVAR(firer);
    +					[QGVARMAIN(handleMarker), ["UPDATED", _markName, _firer, _pos, "", "", "", getDir _projectile, "", "", 1]] call CBA_fnc_localEvent;
    +					[{
    +						[QGVARMAIN(handleMarker), ["DELETED", _this]] call CBA_fnc_localEvent;
    +					}, _markName, GVAR(frameCaptureDelay) * 3] call CBA_fnc_waitAndExecute;
    +				}];
    +
    +				if (GVARMAIN(isDebug)) then {
    +					// Add to debug
    +					// add to map draw array
    +					private _debugArr = [_projectile, _magIcon, format["%1 %2 - %3", str side group _firer, name _firer, _markTextLocal], [side group _firer] call BIS_fnc_sideColor];
    +					[QGVAR(addDebugMagIcon), _debugArr] call CBA_fnc_globalEvent;
    +				};
    +
    +				switch (true) do {
    +					case (_ammoSimType in ["shotGrenade", "shotIlluminating", "shotMine", "shotSmokeX", "shotCM"]): {
    +						GVAR(liveGrenades) pushBack [_submunitionProjectile, _wepString, _firer, getPosASL _submunitionProjectile, _markName, _markTextLocal, _ammoSimType];
    +					};
    +
    +					default {
    +						// case (_ammoSimType in ["shotMissile", "shotRocket", "shotShell", "shotSubmunitions"]):
    +						GVAR(liveMissiles) pushBack [_submunitionProjectile, _wepString, _firer, getPosASL _submunitionProjectile, _markName, _markTextLocal];
    +					};
    +				};
    +			}];
    +		};
    +	};
     };
    diff --git a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    index 78a2f3b..da1cfee 100644
    --- a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    @@ -1,40 +1,41 @@
     /* ----------------------------------------------------------------------------
    -FILE: fnc_eh_onUserAdminStateChanged.sqf
    +	FILE: fnc_eh_onUserAdminStateChanged.sqf
     
    -FUNCTION: OCAP_recorder_fnc_eh_onUserAdminStateChanged
    +	FUNCTION: OCAP_recorder_fnc_eh_onUserAdminStateChanged
     
    -Description:
    -  Uses <OCAP_EH_OnUserAdminStateChanged> to detect when someone is has logged in or out of the server and calls <OCAP_recorder_fnc_adminUIControl> to update the admin UI.
    +	Description:
    +	  Uses <OCAP_EH_OnUserAdminStateChanged> to detect when someone is has logged in or out of the server and calls <OCAP_recorder_fnc_adminUIControl> to update the admin UI.
     
    -Parameters:
    -  _networkId - The network ID of the player who has logged in or out of the server [String]
    -  _loggedIn - Whether the player has logged in or out as admin [Boolean]
    -  _votedIn - Whether the player has been voted in or out of admin [Boolean]
    +	Parameters:
    +	  _networkId - The network ID of the player who has logged in or out of the server [String]
    +	  _loggedIn - Whether the player has logged in or out as admin [Boolean]
    +	  _votedIn - Whether the player has been voted in or out of admin [Boolean]
     
    -Returns:
    -  Nothing
    +	Returns:
    +	  Nothing
     
    -Examples:
    -  > call FUNC(eh_onUserAdminStateChanged);
    +	Examples:
    +	  > call FUNC(eh_onUserAdminStateChanged);
     
    -Public:
    -  No
    +	Public:
    +	  No
     
    -Author:
    -  IndigoFox
    +	Author:
    +	  IndigoFox
     ---------------------------------------------------------------------------- */
     #include "script_component.hpp"
     
     params ["_networkId", "_loggedIn", "_votedIn"];
     
    -_object = (getUserInfo _networkId) select 10;
    -if (isNull _object) exitWith {};
    +_userInfo = (getUserInfo _networkId);
    +if (isNil "_userInfo") exitWith {};
    +_object = _userInfo select 10;
     
     if (_loggedIn && !_votedIn) exitWith {
    -  // if user has become admin by logging, not voting, trigger control addition check
    -  [_networkId, "login"] call FUNC(adminUIcontrol);
    +	// if user has become admin by logging, not voting, trigger control addition check
    +	[_networkId, "login"] call FUNC(adminUIcontrol);
     };
     if (!_loggedIn) then {
    -  // if user has logged out, trigger admin control removal
    -  [_networkId, "logout"] call FUNC(adminUIcontrol);
    +	// if user has logged out, trigger admin control removal
    +	[_networkId, "logout"] call FUNC(adminUIcontrol);
     };
    diff --git a/x/ocap/addons/recorder/fnc_init.sqf b/x/ocap/addons/recorder/fnc_init.sqf
    index 8e924f9..de36520 100644
    --- a/x/ocap/addons/recorder/fnc_init.sqf
    +++ b/x/ocap/addons/recorder/fnc_init.sqf
    @@ -26,6 +26,8 @@ Author:
     
     // exit if in 3DEN editor (when loaded in PreInit XEH)
     if (is3DEN || !isMultiplayer) exitWith {};
    +// exit if not server
    +if (!isServer) exitWith {};
     // if OCAP is disabled do nothing
     if (!GVARMAIN(enabled)) exitWith {};
     // if recording has already initialized this session then just start recording, don't re-init
    
    From d4f1cce8fa3ac5fed1a8200c145a8b3b6efbc112 Mon Sep 17 00:00:00 2001
    From: jonpas <jonpas33@gmail.com>
    Date: Tue, 8 Aug 2023 17:35:26 +0200
    Subject: [PATCH 25/26] Fix Zero divisor error in Administrator UI on
     disconnect (#31)
    
    ---
     x/ocap/addons/recorder/fnc_adminUIcontrol.sqf             | 4 ++--
     x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf | 4 ----
     2 files changed, 2 insertions(+), 6 deletions(-)
    
    diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    index 3032f54..b5215f0 100644
    --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    @@ -35,8 +35,8 @@ params [
     
     if (isNil "_PID") exitWith {};
     
    -private _userInfo = (getUserInfo _PID);
    -if (isNil "_userInfo") exitWith {};
    +private _userInfo = getUserInfo _PID;
    +if (_userInfo isEqualTo []) exitWith {};
     _userInfo params ["_playerID", "_owner", "_playerUID"];
     _unit = _userInfo select 10;
     
    diff --git a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    index da1cfee..19e0b3f 100644
    --- a/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    +++ b/x/ocap/addons/recorder/fnc_eh_onUserAdminStateChanged.sqf
    @@ -27,10 +27,6 @@
     
     params ["_networkId", "_loggedIn", "_votedIn"];
     
    -_userInfo = (getUserInfo _networkId);
    -if (isNil "_userInfo") exitWith {};
    -_object = _userInfo select 10;
    -
     if (_loggedIn && !_votedIn) exitWith {
     	// if user has become admin by logging, not voting, trigger control addition check
     	[_networkId, "login"] call FUNC(adminUIcontrol);
    
    From aaa418c7cba034dd528389c7b354b8dad3d2bbe5 Mon Sep 17 00:00:00 2001
    From: jonpas <jonpas33@gmail.com>
    Date: Tue, 8 Aug 2023 17:37:37 +0200
    Subject: [PATCH 26/26] Add Enabled Administrator UI setting (#30)
    
    * Add Enabled Administrator UI setting
    ---
     x/ocap/addons/main/XEH_preInit.sqf            | 25 +++++++++++++++++++
     x/ocap/addons/recorder/fnc_adminUIcontrol.sqf |  2 ++
     2 files changed, 27 insertions(+)
    
    diff --git a/x/ocap/addons/main/XEH_preInit.sqf b/x/ocap/addons/main/XEH_preInit.sqf
    index 95eccbe..5696df1 100644
    --- a/x/ocap/addons/main/XEH_preInit.sqf
    +++ b/x/ocap/addons/main/XEH_preInit.sqf
    @@ -59,6 +59,31 @@ GVAR(allSettings) = [
         false // requires restart to apply
       ],
     
    +  /*
    +    CBA Setting: OCAP_enabledAdministratorUI
    +    Description:
    +      Turns on or off the Administrator UI in the briefing diary. Default: true
    +
    +    Setting Name:
    +      Administrator UI Enabled
    +
    +    Value Type:
    +      Boolean
    +  */
    +  [
    +    QGVARMAIN(enabledAdministratorUI),
    +    "CHECKBOX", // setting type
    +    [
    +      "Administrator UI Enabled", // Pretty name shown inside the ingame settings menu. Can be stringtable entry.
    +      "Turns on or off the Administrator UI in the briefing diary. Default: true"
    +    ],
    +    [COMPONENT_NAME, "Core"], // Pretty name of the category where the setting can be found. Can be stringtable entry.
    +    true, // default enabled
    +    true, // "_isGlobal" flag. Set this to true to always have this setting synchronized between all clients in multiplayer
    +    {}, // function that will be executed once on mission start and every time the setting is changed.
    +    false // requires restart to apply
    +  ],
    +
       /*
         CBA Setting: OCAP_administratorList
         Description:
    diff --git a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    index b5215f0..8d09436 100644
    --- a/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    +++ b/x/ocap/addons/recorder/fnc_adminUIcontrol.sqf
    @@ -28,6 +28,8 @@
     
     #include "script_component.hpp"
     
    +if (!GVARMAIN(enabledAdministratorUI)) exitWith {};
    +
     params [
     	"_PID",
     	["_event", "", [""]]