diff --git a/DLL_README.md b/DLL_README.md index 89fe73c..f8b5ec1 100644 --- a/DLL_README.md +++ b/DLL_README.md @@ -165,7 +165,7 @@ No other configuration needed, install and forget. ### DPSLog (Combat Log Events) -Provides WotLK 3.3.5-style `COMBAT_LOG_EVENT` for the vanilla client. Fires a single unified event with structured arguments instead of vanilla's fragmented localized text events. Enables modern DPS meter addons without expensive string parsing. +Provides WotLK 3.3.5-style `COMBAT_LOG_EVENT_UNFILTERED` for the vanilla client. Fires a single unified event with structured arguments instead of vanilla's fragmented localized text events. Enables modern DPS meter addons without expensive string parsing. 37 subevents covering all combat interactions: damage (spell, melee, periodic, environmental, damage shield, damage split), healing (direct, periodic, overheal tracking), misses (all types), auras (applied, removed, refreshed, broken, dose changes), casts (start, success, failed, interrupted), power (energize, drain, leech), dispels, extra attacks, deaths, and kills. @@ -297,6 +297,37 @@ if (hMod) { } ``` +### Version Query API + +WeirdUtils registers a Lua global table and query function for addon developers to detect which modules are loaded and their versions. Available from the login screen onward. + +#### `GetWeirdUtilsVersion()` + +Returns the `WeirdUtils` table containing all enabled modules and their version strings: + +```lua +local modules = GetWeirdUtilsVersion() +for name, version in pairs(modules) do + print(name .. " v" .. version) -- e.g. "dpslog v1.0" +end +``` + +#### `GetWeirdUtilsVersion("modulename")` + +Returns the version string for a specific module, or `nil` if not loaded: + +```lua +if GetWeirdUtilsVersion("dpslog") then + -- DPSLog is available, register for COMBAT_LOG_EVENT_UNFILTERED +end + +local ver = GetWeirdUtilsVersion("minimapicons") -- "1.0" or nil +``` + +The `WeirdUtils` table is additive -- if multiple independent DLLs are loaded (e.g. `dpslog.dll` and `minimapicons.dll` separately), each adds its own modules to the shared table. + +--- + ### Module Mutexes Each module also holds a named mutex while active: `Local\WeirdUtils__` (e.g. `Local\WeirdUtils_framecrash_12345`). The exception is transmogfix, which uses `Local\TransmogCoalesceHook_` for legacy reasons. diff --git a/build.zig b/build.zig index ff40963..27a5e67 100644 --- a/build.zig +++ b/build.zig @@ -3,6 +3,7 @@ const std = @import("std"); const ModuleDesc = struct { name: []const u8, desc: []const u8, + version: []const u8 = "1.0", default: bool = true, /// Source directory under src/ (defaults to name if null). src_dir: ?[]const u8 = null, @@ -255,7 +256,7 @@ fn addModuleOptions(b: *std.Build, opts: *std.Build.Step.Options) void { inline for (module_list) |mod| { opts.addOption(bool, "enable_" ++ mod.name, b.option(bool, mod.name, mod.desc) orelse mod.default); } - // Pass full module name list so addons.zig doesn't need a hardcoded copy + // Pass full module name/version lists so main.zig/addons.zig can use them at comptime const names: []const []const u8 = comptime blk: { var n: [module_list.len][]const u8 = undefined; for (module_list, 0..) |mod, i| n[i] = mod.name; @@ -263,6 +264,13 @@ fn addModuleOptions(b: *std.Build, opts: *std.Build.Step.Options) void { break :blk &final; }; opts.addOption([]const []const u8, "all_module_names", names); + const versions: []const []const u8 = comptime blk: { + var v: [module_list.len][]const u8 = undefined; + for (module_list, 0..) |mod, i| v[i] = mod.version; + const final = v; + break :blk &final; + }; + opts.addOption([]const []const u8, "all_module_versions", versions); addFileListOptions(b, opts); } diff --git a/src/dpslog/WSBT/MikCombatEventHelper.lua b/src/dpslog/WSBT/MikCombatEventHelper.lua new file mode 100644 index 0000000..d5a691b --- /dev/null +++ b/src/dpslog/WSBT/MikCombatEventHelper.lua @@ -0,0 +1,4066 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Combat Event Helper +-- Author: Mik +-- Maintainer: Athene +------------------------------------------------------------------------------------- + +-- Create "namespace." +MikCEH = {}; + +------------------------------------------------------------------------------------- +-- Public constants. +------------------------------------------------------------------------------------- + +-- Event types. +MikCEH.EVENTTYPE_DAMAGE = 1; +MikCEH.EVENTTYPE_HEAL = 2; +MikCEH.EVENTTYPE_NOTIFICATION = 3; + +-- Direction types. +MikCEH.DIRECTIONTYPE_PLAYER_INCOMING = 1; +MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING = 2; +MikCEH.DIRECTIONTYPE_PET_OUTGOING = 3; +MikCEH.DIRECTIONTYPE_PET_INCOMING = 4; + +-- Action types. +MikCEH.ACTIONTYPE_HIT = 1; +MikCEH.ACTIONTYPE_MISS = 2; +MikCEH.ACTIONTYPE_DODGE = 3; +MikCEH.ACTIONTYPE_PARRY = 4; +MikCEH.ACTIONTYPE_BLOCK = 5; +MikCEH.ACTIONTYPE_RESIST = 6; +MikCEH.ACTIONTYPE_ABSORB = 7; +MikCEH.ACTIONTYPE_IMMUNE = 8; +MikCEH.ACTIONTYPE_EVADE = 9; +MikCEH.ACTIONTYPE_REFLECT = 10; +MikCEH.ACTIONTYPE_DROWNING = 11; +MikCEH.ACTIONTYPE_FALLING = 12; +MikCEH.ACTIONTYPE_FATIGUE = 13; +MikCEH.ACTIONTYPE_FIRE = 14; +MikCEH.ACTIONTYPE_LAVA = 15; +MikCEH.ACTIONTYPE_SLIME = 16; + + +-- Hit types. +MikCEH.HITTYPE_NORMAL = 1; +MikCEH.HITTYPE_CRIT = 2; +MikCEH.HITTYPE_OVER_TIME = 3; + +-- Damage types. +MikCEH.DAMAGETYPE_PHYSICAL = 1; +MikCEH.DAMAGETYPE_HOLY = 2; +MikCEH.DAMAGETYPE_NATURE = 3; +MikCEH.DAMAGETYPE_FIRE = 4; +MikCEH.DAMAGETYPE_FROST = 5; +MikCEH.DAMAGETYPE_SHADOW = 6; +MikCEH.DAMAGETYPE_ARCANE = 7; +MikCEH.DAMAGETYPE_UNKNOWN = 999; + +-- Partial action types. +MikCEH.PARTIALACTIONTYPE_ABSORB = 1; +MikCEH.PARTIALACTIONTYPE_BLOCK = 2; +MikCEH.PARTIALACTIONTYPE_RESIST = 3; +MikCEH.PARTIALACTIONTYPE_VULNERABLE = 4; +MikCEH.PARTIALACTIONTYPE_CRUSHING = 5; +MikCEH.PARTIALACTIONTYPE_GLANCING = 6; +MikCEH.PARTIALACTIONTYPE_OVERHEAL = 7; + +-- Heal types. +MikCEH.HEALTYPE_NORMAL = 1; +MikCEH.HEALTYPE_CRIT = 2; +MikCEH.HEALTYPE_OVER_TIME = 3; + +-- Notification types. +MikCEH.NOTIFICATIONTYPE_DEBUFF = 1; +MikCEH.NOTIFICATIONTYPE_BUFF = 2; +MikCEH.NOTIFICATIONTYPE_ITEM_BUFF = 3; +MikCEH.NOTIFICATIONTYPE_BUFF_FADE = 4; +MikCEH.NOTIFICATIONTYPE_COMBAT_ENTER = 5; +MikCEH.NOTIFICATIONTYPE_COMBAT_LEAVE = 6; +MikCEH.NOTIFICATIONTYPE_POWER_GAIN = 7; +MikCEH.NOTIFICATIONTYPE_POWER_LOSS = 8; +MikCEH.NOTIFICATIONTYPE_CP_GAIN = 9; +MikCEH.NOTIFICATIONTYPE_HONOR_GAIN = 10; +MikCEH.NOTIFICATIONTYPE_REP_GAIN = 11; +MikCEH.NOTIFICATIONTYPE_REP_LOSS = 12; +MikCEH.NOTIFICATIONTYPE_SKILL_GAIN = 13; +MikCEH.NOTIFICATIONTYPE_EXPERIENCE_GAIN = 14; +MikCEH.NOTIFICATIONTYPE_PC_KILLING_BLOW = 15; +MikCEH.NOTIFICATIONTYPE_NPC_KILLING_BLOW = 16; + + +-- Trigger types. +MikCEH.TRIGGERTYPE_SELF_HEALTH = 1; +MikCEH.TRIGGERTYPE_SELF_MANA = 2; +MikCEH.TRIGGERTYPE_PET_HEALTH = 3; +MikCEH.TRIGGERTYPE_ENEMY_HEALTH = 4; +MikCEH.TRIGGERTYPE_FRIENDLY_HEALTH = 5; +MikCEH.TRIGGERTYPE_SEARCH_PATTERN = 6; + + +------------------------------------------------------------------------------------- +-- Public variables. +------------------------------------------------------------------------------------- + +-- Hold combat event data. +MikCEH.CombatEventData = {}; + +-- Hold trigger event data. +MikCEH.TriggerEventData = {}; + +------------------------------------------------------------------------------------- +-- Private constants. +------------------------------------------------------------------------------------- + +-- Amount of time to delay between selected player list updates and how long +-- to hold a recently selected player in cache. +local RECENTLY_SELECTED_PLAYERS_UPDATE_INTERVAL = 1; +local RECENTLY_SELECTED_PLAYERS_HOLD_TIME = 45; + + +------------------------------------------------------------------------------------- +-- Private variables. +------------------------------------------------------------------------------------- + +-- Holds the events the helper is interested in receiving. +local listenEvents = {}; + +-- Holds formatted global strings. +local globalStringInfoArray = {}; + +-- Holds the name and class of the player. +local playerName; +local playerClass; + +-- Tables to hold ordered/unordered captured data. +local orderedCaptureData = {}; +local unorderedCaptureData = {}; + + +-- Holds whether or not event searching mode is active and the pattern for it. +local searchMode = false; +local searchModePattern; + +-- Tables to hold the triggers in a format optimized for searching. +local selfHealthTriggers = {}; +local selfManaTriggers = {}; +local petHealthTriggers = {}; +local enemyHealthTriggers = {}; +local friendlyHealthTriggers = {}; +local searchPatternTriggers = {}; + +-- Holds previous health and mana values. +local lastSelfHealthPercentage = 0; +local lastSelfManaPercentage = 0; +local lastSelfManaAmount = UnitMana("player"); +local lastPetHealthPercentage = 0; +local lastEnemyHealthPercentage = 0; +local lastFriendlyHealthPercentage = 0; + +-- Holds a list of recently selected hostile players. +MikCEH.recentlySelectedPlayers = {}; +local recentlySelectedPlayers = MikCEH.recentlySelectedPlayers; +local elapsedTime = 0; + +-- Map combat log events to their parsing functions. +local combatEventMap = {} +local onEventMap = {} + +-- Locally cache frequently used functions to minimize global lookups. +local strfind, strgfind = string.find, string.gfind +-- strfind = function () return nil end +local tinsert, tgetn, tsetn = table.insert, table.getn, table.setn +local UnitExists, UnitIsFriend, UnitIsPlayer = UnitExists, UnitIsFriend, UnitIsPlayer +local UnitName, UnitClass, UnitMana = UnitName, UnitClass, UnitMana +local GetComboPoints = GetComboPoints + +-- Determine if the player's pet is referenced in the given message. +local function PetNameInMessage(msg) + local petName = UnitName("pet") + return petName and strfind(msg, petName) +end + + +------------------------------------------------------------------------------------- +-- Core event handlers. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Registers all of the events the helper is interested in. +-- ********************************************************************************** +function MikCEH.RegisterEvents() + -- Register all events (combat parse + core). + for i = 1, tgetn(listenEvents) do + MCEHEventFrame:RegisterEvent(listenEvents[i]) + end +end + + +-- ********************************************************************************** +-- Unregisters all of the event the helper registered for. +-- ********************************************************************************** +function MikCEH.UnregisterEvents() + -- Unregister all events. + for i = 1, tgetn(listenEvents) do + MCEHEventFrame:UnregisterEvent(listenEvents[i]) + end +end + + +-- ********************************************************************************** +-- Events that CLEU replaces (combat damage/heal/miss/dot/shield/pet/aura parsing). +-- Notification-only events (honor, XP, rep, skill, item buffs, buff fades) are NOT +-- replaced by CLEU and stay registered always. +-- ********************************************************************************** +MikCEH.cleuReplacedEvents = { + "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS", + "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS", + "CHAT_MSG_COMBAT_PARTY_HITS", + "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES", + "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES", + "CHAT_MSG_COMBAT_PARTY_MISSES", + "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE", + "CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE", + "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE", + "CHAT_MSG_SPELL_PARTY_DAMAGE", + "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS", + "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF", + "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF", + "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", + "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS", + "CHAT_MSG_COMBAT_SELF_HITS", + "CHAT_MSG_COMBAT_SELF_MISSES", + "CHAT_MSG_SPELL_SELF_DAMAGE", + "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF", + "CHAT_MSG_SPELL_SELF_BUFF", + "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", + "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS", + "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS", + "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", + "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", + "CHAT_MSG_COMBAT_PET_HITS", + "CHAT_MSG_COMBAT_PET_MISSES", + "CHAT_MSG_SPELL_PET_DAMAGE", + "CHAT_MSG_SPELL_AURA_GONE_SELF", + "CHAT_MSG_COMBAT_HOSTILE_DEATH", +} + +-- Build lookup for fast profiling checks +MikCEH.cleuReplacedLookup = {} +for _, ev in ipairs(MikCEH.cleuReplacedEvents) do + MikCEH.cleuReplacedLookup[ev] = true +end + +function MikCEH.RegisterCombatParseEvents() + for _, ev in ipairs(MikCEH.cleuReplacedEvents) do + MCEHEventFrame:RegisterEvent(ev) + end +end + +function MikCEH.UnregisterCombatParseEvents() + for _, ev in ipairs(MikCEH.cleuReplacedEvents) do + MCEHEventFrame:UnregisterEvent(ev) + end +end + + +-- ********************************************************************************** +-- Called when the helper's event frame is loaded. +-- ********************************************************************************** +function MikCEH.OnLoad() + -- Load up the listen events table with the events the helper is interested in. + tinsert(listenEvents, "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS"); -- Incoming Melee Hits/Crits + tinsert(listenEvents, "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS"); -- Incoming Melee Hits/Crits + tinsert(listenEvents, "CHAT_MSG_COMBAT_PARTY_HITS"); -- Incoming Melee Hits/Crits + tinsert(listenEvents, "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES"); -- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes + tinsert(listenEvents, "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES"); -- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes + tinsert(listenEvents, "CHAT_MSG_COMBAT_PARTY_MISSES"); -- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes + tinsert(listenEvents, "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"); -- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Power Losses + tinsert(listenEvents, "CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE"); -- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Power Losses -- athenne add + tinsert(listenEvents, "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"); -- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Power Losses + tinsert(listenEvents, "CHAT_MSG_SPELL_PARTY_DAMAGE"); -- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Power Losses + tinsert(listenEvents, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS"); -- Incoming damage from shields + tinsert(listenEvents, "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF"); -- Incoming Heals + tinsert(listenEvents, "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF"); -- Incoming Heals + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"); -- Incoming Debuffs, DoTs, Power Gains + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"); -- Incoming Buffs, HoTs, Power Gains + + tinsert(listenEvents, "CHAT_MSG_COMBAT_SELF_HITS"); -- Outgoing Melee Hits/Crits, Environmental Damage + tinsert(listenEvents, "CHAT_MSG_COMBAT_SELF_MISSES"); -- Outgoing Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes, Evades + tinsert(listenEvents, "CHAT_MSG_SPELL_SELF_DAMAGE"); -- Outgoing Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades + tinsert(listenEvents, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF"); -- Outgoing damage from shields + tinsert(listenEvents, "CHAT_MSG_SPELL_SELF_BUFF"); -- Outgoing Heals, Power Gains, Dispel/Purge Resists + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"); -- Outgoing HoTs + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"); -- Outgoing HoTs + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS"); + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"); -- Outgoing DoTs + tinsert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"); -- Outgoing DoTs, Power Losses + tinsert(listenEvents, "CHAT_MSG_COMBAT_PET_HITS"); -- Outgoing Pet Melee Hits/Crits + tinsert(listenEvents, "CHAT_MSG_COMBAT_PET_MISSES"); -- Outgoing Pet Melee Misses + tinsert(listenEvents, "CHAT_MSG_SPELL_PET_DAMAGE"); -- Outgoing Pet Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades + + tinsert(listenEvents, "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS"); -- Item Buffs + tinsert(listenEvents, "CHAT_MSG_SPELL_AURA_GONE_SELF"); -- Buff Fades + tinsert(listenEvents, "CHAT_MSG_COMBAT_HONOR_GAIN"); -- Honor Gains + tinsert(listenEvents, "CHAT_MSG_COMBAT_FACTION_CHANGE"); -- Reputation Gains/Losses + tinsert(listenEvents, "CHAT_MSG_SKILL"); -- Skill Gains + tinsert(listenEvents, "CHAT_MSG_COMBAT_XP_GAIN"); -- Experience Gains + tinsert(listenEvents, "CHAT_MSG_COMBAT_HOSTILE_DEATH"); -- Killing Blows +-- tinsert(listenEvents, "CHAT_MSG_SYSTEM"); -- Created Items + + tinsert(listenEvents, "PLAYER_REGEN_ENABLED"); -- Leave Combat + tinsert(listenEvents, "PLAYER_REGEN_DISABLED"); -- Enter Combat + tinsert(listenEvents, "PLAYER_COMBO_POINTS"); -- Combo Point Gains + tinsert(listenEvents, "UNIT_HEALTH"); -- Health changes. + tinsert(listenEvents, "UNIT_MANA"); -- Mana changes. + + tinsert(listenEvents, "PLAYER_TARGET_CHANGED"); -- Target changes. + + -- Register for the ADDON_LOADED event. + MCEHEventFrame:RegisterEvent("ADDON_LOADED"); +end + + +-- ********************************************************************************** +-- Called when the events the helper registered for occur. +-- ********************************************************************************** +function MikCEH.OnEvent() + if event == "ADDON_LOADED" then + if arg1 == MikSBT.MOD_NAME then + this:UnregisterEvent("ADDON_LOADED") + MikCEH.RegisterEvents() + MikCEH.Init() + end + return + end + + local handler = onEventMap[event] + if handler then + handler() + else + MikCEH.ParseSearchPatternTriggers(event, arg1) + MikCEH.ParseCombatEvents(event, arg1) + end +end + + +-- ********************************************************************************** +-- This function parses the chat message combat events. +-- ********************************************************************************** +function MikCEH.OnUpdate() + -- Increment the amount of time passed since the last update. + elapsedTime = elapsedTime + arg1; + + -- Check if it's time for an update. + if (elapsedTime >= RECENTLY_SELECTED_PLAYERS_UPDATE_INTERVAL) then + -- Loop through all of the recently selected players. + for playerName, lastSeen in recentlySelectedPlayers do + -- Increment the amount of time since the player was last seen. + recentlySelectedPlayers[playerName] = lastSeen + elapsedTime; + + -- Check if enough time has passed and remove the player from the list. + if (lastSeen + elapsedTime >= RECENTLY_SELECTED_PLAYERS_HOLD_TIME) then + recentlySelectedPlayers[playerName] = nil; + end + end + + -- Reset the elapsed time. + elapsedTime = 0; + end +end + + +-- ********************************************************************************** +-- This function parses the chat message combat events. +-- ********************************************************************************** +function MikCEH.ParseCombatEvents(event, combatMessage) + local func = combatEventMap[event] + if func then + func(combatMessage) + end +end + + +-- ********************************************************************************** +-- Called when the helper is fully loaded. +-- ********************************************************************************** +function MikCEH.Init() + -- Get the name of the player and the player's class. + playerName = UnitName("player"); + _, playerClass = UnitClass("player"); + MikCEH.InitCombatEventMap(); + MikCEH.InitOnEventMap(); +end + +-- ***************************************************************************** +-- Builds a lookup table for combat log events to minimize string comparisons. +-- ***************************************************************************** +function MikCEH.InitCombatEventMap() + local assign = function(events, func) + for _, e in ipairs(events) do + combatEventMap[e] = func + end + end + + local function incomingSpellDamage(msg) + MikCEH.ParseForIncomingSpellHitsAndMisses(msg) + MikCEH.ParseForPowerLosses(msg) + end + + local function periodicSelfBuffs(msg) + if (not MikCEH.ParseForIncomingSpellHeals(msg)) then + MikCEH.ParseForIncomingBuffs(msg) + MikCEH.ParseForOutgoingHoTs(msg) + end + end + + local function selfHits(msg) + if (not MikCEH.ParseForEnvironmentalDamage(msg)) then + MikCEH.ParseForOutgoingHits(msg) + end + end + + local function selfBuff(msg) + if (not MikCEH.ParseForPowerGains(msg)) then + MikCEH.ParseForOutgoingSpellHeals(msg) + MikCEH.ParseForOutgoingDispelResists(msg) + end + end + + local function outgoingDoTs(msg) + MikCEH.ParseForOutgoingDoTs(msg) + MikCEH.ParseForPowerLosses(msg) + end + + assign({"CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS", "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS", "CHAT_MSG_COMBAT_PARTY_HITS"}, MikCEH.ParseForIncomingHits) + assign({"CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES", "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES", "CHAT_MSG_COMBAT_PARTY_MISSES"}, MikCEH.ParseForIncomingMisses) + assign({"CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE", "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE", "CHAT_MSG_SPELL_PARTY_DAMAGE"}, incomingSpellDamage) + assign({"CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS"}, MikCEH.ParseForIncomingDamageShieldDamage) + assign({"CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF", "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF"}, MikCEH.ParseForIncomingSpellHeals) + assign({"CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"}, function(msg) MikCEH.ParseForIncomingDebuffs(msg); MikCEH.ParseForPowerGains(msg); end) + assign({"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}, periodicSelfBuffs) + assign({"CHAT_MSG_COMBAT_SELF_HITS"}, selfHits) + assign({"CHAT_MSG_COMBAT_SELF_MISSES"}, MikCEH.ParseForOutgoingMisses) + assign({"CHAT_MSG_SPELL_SELF_DAMAGE"}, MikCEH.ParseForOutgoingSpellHitsAndMisses) + assign({"CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF"}, MikCEH.ParseForOutgoingDamageShieldDamage) + assign({"CHAT_MSG_SPELL_SELF_BUFF"}, selfBuff) + assign({"CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"}, MikCEH.ParseForOutgoingHoTs) + assign({"CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"}, outgoingDoTs) + assign({"CHAT_MSG_COMBAT_PET_HITS"}, MikCEH.ParseForOutgoingPetHits) + assign({"CHAT_MSG_COMBAT_PET_MISSES"}, MikCEH.ParseForOutgoingPetMisses) + assign({"CHAT_MSG_SPELL_PET_DAMAGE"}, MikCEH.ParseForOutgoingPetSpellHitsAndMisses) + assign({"CHAT_MSG_SPELL_ITEM_ENCHANTMENTS"}, MikCEH.ParseForIncomingItemBuffs) + assign({"CHAT_MSG_SPELL_AURA_GONE_SELF"}, MikCEH.ParseForBuffFades) + assign({"CHAT_MSG_COMBAT_HONOR_GAIN"}, MikCEH.ParseForHonorGains) + assign({"CHAT_MSG_COMBAT_FACTION_CHANGE"}, MikCEH.ParseForReputationGainsAndLosses) + assign({"CHAT_MSG_SKILL"}, MikCEH.ParseForSkillGains) + assign({"CHAT_MSG_COMBAT_XP_GAIN"}, MikCEH.ParseForExperienceGains) + assign({"CHAT_MSG_COMBAT_HOSTILE_DEATH"}, MikCEH.ParseForKillingBlows) +end + +-- Build a lookup table for non-chat events handled in OnEvent. +function MikCEH.InitOnEventMap() + onEventMap.PLAYER_REGEN_ENABLED = function() + local data = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_COMBAT_LEAVE, nil, nil) + MikCEH.SendEvent(data) + end + + onEventMap.PLAYER_REGEN_DISABLED = function() + local data = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_COMBAT_ENTER, nil, nil) + MikCEH.SendEvent(data) + end + + onEventMap.PLAYER_COMBO_POINTS = function() + local numCP = GetComboPoints() + if numCP ~= 0 then + local data = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_CP_GAIN, numCP, nil) + MikCEH.SendEvent(data) + end + end + + onEventMap.UNIT_HEALTH = function() + if arg1 == "player" then + MikCEH.ParseSelfHealthTriggers() + elseif arg1 == "target" then + if not UnitIsFriend("player", "target") then + MikCEH.ParseEnemyHealthTriggers() + else + MikCEH.ParseFriendlyHealthTriggers() + end + elseif arg1 == "pet" then + MikCEH.ParsePetHealthTriggers() + end + end + + onEventMap.UNIT_MANA = function() + if arg1 == "player" then + MikCEH.ParseSelfManaTriggers() + end + end + + onEventMap.PLAYER_TARGET_CHANGED = function() + if UnitExists("target") and UnitIsPlayer("target") and not UnitIsFriend("player", "target") then + local name = UnitName("target") + if name then + recentlySelectedPlayers[name] = 0 + end + end + end +end + + +------------------------------------------------------------------------------------- +-- Combat Parse Functions. +------------------------------------------------------------------------------------- + + +-- ********************************************************************************** +-- Parses the passed combat message for an incoming hit message. +-- ********************************************************************************** +function MikCEH.ParseForIncomingHits(combatMessage) + -- Look for a normal hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITOTHERSELF", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITOTHERSELF", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a normal hit from an elemental. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITSCHOOLOTHERSELF", {"%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a crit from an elemental. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITSCHOOLOTHERSELF", {"%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a normal hit on your pet + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITOTHEROTHER", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a crit on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITOTHEROTHER", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for an incoming miss message. +-- ********************************************************************************** +function MikCEH.ParseForIncomingMisses(combatMessage) + -- Look for a normal miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "MISSEDOTHERSELF", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSDODGEOTHERSELF", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSPARRYOTHERSELF", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSBLOCKOTHERSELF", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSABSORBOTHERSELF", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSIMMUNEOTHERSELF", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a normal Pet miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "MISSEDOTHEROTHER", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a Pet dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSDODGEOTHEROTHER", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a Pet parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSPARRYOTHEROTHER", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a Pet block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSBLOCKOTHEROTHER", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an Pet absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSABSORBOTHEROTHER", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an Pet immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSIMMUNEOTHEROTHER", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for an incoming spell hit/miss message. +-- ********************************************************************************** +function MikCEH.ParseForIncomingSpellHitsAndMisses(combatMessage) + -- Look for an ability hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGOTHERSELF", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an ability crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITOTHERSELF", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGSCHOOLOTHERSELF", {"%n", "%s", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITSCHOOLOTHERSELF", {"%n", "%s", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLMISSOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLDODGEDOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPARRIEDOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLBLOCKEDOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLIMMUNEOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a reflect. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLREFLECTOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_REFLECT, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an ability hit on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGOTHEROTHER", {"%n", "%s", "%c", "%a"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an ability crit on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITOTHEROTHER", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell hit on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGSCHOOLOTHEROTHER", {"%n", "%s", "%c", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell crit on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITSCHOOLOTHEROTHER", {"%n", "%s", "%c", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a miss on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLMISSOTHEROTHER", {"%n", "%s", "%c"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLDODGEDOTHEROTHER", {"%c", "%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPARRIEDOTHEROTHER", {"%c", "%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLBLOCKEDOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a resist on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTOTHEROTHER", {"%n", "%s", "%c"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBOTHEROTHER", {"%n", "%s", "%c"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLIMMUNEOTHEROTHER", {"%n", "%s", "%c"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for incoming damage shield damage. +-- ********************************************************************************** +function MikCEH.ParseForIncomingDamageShieldDamage(combatMessage) + local capturedData = MikCEH.GetCapturedData(combatMessage, "DAMAGESHIELDOTHERSELF", {"%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLMISSOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLDODGEDOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPARRIEDOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLBLOCKEDOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLIMMUNEOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a reflect. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLREFLECTOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_REFLECT, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for incoming heal info. +-- ********************************************************************************** +function MikCEH.ParseForIncomingSpellHeals(combatMessage) + -- Look for a critical heal from another player / creature. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDCRITOTHERSELF", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.HEALTYPE_CRIT, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + eventData.Name = playerName + MikCEH.PopulateOverhealData(eventData); --athenne add + eventData.Name = capturedData.Name + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a heal from another player / creature. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDOTHERSELF", {"%n", "%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.HEALTYPE_NORMAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + eventData.Name = playerName + MikCEH.PopulateOverhealData(eventData); --athenne add + eventData.Name = capturedData.Name + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a HoT from someone else. + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURAHEALOTHERSELF", {"%a", "%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.HEALTYPE_OVER_TIME, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + eventData.Name = playerName + if GetLocale() ~= "zhCN" then -- edge case for chinese client (unable to show overheal amount for hot casted on self by other) + MikCEH.PopulateOverhealData(eventData); -- athenne add + end + eventData.Name = capturedData.Name + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a HoT from yourself. + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURAHEALSELFSELF", {"%a", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.HEALTYPE_OVER_TIME, capturedData.Amount, capturedData.SpellName, playerName); + + -- Get overheal info. + MikCEH.PopulateOverhealData(eventData); --athenne add + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a critical heal from another player / creature on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDCRITOTHEROTHER", {"%n", "%s", "%c", "%a"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.HEALTYPE_CRIT, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + eventData.Name = UnitName("pet") + MikCEH.PopulateOverhealData(eventData); -- athenne add + eventData.Name = capturedData.Name + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a heal from another player / creature on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDOTHEROTHER", {"%n", "%s", "%c", "%a"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.HEALTYPE_NORMAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + eventData.Name = UnitName("pet") + MikCEH.PopulateOverhealData(eventData); -- athenne add + eventData.Name = capturedData.Name + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a HoT from someone else on your pet. + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURAHEALOTHEROTHER", {"%c", "%a", "%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil and PetNameInMessage(combatMessage)) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PET_INCOMING, MikCEH.HEALTYPE_OVER_TIME, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + eventData.Name = UnitName("pet") + if GetLocale() ~= "zhCN" then -- edge case for chinese client (unable to show overheal amount for hot casted on your pet by other) + MikCEH.PopulateOverhealData(eventData); -- athenne add + end + eventData.Name = capturedData.Name + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for incoming debuff info. +-- ********************************************************************************** +function MikCEH.ParseForIncomingDebuffs(combatMessage) + -- Look for a debuff. + local capturedData = MikCEH.GetCapturedData(combatMessage, "AURAADDEDSELFHARMFUL", {"%b"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_DEBUFF, capturedData.Amount, capturedData.BuffName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + capturedData = nil + -- Look for damage from a debuff. + if (GetLocale() == "frFR") then + capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURADAMAGEOTHERSELF", {"%t", "%a", "%s", "%n"}); + else + capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURADAMAGEOTHERSELF", {"%a", "%t", "%n", "%s"}); +end + + -- If a match was found. + if (capturedData ~= nil) then + + local eventData + if (GetLocale() == "frFR") then + eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_OVER_TIME, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + else + eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_OVER_TIME, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + end + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + + -- Look for damage from a self debuff. + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURADAMAGESELFSELF", {"%a", "%t", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_OVER_TIME, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for absorbed damage from a self debuff. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBSELFSELF", {"%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for absorbed damage from a debuff. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBOTHERSELF", {"%n", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses for power gains. +-- ********************************************************************************** +function MikCEH.ParseForPowerGains(combatMessage) + local capturedData = nil + + -- Look for power gains from others. + local capturedData = MikCEH.GetCapturedData(combatMessage, "POWERGAINOTHERSELF", {"%p", "%a", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + -- Make sure it's mana, rage, or energy. + if (capturedData.PowerType == MANA or capturedData.PowerType == RAGE or capturedData.PowerType == ENERGY) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_GAIN, capturedData.Amount, capturedData.PowerType, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + end + + -- Look for self power gains. +if (GetLocale() == "frFR") then + capturedData = MikCEH.GetCapturedData(combatMessage, "POWERGAINSELFSELF", {"%p", "%s", "%a"}); +else + capturedData = MikCEH.GetCapturedData(combatMessage, "POWERGAINSELFSELF", {"%a", "%p", "%s"}); +end + + -- If a match was found. + if (capturedData ~= nil) then + -- Make sure it's mana, rage, or energy. + if (capturedData.PowerType == MANA or capturedData.PowerType == RAGE or capturedData.PowerType == ENERGY) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_GAIN, capturedData.Amount, capturedData.PowerType, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + end + + -- Look for power gains from draining others. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPOWERLEECHSELFOTHER", {"%s", "%a", "%p", "%n", "", "", ""}); + + -- If a match was found. + if (capturedData ~= nil) then + -- Make sure it's mana, rage, or energy. + if (capturedData.PowerType == MANA or capturedData.PowerType == RAGE or capturedData.PowerType == ENERGY) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_GAIN, capturedData.Amount, capturedData.PowerType, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses for power losses. +-- ********************************************************************************** +function MikCEH.ParseForPowerLosses(combatMessage) + -- Look for a power leech. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPOWERLEECHOTHERSELF", {"%n", "%s", "%a", "%p", "", "", ""}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_LOSS, capturedData.Amount, capturedData.PowerType, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a power drain. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPOWERDRAINOTHERSELF", {"%n", "%s", "%a", "%p"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_LOSS, capturedData.Amount, capturedData.PowerType, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for incoming buff and power gain info. +-- ********************************************************************************** +function MikCEH.ParseForIncomingBuffs(combatMessage) + -- Parse for power gains. + if (MikCEH.ParseForPowerGains(combatMessage)) then + -- Return the parse was successful. + return true; + end + + -- Look for a buff. + local capturedData = MikCEH.GetCapturedData(combatMessage, "AURAADDEDSELFHELPFUL", {"%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_BUFF, nil, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a self mana drain. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPOWERDRAINSELFSELF", {"%s", "%a", "%p"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_LOSS, capturedData.Amount, capturedData.PowerType, capturedData.SpellName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for environmental damage. +-- ********************************************************************************** +function MikCEH.ParseForEnvironmentalDamage(combatMessage) + -- Look for drowning damage. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSENVIRONMENTALDAMAGE_DROWNING_SELF", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_DROWNING, nil, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for falling damage. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSENVIRONMENTALDAMAGE_FALLING_SELF", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_FALLING, nil, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for fatigue damage. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSENVIRONMENTALDAMAGE_FATIGUE_SELF", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_FATIGUE, nil, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for fire damage. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSENVIRONMENTALDAMAGE_FIRE_SELF", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_FIRE, nil, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for lava damage. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSENVIRONMENTALDAMAGE_LAVA_SELF", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_LAVA, nil, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for slime damage. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSENVIRONMENTALDAMAGE_SLIME_SELF", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_SLIME, nil, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for outgoing hits. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingHits(combatMessage) + -- Look for a normal hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITSELFOTHER", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITSELFOTHER", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for outgoing hits. +-- ********************************************************************************** +function MikCEL_ParseForOutgoingHits(combatMessage) + -- Look for a normal hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITSELFOTHER", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITSELFOTHER", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for an outgoing miss message. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingMisses(combatMessage) + -- Look for a normal miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "MISSEDSELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSDODGESELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSPARRYSELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSBLOCKSELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSABSORBSELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSIMMUNESELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an evade. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSEVADESELFOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_EVADE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for outgoing spell/ability hits and misses. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingSpellHitsAndMisses(combatMessage) + -- Look for an ability crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITSELFOTHER", {"%s", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an ability hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGSELFOTHER", {"%s", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell crit to yourself. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITSCHOOLSELFSELF", {"%s", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, playerName); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a spell hit to yourself. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGSCHOOLSELFSELF", {"%s", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, playerName); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for spell crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITSCHOOLSELFOTHER", {"%s", "%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGSCHOOLSELFOTHER", {"%s", "%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLMISSSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLDODGEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPARRIEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLBLOCKEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLIMMUNESELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a reflect. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLREFLECTSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_REFLECT, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an evade + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLEVADEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_EVADE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for outgoing damage shield damage. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingDamageShieldDamage(combatMessage) + local capturedData = MikCEH.GetCapturedData(combatMessage, "DAMAGESHIELDSELFOTHER", {"%a", "%t", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData + if capturedData.DamageType == 2 and capturedData.Amount == "20" then + eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, "Aura de vindicte", capturedData.Name); + else + eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, nil, capturedData.Name); + end + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLIMMUNESELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for an evade + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLEVADEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_EVADE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLMISSSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLDODGEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPARRIEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLBLOCKEDSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for outgoing heal info. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingSpellHeals(combatMessage) + -- Look for a critical heal to yourself. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDCRITSELFSELF", {"%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.HEALTYPE_CRIT, capturedData.Amount, capturedData.SpellName, playerName); + + -- Get overheal info. + MikCEH.PopulateOverhealData(eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a heal to yourself. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDSELFSELF", {"%s", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_INCOMING, MikCEH.HEALTYPE_NORMAL, capturedData.Amount, capturedData.SpellName, playerName); + + -- Get overheal info. + MikCEH.PopulateOverhealData(eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a critical heal to someone else. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDCRITSELFOTHER", {"%s", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.HEALTYPE_CRIT, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + MikCEH.PopulateOverhealData(eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a heal to someone else. + local capturedData = MikCEH.GetCapturedData(combatMessage, "HEALEDSELFOTHER", {"%s", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.HEALTYPE_NORMAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + MikCEH.PopulateOverhealData(eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for outgoing dispel/purge resists. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingDispelResists(combatMessage) + -- Look for a resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for outgoing Heals over time. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingHoTs(combatMessage) + -- Look for a HoT to someone else. + + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURAHEALSELFOTHER", {"%n", "%a", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetHealEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.HEALTYPE_OVER_TIME, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Get overheal info. + MikCEH.PopulateOverhealData(eventData); --athenne add + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for outgoing DoTs. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingDoTs(combatMessage) + -- Look for damage from a DoT. + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURADAMAGESELFOTHER", {"%n", "%a", "%t", "%s"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_OVER_TIME, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for absorbed damage from a DoT. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBSELFOTHER", {"%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for outgoing pet hits. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingPetHits(combatMessage) + -- Look for a normal pet hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITOTHEROTHER", {"%c", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a pet crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITOTHEROTHER", {"%c", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a pet elemental hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITSCHOOLOTHEROTHER", {"%c", "%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a pet elemental crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATHITCRITSCHOOLOTHEROTHER", {"%c", "%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, nil, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for outgoing pet misses. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingPetMisses(combatMessage) + -- Look for a normal miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "MISSEDOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSDODGEOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSPARRYOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSBLOCKOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSABSORBOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSIMMUNEOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an evade. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VSEVADEOTHEROTHER", {"%c", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_EVADE, nil, nil, nil, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for outgoing pet spell hits and misses. +-- ********************************************************************************** +function MikCEH.ParseForOutgoingPetSpellHitsAndMisses(combatMessage) + -- Look for an ability hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGOTHEROTHER", {"%c", "%s", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an ability crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITOTHEROTHER", {"%c", "%s", "%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, MikCEH.DAMAGETYPE_PHYSICAL, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a spell hit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGSCHOOLOTHEROTHER", {"%c", "%s", "%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for spell crit. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGCRITSCHOOLOTHEROTHER", {"%c", "%s", "%n", "%a", "%t"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_CRIT, capturedData.DamageType, capturedData.Amount, capturedData.SpellName, capturedData.Name); + + -- Look for any partial actions and populate them into the event data. + MikCEH.ParseForPartialActions(combatMessage, eventData); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a miss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLMISSOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_MISS, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a dodge. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLDODGEDOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_DODGE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a parry. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLPARRIEDOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_PARRY, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLBLOCKEDOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_BLOCK, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for a resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLRESISTOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_RESIST, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLLOGABSORBOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_ABSORB, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an immune. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLIMMUNEOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_IMMUNE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an evade + local capturedData = MikCEH.GetCapturedData(combatMessage, "SPELLEVADEDOTHEROTHER", {"%c", "%s", "%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PET_OUTGOING, MikCEH.ACTIONTYPE_EVADE, nil, nil, nil, capturedData.SpellName, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for incoming item buff info. +-- ********************************************************************************** +function MikCEH.ParseForIncomingItemBuffs(combatMessage) + -- Look for an item buff from yourself. + local capturedData = MikCEH.GetCapturedData(combatMessage, "ITEMENCHANTMENTADDSELFSELF", {"%b"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_ITEM_BUFF, nil, capturedData.BuffName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Look for an item buff from someone else. + local capturedData = MikCEH.GetCapturedData(combatMessage, "ITEMENCHANTMENTADDOTHERSELF", {"%n", "%b"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_ITEM_BUFF, nil, capturedData.BuffName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for buff fades. +-- ********************************************************************************** +function MikCEH.ParseForBuffFades(combatMessage) + -- Look for a buff fade due to wearing off. + local capturedData = MikCEH.GetCapturedData(combatMessage, "AURAREMOVEDSELF", {"%b"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_BUFF_FADE, nil, capturedData.BuffName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for honor gains. +-- ********************************************************************************** +function MikCEH.ParseForHonorGains(combatMessage) + -- Look for an estimated honor gain from a kill. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATLOG_HONORGAIN", {"%n", "", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_HONOR_GAIN, capturedData.Amount, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for awarded honor. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATLOG_HONORAWARD", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_HONOR_GAIN, capturedData.Amount, nil); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for reputation gains/losses. +-- ********************************************************************************** +function MikCEH.ParseForReputationGainsAndLosses(combatMessage) + -- Look for a rep increase. + local capturedData = MikCEH.GetCapturedData(combatMessage, "FACTION_STANDING_INCREASED", {"%f", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_REP_GAIN, capturedData.Amount, capturedData.FactionName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + -- Look for a rep loss. + local capturedData = MikCEH.GetCapturedData(combatMessage, "FACTION_STANDING_DECREASED", {"%f", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_REP_LOSS, capturedData.Amount, capturedData.FactionName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for skill gains. +-- ********************************************************************************** +function MikCEH.ParseForSkillGains(combatMessage) + -- Look for a skill gain. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SKILL_RANK_UP", {"%k", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_SKILL_GAIN, capturedData.Amount, capturedData.SkillName); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for experience gains. +-- ********************************************************************************** +function MikCEH.ParseForExperienceGains(combatMessage) + -- Look for an experience gain. + local capturedData = MikCEH.GetCapturedData(combatMessage, "COMBATLOG_XPGAIN_FIRSTPERSON", {"%n", "%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_EXPERIENCE_GAIN, capturedData.Amount, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parses the passed combat message for killing blows. +-- ********************************************************************************** +function MikCEH.ParseForKillingBlows(combatMessage) + -- Look for a killing blow. + local capturedData = MikCEH.GetCapturedData(combatMessage, "SELFKILLOTHER", {"%n"}); + + -- If a match was found. + if (capturedData ~= nil) then + -- Hold the notification type. + local notificationType = MikCEH.NOTIFICATIONTYPE_NPC_KILLING_BLOW; + + -- Check if the current target is the slain enemy and is a player, or the slain target is on the recently + -- selected players list. + if ((UnitExists("target") and (UnitName("target") == capturedData.Name) and UnitIsPlayer("target")) or + (recentlySelectedPlayers[capturedData.Name] ~= nil)) then + notificationType = MikCEH.NOTIFICATIONTYPE_PC_KILLING_BLOW; + end + + -- Create the event. + local eventData = MikCEH.GetNotificationEventData(notificationType, nil, capturedData.Name); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + + + -- Return the parse was NOT successful. + return false; +end + + +-- ********************************************************************************** +-- Parse the passed combat message for partial actions and populate the info +-- from any actions found into the passed event data. +-- ********************************************************************************** +function MikCEH.ParseForPartialActions(combatMessage, eventData) + -- Look for a partial absorb. + local capturedData = MikCEH.GetCapturedData(combatMessage, "ABSORB_TRAILER", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_ABSORB; + eventData.PartialAmount = capturedData.Amount; + + -- Return that partial action information was found. + return true; + end + + + -- Look for a partial block. + local capturedData = MikCEH.GetCapturedData(combatMessage, "BLOCK_TRAILER", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_BLOCK; + eventData.PartialAmount = capturedData.Amount; + + -- Return that partial effect information was found. + return true; + end + + + -- Look for a partial resist. + local capturedData = MikCEH.GetCapturedData(combatMessage, "RESIST_TRAILER", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_RESIST; + eventData.PartialAmount = capturedData.Amount; + + -- Return that partial action information was found. + return true; + end + + + -- Look for a vulnerability bonus. + local capturedData = MikCEH.GetCapturedData(combatMessage, "VULNERABLE_TRAILER", {"%a"}); + + -- If a match was found. + if (capturedData ~= nil) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_VULNERABLE; + eventData.PartialAmount = capturedData.Amount; + + -- Return that partial action information was found. + return true; + end + + + -- Look for a crushing blow. + local capturedData = MikCEH.GetCapturedData(combatMessage, "CRUSHING_TRAILER", {}); + -- If a match was found. + if (capturedData ~= nil) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_CRUSHING; + + -- Return that partial action information was found. + return true; + end + + + -- Look for a glancing blow. + local capturedData = MikCEH.GetCapturedData(combatMessage, "GLANCING_TRAILER", {}); + -- If a match was found. + if (capturedData ~= nil) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_GLANCING; + + -- Return that partial action information was found. + return true; + end + + + -- Return that no partial action information was found. + return false; +end + + +------------------------------------------------------------------------------------- +-- Utility functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Get a lua compatible search string and the argument order from a global string +-- provided by blizzard. +-- ********************************************************************************** +function MikCEH.GetGlobalStringInfo(globalStringName) + + -- Check if the passed global string does not exist. + local globalString = getglobal(globalStringName); + if (globalString == nil) then + return; + end + + -- Check if the global string info doesn't already exist for the passed global string name. + if (globalStringInfoArray[globalStringName] == nil) then + local searchString = ""; + local currentChar; + local formatCode; + local argumentNumber = 0; + local argumentOrder = {}; + + -- Loop through all of the characters in the passed string. + local stringLength = string.len(globalString); + for index = 0, stringLength do + -- Get the current character. + currentChar = string.sub(globalString, index, index); + + -- Check if we aren't in a formatting code. + if (formatCode == nil) then + -- Check if the current character is the start of a formatting code. + if (currentChar == "%") then + formatCode = currentChar; + else + -- Check if the character is one of the magic characters and escape it. + if (strfind(currentChar, "[%^%$%(%)%.%[%]%*%-%+%?]")) then + searchString = searchString .. "%" .. currentChar; + -- Normal character so just add it to the formatted string. + else + searchString = searchString .. currentChar; + end + end + + -- We are in a formatting code. + else + -- Add the current character to the format code. + formatCode = formatCode .. currentChar; + + -- Check if the % character is being escaped. + if (formatCode == "%%") then + -- Add the % to the search string. + searchString = searchString .. "%%"; + + -- Clear the format code. + formatCode = nil; + + -- Check if it's a digit, a period, or a $ and do nothing so we loop to the next character in the format code. + elseif (strfind(currentChar, "[%$%.%d]")) then + -- Do nothing. + + -- Check for one of the types that need a string. + elseif (strfind(currentChar, "[cEefgGiouXxqs]")) then + -- Replace the format code with lua capture string syntax. + if GetLocale() == "zhCN" and globalStringName == "HEALEDSELFOTHER" then -- edge for outgoing heals on others case for zhCN client + searchString = searchString .. "([^0-9.]+)" + else + searchString = searchString .. "(.+)"; + end + + -- Increment the argument number. + argumentNumber = argumentNumber + 1; + + -- Check if there is an argument position specified. + local _, _, argumentPosition = strfind(formatCode, "(%d+)%$"); + if (argumentPosition) then + argumentOrder[argumentNumber] = tonumber(argumentPosition); + else + argumentOrder[argumentNumber] = argumentNumber; + end + + -- Clear the format code. + formatCode = nil; + + -- Check if it's the type that needs a number. + elseif (currentChar == "d") then + -- Replace the format code with lua capture digits syntax. + searchString = searchString .. "(%d+)"; + + -- Increment the argument number. + argumentNumber = argumentNumber + 1; + + -- Check if there is an argument position specified. + local _, _, argumentPosition = strfind(formatCode, "(%d+)%$"); + if (argumentPosition) then + argumentOrder[argumentNumber] = tonumber(argumentPosition); + else + argumentOrder[argumentNumber] = argumentNumber; + end + + -- Clear the format code. + formatCode = nil; + + else + -- Clear the format code. + formatCode = nil; + end + end + end + + -- Cache the global string info for later retrieval. + globalStringInfoArray[globalStringName] = {Search=searchString, ArgumentOrder=argumentOrder}; + end + + -- Return the format info for the global string. + return globalStringInfoArray[globalStringName]; +end + + +-- ********************************************************************************** +-- This function returns the captured data table with all the captured data in fields. +-- If the pattern wasn't found then nil is returned. +-- Capture order keys. +-- %a = amount +-- %b = name of a buff/debuff +-- %c = name of creature (pet) +-- %f = name of the faction +-- %k = name of the skill +-- %n = name of enemy/player +-- %p = power type (mana, rage, energy) +-- %s = name of the ability/spell +-- %t = damage type +-- ********************************************************************************** +function MikCEH.GetCapturedData(combatMessage, globalStringName, captureOrder) + -- Check if the passed global string does not exist. + if (getglobal(globalStringName) == nil) then + -- Print out a debug message. + MikSBT.PrintDebug("Unable to find global string: " .. globalStringName, 1, 0, 0); + return; + end + + -- Format the global string into a lua compatible search string. + local globalStringInfo = MikCEH.GetGlobalStringInfo(globalStringName); + + -- Whether or not the search pattern was found. + local stringFound = false; + + -- Erase old captured data. + MikCEH.EraseTable(orderedCaptureData); + + -- Get the unordered capture data. + local tempCapturedData = MikCEH.GetUnorderedCaptureDataTable(strgfind(combatMessage, globalStringInfo.Search)()); + + -- If a match was found. + if (tgetn(tempCapturedData) ~= 0) then + -- Loop through all of the values in the passed capture order table. + for argNum, substituteValue in captureOrder do + local captureString = tempCapturedData[globalStringInfo.ArgumentOrder[argNum]]; + if (substituteValue == "%a") then + orderedCaptureData.Amount = captureString; + elseif (substituteValue == "%b") then + orderedCaptureData.BuffName = captureString; + elseif (substituteValue == "%c") then + orderedCaptureData.PetName = captureString; + elseif (substituteValue == "%f") then + orderedCaptureData.FactionName = captureString; + elseif (substituteValue == "%k") then + orderedCaptureData.SkillName = captureString; + elseif (substituteValue == "%n") then + orderedCaptureData.Name = captureString; + elseif (substituteValue == "%p") then + orderedCaptureData.PowerType = captureString; + elseif (substituteValue == "%s") then + orderedCaptureData.SpellName = captureString; + elseif (substituteValue == "%t") then + orderedCaptureData.DamageType = MikCEH.GetDamageTypeNumber(captureString); + end + end + + -- Return the captured data. + return orderedCaptureData; + end + + -- Return nil value. + return nil; +end + + +-- ********************************************************************************** +-- This function populates the unordered capture data table with all of the +-- parameters passed. +-- ********************************************************************************** +function MikCEH.GetUnorderedCaptureDataTable(c1, c2, c3, c4, c5, c6, c7, c8, c9) + -- Populate the capture array without wiping it each call. + local idx = 1 + if (c1 ~= nil) then unorderedCaptureData[idx] = c1; idx = idx + 1 end + if (c2 ~= nil) then unorderedCaptureData[idx] = c2; idx = idx + 1 end + if (c3 ~= nil) then unorderedCaptureData[idx] = c3; idx = idx + 1 end + if (c4 ~= nil) then unorderedCaptureData[idx] = c4; idx = idx + 1 end + if (c5 ~= nil) then unorderedCaptureData[idx] = c5; idx = idx + 1 end + if (c6 ~= nil) then unorderedCaptureData[idx] = c6; idx = idx + 1 end + if (c7 ~= nil) then unorderedCaptureData[idx] = c7; idx = idx + 1 end + if (c8 ~= nil) then unorderedCaptureData[idx] = c8; idx = idx + 1 end + if (c9 ~= nil) then unorderedCaptureData[idx] = c9; idx = idx + 1 end + + -- Clear any leftover values from a previous call. + local count = tgetn(unorderedCaptureData) + for i = idx, count do + unorderedCaptureData[i] = nil + end + tsetn(unorderedCaptureData, idx - 1) + + -- Return the populated unordered capture data table. + return unorderedCaptureData; +end + + +-- ********************************************************************************** +-- Gets the damage type number for the given string. +-- ********************************************************************************** +function MikCEH.GetDamageTypeNumber(damageTypeString) + -- Return the correct damage type number for the passed string. + if (damageTypeString == SPELL_SCHOOL0_CAP) then + return MikCEH.DAMAGETYPE_PHYSICAL; + elseif (damageTypeString == SPELL_SCHOOL1_CAP) then + return MikCEH.DAMAGETYPE_HOLY; + elseif (damageTypeString == SPELL_SCHOOL2_CAP) then + return MikCEH.DAMAGETYPE_FIRE; + elseif (damageTypeString == SPELL_SCHOOL3_CAP) then + return MikCEH.DAMAGETYPE_NATURE; + elseif (damageTypeString == SPELL_SCHOOL4_CAP) then + return MikCEH.DAMAGETYPE_FROST; + elseif (damageTypeString == SPELL_SCHOOL5_CAP) then + return MikCEH.DAMAGETYPE_SHADOW; + elseif (damageTypeString == SPELL_SCHOOL6_CAP) then + return MikCEH.DAMAGETYPE_ARCANE; + elseif (damageTypeString == "Arcane") then + return MikCEH.DAMAGETYPE_ARCANE; + end + + -- Return the unknown damage type. + return MikCEH.DAMAGETYPE_UNKNOWN; +end + + +-- ********************************************************************************** +-- Get a string for the passed damage type. +-- ********************************************************************************** +function MikCEH.GetDamageTypeString(damageType) + -- Return the correct damage type string for the passed number. + if (damageType == MikCEH.DAMAGETYPE_PHYSICAL) then + return SPELL_SCHOOL0_CAP; + elseif (damageType == MikCEH.DAMAGETYPE_HOLY) then + return SPELL_SCHOOL1_CAP; + elseif (damageType == MikCEH.DAMAGETYPE_FIRE) then + return SPELL_SCHOOL2_CAP; + elseif (damageType == MikCEH.DAMAGETYPE_NATURE) then + return SPELL_SCHOOL3_CAP; + elseif (damageType == MikCEH.DAMAGETYPE_FROST) then + return SPELL_SCHOOL4_CAP; + elseif (damageType == MikCEH.DAMAGETYPE_SHADOW) then + return SPELL_SCHOOL5_CAP; + elseif (damageType == MikCEH.DAMAGETYPE_ARCANE) then + return SPELL_SCHOOL6_CAP; + end + + -- Return the unknown damage type string. + + return UNKNOWN; +end + +-- Utility to quickly reset the shared combat event data table without iteration. +local function ClearCombatEventData() + local data = MikCEH.CombatEventData + data.ActionType = nil + data.Amount = nil + data.CapturedData = nil + data.DamageType = nil + data.DirectionType = nil + data.EffectName = nil + data.EventType = nil + data.HealType = nil + data.HitType = nil + data.Name = nil + data.NotificationType = nil + data.NumCaptures = nil + data.PartialActionType = nil + data.PartialAmount = nil + data.TriggerKey = nil +end + + +-- ********************************************************************************** +-- Populates the combat event data table for a damage event with the passed info. +-- ********************************************************************************** +function MikCEH.GetDamageEventData(directionType, actionType, hitType, damageType, amount, effectName, name) + -- Get the global combat event data table. + local eventData = MikCEH.CombatEventData; + + -- Reset the combat event data table. + ClearCombatEventData(); + + + -- Populate the event data fields. + eventData.EventType = MikCEH.EVENTTYPE_DAMAGE; + eventData.DirectionType = directionType; + eventData.ActionType = actionType; + eventData.HitType = hitType; + eventData.DamageType = damageType; + eventData.Amount = amount; + eventData.EffectName = effectName; + eventData.Name = name; + + -- Return the event data. + return eventData; +end + + +-- ********************************************************************************** +-- Populates the combat event data table for a heal event with the passed info. +-- ********************************************************************************** +function MikCEH.GetHealEventData(directionType, healType, amount, effectName, name) + -- Get the global combat event data table. + local eventData = MikCEH.CombatEventData; + + -- Reset the combat event data table. + ClearCombatEventData(); + + -- Populate the event data fields. + eventData.EventType = MikCEH.EVENTTYPE_HEAL; + eventData.DirectionType = directionType; + eventData.HealType = healType; + eventData.Amount = amount; + eventData.EffectName = effectName; + eventData.Name = name; + + -- Return the event data. + return eventData; +end + + +-- ********************************************************************************** +-- Populates the combat event data table for a notification event with the passed +-- info. +-- ********************************************************************************** +function MikCEH.GetNotificationEventData(notificationType, amount, effectName, SpellName) + -- Get the global combat event data table. + local eventData = MikCEH.CombatEventData; + + -- Reset the combat event data table. + ClearCombatEventData(); + + -- Populate the event data fields. + eventData.EventType = MikCEH.EVENTTYPE_NOTIFICATION; + eventData.NotificationType = notificationType; + eventData.Amount = amount; + if effectName == MANA or effectName == RAGE or effectName == ENERGY then + eventData.Amount = amount.." "..effectName; + eventData.EffectName = SpellName; + else + eventData.EffectName = effectName; + end + + -- Return the event data. + return eventData; +end + + +-- ********************************************************************************** +-- Gets a unit id for the name. +-- ********************************************************************************** +function MikCEH.GetUnitIDFromName(uName) + local unitID; + + -- Check if the name is the player. + if (uName == playerName) then + unitID = "player"; + + -- Check if the name is the pet. + elseif (uName == UnitName("pet")) then + unitID = "pet"; + + -- Check if the name is one of the player's raid or party members. + else + -- Loop through all of the raid members. + local numRaidMembers = GetNumRaidMembers(); + for i = 1, numRaidMembers do + if (uName == UnitName("raid" .. i)) then + unitID = "raid" .. i; + end + end + + for i = 1, numRaidMembers do + if (uName == UnitName("raidpet" .. i)) then + unitID = "raidpet" .. i; + end + end + + -- Check if the unit ID was not already found. + if (not unitID) then + -- Loop through all of the party members. + local numPartyMembers = GetNumPartyMembers(); + for i = 1, numPartyMembers do + if (uName == UnitName("party" .. i)) then + unitID = "party" .. i; + end + end + end + + if (not unitID) then + -- Loop through all of the party members. + local numPartyMembers = GetNumPartyMembers(); + for i = 1, numPartyMembers do + if (uName == UnitName("partypet" .. i)) then + unitID = "partypet" .. i; + end + end + end + + end + + -- Return the unit id. + return unitID; +end + + +-- ********************************************************************************** +-- Populates the passed event data with overheal info. +-- ********************************************************************************** +function MikCEH.PopulateOverhealData(eventData) + -- Get the appropriate unit id for the unit being checked for overheals. + + local unitID = MikCEH.GetUnitIDFromName(eventData.Name); + + if not unitID then + if UnitName("target") == eventData.Name then + unitID = "target"; + end + end + + -- Make sure it's a valid unit id. + if (unitID) then + local healthMissing = UnitHealthMax(unitID) - UnitHealth(unitID); + local overhealAmount = eventData.Amount - healthMissing; + + -- Check if any overhealing occured (note threshold is 100 because heals on non-group members + -- will show show max health 100 instead of the proper maximum health of the target) + if (overhealAmount > 0 and UnitHealthMax(unitID) ~= 100) then + eventData.PartialActionType = MikCEH.PARTIALACTIONTYPE_OVERHEAL; + eventData.PartialAmount = overhealAmount; + end + end +end + + +-- ********************************************************************************** +-- Sends the event to MSBT's combat events handler function. +-- ********************************************************************************** +function MikCEH.SendEvent(eventData) + -- Make sure MSBT's combat events handler function is defined. + if (MikSBT.CombatEventsHandler ~= nil) then + MikSBT.CombatEventsHandler(eventData); + end +end + + +-- ********************************************************************************** +-- Erases the passed table without losing the reference to the original memory. +-- This helps prevent GC churn. +-- ********************************************************************************** +function MikCEH.EraseTable(t) + -- Loop through all the keys in the table and clear it. + for key in pairs(t) do + t[key] = nil; + end + + -- Set the length of the table to 0. + tsetn(t, 0); +end + + +------------------------------------------------------------------------------------- +-- Trigger utility functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Enables event searching mode. The passed pattern will be used to only show event +-- types where the combat message contains the pattern. +-- ********************************************************************************** +function MikCEH.EnableEventSearching(pattern) + -- Enable the event searching mode flag and set the search pattern + -- to the passed pattern. + searchMode = true; + searchModePattern = pattern; +end + + +-- ********************************************************************************** +-- Disables event searching mode. +-- ********************************************************************************** +function MikCEH.DisableEventSearching() + -- Clear the event searching mode flag and search pattern. + searchMode = false; + searchModePattern = nil; +end + + +-- ********************************************************************************** +-- Sends the trigger to MSBT's trigger events handler function. +-- ********************************************************************************** +function MikCEH.SendTriggerEvent(eventData) + -- Make sure MSBT's trigger handler function is defined. + if (MikSBT.TriggerHandler ~= nil) then + MikSBT.TriggerHandler(eventData); + end +end + + +-- ********************************************************************************** +-- Populates the trigger event data table using the passed info +-- ********************************************************************************** +function MikCEH.GetSearchTriggerEventData(triggerKey, capturedData) + -- Get the common trigger event data table. + local eventData = MikCEH.TriggerEventData; + + -- Erase the trigger event data table. + MikCEH.EraseTable(eventData); + + -- Populate the trigger event data fields. + eventData.TriggerKey = triggerKey; + eventData.NumCaptures = tgetn(capturedData); + + -- Loop through each captured data entry and set a corresponding field in + -- the trigger event. + for i = 1, eventData.NumCaptures do + eventData["CapturedData" .. i] = capturedData[i]; + end + + -- Return the event data. + return eventData; +end + + +-- ********************************************************************************** +-- Populates the trigger event data table using the passed info +-- ********************************************************************************** +function MikCEH.GetThresholdTriggerEventData(triggerKey, triggerAmount) + -- Get the common trigger event data table. + local eventData = MikCEH.TriggerEventData; + + -- Erase the trigger event data table. + MikCEH.EraseTable(eventData); + + -- Populate the trigger event data fields. + eventData.TriggerKey = triggerKey; + eventData.NumCaptures = 1; + eventData.CapturedData1 = triggerAmount; + + -- Return the event data. + return eventData; +end + + +------------------------------------------------------------------------------------- +-- Trigger functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Registers the passed trigger key with the passed trigger settings. +-- ********************************************************************************** +function MikCEH.RegisterTrigger(triggerKey, triggerSettings) + -- Check if the trigger is for the player's class. + if (not triggerSettings.Classes or triggerSettings.Classes[playerClass]) then + -- Add to various arrays of triggers to check. This is done so time is not wasted on + -- checking for triggers that don't apply. + + -- Self Health. + if (triggerSettings.TriggerType == MikCEH.TRIGGERTYPE_SELF_HEALTH) then + selfHealthTriggers[triggerKey] = triggerSettings; + + -- Self Mana. + elseif (triggerSettings.TriggerType == MikCEH.TRIGGERTYPE_SELF_MANA) then + selfManaTriggers[triggerKey] = triggerSettings; + + -- Pet Health. + elseif (triggerSettings.TriggerType == MikCEH.TRIGGERTYPE_PET_HEALTH) then + petHealthTriggers[triggerKey] = triggerSettings; + + -- Enemy Health. + elseif (triggerSettings.TriggerType == MikCEH.TRIGGERTYPE_ENEMY_HEALTH) then + enemyHealthTriggers[triggerKey] = triggerSettings; + + -- Friendly Health. + elseif (triggerSettings.TriggerType == MikCEH.TRIGGERTYPE_FRIENDLY_HEALTH) then + friendlyHealthTriggers[triggerKey] = triggerSettings; + + -- Search Pattern. + elseif (triggerSettings.TriggerType == MikCEH.TRIGGERTYPE_SEARCH_PATTERN) then + -- Loop through all of the trigger events. + for _, triggerEvent in triggerSettings.TriggerEvents do + -- Check if there is not already a table for the trigger event and create one. + if (not searchPatternTriggers[triggerEvent]) then + searchPatternTriggers[triggerEvent] = {}; + end + + -- Add the trigger to the search pattern array for the event. + searchPatternTriggers[triggerEvent][triggerKey] = triggerSettings; + end + end + + end -- Is trigger for class? +end + + +-- ********************************************************************************** +-- Unregisters all of the triggers. +-- ********************************************************************************** +function MikCEH.UnregisterAllTriggers() + -- Erase the trigger arrays. + MikCEH.EraseTable(selfHealthTriggers); + MikCEH.EraseTable(selfManaTriggers); + MikCEH.EraseTable(petHealthTriggers); + MikCEH.EraseTable(enemyHealthTriggers); + MikCEH.EraseTable(friendlyHealthTriggers); + MikCEH.EraseTable(searchPatternTriggers); +end + + +-- ********************************************************************************** +-- Parses the self health triggers. +-- ********************************************************************************** +function MikCEH.ParseSelfHealthTriggers() + local healthAmount = UnitHealth("player"); + local healthPercentage = healthAmount / UnitHealthMax("player"); + + -- Loop through self health triggers. + for triggerKey, triggerSettings in selfHealthTriggers do + -- Check if we just crossed the trigger's threshold. + if (healthPercentage < triggerSettings.Threshold/100 and lastSelfHealthPercentage >= triggerSettings.Threshold/100) then + -- Get trigger event data and call the trigger handler. + local eventData = MikCEH.GetThresholdTriggerEventData(triggerKey, healthAmount); + MikCEH.SendTriggerEvent(eventData); + if MikSBT.CurrentProfile.LowHealthSound then + PlaySoundFile("Interface\\AddOns\\MikScrollingBattleText\\sounds\\LowHealth.mp3"); + end + end + end + + -- Update the last health percentage. + lastSelfHealthPercentage = healthPercentage; +end + + +-- ********************************************************************************** +-- Parses the self mana triggers. +-- ********************************************************************************** +function MikCEH.ParseSelfManaTriggers() + -- Make sure we're dealing with mana. + if (UnitPowerType("player") == 0) then + local manaAmount = UnitMana("player"); + local manaPercentage = manaAmount / UnitManaMax("player"); + + -- Mana per Five Ticks + local manaDiff = manaAmount - lastSelfManaAmount; + if ( manaDiff > 0 and MikSBT.CurrentProfile.ShowAllManaGains) then + local eventData = MikCEH.GetNotificationEventData(MikCEH.NOTIFICATIONTYPE_POWER_GAIN, manaDiff.." "..MANA, 0); + MikCEH.SendEvent(eventData); + end + + -- Loop through self mana triggers. + for triggerKey, triggerSettings in selfManaTriggers do + -- Check if we just crossed the trigger's threshold. + if (manaPercentage < triggerSettings.Threshold/100 and lastSelfManaPercentage >= triggerSettings.Threshold/100) then + -- Get trigger event data and call the trigger handler. + local eventData = MikCEH.GetThresholdTriggerEventData(triggerKey, manaAmount); + MikCEH.SendTriggerEvent(eventData); + if MikSBT.CurrentProfile.LowManaSound then + PlaySoundFile("Interface\\AddOns\\MikScrollingBattleText\\sounds\\LowMana.mp3"); + end + end + end + + -- Update the last mana percentage. + lastSelfManaPercentage = manaPercentage; + lastSelfManaAmount = manaAmount; + end +end + + +-- ********************************************************************************** +-- Parses the pet health triggers. +-- ********************************************************************************** +function MikCEH.ParsePetHealthTriggers() + local healthAmount = UnitHealth("pet"); + local healthPercentage = healthAmount / UnitHealthMax("pet"); + + -- Loop through pet health triggers. + for triggerKey, triggerSettings in petHealthTriggers do + -- Check if we just crossed the trigger's threshold. + if (healthPercentage < triggerSettings.Threshold/100 and lastPetHealthPercentage >= triggerSettings.Threshold/100) then + -- Get trigger event data and call the trigger handler. + local eventData = MikCEH.GetThresholdTriggerEventData(triggerKey, healthAmount); + MikCEH.SendTriggerEvent(eventData) + end + end + + -- Update the last health percentage. + lastPetHealthPercentage = healthPercentage; +end + + +-- ********************************************************************************** +-- Parses the enemy health triggers. +-- ********************************************************************************** +function MikCEH.ParseEnemyHealthTriggers() + local healthAmount = UnitHealth("target"); + local healthPercentage = healthAmount / UnitHealthMax("target"); + + -- Loop through self health triggers. + for triggerKey, triggerSettings in enemyHealthTriggers do + -- Check if we just crossed the trigger's threshold. + if (healthPercentage < triggerSettings.Threshold/100 and lastEnemyHealthPercentage >= triggerSettings.Threshold/100) then + -- Get trigger event data and call the trigger handler. + local eventData = MikCEH.GetThresholdTriggerEventData(triggerKey, healthAmount); + MikCEH.SendTriggerEvent(eventData) + end + end + + -- Update the last health percentage. + lastEnemyHealthPercentage = healthPercentage; +end + + +-- ********************************************************************************** +-- Parses the friendly health triggers. +-- ********************************************************************************** +function MikCEH.ParseFriendlyHealthTriggers() + local healthAmount = UnitHealth("target"); + local healthPercentage = healthAmount / UnitHealthMax("target"); + + -- Loop through self health triggers. + for triggerKey, triggerSettings in friendlyHealthTriggers do + -- Check if we just crossed the trigger's threshold. + if (healthPercentage < triggerSettings.Threshold/100 and lastFriendlyHealthPercentage >= triggerSettings.Threshold/100) then + -- Get trigger event data and call the trigger handler. + local eventData = MikCEH.GetThresholdTriggerEventData(triggerKey, healthAmount); + MikCEH.SendTriggerEvent(eventData) + end + end + + -- Update the last health percentage. + lastFriendlyHealthPercentage = healthPercentage; +end + + + +-- ********************************************************************************** +-- Parses the search pattern triggers for a match with the passed combat message +-- and event. +-- ********************************************************************************** +function MikCEH.ParseSearchPatternTriggers(event, combatMessage) + -- Check if event search mode is enabled. + if (searchMode) then + -- Check if the pattern is in the combat message. + if (strfind(combatMessage, searchModePattern)) then + -- Print out the event type and the combat message. + MikSBT.Print(event .. " - " .. combatMessage, 0, 1, 0); + end + end + + -- Check if there are no triggers for the event type and bail. + if (not searchPatternTriggers[event]) then + return; + end + + -- Loop through all triggers for the event. + for triggerKey, triggerSettings in searchPatternTriggers[event] do + -- Loop through all of the search patterns for the trigger. + for _, searchPattern in triggerSettings.SearchPatterns do + + -- Check if the search pattern is a global string. + if (getglobal(searchPattern) ~= nil) then + -- Format the global string into a lua compatible search string. + local globalStringInfo = MikCEH.GetGlobalStringInfo(searchPattern); + + -- Replace the search pattern with the lua compatible search string. + searchPattern = globalStringInfo.Search; + end + + -- Get capture data. + local capturedData = MikCEH.GetUnorderedCaptureDataTable(strgfind(combatMessage, searchPattern)()); + + -- Check if a match was found. + if (tgetn(capturedData) ~= 0) then + -- Get trigger event data and call the trigger handler. + local eventData = MikCEH.GetSearchTriggerEventData(triggerKey, capturedData); + MikCEH.SendTriggerEvent(eventData); + break; + end + end -- Loop through search patterns. + end -- Loop through triggers. +end diff --git a/src/dpslog/WSBT/MikScrollingBattleText-deDE.lua b/src/dpslog/WSBT/MikScrollingBattleText-deDE.lua new file mode 100644 index 0000000..3a74861 --- /dev/null +++ b/src/dpslog/WSBT/MikScrollingBattleText-deDE.lua @@ -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) \ No newline at end of file diff --git a/src/dpslog/WSBT/MikScrollingBattleText-enUS.lua b/src/dpslog/WSBT/MikScrollingBattleText-enUS.lua new file mode 100644 index 0000000..d6bf718 --- /dev/null +++ b/src/dpslog/WSBT/MikScrollingBattleText-enUS.lua @@ -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) \ No newline at end of file diff --git a/src/dpslog/WSBT/MikScrollingBattleText-frFR.lua b/src/dpslog/WSBT/MikScrollingBattleText-frFR.lua new file mode 100644 index 0000000..23728ae --- /dev/null +++ b/src/dpslog/WSBT/MikScrollingBattleText-frFR.lua @@ -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) \ No newline at end of file diff --git a/src/dpslog/WSBT/WSBT.toc b/src/dpslog/WSBT/WSBT.toc new file mode 100644 index 0000000..0f11516 --- /dev/null +++ b/src/dpslog/WSBT/WSBT.toc @@ -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 diff --git a/src/dpslog/WSBT/WSBT_CLEUAdapter.lua b/src/dpslog/WSBT/WSBT_CLEUAdapter.lua new file mode 100644 index 0000000..2af7c85 --- /dev/null +++ b/src/dpslog/WSBT/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() diff --git a/src/dpslog/WSBT/localization.lua b/src/dpslog/WSBT/localization.lua new file mode 100644 index 0000000..6e1aab7 --- /dev/null +++ b/src/dpslog/WSBT/localization.lua @@ -0,0 +1,1252 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Scrolling Battle Text +-- Author: Mik +-- Maintainer: Athene +------------------------------------------------------------------------------------- + +-- Create "namespace." +MikSBT = {}; + +------------------------------------------------------------------------------- +-- Mod Constants +------------------------------------------------------------------------------- + +MikSBT.MOD_NAME = "WSBT" +MikSBT.VERSION_NUMBER = 4.43; +MikSBT.VERSION_STRING = "v4.43-WSBT"; +MikSBT.WINDOW_TITLE = "Weird Scrolling Battle Text " .. MikSBT.VERSION_STRING; + +MikSBT.COMMAND = "/msbt"; + +local BS = AceLibrary("Babble-Spell-2.2") +local L = AceLibrary("AceLocale-2.2"):new(MikSBT.MOD_NAME) + +------------------------------------------------------------------------------- +-- English (Default) +------------------------------------------------------------------------------- + +------------------------------ +-- Commands +------------------------------ +MikSBT.COMMAND_RESET = "reset"; +MikSBT.COMMAND_DISABLE = "disable"; +MikSBT.COMMAND_ENABLE = "enable"; +MikSBT.COMMAND_DISPLAY = "display"; +MikSBT.COMMAND_SHOWVER = "version"; +MikSBT.COMMAND_STATS = "stats"; +MikSBT.COMMAND_SEARCH = "search"; +MikSBT.COMMAND_DEBUG = "debug"; +MikSBT.COMMAND_HELP = "help"; + +MikSBT.COMMAND_USAGE = { + "Usage: " .. MikSBT.COMMAND .. " [params]", + " Commands:", + " " .. MikSBT.COMMAND_RESET .. " - Reset the current profile to the default settings.", + " " .. MikSBT.COMMAND_DISABLE .. " - Disables the mod.", + " " .. MikSBT.COMMAND_ENABLE .. " - Enables the mod.", + " " .. MikSBT.COMMAND_SHOWVER .. " - Shows the current version.", + " " .. MikSBT.COMMAND_STATS .. " - Reports stats about table recycling.", + " " .. MikSBT.COMMAND_SEARCH .. " filter - Sets a filter for searching event types.", + " " .. MikSBT.COMMAND_DEBUG .. " - Toggles debug mode.", + " " .. MikSBT.COMMAND_HELP .. " - Show the command usage.", +}; + + +------------------------------ +-- Output messages +------------------------------ + +MikSBT.MSG_DEBUG_ENABLE = L["Debug mode has been enabled."]; +MikSBT.MSG_DEBUG_DISABLE = L["Debug mode has been disabled."]; +MikSBT.MSG_SEARCH_ENABLE = L["Event search mode has been enabled. Searching for: "]; +MikSBT.MSG_SEARCH_DISABLE = L["Event search mode has been disabled."]; +MikSBT.MSG_DISABLE = L["The mod is now disabled."]; +MikSBT.MSG_ENABLE = L["The mod is now enabled."]; +MikSBT.MSG_HITS = L["Hits"]; +MikSBT.MSG_CRIT = L["Crit"]; +MikSBT.MSG_CRITS = L["Crits"]; +MikSBT.MSG_MULTIPLE_TARGETS = L["Multiple"]; +MikSBT.MSG_VULERNABLE_TRAILER = L[" (%d vulnerability)"]; +MikSBT.MSG_OVERHEAL_TRAILER = L[" <\124cff00b37e\124h%d\124h\124r>"]; +MikSBT.MSG_PROFILE_RESET = L["Profile Reset"]; +MikSBT.MSG_ENVIRONMENTAL_DROWNING = L["Drowning"]; +MikSBT.MSG_ENVIRONMENTAL_FALLING = L["Falling"]; +MikSBT.MSG_ENVIRONMENTAL_FATIGUE = L["Fatigue"]; +MikSBT.MSG_ENVIRONMENTAL_FIRE = L["Fire"]; +MikSBT.MSG_ENVIRONMENTAL_LAVA = L["Lava"]; +MikSBT.MSG_ENVIRONMENTAL_SLIME = L["Slime"]; + + + +------------------------------ +-- Font info +------------------------------ + +-- Holds the available fonts. +MikSBT.AVAILABLE_FONTS = { + [1] = {Name="Adventure", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\adventure.ttf"}, + [2] = {Name="Backsplatter", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\backsplatter.ttf"}, + [3] = {Name="Budhand", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\budhand.ttf"}, + [4] = {Name="Comic", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\comic.ttf"}, + [5] = {Name="Creeper", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\creeper.ttf"}, + [6] = {Name="Friz", Path="Fonts\\FRIZQT__.TTF"}, + [7] = {Name="Porky", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\porky.ttf"}, + [8] = {Name="Signature", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\signature.ttf"}, + [9] = {Name="Black Castle", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\BlackCastleMF.ttf"}, + [10] = {Name="Exocet", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\exocet.ttf"}, + [11] = {Name="FuturaBold", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\FuturaBold.ttf"}, + [12] = {Name="Mail Ray Stuff", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\mailrays.ttf"}, + [13] = {Name="Pepsi", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\pepsi.ttf"}, + [14] = {Name="Bazooka", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\bazooka.ttf"}, + [15] = {Name="Cooline", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\cooline.ttf"}, + [16] = {Name="Yellowjacket", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\yellowjacket.ttf"}, + [17] = {Name="Defused", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\defused.ttf"}, + [18] = {Name="Zombie", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\zombie.ttf"}, + [19] = {Name="Basket Of Hammers", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\basketofhammers.ttf"}, + [20] = {Name="College", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\college.ttf"}, + [21] = {Name="Galaxy", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\galaxy.ttf"}, + [22] = {Name="Skratch Punk", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\skratchpunk.ttf"}, + [23] = {Name="DieDieDie", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\DieDieDie.ttf"}, + [24] = {Name="BigNoodleTitling", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\BigNoodleTitling.ttf"}, + [25] = {Name="Continuum", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\Continuum.ttf"}, + [26] = {Name="Expressway", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\Expressway.ttf"}, + [27] = {Name="Homespun", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\Homespun.ttf"}, + [28] = {Name="Myriad-Pro", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\Myriad-Pro.ttf"}, + [29] = {Name="PT-Sans-Narrow-Bold", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\PT-Sans-Narrow-Bold.ttf"}, + [30] = {Name="PT-Sans-Narrow-Regular", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\PT-Sans-Narrow-Regular.ttf"}, + [31] = {Name="Simhei", Path="Interface\\Addons\\MikScrollingBattleText\\Fonts\\simhei.ttf"}, +}; + +-- Holds the available font outlines. +MikSBT.AVAILABLE_OUTLINES = { + [1] = {Name="None", Style=""}, + [2] = {Name="Thin", Style="OUTLINE"}, + [3] = {Name="Thick", Style="THICKOUTLINE"}, +}; + +-- Holds the available text aligns. +MikSBT.AVAILABLE_TEXT_ALIGNS = { + [1] = {Name="Left", AnchorPoint="BOTTOMLEFT"}, + [2] = {Name="Center", AnchorPoint="BOTTOM"}, + [3] = {Name="Right", AnchorPoint="BOTTOMRIGHT"}, +}; + + +------------------------------ +-- Animation info +------------------------------ + +MikSBT.AVAILABLE_SCROLL_DIRECTIONS = { + [1] = {Name="Up"}, + [2] = {Name="Down"}, +} + +-- Holds the available animation styles. +MikSBT.AVAILABLE_ANIMATION_STYLES = { + [1] = {Name="Straight", AllowedScrollDirections={1,2}}, + [2] = {Name="Left Parabola", AllowedScrollDirections={1,2}}, + [3] = {Name="Right Parabola", AllowedScrollDirections={1,2}}, +}; + + +------------------------------ +-- Trigger info +------------------------------ + +-- Holds the available trigger types. +MikSBT.AVAILABLE_TRIGGER_TYPES = { + [1] = {Name="Self Health"}, + [2] = {Name="Self Mana"}, + [3] = {Name="Pet Health"}, + [4] = {Name="Enemy Target Health"}, + [5] = {Name="Friendly Target Health"}, + [6] = {Name="Search Pattern"}, +}; + + +------------------------------ +-- Stances info +------------------------------ + +-- Holds the available stances. +MikSBT.AVAILABLE_STANCES = { + [1] = {Name="|cffC79C6EBattle Stance|r/|cffFF7D0ABear Form|r/|cffFFF569Stealth|r/|cffF58CBADevotion Aura"}, + [2] = {Name="|cffC79C6EDefensive Stance|r/|cffFF7D0AAquatic Form|r/|cffF58CBARetribution Aura"}, + [3] = {Name="|cffC79C6EBeserker Stance|r/|cffFF7D0ACat Form|r/|cffF58CBAConcentration Aura"}, + [4] = {Name="|cffFF7D0ATravel Form|r/|cffF58CBAShadow Resistance Aura"}, + [5] = {Name="|cffFF7D0AMoonkin Form|r/|cffF58CBAFrost Resistance Aura"}, + [6] = {Name="|cffF58CBAFire Resistance Aura"}, + [7] = {Name="Any"}, +}; + + +------------------------------ +-- Defaults +------------------------------ +MikSBT.DEFAULT_PROFILE_NAME = "Default"; + +-- %a = amount of the attack, heal, gain, loss, etc. +-- %n = name of enemy/player +-- %s = name of the spell, ability, buff, debuff, power type, etc. +-- %t = damage type +MikSBT.DEFAULT_CONFIG = { + CreationVersion = MikSBT.VERSION_NUMBER, + ShowPartialEffects = true, + ShowOverheals = true, + ShowGameDamage = true, + UseStickyCrits = true, + ShowAllManaGains = false, + LowHealthSound = true, + LowManaSound = true, + ResistSound = true, + AnimationStep = 1.5, + MasterFontSettings = { + Normal = {FontIndex=7, OutlineIndex=2, FontSize=18}, + Crit = {FontIndex=7, OutlineIndex=2, FontSize=26}, + }, + BlizzardFontSettings = { + Normal = {FontIndex=6}, + }, + DisplaySettings = { + Incoming = { + Show = true, + FrameOffsets = {x=-450, y=-170}, + AnimationStyle = 1, + ScrollDirection = 1, + ScrollHeight = 450, + FontSettings = { + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + TextAlignIndex = 3, + }, + }, + Outgoing = { + Show = true, + FrameOffsets = {x=450, y=-170}, + AnimationStyle = 1, + ScrollDirection = 1, + ScrollHeight = 450, + FontSettings = { + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + TextAlignIndex = 1, + }, + }, + Notification = { + Show = true, + FrameOffsets = {x=-300, y=-322}, + AnimationStyle = 1, + ScrollDirection = 1, + ScrollHeight = 150, + FontSettings = { + Normal = {FontIndex=0, OutlineIndex=0, FontSize = 0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize = 0}, + TextAlignIndex = 2, + }, + }, + }, + EventSettings = { + MSBT_EVENTTYPE_INCOMING_DAMAGE = { + Show = true, + Message = "-%a", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_MISS = { + Show = true, + Message = MISS.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_DODGE = { + Show = true, + Message = DODGE.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PARRY = { + Show = true, + Message = PARRY.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_BLOCK = { + Show = true, + Message = BLOCK.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_ABSORB = { + Show = true, + Message = ABSORB.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_IMMUNE = { + Show = true, + Message = IMMUNE.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_DAMAGE = { + Show = true, + Message = "-%a", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_DOT = { + Show = true, + Message = "-%a", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_MISS = { + Show = true, + Message = MISS.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_DODGE = { + Show = true, + Message = DODGE.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_PARRY = { + Show = true, + Message = PARRY.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_BLOCK = { + Show = true, + Message = BLOCK.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_RESIST = { + Show = true, + Message = "(%s) "..RESIST.."!", + FontSettings = { + Color = {r=0.502, g=0, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_ABSORB = { + Show = true, + Message = "(%s) "..ABSORB.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_IMMUNE = { + Show = true, + Message = "(%s) "..IMMUNE.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_REFLECT = { + Show = true, + Message = "(%s) "..REFLECT.."!", + FontSettings = { + Color = {r=0.502, g=0, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_HEAL = { + Show = true, + Message = "+%a (%n)", + FontSettings = { + Color = {r=0, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + } + }, + MSBT_EVENTTYPE_INCOMING_HOT = { + Show = true, + Message = "+%a (%n)", + FontSettings = { + Color = {r=0, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + } + }, + MSBT_EVENTTYPE_INCOMING_ENVIRONMENTAL = { + Show = true, + Message = "-%a %s", + FontSettings = { + Color = {r=1, g=0, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_DAMAGE = { + Show = true, + Message = "-%a (Pet)", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_MISS = { + Show = true, + Message = MISS.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_DODGE = { + Show = true, + Message = DODGE.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_PARRY = { + Show = true, + Message = PARRY.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_BLOCK = { + Show = true, + Message = BLOCK.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_ABSORB = { + Show = true, + Message = ABSORB.."! (Pet)", + FontSettings = { + Color = {r=1, g=0.7, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_IMMUNE = { + Show = true, + Message = IMMUNE.."! (Pet)", + FontSettings = { + Color = {r=1, g=0.7, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_DAMAGE = { + Show = true, + Message = "-%a (Pet)", + FontSettings = { + Color = {r=1, g=0.7, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_DOT = { + Show = true, + Message = "-%a (Pet)", + FontSettings = { + Color = {r=1, g=0.7, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_MISS = { + Show = true, + Message = MISS.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_DODGE = { + Show = true, + Message = DODGE.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_PARRY = { + Show = true, + Message = PARRY.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_BLOCK = { + Show = true, + Message = BLOCK.."! (Pet)", + FontSettings = { + Color = {r=0.2, g=0.4, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_RESIST = { + Show = true, + Message = RESIST.."! (Pet)", + FontSettings = { + Color = {r=0.5, g=0, b=0.4}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_ABSORB = { + Show = true, + Message = "(%s) "..ABSORB.."! (Pet)", + FontSettings = { + Color = {r=1, g=0.7, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_SPELL_IMMUNE = { + Show = true, + Message = "(%s) "..IMMUNE.."! (Pet)", + FontSettings = { + Color = {r=1, g=0.7, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_PET_HEAL = { + Show = true, + Message = "+%a (%n) (Pet)", + FontSettings = { + Color = {r=0, g=1, b=0.4}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + } + }, + MSBT_EVENTTYPE_INCOMING_PET_HOT = { + Show = true, + Message = "+%a (%n) (Pet)", + FontSettings = { + Color = {r=0, g=1, b=0.4}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + } + }, + MSBT_EVENTTYPE_OUTGOING_DAMAGE = { + Show = true, + Message = "%a", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_MISS = { + Show = true, + Message = MISS.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_DODGE = { + Show = true, + Message = DODGE.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PARRY = { + Show = true, + Message = PARRY.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_BLOCK = { + Show = true, + Message = BLOCK.."!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_ABSORB = { + Show = true, + Message = ABSORB.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_IMMUNE = { + Show = true, + Message = IMMUNE.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_EVADE = { + Show = true, + Message = EVADE.."!", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=22}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_DAMAGE = { + Show = true, + Message = "%a", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_DOT = { + Show = true, + Message = "%a", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_MISS = { + Show = true, + Message = MISS.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_DODGE = { + Show = true, + Message = DODGE.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_PARRY = { + Show = true, + Message = PARRY.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_BLOCK = { + Show = true, + Message = BLOCK.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_RESIST = { + Show = true, + Message = RESIST.."!", + FontSettings = { + Color = {r=0.502, g=0.502, b=0.698}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_ABSORB = { + Show = true, +Message = "(%s) "..ABSORB.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_IMMUNE = { + Show = true, + Message = "(%s) "..IMMUNE.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_REFLECT = { + Show = true, + Message = "(%s) "..REFLECT.."!", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_EVADE = { + Show = true, + Message = "(%s) "..EVADE.."!", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=22}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_HEAL = { + Show = true, + Message = "+%a", + FontSettings = { + Color = {r=0, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=22}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_HOT = { + Show = true, + Message = "+%a", + FontSettings = { + Color = {r=0, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + + + + MSBT_EVENTTYPE_OUTGOING_PET_DAMAGE = { + Show = true, + Message = "Pet %a", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + + }, + MSBT_EVENTTYPE_OUTGOING_PET_MISS = { + Show = true, + Message = "Pet "..MISS.."!", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_DODGE = { + Show = true, + Message = "Pet "..DODGE.."!", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_PARRY = { + Show = true, + Message = "Pet "..PARRY.."!", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_BLOCK = { + Show = true, + Message = "Pet "..BLOCK.."!", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_ABSORB = { + Show = true, + Message = "Pet "..ABSORB.."!", + FontSettings = { + Color = {r=0.502, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_IMMUNE = { + Show = true, + Message = "Pet "..IMMUNE.."!", + FontSettings = { + Color = {r=0.502, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_EVADE = { + Show = true, + Message = "Pet "..EVADE.."!", + FontSettings = { + Color = {r=1, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_DAMAGE = { + Show = true, + Message = "Pet %a (%s)", + FontSettings = { + Color = {r=0.33, g=0.33, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_MISS = { + Show = true, + Message = "Pet "..MISS.."! (%s)", + FontSettings = { + Color = {r=0.33, g=0.33, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_DODGE = { + Show = true, + Message = "Pet "..DODGE.."! (%s)", + FontSettings = { + Color = {r=0.33, g=0.33, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_PARRY = { + Show = true, + Message = "Pet "..PARRY.."! (%s)", + FontSettings = { + Color = {r=0.33, g=0.33, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_BLOCK = { + Show = true, + Message = "Pet "..BLOCK.."! (%s)", + FontSettings = { + Color = {r=0.33, g=0.33, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_RESIST = { + Show = true, + Message = "Pet "..RESIST.."! (%s)", + FontSettings = { + Color = {r=0.502, g=0.502, b=0.698}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_ABSORB = { + Show = true, + Message = "Pet "..ABSORB.."! (%s)", + FontSettings = { + Color = {r=0.502, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_IMMUNE = { + Show = true, + Message = "Pet "..IMMUNE.."! (%s)", + FontSettings = { + Color = {r=0.502, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PET_SPELL_EVADE = { + Show = true, + Message = "Pet "..EVADE.."! (%s)", + FontSettings = { + Color = {r=1, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=22}, + }, + }, + + + + MSBT_EVENTTYPE_NOTIFICATION_DEBUFF = { + Show = true, + Message = "[%s]", + IsSticky = false, + FontSettings = { + Color = {r=0, g=0.502, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_BUFF = { + Show = true, + Message = "[%s]", + IsSticky = false, + FontSettings = { + Color = {r=0.698, g=0.698, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_ITEM_BUFF = { + Show = true, + Message = "[%s]", + IsSticky = false, + FontSettings = { + Color = {r=0.698, g=0.698, b=0.698}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_BUFF_FADE = { + Show = true, + Message = "-[%s]", + IsSticky = false, + FontSettings = { + Color = {r=0.698, g=0.698, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_COMBAT_ENTER = { + Show = true, + Message = "+Combat", + IsSticky = false, + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_COMBAT_LEAVE = { + Show = true, + Message = "-Combat", + IsSticky = false, + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_POWER_GAIN = { + Show = true, + Message = "+%a", + IsSticky = false, + FontSettings = { + Color = {r=0.3098039215686275, g=0.3098039215686275, b=0.8784313725490196}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_POWER_LOSS = { + Show = true, + Message = "-%a", + IsSticky = false, + FontSettings = { + Color = {r=0.7568627450980392, g=0.2705882352941176, b=0.8235294117647058}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_CP_GAIN = { + Show = true, + Message = "%a CP", + IsSticky = false, + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_CP_FULL = { + Show = true, + Message = "%a CP Finish It!", + IsSticky = false, + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_HONOR_GAIN = { + Show = true, + Message = "+%a "..HONOR, + IsSticky = false, + FontSettings = { + Color = {r=0.502, g=0.502, b=0.698}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_REP_GAIN = { + Show = true, + Message = "+%a "..TUTORIAL_TITLE16.." (%s)", + IsSticky = false, + FontSettings = { + Color = {r=0.502, g=0.502, b=0.698}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_REP_LOSS = { + Show = true, + Message = "-%a "..TUTORIAL_TITLE16.." (%s)", + IsSticky = false, + FontSettings = { + Color = {r=0.502, g=0.502, b=0.698}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_SKILL_GAIN = { + Show = true, + Message = "%s: %a", + IsSticky = false, + FontSettings = { + Color = {r=0.333, g=0.333, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_EXPERIENCE_GAIN = { + Show = true, + Message = "%a XP", + IsSticky = true, + FontSettings = { + Color = {r=0.756, g=0.270, b=0.823}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_PC_KILLING_BLOW = { + Show = true, + Message = "Killing Blow! (%s)", + IsSticky = true, + FontSettings = { + Color = {r=0.333, g=0.333, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=20}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_NPC_KILLING_BLOW = { + Show = false, + Message = "Killing Blow! (%s)", + IsSticky = true, + FontSettings = { + Color = {r=0.333, g=0.333, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=20}, + }, + }, + }, + + Triggers = { + MSBT_TRIGGER_LOW_HEALTH = { + EventSettings = { + Show = true, + Message = "Low Health! (%1)", + IsSticky = false, + FontSettings = { + Color = {r=1, g=0.502, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + TriggerType = 1, + Threshold = 40, + }, + Texture = "Interface\\Icons\\spell_holy_sealofsacrifice" + }, + MSBT_TRIGGER_LOW_MANA = { + EventSettings = { + Show = true, + Message = "Low Mana! (%1)", + IsSticky = false, + FontSettings = { + Color = {r=0.502, g=0.502, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {DRUID=true,HUNTER=true,MAGE=true,PALADIN=true,PRIEST=true,SHAMAN=true,WARLOCK=true}, + TriggerType = 2, + Threshold = 35, + }, + }, + MSBT_TRIGGER_LOW_PET_HEALTH = { + EventSettings = { + Show = true, + Message = "Low Pet Health! (%1)", + IsSticky = false, + FontSettings = { + Color = {r=1, g=0.502, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {HUNTER=true,WARLOCK=true}, + TriggerType = 3, + Threshold = 40, + }, + Texture = "Interface\\Icons\\spell_holy_sealofsacrifice" + }, + MSBT_TRIGGER_EXECUTE = { + EventSettings = { + Show = true, + Message = BS["Execute"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {WARRIOR=true}, + TriggerType = 4, + Threshold = 20, + }, + Texture = "Interface\\Icons\\inv_sword_48" + }, + MSBT_TRIGGER_HAMMER_OF_WRATH = { + EventSettings = { + Show = true, + Message = BS["Hammer of Wrath"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {PALADIN=true}, + TriggerType = 4, + Threshold = 20, + }, + Texture = "Interface\\Icons\\ability_thunderclap" + }, + MSBT_TRIGGER_COUNTER_ATTACK = { + EventSettings = { + Show = true, + Message = BS["Counterattack"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {HUNTER=true}, + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES", "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES", "CHAT_MSG_COMBAT_PARTY_MISSES", + "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE", "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE", "CHAT_MSG_SPELL_PARTY_DAMAGE"}, + SearchPatterns = {"VSPARRYOTHERSELF", "SPELLPARRIEDOTHERSELF"}, + }, + Texture = "Interface\\Icons\\ability_warrior_challange" + }, + MSBT_TRIGGER_MONGOOSE_BITE = { + EventSettings = { + Show = true, + Message = BS["Mongoose Bite"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {HUNTER=true}, + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES", "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES", "CHAT_MSG_COMBAT_PARTY_MISSES", + "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE", "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE", "CHAT_MSG_SPELL_PARTY_DAMAGE"}, + SearchPatterns = {"VSDODGEOTHERSELF", "SPELLDODGEDOTHERSELF"}, + }, + Texture = "Interface\\Icons\\ability_hunter_swiftstrike" + }, + MSBT_TRIGGER_CLEARCAST = { + EventSettings = { + Show = true, + Message = BS["Clearcasting"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {MAGE=true,SHAMAN=true}, + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}, + SearchPatterns = {string.format(AURAADDEDSELFHELPFUL, BS["Clearcasting"])}, + }, + Texture = "Interface\\Icons\\spell_shadow_manaburn" + }, + MSBT_TRIGGER_RIPOSTE = { + EventSettings = { + Show = true, + Message = BS["Riposte"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {ROGUE=true}, + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES", "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES", "CHAT_MSG_COMBAT_PARTY_MISSES", + "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE", "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE", "CHAT_MSG_SPELL_PARTY_DAMAGE"}, + SearchPatterns = {"VSPARRYOTHERSELF", "SPELLPARRIEDOTHERSELF"}, + }, + Texture = "Interface\\Icons\\ability_warrior_challange" + }, + MSBT_TRIGGER_WINDFURY = { + EventSettings = { + Show = true, + Message = "Windfury!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}, + SearchPatterns = {string.format(AURAADDEDSELFHELPFUL, string.gsub(BS["Windfury Totem"], "-", "%%-"))}, --"Vous gagnez Totem Furie-des-vents" -- escape char ! + }, + Texture = "Interface\\Icons\\spell_nature_cyclone" + }, + MSBT_TRIGGER_NIGHTFALL = { + EventSettings = { + Show = true, + Message = BS["Nightfall"].."!", + IsSticky = true, + FontSettings = { + Color = {r=0.709, g=0, b=0.709}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {WARLOCK=true}, + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"}, + SearchPatterns = {string.format(AURAADDEDSELFHELPFUL, BS["Shadow Trance"])}, + }, + Texture = "Interface\\Icons\\spell_shadow_twilight" + }, + MSBT_TRIGGER_OVERPOWER = { + EventSettings = { + Show = true, + Message = BS["Overpower"].."!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {WARRIOR=true}, + TriggerType = 6, + TriggerEvents = {"CHAT_MSG_COMBAT_SELF_MISSES", "CHAT_MSG_SPELL_SELF_DAMAGE"}, + SearchPatterns = {"VSDODGESELFOTHER", "SPELLDODGEDSELFOTHER"}, + }, + Texture = "Interface\\Icons\\ability_meleedamage" + }, + }, + + Suppressions = { + MSBT_SUPPRESSION_WISDOM_MANA_GAINS = { + Enabled = true, + SearchPattern = "+[35][039] " .. MANA, + }, + }, +}; diff --git a/src/dpslog/WeirdDPSMate/DPSMate_CLEUAdapter.lua b/src/dpslog/WeirdDPSMate/DPSMate_CLEUAdapter.lua index 20a50c9..732c9e6 100644 --- a/src/dpslog/WeirdDPSMate/DPSMate_CLEUAdapter.lua +++ b/src/dpslog/WeirdDPSMate/DPSMate_CLEUAdapter.lua @@ -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) diff --git a/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua b/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua index e4fa2a8..d87ce67 100644 --- a/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua +++ b/src/dpslog/WeirdUtils_DPSLog/DPSLog.lua @@ -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 diff --git a/src/dpslog/dpslog.zig b/src/dpslog/dpslog.zig index 47c79c8..e1a7042 100644 --- a/src/dpslog/dpslog.zig +++ b/src/dpslog/dpslog.zig @@ -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(); diff --git a/src/lua.zig b/src/lua.zig index a554b97..276b622 100644 --- a/src/lua.zig +++ b/src/lua.zig @@ -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); +} diff --git a/src/main.zig b/src/main.zig index 6c3930c..02cae88 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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();