Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions addons/recorder/XEH_preInit.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -241,6 +321,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:
Expand Down
4 changes: 4 additions & 0 deletions addons/recorder/XEH_prep.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ PREP(updateTime);

PREP(startRecording);
PREP(stopRecording);
PREP(autoRestartMonitor);
PREP(getAutoStartPlayerCount);
PREP(captureLoop);
PREP(isKindOfApc);
PREP(getClass);
Expand All @@ -18,6 +20,8 @@ PREP(addUnitEventHandlers);

PREP(eh_connected);
PREP(eh_disconnected);
PREP(recordPlayerConnectionEvent);
PREP(flushPlayerConnectionEvents);
PREP(eh_onUserAdminStateChanged);
PREP(eh_onUserSelectedPlayer);
PREP(adminUIcontrol);
Expand Down
29 changes: 29 additions & 0 deletions addons/recorder/fnc_autoRestartMonitor.sqf
Original file line number Diff line number Diff line change
@@ -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);
12 changes: 3 additions & 9 deletions addons/recorder/fnc_eh_connected.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ 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);
10 changes: 2 additions & 8 deletions addons/recorder/fnc_eh_disconnected.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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:", [
Expand All @@ -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);
};

Expand Down
38 changes: 38 additions & 0 deletions addons/recorder/fnc_flushPlayerConnectionEvents.sqf
Original file line number Diff line number Diff line change
@@ -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) = [];
26 changes: 26 additions & 0 deletions addons/recorder/fnc_getAutoStartPlayerCount.sqf
Original file line number Diff line number Diff line change
@@ -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
17 changes: 16 additions & 1 deletion addons/recorder/fnc_init.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -225,13 +234,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);
Expand Down
45 changes: 45 additions & 0 deletions addons/recorder/fnc_recordPlayerConnectionEvent.sqf
Original file line number Diff line number Diff line change
@@ -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);
5 changes: 5 additions & 0 deletions addons/recorder/fnc_startRecording.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -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);

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