From 0e6d479667399b4a6ad610c82cffac917f91de45 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Sat, 7 Mar 2026 09:03:32 -0800 Subject: [PATCH] Fix marker placement when cursor is over a unit or game object UpdateHitTest returns hit type 2 (object) when the cursor is over a unit/game object, with the intersection point landing at the camera position rather than the object. Detect object hits via the hit type field and resolve the object's actual world position from its GUID stored in the HitTestResult. --- src/markers/markers.zig | 32 ++++++++++++++++++++++++++------ src/markers/offsets.zig | 9 +++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/markers/markers.zig b/src/markers/markers.zig index 16825d1..e01d1fd 100644 --- a/src/markers/markers.zig +++ b/src/markers/markers.zig @@ -257,17 +257,15 @@ fn resolveUnitPosition(unit_id: [*:0]const u8) ?Vec3 { return pos; } -/// Get the terrain position under the mouse cursor by calling UpdateHitTest. -/// This performs a camera-through-cursor raycast and stores the result at -/// worldFrame+0x350. Safe to call from Lua callbacks (saves/restores matrices). +/// Get the world position under the mouse cursor via UpdateHitTest. +/// Accepts terrain, WMO, and object hits. For object hits (type 2), uses the +/// object's unit position instead of the ray intersection point (which can +/// land at the camera position due to how object raycasting works). fn getCursorTerrainPosition() ?Vec3 { const world_frame = hook.readMem(u32, o.PTR_WORLD_FRAME); if (world_frame == 0 or world_frame < 0x10000) return null; // Zero the intersection point before raycasting so we can detect "no hit" - // (HitTestPoint returns 0 for both "terrain hit" and "no hit" in normal mode - - // WorldIntersectionTest returns gameStateFlags & 1, which is 0 outside AoE targeting. - // On a real hit the coords are overwritten; on sky/no-hit they stay zeroed.) @as(*align(1) u32, @ptrFromInt(world_frame + o.WF_HIT_TERRAIN_X)).* = 0; @as(*align(1) u32, @ptrFromInt(world_frame + o.WF_HIT_TERRAIN_Y)).* = 0; @as(*align(1) u32, @ptrFromInt(world_frame + o.WF_HIT_TERRAIN_Z)).* = 0; @@ -275,10 +273,32 @@ fn getCursorTerrainPosition() ?Vec3 { // UpdateHitTest - __fastcall(ECX=worldFrame) hook.fastcall(void, o.FN_UPDATE_HIT_TEST, world_frame, 0); + const hit_type = hook.readMem(u32, world_frame + o.WF_HIT_TYPE); const x = hook.readMem(f32, world_frame + o.WF_HIT_TERRAIN_X); const y = hook.readMem(f32, world_frame + o.WF_HIT_TERRAIN_Y); const z = hook.readMem(f32, world_frame + o.WF_HIT_TERRAIN_Z); + con.fmt("[worldmarkers] hitTest: type={d} pos={d:.1},{d:.1},{d:.1}\n", .{ hit_type, x, y, z }); + + if (hit_type == 2) { + // Object hit — the intersection point is unreliable (can be at camera). + // Look up the object's actual position via its GUID. + const guid_lo = hook.readMem(u32, world_frame + o.WF_HIT_GUID); + const guid_hi = hook.readMem(u32, world_frame + o.WF_HIT_GUID + 4); + const guid: u64 = @as(u64, guid_hi) << 32 | guid_lo; + if (guid != 0) { + const obj = wow.getObjectByGUID(guid); + if (obj != 0) { + const pos = getUnitPosition(obj); + if (pos.x != 0 or pos.y != 0 or pos.z != 0) { + con.fmt("[worldmarkers] object hit, using unit pos: {d:.1},{d:.1},{d:.1}\n", .{ pos.x, pos.y, pos.z }); + return pos; + } + } + } + // Object hit but couldn't resolve position — fall through to raw coords + } + if (x == 0 and y == 0 and z == 0) return null; return .{ .x = x, .y = y, .z = z }; diff --git a/src/markers/offsets.zig b/src/markers/offsets.zig index b8b4d8e..a8d371d 100644 --- a/src/markers/offsets.zig +++ b/src/markers/offsets.zig @@ -78,15 +78,12 @@ pub const FN_DECREMENT_REFCOUNT: usize = 0x007103a0; pub const PTR_WORLD_FRAME: usize = 0x00B4B2BC; /// UpdateHitTest - __fastcall(ECX=worldFrame), no stack params. -/// Raycasts from camera through mouse cursor, stores result at worldFrame+0x350: -/// +0x350: hit type (0=none, 1=terrain, 2=object) -/// +0x360: terrain intersection X (f32) -/// +0x364: terrain intersection Y (f32) -/// +0x368: terrain intersection Z (f32) +/// Raycasts from camera through mouse cursor, stores result at worldFrame+0x350. pub const FN_UPDATE_HIT_TEST: usize = 0x00481F00; /// Offsets from worldFrame base to HitTestResult fields. -pub const WF_HIT_TYPE: usize = 0x350; +pub const WF_HIT_TYPE: usize = 0x350; // 0=terrain, 1=ground-target, 2=object +pub const WF_HIT_GUID: usize = 0x358; // u64 object GUID (0 for terrain) pub const WF_HIT_TERRAIN_X: usize = 0x360; pub const WF_HIT_TERRAIN_Y: usize = 0x364; pub const WF_HIT_TERRAIN_Z: usize = 0x368;