dpslog: CLEU rename, DPSMate absorb pipeline, WSBT adapter, module version API
- Rename COMBAT_LOG_EVENT -> COMBAT_LOG_EVENT_UNFILTERED across Zig/Lua - DPSMate CLEU adapter: absorb pipeline (SetUnregisterVariables, Absorb, ConfirmAbsorbApplication, UnregisterAbsorb), BuildFail type 2/3, "(Periodic)" suffix, removed dead handler - WSBT (Weird Scrolling Battle Text): CLEU adapter fork of MSBT with dispatch table, A/B combat profiling, selective event unregistration - Module version API: GetWeirdUtilsVersion(name?) Lua function + WeirdUtils global table, additive across independent DLLs. Build system passes per-module version strings via build_options. - Hook Glue_LoadScriptFunctions (0x46ABB0) for login screen availability - Rename lsf_hook -> register_commands_hook (real name: Player_LoadScriptFunctions) - lua.zig: add setglobal/getglobal via LUA_GLOBALSINDEX (-10001)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
local L = AceLibrary("AceLocale-2.2"):new("WSBT")
|
||||
|
||||
L:RegisterTranslations("deDE", function()
|
||||
return {
|
||||
["Debug mode has been enabled."] = "Debug mode has been enabled.",
|
||||
["Debug mode has been disabled."] = "Debug mode has been disabled.",
|
||||
["Event search mode has been enabled. Searching for: "] = "Event search mode has been enabled. Searching for: ",
|
||||
["Event search mode has been disabled."] = "Event search mode has been disabled.",
|
||||
["The mod is now disabled."] = "The mod is now disabled.",
|
||||
["The mod is now enabled."] = "The mod is now enabled.",
|
||||
["Hits"] = "Treffer",
|
||||
["Crit"] = "Krit",
|
||||
["Crits"] = "Krits",
|
||||
["Multiple"] = "Mehrere",
|
||||
[" (%d vulnerability)"] = " (%d vulnerability)",
|
||||
[" <\124cff00b37e\124h%d\124h\124r>"] = " <\124cff00b37e\124h%d\124h\124r>",
|
||||
["Profile Reset"] = "Profile Reset",
|
||||
["Drowning"] = "Drowning",
|
||||
["Falling"] = "Falling",
|
||||
["Fatigue"] = "Fatigue",
|
||||
["Fire"] = "Fire",
|
||||
["Lava"] = "Lava",
|
||||
["Slime"] = "Slime",
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,25 @@
|
||||
local L = AceLibrary("AceLocale-2.2"):new("WSBT")
|
||||
|
||||
L:RegisterTranslations("enUS", function()
|
||||
return {
|
||||
["Debug mode has been enabled."] = true,
|
||||
["Debug mode has been disabled."] = true,
|
||||
["Event search mode has been enabled. Searching for: "] = true,
|
||||
["Event search mode has been disabled."] = true,
|
||||
["The mod is now disabled."] = true,
|
||||
["The mod is now enabled."] = true,
|
||||
["Hits"] = true,
|
||||
["Crit"] = true,
|
||||
["Crits"] = true,
|
||||
["Multiple"] = true,
|
||||
[" (%d vulnerability)"] = true,
|
||||
[" <\124cff00b37e\124h%d\124h\124r>"] = true,
|
||||
["Profile Reset"] = true,
|
||||
["Drowning"] = true,
|
||||
["Falling"] = true,
|
||||
["Fatigue"] = true,
|
||||
["Fire"] = true,
|
||||
["Lava"] = true,
|
||||
["Slime"] = true,
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,25 @@
|
||||
local L = AceLibrary("AceLocale-2.2"):new("WSBT")
|
||||
|
||||
L:RegisterTranslations("frFR", function()
|
||||
return {
|
||||
["Debug mode has been enabled."] = "Debug mode has been enabled.",
|
||||
["Debug mode has been disabled."] = "Debug mode has been disabled.",
|
||||
["Event search mode has been enabled. Searching for: "] = "Event search mode has been enabled. Searching for: ",
|
||||
["Event search mode has been disabled."] = "Event search mode has been disabled.",
|
||||
["The mod is now disabled."] = "The mod is now disabled.",
|
||||
["The mod is now enabled."] = "The mod is now enabled.",
|
||||
["Hits"] = "Coups",
|
||||
["Crit"] = "Crit",
|
||||
["Crits"] = "Crits",
|
||||
["Multiple"] = "Multiple",
|
||||
[" (%d vulnerability)"] = " (%d vulnérabilité)",
|
||||
[" <\124cff00b37e\124h%d\124h\124r>"] = " <\124cff00b37e\124h%d\124h\124r>",
|
||||
["Profile Reset"] = "Profile Reset",
|
||||
["Drowning"] = "Noyade",
|
||||
["Falling"] = "Chute",
|
||||
["Fatigue"] = "Fatigue",
|
||||
["Fire"] = "Feu",
|
||||
["Lava"] = "Lave",
|
||||
["Slime"] = "Gelée",
|
||||
}
|
||||
end)
|
||||
@@ -0,0 +1,22 @@
|
||||
## Interface: 11200
|
||||
## Version: 4.43-WSBT
|
||||
## Title: Weird Scrolling Battle Text
|
||||
## Notes: Scrolls battle information around the character model. CLEU-powered fork of MSBT.
|
||||
## OptionalDeps: SW_FixLogStrings
|
||||
## SavedVariables: MikSBT_Save
|
||||
|
||||
Libs\AceLibrary\AceLibrary.lua
|
||||
Libs\AceLocale-2.2\AceLocale-2.2.lua
|
||||
Libs\BabbleSpell-2.2\Babble-Spell-2.2.lua
|
||||
|
||||
MikScrollingBattleText-enUS.lua
|
||||
MikScrollingBattleText-frFR.lua
|
||||
MikScrollingBattleText-deDE.lua
|
||||
|
||||
localization.lua
|
||||
MikTableRecyclerObject.lua
|
||||
MikCombatEventHelper.lua
|
||||
MikScrollingBattleText.lua
|
||||
MikCombatEventHelper.xml
|
||||
MikScrollingBattleText.xml
|
||||
WSBT_CLEUAdapter.lua
|
||||
@@ -0,0 +1,578 @@
|
||||
-- WSBT CLEU Adapter
|
||||
-- Replaces MikCEH's CHAT_MSG_* string parsing with structured COMBAT_LOG_EVENT_UNFILTERED.
|
||||
-- Toggle: /wsbtcleu (switches between CLEU adapter and original string parser)
|
||||
-- Requires: DPSLog module (provides COMBAT_LOG_EVENT_UNFILTERED)
|
||||
|
||||
-- Wait for MikCEH to be initialized
|
||||
if not MikCEH or not MikCEH.SendEvent then return end
|
||||
|
||||
local CEH = MikCEH
|
||||
local SendEvent = MikCEH.SendEvent
|
||||
local GetDamageData = MikCEH.GetDamageEventData
|
||||
local GetHealData = MikCEH.GetHealEventData
|
||||
local GetNotifData = MikCEH.GetNotificationEventData
|
||||
|
||||
-- ============================================================================
|
||||
-- Constants (local refs for speed)
|
||||
-- ============================================================================
|
||||
|
||||
local INCOMING = CEH.DIRECTIONTYPE_PLAYER_INCOMING
|
||||
local OUTGOING = CEH.DIRECTIONTYPE_PLAYER_OUTGOING
|
||||
local PET_OUT = CEH.DIRECTIONTYPE_PET_OUTGOING
|
||||
local PET_IN = CEH.DIRECTIONTYPE_PET_INCOMING
|
||||
|
||||
local HIT = CEH.ACTIONTYPE_HIT
|
||||
local MISS = CEH.ACTIONTYPE_MISS
|
||||
local DODGE = CEH.ACTIONTYPE_DODGE
|
||||
local PARRY = CEH.ACTIONTYPE_PARRY
|
||||
local BLOCK = CEH.ACTIONTYPE_BLOCK
|
||||
local RESIST = CEH.ACTIONTYPE_RESIST
|
||||
local ABSORB = CEH.ACTIONTYPE_ABSORB
|
||||
local IMMUNE = CEH.ACTIONTYPE_IMMUNE
|
||||
local EVADE = CEH.ACTIONTYPE_EVADE
|
||||
local REFLECT = CEH.ACTIONTYPE_REFLECT
|
||||
|
||||
local HIT_NORMAL = CEH.HITTYPE_NORMAL
|
||||
local HIT_CRIT = CEH.HITTYPE_CRIT
|
||||
local HIT_DOT = CEH.HITTYPE_OVER_TIME
|
||||
|
||||
local HEAL_NORMAL = CEH.HEALTYPE_NORMAL
|
||||
local HEAL_CRIT = CEH.HEALTYPE_CRIT
|
||||
local HEAL_HOT = CEH.HEALTYPE_OVER_TIME
|
||||
|
||||
local DMG_PHYSICAL = CEH.DAMAGETYPE_PHYSICAL
|
||||
local DMG_UNKNOWN = CEH.DAMAGETYPE_UNKNOWN
|
||||
|
||||
local PARTIAL_ABSORB = CEH.PARTIALACTIONTYPE_ABSORB
|
||||
local PARTIAL_BLOCK = CEH.PARTIALACTIONTYPE_BLOCK
|
||||
local PARTIAL_RESIST = CEH.PARTIALACTIONTYPE_RESIST
|
||||
local PARTIAL_CRUSHING = CEH.PARTIALACTIONTYPE_CRUSHING
|
||||
local PARTIAL_GLANCING = CEH.PARTIALACTIONTYPE_GLANCING
|
||||
local PARTIAL_OVERHEAL = CEH.PARTIALACTIONTYPE_OVERHEAL
|
||||
|
||||
local NOTIF_DEBUFF = CEH.NOTIFICATIONTYPE_DEBUFF
|
||||
local NOTIF_BUFF = CEH.NOTIFICATIONTYPE_BUFF
|
||||
local NOTIF_BUFF_FADE = CEH.NOTIFICATIONTYPE_BUFF_FADE
|
||||
local NOTIF_POWER_GAIN = CEH.NOTIFICATIONTYPE_POWER_GAIN
|
||||
local NOTIF_POWER_LOSS = CEH.NOTIFICATIONTYPE_POWER_LOSS
|
||||
|
||||
-- Miss type string → MikCEH action type
|
||||
local missActionMap = {
|
||||
MISS = MISS,
|
||||
DODGE = DODGE,
|
||||
PARRY = PARRY,
|
||||
BLOCK = BLOCK,
|
||||
RESIST = RESIST,
|
||||
ABSORB = ABSORB,
|
||||
IMMUNE = IMMUNE,
|
||||
EVADE = EVADE,
|
||||
REFLECT = REFLECT,
|
||||
DEFLECT = MISS, -- no DEFLECT in MSBT, treat as miss
|
||||
}
|
||||
|
||||
-- WoW spell school bitmask → MSBT damage type
|
||||
-- 1=Physical, 2=Holy, 4=Fire, 8=Nature, 16=Frost, 32=Shadow, 64=Arcane
|
||||
local schoolMap = {
|
||||
[1] = 1, -- Physical
|
||||
[2] = 2, -- Holy
|
||||
[4] = 4, -- Fire
|
||||
[8] = 3, -- Nature
|
||||
[16] = 5, -- Frost
|
||||
[32] = 6, -- Shadow
|
||||
[64] = 7, -- Arcane
|
||||
}
|
||||
|
||||
local function schoolToDamageType(school)
|
||||
if not school or school == 0 then return DMG_PHYSICAL end
|
||||
return schoolMap[school] or DMG_UNKNOWN
|
||||
end
|
||||
|
||||
-- Power type ID → localized string
|
||||
local powerTypeNames = {
|
||||
[0] = MANA or "Mana",
|
||||
[1] = RAGE or "Rage",
|
||||
[2] = "Focus",
|
||||
[3] = ENERGY or "Energy",
|
||||
}
|
||||
|
||||
-- ============================================================================
|
||||
-- Player/pet identity
|
||||
-- ============================================================================
|
||||
|
||||
local playerName = UnitName("player")
|
||||
local petName = UnitName("pet")
|
||||
|
||||
local identityFrame = CreateFrame("Frame")
|
||||
identityFrame:RegisterEvent("UNIT_NAME_UPDATE")
|
||||
identityFrame:RegisterEvent("PLAYER_PET_CHANGED")
|
||||
identityFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
identityFrame:SetScript("OnEvent", function()
|
||||
playerName = UnitName("player")
|
||||
petName = UnitName("pet")
|
||||
end)
|
||||
|
||||
local function getDirection(srcName, dstName)
|
||||
-- Returns direction, counterpartName
|
||||
if srcName == playerName then
|
||||
return OUTGOING, dstName
|
||||
elseif dstName == playerName then
|
||||
return INCOMING, srcName
|
||||
elseif petName and srcName == petName then
|
||||
return PET_OUT, dstName
|
||||
elseif petName and dstName == petName then
|
||||
return PET_IN, srcName
|
||||
end
|
||||
return nil, nil -- not relevant to player/pet
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Toggle state
|
||||
-- ============================================================================
|
||||
|
||||
local cleuActive = false
|
||||
local cleuFrame = CreateFrame("Frame")
|
||||
|
||||
local function enableCLEU()
|
||||
-- Only unregister CHAT_MSG_* events; keep core events (PLAYER_REGEN_*, UNIT_HEALTH, etc.)
|
||||
MikCEH.UnregisterCombatParseEvents()
|
||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
cleuActive = true
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WSBT CLEU|r: |cff00ff00ON|r (structured events)")
|
||||
end
|
||||
|
||||
local function enableOriginal()
|
||||
cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
MikCEH.RegisterCombatParseEvents()
|
||||
cleuActive = false
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WSBT CLEU|r: |cffff4444OFF|r (string parser)")
|
||||
end
|
||||
|
||||
local function toggle()
|
||||
if cleuActive then enableOriginal() else enableCLEU() end
|
||||
end
|
||||
|
||||
SLASH_WSBTCLEU1 = "/wsbtcleu"
|
||||
SlashCmdList["WSBTCLEU"] = function(msg)
|
||||
if msg == "on" then
|
||||
if not cleuActive then enableCLEU() end
|
||||
elseif msg == "off" then
|
||||
if cleuActive then enableOriginal() end
|
||||
elseif msg == "status" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WSBT CLEU|r: " .. (cleuActive and "|cff00ff00ON|r" or "|cffff4444OFF|r"))
|
||||
else
|
||||
toggle()
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Partial action helpers
|
||||
-- ============================================================================
|
||||
|
||||
local function applyPartials(eventData, resisted, blocked, absorbed, glancing, crushing)
|
||||
-- Only one partial action per event in MSBT's model
|
||||
if crushing and crushing == 1 then
|
||||
eventData.PartialActionType = PARTIAL_CRUSHING
|
||||
elseif glancing and glancing == 1 then
|
||||
eventData.PartialActionType = PARTIAL_GLANCING
|
||||
elseif absorbed and absorbed > 0 then
|
||||
eventData.PartialActionType = PARTIAL_ABSORB
|
||||
eventData.PartialAmount = absorbed
|
||||
elseif blocked and blocked > 0 then
|
||||
eventData.PartialActionType = PARTIAL_BLOCK
|
||||
eventData.PartialAmount = blocked
|
||||
elseif resisted and resisted > 0 then
|
||||
eventData.PartialActionType = PARTIAL_RESIST
|
||||
eventData.PartialAmount = resisted
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- CLEU handler
|
||||
-- ============================================================================
|
||||
|
||||
local envActionMap = {
|
||||
DROWNING = CEH.ACTIONTYPE_DROWNING,
|
||||
FALLING = CEH.ACTIONTYPE_FALLING,
|
||||
FATIGUE = CEH.ACTIONTYPE_FATIGUE,
|
||||
FIRE = CEH.ACTIONTYPE_FIRE,
|
||||
LAVA = CEH.ACTIONTYPE_LAVA,
|
||||
SLIME = CEH.ACTIONTYPE_SLIME,
|
||||
EXHAUSTED = CEH.ACTIONTYPE_FATIGUE,
|
||||
}
|
||||
|
||||
local cleuHandler
|
||||
|
||||
cleuHandler = function()
|
||||
local sub = arg1
|
||||
if not sub then return end
|
||||
|
||||
local srcName = arg3
|
||||
local dstName = arg5
|
||||
|
||||
if not srcName or srcName == "" then srcName = "Unknown" end
|
||||
if not dstName or dstName == "" then dstName = "Unknown" end
|
||||
|
||||
-- ========================================================================
|
||||
-- SWING_DAMAGE
|
||||
-- args: amount(6), overkill(7), school(8), resisted(9), blocked(10),
|
||||
-- absorbed(11), critical(12), glancing(13), crushing(14)
|
||||
-- ========================================================================
|
||||
|
||||
if sub == "SWING_DAMAGE" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local amount = arg6 or 0
|
||||
local resisted = arg9 or 0
|
||||
local blocked = arg10 or 0
|
||||
local absorbed = arg11 or 0
|
||||
local critical = arg12 == 1
|
||||
local glancing = arg13 == 1 and 1 or 0
|
||||
local crushing = arg14 == 1 and 1 or 0
|
||||
local hitType = critical and HIT_CRIT or HIT_NORMAL
|
||||
|
||||
local data = GetDamageData(dir, HIT, hitType, DMG_PHYSICAL, amount, nil, name)
|
||||
applyPartials(data, resisted, blocked, absorbed, glancing, crushing)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SWING_MISSED
|
||||
-- args: missType(6), amountMissed(7)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SWING_MISSED" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local action = missActionMap[arg6] or MISS
|
||||
local data = GetDamageData(dir, action, nil, nil, nil, nil, name)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_DAMAGE / RANGE_DAMAGE / SPELL_PERIODIC_DAMAGE / DAMAGE_SHIELD / DAMAGE_SPLIT
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), overkill(10), school(11), resisted(12), blocked(13),
|
||||
-- absorbed(14), critical(15), glancing(16), crushing(17)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_DAMAGE" or sub == "RANGE_DAMAGE" or sub == "DAMAGE_SHIELD"
|
||||
or sub == "DAMAGE_SPLIT" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local resisted = arg12 or 0
|
||||
local blocked = arg13 or 0
|
||||
local absorbed = arg14 or 0
|
||||
local critical = arg15 == 1
|
||||
local crushing = arg17 == 1 and 1 or 0
|
||||
local hitType = critical and HIT_CRIT or HIT_NORMAL
|
||||
local dmgType = schoolToDamageType(school)
|
||||
|
||||
local data = GetDamageData(dir, HIT, hitType, dmgType, amount, spellName, name)
|
||||
applyPartials(data, resisted, blocked, absorbed, 0, crushing)
|
||||
SendEvent(data)
|
||||
|
||||
elseif sub == "SPELL_PERIODIC_DAMAGE" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local resisted = arg12 or 0
|
||||
local absorbed = arg14 or 0
|
||||
local dmgType = schoolToDamageType(school)
|
||||
|
||||
local data = GetDamageData(dir, HIT, HIT_DOT, dmgType, amount, spellName, name)
|
||||
applyPartials(data, resisted, 0, absorbed, 0, 0)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_MISSED / RANGE_MISSED / SPELL_PERIODIC_MISSED / DAMAGE_SHIELD_MISSED
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: missType(9), amountMissed(10)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_MISSED" or sub == "RANGE_MISSED"
|
||||
or sub == "SPELL_PERIODIC_MISSED" or sub == "DAMAGE_SHIELD_MISSED" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local action = missActionMap[arg9] or MISS
|
||||
local data = GetDamageData(dir, action, nil, nil, nil, spellName, name)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- ENVIRONMENTAL_DAMAGE
|
||||
-- args: envType(6), amount(7), overkill(8), school(9), resisted(10),
|
||||
-- blocked(11), absorbed(12), critical(13), glancing(14), crushing(15)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "ENVIRONMENTAL_DAMAGE" then
|
||||
if dstName ~= playerName then return end
|
||||
local envType = arg6
|
||||
local amount = arg7 or 0
|
||||
local action = envActionMap[envType] or HIT
|
||||
local data = GetDamageData(INCOMING, action, HIT_NORMAL, DMG_PHYSICAL, amount, nil, envType or "Environment")
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_HEAL / SPELL_PERIODIC_HEAL
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), overheal(10), absorbed(11), critical(12)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_HEAL" or sub == "SPELL_PERIODIC_HEAL" then
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local overheal = arg10 or 0
|
||||
local critical = arg12 == 1
|
||||
local isPeriodic = (sub == "SPELL_PERIODIC_HEAL")
|
||||
|
||||
local healType
|
||||
if isPeriodic then
|
||||
healType = HEAL_HOT
|
||||
elseif critical then
|
||||
healType = HEAL_CRIT
|
||||
else
|
||||
healType = HEAL_NORMAL
|
||||
end
|
||||
|
||||
-- For outgoing heals, name = target (dstName); for incoming, name = healer (srcName)
|
||||
local data = GetHealData(dir, healType, amount, spellName, name)
|
||||
|
||||
-- Overheal partial
|
||||
if overheal > 0 then
|
||||
data.PartialActionType = PARTIAL_OVERHEAL
|
||||
data.PartialAmount = overheal
|
||||
end
|
||||
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_ENERGIZE / SPELL_PERIODIC_ENERGIZE
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), powerType(10)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_ENERGIZE" or sub == "SPELL_PERIODIC_ENERGIZE" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local powerType = arg10 or 0
|
||||
local powerName = powerTypeNames[powerType] or "Mana"
|
||||
local data = GetNotifData(NOTIF_POWER_GAIN, amount .. " " .. powerName, spellName)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- SPELL_PERIODIC_DRAIN / SPELL_PERIODIC_LEECH
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: amount(9), powerType(10), extraAmount(11)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_PERIODIC_DRAIN" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local powerType = arg10 or 0
|
||||
local powerName = powerTypeNames[powerType] or "Mana"
|
||||
local data = GetNotifData(NOTIF_POWER_LOSS, amount .. " " .. powerName, spellName)
|
||||
SendEvent(data)
|
||||
|
||||
elseif sub == "SPELL_PERIODIC_LEECH" then
|
||||
-- Leech = damage to target + heal to source
|
||||
-- Show as damage taken if we're the target
|
||||
local dir, name = getDirection(srcName, dstName)
|
||||
if not dir then return end
|
||||
local spellName = arg7
|
||||
local amount = arg9 or 0
|
||||
local dmgType = schoolToDamageType(arg8)
|
||||
local data = GetDamageData(dir, HIT, HIT_DOT, dmgType, amount, spellName, name)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- AURA events
|
||||
-- prefix: spellId(6), spellName(7), spellSchool(8)
|
||||
-- suffix: auraType(9)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_AURA_APPLIED" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
local data = GetNotifData(NOTIF_DEBUFF, nil, spellName)
|
||||
SendEvent(data)
|
||||
else
|
||||
local data = GetNotifData(NOTIF_BUFF, nil, spellName)
|
||||
SendEvent(data)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_REMOVED" then
|
||||
if dstName ~= playerName then return end
|
||||
local spellName = arg7
|
||||
local data = GetNotifData(NOTIF_BUFF_FADE, nil, spellName)
|
||||
SendEvent(data)
|
||||
|
||||
-- ========================================================================
|
||||
-- PARTY_KILL — killing blow notification
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "PARTY_KILL" then
|
||||
if srcName ~= playerName then return end
|
||||
local notifType = CEH.NOTIFICATIONTYPE_NPC_KILLING_BLOW
|
||||
if CEH.recentlySelectedPlayers[dstName] then
|
||||
notifType = CEH.NOTIFICATIONTYPE_PC_KILLING_BLOW
|
||||
end
|
||||
local data = GetNotifData(notifType, nil, dstName)
|
||||
SendEvent(data)
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Performance profiling (A/B per-combat)
|
||||
-- ============================================================================
|
||||
|
||||
local profiling = false
|
||||
local profCLEU = { events = 0, totalMs = 0, gcStart = 0 }
|
||||
local profOrig = { events = 0, totalMs = 0, gcStart = 0 }
|
||||
local profCurrent = nil
|
||||
|
||||
-- Hook the original MikCEH.OnEvent to measure original mode
|
||||
-- Only count events that CLEU replaces (combat parse), not honor/XP/rep/health/etc.
|
||||
local origOnEvent = MikCEH.OnEvent
|
||||
local cleuReplacedLookup = CEH.cleuReplacedLookup
|
||||
local function measuredOrigOnEvent()
|
||||
if profiling and profCurrent == profOrig and cleuReplacedLookup[event] then
|
||||
local before = debugprofilestop()
|
||||
origOnEvent()
|
||||
local after = debugprofilestop()
|
||||
profOrig.totalMs = profOrig.totalMs + (after - before)
|
||||
profOrig.events = profOrig.events + 1
|
||||
else
|
||||
origOnEvent()
|
||||
end
|
||||
end
|
||||
-- Patch MikCEH.OnEvent so the XML OnEvent handler calls the measured version
|
||||
MikCEH.OnEvent = measuredOrigOnEvent
|
||||
|
||||
local cleuEventsSkipped = 0
|
||||
|
||||
local function measuredCLEUHandler()
|
||||
if profiling and profCurrent == profCLEU then
|
||||
-- Quick relevance check: is player or pet involved?
|
||||
local srcName = arg3 or ""
|
||||
local dstName = arg5 or ""
|
||||
local relevant = (srcName == playerName or dstName == playerName
|
||||
or (petName and (srcName == petName or dstName == petName)))
|
||||
|
||||
local before = debugprofilestop()
|
||||
cleuHandler()
|
||||
local after = debugprofilestop()
|
||||
|
||||
if relevant then
|
||||
profCLEU.totalMs = profCLEU.totalMs + (after - before)
|
||||
profCLEU.events = profCLEU.events + 1
|
||||
else
|
||||
cleuEventsSkipped = cleuEventsSkipped + 1
|
||||
end
|
||||
else
|
||||
cleuHandler()
|
||||
end
|
||||
end
|
||||
|
||||
cleuFrame:SetScript("OnEvent", measuredCLEUHandler)
|
||||
|
||||
local function profReset(tbl)
|
||||
tbl.events = 0
|
||||
tbl.totalMs = 0
|
||||
tbl.gcStart = gcinfo()
|
||||
end
|
||||
|
||||
local lastCLEUAvg = nil
|
||||
local lastOrigAvg = nil
|
||||
|
||||
local function profReport(label, tbl)
|
||||
local gcEnd = gcinfo()
|
||||
local gcDelta = gcEnd - tbl.gcStart
|
||||
local totalMs = tbl.totalMs / 1000
|
||||
local avgUs = tbl.events > 0 and (tbl.totalMs / tbl.events) or 0
|
||||
|
||||
if label == "CLEU" then
|
||||
lastCLEUAvg = avgUs
|
||||
else
|
||||
lastOrigAvg = avgUs
|
||||
end
|
||||
|
||||
local skipMsg = ""
|
||||
if label == "CLEU" then
|
||||
skipMsg = string.format(" (%d skipped)", cleuEventsSkipped)
|
||||
cleuEventsSkipped = 0
|
||||
end
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[WSBT %s]|r %d events%s, %.1fms total, %.1f us/event, %+.1f KB gc",
|
||||
label, tbl.events, skipMsg, totalMs, avgUs, gcDelta))
|
||||
|
||||
if lastCLEUAvg and lastOrigAvg and lastOrigAvg > 0 then
|
||||
local pct = ((lastCLEUAvg - lastOrigAvg) / lastOrigAvg) * 100
|
||||
local sign = pct < 0 and "" or "+"
|
||||
local color = pct < 0 and "|cff00ff00" or "|cffff4444"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"%s[WSBT CLEU vs ORIGINAL]|r %.1f vs %.1f us/event = %s%.1f%%|r %s",
|
||||
color, lastCLEUAvg, lastOrigAvg, sign, pct,
|
||||
pct < 0 and "(CLEU faster)" or "(ORIGINAL faster)"))
|
||||
end
|
||||
end
|
||||
|
||||
local benchActive = true
|
||||
local benchFrame = CreateFrame("Frame")
|
||||
|
||||
local function benchCombatStart()
|
||||
if not benchActive then return end
|
||||
profCurrent = cleuActive and profCLEU or profOrig
|
||||
profReset(profCurrent)
|
||||
debugprofilestart()
|
||||
profiling = true
|
||||
local label = cleuActive and "CLEU" or "ORIGINAL"
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[WSBT Bench]|r combat started, measuring %s", label))
|
||||
end
|
||||
|
||||
local function benchCombatEnd()
|
||||
if not benchActive or not profiling then return end
|
||||
profiling = false
|
||||
local label = cleuActive and "CLEU" or "ORIGINAL"
|
||||
profReport(label, profCurrent)
|
||||
toggle()
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[WSBT Bench]|r next combat will use: %s", cleuActive and "CLEU" or "ORIGINAL"))
|
||||
end
|
||||
|
||||
benchFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
benchFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
benchFrame:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_REGEN_DISABLED" then
|
||||
benchCombatStart()
|
||||
elseif event == "PLAYER_REGEN_ENABLED" then
|
||||
benchCombatEnd()
|
||||
end
|
||||
end)
|
||||
|
||||
SLASH_WSBTBENCH1 = "/wsbtbench"
|
||||
SlashCmdList["WSBTBENCH"] = function()
|
||||
benchActive = not benchActive
|
||||
if benchActive then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||
"|cff00ff00[WSBT Bench]|r enabled. Current mode: %s. Enter combat to begin.",
|
||||
cleuActive and "CLEU" or "ORIGINAL"))
|
||||
else
|
||||
profiling = false
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[WSBT Bench]|r disabled.")
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Start in CLEU mode by default
|
||||
-- ============================================================================
|
||||
|
||||
enableCLEU()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
-- DPSMate CLEU Adapter
|
||||
-- Replaces the string-parsing CHAT_MSG_* event system with structured COMBAT_LOG_EVENT
|
||||
-- data from the DPSLog module. Registers for COMBAT_LOG_EVENT and calls DPSMate.DB
|
||||
-- Replaces the string-parsing CHAT_MSG_* event system with structured COMBAT_LOG_EVENT_UNFILTERED
|
||||
-- data from the DPSLog module. Registers for COMBAT_LOG_EVENT_UNFILTERED and calls DPSMate.DB
|
||||
-- functions directly with extracted values.
|
||||
--
|
||||
-- Toggle: /dpscleu (switches between CLEU adapter and original string parser)
|
||||
-- Requires: DPSLog module (provides COMBAT_LOG_EVENT + GetSpellInfo)
|
||||
-- Requires: DPSLog module (provides COMBAT_LOG_EVENT_UNFILTERED + GetSpellInfo)
|
||||
|
||||
if not DPSMate or not DPSMate.DB then return end
|
||||
|
||||
@@ -75,14 +75,14 @@ local function enableCLEU()
|
||||
Parser:UnregisterEvent(ev)
|
||||
end
|
||||
-- Enable CLEU
|
||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT")
|
||||
cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
cleuActive = true
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00DPSMate CLEU Adapter|r: |cff00ff00ON|r (structured events)")
|
||||
end
|
||||
|
||||
local function enableOriginal()
|
||||
-- Disable CLEU
|
||||
cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT")
|
||||
cleuFrame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
-- Re-enable string parser
|
||||
for _, ev in ipairs(chatEvents) do
|
||||
Parser:RegisterEvent(ev)
|
||||
@@ -129,177 +129,9 @@ local function isGroupMember(name)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- CLEU event handler
|
||||
-- (dead handler removed — real handler is cleuHandler below, wrapped by profiling)
|
||||
-- ============================================================================
|
||||
|
||||
cleuFrame:SetScript("OnEvent", function()
|
||||
local sub = arg1
|
||||
if not sub then return end
|
||||
|
||||
local srcGUID = arg2
|
||||
local srcName = arg3
|
||||
local dstGUID = arg4
|
||||
local dstName = arg5
|
||||
|
||||
if not srcName or srcName == "" then srcName = "Unknown" end
|
||||
if not dstName or dstName == "" then dstName = "Unknown" end
|
||||
|
||||
-- ========================================================================
|
||||
-- DAMAGE events
|
||||
-- ========================================================================
|
||||
|
||||
if sub == "SWING_DAMAGE" then
|
||||
local amount = arg6 or 0
|
||||
local critical = arg12 == 1 and 1 or 0
|
||||
local glancing = arg13 == 1 and 1 or 0
|
||||
local crushing = arg14 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, AAttack, amount, hit, critical, "hit", crushing)
|
||||
|
||||
elseif sub == "SWING_MISSED" then
|
||||
local missType = arg6
|
||||
local miss = (missType == "MISS") and 1 or 0
|
||||
local parry = (missType == "PARRY") and 1 or 0
|
||||
local dodge = (missType == "DODGE") and 1 or 0
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
|
||||
elseif sub == "SPELL_DAMAGE" or sub == "RANGE_DAMAGE" or sub == "SPELL_PERIODIC_DAMAGE"
|
||||
or sub == "DAMAGE_SHIELD" or sub == "DAMAGE_SPLIT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local critical = arg15 == 1 and 1 or 0
|
||||
local glancing = arg16 == 1 and 1 or 0
|
||||
local crushing = arg17 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, spellName, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, spellName, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, spellName, amount, hit, critical, "hit", crushing)
|
||||
if school then DB:AddSpellSchool(spellName, school) end
|
||||
|
||||
elseif sub == "SPELL_MISSED" or sub == "RANGE_MISSED"
|
||||
or sub == "SPELL_PERIODIC_MISSED" or sub == "DAMAGE_SHIELD_MISSED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local missType = arg9
|
||||
local miss = (missType == "MISS") and 1 or 0
|
||||
local parry = (missType == "PARRY") and 1 or 0
|
||||
local dodge = (missType == "DODGE") and 1 or 0
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
|
||||
elseif sub == "ENVIRONMENTAL_DAMAGE" then
|
||||
local envType = arg6
|
||||
local amount = arg7 or 0
|
||||
DB:DamageTaken(dstName, envType or "Environment", 1, 0, 0, 0, 0, 0, amount, envType or "Environment", 0, 0)
|
||||
DB:DeathHistory(dstName, envType or "Environment", envType or "Environment", amount, 1, 0, "hit", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- HEAL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_HEAL" or sub == "SPELL_PERIODIC_HEAL" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
local overheal = arg10 or 0
|
||||
local critical = arg12 == 1 and 1 or 0
|
||||
local hit = critical == 0 and 1 or 0
|
||||
local effective = amount - overheal
|
||||
if effective < 0 then effective = 0 end
|
||||
|
||||
DB:Healing(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective)
|
||||
DB:Healing(2, DPSMateOverhealing, srcName, spellName, hit, critical, overheal)
|
||||
DB:HealingTaken(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective, dstName)
|
||||
DB:DeathHistory(dstName, srcName, spellName, effective, hit, critical, "heal", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- AURA events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_AURA_APPLIED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
DB:BuildBuffs(srcName, dstName, spellName, false)
|
||||
if Parser.CC[spellName] then
|
||||
DB:BuildActiveCC(dstName, spellName)
|
||||
end
|
||||
else
|
||||
DB:BuildBuffs(srcName, dstName, spellName, true)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_REMOVED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
DB:DestroyBuffs(dstName, spellName)
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
DB:RemoveActiveCC(dstName, spellName)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_BROKEN_SPELL" or sub == "SPELL_AURA_BROKEN" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
DB:RemoveActiveCC(dstName, spellName)
|
||||
if Parser.CC[spellName] then
|
||||
DB:CCBreaker(dstName, spellName, srcName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- CAST events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_CAST_SUCCESS" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
if Parser.Kicks and Parser.Kicks[spellName] then
|
||||
DB:RegisterPotentialKick(srcName, spellName, GT())
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- INTERRUPT / DISPEL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_INTERRUPT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local extraSpellName = arg10 or "Unknown"
|
||||
DB:Kick(srcName, dstName, spellName, extraSpellName)
|
||||
|
||||
elseif sub == "SPELL_DISPEL" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local extraSpellName = arg10 or "Unknown"
|
||||
if isGroupMember(srcName) then
|
||||
DB:Dispels(srcName, spellName, dstName, extraSpellName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- DEATH events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "UNIT_DIED" or sub == "UNIT_DESTROYED" then
|
||||
DB:UnregisterDeath(dstName)
|
||||
|
||||
elseif sub == "SPELL_SUMMON" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
if not Parser.petToOwnerMap then Parser.petToOwnerMap = {} end
|
||||
if not Parser.petToOwnerMap[dstName] then Parser.petToOwnerMap[dstName] = {} end
|
||||
Parser.petToOwnerMap[dstName][srcName] = true
|
||||
end
|
||||
end)
|
||||
|
||||
-- ============================================================================
|
||||
-- Performance profiling
|
||||
-- ============================================================================
|
||||
@@ -441,6 +273,10 @@ cleuFrame:SetScript("OnEvent", function()
|
||||
end)
|
||||
|
||||
-- The actual CLEU handler (extracted so we can call it directly or measured)
|
||||
local ShieldFlags = DB.ShieldFlags
|
||||
local FailDT = DPSMate.Parser.FailDT
|
||||
local FailDB = DPSMate.Parser.FailDB
|
||||
|
||||
cleuHandler = function()
|
||||
local sub = arg1
|
||||
if not sub then return end
|
||||
@@ -453,17 +289,30 @@ cleuHandler = function()
|
||||
if not srcName or srcName == "" then srcName = "Unknown" end
|
||||
if not dstName or dstName == "" then dstName = "Unknown" end
|
||||
|
||||
-- ========================================================================
|
||||
-- DAMAGE events
|
||||
-- ========================================================================
|
||||
-- SWING_DAMAGE args: amount(6), overkill(7), school(8), resisted(9),
|
||||
-- blocked(10), absorbed(11), critical(12), glancing(13), crushing(14)
|
||||
|
||||
if sub == "SWING_DAMAGE" then
|
||||
local amount = arg6 or 0
|
||||
local absorbed = arg11 or 0
|
||||
local critical = arg12 == 1 and 1 or 0
|
||||
local glancing = arg13 == 1 and 1 or 0
|
||||
local crushing = arg14 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, AAttack, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, AAttack, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, AAttack, amount, hit, critical, "hit", crushing)
|
||||
-- Partial absorb on a hit
|
||||
if absorbed > 0 then
|
||||
DB:SetUnregisterVariables(absorbed, AAttack, srcName)
|
||||
DB:Absorb(AAttack, dstName, srcName)
|
||||
end
|
||||
|
||||
elseif sub == "SWING_MISSED" then
|
||||
local missType = arg6
|
||||
@@ -473,24 +322,44 @@ cleuHandler = function()
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
|
||||
DB:DamageDone(srcName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, AAttack, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
-- Full absorb
|
||||
if absorb == 1 then
|
||||
DB:Absorb(AAttack, dstName, srcName)
|
||||
end
|
||||
|
||||
-- SPELL_DAMAGE args (after prefix): amount(9), overkill(10), school(11),
|
||||
-- resisted(12), blocked(13), absorbed(14), critical(15), glancing(16), crushing(17)
|
||||
|
||||
elseif sub == "SPELL_DAMAGE" or sub == "RANGE_DAMAGE" or sub == "SPELL_PERIODIC_DAMAGE"
|
||||
or sub == "DAMAGE_SHIELD" or sub == "DAMAGE_SPLIT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
local school = arg11
|
||||
local absorbed = arg14 or 0
|
||||
local critical = arg15 == 1 and 1 or 0
|
||||
local glancing = arg16 == 1 and 1 or 0
|
||||
local crushing = arg17 == 1 and 1 or 0
|
||||
local hit = (critical == 0 and glancing == 0 and crushing == 0) and 1 or 0
|
||||
DB:DamageDone(srcName, spellName, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, spellName, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, spellName, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, spellName, amount, hit, critical, "hit", crushing)
|
||||
if school then DB:AddSpellSchool(spellName, school) end
|
||||
local abilityName = (sub == "SPELL_PERIODIC_DAMAGE") and (spellName .. "(Periodic)") or spellName
|
||||
|
||||
DB:DamageDone(srcName, abilityName, hit, critical, 0, 0, 0, 0, amount, glancing, 0)
|
||||
DB:DamageTaken(dstName, abilityName, hit, critical, 0, 0, 0, 0, amount, srcName, crushing, 0)
|
||||
DB:EnemyDamage(1, DPSMateEDT, dstName, abilityName, hit, critical, 0, 0, 0, 0, amount, srcName, 0, crushing)
|
||||
DB:EnemyDamage(2, DPSMateEDD, srcName, abilityName, hit, critical, 0, 0, 0, 0, amount, dstName, 0, 0)
|
||||
DB:DeathHistory(dstName, srcName, abilityName, amount, hit, critical, "hit", crushing)
|
||||
if school then DB:AddSpellSchool(abilityName, school) end
|
||||
-- Partial absorb on a hit
|
||||
if absorbed > 0 then
|
||||
DB:SetUnregisterVariables(absorbed, abilityName, srcName)
|
||||
DB:Absorb(abilityName, dstName, srcName)
|
||||
end
|
||||
-- Avoidable damage taken (fire, void zones, etc.)
|
||||
if FailDT and FailDT[spellName] then
|
||||
DB:BuildFail(2, srcName, dstName, abilityName, amount)
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_MISSED" or sub == "RANGE_MISSED"
|
||||
or sub == "SPELL_PERIODIC_MISSED" or sub == "DAMAGE_SHIELD_MISSED" then
|
||||
@@ -502,8 +371,15 @@ cleuHandler = function()
|
||||
local resist = (missType == "RESIST" or missType == "IMMUNE") and 1 or 0
|
||||
local block = (missType == "BLOCK") and 1 or 0
|
||||
local absorb = (missType == "ABSORB") and 1 or 0
|
||||
DB:DamageDone(srcName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, spellName, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
local isPeriodic = (sub == "SPELL_PERIODIC_MISSED")
|
||||
local abilityName = isPeriodic and (spellName .. "(Periodic)") or spellName
|
||||
|
||||
DB:DamageDone(srcName, abilityName, 0, 0, miss + absorb, parry, dodge, resist, 0, 0, block)
|
||||
DB:DamageTaken(dstName, abilityName, 0, 0, miss + absorb, parry, dodge, resist, 0, srcName, 0, block)
|
||||
-- Full absorb
|
||||
if absorb == 1 then
|
||||
DB:Absorb(abilityName, dstName, srcName)
|
||||
end
|
||||
|
||||
elseif sub == "ENVIRONMENTAL_DAMAGE" then
|
||||
local envType = arg6
|
||||
@@ -511,6 +387,10 @@ cleuHandler = function()
|
||||
DB:DamageTaken(dstName, envType or "Environment", 1, 0, 0, 0, 0, 0, amount, envType or "Environment", 0, 0)
|
||||
DB:DeathHistory(dstName, envType or "Environment", envType or "Environment", amount, 1, 0, "hit", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- HEAL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_HEAL" or sub == "SPELL_PERIODIC_HEAL" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local amount = arg9 or 0
|
||||
@@ -519,11 +399,16 @@ cleuHandler = function()
|
||||
local hit = critical == 0 and 1 or 0
|
||||
local effective = amount - overheal
|
||||
if effective < 0 then effective = 0 end
|
||||
|
||||
DB:Healing(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective)
|
||||
DB:Healing(2, DPSMateOverhealing, srcName, spellName, hit, critical, overheal)
|
||||
DB:HealingTaken(1, DPSMateHealingTaken, srcName, spellName, hit, critical, effective, dstName)
|
||||
DB:DeathHistory(dstName, srcName, spellName, effective, hit, critical, "heal", 0)
|
||||
|
||||
-- ========================================================================
|
||||
-- AURA events (+ absorb shield lifecycle)
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_AURA_APPLIED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
local auraType = arg9
|
||||
@@ -532,15 +417,29 @@ cleuHandler = function()
|
||||
if Parser.CC[spellName] then
|
||||
DB:BuildActiveCC(dstName, spellName)
|
||||
end
|
||||
-- Avoidable debuff application
|
||||
if FailDB and FailDB[spellName] then
|
||||
DB:BuildFail(3, "Environment", dstName, spellName, 0)
|
||||
end
|
||||
else
|
||||
DB:BuildBuffs(srcName, dstName, spellName, true)
|
||||
-- Absorb shield applied
|
||||
if ShieldFlags[spellName] then
|
||||
DB:ConfirmAbsorbApplication(spellName, dstName, GT())
|
||||
end
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_REMOVED" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
DB:DestroyBuffs(dstName, spellName)
|
||||
if arg9 == "DEBUFF" then
|
||||
local auraType = arg9
|
||||
if auraType == "DEBUFF" then
|
||||
DB:RemoveActiveCC(dstName, spellName)
|
||||
else
|
||||
-- Absorb shield removed
|
||||
if ShieldFlags[spellName] then
|
||||
DB:UnregisterAbsorb(spellName, dstName)
|
||||
end
|
||||
end
|
||||
|
||||
elseif sub == "SPELL_AURA_BROKEN_SPELL" or sub == "SPELL_AURA_BROKEN" then
|
||||
@@ -550,11 +449,23 @@ cleuHandler = function()
|
||||
DB:CCBreaker(dstName, spellName, srcName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- CAST events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_CAST_SUCCESS" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
if Parser.Kicks and Parser.Kicks[spellName] then
|
||||
DB:RegisterPotentialKick(srcName, spellName, GT())
|
||||
end
|
||||
-- Shield cast → await confirmation via SPELL_AURA_APPLIED
|
||||
if ShieldFlags[spellName] then
|
||||
DB:AwaitingAbsorbConfirmation(srcName, spellName, dstName, GT())
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- INTERRUPT / DISPEL events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "SPELL_INTERRUPT" then
|
||||
local spellName = arg7 or "Unknown"
|
||||
@@ -568,6 +479,10 @@ cleuHandler = function()
|
||||
DB:Dispels(srcName, spellName, dstName, extraSpellName)
|
||||
end
|
||||
|
||||
-- ========================================================================
|
||||
-- DEATH events
|
||||
-- ========================================================================
|
||||
|
||||
elseif sub == "UNIT_DIED" or sub == "UNIT_DESTROYED" then
|
||||
DB:UnregisterDeath(dstName)
|
||||
|
||||
|
||||
@@ -591,7 +591,7 @@ end)
|
||||
|
||||
-- Expand args table to handle up to 17 args (spell damage has the most: 5 base + 3 prefix + 9 suffix)
|
||||
local eventFrame = CreateFrame("Frame")
|
||||
eventFrame:RegisterEvent("COMBAT_LOG_EVENT")
|
||||
eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
||||
|
||||
eventFrame:SetScript("OnEvent", function()
|
||||
local subevent = arg1
|
||||
|
||||
+437
-210
@@ -1,6 +1,6 @@
|
||||
//! DPS log module — TBC/WotLK-style COMBAT_LOG_EVENT for vanilla 1.12.1.
|
||||
//! DPS log module — TBC/WotLK-style COMBAT_LOG_EVENT_UNFILTERED for vanilla 1.12.1.
|
||||
//!
|
||||
//! Fires a unified COMBAT_LOG_EVENT with WotLK-style subevent names and
|
||||
//! Fires a unified COMBAT_LOG_EVENT_UNFILTERED with WotLK-style subevent names and
|
||||
//! structured args. All subevents share: arg1=subevent, arg2=sourceGUID,
|
||||
//! arg3=destGUID. Remaining args are subevent-specific.
|
||||
//!
|
||||
@@ -87,12 +87,12 @@ pub fn isActive() bool {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMBAT_LOG_EVENT — slot assigned dynamically in createEventsDetour
|
||||
// COMBAT_LOG_EVENT_UNFILTERED — slot assigned dynamically in createEventsDetour
|
||||
// =============================================================================
|
||||
|
||||
var g_event_combat_log: u32 = 0;
|
||||
const EVENT_TABLE_MAIN: u32 = 0xBE1198;
|
||||
const event_name: [*:0]const u8 = "COMBAT_LOG_EVENT";
|
||||
const event_name: [*:0]const u8 = "COMBAT_LOG_EVENT_UNFILTERED";
|
||||
|
||||
// =============================================================================
|
||||
// Subevent name strings (WotLK naming)
|
||||
@@ -119,6 +119,7 @@ const SUB_SPELL_PERIODIC_HEAL: [*:0]const u8 = "SPELL_PERIODIC_HEAL";
|
||||
|
||||
// Energize / Drain
|
||||
const SUB_SPELL_ENERGIZE: [*:0]const u8 = "SPELL_ENERGIZE";
|
||||
const SUB_SPELL_DRAIN: [*:0]const u8 = "SPELL_DRAIN";
|
||||
const SUB_SPELL_PERIODIC_ENERGIZE: [*:0]const u8 = "SPELL_PERIODIC_ENERGIZE";
|
||||
const SUB_SPELL_PERIODIC_DRAIN: [*:0]const u8 = "SPELL_PERIODIC_DRAIN";
|
||||
const SUB_SPELL_PERIODIC_LEECH: [*:0]const u8 = "SPELL_PERIODIC_LEECH";
|
||||
@@ -296,6 +297,94 @@ fn guidToString(guid: u64) [*:0]const u8 {
|
||||
return @ptrCast(buf[0..18 :0]);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Handler Table Swap — replace NetClient opcode dispatch entries
|
||||
// =============================================================================
|
||||
//
|
||||
// NetClient__ProcessMessage (0x537AA0) dispatches packets via:
|
||||
// handler = NetClient[opcode * 4 + 0x74]
|
||||
// call handler(ECX=context, EDX=opcode, stack: timestamp, CDataStore*)
|
||||
//
|
||||
// We swap pointers in the heap-allocated NetClient object (no code patching).
|
||||
// Original handler is saved and called after our processing.
|
||||
|
||||
const NETCLIENT_PTR: u32 = 0xC28128;
|
||||
const HANDLER_TABLE_BASE: u32 = 0x74;
|
||||
|
||||
const HandlerSwap = struct {
|
||||
opcode: u16,
|
||||
original: u32 = 0,
|
||||
active: bool = false,
|
||||
};
|
||||
|
||||
// Max swaps we'll ever need (one per opcode we intercept)
|
||||
const MAX_SWAPS = 16;
|
||||
var handler_swaps: [MAX_SWAPS]HandlerSwap = [_]HandlerSwap{.{ .opcode = 0 }} ** MAX_SWAPS;
|
||||
var swap_count: u32 = 0;
|
||||
|
||||
fn getNetClient() ?u32 {
|
||||
const nc = hook.readMem(u32, NETCLIENT_PTR);
|
||||
return if (nc != 0) nc else null;
|
||||
}
|
||||
|
||||
fn handlerSlotAddr(net_client: u32, opcode: u16) u32 {
|
||||
return net_client + @as(u32, opcode) * 4 + HANDLER_TABLE_BASE;
|
||||
}
|
||||
|
||||
fn swapHandler(opcode: u16, replacement: u32) bool {
|
||||
const nc = getNetClient() orelse {
|
||||
log.fmt("swapHandler: NetClient is NULL, cannot swap opcode 0x{X}\n", .{opcode});
|
||||
return false;
|
||||
};
|
||||
if (swap_count >= MAX_SWAPS) {
|
||||
log.fmt("swapHandler: swap table full\n", .{});
|
||||
return false;
|
||||
}
|
||||
const slot = handlerSlotAddr(nc, opcode);
|
||||
const original = hook.readMem(u32, slot);
|
||||
if (original == 0) {
|
||||
log.fmt("swapHandler: no handler registered for opcode 0x{X}\n", .{opcode});
|
||||
return false;
|
||||
}
|
||||
|
||||
handler_swaps[swap_count] = .{
|
||||
.opcode = opcode,
|
||||
.original = original,
|
||||
.active = true,
|
||||
};
|
||||
swap_count += 1;
|
||||
|
||||
// Write our replacement into the table (heap memory, writable)
|
||||
@as(*align(1) u32, @ptrFromInt(slot)).* = replacement;
|
||||
log.fmt("swapHandler: opcode 0x{X} swapped (original=0x{X})\n", .{ opcode, original });
|
||||
return true;
|
||||
}
|
||||
|
||||
fn getOriginalHandler(opcode: u16) ?u32 {
|
||||
for (handler_swaps[0..swap_count]) |entry| {
|
||||
if (entry.opcode == opcode and entry.active) return entry.original;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn callOriginalHandler(opcode: u16, unk: u32, opc: u32, unk2: u32, cds: u32) u32 {
|
||||
const original = getOriginalHandler(opcode) orelse return 0;
|
||||
return @call(.auto, @as(*const FastCallPacketHandlerFn, @ptrFromInt(original)), .{ unk, opc, unk2, cds });
|
||||
}
|
||||
|
||||
fn restoreAllHandlers() void {
|
||||
const nc = getNetClient() orelse return;
|
||||
for (handler_swaps[0..swap_count]) |*entry| {
|
||||
if (entry.active) {
|
||||
const slot = handlerSlotAddr(nc, entry.opcode);
|
||||
@as(*align(1) u32, @ptrFromInt(slot)).* = entry.original;
|
||||
entry.active = false;
|
||||
log.fmt("restoreHandler: opcode 0x{X} restored\n", .{entry.opcode});
|
||||
}
|
||||
}
|
||||
swap_count = 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SignalEventParam fire functions — one per format pattern
|
||||
// =============================================================================
|
||||
@@ -406,6 +495,21 @@ fn fireBase(sub: [*:0]const u8, src: [*:0]const u8, src_name: [*:0]const u8, dst
|
||||
@call(.auto, @as(*const F, @ptrFromInt(SIGNAL)), .{ g_event_combat_log, "%s%s%s%s%s", sub, src, src_name, dst, dst_name });
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: InitializeGameEngine (0x401570)
|
||||
// __thiscall(ECX=this), RET 0xC (3 stack params)
|
||||
// Registers all packet handlers. We install table swaps after it returns.
|
||||
// =============================================================================
|
||||
|
||||
const InitGameEngineFn = fn (u32, u32, u32, u32) callconv(hook.cc.thiscall) void;
|
||||
var init_engine_hook: hook.Detour(InitGameEngineFn) = .{};
|
||||
|
||||
fn initGameEngineDetour(this: u32, p1: u32, p2: u32, p3: u32) callconv(hook.cc.thiscall) void {
|
||||
init_engine_hook.callOriginal(.{ this, p1, p2, p3 });
|
||||
// All packet handlers are now registered — install our table swaps
|
||||
installHandlerSwaps();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: FrameScript_CreateEvents (0x703D90)
|
||||
// After the chain runs, write our event into the internal table.
|
||||
@@ -475,10 +579,76 @@ fn createEventsDetour(param1: u32, max_event_id: u32) callconv(hook.cc.fastcall)
|
||||
hook.writeMem(internal_array + slot * 16, std.mem.asBytes(&name_ptr));
|
||||
g_event_combat_log = slot;
|
||||
|
||||
log.fmt("createEventsDetour: COMBAT_LOG_EVENT at slot {d}, capacity={d}\n", .{ slot, capacity });
|
||||
log.fmt("createEventsDetour: COMBAT_LOG_EVENT_UNFILTERED at slot {d}, capacity={d}\n", .{ slot, capacity });
|
||||
}
|
||||
}
|
||||
|
||||
fn installHandlerSwaps() void {
|
||||
if (swap_count > 0) return; // already installed
|
||||
// Packet handler table swaps (no code patching, heap pointer writes only)
|
||||
if (!swapHandler(0x250, @intFromPtr(&spellNonMeleeDmgLogDetour)))
|
||||
log.print("FAILED to swap SPELLNONMELEEDAMAGELOG (0x250)\n")
|
||||
else
|
||||
log.print("Swapped SPELLNONMELEEDAMAGELOG (0x250)\n");
|
||||
|
||||
if (!swapHandler(0x24E, @intFromPtr(&periodicAuraLogDetour)))
|
||||
log.print("FAILED to swap PERIODICAURALOG (0x24E)\n")
|
||||
else
|
||||
log.print("Swapped PERIODICAURALOG (0x24E)\n");
|
||||
|
||||
if (!swapHandler(0x150, @intFromPtr(&healLogDetour)))
|
||||
log.print("FAILED to swap SPELLHEALLOG (0x150)\n")
|
||||
else
|
||||
log.print("Swapped SPELLHEALLOG (0x150)\n");
|
||||
|
||||
if (!swapHandler(0x14A, @intFromPtr(&meleeDispatcherDetour)))
|
||||
log.print("FAILED to swap ATTACKERSTATEUPDATE (0x14A)\n")
|
||||
else
|
||||
log.print("Swapped ATTACKERSTATEUPDATE (0x14A)\n");
|
||||
|
||||
if (!swapHandler(0x1F5, @intFromPtr(&partyKillLogDetour)))
|
||||
log.print("FAILED to swap PARTYKILLLOG (0x1F5)\n")
|
||||
else
|
||||
log.print("Swapped PARTYKILLLOG (0x1F5)\n");
|
||||
|
||||
if (!swapHandler(0x131, @intFromPtr(&spellStartDetour)))
|
||||
log.print("FAILED to swap SPELL_START (0x131)\n")
|
||||
else
|
||||
log.print("Swapped SPELL_START (0x131)\n");
|
||||
|
||||
if (!swapHandler(0x132, @intFromPtr(&spellStartDetour)))
|
||||
log.print("FAILED to swap SPELL_GO (0x132)\n")
|
||||
else
|
||||
log.print("Swapped SPELL_GO (0x132)\n");
|
||||
|
||||
if (!swapHandler(0x130, @intFromPtr(&castResultDetour)))
|
||||
log.print("FAILED to swap CAST_RESULT (0x130)\n")
|
||||
else
|
||||
log.print("Swapped CAST_RESULT (0x130)\n");
|
||||
|
||||
if (!swapHandler(0x24B, @intFromPtr(&spellMissedDetour)))
|
||||
log.print("FAILED to swap SPELLLOGMISS (0x24B)\n")
|
||||
else
|
||||
log.print("Swapped SPELLLOGMISS (0x24B)\n");
|
||||
|
||||
if (!swapHandler(0x24F, @intFromPtr(&damageShieldDetour)))
|
||||
log.print("FAILED to swap SPELLDAMAGESHIELD (0x24F)\n")
|
||||
else
|
||||
log.print("Swapped SPELLDAMAGESHIELD (0x24F)\n");
|
||||
|
||||
if (!swapHandler(0x24C, @intFromPtr(&spellLogExecuteDetour)))
|
||||
log.print("FAILED to swap SPELLLOGEXECUTE (0x24C)\n")
|
||||
else
|
||||
log.print("Swapped SPELLLOGEXECUTE (0x24C)\n");
|
||||
|
||||
if (!swapHandler(0x32F, @intFromPtr(&instaKillDetour)))
|
||||
log.print("FAILED to swap SPELLINSTAKILLLOG (0x32F)\n")
|
||||
else
|
||||
log.print("Swapped SPELLINSTAKILLLOG (0x32F)\n");
|
||||
|
||||
log.fmt("installHandlerSwaps: {d} handlers swapped\n", .{swap_count});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: SpellNonMeleeDmgLogHandler (0x5E85E0)
|
||||
// Packet: SMSG_SPELLNONMELEEDAMAGELOG
|
||||
@@ -487,8 +657,6 @@ fn createEventsDetour(param1: u32, max_event_id: u32) callconv(hook.cc.fastcall)
|
||||
|
||||
const FastCallPacketHandlerFn = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) u32;
|
||||
|
||||
var spell_dmg_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn spellNonMeleeDmgLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -548,7 +716,7 @@ fn spellNonMeleeDmgLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callcon
|
||||
}
|
||||
}
|
||||
|
||||
return spell_dmg_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x250, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -558,8 +726,6 @@ fn spellNonMeleeDmgLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callcon
|
||||
// SPELL_PERIODIC_DRAIN, SPELL_PERIODIC_LEECH
|
||||
// =============================================================================
|
||||
|
||||
var periodic_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn periodicAuraLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -650,45 +816,44 @@ fn periodicAuraLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(ho
|
||||
}
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
return periodic_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x24E, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessSpellPowerDrainMessage (0x62C770) — downstream of SMSG_SPELLHEALLOG
|
||||
// __fastcall(ECX=casterGuid_ptr, EDX=targetGuid_ptr, stack: spellId, healAmount, isCrit)
|
||||
// RET 0xC (3 stack params). Ghidra mislabel — actually heal display function.
|
||||
// Hook: SMSG_SPELLHEALLOG (opcode 0x150) — handler table swap
|
||||
// Packet: victimPackGUID, casterPackGUID, uint32 spellId, uint32 healAmount, uint8 isCrit
|
||||
// Fires: SPELL_HEAL
|
||||
// =============================================================================
|
||||
|
||||
const HealDisplayFn = fn (u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
|
||||
|
||||
var heal_hook: hook.Detour(HealDisplayFn) = .{};
|
||||
|
||||
fn healDisplayDetour(caster_ptr: u32, target_ptr: u32, spell_id: u32, heal_amount: u32, is_crit: u32) callconv(hook.cc.fastcall) void {
|
||||
fn healLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
if (caster_ptr != 0 and target_ptr != 0 and spell_id != 0) {
|
||||
const caster_lo = hook.readMem(u32, caster_ptr);
|
||||
const caster_hi = hook.readMem(u32, caster_ptr + 4);
|
||||
const target_lo = hook.readMem(u32, target_ptr);
|
||||
const target_hi = hook.readMem(u32, target_ptr + 4);
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const target_guid: u64 = @as(u64, target_hi) << 32 | @as(u64, target_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (caster_guid != 0 and target_guid != 0 and !isPeriodicLeechSpell(spell_id)) {
|
||||
// Suppress heal for periodic leech spells — already covered by SPELL_PERIODIC_LEECH
|
||||
const src_str = guidToString(caster_guid);
|
||||
const dst_str = guidToString(target_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const dst_name = wow.getNameByGUID(target_guid);
|
||||
const critical: u32 = if (is_crit != 0) 1 else 0;
|
||||
const overheal = computeOverheal(target_guid, heal_amount);
|
||||
// _HEAL: spellId, amount, overheal, absorbed(0), critical
|
||||
fireSpellHeal(SUB_SPELL_HEAL, src_str, src_name, dst_str, dst_name, spell_id, getSpellName(spell_id), getSpellSchool(spell_id), heal_amount, overheal, 0, critical);
|
||||
}
|
||||
const target_guid = cdsGetPackedGuid(cds);
|
||||
const caster_guid = cdsGetPackedGuid(cds);
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
const heal_amount = cdsGet(u32, cds);
|
||||
const is_crit = cdsGet(u8, cds);
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
|
||||
if (target_guid != null and caster_guid != null and spell_id != null and
|
||||
heal_amount != null and is_crit != null and
|
||||
caster_guid.? != 0 and target_guid.? != 0 and !isPeriodicLeechSpell(spell_id.?))
|
||||
{
|
||||
// Suppress heal for periodic leech spells — already covered by SPELL_PERIODIC_LEECH
|
||||
const src_str = guidToString(caster_guid.?);
|
||||
const dst_str = guidToString(target_guid.?);
|
||||
const src_name = wow.getNameByGUID(caster_guid.?);
|
||||
const dst_name = wow.getNameByGUID(target_guid.?);
|
||||
const critical: u32 = if (is_crit.? != 0) 1 else 0;
|
||||
const overheal = computeOverheal(target_guid.?, heal_amount.?);
|
||||
// _HEAL: spellId, amount, overheal, absorbed(0), critical
|
||||
fireSpellHeal(SUB_SPELL_HEAL, src_str, src_name, dst_str, dst_name, spell_id.?, getSpellName(spell_id.?), getSpellSchool(spell_id.?), heal_amount.?, overheal, 0, critical);
|
||||
}
|
||||
|
||||
heal_hook.callOriginal(.{ caster_ptr, target_ptr, spell_id, heal_amount, is_crit });
|
||||
return callOriginalHandler(0x150, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -700,8 +865,6 @@ fn healDisplayDetour(caster_ptr: u32, target_ptr: u32, spell_id: u32, heal_amoun
|
||||
|
||||
const OPCODE_ATTACKERSTATEUPDATE: u32 = 0x14A;
|
||||
|
||||
var melee_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn meleeDispatcherDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -709,7 +872,7 @@ fn meleeDispatcherDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(ho
|
||||
parseMeleePacket(cds);
|
||||
}
|
||||
|
||||
return melee_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x14A, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
fn parseMeleePacket(cds: u32) void {
|
||||
@@ -851,81 +1014,87 @@ fn getActivePlayerGuid() u64 {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessSpellCombatResult (0x62BAB0) — downstream of SMSG_SPELLLOGMISS
|
||||
// __fastcall(ECX=missType, EDX=spellId, stack: casterGuidLo, casterGuidHi,
|
||||
// targetGuidLo, targetGuidHi, isFromSpellLogMiss)
|
||||
// RET 0x14 (5 stack params)
|
||||
// Hook: SMSG_SPELLLOGMISS (opcode 0x24B) — handler table swap
|
||||
// Packet: uint32 spellId, casterGUID(8 raw), uint8 unk, uint32 targetCount,
|
||||
// [targetGUID(8 raw), uint8 missInfo] x count
|
||||
// Fires: SPELL_MISSED, RANGE_MISSED, SPELL_PERIODIC_MISSED, DAMAGE_SHIELD_MISSED
|
||||
// =============================================================================
|
||||
|
||||
const SpellMissedFn = fn (u32, u32, u32, u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
|
||||
|
||||
var spell_missed_hook: hook.Detour(SpellMissedFn) = .{};
|
||||
|
||||
fn spellMissedDetour(miss_type: u32, spell_id: u32, caster_lo: u32, caster_hi: u32, target_lo: u32, target_hi: u32, is_spell_log_miss: u32) callconv(hook.cc.fastcall) void {
|
||||
fn spellMissedDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const target_guid: u64 = @as(u64, target_hi) << 32 | @as(u64, target_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (caster_guid != 0 and target_guid != 0 and spell_id != 0) {
|
||||
const src_str = guidToString(caster_guid);
|
||||
const dst_str = guidToString(target_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const dst_name = wow.getNameByGUID(target_guid);
|
||||
const miss_str = missInfoToString(@truncate(miss_type));
|
||||
const school = getSpellSchool(spell_id);
|
||||
// Classify miss subevent by spell type
|
||||
const sub = if (spell_id == 75 or spell_id == 5019)
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
const caster_guid = cdsGet(u64, cds);
|
||||
_ = cdsGet(u8, cds); // unknown byte
|
||||
const target_count = cdsGet(u32, cds);
|
||||
|
||||
if (spell_id != null and caster_guid != null and target_count != null and
|
||||
caster_guid.? != 0 and spell_id.? != 0)
|
||||
{
|
||||
const src_str = guidToString(caster_guid.?);
|
||||
const src_name = wow.getNameByGUID(caster_guid.?);
|
||||
const school = getSpellSchool(spell_id.?);
|
||||
const sub = if (spell_id.? == 75 or spell_id.? == 5019)
|
||||
SUB_RANGE_MISSED
|
||||
else if (isDamageShieldSpell(spell_id))
|
||||
else if (isDamageShieldSpell(spell_id.?))
|
||||
SUB_DAMAGE_SHIELD_MISSED
|
||||
else if (isPeriodicSpell(spell_id))
|
||||
else if (isPeriodicSpell(spell_id.?))
|
||||
SUB_SPELL_PERIODIC_MISSED
|
||||
else
|
||||
SUB_SPELL_MISSED;
|
||||
log.fmt("{s}: [{d}]{s} miss={s} logmiss={d}\n", .{ std.mem.span(sub), spell_id, std.mem.span(getSpellName(spell_id)), std.mem.span(miss_str), is_spell_log_miss });
|
||||
fireSpellMissed(sub, src_str, src_name, dst_str, dst_name, spell_id, getSpellName(spell_id), school, miss_str, 0);
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < target_count.?) : (i += 1) {
|
||||
const target_guid = cdsGet(u64, cds) orelse break;
|
||||
const miss_info = cdsGet(u8, cds) orelse break;
|
||||
if (target_guid == 0) continue;
|
||||
|
||||
const dst_str = guidToString(target_guid);
|
||||
const dst_name = wow.getNameByGUID(target_guid);
|
||||
const miss_str = missInfoToString(miss_info);
|
||||
log.fmt("{s}: [{d}]{s} miss={s}\n", .{ std.mem.span(sub), spell_id.?, std.mem.span(getSpellName(spell_id.?)), std.mem.span(miss_str) });
|
||||
fireSpellMissed(sub, src_str, src_name, dst_str, dst_name, spell_id.?, getSpellName(spell_id.?), school, miss_str, 0);
|
||||
}
|
||||
}
|
||||
|
||||
spell_missed_hook.callOriginal(.{ miss_type, spell_id, caster_lo, caster_hi, target_lo, target_hi, is_spell_log_miss });
|
||||
cdsSetRead(cds, saved_read);
|
||||
return callOriginalHandler(0x24B, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessSpellDrainEffectMessage (0x62CA20) — downstream of SMSG_SPELLDAMAGESHIELD
|
||||
// __fastcall(ECX=victimGuid_ptr, EDX=casterGuid_ptr, stack: damage, school)
|
||||
// RET 0x8 (2 stack params)
|
||||
// Packet: victimGUID(8 raw), attackerGUID(8 raw), uint32 damage, uint32 school
|
||||
// Fires: DAMAGE_SHIELD
|
||||
// =============================================================================
|
||||
|
||||
const DamageShieldFn = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) void;
|
||||
|
||||
var damage_shield_hook: hook.Detour(DamageShieldFn) = .{};
|
||||
|
||||
fn damageShieldDetour(victim_ptr: u32, caster_ptr: u32, damage: u32, school: u32) callconv(hook.cc.fastcall) void {
|
||||
fn damageShieldDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
if (victim_ptr != 0 and caster_ptr != 0) {
|
||||
const victim_lo = hook.readMem(u32, victim_ptr);
|
||||
const victim_hi = hook.readMem(u32, victim_ptr + 4);
|
||||
const caster_lo = hook.readMem(u32, caster_ptr);
|
||||
const caster_hi = hook.readMem(u32, caster_ptr + 4);
|
||||
const victim_guid: u64 = @as(u64, victim_hi) << 32 | @as(u64, victim_lo);
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (victim_guid != 0 and caster_guid != 0) {
|
||||
const src_str = guidToString(caster_guid);
|
||||
const dst_str = guidToString(victim_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const dst_name = wow.getNameByGUID(victim_guid);
|
||||
const overkill = computeOverkill(victim_guid, damage);
|
||||
log.fmt("DAMAGE_SHIELD: dmg={d} school={d}\n", .{ damage, school });
|
||||
// No spellId available in SMSG_SPELLDAMAGESHIELD — pass 0
|
||||
fireSpellDamage(SUB_DAMAGE_SHIELD, src_str, src_name, dst_str, dst_name, 0, getSpellName(0), school, damage, overkill, school, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
const victim_guid = cdsGet(u64, cds);
|
||||
const attacker_guid = cdsGet(u64, cds);
|
||||
const damage = cdsGet(u32, cds);
|
||||
const school = cdsGet(u32, cds);
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
|
||||
if (victim_guid != null and attacker_guid != null and damage != null and school != null and
|
||||
victim_guid.? != 0 and attacker_guid.? != 0)
|
||||
{
|
||||
const src_str = guidToString(attacker_guid.?);
|
||||
const dst_str = guidToString(victim_guid.?);
|
||||
const src_name = wow.getNameByGUID(attacker_guid.?);
|
||||
const dst_name = wow.getNameByGUID(victim_guid.?);
|
||||
const overkill = computeOverkill(victim_guid.?, damage.?);
|
||||
log.fmt("DAMAGE_SHIELD: dmg={d} school={d}\n", .{ damage.?, school.? });
|
||||
// No spellId in SMSG_SPELLDAMAGESHIELD packet — pass 0
|
||||
fireSpellDamage(SUB_DAMAGE_SHIELD, src_str, src_name, dst_str, dst_name, 0, getSpellName(0), school.?, damage.?, overkill, school.?, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
damage_shield_hook.callOriginal(.{ victim_ptr, caster_ptr, damage, school });
|
||||
return callOriginalHandler(0x24F, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1001,35 +1170,177 @@ fn spellInterruptDetour(caster_ptr: u32, target_ptr: u32, interrupted_spell_id:
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: ProcessInstaKillSpellMessage (0x62CBE0) — downstream of SMSG_SPELLINSTAKILLLOG
|
||||
// __fastcall(ECX=casterGuid_ptr, EDX=spellId)
|
||||
// Plain RET (0 stack params)
|
||||
// Fires: SPELL_INSTAKILL
|
||||
// Hook: SMSG_SPELLLOGEXECUTE (opcode 0x24C) — handler table swap
|
||||
// Packet: casterPackGUID, uint32 spellId, uint32 effectCount,
|
||||
// per effect: uint32 effectType, uint32 logCount,
|
||||
// per log entry: varies by effectType (see SpellDefines.h)
|
||||
// Fires: SPELL_INTERRUPT, SPELL_ENERGIZE, SPELL_EXTRA_ATTACKS,
|
||||
// SPELL_SUMMON, SPELL_RESURRECT
|
||||
// Replaces 4 downstream hooks: 0x626A10, 0x62D9F0, 0x62CA00, 0x62ACE0
|
||||
// =============================================================================
|
||||
|
||||
const InstaKillFn = fn (u32, u32) callconv(hook.cc.fastcall) void;
|
||||
// Vanilla spell effect type constants
|
||||
const EFFECT_INSTAKILL: u32 = 1;
|
||||
const EFFECT_POWER_DRAIN: u32 = 8;
|
||||
const EFFECT_HEAL: u32 = 10;
|
||||
const EFFECT_ADD_EXTRA_ATTACKS: u32 = 19;
|
||||
const EFFECT_CREATE_ITEM: u32 = 24;
|
||||
const EFFECT_ENERGIZE: u32 = 30;
|
||||
const EFFECT_DISPEL: u32 = 38;
|
||||
const EFFECT_SUMMON_PET: u32 = 56;
|
||||
const EFFECT_HEAL_MAX_HEALTH: u32 = 67;
|
||||
const EFFECT_INTERRUPT_CAST: u32 = 68;
|
||||
const EFFECT_FEED_PET: u32 = 101;
|
||||
const EFFECT_DURABILITY_DAMAGE: u32 = 111;
|
||||
const EFFECT_RESURRECT_NEW: u32 = 113;
|
||||
|
||||
var instakill_hook: hook.Detour(InstaKillFn) = .{};
|
||||
|
||||
fn instaKillDetour(caster_ptr: u32, spell_id: u32) callconv(hook.cc.fastcall) void {
|
||||
fn spellLogExecuteDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
if (caster_ptr != 0 and spell_id != 0) {
|
||||
const caster_lo = hook.readMem(u32, caster_ptr);
|
||||
const caster_hi = hook.readMem(u32, caster_ptr + 4);
|
||||
const caster_guid: u64 = @as(u64, caster_hi) << 32 | @as(u64, caster_lo);
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
if (caster_guid != 0) {
|
||||
const src_str = guidToString(caster_guid);
|
||||
const src_name = wow.getNameByGUID(caster_guid);
|
||||
const school = getSpellSchool(spell_id);
|
||||
log.fmt("SPELL_INSTAKILL: [{d}]{s} caster=0x{x}\n", .{ spell_id, std.mem.span(getSpellName(spell_id)), caster_guid });
|
||||
// No victim GUID in SMSG_SPELLINSTAKILLLOG — pass zero GUID
|
||||
fireSpell(SUB_SPELL_INSTAKILL, src_str, src_name, GUID_ZERO, "", spell_id, getSpellName(spell_id), school);
|
||||
const caster_guid = cdsGetPackedGuid(cds);
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
const effect_count = cdsGet(u32, cds);
|
||||
|
||||
if (caster_guid != null and spell_id != null and effect_count != null and
|
||||
caster_guid.? != 0 and spell_id.? != 0)
|
||||
{
|
||||
const src_str = guidToString(caster_guid.?);
|
||||
const src_name = wow.getNameByGUID(caster_guid.?);
|
||||
const spell_school = getSpellSchool(spell_id.?);
|
||||
const spell_name = getSpellName(spell_id.?);
|
||||
|
||||
var eff_i: u32 = 0;
|
||||
while (eff_i < effect_count.?) : (eff_i += 1) {
|
||||
const effect_type = cdsGet(u32, cds) orelse break;
|
||||
const log_count = cdsGet(u32, cds) orelse break;
|
||||
|
||||
var log_i: u32 = 0;
|
||||
while (log_i < log_count) : (log_i += 1) {
|
||||
switch (effect_type) {
|
||||
EFFECT_POWER_DRAIN => {
|
||||
// targetGUID(8), amount(4), powerType(4), multiplier(float 4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const amount = cdsGet(u32, cds) orelse break;
|
||||
const power_type = cdsGet(u32, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break; // multiplier float, skip
|
||||
if (target != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
log.fmt("SPELL_DRAIN: [{d}]{s} amt={d} power={d}\n", .{ spell_id.?, std.mem.span(spell_name), amount, power_type });
|
||||
fireSpellEnergize(SUB_SPELL_DRAIN, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school, amount, power_type);
|
||||
}
|
||||
},
|
||||
EFFECT_ENERGIZE => {
|
||||
// targetGUID(8), amount(4), powerType(4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const amount = cdsGet(u32, cds) orelse break;
|
||||
const power_type = cdsGet(u32, cds) orelse break;
|
||||
if (target != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
log.fmt("SPELL_ENERGIZE: [{d}]{s} amt={d} power={d}\n", .{ spell_id.?, std.mem.span(spell_name), amount, power_type });
|
||||
fireSpellEnergize(SUB_SPELL_ENERGIZE, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school, amount, power_type);
|
||||
}
|
||||
},
|
||||
EFFECT_ADD_EXTRA_ATTACKS => {
|
||||
// targetGUID(8), count(4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const count = cdsGet(u32, cds) orelse break;
|
||||
_ = target;
|
||||
log.fmt("SPELL_EXTRA_ATTACKS: [{d}]{s} count={d}\n", .{ spell_id.?, std.mem.span(spell_name), count });
|
||||
fireSpellExtraAttacks(src_str, src_name, GUID_ZERO, "", spell_id.?, spell_name, spell_school, count);
|
||||
},
|
||||
EFFECT_INTERRUPT_CAST => {
|
||||
// targetGUID(8), interruptedSpellId(4)
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
const interrupted_id = cdsGet(u32, cds) orelse break;
|
||||
if (target != 0 and interrupted_id != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
const extra_school = getSpellSchool(interrupted_id);
|
||||
log.fmt("SPELL_INTERRUPT: [{d}]{s} interrupted=[{d}]{s}\n", .{
|
||||
spell_id.?, std.mem.span(spell_name), interrupted_id, std.mem.span(getSpellName(interrupted_id)),
|
||||
});
|
||||
// Now we have the interrupting spell ID (spell_id) — previously was 0
|
||||
fireSpellInterrupt(SUB_SPELL_INTERRUPT, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school, interrupted_id, getSpellName(interrupted_id), extra_school);
|
||||
}
|
||||
},
|
||||
EFFECT_HEAL, EFFECT_HEAL_MAX_HEALTH => {
|
||||
// targetGUID(8), amount(4), critical(4)
|
||||
_ = cdsGet(u64, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
// Heals from SPELLLOGEXECUTE are handled by SMSG_SPELLHEALLOG — skip here
|
||||
},
|
||||
EFFECT_CREATE_ITEM => {
|
||||
// itemEntry(4)
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
},
|
||||
EFFECT_FEED_PET => {
|
||||
// itemEntry(4)
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
},
|
||||
EFFECT_DURABILITY_DAMAGE => {
|
||||
// targetGUID(8), itemEntry(4), unk(4)
|
||||
_ = cdsGet(u64, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
_ = cdsGet(u32, cds) orelse break;
|
||||
},
|
||||
else => {
|
||||
// Most other effect types: just targetGUID(8)
|
||||
// This covers INSTAKILL, RESURRECT, DISPEL, SUMMON variants, etc.
|
||||
const target = cdsGet(u64, cds) orelse break;
|
||||
|
||||
if (target != 0) {
|
||||
const dst_str = guidToString(target);
|
||||
const dst_name = wow.getNameByGUID(target);
|
||||
|
||||
if (isSummonEffect(effect_type)) {
|
||||
log.fmt("SPELL_SUMMON: [{d}]{s}\n", .{ spell_id.?, std.mem.span(spell_name) });
|
||||
fireSpell(SUB_SPELL_SUMMON, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school);
|
||||
} else if (isResurrectEffect(effect_type)) {
|
||||
log.fmt("SPELL_RESURRECT: [{d}]{s}\n", .{ spell_id.?, std.mem.span(spell_name) });
|
||||
fireSpell(SUB_SPELL_RESURRECT, src_str, src_name, dst_str, dst_name, spell_id.?, spell_name, spell_school);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instakill_hook.callOriginal(.{ caster_ptr, spell_id });
|
||||
cdsSetRead(cds, saved_read);
|
||||
return callOriginalHandler(0x24C, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hook: SMSG_SPELLINSTAKILLLOG (opcode 0x32F) — handler table swap
|
||||
// Packet: victimGUID(8 raw), uint32 spellId
|
||||
// Fires: SPELL_INSTAKILL
|
||||
// =============================================================================
|
||||
|
||||
fn instaKillDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
const saved_read = cdsGetRead(cds);
|
||||
|
||||
const victim_guid = cdsGet(u64, cds);
|
||||
const spell_id = cdsGet(u32, cds);
|
||||
|
||||
cdsSetRead(cds, saved_read);
|
||||
|
||||
if (victim_guid != null and spell_id != null and victim_guid.? != 0) {
|
||||
const dst_str = guidToString(victim_guid.?);
|
||||
const dst_name = wow.getNameByGUID(victim_guid.?);
|
||||
const school = getSpellSchool(spell_id.?);
|
||||
log.fmt("SPELL_INSTAKILL: [{d}]{s} victim=0x{x}\n", .{ spell_id.?, std.mem.span(getSpellName(spell_id.?)), victim_guid.? });
|
||||
// Caster not in packet — src is unknown
|
||||
fireSpell(SUB_SPELL_INSTAKILL, GUID_ZERO, "", dst_str, dst_name, spell_id.?, getSpellName(spell_id.?), school);
|
||||
}
|
||||
|
||||
return callOriginalHandler(0x32F, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1038,8 +1349,6 @@ fn instaKillDetour(caster_ptr: u32, spell_id: u32) callconv(hook.cc.fastcall) vo
|
||||
// Fires: PARTY_KILL
|
||||
// =============================================================================
|
||||
|
||||
var party_kill_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn partyKillLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -1059,7 +1368,7 @@ fn partyKillLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.
|
||||
fireBase(SUB_PARTY_KILL, src_str, src_name, dst_str, dst_name);
|
||||
}
|
||||
|
||||
return party_kill_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x1F5, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1071,8 +1380,6 @@ fn partyKillLogDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.
|
||||
const OPCODE_SPELL_START: u32 = 0x131;
|
||||
const OPCODE_SPELL_GO: u32 = 0x132;
|
||||
|
||||
var spell_start_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -1126,7 +1433,8 @@ fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc
|
||||
cdsSetRead(cds, saved_read);
|
||||
}
|
||||
|
||||
return spell_start_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
// Both 0x131 and 0x132 share this handler; callOriginal uses the opcode passed in EDX
|
||||
return callOriginalHandler(@intCast(opcode), unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1135,8 +1443,6 @@ fn spellStartDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc
|
||||
// Fires: SPELL_CAST_FAILED (when status != 0)
|
||||
// =============================================================================
|
||||
|
||||
var cast_result_hook: hook.Detour(FastCallPacketHandlerFn) = .{};
|
||||
|
||||
fn castResultDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc.fastcall) u32 {
|
||||
asm volatile ("" ::: .{ .esi = true, .edi = true, .ebx = true });
|
||||
|
||||
@@ -1158,7 +1464,7 @@ fn castResultDetour(unk: u32, opcode: u32, unk2: u32, cds: u32) callconv(hook.cc
|
||||
}
|
||||
}
|
||||
|
||||
return cast_result_hook.callOriginal(.{ unk, opcode, unk2, cds });
|
||||
return callOriginalHandler(0x130, unk, opcode, unk2, cds);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -1906,28 +2212,10 @@ pub fn installHooks() void {
|
||||
log.print("Hooked FrameScript_CreateEvents\n");
|
||||
}
|
||||
|
||||
if (spell_dmg_hook.attach(0x5E85E0, &spellNonMeleeDmgLogDetour) != .ok) {
|
||||
log.print("FAILED to hook SpellNonMeleeDmgLogHandler\n");
|
||||
if (init_engine_hook.attach(0x401570, &initGameEngineDetour) != .ok) {
|
||||
log.print("FAILED to hook InitializeGameEngine\n");
|
||||
} else {
|
||||
log.print("Hooked SpellNonMeleeDmgLogHandler\n");
|
||||
}
|
||||
|
||||
if (periodic_hook.attach(0x626DD0, &periodicAuraLogDetour) != .ok) {
|
||||
log.print("FAILED to hook PeriodicAuraLogHandler\n");
|
||||
} else {
|
||||
log.print("Hooked PeriodicAuraLogHandler\n");
|
||||
}
|
||||
|
||||
if (heal_hook.attach(0x62C770, &healDisplayDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellPowerDrainMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellPowerDrainMessage (SPELL_HEAL)\n");
|
||||
}
|
||||
|
||||
if (melee_hook.attach(0x6255B0, &meleeDispatcherDetour) != .ok) {
|
||||
log.print("FAILED to hook MeleeDispatcher\n");
|
||||
} else {
|
||||
log.print("Hooked MeleeDispatcher\n");
|
||||
log.print("Hooked InitializeGameEngine (handler table swaps after return)\n");
|
||||
}
|
||||
|
||||
if (env_dmg_hook.attach(0x62AAC0, &envDamageDetour) != .ok) {
|
||||
@@ -1936,23 +2224,6 @@ pub fn installHooks() void {
|
||||
log.print("Hooked ProcessEnvironmentalDamage\n");
|
||||
}
|
||||
|
||||
if (party_kill_hook.attach(0x628890, &partyKillLogDetour) != .ok) {
|
||||
log.print("FAILED to hook PartyKillLogHandler\n");
|
||||
} else {
|
||||
log.print("Hooked PartyKillLogHandler\n");
|
||||
}
|
||||
|
||||
if (spell_start_hook.attach(0x6E7640, &spellStartDetour) != .ok) {
|
||||
log.print("FAILED to hook SpellStartHandler\n");
|
||||
} else {
|
||||
log.print("Hooked SpellStartHandler\n");
|
||||
}
|
||||
|
||||
if (cast_result_hook.attach(0x6E7330, &castResultDetour) != .ok) {
|
||||
log.print("FAILED to hook CastResultHandler\n");
|
||||
} else {
|
||||
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");
|
||||
@@ -1960,35 +2231,6 @@ pub fn installHooks() void {
|
||||
log.print("Hooked HandleSpellInterruptUpdate (SPELL_CAST_FAILED others)\n");
|
||||
}
|
||||
|
||||
if (spell_missed_hook.attach(0x62BAB0, &spellMissedDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellCombatResult\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellCombatResult (SPELL_MISSED)\n");
|
||||
}
|
||||
|
||||
if (damage_shield_hook.attach(0x62CA20, &damageShieldDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellDrainEffectMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellDrainEffectMessage (DAMAGE_SHIELD)\n");
|
||||
}
|
||||
|
||||
if (energize_hook.attach(0x62CA00, &energizeDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessStandardPowerGainMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessStandardPowerGainMessage (SPELL_ENERGIZE)\n");
|
||||
}
|
||||
|
||||
if (spell_interrupt_hook.attach(0x626A10, &spellInterruptDetour) != .ok) {
|
||||
log.print("FAILED to hook DisplaySpellInterruptMessage\n");
|
||||
} else {
|
||||
log.print("Hooked DisplaySpellInterruptMessage (SPELL_INTERRUPT)\n");
|
||||
}
|
||||
|
||||
if (instakill_hook.attach(0x62CBE0, &instaKillDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessInstaKillSpellMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessInstaKillSpellMessage (SPELL_INSTAKILL)\n");
|
||||
}
|
||||
|
||||
if (unit_death_hook.attach(0x605860, &unitDeathDetour) != .ok) {
|
||||
log.print("FAILED to hook HandleUnitDeath\n");
|
||||
@@ -2014,11 +2256,7 @@ pub fn installHooks() void {
|
||||
log.print("Hooked ValidateSpellSlot (SPELL_AURA_*_DOSE)\n");
|
||||
}
|
||||
|
||||
if (extra_attacks_hook.attach(0x62D9F0, &extraAttacksDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessExtraAttacksSpellMessage\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessExtraAttacksSpellMessage (SPELL_EXTRA_ATTACKS)\n");
|
||||
}
|
||||
// extra_attacks_hook — now handled by SPELLLOGEXECUTE table swap (0x24C)
|
||||
|
||||
if (dispel_hook.attach(0x62D480, &dispelDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessAuraDispelMessage\n");
|
||||
@@ -2026,11 +2264,7 @@ pub fn installHooks() void {
|
||||
log.print("Hooked ProcessAuraDispelMessage (SPELL_DISPEL)\n");
|
||||
}
|
||||
|
||||
if (spell_effect_hook.attach(0x62ACE0, &spellEffectDetour) != .ok) {
|
||||
log.print("FAILED to hook ProcessSpellEffect\n");
|
||||
} else {
|
||||
log.print("Hooked ProcessSpellEffect (SPELL_SUMMON/RESURRECT/ENERGIZE)\n");
|
||||
}
|
||||
// spell_effect_hook — now handled by SPELLLOGEXECUTE table swap (0x24C)
|
||||
|
||||
if (aura_duration_hook.attach(0x4E4390, &auraDurationDetour) != .ok) {
|
||||
log.print("FAILED to hook SetActionCooldownTimer\n");
|
||||
@@ -2047,28 +2281,21 @@ pub fn installHooks() void {
|
||||
|
||||
pub fn removeHooks() void {
|
||||
if (g_is_hook_owner) {
|
||||
// Restore handler table swaps (no code to unpatch)
|
||||
restoreAllHandlers();
|
||||
// Detach remaining JMP-patching detours (non-packet hooks)
|
||||
spell_failed_other_hook.detach();
|
||||
dispel_failed_hook.detach();
|
||||
aura_duration_hook.detach();
|
||||
spell_effect_hook.detach();
|
||||
dispel_hook.detach();
|
||||
extra_attacks_hook.detach();
|
||||
aura_dose_hook.detach();
|
||||
aura_applied_hook.detach();
|
||||
aura_removed_hook.detach();
|
||||
unit_death_hook.detach();
|
||||
instakill_hook.detach();
|
||||
spell_interrupt_hook.detach();
|
||||
damage_shield_hook.detach();
|
||||
spell_missed_hook.detach();
|
||||
cast_result_hook.detach();
|
||||
spell_start_hook.detach();
|
||||
party_kill_hook.detach();
|
||||
// spell_interrupt_hook — now in SPELLLOGEXECUTE table swap
|
||||
env_dmg_hook.detach();
|
||||
melee_hook.detach();
|
||||
heal_hook.detach();
|
||||
periodic_hook.detach();
|
||||
spell_dmg_hook.detach();
|
||||
// energize_hook — now in SPELLLOGEXECUTE table swap
|
||||
init_engine_hook.detach();
|
||||
create_events_hook.detach();
|
||||
resize_events_hook.detach();
|
||||
log.close();
|
||||
|
||||
+14
@@ -144,3 +144,17 @@ pub fn checknumber(L: State, index: i32) f64 {
|
||||
const f: *const fn (State, i32) callconv(hook.cc.fastcall) f64 = @ptrFromInt(0x6F4C80);
|
||||
return f(L, index);
|
||||
}
|
||||
|
||||
// Lua 5.0 pseudo-index for globals table
|
||||
pub const GLOBALS_INDEX: i32 = -10001;
|
||||
|
||||
pub fn setglobal(L: State, name: [*:0]const u8) void {
|
||||
pushstring(L, name);
|
||||
insert(L, -2); // swap: value is now at -1, name at -2 → after insert: name at -2, value at -1
|
||||
settable(L, GLOBALS_INDEX);
|
||||
}
|
||||
|
||||
pub fn getglobal(L: State, name: [*:0]const u8) void {
|
||||
pushstring(L, name);
|
||||
gettable(L, GLOBALS_INDEX);
|
||||
}
|
||||
|
||||
+88
-6
@@ -137,6 +137,74 @@ fn registerLuaFunctions() void {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Module version registry — GetWeirdUtilsVersion(name?) from Lua
|
||||
// =============================================================================
|
||||
//
|
||||
// Populates a global "WeirdUtils" table with { module_name = "version", ... }
|
||||
// for each enabled module. If the table already exists (from another DLL),
|
||||
// new entries are merged in additively.
|
||||
//
|
||||
// GetWeirdUtilsVersion() → returns the full WeirdUtils table
|
||||
// GetWeirdUtilsVersion("name") → returns version string for that module, or nil
|
||||
|
||||
const all_module_names = @import("build_options").all_module_names;
|
||||
const all_module_versions = @import("build_options").all_module_versions;
|
||||
|
||||
fn registerModuleVersions() void {
|
||||
const L = lua.getContext();
|
||||
|
||||
// Get or create the WeirdUtils global table
|
||||
lua.getglobal(L, "WeirdUtils");
|
||||
if (lua.typeOf(L, -1) != 5) { // 5 = LUA_TTABLE
|
||||
lua.pop(L, 1);
|
||||
lua.newtable(L);
|
||||
}
|
||||
|
||||
// For each enabled module, set WeirdUtils[name] = version
|
||||
inline for (all_module_names, 0..) |name, i| {
|
||||
const enabled = @field(@import("build_options"), "enable_" ++ name);
|
||||
if (enabled) {
|
||||
lua.pushstring(L, @ptrCast(name.ptr));
|
||||
lua.pushstring(L, @ptrCast(all_module_versions[i].ptr));
|
||||
lua.settable(L, -3);
|
||||
}
|
||||
}
|
||||
|
||||
// Set as global
|
||||
lua.setglobal(L, "WeirdUtils");
|
||||
|
||||
// Register query function
|
||||
registerFunction("GetWeirdUtilsVersion", @intFromPtr(&luaGetWeirdUtilsVersion));
|
||||
}
|
||||
|
||||
fn luaGetWeirdUtilsVersion(L_ecx: usize) callconv(hook.cc.fastcall) u32 {
|
||||
const L: lua.State = @ptrFromInt(L_ecx);
|
||||
const nargs = lua.gettop(L);
|
||||
|
||||
if (nargs >= 1 and lua.isstring(L, 1)) {
|
||||
// GetWeirdUtilsVersion("name") → return version or nil
|
||||
const name = lua.tostring(L, 1) orelse {
|
||||
lua.pushnil(L);
|
||||
return 1;
|
||||
};
|
||||
lua.getglobal(L, "WeirdUtils");
|
||||
if (lua.typeOf(L, -1) != 5) {
|
||||
lua.pop(L, 1);
|
||||
lua.pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
lua.pushstring(L, name);
|
||||
lua.gettable(L, -2);
|
||||
lua.remove(L, -2); // remove WeirdUtils table, leave value
|
||||
return 1;
|
||||
}
|
||||
|
||||
// GetWeirdUtilsVersion() → return the whole table
|
||||
lua.getglobal(L, "WeirdUtils");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Embedded addon files
|
||||
// =============================================================================
|
||||
@@ -661,11 +729,22 @@ fn removeFileHooks() void {
|
||||
// Hook: LoadScriptFunctions (0x490250)
|
||||
// =============================================================================
|
||||
|
||||
var lsf_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
var register_commands_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
var glue_commands_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
|
||||
fn loadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
|
||||
lsf_hook.callOriginal(.{});
|
||||
/// Hook for Player_LoadScriptFunctions (0x490250).
|
||||
/// Fires after login/reload — registers gameplay Lua functions + version table.
|
||||
fn registerAllSystemCommandsDetour() callconv(hook.cc.stdcall) void {
|
||||
register_commands_hook.callOriginal(.{});
|
||||
registerLuaFunctions();
|
||||
registerModuleVersions();
|
||||
}
|
||||
|
||||
/// Hook for Glue_LoadScriptFunctions (0x46ABB0).
|
||||
/// Fires at the login/glue screen — registers version table so addons can query early.
|
||||
fn glueLoadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
|
||||
glue_commands_hook.callOriginal(.{});
|
||||
registerModuleVersions();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -793,7 +872,8 @@ fn install() void {
|
||||
_ = protection_hook.attach(0x42a320, &luaProtectionDetour);
|
||||
installFileHooks();
|
||||
_ = file_hook.attach(0x648620, &loadFileDetour);
|
||||
_ = lsf_hook.attach(0x490250, &loadScriptFunctionsDetour);
|
||||
_ = register_commands_hook.attach(0x490250, ®isterAllSystemCommandsDetour);
|
||||
_ = glue_commands_hook.attach(0x46ABB0, &glueLoadScriptFunctionsDetour);
|
||||
|
||||
inline for (modules) |m| {
|
||||
if (m.install) |inst| inst();
|
||||
@@ -822,7 +902,8 @@ fn uninstall() void {
|
||||
}
|
||||
|
||||
addons.uninstall();
|
||||
lsf_hook.detach();
|
||||
register_commands_hook.detach();
|
||||
glue_commands_hook.detach();
|
||||
file_hook.detach();
|
||||
removeFileHooks();
|
||||
protection_hook.detach();
|
||||
@@ -900,7 +981,8 @@ fn disableAll() callconv(.c) i32 {
|
||||
logout_hook.detach();
|
||||
engine_init_hook.detach();
|
||||
addons.uninstall();
|
||||
lsf_hook.detach();
|
||||
register_commands_hook.detach();
|
||||
glue_commands_hook.detach();
|
||||
file_hook.detach();
|
||||
removeFileHooks();
|
||||
protection_hook.detach();
|
||||
|
||||
Reference in New Issue
Block a user