From 3eb1cd89051d053c6558d11de787c89b1aefd943 Mon Sep 17 00:00:00 2001 From: Yethe Samartaka Date: Sat, 22 Aug 2026 15:30:24 +0200 Subject: [PATCH 1/3] feat(recorder): improve automatic recording lifecycle Added: - Auto-restart and connection event buffering - New CBA settings to configure everything these changes are touching Changed: - Player counting and Steam ID handling --- addons/recorder/XEH_preInit.sqf | 105 ++++++++++++++++++ addons/recorder/XEH_prep.sqf | 4 + addons/recorder/fnc_autoRestartMonitor.sqf | 29 +++++ addons/recorder/fnc_eh_connected.sqf | 12 +- addons/recorder/fnc_eh_disconnected.sqf | 10 +- .../fnc_flushPlayerConnectionEvents.sqf | 38 +++++++ .../recorder/fnc_getAutoStartPlayerCount.sqf | 26 +++++ addons/recorder/fnc_init.sqf | 17 ++- .../fnc_recordPlayerConnectionEvent.sqf | 45 ++++++++ addons/recorder/fnc_startRecording.sqf | 5 + 10 files changed, 273 insertions(+), 18 deletions(-) create mode 100644 addons/recorder/fnc_autoRestartMonitor.sqf create mode 100644 addons/recorder/fnc_flushPlayerConnectionEvents.sqf create mode 100644 addons/recorder/fnc_getAutoStartPlayerCount.sqf create mode 100644 addons/recorder/fnc_recordPlayerConnectionEvent.sqf diff --git a/addons/recorder/XEH_preInit.sqf b/addons/recorder/XEH_preInit.sqf index 4f6616d..fe3f516 100644 --- a/addons/recorder/XEH_preInit.sqf +++ b/addons/recorder/XEH_preInit.sqf @@ -62,6 +62,86 @@ GVAR(allSettings) = [ false // requires restart to apply ], + /* + CBA Setting: OCAP_settings_excludeHeadlessClientsFromAutoStart + Description: + Exclude headless clients when counting players for automatic recording start and restart. Default: true + + Setting Name: + Exclude Headless Clients + + Value Type: + Boolean + */ + [ + QEGVAR(settings,excludeHeadlessClientsFromAutoStart), + "CHECKBOX", + [ + "Exclude Headless Clients", + "Exclude headless clients when counting players for automatic recording start and restart. Default: true" + ], + [COMPONENT_NAME, "Auto-start Settings"], + true, + true, + {}, + false + ], + + /* + CBA Setting: OCAP_settings_autoRestartAfterEmpty + Description: + Automatically start a new recording after an empty-server auto-save once the minimum player count is reached again. Default: true + + Setting Name: + Auto-Restart After Empty Server + + Value Type: + Boolean + */ + [ + QEGVAR(settings,autoRestartAfterEmpty), + "CHECKBOX", + [ + "Auto-Restart After Empty Server", + "Automatically start a new recording after an empty-server auto-save once the minimum player count is reached again. Default: true" + ], + [COMPONENT_NAME, "Auto-start Settings"], + true, + true, + {}, + false + ], + + /* + CBA Setting: OCAP_settings_bufferPlayerConnectionEvents + Description: + Buffer player connect and disconnect events before a recording starts, then replay them at frame 0. Default: true + + Setting Name: + Buffer Player Connection Events + + Value Type: + Boolean + */ + [ + QEGVAR(settings,bufferPlayerConnectionEvents), + "CHECKBOX", + [ + "Buffer Player Connection Events", + "Buffer player connect and disconnect events before a recording starts, then replay them at frame 0. Default: true" + ], + [COMPONENT_NAME, "Auto-start Settings"], + true, + true, + { + params ["_value"]; + if (!_value && {!isNil QGVAR(connectedPlayerNamesBuffer)}) then { + GVAR(connectedPlayerNamesBuffer) = []; + }; + }, + false + ], + // Section: Core @@ -216,6 +296,31 @@ GVAR(allSettings) = [ // Section: Extra Tracking + /* + CBA Setting: OCAP_settings_includeSteamIdInPlayerConnectionEvents + Description: + Include the player's Steam ID as the fourth value in connected and disconnected events. Default: true + + Setting Name: + Include Steam ID in Connection Events + + Value Type: + Boolean + */ + [ + QEGVAR(settings,includeSteamIdInPlayerConnectionEvents), + "CHECKBOX", + [ + "Include Steam ID in Connection Events", + "Include the player's Steam ID as the fourth value in connected and disconnected events: [frame, action, name, id]. Default: true" + ], + [COMPONENT_NAME, "Extra Tracking"], + true, + true, + {}, + false + ], + /* CBA Setting: OCAP_settings_trackTickets Description: diff --git a/addons/recorder/XEH_prep.sqf b/addons/recorder/XEH_prep.sqf index a967ea6..4b56d9b 100644 --- a/addons/recorder/XEH_prep.sqf +++ b/addons/recorder/XEH_prep.sqf @@ -6,6 +6,8 @@ PREP(updateTime); PREP(startRecording); PREP(stopRecording); +PREP(autoRestartMonitor); +PREP(getAutoStartPlayerCount); PREP(captureLoop); PREP(isKindOfApc); PREP(getClass); @@ -18,6 +20,8 @@ PREP(addUnitEventHandlers); PREP(eh_connected); PREP(eh_disconnected); +PREP(recordPlayerConnectionEvent); +PREP(flushPlayerConnectionEvents); PREP(eh_onUserAdminStateChanged); PREP(adminUIcontrol); diff --git a/addons/recorder/fnc_autoRestartMonitor.sqf b/addons/recorder/fnc_autoRestartMonitor.sqf new file mode 100644 index 0000000..162ea5d --- /dev/null +++ b/addons/recorder/fnc_autoRestartMonitor.sqf @@ -0,0 +1,29 @@ +/* ---------------------------------------------------------------------------- +FILE: fnc_autoRestartMonitor.sqf + +FUNCTION: OCAP_recorder_fnc_autoRestartMonitor + +Description: + Periodically checks whether a recording auto-saved because the server became + empty and starts a fresh recording after the player threshold is met again. + +Returns: + Nothing + +Public: + No +---------------------------------------------------------------------------- */ + +#include "script_component.hpp" + +if (!EGVAR(settings,autoRestartAfterEmpty)) exitWith {}; +if (!GVAR(autoRestartAfterEmptyPending)) exitWith {}; +if (GVAR(recording) || {!isNil QGVAR(startTime)}) exitWith {}; +if (getClientStateNumber <= 9) exitWith {}; + +private _playerCount = call FUNC(getAutoStartPlayerCount); +if (_playerCount < EGVAR(settings,minPlayerCount)) exitWith {}; + +GVAR(autoRestartAfterEmptyPending) = false; +INFO_1("Auto-restarting recording after empty-server save; eligible players: %1",_playerCount); +call FUNC(startRecording); diff --git a/addons/recorder/fnc_eh_connected.sqf b/addons/recorder/fnc_eh_connected.sqf index a0e8210..6669b91 100644 --- a/addons/recorder/fnc_eh_connected.sqf +++ b/addons/recorder/fnc_eh_connected.sqf @@ -31,15 +31,9 @@ params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; // skip for server 'connected' message if (_owner isEqualTo 2) exitWith {}; -// log to timeline -[":EVENT:GENERAL:", [ - GVAR(captureFrameNo), - "connected", - _name, - [createHashMapFromArray [ - ["playerUid", _uid] - ]] call CBA_fnc_encodeJSON -]] call EFUNC(extension,sendData); +// Log immediately during an active session, otherwise preserve the event for +// frame 0 of the next recording. +["connected", _name, _uid] call FUNC(recordPlayerConnectionEvent); // trigger admin control check for all connecting players [_idstr, "connect"] call FUNC(adminUIcontrol); diff --git a/addons/recorder/fnc_eh_disconnected.sqf b/addons/recorder/fnc_eh_disconnected.sqf index d95b48a..65c6e95 100644 --- a/addons/recorder/fnc_eh_disconnected.sqf +++ b/addons/recorder/fnc_eh_disconnected.sqf @@ -27,14 +27,7 @@ params ["_unit", "_id", "_uid", "_name"]; -[":EVENT:GENERAL:", [ - GVAR(captureFrameNo), - "disconnected", - _name, - [createHashMapFromArray [ - ["playerUid", _uid] - ]] call CBA_fnc_encodeJSON -]] call EFUNC(extension,sendData); +["disconnected", _name, _uid] call FUNC(recordPlayerConnectionEvent); if (_unit getVariable [QGVARMAIN(isInitialized), false]) then { [":SOLDIER:DELETE:", [ @@ -54,6 +47,7 @@ if ( {(call CBA_fnc_players) - [_unit] isEqualTo []} && {(GVAR(frameCaptureDelay) * GVAR(captureFrameNo)) / 60 >= GVAR(minMissionTime)} ) then { + GVAR(autoRestartAfterEmptyPending) = true; [nil, "Recording ended due to server being empty"] call FUNC(exportData); }; diff --git a/addons/recorder/fnc_flushPlayerConnectionEvents.sqf b/addons/recorder/fnc_flushPlayerConnectionEvents.sqf new file mode 100644 index 0000000..7ed0ddc --- /dev/null +++ b/addons/recorder/fnc_flushPlayerConnectionEvents.sqf @@ -0,0 +1,38 @@ +/* ---------------------------------------------------------------------------- +FILE: fnc_flushPlayerConnectionEvents.sqf + +FUNCTION: OCAP_recorder_fnc_flushPlayerConnectionEvents + +Description: + Replays buffered player connection events into the newly registered recording + at frame 0, then clears the buffer. + +Returns: + Nothing + +Public: + No +---------------------------------------------------------------------------- */ + +#include "script_component.hpp" + +{ + _x params ["_eventType", "_name", "_uid"]; + + private _extraData = createHashMap; + + if (EGVAR(settings,includeSteamIdInPlayerConnectionEvents)) then { + _extraData set ["playerUid", _uid]; + }; + + private _eventData = [ + 0, + _eventType, + _name, + [_extraData] call CBA_fnc_encodeJSON + ]; + + [":EVENT:GENERAL:", _eventData] call EFUNC(extension,sendData); +} forEach GVAR(connectedPlayerNamesBuffer); + +GVAR(connectedPlayerNamesBuffer) = []; diff --git a/addons/recorder/fnc_getAutoStartPlayerCount.sqf b/addons/recorder/fnc_getAutoStartPlayerCount.sqf new file mode 100644 index 0000000..a2a18eb --- /dev/null +++ b/addons/recorder/fnc_getAutoStartPlayerCount.sqf @@ -0,0 +1,26 @@ +/* ---------------------------------------------------------------------------- +FILE: fnc_getAutoStartPlayerCount.sqf + +FUNCTION: OCAP_recorder_fnc_getAutoStartPlayerCount + +Description: + Returns the number of players eligible for automatic recording start. The + configured headless-client exclusion is applied here for both initial start + and automatic restart checks. + +Returns: + Eligible player count [Number] + +Public: + No +---------------------------------------------------------------------------- */ + +#include "script_component.hpp" + +private _players = allPlayers; + +if (EGVAR(settings,excludeHeadlessClientsFromAutoStart)) then { + _players = _players - (entities "HeadlessClient_F"); +}; + +count _players diff --git a/addons/recorder/fnc_init.sqf b/addons/recorder/fnc_init.sqf index 44457f8..dc4081e 100644 --- a/addons/recorder/fnc_init.sqf +++ b/addons/recorder/fnc_init.sqf @@ -54,6 +54,15 @@ publicVariable QGVAR(captureFrameNo); */ GVAR(nextId) = 0; +/* + Connection events which occur before the extension is ready to receive a + recording are replayed at frame 0 when that recording starts. +*/ +GVAR(connectedPlayerNamesBuffer) = []; + +// Only an empty-server auto-save may arm the automatic restart path. +GVAR(autoRestartAfterEmptyPending) = false; + // save static setting values so changes during a mission don't interrupt timeline @@ -218,13 +227,19 @@ call EFUNC(extension,initSession); Start recording AFTER Briefing screen, so the beginning of the recording matches the start of the actual mission session. */ [ - {(getClientStateNumber > 9 && (count allPlayers) >= EGVAR(settings,minPlayerCount) && GVAR(autoStart)) || !isNil QGVAR(startTime)}, + {(getClientStateNumber > 9 && (call FUNC(getAutoStartPlayerCount)) >= 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; +// Periodically re-arm auto-start after an empty-server auto-save. Unlike the +// initial waiter above, this handler remains active for the whole mission. +[{ + call FUNC(autoRestartMonitor); +}, 10] call CBA_fnc_addPerFrameHandler; + if (isNil QGVAR(entityMonitorsInitialized)) then { call FUNC(entityMonitors); diff --git a/addons/recorder/fnc_recordPlayerConnectionEvent.sqf b/addons/recorder/fnc_recordPlayerConnectionEvent.sqf new file mode 100644 index 0000000..de8ba71 --- /dev/null +++ b/addons/recorder/fnc_recordPlayerConnectionEvent.sqf @@ -0,0 +1,45 @@ +/* ---------------------------------------------------------------------------- +FILE: fnc_recordPlayerConnectionEvent.sqf + +FUNCTION: OCAP_recorder_fnc_recordPlayerConnectionEvent + +Description: + Records a player connection event immediately when a recording session is + available, or buffers it for frame 0 of the next recording. + +Parameters: + _eventType - "connected" or "disconnected" [String] + _name - Player name [String] + _uid - Player UID [String] + +Returns: + Nothing + +Public: + No +---------------------------------------------------------------------------- */ + +#include "script_component.hpp" + +params ["_eventType", "_name", "_uid"]; + +if (isNil QGVAR(startTime) || {!EGVAR(extension,sessionReady)}) exitWith { + if (EGVAR(settings,bufferPlayerConnectionEvents)) then { + GVAR(connectedPlayerNamesBuffer) pushBack [_eventType, _name, _uid]; + }; +}; + +private _extraData = createHashMap; + +if (EGVAR(settings,includeSteamIdInPlayerConnectionEvents)) then { + _extraData set ["playerUid", _uid]; +}; + +private _eventData = [ + GVAR(captureFrameNo), + _eventType, + _name, + [_extraData] call CBA_fnc_encodeJSON +]; + +[":EVENT:GENERAL:", _eventData] call EFUNC(extension,sendData); diff --git a/addons/recorder/fnc_startRecording.sqf b/addons/recorder/fnc_startRecording.sqf index 975b60f..ea14f33 100644 --- a/addons/recorder/fnc_startRecording.sqf +++ b/addons/recorder/fnc_startRecording.sqf @@ -39,6 +39,9 @@ if (GVAR(recording) && GVAR(captureFrameNo) > 10) exitWith { ] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; }; +// Any explicit or automatic start consumes a pending empty-server restart. +GVAR(autoRestartAfterEmptyPending) = false; + GVAR(recording) = true; publicVariable QGVAR(recording); @@ -71,12 +74,14 @@ if (GVAR(captureFrameNo) == 0) then { call EFUNC(extension,newMission); // Wait for extension to confirm new mission before starting capture [{EGVAR(extension,sessionReady)}, { + call FUNC(flushPlayerConnectionEvents); call FUNC(captureLoop); }, [], 30, { ERROR("Timeout waiting for new mission confirmation from extension. Recording will not start."); ["OCAP failed to start recording: extension did not respond", 1, [1, 0, 0, 1]] remoteExecCall ["CBA_fnc_notify", [0, -2] select isDedicated]; }] call CBA_fnc_waitUntilAndExecute; } else { + call FUNC(flushPlayerConnectionEvents); call FUNC(captureLoop); }; }; From fdb88445f1e96c60d2f6eab7cdf369ce06aa4877 Mon Sep 17 00:00:00 2001 From: YetheSamartaka <55753928+YetheSamartaka@users.noreply.github.com> Date: Sat, 22 Aug 2026 16:58:14 +0200 Subject: [PATCH 2/3] Update addons/recorder/fnc_eh_connected.sqf Co-authored-by: thegamecracks <61257169+thegamecracks@users.noreply.github.com> --- addons/recorder/fnc_eh_connected.sqf | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/recorder/fnc_eh_connected.sqf b/addons/recorder/fnc_eh_connected.sqf index 0a074ac..d3f0d5d 100644 --- a/addons/recorder/fnc_eh_connected.sqf +++ b/addons/recorder/fnc_eh_connected.sqf @@ -32,6 +32,3 @@ if (_owner isEqualTo 2) exitWith {}; // Log immediately during an active session, otherwise preserve the event for // frame 0 of the next recording. ["connected", _name, _uid] call FUNC(recordPlayerConnectionEvent); - -// Trigger admin control check for all connecting players -[_idstr, "connect"] call FUNC(adminUIcontrol); From 23693b4eda5c95fc48b7f2ba1de473aad038bf15 Mon Sep 17 00:00:00 2001 From: Yethe Samartaka Date: Wed, 14 Oct 2026 07:17:27 +0200 Subject: [PATCH 3/3] fix(recorder): respect auto-start and discard stale connection history --- addons/recorder/XEH_preInit.sqf | 23 ++++------ addons/recorder/fnc_autoRestartMonitor.sqf | 2 +- addons/recorder/fnc_eh_connected.sqf | 3 +- .../fnc_flushPlayerConnectionEvents.sqf | 27 +++--------- addons/recorder/fnc_init.sqf | 6 --- .../fnc_recordPlayerConnectionEvent.sqf | 19 ++++---- docs/recording-lifecycle.md | 43 +++++++++++++++++++ 7 files changed, 70 insertions(+), 53 deletions(-) create mode 100644 docs/recording-lifecycle.md diff --git a/addons/recorder/XEH_preInit.sqf b/addons/recorder/XEH_preInit.sqf index 3e39b7e..a8cfe7b 100644 --- a/addons/recorder/XEH_preInit.sqf +++ b/addons/recorder/XEH_preInit.sqf @@ -65,7 +65,7 @@ GVAR(allSettings) = [ /* CBA Setting: OCAP_settings_excludeHeadlessClientsFromAutoStart Description: - Exclude headless clients when counting players for automatic recording start and restart. Default: true + Exclude headless clients when counting players for automatic recording start and restart. If disabled, headless clients alone can meet the threshold, including after an empty-server save. Default: true Setting Name: Exclude Headless Clients @@ -78,7 +78,7 @@ GVAR(allSettings) = [ "CHECKBOX", [ "Exclude Headless Clients", - "Exclude headless clients when counting players for automatic recording start and restart. Default: true" + "Exclude headless clients when counting players for automatic recording start and restart. If disabled, headless clients alone can meet the threshold, including after an empty-server save. Default: true" ], [COMPONENT_NAME, "Auto-start Settings"], true, @@ -90,7 +90,7 @@ GVAR(allSettings) = [ /* CBA Setting: OCAP_settings_autoRestartAfterEmpty Description: - Automatically start a new recording after an empty-server auto-save once the minimum player count is reached again. Default: true + With Auto Start Recording enabled at mission initialization, automatically start a new recording after an empty-server auto-save once the minimum player count is reached again. Default: true Setting Name: Auto-Restart After Empty Server @@ -103,7 +103,7 @@ GVAR(allSettings) = [ "CHECKBOX", [ "Auto-Restart After Empty Server", - "Automatically start a new recording after an empty-server auto-save once the minimum player count is reached again. Default: true" + "With Auto Start Recording enabled at mission initialization, automatically start a new recording after an empty-server auto-save once the minimum player count is reached again. Default: true" ], [COMPONENT_NAME, "Auto-start Settings"], true, @@ -115,10 +115,10 @@ GVAR(allSettings) = [ /* CBA Setting: OCAP_settings_bufferPlayerConnectionEvents Description: - Buffer player connect and disconnect events before a recording starts, then replay them at frame 0. Default: true + Include currently connected human players at the first recorded frame. Connections and disconnections before recording are not retained. Default: true Setting Name: - Buffer Player Connection Events + Include Connected Players at Recording Start Value Type: Boolean @@ -127,18 +127,13 @@ GVAR(allSettings) = [ QEGVAR(settings,bufferPlayerConnectionEvents), "CHECKBOX", [ - "Buffer Player Connection Events", - "Buffer player connect and disconnect events before a recording starts, then replay them at frame 0. Default: true" + "Include Connected Players at Recording Start", + "Include currently connected human players at the first recorded frame. Connections and disconnections before recording are not retained. Default: true" ], [COMPONENT_NAME, "Auto-start Settings"], true, true, - { - params ["_value"]; - if (!_value && {!isNil QGVAR(connectedPlayerNamesBuffer)}) then { - GVAR(connectedPlayerNamesBuffer) = []; - }; - }, + {}, false ], diff --git a/addons/recorder/fnc_autoRestartMonitor.sqf b/addons/recorder/fnc_autoRestartMonitor.sqf index 162ea5d..93a4cc0 100644 --- a/addons/recorder/fnc_autoRestartMonitor.sqf +++ b/addons/recorder/fnc_autoRestartMonitor.sqf @@ -16,7 +16,7 @@ Public: #include "script_component.hpp" -if (!EGVAR(settings,autoRestartAfterEmpty)) exitWith {}; +if (!GVAR(autoStart) || {!EGVAR(settings,autoRestartAfterEmpty)}) exitWith {}; if (!GVAR(autoRestartAfterEmptyPending)) exitWith {}; if (GVAR(recording) || {!isNil QGVAR(startTime)}) exitWith {}; if (getClientStateNumber <= 9) exitWith {}; diff --git a/addons/recorder/fnc_eh_connected.sqf b/addons/recorder/fnc_eh_connected.sqf index d3f0d5d..e16f06d 100644 --- a/addons/recorder/fnc_eh_connected.sqf +++ b/addons/recorder/fnc_eh_connected.sqf @@ -29,6 +29,5 @@ params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"]; // skip for server 'connected' message if (_owner isEqualTo 2) exitWith {}; -// Log immediately during an active session, otherwise preserve the event for -// frame 0 of the next recording. +// Log during an active session. Recording start snapshots current players. ["connected", _name, _uid] call FUNC(recordPlayerConnectionEvent); diff --git a/addons/recorder/fnc_flushPlayerConnectionEvents.sqf b/addons/recorder/fnc_flushPlayerConnectionEvents.sqf index 7ed0ddc..a668c0c 100644 --- a/addons/recorder/fnc_flushPlayerConnectionEvents.sqf +++ b/addons/recorder/fnc_flushPlayerConnectionEvents.sqf @@ -4,8 +4,8 @@ FILE: fnc_flushPlayerConnectionEvents.sqf FUNCTION: OCAP_recorder_fnc_flushPlayerConnectionEvents Description: - Replays buffered player connection events into the newly registered recording - at frame 0, then clears the buffer. + Records a snapshot of currently connected human players at the first recorded + frame. No connection history is retained between recordings. Returns: Nothing @@ -16,23 +16,8 @@ Public: #include "script_component.hpp" -{ - _x params ["_eventType", "_name", "_uid"]; - - private _extraData = createHashMap; - - if (EGVAR(settings,includeSteamIdInPlayerConnectionEvents)) then { - _extraData set ["playerUid", _uid]; - }; +if (!EGVAR(settings,bufferPlayerConnectionEvents)) exitWith {}; - private _eventData = [ - 0, - _eventType, - _name, - [_extraData] call CBA_fnc_encodeJSON - ]; - - [":EVENT:GENERAL:", _eventData] call EFUNC(extension,sendData); -} forEach GVAR(connectedPlayerNamesBuffer); - -GVAR(connectedPlayerNamesBuffer) = []; +{ + ["connected", name _x, getPlayerUID _x, 1] call FUNC(recordPlayerConnectionEvent); +} forEach (allPlayers - (entities "HeadlessClient_F")); diff --git a/addons/recorder/fnc_init.sqf b/addons/recorder/fnc_init.sqf index 3443ec9..1d8c982 100644 --- a/addons/recorder/fnc_init.sqf +++ b/addons/recorder/fnc_init.sqf @@ -54,12 +54,6 @@ publicVariable QGVAR(captureFrameNo); */ GVAR(nextId) = 0; -/* - Connection events which occur before the extension is ready to receive a - recording are replayed at frame 0 when that recording starts. -*/ -GVAR(connectedPlayerNamesBuffer) = []; - // Only an empty-server auto-save may arm the automatic restart path. GVAR(autoRestartAfterEmptyPending) = false; diff --git a/addons/recorder/fnc_recordPlayerConnectionEvent.sqf b/addons/recorder/fnc_recordPlayerConnectionEvent.sqf index de8ba71..fbdd200 100644 --- a/addons/recorder/fnc_recordPlayerConnectionEvent.sqf +++ b/addons/recorder/fnc_recordPlayerConnectionEvent.sqf @@ -4,13 +4,15 @@ FILE: fnc_recordPlayerConnectionEvent.sqf FUNCTION: OCAP_recorder_fnc_recordPlayerConnectionEvent Description: - Records a player connection event immediately when a recording session is - available, or buffers it for frame 0 of the next recording. + Records a player connection event when a recording session is available. + Events outside a recording are discarded; recording start takes a snapshot + of the players who are still connected instead. Parameters: _eventType - "connected" or "disconnected" [String] _name - Player name [String] _uid - Player UID [String] + _frame - Recording frame, defaults to the current capture frame [optional, Number] Returns: Nothing @@ -21,13 +23,12 @@ Public: #include "script_component.hpp" -params ["_eventType", "_name", "_uid"]; +params ["_eventType", "_name", "_uid", ["_frame", GVAR(captureFrameNo), [0]]]; -if (isNil QGVAR(startTime) || {!EGVAR(extension,sessionReady)}) exitWith { - if (EGVAR(settings,bufferPlayerConnectionEvents)) then { - GVAR(connectedPlayerNamesBuffer) pushBack [_eventType, _name, _uid]; - }; -}; +if (!EGVAR(extension,sessionReady)) exitWith {}; +// startRecording sets recording before the startup snapshot, while captureLoop +// sets startTime afterwards. A paused recording keeps startTime and its events. +if (isNil QGVAR(startTime) && {!GVAR(recording)}) exitWith {}; private _extraData = createHashMap; @@ -36,7 +37,7 @@ if (EGVAR(settings,includeSteamIdInPlayerConnectionEvents)) then { }; private _eventData = [ - GVAR(captureFrameNo), + _frame max 1, _eventType, _name, [_extraData] call CBA_fnc_encodeJSON diff --git a/docs/recording-lifecycle.md b/docs/recording-lifecycle.md new file mode 100644 index 0000000..99c7041 --- /dev/null +++ b/docs/recording-lifecycle.md @@ -0,0 +1,43 @@ +# Automatic recording lifecycle + +After `saveOnEmpty` exports a recording, the recorder checks the player threshold +every 10 seconds. It starts a new recording only if `autoStart` was enabled at +mission initialization and `autoRestartAfterEmpty` is enabled. Manual starts and +paused recordings keep their existing behavior. + +`excludeHeadlessClientsFromAutoStart` still controls the player count for both +initial start and restart. Disabling it allows headless clients alone to meet the +threshold, even after an empty-server save. + +The existing `OCAP_settings_bufferPlayerConnectionEvents` setting now controls +**Include Connected Players at Recording Start**. When enabled, a new recording +includes one `connected` event per currently connected human player at internal +frame 1 (the first recorded frame). Disconnected players and headless clients are +omitted. No connection history is stored while waiting for a recording, so churn +between recordings cannot accumulate or appear in a later recording. Active and +paused recordings still receive live connection events. The Steam ID setting and +`playerUid` payload format are unchanged. + +## Dedicated-server verification + +These scenarios require Arma 3 and the recorder extension; HEMTT checks and builds +do not exercise the server lifecycle. Inspect the resulting recordings as well +as the server notifications. + +1. Enable auto-start, restart and save-on-empty. Reach the player threshold, + record beyond `minMissionTime`, then disconnect everyone. Verify one export; + rejoin below the threshold and verify recording stays stopped; reach the + threshold and verify a fresh recording starts within 10 seconds. Repeat. +2. Disable auto-start before mission initialization, then start manually. Repeat + the empty-server save and rejoin flow. Verify no automatic recording starts, + even when the threshold is reached. Verify manual start still works. +3. Between recordings, connect and disconnect several players. Leave some humans + and a headless client connected, then start. Verify only the humans still + connected have startup `connected` events, at the first frame; no stale + `disconnected` events appear. Repeat before the first recording. +4. Disable the startup-player setting and verify no startup snapshot is emitted. + Check live connect/disconnect events while recording and paused. With Steam + IDs enabled, verify `playerUid`; disable the setting and verify it is omitted. +5. With headless-client exclusion enabled, verify headless clients do not help + reach the start/restart threshold. With it disabled, verify the configured + counting behavior, including the documented headless-only restart case.