diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..86651c2 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,11 @@ +# Unreleased Changes + +Track notable changes here between releases. Clear this file when cutting a new release +(move contents into the release notes on Codeberg). + +## What's New + +- **Clickthrough**: Lootable corpses now click-through over players. Previously only + interactable NPCs (vendors, quest givers) and game objects would win over a blocking + player. Now dead units with the lootable flag are also prioritized, so you can loot + corpses through the player crowd without having to reposition. diff --git a/RELEASING.md b/RELEASING.md index 27f19e9..c5bf349 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -200,6 +200,7 @@ print(r[0]['id']) if r else print('not found') ## Checklist +- [ ] Check `RELEASE_NOTES.md` for unreleased changes — move into release notes - [ ] Built with `ReleaseSmall` (both default and `all-variants`) - [ ] Remote README updated — no unreleased module sections or names - [ ] `weirdutils_api.h` trimmed to released DLL names only and uploaded diff --git a/src/clickthrough/clickthrough.zig b/src/clickthrough/clickthrough.zig index 5697af8..d8c6668 100644 --- a/src/clickthrough/clickthrough.zig +++ b/src/clickthrough/clickthrough.zig @@ -49,12 +49,14 @@ fn isInteractableGO(guid_lo: u32, guid_hi: u32) bool { return hook.call(fn (u32) callconv(hook.cc.fastcall) u8, offsets.FN_CALL_SPELL_CAST_HANDLER, .{obj}) != 0; } -/// Check if the GUID refers to an NPC with interaction flags (vendor, quest giver, etc.) +/// Check if the GUID refers to an interactable unit: NPC with interaction flags, +/// or a lootable corpse (dead unit with UNIT_DYNFLAG_LOOTABLE). fn isInteractableNPC(guid_lo: u32, guid_hi: u32) bool { const obj = wow.getObjectByGUIDSplit(guid_lo, guid_hi); if (obj == 0) return false; if (wow.getObjectTypeRaw(obj) != @intFromEnum(wow.ObjectType.unit)) return false; - return wow.getNpcFlags(obj) != 0; + if (wow.getNpcFlags(obj) != 0) return true; + return wow.isLootable(obj); } /// Check if the second raycast result is something we should click through to. @@ -107,7 +109,7 @@ fn worldIntersectDetour(world_frame: u32, ray_start: u32, ray_end: u32, flags: u const type_mask = hook.readMem(u32, desc_ptr + OBJ_TYPE_MASK_OFFSET); // Determine re-raycast flags and whether NPCs are valid targets: - // Player hit → remove player flag, allow NPCs + GOs + // Player hit → remove player flag, allow NPCs + GOs (including lootable corpses) // Unit hit → GO-only flags, only allow GOs const recast_flags: u32 = switch (type_mask) { TYPE_PLAYER => flags & ~@as(u32, 0x10), // everything except players diff --git a/src/offsets.zig b/src/offsets.zig index 359a7c8..b7bf94d 100644 --- a/src/offsets.zig +++ b/src/offsets.zig @@ -42,6 +42,12 @@ pub const DESC_ENTRY: usize = 0x03 * 4; /// UNIT_NPC_FLAGS = OBJECT_END(0x06) + 0x8D = 0x93 pub const DESC_NPC_FLAGS: usize = 0x93 * 4; +/// UNIT_DYNAMIC_FLAGS = OBJECT_END(0x06) + 0x90 = 0x96 +pub const DESC_UNIT_DYNAMIC_FLAGS: usize = 0x96 * 4; + +/// Bit 0: unit corpse is lootable by the local player. +pub const UNIT_DYNFLAG_LOOTABLE: u32 = 0x01; + /// GAMEOBJECT_TYPE_ID (index 0x15) pub const DESC_GO_TYPE: usize = 0x15 * 4; diff --git a/src/wow.zig b/src/wow.zig index f4cd3d3..f19bd5d 100644 --- a/src/wow.zig +++ b/src/wow.zig @@ -122,6 +122,12 @@ pub fn getNpcFlags(obj: u32) u32 { return hook.readMem(u32, desc + o.DESC_NPC_FLAGS); } +pub fn isLootable(obj: u32) bool { + const desc = getDescriptor(obj); + if (!isValidPtr(desc)) return 0 != 0; + return (hook.readMem(u32, desc + o.DESC_UNIT_DYNAMIC_FLAGS) & o.UNIT_DYNFLAG_LOOTABLE) != 0; +} + // ============================================================================= // Unit helpers // =============================================================================