Unify marker sync protocol: remove SetMarkerDefSync, use SF/SD messages

SetMarkerDefSync checked canSetMarkers() on the local player, which
blocked non-leaders from receiving sync data. Now SF messages use the
same SetMarkerDef (senderHasPermission) check as P messages.

Protocol change: sync responder sends SF: for each marker then SD to
signal completion. Requester enters sync mode with a 5s fallback timer,
locks to first SF sender, and exits on SD. Non-syncing clients ignore
SF/SD. Consolidated login delay and sync timeout into one timer frame.
This commit is contained in:
MarcelineVQ
2026-03-03 18:52:28 -08:00
parent 11851c4ece
commit 38e5872591
3 changed files with 83 additions and 109 deletions
-1
View File
@@ -117,7 +117,6 @@ fn registerLuaFunctions() void {
// Internal functions in WorldMarkers table (via luaL_openlib)
const lib = [_]lua.LuaReg{
.{ .name = "SetMarkerDef", .func = @intFromPtr(&markers.luaSetMarkerDef) },
.{ .name = "SetMarkerDefSync", .func = @intFromPtr(&markers.luaSetMarkerDefSync) },
.{ .name = "ClearMarkerDef", .func = @intFromPtr(&markers.luaClearMarkerDef) },
.{ .name = "GetMarkerDef", .func = @intFromPtr(&markers.luaGetMarkerDef) },
+83 -30
View File
@@ -10,13 +10,22 @@ BINDING_HEADER_WORLDMARKERS = "World Markers"
-- =============================================================================
local function log(msg)
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[WMark]|r " .. msg)
-- DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[WMark]|r " .. msg)
end
-- =============================================================================
-- Addon message protocol
-- Delimiter is ":" (pipe "|" is WoW's escape char for color codes)
--
-- Messages:
-- P:idx:x:y:z:area — live placement (everyone processes)
-- C:idx — clear one marker
-- CA — clear all markers
-- SR — sync request (non-leader asking leader to send defs)
-- LSR — leader sync request (leader asking anyone to send defs)
-- SF:idx:x:y:z:area — sync fill (only processed by requester in sync mode)
-- SD — sync done (ends sync mode on requester)
--
-- Permission model: ALL permission checks are enforced DLL-side.
-- WorldMarker/ClearWorldMarker: DLL checks local player is leader/assist.
-- SetMarkerDef/ClearMarkerDef: DLL checks sender name against roster.
@@ -35,7 +44,58 @@ local function getChannel()
return nil
end
-- =============================================================================
-- Sync state: after sending SR/LSR, accept SF from the first responder only.
-- Cleared by SD from the locked sender, or by a 5s fallback timer.
-- Also cleared when we send our own placement/clear messages.
-- =============================================================================
local syncing = false
local syncSender = nil
-- General-purpose one-shot timer. Set .delay, .callback, then :Show().
local syncTimer = CreateFrame("Frame")
syncTimer.elapsed = 0
syncTimer.delay = 5
syncTimer.callback = nil
syncTimer:Hide()
syncTimer:SetScript("OnUpdate", function()
syncTimer.elapsed = syncTimer.elapsed + arg1
if syncTimer.elapsed >= syncTimer.delay then
syncTimer:Hide()
if syncTimer.callback then
syncTimer.callback()
end
end
end)
local function clearSyncState()
syncing = false
syncSender = nil
syncTimer:Hide()
end
local function startSyncing()
syncing = true
syncSender = nil
syncTimer.elapsed = 0
syncTimer.delay = 5
syncTimer.callback = function()
if syncing then
log("sync timeout (5s), clearing sync state")
syncing = false
syncSender = nil
end
end
syncTimer:Show()
end
-- =============================================================================
-- Broadcast helpers
-- =============================================================================
local function broadcastPlace(index, x, y, z, areaId)
clearSyncState()
local ch = getChannel()
if not ch then return end
local msg = "P:" .. index .. ":" .. x .. ":" .. y .. ":" .. z .. ":" .. areaId
@@ -44,6 +104,7 @@ local function broadcastPlace(index, x, y, z, areaId)
end
local function broadcastClear(index)
clearSyncState()
local ch = getChannel()
if not ch then return end
local msg = "C:" .. index
@@ -52,6 +113,7 @@ local function broadcastClear(index)
end
local function broadcastClearAll()
clearSyncState()
local ch = getChannel()
if not ch then return end
log("SEND [" .. ch .. "] CA")
@@ -71,6 +133,8 @@ local function broadcastAllDefs()
count = count + 1
end
end
log("SEND [" .. ch .. "] SD")
SendAddonMessage(MSG_PREFIX, "SD", ch)
log("syncAll: " .. count .. " on " .. ch)
end
@@ -135,17 +199,6 @@ function ClearWorldMarker(index)
end
end
-- =============================================================================
-- Sync response deduplication
-- Only accept SF from the first responder after we send SR/LSR.
-- =============================================================================
local syncSender = nil -- name of the first SF responder
local function resetSyncSender()
syncSender = nil
end
-- =============================================================================
-- Addon message handler
-- All mutation commands pass sender name to the DLL for permission check.
@@ -195,7 +248,11 @@ local function onAddonMessage(prefix, message, channel, sender)
broadcastAllDefs()
return
elseif cmd == "SF" then
-- Sync response: only accept from the first responder
-- Sync fill: only process if we're in sync mode
if not syncing then
log(" ignoring SF (not syncing)")
return
end
if syncSender == nil then
syncSender = sender
log(" sync responder locked: " .. sender)
@@ -205,11 +262,18 @@ local function onAddonMessage(prefix, message, channel, sender)
end
local idx, x, y, z, areaId = parseMarkerFields(parts)
if idx then
WorldMarkers.SetMarkerDefSync(idx, x, y, z, areaId, sender)
WorldMarkers.SetMarkerDef(idx, x, y, z, areaId, sender)
else
log(" PARSE FAIL")
end
return
elseif cmd == "SD" then
-- Sync done: clear sync state if from the locked sender
if syncing and (syncSender == nil or syncSender == sender) then
log(" sync done from " .. tostring(sender))
clearSyncState()
end
return
end
-- P, C, CA — DLL checks sender permission via SetMarkerDef/ClearMarkerDef
@@ -242,8 +306,7 @@ local function broadcastSyncRequest()
log("syncReq: no channel (not in group)")
return
end
-- Reset first-responder lock before requesting
syncSender = nil
startSyncing()
local cmd = CanSetWorldMarkers() and "LSR" or "SR"
log("SEND [" .. ch .. "] " .. cmd)
SendAddonMessage(MSG_PREFIX, cmd, ch)
@@ -251,23 +314,13 @@ end
-- Delayed sync request: roster isn't populated at PLAYER_LOGIN/ENTERING_WORLD,
-- so we fire a one-shot 5s timer to request markers after joining the group.
syncTimer = CreateFrame("Frame")
syncTimer.elapsed = 0
syncTimer.pending = false
syncTimer:Hide()
syncTimer:SetScript("OnUpdate", function()
syncTimer.elapsed = syncTimer.elapsed + arg1
if syncTimer.elapsed >= 5 then
syncTimer:Hide()
syncTimer.pending = false
local function scheduleSyncRequest()
syncTimer.elapsed = 0
syncTimer.delay = 5
syncTimer.callback = function()
log("login sync timer fired")
broadcastSyncRequest()
end
end)
local function scheduleSyncRequest()
syncTimer.elapsed = 0
syncTimer.pending = true
syncTimer:Show()
log("sync request scheduled (5s)")
end
-78
View File
@@ -15,7 +15,6 @@
//!
//! Lua API (WorldMarkers table — internal, used by addon):
//! WorldMarkers.SetMarkerDef(i, x, y, z, area, sender)
//! WorldMarkers.SetMarkerDefSync(i, x, y, z, area, sender)
//! WorldMarkers.ClearMarkerDef([index,] sender)
//! WorldMarkers.GetMarkerDef(index) — returns x, y, z, areaId or nil
@@ -225,42 +224,6 @@ fn senderHasPermission(sender: [*:0]const u8) bool {
return std.mem.eql(u8, std.mem.span(leader_name), sender_span);
}
/// Check if a named sender is in the group (any rank).
/// Weaker than senderHasPermission — used for sync relay (SF) where the
/// sender is just echoing stored data, not issuing a command.
fn senderInGroup(sender: [*:0]const u8) bool {
const sender_span = std.mem.span(sender);
if (sender_span.len == 0) return false;
const raid_count = hook.readMem(u32, o.RAID_MEMBER_COUNT);
if (raid_count > 0) {
// Raid: find sender anywhere in roster (any rank)
const count = @min(raid_count, 40);
for (0..count) |i| {
const entry = hook.readMem(u32, o.RAID_ROSTER_ARRAY + i * 4);
if (entry == 0 or entry < 0x10000) continue;
const guid_lo = hook.readMem(u32, entry);
const guid_hi = hook.readMem(u32, entry + 4);
const name = getNameFromGUID(guid_lo, guid_hi) orelse continue;
if (std.mem.eql(u8, std.mem.span(name), sender_span)) {
return true;
}
}
return false;
}
// Party: check if sender is any party member
for (0..4) |i| {
const guid_lo = hook.readMem(u32, o.PARTY_MEMBER_GUIDS + i * 8);
const guid_hi = hook.readMem(u32, o.PARTY_MEMBER_GUIDS + i * 8 + 4);
if (guid_lo == 0 and guid_hi == 0) continue;
const name = getNameFromGUID(guid_lo, guid_hi) orelse continue;
if (std.mem.eql(u8, std.mem.span(name), sender_span)) {
return true;
}
}
return false;
}
// =============================================================================
// Position helpers
@@ -710,47 +673,6 @@ pub fn luaSetMarkerDef(L: lua.State) callconv(.c) u32 {
return 0;
}
/// Lua: SetMarkerDefSync(index, x, y, z, areaId, senderName)
/// Like SetMarkerDef but for sync relay (SF messages). Two checks:
/// 1. Local player must be leader/assist (only they request syncs)
/// 2. Sender must be in the group (any rank — they're just relaying data)
pub fn luaSetMarkerDefSync(L: lua.State) callconv(.c) u32 {
const nargs = lua.gettop(L);
if (nargs < 6) return 0;
if (!lua.isnumber(L, 1) or !lua.isnumber(L, 2) or !lua.isnumber(L, 3) or !lua.isnumber(L, 4) or !lua.isnumber(L, 5) or !lua.isstring(L, 6)) return 0;
if (!canSetMarkers()) {
con.print("[worldmarkers] SetMarkerDefSync: local player not leader/assist\n");
return 0;
}
const sender = lua.tostring(L, 6) orelse return 0;
if (!senderInGroup(sender)) {
con.fmt("[worldmarkers] SetMarkerDefSync: sender '{s}' not in group\n", .{std.mem.span(sender)});
return 0;
}
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
const index: usize = @intCast(raw_index - 1);
const x: f32 = @floatCast(lua.tonumber(L, 2));
const y: f32 = @floatCast(lua.tonumber(L, 3));
const z: f32 = @floatCast(lua.tonumber(L, 4));
const area_id: u32 = @intFromFloat(lua.tonumber(L, 5));
clearEntity(index);
marker_defs[index] = .{
.pos = .{ .x = x, .y = y, .z = z },
.area_id = area_id,
.active = true,
};
con.fmt("[worldmarkers] SetMarkerDefSync [{d}] at {d:.1},{d:.1},{d:.1} area={d}\n", .{ index + 1, x, y, z, area_id });
return 0;
}
/// Lua: ClearMarkerDef(senderName) — clear all
/// Lua: ClearMarkerDef(index, senderName) — clear one
/// senderName is verified against the group roster for leader/officer permission.