From 78b0c23d867ba9bb2e34f95ae5942a47194b35f7 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Tue, 12 May 2026 10:22:01 +0200 Subject: [PATCH 1/4] fix(ace): gate ACE explosives placement on locality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ace_explosives_place is a CBA global event that fires on every machine. On a dedicated server the explosive's position has not yet network-synced when the event fires, so getPosASL returns 0,0,~0 and the recording shows the charge at the map origin. Gate the listener body on `local _explosive` so only the placer — who owns the object with the authoritative position — packages and ships the data via CBA_fnc_serverEvent. This matches the vanilla mine path in fnc_eh_fired_client.sqf. --- addons/recorder/fnc_aceExplosives.sqf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addons/recorder/fnc_aceExplosives.sqf b/addons/recorder/fnc_aceExplosives.sqf index 3e41427..1832c71 100644 --- a/addons/recorder/fnc_aceExplosives.sqf +++ b/addons/recorder/fnc_aceExplosives.sqf @@ -38,6 +38,14 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_explosive", "_dir", "_pitch", "_unit"]; +// ace_explosives_place is a CBA global event — it fires on every machine. On a +// dedicated server the explosive's position has not yet network-synced when the +// event fires (getPosASL returns 0,0,~0). Only the placer (where the object is +// local) has the authoritative position at this moment, so gate execution to it. +// The placer then ships the data to the server via CBA_fnc_serverEvent below, +// matching the vanilla mine path in fnc_eh_fired_client.sqf. +if (!local _explosive) exitWith {}; + // Resolve explosive metadata from config private _explType = typeOf _explosive; private _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); From ba2e73491ca3b10462d02b3fc12df463d5e03a48 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Tue, 12 May 2026 10:45:38 +0200 Subject: [PATCH 2/4] fix(ace): register lifecycle EHs on all machines, gate only the send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback: addEventHandler is local, so registering EHs only on the placer means tracking breaks if locality transfers (e.g. placer disconnect). Move the locality check to gate just the :PLACED:CREATE: send — EH registration now happens on every machine, and whichever one owns the object at event-time fires its local EH. --- addons/recorder/fnc_aceExplosives.sqf | 72 ++++++++++++++------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/addons/recorder/fnc_aceExplosives.sqf b/addons/recorder/fnc_aceExplosives.sqf index 1832c71..e7599ec 100644 --- a/addons/recorder/fnc_aceExplosives.sqf +++ b/addons/recorder/fnc_aceExplosives.sqf @@ -38,42 +38,14 @@ if (!SHOULDSAVEEVENTS) exitWith {}; params ["_explosive", "_dir", "_pitch", "_unit"]; -// ace_explosives_place is a CBA global event — it fires on every machine. On a -// dedicated server the explosive's position has not yet network-synced when the -// event fires (getPosASL returns 0,0,~0). Only the placer (where the object is -// local) has the authoritative position at this moment, so gate execution to it. -// The placer then ships the data to the server via CBA_fnc_serverEvent below, -// matching the vanilla mine path in fnc_eh_fired_client.sqf. -if (!local _explosive) exitWith {}; - -// Resolve explosive metadata from config -private _explType = typeOf _explosive; -private _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); -private _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); -private _explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture"); - -// Get placer's OCAP ID -private _unitOcapId = _unit getVariable [QGVARMAIN(id), -1]; -if (_unitOcapId isEqualTo -1) exitWith {}; - _explosive setVariable [QGVARMAIN(detonated), false]; -// Build :PLACED:CREATE: data — same format as vanilla mines in fnc_eh_fired_client.sqf -private _placedData = [ - EGVAR(recorder,captureFrameNo), // 0: captureFrameNo - -1, // 1: placedId (assigned by server) - _explType, // 2: className - _explosiveDisp, // 3: displayName - (getPosASL _explosive) joinString ",", // 4: position - _unitOcapId, // 5: firerOcapId - str (side group _unit), // 6: side - "put", // 7: weapon - _explosivePic // 8: magazineIcon -]; - -[QGVARMAIN(handlePlacedData), [_placedData, _explosive]] call CBA_fnc_serverEvent; - -// Attach lifecycle EHs — identical to vanilla path in fnc_eh_fired_client.sqf +// Attach lifecycle EHs on every machine. addEventHandler is local, and the +// EH only fires on the machine that owns the object at event-time, so +// registering everywhere keeps tracking resilient to locality transfers +// (e.g. placer disconnect → object transfers to server or another client). +// The server-side polling in fnc_eh_fired_server.sqf is a secondary safety +// net for the server-takeover case. _explosive addEventHandler ["HitExplosion", { params ["_explosive", "_hitEntity", "_explosiveOwner", "_hitThings"]; if (isNull _hitEntity) exitWith {}; @@ -119,3 +91,35 @@ _explosive addEventHandler ["Deleted", { ]; [QGVARMAIN(handlePlacedEvent), [_eventData]] call CBA_fnc_serverEvent; }]; + +// Send :PLACED:CREATE: only from the placer. ace_explosives_place is a CBA +// global event, so it fires on every machine — but on a dedicated server the +// explosive's position has not yet network-synced (getPosASL returns 0,0,~0). +// Only the machine that owns the object has the authoritative position at +// this moment, matching the vanilla mine path in fnc_eh_fired_client.sqf. +if (!local _explosive) exitWith {}; + +// Resolve explosive metadata from config +private _explType = typeOf _explosive; +private _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); +private _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); +private _explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture"); + +// Get placer's OCAP ID +private _unitOcapId = _unit getVariable [QGVARMAIN(id), -1]; +if (_unitOcapId isEqualTo -1) exitWith {}; + +// Build :PLACED:CREATE: data — same format as vanilla mines in fnc_eh_fired_client.sqf +private _placedData = [ + EGVAR(recorder,captureFrameNo), // 0: captureFrameNo + -1, // 1: placedId (assigned by server) + _explType, // 2: className + _explosiveDisp, // 3: displayName + (getPosASL _explosive) joinString ",", // 4: position + _unitOcapId, // 5: firerOcapId + str (side group _unit), // 6: side + "put", // 7: weapon + _explosivePic // 8: magazineIcon +]; + +[QGVARMAIN(handlePlacedData), [_placedData, _explosive]] call CBA_fnc_serverEvent; From 327d7cae3c2393d61b7d98ba56c9fe65f2ce129b Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Tue, 12 May 2026 11:15:37 +0200 Subject: [PATCH 3/4] fix(ace): send :PLACED:CREATE: from server with a 1s settle delay Locality check on the placer was wrong: ace_explosives_place fires before the explosive's position has synced on the server (getPosASL returns 0,0,~0 immediately, correct after ~1s), and OCAP_recorder_captureFrameNo only exists on the server, so a client-side send produced no valid :PLACED:CREATE: at all. Send from the server, delayed by 1s via CBA_fnc_waitAndExecute. This matches the empirical timing the user reported. Lifecycle EHs remain registered on every machine so locality transfers don't lose tracking. --- addons/recorder/fnc_aceExplosives.sqf | 69 +++++++++++++++------------ 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/addons/recorder/fnc_aceExplosives.sqf b/addons/recorder/fnc_aceExplosives.sqf index e7599ec..3fce2c4 100644 --- a/addons/recorder/fnc_aceExplosives.sqf +++ b/addons/recorder/fnc_aceExplosives.sqf @@ -92,34 +92,41 @@ _explosive addEventHandler ["Deleted", { [QGVARMAIN(handlePlacedEvent), [_eventData]] call CBA_fnc_serverEvent; }]; -// Send :PLACED:CREATE: only from the placer. ace_explosives_place is a CBA -// global event, so it fires on every machine — but on a dedicated server the -// explosive's position has not yet network-synced (getPosASL returns 0,0,~0). -// Only the machine that owns the object has the authoritative position at -// this moment, matching the vanilla mine path in fnc_eh_fired_client.sqf. -if (!local _explosive) exitWith {}; - -// Resolve explosive metadata from config -private _explType = typeOf _explosive; -private _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); -private _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); -private _explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture"); - -// Get placer's OCAP ID -private _unitOcapId = _unit getVariable [QGVARMAIN(id), -1]; -if (_unitOcapId isEqualTo -1) exitWith {}; - -// Build :PLACED:CREATE: data — same format as vanilla mines in fnc_eh_fired_client.sqf -private _placedData = [ - EGVAR(recorder,captureFrameNo), // 0: captureFrameNo - -1, // 1: placedId (assigned by server) - _explType, // 2: className - _explosiveDisp, // 3: displayName - (getPosASL _explosive) joinString ",", // 4: position - _unitOcapId, // 5: firerOcapId - str (side group _unit), // 6: side - "put", // 7: weapon - _explosivePic // 8: magazineIcon -]; - -[QGVARMAIN(handlePlacedData), [_placedData, _explosive]] call CBA_fnc_serverEvent; +// Send :PLACED:CREATE: from the server only — captureFrameNo and the placed-id +// counter (GVAR(nextId)) live there, and the server is where the position +// eventually syncs. Delay the read briefly: ace_explosives_place fires before +// the explosive's position has settled on the server (getPosASL initially +// returns 0,0,~0; correct after ~1s), which previously caused recordings to +// show charges at the map origin. +if (!isServer) exitWith {}; + +[{ + params ["_explosive", "_unit"]; + if (isNull _explosive || {isNull _unit}) exitWith {}; + + // Get placer's OCAP ID + private _unitOcapId = _unit getVariable [QGVARMAIN(id), -1]; + if (_unitOcapId isEqualTo -1) exitWith {}; + + // Resolve explosive metadata from config + private _explType = typeOf _explosive; + private _explosiveMag = getText(configFile >> "CfgAmmo" >> _explType >> "defaultMagazine"); + private _explosiveDisp = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "displayName"); + private _explosivePic = getText(configFile >> "CfgMagazines" >> _explosiveMag >> "picture"); + + // Build :PLACED:CREATE: data — same format as vanilla mines in fnc_eh_fired_client.sqf + private _placedData = [ + EGVAR(recorder,captureFrameNo), // 0: captureFrameNo + -1, // 1: placedId (assigned by server) + _explType, // 2: className + _explosiveDisp, // 3: displayName + (getPosASL _explosive) joinString ",", // 4: position + _unitOcapId, // 5: firerOcapId + str (side group _unit), // 6: side + "put", // 7: weapon + _explosivePic // 8: magazineIcon + ]; + + // handlePlacedData is registered server-side, so localEvent is sufficient + [QGVARMAIN(handlePlacedData), [_placedData, _explosive]] call CBA_fnc_localEvent; +}, [_explosive, _unit], 1] call CBA_fnc_waitAndExecute; From 0c9eeb90885d411ee92b2c723446a6bacd65eb77 Mon Sep 17 00:00:00 2001 From: Florian Kinder Date: Tue, 12 May 2026 11:37:25 +0200 Subject: [PATCH 4/4] =?UTF-8?q?docs(ace):=20correct=20comment=20=E2=80=94?= =?UTF-8?q?=20captureFrameNo=20is=20publicVariable'd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the incorrect rationale about captureFrameNo being server-only; clients receive it via publicVariable. The reason for the server-side delayed send is simpler: getPosASL needs ~1s on the server to reflect the settled position after ace_explosives_place fires. --- addons/recorder/fnc_aceExplosives.sqf | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/addons/recorder/fnc_aceExplosives.sqf b/addons/recorder/fnc_aceExplosives.sqf index 3fce2c4..5908a46 100644 --- a/addons/recorder/fnc_aceExplosives.sqf +++ b/addons/recorder/fnc_aceExplosives.sqf @@ -92,12 +92,11 @@ _explosive addEventHandler ["Deleted", { [QGVARMAIN(handlePlacedEvent), [_eventData]] call CBA_fnc_serverEvent; }]; -// Send :PLACED:CREATE: from the server only — captureFrameNo and the placed-id -// counter (GVAR(nextId)) live there, and the server is where the position -// eventually syncs. Delay the read briefly: ace_explosives_place fires before -// the explosive's position has settled on the server (getPosASL initially -// returns 0,0,~0; correct after ~1s), which previously caused recordings to -// show charges at the map origin. +// Send :PLACED:CREATE: from the server only, after a brief settle delay. +// ace_explosives_place fires before the explosive's position has synced on +// the server (getPosASL initially returns 0,0,~0; correct after ~1s), which +// previously caused recordings to show charges at the map origin. Reading +// after the delay on the server gives the authoritative settled position. if (!isServer) exitWith {}; [{