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:
@@ -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) },
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user