From 8bd82aeea7782e772c177256a05747233310ac81 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Tue, 17 Mar 2026 14:57:22 -0700 Subject: [PATCH] dpslog: SPELL_CAST_FAILED for other units via SMSG_SPELL_FAILED_OTHER (0x6E75F0); addon tracks DAMAGE_SPLIT, SPELL_DISPEL_FAILED, UNIT_DESTROYED (37 subevents) --- src/dpslog/WeirdUtils_DPSLog/DPSLog.lua | 20 +++++++++- src/dpslog/dpslog.zig | 49 +++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua b/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua index f0d6302..6d8662d 100644 --- a/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua +++ b/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua @@ -101,9 +101,13 @@ local SUBEVENTS = { "SPELL_AURA_BROKEN_SPELL", -- Pattern H: fireEnvDamage (sub, src, dst, envStr, amount, school, absorbed, 0, 0) "ENVIRONMENTAL_DAMAGE", + -- New: damage split, dispel failed, unit destroyed + "DAMAGE_SPLIT", + "SPELL_DISPEL_FAILED", + "UNIT_DESTROYED", } -local NUM_SUBEVENTS = 35 +local NUM_SUBEVENTS = 37 -- Reverse lookup: subevent name -> index local subeventIndex = {} @@ -212,6 +216,11 @@ argFormatters["ENVIRONMENTAL_DAMAGE"] = function(a4,a5,a6,a7) return format("env=%s amt=%s %s abs=%s", s(a4), s(a5), schoolName(a6 or 0), s(a7)) end +-- New subevents +argFormatters["DAMAGE_SPLIT"] = function(a4,a5,a6,a7,a8,a9,a10) return fmtSpellDmg(a4,a5,a6,a7,a8,a9,a10) end +argFormatters["SPELL_DISPEL_FAILED"] = function(a4,a5) return format("spell=%s %s", s(a4), schoolName(a5 or 0)) end +argFormatters["UNIT_DESTROYED"] = function() return "" end + -- ============================================================================ -- Chat output handlers (preserved from original) -- ============================================================================ @@ -336,6 +345,15 @@ end chatHandlers["DAMAGE_SHIELD_MISSED"] = function(src, dst, spellId, spellSchool, missType) DEFAULT_CHAT_FRAME:AddMessage(format("|cffaaaaaaDAMAGE_SHIELD_MISSED|r %s -> %s: spell %d %s", src, dst, spellId, missType)) end +chatHandlers["DAMAGE_SPLIT"] = function(src, dst, spellId, amount, school) + DEFAULT_CHAT_FRAME:AddMessage(format("|cffff6600DAMAGE_SPLIT|r %s -> %s: spell %d for %d %s", src, dst, spellId, amount, schoolName(school))) +end +chatHandlers["SPELL_DISPEL_FAILED"] = function(src, dst, spellId, spellSchool) + DEFAULT_CHAT_FRAME:AddMessage(format("|cff888888SPELL_DISPEL_FAILED|r %s -> %s: spell %d resisted", src, dst, spellId)) +end +chatHandlers["UNIT_DESTROYED"] = function(src, dst) + DEFAULT_CHAT_FRAME:AddMessage(format("|cff666666UNIT_DESTROYED|r %s", dst)) +end -- ============================================================================ -- UI — Draggable popup tracker diff --git a/src/dpslog/dpslog.zig b/src/dpslog/dpslog.zig index 53bc43d..39ceda8 100644 --- a/src/dpslog/dpslog.zig +++ b/src/dpslog/dpslog.zig @@ -993,7 +993,7 @@ fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc // ============================================================================= // Hook: CastResultHandler (0x6E7330) -// Packet: SMSG_CAST_RESULT (opcode 0x0130) +// Packet: SMSG_CAST_RESULT (opcode 0x0130) — local player only // Fires: SPELL_CAST_FAILED (when status != 0) // ============================================================================= @@ -1010,12 +1010,11 @@ fn castResultDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc cdsSetRead(cds, saved_read); if (spell_id != null and status != null and status.? != 0) { - // SMSG_CAST_RESULT is only sent to the casting player const player_guid = getActivePlayerGuid(); if (player_guid != 0) { const src_str = guidToString(player_guid); const school = getSpellSchool(spell_id.?); - log.fmt("SPELL_CAST_FAILED: spell={d} status={d}\n", .{ spell_id.?, status.? }); + log.fmt("SPELL_CAST_FAILED: spell={d} status={d} (self)\n", .{ spell_id.?, status.? }); fireSpellStr(SUB_SPELL_CAST_FAILED, src_str, GUID_ZERO, spell_id.?, school, "FAILED"); } } @@ -1023,6 +1022,41 @@ fn castResultDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc return cast_result_hook.callOriginal(.{ unk, opcode, unk2, cds }); } +// ============================================================================= +// Hook: HandleSpellInterruptUpdate (0x6E75F0) +// Handles SMSG_SPELL_FAILED_OTHER (0x2A6) and SMSG_SPELL_FAILURE (0x133) +// Broadcast to all nearby — captures other units' cast failures. +// stdcall(msgType, dataBuffer), RET 8. +// Packet: packedGuid + spellId(u32) +// Fires: SPELL_CAST_FAILED for other units +// ============================================================================= + +var spell_failed_other_hook: hook.Detour(DispelFailedFn) = .{}; // same CC as dispel failed: stdcall(2), ret 8 + +fn spellFailedOtherDetour(msg_type: u32, cds: u32) callconv(hook.cc.stdcall) ?*anyopaque { + asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true }); + + const saved_read = cdsGetRead(cds); + + const caster_guid = cdsGetPackedGuid(cds); + const spell_id = cdsGet(u32, cds); + + cdsSetRead(cds, saved_read); + + if (caster_guid != null and spell_id != null and spell_id.? != 0) { + // Skip if this is the local player (already handled by CastResultHandler) + const player_guid = getActivePlayerGuid(); + if (caster_guid.? != player_guid) { + const src_str = guidToString(caster_guid.?); + const school = getSpellSchool(spell_id.?); + log.fmt("SPELL_CAST_FAILED: spell={d} caster=0x{x} (other)\n", .{ spell_id.?, caster_guid.? }); + fireSpellStr(SUB_SPELL_CAST_FAILED, src_str, GUID_ZERO, spell_id.?, school, "FAILED"); + } + } + + return spell_failed_other_hook.callOriginal(.{ msg_type, cds }); +} + // ============================================================================= // Hook: HandleUnitDeath (0x605860) — death transition callback // __fastcall(ECX=unitObject), plain RET (0 stack params) @@ -1692,7 +1726,13 @@ pub fn installHooks() void { if (cast_result_hook.attach(0x6E7330, &castResultDetour) != .ok) { log.print("FAILED to hook CastResultHandler\n"); } else { - log.print("Hooked CastResultHandler\n"); + log.print("Hooked CastResultHandler (SPELL_CAST_FAILED self)\n"); + } + + if (spell_failed_other_hook.attach(0x6E75F0, &spellFailedOtherDetour) != .ok) { + log.print("FAILED to hook HandleSpellInterruptUpdate\n"); + } else { + log.print("Hooked HandleSpellInterruptUpdate (SPELL_CAST_FAILED others)\n"); } if (spell_missed_hook.attach(0x62BAB0, &spellMissedDetour) != .ok) { @@ -1776,6 +1816,7 @@ pub fn installHooks() void { pub fn removeHooks() void { if (g_is_hook_owner) { + spell_failed_other_hook.detach(); dispel_failed_hook.detach(); aura_duration_hook.detach(); spell_effect_hook.detach();