Clickthrough: lootable corpses prioritize over players

Lootable unit corpses (UNIT_DYNFLAG_LOOTABLE) now click-through over
blocking players, matching the existing behavior for interactable NPCs
and game objects. Adds isLootable() check to the interactable NPC test
and UNIT_DYNAMIC_FLAGS descriptor offset to shared offsets.

Also adds RELEASE_NOTES.md for tracking changes between releases, and
adds a checklist item in RELEASING.md to review it before publishing.
This commit is contained in:
MarcelineVQ
2026-03-13 22:22:54 -07:00
parent d9701c528d
commit e978710c44
5 changed files with 29 additions and 3 deletions
+11
View File
@@ -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.
+1
View File
@@ -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
+5 -3
View File
@@ -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
+6
View File
@@ -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;
+6
View File
@@ -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
// =============================================================================