From bc731bb1eb2ba091cd9d77881aa5b56e2a3af8e0 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Mon, 2 Mar 2026 04:13:34 -0800 Subject: [PATCH] Fix marker Hold animation glitch and simplify animation control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M2 changes (all 5 Raid_UI_FX models): - Set Stand nextAnim=1 to chain to Hold via engine's native mechanism - Set Hold nextAnim=1 for self-loop chain - Revert Hold duration from 300000ms (test residue) to 4000ms - Fix Bone1 rotation quaternion loop: match last keyframe to first, fix hemisphere flip at 180° keyframe, zero X/Y drift in keyframes 6-9 markers.zig: - Remove Hold re-queue logic (was causing blend transition glitches) - Queue Hold once after Stand duration instead of every 1500ms - Remove debug logging (process_call_count, per-frame dumps) --- src/markers/assets/Spells/Raid_UI_FX_Cyan.m2 | Bin 43308 -> 43308 bytes src/markers/assets/Spells/Raid_UI_FX_Green.m2 | Bin 43311 -> 43311 bytes .../assets/Spells/Raid_UI_FX_Purple.m2 | Bin 43177 -> 43177 bytes src/markers/assets/Spells/Raid_UI_FX_Red.m2 | Bin 43168 -> 43168 bytes .../assets/Spells/Raid_UI_FX_Yellow.m2 | Bin 43308 -> 43308 bytes src/markers/markers.zig | 47 ++++++++++++++---- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/markers/assets/Spells/Raid_UI_FX_Cyan.m2 b/src/markers/assets/Spells/Raid_UI_FX_Cyan.m2 index a08ce14ea467af2177d964f1f88873fe9618d915..8787fdef91edf9bbda8ed388df4202d05a7d98e5 100644 GIT binary patch delta 364 zcmZ2;iD}IxrVUFOnHU)+FK2WDQk!2hmT@w7?PZyKfk$+5CHK*I28IR(FmBjCfziVn z$OeNaPYd_^PhGwb#-F9=yWdD%azBi(Uo5_VZn7VoZ^Jup-}CxnI3J>}VgF=9ZWVqv z0S1PpG7Jo>G#MCxc5lAG!#IU?w;fQ;H+gs_S8^YXXJpvH0KyCn_7fOA zc7xeK{*$ML`~9ab-v{H*QuN(#q%OH1#@8VtBbK#;^Cb_WKu-Fe}AId<+flQhi2_nz}VL!uU!-c|= z-M9t#-2@mImdY?NtkPs)06JjvMV`NtIRfmaPn%}Pz%Y6L5-XWAJ}?Ngg@Iwo;>C8C zE?lskd~k_{6ioVz4@AK}WiI<|(oFVH{ikza5F(w!I^WhbW42vXkG&m8zs!Go7{soC z3nu;Fesk8MekQq9%Bz&i4ayBb;vg+;Y9JEAKBEINljn8d>%i1B2;3aK^a?Wo9_e~o delta 306 zcmZ2~iD~^MrVYy&ng0Kuyn@jQNNs+@SjIV7dL`fFJ3L&ItGJIQFf#050AYp(`w5I5 zyTNQA|H;$B{r*#z?}PDYDf;d=QkUEhi`tpwLfu2%-zU6J_+S6Oo&gAE bMuNyQJ|OacJ;)-SG#~&;gW%@qrB|2%r(=3^ diff --git a/src/markers/assets/Spells/Raid_UI_FX_Purple.m2 b/src/markers/assets/Spells/Raid_UI_FX_Purple.m2 index da316450c0e2589c95a8dcfea55cc75f7be56df3..4dfeefe6a57aa8ee7882e98028e92f0852a137d1 100644 GIT binary patch delta 330 zcmZ2^k!j^crVYy(nHU)+uVi!rQk&m0mT@w7?PZyq#(gWEfuVr`j2re(VDzvCvccfV z)587!QJ8Dos5DZ5Ts+V0f_u>zxn2p3(NrN C9B7RI delta 270 zcmZ2^k!j^crVYy(ng0KuypqugNNs-0SjIUyav{&;eC~_!j0`&%K$xMyegdP%ZZI3j zfAX|&zyH+b`(XT8ioW}e)Ft=B`1-}-`{yS6!TC14^Y%TjFNX6&7+38EnK^krj{(T8 z&9`~JOy-oaX8?nS$qfrFCvROO!2ROcGdqybzdtolF%fojHCwls*gt#n1gH+74^vEN ebMYcSCW$m4_+Jl!AcOwbgUB;Jn{O_;zzhHuUuVbw diff --git a/src/markers/assets/Spells/Raid_UI_FX_Red.m2 b/src/markers/assets/Spells/Raid_UI_FX_Red.m2 index b3091cdf26313761249c6b7345f6d513c0b5cbe7..1acf4c29d91673b65b4f53d34cf85cee826800c5 100644 GIT binary patch delta 335 zcmZ2*k!it2rVUFNnHU)+FJp89Qk!2fmT@w7?PZyKo=0?Y1^3Z-28IR(FmBjCfziVn z$OeNaPYd_^PhGwb#-F9=yWdD%azBi(Uo5_VZn7VoZ^Jup-}CxnI3J>}VgF&A-@U2huO|-yR0BE8v1j|F_?qu_%B^qTi|C$tV~C QL0T3YfJmV1g(Vl50pnC}Q~&?~ delta 285 zcmZ2*k!it2rVUFNng0Kuyo}KaNNs+_SjIVd)k2=hH+Xm^S8yMVXJpvH0KyCn_7fOA zc7xeK{*$ML`~9ab-v{H*QuN(#q%OH1#@8^a0qQKSmePZkp=|FHvF##nRUiz^MxfB Fm;qvUbRz%& diff --git a/src/markers/assets/Spells/Raid_UI_FX_Yellow.m2 b/src/markers/assets/Spells/Raid_UI_FX_Yellow.m2 index 1cf673f074af894e65c4ee93e02bf12e64321982..d1c9474457c5f96d9f27a62385f537306c6884fd 100644 GIT binary patch delta 363 zcmZ2;iD}IxrVYy(nHU)+uVi!rQk&m0mT@w7?PZyq#(gV+fuVr`j2re(VDzvCvccfV z)587!QBIWP#36S%w5&NO4TT~v>~9Z<8SwEcf5#4gVX wm*1SdsGCV{mGUa(a)WXMkS>spHZ>3lVV}_fIfv(U;OoHDGzi=rvGfWv02*F+)c^nh delta 301 zcmZ2;iD}IxrVYy(ng0KuypqugNNs-0SjIW|)e@e``P>&17#VgjfG|UY{RBpj-C#D5 z|Kw@me*dY<_rdtH6n*y_sY~vM@%4+v_s>oCgY#{8=k0r5UkvAmFs|ASGLvEQ#}&ep z-MIyr+yo{+SR%IhHqV#IY!~ddZ`)=!xptw=}%KP*aIad*DSOJE84rq#vUYg?aCFnA|5P)LYs>hH8aVDx`(>IPk5j3zy5zc0}#xN b1d(TaK;-{=kS#oEKmd{k!Oam%uP_4u9V&c1 diff --git a/src/markers/markers.zig b/src/markers/markers.zig index b327837..d118517 100644 --- a/src/markers/markers.zig +++ b/src/markers/markers.zig @@ -39,7 +39,7 @@ const MARKER_Z_OFFSET: f32 = 2.0; const ANIM_STAND: u32 = 0; // 4000ms grow-in (bones scale from 1x to full) const ANIM_HOLD: u32 = 158; // sustained idle at full scale (loops) const ANIM_DECAY: u32 = 159; // 666ms shrink-out -const DECAY_DURATION_MS: u32 = 700; // slightly over 666ms to ensure animation completes +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", @@ -64,10 +64,13 @@ pub const Vec3 = struct { // ============================================================================= var marker_entities: [NUM_MARKERS]?*anyopaque = .{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 = 4000; // Entities playing their Decay animation before destruction. -// Cleaned up lazily on the next marker operation. -// TODO: could use actual game timing (e.g. frame delta from WorldFrameUpdate) instead of GetTickCount +// Cleaned up every frame by luaProcessAnimations (via Lua OnUpdate). const MAX_DESPAWNING = 8; const DespawningEntity = struct { entity: *anyopaque, @@ -221,7 +224,8 @@ fn cleanupEntity(obj: *anyopaque) void { /// Play an animation on an entity's M2 model render context (entity+0x88). /// CM2Model__PlayBoneAnimation — __thiscall(ECX=model), RET 0x1c. fn playAnimation(entity: *anyopaque, anim_id: u32, queue: bool) void { - const model = hook.readMem(u32, @intFromPtr(entity) + 0x88); + const entity_addr = @intFromPtr(entity); + const model = hook.readMem(u32, entity_addr + 0x88); if (model == 0 or model < 0x10000) return; const speed_bits: u32 = @bitCast(@as(f32, 1.0)); @@ -232,7 +236,7 @@ fn playAnimation(entity: *anyopaque, anim_id: u32, queue: bool) void { 0, // animData: NULL speed_bits, // speed: 1.0 1, // blendMode: blend - if (queue) @as(u32, 1) else @as(u32, 0), + @intFromBool(queue), }; asm volatile ( @@ -276,7 +280,7 @@ fn forceCleanupDespawning() void { /// Start despawn animation and defer entity destruction. fn beginDespawn(entity: *anyopaque) void { - playAnimation(entity, ANIM_DECAY, false); + playAnimation(entity, ANIM_DECAY, true); // Find a free despawning slot for (&despawning) |*slot| { @@ -311,11 +315,13 @@ fn placeMarker(index: usize, pos: Vec3) bool { return false; }; - // CM2Model_CreateForModelObject already plays Stand (grow-in). - // Queue Hold (sustained idle) to start after Stand completes. - playAnimation(obj, ANIM_HOLD, true); - + // Stand (grow-in) is queued by the engine in CM2Model_CreateForModelObject. + // Hold is deferred to the per-frame callback (luaProcessAnimations) once + // the model is fully initialised (model+0x10 != 0), so it doesn't clobber + // Stand in the command queue. marker_entities[index] = obj; + 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 }); return true; } @@ -327,6 +333,7 @@ fn clearMarker(index: usize) void { if (marker_entities[index]) |existing| { beginDespawn(existing); marker_entities[index] = null; + hold_queued[index] = false; } } @@ -422,6 +429,26 @@ pub fn luaClearWorldMarker(L: u32) callconv(.c) u32 { return 0; } +/// Per-frame callback (driven by Lua OnUpdate). +/// Queues Hold once after Stand finishes, then cleans up despawning entities. +pub fn luaProcessAnimations(L: u32) callconv(.c) u32 { + _ = L; + const now = GetTickCount(); + + cleanupDespawning(); + + for (0..NUM_MARKERS) |i| { + if (hold_queued[i]) continue; + const entity = marker_entities[i] orelse continue; + if (now -% marker_created_tick[i] < STAND_DURATION_MS) continue; + + playAnimation(entity, ANIM_HOLD, true); + hold_queued[i] = true; + } + + return 0; +} + /// Lua: local x, y, z = GetPlayerPosition() pub fn luaGetPlayerPosition(L: u32) callconv(.c) u32 { const player = wow.getLocalPlayer();