Move marker animation tick to DLL-side OnWorldUpdate hook
- Hook OnWorldUpdate (0x482EA0) for per-frame tick while world is active - Remove Lua OnUpdate animation driver (ProcessMarkerAnimations) - Add DistanceToMark(index) Lua API returning player-to-marker distance - Track marker positions DLL-side for distance queries - Clean up positions on marker clear and world teardown
This commit is contained in:
+1
-1
@@ -262,7 +262,7 @@ fn registerLuaFunctions() void {
|
||||
registerFunction("WorldMarker", @intFromPtr(&markers.luaWorldMarker));
|
||||
registerFunction("ClearWorldMarker", @intFromPtr(&markers.luaClearWorldMarker));
|
||||
registerFunction("GetPlayerPosition", @intFromPtr(&markers.luaGetPlayerPosition));
|
||||
registerFunction("ProcessMarkerAnimations", @intFromPtr(&markers.luaProcessAnimations));
|
||||
registerFunction("DistanceToMark", @intFromPtr(&markers.luaDistanceToMark));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,78 +14,42 @@ frame:SetScript("OnEvent", function()
|
||||
end
|
||||
end)
|
||||
|
||||
-- Per-frame animation driver: queues Hold after Stand finishes on new
|
||||
-- markers and re-queues it periodically so it never falls back to Stand.
|
||||
local animFrame = CreateFrame("Frame")
|
||||
animFrame:SetScript("OnUpdate", function()
|
||||
ProcessMarkerAnimations()
|
||||
end)
|
||||
|
||||
SLASH_MARKERS1 = "/markers"
|
||||
SLASH_MARKERS2 = "/mark"
|
||||
SlashCmdList["MARKERS"] = function(msg)
|
||||
SLASH_WORLDMARKER1 = "/worldmarker"
|
||||
SLASH_WORLDMARKER2 = "/wm"
|
||||
SlashCmdList["WORLDMARKER"] = function(msg)
|
||||
msg = string.lower(msg or "")
|
||||
|
||||
if msg == "" or msg == "help" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers|r commands:")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark <1-5> - Place marker at cursor")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark <1-5> <unit> - Place marker at unit")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark <1-5> <x> <y> <z> - Place marker at coords")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark clear [1-5] - Clear one or all markers")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /mark pos - Show player position")
|
||||
-- Parse arguments
|
||||
local parts = {}
|
||||
for word in string.gfind(msg, "%S+") do
|
||||
table.insert(parts, word)
|
||||
end
|
||||
|
||||
elseif msg == "clear" then
|
||||
ClearWorldMarker()
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers:|r all cleared")
|
||||
|
||||
elseif msg == "pos" or msg == "position" then
|
||||
local x, y, z = GetPlayerPosition()
|
||||
if x then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("Position: %.2f, %.2f, %.2f", x, y, z))
|
||||
else
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000No player position available|r")
|
||||
end
|
||||
local index = tonumber(parts[1])
|
||||
if not index then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00World Markers|r commands:")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /wm 1-5 - Place marker at cursor")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /wm 1-5 <unit> - Place marker at unit")
|
||||
DEFAULT_CHAT_FRAME:AddMessage(" /cwm [1-5] - Clear one or all markers")
|
||||
return
|
||||
end
|
||||
|
||||
if parts[2] then
|
||||
-- /wm 1 target
|
||||
WorldMarker(index, parts[2])
|
||||
else
|
||||
-- Parse arguments
|
||||
local parts = {}
|
||||
for word in string.gfind(msg, "%S+") do
|
||||
table.insert(parts, word)
|
||||
end
|
||||
-- /wm 1 (cursor position)
|
||||
WorldMarker(index)
|
||||
end
|
||||
end
|
||||
|
||||
-- /mark clear <index>
|
||||
if parts[1] == "clear" then
|
||||
local index = tonumber(parts[2])
|
||||
if index then
|
||||
ClearWorldMarker(index)
|
||||
else
|
||||
ClearWorldMarker()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local index = tonumber(parts[1])
|
||||
if not index then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Usage:|r /mark <1-5> [unit | x y z] or /mark clear")
|
||||
return
|
||||
end
|
||||
|
||||
if tonumber(parts[2]) then
|
||||
-- /mark 1 x y z
|
||||
local x = tonumber(parts[2])
|
||||
local y = tonumber(parts[3])
|
||||
local z = tonumber(parts[4])
|
||||
if x and y and z then
|
||||
WorldMarker(index, x, y, z)
|
||||
else
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Usage:|r /mark <1-5> <x> <y> <z>")
|
||||
end
|
||||
elseif parts[2] then
|
||||
-- /mark 1 target
|
||||
WorldMarker(index, parts[2])
|
||||
else
|
||||
-- /mark 1 (cursor position)
|
||||
WorldMarker(index)
|
||||
end
|
||||
SLASH_CLEARWORLDMARKER1 = "/clearworldmarker"
|
||||
SLASH_CLEARWORLDMARKER2 = "/cwm"
|
||||
SlashCmdList["CLEARWORLDMARKER"] = function(msg)
|
||||
local index = tonumber(msg or "")
|
||||
if index then
|
||||
ClearWorldMarker(index)
|
||||
else
|
||||
ClearWorldMarker()
|
||||
end
|
||||
end
|
||||
|
||||
+58
-14
@@ -47,11 +47,11 @@ const ANIM_DECAY: u32 = 159; // 666ms shrink-out
|
||||
const DECAY_DURATION_MS: u32 = 650; // slightly over 666ms to ensure animation completes
|
||||
|
||||
const MODEL_PATHS = [NUM_MARKERS][*:0]const u8{
|
||||
"Spells\\Raid_UI_FX_Yellow.m2",
|
||||
"Spells\\Raid_UI_FX_Cyan.m2",
|
||||
"Spells\\Raid_UI_FX_Green.m2",
|
||||
"Spells\\Raid_UI_FX_Purple.m2",
|
||||
"Spells\\Raid_UI_FX_Red.m2",
|
||||
"Spells\\Raid_UI_FX_Cyan.m2", // 1 - Blue Square
|
||||
"Spells\\Raid_UI_FX_Green.m2", // 2 - Green Triangle
|
||||
"Spells\\Raid_UI_FX_Purple.m2", // 3 - Purple Diamond
|
||||
"Spells\\Raid_UI_FX_Red.m2", // 4 - Red Cross
|
||||
"Spells\\Raid_UI_FX_Yellow.m2", // 5 - Yellow Star
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
@@ -69,13 +69,14 @@ pub const Vec3 = struct {
|
||||
// =============================================================================
|
||||
|
||||
var marker_entities: [NUM_MARKERS]?*anyopaque = .{null} ** NUM_MARKERS;
|
||||
var marker_positions: [NUM_MARKERS]?Vec3 = .{null} ** NUM_MARKERS;
|
||||
var marker_created_tick: [NUM_MARKERS]u32 = .{0} ** NUM_MARKERS;
|
||||
var hold_queued: [NUM_MARKERS]bool = .{false} ** NUM_MARKERS;
|
||||
|
||||
const STAND_DURATION_MS: u32 = 3900;
|
||||
|
||||
// Entities playing their Decay animation before destruction.
|
||||
// Cleaned up every frame by luaProcessAnimations (via Lua OnUpdate).
|
||||
// Cleaned up every frame by tickAnimations (via OnWorldUpdate hook).
|
||||
const MAX_DESPAWNING = 8;
|
||||
const DespawningEntity = struct {
|
||||
entity: *anyopaque,
|
||||
@@ -322,10 +323,11 @@ fn placeMarker(index: usize, pos: Vec3) bool {
|
||||
};
|
||||
|
||||
// Stand (grow-in) is queued by the engine in CM2Model_CreateForModelObject.
|
||||
// Hold is deferred to the per-frame callback (luaProcessAnimations) once
|
||||
// Hold is deferred to the per-frame callback (tickAnimations) once
|
||||
// the model is fully initialised (model+0x10 != 0), so it doesn't clobber
|
||||
// Stand in the command queue.
|
||||
marker_entities[index] = obj;
|
||||
marker_positions[index] = pos;
|
||||
hold_queued[index] = false;
|
||||
marker_created_tick[index] = GetTickCount();
|
||||
con.fmt("[markers] marker {d} placed at {d:.1}, {d:.1}, {d:.1}\n", .{ index + 1, pos.x, pos.y, pos.z });
|
||||
@@ -339,6 +341,7 @@ fn clearMarker(index: usize) void {
|
||||
if (marker_entities[index]) |existing| {
|
||||
beginDespawn(existing);
|
||||
marker_entities[index] = null;
|
||||
marker_positions[index] = null;
|
||||
hold_queued[index] = false;
|
||||
}
|
||||
}
|
||||
@@ -351,6 +354,7 @@ fn clearAllMarkers() void {
|
||||
if (marker_entities[i]) |existing| {
|
||||
beginDespawn(existing);
|
||||
marker_entities[i] = null;
|
||||
marker_positions[i] = null;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
@@ -435,10 +439,9 @@ pub fn luaClearWorldMarker(L: u32) callconv(.c) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Per-frame callback (driven by Lua OnUpdate).
|
||||
/// Per-frame animation tick (driven by OnWorldUpdate hook).
|
||||
/// Queues Hold once after Stand finishes, then cleans up despawning entities.
|
||||
pub fn luaProcessAnimations(L: u32) callconv(.c) u32 {
|
||||
_ = L;
|
||||
fn tickAnimations() void {
|
||||
const now = GetTickCount();
|
||||
|
||||
cleanupDespawning();
|
||||
@@ -451,8 +454,6 @@ pub fn luaProcessAnimations(L: u32) callconv(.c) u32 {
|
||||
playAnimation(entity, ANIM_HOLD, true);
|
||||
hold_queued[i] = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Lua: local x, y, z = GetPlayerPosition()
|
||||
@@ -467,11 +468,46 @@ pub fn luaGetPlayerPosition(L: u32) callconv(.c) u32 {
|
||||
return 3;
|
||||
}
|
||||
|
||||
/// Lua: local dist = DistanceToMark(index)
|
||||
/// Returns distance in yards from player to the stored marker position,
|
||||
/// or nil if the marker doesn't exist or there's no player.
|
||||
pub fn luaDistanceToMark(L: u32) callconv(.c) u32 {
|
||||
const nargs = lapi.gettop(L);
|
||||
if (nargs < 1 or !lapi.isnumber(L, 1)) return 0;
|
||||
|
||||
const raw_index = @as(i32, @intFromFloat(lapi.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
|
||||
const index: usize = @intCast(raw_index - 1);
|
||||
|
||||
const mark_pos = marker_positions[index] orelse return 0;
|
||||
|
||||
const player = wow.getLocalPlayer();
|
||||
if (player == 0) return 0;
|
||||
const player_pos = getUnitPosition(player);
|
||||
if (player_pos.x == 0 and player_pos.y == 0 and player_pos.z == 0) return 0;
|
||||
|
||||
const dx = player_pos.x - mark_pos.x;
|
||||
const dy = player_pos.y - mark_pos.y;
|
||||
const dz = player_pos.z - mark_pos.z;
|
||||
const dist = @sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
lapi.pushnumber(L, @floatCast(dist));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// World teardown hook
|
||||
// =============================================================================
|
||||
|
||||
var world_cleanup_hook: hook.Detour(fn () callconv(sc) void) = .{};
|
||||
var world_update_hook: hook.Detour(fn (u32) callconv(fc) void) = .{};
|
||||
|
||||
/// OnWorldUpdate hook — per-frame tick while world is active.
|
||||
/// Drives animation state (Hold queue after Stand, despawn cleanup).
|
||||
fn worldUpdateDetour(frame: u32) callconv(fc) void {
|
||||
tickAnimations();
|
||||
world_update_hook.callOriginal(.{frame});
|
||||
}
|
||||
|
||||
/// Pre-hook on CleanupWorldAndEntities (0x66fc40).
|
||||
/// Destroys all our entities via CleanupEntity_ProcessAttachments before the
|
||||
@@ -501,6 +537,7 @@ fn destroyAllEntities() void {
|
||||
}
|
||||
for (&hold_queued) |*h| h.* = false;
|
||||
for (&marker_created_tick) |*t| t.* = 0;
|
||||
for (&marker_positions) |*p| p.* = null;
|
||||
|
||||
for (&despawning, 0..) |*slot, i| {
|
||||
if (slot.*) |d| {
|
||||
@@ -542,10 +579,16 @@ pub fn installHooks() void {
|
||||
}
|
||||
g_is_hook_owner = true;
|
||||
|
||||
// Hook OnWorldUpdate for per-frame animation tick (runs every frame while world is active).
|
||||
if (world_update_hook.attach(o.FN_ON_WORLD_UPDATE, &worldUpdateDetour) != .ok) {
|
||||
con.print("[markers] FAILED to hook OnWorldUpdate!\n");
|
||||
} else {
|
||||
con.print("[markers] hooked OnWorldUpdate OK\n");
|
||||
}
|
||||
|
||||
// Hook CleanupWorldAndEntities to destroy our entities before world teardown.
|
||||
// This fires on map change, logout, AND exit — before heaps are destroyed.
|
||||
const hook_result = world_cleanup_hook.attach(o.FN_CLEANUP_WORLD_AND_ENTITIES, &worldCleanupDetour);
|
||||
if (hook_result != .ok) {
|
||||
if (world_cleanup_hook.attach(o.FN_CLEANUP_WORLD_AND_ENTITIES, &worldCleanupDetour) != .ok) {
|
||||
con.print("[markers] FAILED to hook CleanupWorldAndEntities!\n");
|
||||
} else {
|
||||
con.print("[markers] hooked CleanupWorldAndEntities OK\n");
|
||||
@@ -557,6 +600,7 @@ pub fn removeHooks() void {
|
||||
// destroyAllEntities is idempotent — if worldCleanupDetour already ran,
|
||||
// all slots are null and this is a no-op.
|
||||
destroyAllEntities();
|
||||
world_update_hook.detach();
|
||||
world_cleanup_hook.detach();
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,15 @@ pub const WF_HIT_TERRAIN_X: usize = 0x360;
|
||||
pub const WF_HIT_TERRAIN_Y: usize = 0x364;
|
||||
pub const WF_HIT_TERRAIN_Z: usize = 0x368;
|
||||
|
||||
// =============================================================================
|
||||
// Per-frame world update
|
||||
// =============================================================================
|
||||
|
||||
/// OnWorldUpdate — __fastcall(ECX=worldFrame), no stack params, void return.
|
||||
/// Called every frame while the world is active (in-game, not login screen).
|
||||
/// Part of CGWorldFrame update pipeline.
|
||||
pub const FN_ON_WORLD_UPDATE: usize = 0x00482EA0;
|
||||
|
||||
// =============================================================================
|
||||
// Model creation
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user