dpslog: SPELL_CAST_FAILED for other units via SMSG_SPELL_FAILED_OTHER (0x6E75F0); addon tracks DAMAGE_SPLIT, SPELL_DISPEL_FAILED, UNIT_DESTROYED (37 subevents)

This commit is contained in:
MarcelineVQ
2026-03-17 14:57:22 -07:00
parent 3eff6928b9
commit 8bd82aeea7
2 changed files with 64 additions and 5 deletions
+19 -1
View File
@@ -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
+45 -4
View File
@@ -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();