From 73212ebe33c2a5bc364314f18eff7c77850c123d Mon Sep 17 00:00:00 2001 From: Athene Date: Fri, 29 May 2015 23:25:35 +0200 Subject: [PATCH] Initial Release Clean MSBT 3.01 from Mik's --- .../Artwork/PlainBackdrop.tga | Bin 0 -> 1068 bytes MikScrollingBattleText/Fonts/adventure.ttf | Bin 0 -> 50896 bytes MikScrollingBattleText/Fonts/backsplatter.ttf | Bin 0 -> 38164 bytes MikScrollingBattleText/Fonts/budhand.ttf | Bin 0 -> 30768 bytes MikScrollingBattleText/Fonts/comic.ttf | Bin 0 -> 126364 bytes MikScrollingBattleText/Fonts/creeper.ttf | Bin 0 -> 48752 bytes MikScrollingBattleText/Fonts/porky.ttf | Bin 0 -> 79752 bytes MikScrollingBattleText/Fonts/signature.ttf | Bin 0 -> 39904 bytes .../MikCombatEventHelper.lua | 3277 +++++++++++++++ .../MikCombatEventHelper.xml | 13 + .../MikScrollingBattleText.lua | 2242 +++++++++++ .../MikScrollingBattleText.toc | 11 + .../MikScrollingBattleText.xml | 346 ++ .../MikTableRecyclerObject.lua | 173 + MikScrollingBattleText/localization.lua | 1030 +++++ MikScrollingBattleText/readme.html | 808 ++++ MikScrollingBattleTextOptions/MSBTOptions.lua | 3561 +++++++++++++++++ MikScrollingBattleTextOptions/MSBTOptions.xml | 2645 ++++++++++++ .../MikScrollingBattleTextOptions.toc | 9 + .../localization_options.lua | 259 ++ 20 files changed, 14374 insertions(+) create mode 100644 MikScrollingBattleText/Artwork/PlainBackdrop.tga create mode 100644 MikScrollingBattleText/Fonts/adventure.ttf create mode 100644 MikScrollingBattleText/Fonts/backsplatter.ttf create mode 100644 MikScrollingBattleText/Fonts/budhand.ttf create mode 100644 MikScrollingBattleText/Fonts/comic.ttf create mode 100644 MikScrollingBattleText/Fonts/creeper.ttf create mode 100644 MikScrollingBattleText/Fonts/porky.ttf create mode 100644 MikScrollingBattleText/Fonts/signature.ttf create mode 100644 MikScrollingBattleText/MikCombatEventHelper.lua create mode 100644 MikScrollingBattleText/MikCombatEventHelper.xml create mode 100644 MikScrollingBattleText/MikScrollingBattleText.lua create mode 100644 MikScrollingBattleText/MikScrollingBattleText.toc create mode 100644 MikScrollingBattleText/MikScrollingBattleText.xml create mode 100644 MikScrollingBattleText/MikTableRecyclerObject.lua create mode 100644 MikScrollingBattleText/localization.lua create mode 100644 MikScrollingBattleText/readme.html create mode 100644 MikScrollingBattleTextOptions/MSBTOptions.lua create mode 100644 MikScrollingBattleTextOptions/MSBTOptions.xml create mode 100644 MikScrollingBattleTextOptions/MikScrollingBattleTextOptions.toc create mode 100644 MikScrollingBattleTextOptions/localization_options.lua diff --git a/MikScrollingBattleText/Artwork/PlainBackdrop.tga b/MikScrollingBattleText/Artwork/PlainBackdrop.tga new file mode 100644 index 0000000000000000000000000000000000000000..035352bcdaaf301c3b686d6973e8bfe5ff39b2b0 GIT binary patch literal 1068 ycmZQzU}As)0R{mE1&;rtU^E0qLtw;)0IW0!2?})$^9=U%_tTAV^Yn4mV*mhqx&d+k literal 0 HcmV?d00001 diff --git a/MikScrollingBattleText/Fonts/adventure.ttf b/MikScrollingBattleText/Fonts/adventure.ttf new file mode 100644 index 0000000000000000000000000000000000000000..964ab3cc71435114fb064cb38a09c6e624775ddf GIT binary patch literal 50896 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 literal 0 HcmV?d00001 diff --git a/MikScrollingBattleText/Fonts/budhand.ttf b/MikScrollingBattleText/Fonts/budhand.ttf new file mode 100644 index 0000000000000000000000000000000000000000..47098e146347f4ac2668f8e50e228e9e93cb8108 GIT binary patch literal 30768 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 U_`3LQucm(aet5sR7s%ZI0n;jcOaK4? literal 0 HcmV?d00001 diff --git a/MikScrollingBattleText/Fonts/creeper.ttf b/MikScrollingBattleText/Fonts/creeper.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8bccc58d739f13cfa75f44159d79d71903a7a382 GIT binary patch literal 48752 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

ACiq?>%(x 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 AD*ylh literal 0 HcmV?d00001 diff --git a/MikScrollingBattleText/Fonts/signature.ttf b/MikScrollingBattleText/Fonts/signature.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c173b8344afcdce36c70db946d23a9d6a26b2050 GIT binary patch literal 39904 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 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) + -- Incoming Melee Hits/Crits + if (event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS" or + event == "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS" or + event == "CHAT_MSG_COMBAT_PARTY_HITS") then + MikCEH.ParseForIncomingHits(combatMessage); + + -- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes + elseif (event == "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES" or + event == "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES" or + event == "CHAT_MSG_COMBAT_PARTY_MISSES") then + MikCEH.ParseForIncomingMisses(combatMessage); + + -- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Power Losses + elseif (event == "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE" or + event == "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE" or + event == "CHAT_MSG_SPELL_PARTY_DAMAGE") then + MikCEH.ParseForIncomingSpellHitsAndMisses(combatMessage); + MikCEH.ParseForPowerLosses(combatMessage); + + -- Incoming damage from shields + elseif (event == "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS") then + MikCEH.ParseForIncomingDamageShieldDamage(combatMessage); + + -- Incoming Heals + elseif (event == "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF" or + event == "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF") then + MikCEH.ParseForIncomingSpellHeals(combatMessage); + + -- Incoming Debuffs, DoTs, Power Gains + elseif (event == "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE") then + MikCEH.ParseForIncomingDebuffs(combatMessage); + MikCEH.ParseForPowerGains(combatMessage); + + -- Incoming Buffs, HoTs, Power Gains + elseif (event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS") then + if (not MikCEH.ParseForIncomingSpellHeals(combatMessage)) then + MikCEH.ParseForIncomingBuffs(combatMessage); + MikCEH.ParseForOutgoingHoTs(combatMessage); + end + + + + -- Outgoing Melee Hits/Crits, Environmental Damage + elseif (event == "CHAT_MSG_COMBAT_SELF_HITS") then + if (not MikCEH.ParseForEnvironmentalDamage(combatMessage)) then + MikCEH.ParseForOutgoingHits(combatMessage); + end + + -- Outgoing Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes, Evades + elseif (event == "CHAT_MSG_COMBAT_SELF_MISSES") then + MikCEH.ParseForOutgoingMisses(combatMessage); + + -- Outgoing Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades + elseif (event == "CHAT_MSG_SPELL_SELF_DAMAGE") then + MikCEH.ParseForOutgoingSpellHitsAndMisses(combatMessage); + + -- Outgoing damage from shields + elseif (event == "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF") then + MikCEH.ParseForOutgoingDamageShieldDamage(combatMessage); + + -- Outgoing Heals, Power Gains, Dispel/Purge Resists + elseif (event == "CHAT_MSG_SPELL_SELF_BUFF") then + if (not MikCEH.ParseForPowerGains(combatMessage)) then + MikCEH.ParseForOutgoingSpellHeals(combatMessage); + MikCEH.ParseForOutgoingDispelResists(combatMessage); + end + + -- Outgoing HoTs + elseif (event == "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS" or + event == "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS") then + MikCEH.ParseForOutgoingHoTs(combatMessage); + + -- Outgoing DoTs, Power Losses + elseif (event == "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE" or + event == "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE") then + MikCEH.ParseForOutgoingDoTs(combatMessage); + MikCEH.ParseForPowerLosses(combatMessage); + + -- Outgoing Pet Hits/Crits + elseif (event == "CHAT_MSG_COMBAT_PET_HITS") then + MikCEH.ParseForOutgoingPetHits(combatMessage); + + -- Outgoing Pet Melee Misses + elseif(event == "CHAT_MSG_COMBAT_PET_MISSES") then + MikCEH.ParseForOutgoingPetMisses(combatMessage); + + -- Outgoing Pet Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades + elseif(event == "CHAT_MSG_SPELL_PET_DAMAGE") then + MikCEH.ParseForOutgoingPetSpellHitsAndMisses(combatMessage); + + + + -- Item Buffs + elseif (event == "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS") then + MikCEH.ParseForIncomingItemBuffs(combatMessage); + + -- Buff Fades + elseif (event == "CHAT_MSG_SPELL_AURA_GONE_SELF") then + MikCEH.ParseForBuffFades(combatMessage); + + -- Honor Gains + elseif (event == "CHAT_MSG_COMBAT_HONOR_GAIN") then + MikCEH.ParseForHonorGains(combatMessage); + + -- Reputation Gains/Losses + elseif (event == "CHAT_MSG_COMBAT_FACTION_CHANGE") then + MikCEH.ParseForReputationGainsAndLosses(combatMessage); + + -- Skill Gains + elseif (event == "CHAT_MSG_SKILL") then + MikCEH.ParseForSkillGains(combatMessage); + + -- Experience Gains + elseif (event == "CHAT_MSG_COMBAT_XP_GAIN") then + MikCEH.ParseForExperienceGains(combatMessage); + + -- Killing Blows + elseif (event == "CHAT_MSG_COMBAT_HOSTILE_DEATH") then + MikCEH.ParseForKillingBlows(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"); +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 + + + -- 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 + + -- 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 + + + -- 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 + + -- 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); + + -- 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); + + -- 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); + + -- 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); + + -- 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 + + + -- Look for damage from a debuff. + local capturedData = MikCEH.GetCapturedData(combatMessage, "PERIODICAURADAMAGEOTHERSELF", {"%a", "%t", "%n", "%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, 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 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) + -- Look for self power gains. + local capturedData = MikCEH.GetCapturedData(combatMessage, "POWERGAINSELFSELF", {"%a", "%p", "%s"}); + + -- 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); + + -- Send the event. + MikCEH.SendEvent(eventData); + + -- Return the parse was successful. + return true; + end + end + + -- Look for power gains from others. + local capturedData = MikCEH.GetCapturedData(combatMessage, "POWERGAINOTHERSELF", {"%a", "%p", "%n", "%s"}); + + -- 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); + + -- 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); + + -- 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); + + -- 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); + + -- 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 + + -- 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 = MikCEH.GetDamageEventData(MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING, MikCEH.ACTIONTYPE_HIT, MikCEH.HITTYPE_NORMAL, capturedData.DamageType, capturedData.Amount, 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 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); + + -- 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 + + + -- 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 (string.find(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 (string.find(currentChar, "[%$%.%d]")) then + -- Do nothing. + + -- Check for one of the types that need a string. + elseif (string.find(currentChar, "[cEefgGiouXxqs]")) then + -- Replace the format code with lua capture string syntax. + searchString = searchString .. "(.+)"; + + -- Increment the argument number. + argumentNumber = argumentNumber + 1; + + -- Check if there is an argument position specified. + local _, _, argumentPosition = string.find(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 = string.find(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(string.gfind(combatMessage, globalStringInfo.Search)()); + + -- If a match was found. + if (table.getn(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) + -- Erase old unorderd capture data. + MikCEH.EraseTable(unorderedCaptureData); + + if (c1 ~= nil) then table.insert(unorderedCaptureData, c1); end + if (c2 ~= nil) then table.insert(unorderedCaptureData, c2); end + if (c3 ~= nil) then table.insert(unorderedCaptureData, c3); end + if (c4 ~= nil) then table.insert(unorderedCaptureData, c4); end + if (c5 ~= nil) then table.insert(unorderedCaptureData, c5); end + if (c6 ~= nil) then table.insert(unorderedCaptureData, c6); end + if (c7 ~= nil) then table.insert(unorderedCaptureData, c7); end + if (c8 ~= nil) then table.insert(unorderedCaptureData, c8); end + if (c9 ~= nil) then table.insert(unorderedCaptureData, c9); end + + -- 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; + 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 + + +-- ********************************************************************************** +-- 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; + + -- Erase the combat event data table. + MikCEH.EraseTable(eventData); + + + -- 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; + + -- Erase the combat event data table. + MikCEH.EraseTable(eventData); + + -- 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) + -- Get the global combat event data table. + local eventData = MikCEH.CombatEventData; + + -- Erase the combat event data table. + MikCEH.EraseTable(eventData); + + -- Populate the event data fields. + eventData.EventType = MikCEH.EVENTTYPE_NOTIFICATION; + eventData.NotificationType = notificationType; + eventData.Amount = amount; + eventData.EffectName = effectName; + + -- 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 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 + + -- 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 + + 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); + + -- 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. + if (overhealAmount > 0) 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. + table.setn(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 = table.getn(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) + 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"); + + -- 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) + end + end + + -- Update the last mana percentage. + lastSelfManaPercentage = manaPercentage; + 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 (string.find(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(string.gfind(combatMessage, searchPattern)()); + + -- Check if a match was found. + if (table.getn(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 \ No newline at end of file diff --git a/MikScrollingBattleText/MikCombatEventHelper.xml b/MikScrollingBattleText/MikCombatEventHelper.xml new file mode 100644 index 0000000..f36ffe2 --- /dev/null +++ b/MikScrollingBattleText/MikCombatEventHelper.xml @@ -0,0 +1,13 @@ + + + + + + MikCEH.OnEvent(); + MikCEH.OnLoad(); + MikCEH.OnUpdate(); + + + \ No newline at end of file diff --git a/MikScrollingBattleText/MikScrollingBattleText.lua b/MikScrollingBattleText/MikScrollingBattleText.lua new file mode 100644 index 0000000..940e471 --- /dev/null +++ b/MikScrollingBattleText/MikScrollingBattleText.lua @@ -0,0 +1,2242 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Scrolling Battle Text +-- Author: Mik +------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------- +-- Public Constants. +------------------------------------------------------------------------------------- + +-- Display types for incoming, outgoing, and notification events. +MikSBT.DISPLAYTYPE_INCOMING = 1; +MikSBT.DISPLAYTYPE_OUTGOING = 2; +MikSBT.DISPLAYTYPE_NOTIFICATION = 3; + + +------------------------------------------------------------------------------------- +-- Public Variables. +------------------------------------------------------------------------------------- + +-- Holds the current profile. +MikSBT.CurrentProfile = nil; + + +------------------------------------------------------------------------------------- +-- Private constants. +------------------------------------------------------------------------------------- + +-- Number of font strings. +local NUM_FONT_STRINGS = 15; + +-- Animation values for incoming and outgoing events. +local MIN_VERTICAL_SPACING = 8; + +-- How long to wait before showing events so that merges may happen. +local MERGE_DELAY_TIME = 0.3; + +-- How long to show stickies before they are faded. +local STICKY_FADE_IN_TIME = 0.15; +local STICKY_DISPLAY_TIME = 2; +local STICKY_FADE_OUT_TIME = 0.5; + +-- The animation speed. +local ANIMATION_SPEED = 0.015; + +-- Scroll directions +local SCROLL_DIRECTION_UP = 1; +local SCROLL_DIRECTION_DOWN = 2; + +-- Animation Styles +local ANIMATION_STYLE_STRAIGHT = 1; +local ANIMATION_STYLE_LEFT_PARABOLA = 2; +local ANIMATION_STYLE_RIGHT_PARABOLA = 3; + +-- Max number of combo points. +local MAX_COMBO_POINTS = 5; + + +------------------------------------------------------------------------------------- +-- Private variables. +------------------------------------------------------------------------------------- + +-- Holds whether or not debugging is active. +local debugMode = false; + + +-- Holds the events the mod is interested in. +local listenEvents = {}; + +-- Holds data for each scroll area. +local scrollAreas = {}; + +-- Holds arrays of unmerged events, merged events to be displayed and the time since +-- the last merge for incoming and outgoing events. +local animationMergeData = { + ["Incoming"] = {["TimeSinceMerge"] = 0, UnmergedEvents={}, MergedEvents={}}, + ["Outgoing"] = {["TimeSinceMerge"] = 0, UnmergedEvents={}, MergedEvents={}}, +}; + +-- Holds whether or not a merge is still being processed. +local stillMerging = false; + +-- Temp arrays for repositioning animation display data to avoid overlaps. +local activeNonStickies = {}; +local activeStickies = {}; + +-- Create a new table recycler object that holds up to 50 table references +-- in primary cache. +local eventsRecycler = MikTRO:NewRecyclerObject(50); + + +------------------------------------------------------------------------------------- +-- Printing functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Prints out the passed message to the default chat frame. +-- ********************************************************************************** +function MikSBT.Print(msg, r, g, b) + -- Add the message to the default chat frame. + DEFAULT_CHAT_FRAME:AddMessage("MSBT: " .. msg, r, g, b); +end + + +-- ********************************************************************************** +-- Prints out the passed message to the default chat frame. +-- ********************************************************************************** +function MikSBT.PrintDebug(msg, r, g, b) + -- Check if debugging is active. + if (debugMode) then + -- Add the message to the default chat frame. + DEFAULT_CHAT_FRAME:AddMessage(msg, r, g, b); + end +end + + +------------------------------------------------------------------------------------- +-- Core event handlers. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Registers all of the core events the mod is interested in. +-- ********************************************************************************** +function MikSBT.RegisterEvents() + -- Register the events we are interested in receiving for shaman. + for k, v in listenEvents do + MSBTEventFrame:RegisterEvent(v); + end +end + + +-- ********************************************************************************** +-- Unregisters all of the events the mod registered for. +-- ********************************************************************************** +function MikSBT.UnregisterEvents() + for k, v in listenEvents do + MSBTEventFrame:UnregisterEvent(v); + end +end + + +-- ********************************************************************************** +-- Called when the mod is loaded. +-- ********************************************************************************** +function MikSBT.OnLoad() + -- Register for the ADDON_LOADED event. + MSBTEventFrame:RegisterEvent("ADDON_LOADED"); +end + +-- ********************************************************************************** +-- Called when the events we registered for occur. +-- ********************************************************************************** +function MikSBT.OnEvent() + -- When an addon is loaded. + if (event == "ADDON_LOADED") then + -- Make sure it's this addon. + if (arg1 == MikSBT.MOD_NAME) then + + -- Don't get notification for other addons being loaded. + this:UnregisterEvent("ADDON_LOADED"); + + -- Register for the events the helper is interested in receiving. + MikSBT.RegisterEvents(); + + -- Initialize the mod. + MikSBT.Init(); + end + + end +end + + +-- ********************************************************************************** +-- Called when the core events frame is updated. +-- ********************************************************************************** +function MikSBT.OnUpdate() + + -- Loop through all of the merge data tables. + for _, mergeData in animationMergeData do + -- Add the elapsed time since the last merge. + mergeData.TimeSinceMerge = mergeData.TimeSinceMerge + arg1; + + -- Once enough time has passed and all previous merges are done. + if ((mergeData.TimeSinceMerge >= MERGE_DELAY_TIME) and not stillMerging) then + -- Set the still merging flag. + stillMerging = true; + + -- Merge like events. + local numEvents = table.getn(mergeData.UnmergedEvents); + MikSBT.MergeEvents(mergeData, numEvents); + + -- Add the merged animation events to the animation system. + for _, animationEvent in mergeData.MergedEvents do + MikSBT.AddAnimation(animationEvent); + end + + -- Clear the merged animation events array. + local numMergedEvents = table.getn(mergeData.MergedEvents); + for i = 1, numMergedEvents do + table.remove(mergeData.MergedEvents, 1); + end + + + -- Reset the last merged time. + mergeData.TimeSinceMerge = 0; + + -- Clear the still merging flag. + stillMerging = false; + end + end +end + + +-- ********************************************************************************** +-- Called when the mod's animation frames are updated. +-- ********************************************************************************** +function MikSBT.OnUpdateAnimationFrame(frame) + local scrollArea; + + -- Get the frame's name. + local frameName = frame:GetName(); + if (frameName == scrollAreas.Incoming.AssociatedFrame) then + scrollArea = scrollAreas.Incoming; + elseif (frameName == scrollAreas.Outgoing.AssociatedFrame) then + scrollArea = scrollAreas.Outgoing; + elseif (frameName == scrollAreas.Notification.AssociatedFrame) then + scrollArea = scrollAreas.Notification; + end + + -- Flag for whether or not all the animations are inactive. + local allInactive = true; + + -- Loop through all possible animation display objects for the scroll area. + for _, animDisplayInfo in scrollArea.AnimationDisplayArray do + -- Check if the animation is active. + if (animDisplayInfo.IsActive) then + -- Do the animation and clear the all inactive flag. + MikSBT.DoAnimation(animDisplayInfo, scrollArea); + allInactive = false; + end + end + + -- Check if none of the animations are active and the events frame is visible. If so, hide it so + -- the OnUpdate events stop firing. This is done to keep the number of OnUpdate events down to a minimum for + -- better performance. + if (allInactive and frame:IsVisible()) then + frame:Hide(); + end +end + + +-- ********************************************************************************** +-- Called when the mod is fully loaded (variables loaded). +-- ********************************************************************************** +function MikSBT.Init() + -- Register slash commands + SLASH_MSBT1 = MikSBT.COMMAND; + SlashCmdList["MSBT"] = function (params) + MikSBT.CommandHandler(params); + end + + -- Check if there are no saved variables. + if (not MikSBT_Save or not MikSBT_Save.Profiles) then + MikSBT_Save = {}; + + MikSBT_Save.UserDisabled = false; + MikSBT_Save.CurrentProfile = MikSBT.DEFAULT_PROFILE_NAME; + MikSBT_Save.Profiles = {}; + MikSBT_Save.Profiles[MikSBT_Save.CurrentProfile] = {}; + + -- Set all the options to their defaults. + MikSBT.ResetProfile(MikSBT_Save.CurrentProfile); + end + + -- Updates profiles created by older versions. + MikSBT.UpdateProfiles(); + + + -- Set the current profile. + MikSBT.CurrentProfile = MikSBT_Save.Profiles[MikSBT_Save.CurrentProfile]; + + -- Sets up all of the animation scroll areas. + MikSBT.SetupAnimationScrollAreas(); + + -- Reposition the animation frames to the stored offsets. + MikSBT.RepositionAnimationFrames(); + + -- Register the triggers with the combat event helper. + MikSBT.RegisterTriggers(); + + -- Disable or enable the mod depending on the saved setting. + MikSBT.SetOptionUserDisabled(MikSBT_Save.UserDisabled) +end + + +-- ********************************************************************************** +-- Sets up all of the animation scroll areas. +-- ********************************************************************************** +function MikSBT.SetupAnimationScrollAreas() + -- Setup a table for data for each scroll area. + scrollAreas.Incoming = {}; + scrollAreas.Outgoing = {}; + scrollAreas.Notification = {}; + + -- Setup the associated frames. + scrollAreas.Incoming.AssociatedFrame = "MSBTFrameIncoming"; + scrollAreas.Outgoing.AssociatedFrame = "MSBTFrameOutgoing"; + scrollAreas.Notification.AssociatedFrame = "MSBTFrameNotification"; + + -- Setup the display settings. + scrollAreas.Incoming.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Incoming; + scrollAreas.Outgoing.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Outgoing; + scrollAreas.Notification.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Notification; + + + -- Loop through the scroll areas. + for _, scrollArea in scrollAreas do + -- Initialize the next available animation display object. + scrollArea.NextAnimationDisplay = 1; + + -- Initialize the next available sticky animation display object. + scrollArea.FirstSticky = 11; + scrollArea.NextSticky = scrollArea.FirstSticky; + + -- Create arrays for the animation display info. + scrollArea.AnimationDisplayArray = {}; + + -- Associate the appropriate font string object for each animation display object. + for x = 1, NUM_FONT_STRINGS do + scrollArea.AnimationDisplayArray[x] = {}; + scrollArea.AnimationDisplayArray[x].FontObject = getglobal(scrollArea.AssociatedFrame .. "Text" .. x); + scrollArea.AnimationDisplayArray[x].LastUpdated = 0; + end + end +end + + +-- ********************************************************************************** +-- Repositions the animation frames based on the stored offsets. +-- ********************************************************************************** +function MikSBT.RepositionAnimationFrames() + -- Loop through each scroll area and reposition its associated frame. + for _, scrollArea in scrollAreas do + getglobal(scrollArea.AssociatedFrame):SetPoint("BOTTOM", "UIParent", "CENTER", scrollArea.DisplaySettings.FrameOffsets.x, scrollArea.DisplaySettings.FrameOffsets.y); + end +end + + +-- ********************************************************************************** +-- Receive combat events from Mik's combat event helper. +-- ********************************************************************************** +function MikSBT.CombatEventsHandler(combatEvent) + -- Get a recycled table from the events recycler object. + local animationEvent = eventsRecycler:AcquireTable(); + + -- Initalize the event type string to the common prefix. + local eventTypeString = "MSBT_EVENTTYPE_"; + + -- Append the appropriate direction type to the event type string. + if (combatEvent.DirectionType == MikCEH.DIRECTIONTYPE_PLAYER_INCOMING) then + animationEvent.ScrollArea = scrollAreas.Incoming; + eventTypeString = eventTypeString .. "INCOMING_"; + elseif (combatEvent.DirectionType == MikCEH.DIRECTIONTYPE_PLAYER_OUTGOING) then + animationEvent.ScrollArea = scrollAreas.Outgoing; + eventTypeString = eventTypeString .. "OUTGOING_"; + elseif (combatEvent.DirectionType == MikCEH.DIRECTIONTYPE_PET_OUTGOING) then + animationEvent.ScrollArea = scrollAreas.Outgoing; + eventTypeString = eventTypeString .. "OUTGOING_PET_"; + end + + -- Check if it's a damage event. + if (combatEvent.EventType == MikCEH.EVENTTYPE_DAMAGE) then + -- Check if there is an associated spell/ability name. + if (combatEvent.EffectName ~= nil) then + eventTypeString = eventTypeString .. "SPELL_"; + end + + -- Append the appropriate action type to the event type string. + if (combatEvent.ActionType == MikCEH.ACTIONTYPE_HIT) then + if (combatEvent.HitType == MikCEH.HITTYPE_OVER_TIME) then + eventTypeString = eventTypeString .. "DOT"; + else + eventTypeString = eventTypeString .. "DAMAGE"; + end + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_MISS) then + eventTypeString = eventTypeString .. "MISS"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_DODGE) then + eventTypeString = eventTypeString .. "DODGE"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_PARRY) then + eventTypeString = eventTypeString .. "PARRY"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_BLOCK) then + eventTypeString = eventTypeString .. "BLOCK"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_RESIST) then + eventTypeString = eventTypeString .. "RESIST"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_ABSORB) then + eventTypeString = eventTypeString .. "ABSORB"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_IMMUNE) then + eventTypeString = eventTypeString .. "IMMUNE"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_EVADE) then + eventTypeString = eventTypeString .. "EVADE"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_REFLECT) then + eventTypeString = eventTypeString .. "REFLECT"; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_DROWNING) then + eventTypeString = eventTypeString .. "ENVIRONMENTAL" + combatEvent.EffectName = MikSBT.MSG_ENVIRONMENTAL_DROWNING; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_FALLING) then + eventTypeString = eventTypeString .. "ENVIRONMENTAL" + combatEvent.EffectName = MikSBT.MSG_ENVIRONMENTAL_FALLING; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_FATIGUE) then + eventTypeString = eventTypeString .. "ENVIRONMENTAL" + combatEvent.EffectName = MikSBT.MSG_ENVIRONMENTAL_FATIGUE; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_FIRE) then + eventTypeString = eventTypeString .. "ENVIRONMENTAL" + combatEvent.EffectName = MikSBT.MSG_ENVIRONMENTAL_FIRE; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_LAVA) then + eventTypeString = eventTypeString .. "ENVIRONMENTAL" + combatEvent.EffectName = MikSBT.MSG_ENVIRONMENTAL_LAVA; + elseif (combatEvent.ActionType == MikCEH.ACTIONTYPE_SLIME) then + eventTypeString = eventTypeString .. "ENVIRONMENTAL" + combatEvent.EffectName = MikSBT.MSG_ENVIRONMENTAL_SLIME; + end + + -- Heal event. + elseif (combatEvent.EventType == MikCEH.EVENTTYPE_HEAL) then + if (combatEvent.HealType == MikCEH.HEALTYPE_NORMAL or combatEvent.HealType == MikCEH.HEALTYPE_CRIT) then + eventTypeString = eventTypeString .. "HEAL"; + elseif (combatEvent.HealType == MikCEH.HEALTYPE_OVER_TIME) then + eventTypeString = eventTypeString .. "HOT"; + end + + + -- Notification event. + elseif (combatEvent.EventType == MikCEH.EVENTTYPE_NOTIFICATION) then + animationEvent.ScrollArea = scrollAreas.Notification; + eventTypeString = eventTypeString .. "NOTIFICATION_"; + + -- Append the appropriate notification type to the event type string. + if (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_DEBUFF) then + eventTypeString = eventTypeString .. "DEBUFF"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_BUFF) then + eventTypeString = eventTypeString .. "BUFF"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_ITEM_BUFF) then + eventTypeString = eventTypeString .. "ITEM_BUFF"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_BUFF_FADE) then + eventTypeString = eventTypeString .. "BUFF_FADE"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_COMBAT_ENTER) then + eventTypeString = eventTypeString .. "COMBAT_ENTER"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_COMBAT_LEAVE) then + eventTypeString = eventTypeString .. "COMBAT_LEAVE"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_POWER_GAIN) then + eventTypeString = eventTypeString .. "POWER_GAIN"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_POWER_LOSS) then + eventTypeString = eventTypeString .. "POWER_LOSS"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_CP_GAIN) then + -- Check if we are at max combo points. + if (combatEvent.Amount == MAX_COMBO_POINTS) then + eventTypeString = eventTypeString .. "CP_FULL"; + else + eventTypeString = eventTypeString .. "CP_GAIN"; + end + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_HONOR_GAIN) then + eventTypeString = eventTypeString .. "HONOR_GAIN"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_REP_GAIN) then + eventTypeString = eventTypeString .. "REP_GAIN"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_REP_LOSS) then + eventTypeString = eventTypeString .. "REP_LOSS"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_SKILL_GAIN) then + eventTypeString = eventTypeString .. "SKILL_GAIN"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_EXPERIENCE_GAIN) then + eventTypeString = eventTypeString .. "EXPERIENCE_GAIN"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_PC_KILLING_BLOW) then + eventTypeString = eventTypeString .. "PC_KILLING_BLOW"; + elseif (combatEvent.NotificationType == MikCEH.NOTIFICATIONTYPE_NPC_KILLING_BLOW) then + eventTypeString = eventTypeString .. "NPC_KILLING_BLOW"; + elseif (combatEvent.NotificationType ~= nil) then + eventTypeString = eventTypeString .. combatEvent.NotificationType; + end + end + + -- Check if the event is a crit and set the crit field appropriately + if (combatEvent.HitType == MikCEH.HITTYPE_CRIT or combatEvent.HealType == MikCEH.HEALTYPE_CRIT) then + animationEvent.IsCrit = true; + end + + -- Copy the amount, effect name, and player/enemy name from the passed combat event data. + animationEvent.Amount = combatEvent.Amount; + animationEvent.EffectName = combatEvent.EffectName; + animationEvent.Name = combatEvent.Name; + + -- Check if there is a damage type and get the appropriate string. + if (combatEvent.DamageType ~= nil) then + animationEvent.DamageType = MikCEH.GetDamageTypeString(combatEvent.DamageType); + end + + -- If there are partial action types set the partial effect text appropriately. + if (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_ABSORB) then + if (ABSORB_TRAILER) then + animationEvent.PartialEffectText = string.gsub(ABSORB_TRAILER, "%%d", combatEvent.PartialAmount); + end + elseif (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_BLOCK) then + if (BLOCK_TRAILER) then + animationEvent.PartialEffectText = string.gsub(BLOCK_TRAILER, "%%d", combatEvent.PartialAmount); + end + elseif (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_RESIST) then + if (RESIST_TRAILER) then + animationEvent.PartialEffectText = string.gsub(RESIST_TRAILER, "%%d", combatEvent.PartialAmount); + end + elseif (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_VULNERABLE) then + if (MikSBT.MSG_VULERNABLE_TRAILER) then + animationEvent.PartialEffectText = string.gsub(MikSBT.MSG_VULERNABLE_TRAILER, "%%d", combatEvent.PartialAmount); + end + elseif (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_CRUSHING) then + if (CRUSHING_TRAILER) then + animationEvent.PartialEffectText = CRUSHING_TRAILER; + end + elseif (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_GLANCING) then + if (GLANCING_TRAILER) then + animationEvent.PartialEffectText = GLANCING_TRAILER; + end + elseif (combatEvent.PartialActionType == MikCEH.PARTIALACTIONTYPE_OVERHEAL) then + animationEvent.OverhealAmount = combatEvent.PartialAmount; + end + + + + -- Check if there is profile data for the event type. + if (MikSBT.CurrentProfile.EventSettings[eventTypeString]) then + -- Set the event type, the settings for it, its unformatted text and dispatch it to the animation system. + animationEvent.EventType = eventTypeString; + animationEvent.EventSettings = MikSBT.CurrentProfile.EventSettings[animationEvent.EventType]; + animationEvent.UnformattedText = animationEvent.EventSettings.Message; + + -- Dispatch the event to the animation system. + MikSBT.DispatchAnimationEvent(animationEvent); + else + -- Print an error message and reclaim the animation event table to the table recycler. + MikSBT.Print("No profile data found for " .. eventTypeString, 1, 0, 0) + eventsRecycler:ReclaimTable(animationEvent); + end +end + + + +-- ********************************************************************************** +-- Receive trigger events from Mik's trigger helper. +-- ********************************************************************************** +function MikSBT.TriggerHandler(triggerEvent) + -- Get a recycled table from the events recycler object. + local animationEvent = eventsRecycler:AcquireTable(); + + -- Check if there is profile data for the trigger. + if (MikSBT.CurrentProfile.Triggers[triggerEvent.TriggerKey].EventSettings) then + -- Set the scroll area, event settings, and output text. + animationEvent.ScrollArea = scrollAreas.Notification; + animationEvent.EventSettings = MikSBT.CurrentProfile.Triggers[triggerEvent.TriggerKey].EventSettings; + MikSBT.FormatTriggerText(animationEvent, triggerEvent); + + -- Dispatch the event to the animation system. + MikSBT.DispatchAnimationEvent(animationEvent); + else + -- Print an error message and reclaim the animation event table to the table recycler. + MikSBT.Print("No profile data found for trigger key " .. triggerEvent.TriggerKey, 1, 0, 0); + eventsRecycler:ReclaimTable(animationEvent); + end +end + + +------------------------------------------------------------------------------------- +-- Utility functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- This function copies a table. +-- ********************************************************************************** +function MikSBT.CopyTable(srcTable) + -- Create a new table. + local newTable = {}; + + -- Loop through all of the entires in the table. + for key, value in srcTable do + -- Check if it's a nested table. + if (type(value) == "table") then + -- Recursively call the function to copy the nested table. + value = MikSBT.CopyTable(value); + end + + -- Make a copy of the value into the new table. + newTable[key] = value; + end + + -- Return the new table. + return newTable; +end + + +-- ********************************************************************************** +-- Merges like animation events. +-- ********************************************************************************** +function MikSBT.MergeEvents(mergeData, numEvents) + -- Hold whether or not the event was merged. + local eventMerged = false; + + -- Don't attempt to merge any more events than were available when the function was called since + -- more events may get added while the merge is taking place. + for x = 1, numEvents do + + -- Loop through all of the events in the merged events array. + for _, animationEvent in mergeData.MergedEvents do + -- Check if the event types match and the effect names are the same. + if ((mergeData.UnmergedEvents[x].EventType == animationEvent.EventType) and + (mergeData.UnmergedEvents[x].EffectName == animationEvent.EffectName)) then + + -- Check if there is no effect name + if (mergeData.UnmergedEvents[x].EffectName == nil) then + -- Check if the affected unit name is the same. + if ((mergeData.UnmergedEvents[x].Name == animationEvent.Name) and mergeData.UnmergedEvents[x].Name ~= nil) then + -- Do Merge. + eventMerged = true; + end -- Same name. + + -- There is an effect name. + else + -- Check if there is a name and replace it with the multiple targets string. + if (animationEvent.Name ~= nil) then + animationEvent.Name = MikSBT.MSG_MULTIPLE_TARGETS; + end + + -- Do Merge. + eventMerged = true; + end -- Effect name? + end -- Event types match and effect names the same. + + + -- Check if the event should be merged. + if (eventMerged) then + -- Set the event merged flag for the event being merged. + mergeData.UnmergedEvents[x].EventMerged = true; + + -- If the events have an amount then total them. + if (animationEvent.Amount ~= nil and mergeData.UnmergedEvents[x].Amount ~= nil) then + animationEvent.Amount = animationEvent.Amount + mergeData.UnmergedEvents[x].Amount; + end + + -- Check if there is an overheal amount and total it. + if (mergeData.UnmergedEvents[x].OverhealAmount ~= nil) then + animationEvent.OverhealAmount = (animationEvent.OverhealAmount or 0) + mergeData.UnmergedEvents[x].OverhealAmount; + end + + -- Clear any partial effect text. + animationEvent.PartialEffectText = nil; + + + -- Increment the number of merged events. + animationEvent.NumMerged = animationEvent.NumMerged + 1; + + -- Check if the event being merged is a crit and increment the number of crits. + if (mergeData.UnmergedEvents[x].IsCrit) then + animationEvent.NumCrits = animationEvent.NumCrits + 1; + end + + -- Make sure the merged event is not flagged as a crit. + animationEvent.IsCrit = false; + end -- Do Merge. + end -- Loop through all merged events. + + + -- If the event wasn't merged add it to the end of the merged events array. + if (not eventMerged) then + local animationEvent = mergeData.UnmergedEvents[x]; + animationEvent.NumMerged = 0; + + -- Check if the event is a crit and set the number of crits appropriately. + if (animationEvent.IsCrit) then + animationEvent.NumCrits = 1; + else + animationEvent.NumCrits = 0; + end + + -- Add the animation event to the end of the merged events array. + table.insert(mergeData.MergedEvents, animationEvent); + end + + -- Reset the event merged flag. + eventMerged = false; + end + + + -- Loop through each event in the merged events array. + for _, animationEvent in mergeData.MergedEvents do + -- Check if there were any events merged. + if (animationEvent.NumMerged > 0) then + -- Create trailer text with the number of merged events. + local trailerText = " [" .. animationEvent.NumMerged + 1 .. " " .. MikSBT.MSG_HITS; + + -- Check if there were any crits merged. + if (animationEvent.NumCrits > 1) then + trailerText = trailerText .. ", " .. animationEvent.NumCrits .. " " .. MikSBT.MSG_CRITS; + elseif (animationEvent.NumCrits == 1) then + trailerText = trailerText .. ", " .. animationEvent.NumCrits .. " " .. MikSBT.MSG_CRIT; + end + + -- Close the merge string. + trailerText = trailerText .. "]"; + + -- Set the animation event's merge trailer text field. + animationEvent.MergeTrailerText = trailerText; + end + end + + + -- Remove the processed animation events from unmerged events queue. + for x = 1, numEvents do + -- Check if the event was merged into a different animation event. + if (mergeData.UnmergedEvents[1].EventMerged) then + -- Reclaim the animation event table to the events recycler. + eventsRecycler:ReclaimTable(mergeData.UnmergedEvents[1]); + end + + -- Remove the event from the unmerged events array. + table.remove(mergeData.UnmergedEvents, 1); + end +end + + +-- ********************************************************************************** +-- This function gets the appropriate font size for an event. +-- ********************************************************************************** +function MikSBT.GetFontSize(scrollArea, eventSettings, isCrit) + -- Holds the font sizes for the event level. + local eventFontSizeNormal = 0; + local eventFontSizeCrit = 0; + + -- Check if there are event settings. + if (eventSettings ~= nil) then + -- Get the font sizes for the event type. + eventFontSizeNormal = eventSettings.FontSettings.Normal.FontSize; + + -- Check if there are crit font settings for the event. + if (eventSettings.FontSettings.Crit ~= nil) then + eventFontSizeCrit = eventSettings.FontSettings.Crit.FontSize; + end + end + + -- Get the font sizes for the scroll area and master. + local scrollAreaFontSizeNormal = scrollArea.DisplaySettings.FontSettings.Normal.FontSize; + local scrollAreaFontSizeCrit = scrollArea.DisplaySettings.FontSettings.Crit.FontSize; + local masterFontSizeNormal = MikSBT.CurrentProfile.MasterFontSettings.Normal.FontSize; + local masterFontSizeCrit = MikSBT.CurrentProfile.MasterFontSettings.Crit.FontSize; + + + -- Get the normal font size for the event. + local fontSize = eventFontSizeNormal; + + -- Check if the font size is inherited from the scroll area font size. + if (fontSize == 0) then + fontSize = scrollAreaFontSizeNormal; + end + + -- Check if the font size for the scroll area is inherited from the master font size. + if (fontSize == 0) then + fontSize = masterFontSizeNormal; + end + + + -- Check if the event is a crit. + if (isCrit) then + -- Get the crit font size for the event. + fontSize = eventFontSizeCrit; + + -- Check if the font size is inherited from the scroll area font size. + if (fontSize == 0) then + fontSize = scrollAreaFontSizeCrit; + end + + -- Check if the font size for the scroll area is inherited from the master font size. + if (fontSize == 0) then + fontSize = masterFontSizeCrit; + end + end + + -- Return the font size. + return fontSize; +end + + +-- ********************************************************************************** +-- This function gets the appropriate font path for an event. +-- ********************************************************************************** +function MikSBT.GetFontPath(scrollArea, eventSettings, isCrit) + -- Holds the font indices for the event level. + local eventFontIndexNormal = 0; + local eventFontIndexCrit = 0; + + -- Check if there are event settings. + if (eventSettings ~= nil) then + -- Get the font indices for the event type. + eventFontIndexNormal = eventSettings.FontSettings.Normal.FontIndex; + + -- Check if there are crit font settings for the event. + if (eventSettings.FontSettings.Crit ~= nil) then + eventFontIndexCrit = eventSettings.FontSettings.Crit.FontIndex; + end + end + + -- Get the font indices for the scroll area and master. + local scrollAreaFontIndexNormal = scrollArea.DisplaySettings.FontSettings.Normal.FontIndex; + local scrollAreaFontIndexCrit = scrollArea.DisplaySettings.FontSettings.Crit.FontIndex; + local masterFontIndexNormal = MikSBT.CurrentProfile.MasterFontSettings.Normal.FontIndex; + local masterFontIndexCrit = MikSBT.CurrentProfile.MasterFontSettings.Crit.FontIndex; + + + -- Get the normal font index for the event. + local fontIndex = eventFontIndexNormal; + + -- Check if the font index is inherited from the scroll area font index. + if (fontIndex == 0) then + fontIndex = scrollAreaFontIndexNormal; + end + + -- Check if the font index for the scroll area is inherited from the master font index. + if (fontIndex == 0) then + fontIndex = masterFontIndexNormal; + end + + + -- Check if the event is a crit. + if (isCrit) then + -- Get the crit font index for the event. + fontIndex = eventFontIndexCrit; + + -- Check if the font index is inherited from the scroll area font index. + if (fontIndex == 0) then + fontIndex = scrollAreaFontIndexCrit; + end + + -- Check if the font index for the scroll area is inherited from the master font index. + if (fontIndex == 0) then + fontIndex = masterFontIndexCrit; + end + end + + -- Check if the font index is invalid and set it to the default if it is. + if (MikSBT.AVAILABLE_FONTS[fontIndex] == nil) then + fontIndex = 1; + end + + -- Return the font path. + return MikSBT.AVAILABLE_FONTS[fontIndex].Path; +end + + +-- ********************************************************************************** +-- This function gets the appropriate font outline style for an event. +-- ********************************************************************************** +function MikSBT.GetFontOutline(scrollArea, eventSettings, isCrit) + -- Holds the outline indices for the event level. + local eventOutlineIndexNormal = 0; + local eventOutlineIndexCrit = 0; + + -- Check if there are event settings. + if (eventSettings ~= nil) then + -- Get the outline indices for the event type. + eventOutlineIndexNormal = eventSettings.FontSettings.Normal.OutlineIndex; + + -- Check if there are crit font settings for the event. + if (eventSettings.FontSettings.Crit ~= nil) then + eventOutlineIndexCrit = eventSettings.FontSettings.Crit.OutlineIndex; + end + end + + -- Get the outline indices for the scroll area and master. + local scrollAreaOutlineIndexNormal = scrollArea.DisplaySettings.FontSettings.Normal.OutlineIndex; + local scrollAreaOutlineIndexCrit = scrollArea.DisplaySettings.FontSettings.Crit.OutlineIndex; + local masterOutlineIndexNormal = MikSBT.CurrentProfile.MasterFontSettings.Normal.OutlineIndex; + local masterOutlineIndexCrit = MikSBT.CurrentProfile.MasterFontSettings.Crit.OutlineIndex; + + + -- Get the normal outline index for the event. + local outlineIndex = eventOutlineIndexNormal; + + -- Check if the outline index is inherited from the scroll area outline index. + if (outlineIndex == 0) then + outlineIndex = scrollAreaOutlineIndexNormal; + end + + -- Check if the outline index for the scroll area is inherited from the master outline index. + if (outlineIndex == 0) then + outlineIndex = masterOutlineIndexNormal; + end + + + -- Check if the event is a crit. + if (isCrit) then + -- Get the crit outline index for the event. + outlineIndex = eventOutlineIndexCrit; + + -- Check if the outline index is inherited from the scroll area outline index. + if (outlineIndex == 0) then + outlineIndex = scrollAreaOutlineIndexCrit; + end + + -- Check if the outline index for the scroll area is inherited from the master outline index. + if (outlineIndex == 0) then + outlineIndex = masterOutlineIndexCrit; + end + end + + + -- Return the outline style. + return MikSBT.AVAILABLE_OUTLINES[outlineIndex].Style; +end + + +-- ********************************************************************************** +-- This function substitutes the codes in the unformatted text of the passed +-- animation event with the values in its fields. +-- ********************************************************************************** +function MikSBT.FormatEventText(animationEvent) + local outputString = animationEvent.UnformattedText; + + -- Check if there is no unformatted text and bail. + if (outputString == nil) then + return; + end + + -- Substitute amount. + if (animationEvent.Amount ~= nil) then + -- Check if there is overheal info + if (animationEvent.OverhealAmount ~= nil and MikSBT.CurrentProfile.ShowOverheals) then + local overhealTrailer = string.gsub(MikSBT.MSG_OVERHEAL_TRAILER, "%%d", animationEvent.OverhealAmount); + outputString = string.gsub(outputString, "%%a", (animationEvent.Amount - animationEvent.OverhealAmount) .. overhealTrailer); + else + outputString = string.gsub(outputString, "%%a", animationEvent.Amount); + end + end + + -- Substitute enemy/player names. + if (animationEvent.Name ~= nil) then + outputString = string.gsub(outputString, "%%n", animationEvent.Name); + end + + -- Substitute effect names. + if (animationEvent.EffectName ~= nil) then + outputString = string.gsub(outputString, "%%s", animationEvent.EffectName); + end + + -- Substitute damage types. + if (animationEvent.DamageType ~= nil) then + outputString = string.gsub(outputString, "%%t", animationEvent.DamageType); + end + + -- Check if there is partial effect text and the show partial effects flag is on and append it. + if (animationEvent.PartialEffectText ~= nil and MikSBT.CurrentProfile.ShowPartialEffects) then + outputString = outputString .. animationEvent.PartialEffectText; + end + + + -- Check if there is merge trailer text and append it. + if (animationEvent.MergeTrailerText ~= nil) then + outputString = outputString .. animationEvent.MergeTrailerText; + end + + -- Set the event's output text. + animationEvent.Text = outputString; +end + + +-- ********************************************************************************** +-- This function returns the output message for the passed trigger event with all +-- %1-9 codes substituted with captured data. +-- ********************************************************************************** +function MikSBT.FormatTriggerText(animationEvent, triggerEvent) + local outputString = animationEvent.EventSettings.Message; + + -- Check if there is no text and bail. + if (outputString == nil) then + return; + end + + -- Loop through all of the captures in the trigger event. + for i = 1, triggerEvent.NumCaptures do + outputString = string.gsub(outputString , "%%" .. i, triggerEvent["CapturedData" .. i]); + end + + -- Set the event's output text. + animationEvent.Text = outputString; +end + + +-- ********************************************************************************** +-- Registers the triggers in the current profile with combat event lib. +-- ********************************************************************************** +function MikSBT.RegisterTriggers() + -- Unregister all of the triggers from the combat event helper. + MikCEH.UnregisterAllTriggers(); + + -- Loop through all of the triggers in the current profile. + for triggerKey, triggerData in MikSBT.CurrentProfile.Triggers do + -- Make sure the trigger is being shown and register it with the combat event helper. + if (triggerData.EventSettings.Show) then + MikCEH.RegisterTrigger(triggerKey, triggerData.TriggerSettings); + end + end +end + + +------------------------------------------------------------------------------------- +-- Profile functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Updates profiles created with older versions. +-- ********************************************************************************** +function MikSBT.UpdateProfiles() + -- Loop through all the profiles. + for _, profile in MikSBT_Save.Profiles do + -- Check if the profile was created prior to version 2.0. + if (profile.CreationVersion < 2.0) then + -- Update the profile's structure. + profile.ShowOverheals = MikSBT.DEFAULT_CONFIG.ShowOverheals; + profile.ShowGameDamage = MikSBT.DEFAULT_CONFIG.ShowGameDamage; + profile.UseStickyCrits = MikSBT.DEFAULT_CONFIG.UseStickyCrits; + + profile.EventSettings["MSBT_EVENTTYPE_INCOMING_SPELL_DOT"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_INCOMING_SPELL_DOT"]); + profile.EventSettings["MSBT_EVENTTYPE_INCOMING_HOT"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_INCOMING_HOT"]); + profile.EventSettings["MSBT_EVENTTYPE_OUTGOING_SPELL_DOT"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_OUTGOING_SPELL_DOT"]); + profile.EventSettings["MSBT_EVENTTYPE_OUTGOING_HOT"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_OUTGOING_HOT"]); + + -- Update the creation version to the current version. + profile.CreationVersion = 2.0; + end + + + -- Check if the profile was created prior to version 2.1. + if (profile.CreationVersion < 2.1) then + -- Update the profile's structure. + + -- Loop through all of the event settings. + for eventTypeName, eventType in profile.EventSettings do + -- Check for the event types that can't be crits and remove the crit settings table. + if (string.find(eventTypeName, "MISS") or string.find(eventTypeName, "DODGE") or string.find(eventTypeName, "PARRY") or + string.find(eventTypeName, "BLOCK") or string.find(eventTypeName, "ABSORB") or string.find(eventTypeName, "IMMUNE") or + string.find(eventTypeName, "DOT") or string.find(eventTypeName, "RESIST") or string.find(eventTypeName, "HOT") or + string.find(eventTypeName, "ENVIRONMENTAL") or string.find(eventTypeName, "EVADE") or string.find(eventTypeName, "NOTIFICATION")) then + + -- Remove the crit settings table. + eventType.FontSettings.Crit = nil; + end + + -- Check if the event type is a notification and add the IsSticky flag. + if (string.find(eventTypeName, "NOTIFICATION")) then + eventType.IsSticky = MikSBT.DEFAULT_CONFIG.EventSettings[eventTypeName].IsSticky; + end + + -- Add event settings for experience gains. + profile.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_EXPERIENCE_GAIN"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_EXPERIENCE_GAIN"]); + end + + -- Update the creation version to the current version. + profile.CreationVersion = 2.1; + end + + + -- Check if the profile was created prior to version 3.0. + if (profile.CreationVersion < 3.0) then + -- Remove the low health, low mana, and execute notification event settings since they are now triggers. + profile.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_LOW_HEALTH"] = nil; + profile.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_LOW_MANA"] = nil; + profile.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_EXECUTE"] = nil; + + -- Remove the low health and low mana warning percentage info since they are now triggers. + profile.WarningPercentageHealth = nil; + profile.WarningPercentageMana = nil; + + + -- Update the profile's structure with triggers. + profile.Triggers = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.Triggers); + + -- Add event settings for killing blows. + profile.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_PC_KILLING_BLOW"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_PC_KILLING_BLOW"]); + profile.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_NPC_KILLING_BLOW"] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.EventSettings["MSBT_EVENTTYPE_NOTIFICATION_NPC_KILLING_BLOW"]); + + -- Update the profile's structure with suppresions. + profile.Suppressions = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG.Suppressions); + + -- Update the creation version to the current version. + profile.CreationVersion = 3.0; + end + + end +end + + +-- ********************************************************************************** +-- Returns whether or not the passed profile exists. +-- ********************************************************************************** +function MikSBT.ProfileExists(profileName) + local profileFound = false; + + -- Loop through all of the profiles looking for the passed one. + for pName in MikSBT_Save.Profiles do + if (pName == profileName) then + profileFound = true; + end + end + + -- Return whether or not the profile exists. + return profileFound; +end + + +-- ********************************************************************************** +-- Resets the passed profile to its defaults. +-- ********************************************************************************** +function MikSBT.ResetProfile(profileName, showOutput) + -- Get whether or not the profile exists. + local profileExists = MikSBT.ProfileExists(profileName); + + -- Check if the profile exists. + if (profileExists) then + -- Reset the profile. + MikSBT_Save.Profiles[profileName] = MikSBT.CopyTable(MikSBT.DEFAULT_CONFIG); + + -- Check if it's the current profile being reset. + if (profileName == MikSBT_Save.CurrentProfile) then + -- Reset the current profile pointer. + MikSBT.CurrentProfile = MikSBT_Save.Profiles[MikSBT_Save.CurrentProfile]; + + -- Reassociate the display settings if the scroll areas have been setup. + if (scrollAreas.Incoming ~= nil) then + -- Setup the display settings. + scrollAreas.Incoming.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Incoming; + scrollAreas.Outgoing.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Outgoing; + scrollAreas.Notification.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Notification; + end + + -- Reposition the animation frames. + MikSBT.RepositionAnimationFrames(); + + -- Register the triggers with the combat event helper. + MikSBT.RegisterTriggers(); + end + + -- Check if the output text is to be shown. + if (showOutput) then + -- Print the profile reset string. + MikSBT.Print(profileName .. " " .. MikSBT.MSG_PROFILE_RESET, 0, 1, 0); + end + end +end + + +-- ********************************************************************************** +-- Selects the passed profile. +-- ********************************************************************************** +function MikSBT.SelectProfile(profileName) + -- Get whether or not the profile exists. + local profileExists = MikSBT.ProfileExists(profileName); + + -- Check if the profile exists. + if (profileExists) then + -- Set the current profile name to the one being selected. + MikSBT_Save.CurrentProfile = profileName; + + -- Reset the current profile pointer. + MikSBT.CurrentProfile = MikSBT_Save.Profiles[MikSBT_Save.CurrentProfile]; + + -- Reassociate the display settings if the scroll areas have been setup. + if (scrollAreas.Incoming ~= nil) then + -- Setup the display settings. + scrollAreas.Incoming.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Incoming; + scrollAreas.Outgoing.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Outgoing; + scrollAreas.Notification.DisplaySettings = MikSBT.CurrentProfile.DisplaySettings.Notification; + end + + -- Reposition the animation frames. + MikSBT.RepositionAnimationFrames(); + + -- Register the triggers with the combat event helper. + MikSBT.RegisterTriggers(); + end +end + + +-- ********************************************************************************** +-- Creates a profile for the passed name. +-- ********************************************************************************** +function MikSBT.CreateProfile(profileName) + -- Get whether or not the profile exists. + local profileExists = MikSBT.ProfileExists(profileName); + + -- Make sure a profile name was passed and it doesn't already exist. + if (not profileExists and profileName ~= nil and profileName ~= "") then + -- Create an array for the profile. + MikSBT_Save.Profiles[profileName] = {}; + + -- Reset the profile. + MikSBT.ResetProfile(profileName); + end +end + + +-- ********************************************************************************** +-- Deletes the passed profile. +-- ********************************************************************************** +function MikSBT.DeleteProfile(profileName) + -- Check if the passed profile is the default one. + if (profileName == MikSBT.DEFAULT_PROFILE_NAME) then + return; + end + + -- Get whether or not the profile exists. + local profileExists = MikSBT.ProfileExists(profileName); + + -- Check if the profile exists. + if (profileExists) then + -- Check if the profile being deleted is the current one. + if (profileName == MikSBT_Save.CurrentProfile) then + -- Select the default profile. + MikSBT.SelectProfile(MikSBT.DEFAULT_PROFILE_NAME); + end + + -- Delete the profile. + MikSBT_Save.Profiles[profileName] = nil; + end +end + + +------------------------------------------------------------------------------------- +-- Option functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Set the user disabled option +-- ********************************************************************************** +function MikSBT.SetOptionUserDisabled(isDisabled) + MikSBT_Save.UserDisabled = isDisabled; + + -- Check if the mod is being set to disabled. + if (isDisabled) then + -- Unregister all events. + MikSBT.UnregisterEvents(); + MikCEH.UnregisterEvents(); + + -- Turn on the game's display of outgoing damage. + SetCVar("CombatDamage", 1); + + -- Turn on Blizzard's default combat text. + if (SHOW_COMBAT_TEXT ~= nil) then + SHOW_COMBAT_TEXT = 1; + end + else + -- Reregister events. + MikSBT.RegisterEvents(); + MikCEH.RegisterEvents(); + + -- Turn the game damage display on or off depending on the user setting. + MikSBT.SetOptionShowGameDamage(MikSBT.CurrentProfile.ShowGameDamage); + + -- Turn off Blizzard's default combat text. + if (SHOW_COMBAT_TEXT ~= nil) then + SHOW_COMBAT_TEXT = 0; + end + + -- Make sure that periodic spell effects are reported. + SetCVar("CombatLogPeriodicSpells", 1); + end +end + + +-- ********************************************************************************** +-- Set the game damage disabled option +-- ********************************************************************************** +function MikSBT.SetOptionShowGameDamage(showDamage) + MikSBT.CurrentProfile.ShowGameDamage = showDamage; + + -- Check if the game damage is being set to on. + if (showDamage) then + -- Turn on the game damage display. + SetCVar("CombatDamage", 1); + else + -- Turn off the game damage display. + SetCVar("CombatDamage", 0); + end +end + + +-- ********************************************************************************** +-- This function takes a string version of a variable path +-- and returns its value from the current profile. +-- ********************************************************************************** +function MikSBT.GetOptionFromVarPath(varPath) + -- Set the current table to the current profile. + local currentTable = MikSBT.CurrentProfile; + + -- Index into each sub table until we reach the actual variable wanted. + for varName in string.gfind(varPath, "[%w_]+") do + if (type(currentTable[varName]) == "table") then + currentTable = currentTable[varName]; + else + return currentTable[varName]; + end + end + + return nil; +end + + +-- ********************************************************************************** +-- This function takes a string version of a variable path +-- and sets its value in the current profile to the passed value. +-- ********************************************************************************** +function MikSBT.SetOptionFromVarPath(varPath, newValue) + -- Sets the current table to the current profile. + local currentTable = MikSBT.CurrentProfile; + + -- Index into each sub table until we reach the actual variable wanted and set it + -- to the passed value. + for varName in string.gfind(varPath, "[%w_]+") do + if (type(currentTable[varName]) == "table") then + currentTable = currentTable[varName] + else + currentTable[varName] = newValue; + end + end +end + + +------------------------------------------------------------------------------------- +-- Animation functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Adds the passed animation event to the animation system for the appropriate scroll +-- area. +-- +-- animationEvent Possible Fields: +-- ScrollArea - The scroll area to use for the event. +-- EventType - The event type. +-- EventSettings - The event settings for the event. +-- IsCrit - Whether or not the event is a crit. +-- UnformattedText - The unformatted text to display. +-- Amount - The amount of the attack, heal, gain, loss, etc. +-- EffectName - The name of the spell, ability, buff, debuff, power type, etc. +-- Name - The name of the enemy/player. +-- DamageType - The type of damage. +-- PartialEffectText - Partial effect text to be appended. (partial resists, absorbs, blocks) +-- OverhealAmount - The amount overhealed. +-- Text - The formatted text to display. +-- ********************************************************************************** +function MikSBT.DispatchAnimationEvent(animationEvent) + -- Check if the event is to be shown. + if (animationEvent.EventSettings.Show and animationEvent.ScrollArea.DisplaySettings.Show) then + + -- If it's an incoming or outgoing event add it to the associated scroll area's events array + -- for potential merging. + if (animationEvent.ScrollArea == scrollAreas.Incoming) then + table.insert(animationMergeData.Incoming.UnmergedEvents, animationEvent); + elseif (animationEvent.ScrollArea == scrollAreas.Outgoing) then + table.insert(animationMergeData.Outgoing.UnmergedEvents, animationEvent); + + -- Add the event the animation system. + else + MikSBT.AddAnimation(animationEvent); + end + end +end + + +-- ********************************************************************************** +-- Add the event to the appropriate scroll area's animation display array. +-- ********************************************************************************** +function MikSBT.AddAnimation(animationEvent) + -- Format the animation event's output text. + MikSBT.FormatEventText(animationEvent); + + -- Loop through all of the suppression entries and look for a match. + for _, suppressionSettings in MikSBT.CurrentProfile.Suppressions do + -- Check if the suppression's search pattern is a match. + if (suppressionSettings.Enabled and string.find(animationEvent.Text, suppressionSettings.SearchPattern)) then + -- Reclaim the animation event table to the events recycler and leave the function. + eventsRecycler:ReclaimTable(animationEvent); + return; + end + end + + + -- Check if the animation event is to be displayed sticky style. + if (animationEvent.EventSettings ~= nil and animationEvent.EventSettings.IsSticky) then + animationEvent.IsSticky = true; + end + + + -- Get the next available animation display info object for the scroll area. + local animDisplayInfo = MikSBT.GetNextAnimationDisplayInfo(animationEvent); + + -- Get the scroll function to use. + animDisplayInfo.ScrollFunction = MikSBT.GetScrollFunction(animationEvent); + + -- Get the starting X and Y positions. + animDisplayInfo.PositionX, animDisplayInfo.PositionY = MikSBT.GetAnimationStartingPosition(animationEvent) + + + -- Check if the animation event has event settings associated with it. + if (animationEvent.EventSettings ~= nil) then + -- Get the font size for the event. + animDisplayInfo.FontSize = MikSBT.GetFontSize(animationEvent.ScrollArea, animationEvent.EventSettings, animationEvent.IsCrit); + + -- Get the font path for the event. + animDisplayInfo.FontPath = MikSBT.GetFontPath(animationEvent.ScrollArea, animationEvent.EventSettings, animationEvent.IsCrit); + + -- Get the font outline style for the event. + animDisplayInfo.FontOutline = MikSBT.GetFontOutline(animationEvent.ScrollArea, animationEvent.EventSettings, animationEvent.IsCrit); + + -- Set the r, g, and b color for the animation display object. + animDisplayInfo.ColorR = animationEvent.EventSettings.FontSettings.Color.r; + animDisplayInfo.ColorG = animationEvent.EventSettings.FontSettings.Color.g; + animDisplayInfo.ColorB = animationEvent.EventSettings.FontSettings.Color.b; + + -- No event type associated with the event so get the display info from the animation event itself. + else + -- Get the font size for the event. + animDisplayInfo.FontSize = animationEvent.FontSize; + + -- Get the font path for the event. + animDisplayInfo.FontPath = animationEvent.FontPath; + + -- Get the font outline style for the event. + animDisplayInfo.FontOutline = animationEvent.FontOutline; + + -- Set the r, g, and b color for the animation display object. + animDisplayInfo.ColorR = animationEvent.ColorR; + animDisplayInfo.ColorG = animationEvent.ColorG; + animDisplayInfo.ColorB = animationEvent.ColorB; + end + + + -- Set the animation display alpha, anchor point, crit flag, and sticky flag. + animDisplayInfo.Alpha = 0; + animDisplayInfo.AnchorPoint = MikSBT.AVAILABLE_TEXT_ALIGNS[animationEvent.ScrollArea.DisplaySettings.FontSettings.TextAlignIndex].AnchorPoint; + animDisplayInfo.IsCrit = animationEvent.IsCrit; + animDisplayInfo.IsSticky = animationEvent.IsSticky; + + -- Set the font object properties. + animDisplayInfo.FontObject:ClearAllPoints(); + animDisplayInfo.FontObject:SetFont(animDisplayInfo.FontPath, animDisplayInfo.FontSize, animDisplayInfo.FontOutline); + animDisplayInfo.FontObject:SetTextColor(animDisplayInfo.ColorR, animDisplayInfo.ColorG, animDisplayInfo.ColorB); + animDisplayInfo.FontObject:SetText(animationEvent.Text); + animDisplayInfo.FontObject:SetAlpha(animDisplayInfo.Alpha); + animDisplayInfo.FontObject:SetPoint(animDisplayInfo.AnchorPoint, animDisplayInfo.PositionX, animDisplayInfo.PositionY); + + + -- Set the last update to the current time. + animDisplayInfo.LastUpdated = GetTime(); + + -- Set the active flag. + animDisplayInfo.IsActive = true; + + + -- Check if the scroll area's display frame is not visible and make it visible so the OnUpdate events start firing. + -- This is done to keep the number of OnUpdate events down to a minimum for better performance. + if (not getglobal(animationEvent.ScrollArea.AssociatedFrame):IsVisible()) then + getglobal(animationEvent.ScrollArea.AssociatedFrame):Show(); + end + + -- Check if it's a sticky or a crit and sticky crits are enabled. + if (animDisplayInfo.IsSticky or (animDisplayInfo.IsCrit and MikSBT.CurrentProfile.UseStickyCrits)) then + -- Make sure the stickies don't overlap. + MikSBT.RepositionStickyAnimDisplayInfo(animationEvent.ScrollArea); + else + -- Make sure there are no overlaps in the scrolling text. + MikSBT.RepositionAnimDisplayInfo(animationEvent.ScrollArea); + end + + -- Reclaim the animation event table to the events recycler. + eventsRecycler:ReclaimTable(animationEvent); +end + + +-- ********************************************************************************** +-- This function gets the next animation display info object for the correct +-- scroll area. +-- ********************************************************************************** +function MikSBT.GetNextAnimationDisplayInfo(animationEvent) + -- Holds the next animation display info object. + local animDisplayInfo; + + -- Check if it's a sticky or it's a crit and sticky crits are enabled. + if (animationEvent.IsSticky or (animationEvent.IsCrit and MikSBT.CurrentProfile.UseStickyCrits)) then + -- Get the next available animation display object for stickies. + -- This is done so that stickies use the last set of font strings to avoid normal hits + -- overlapping them. + animDisplayInfo = animationEvent.ScrollArea.AnimationDisplayArray[animationEvent.ScrollArea.NextSticky]; + + -- Increment to the next sticky animation display object. + animationEvent.ScrollArea.NextSticky = animationEvent.ScrollArea.NextSticky + 1; + + -- Loop back around to the first sticky animation display object. + if (animationEvent.ScrollArea.NextSticky > NUM_FONT_STRINGS) then + animationEvent.ScrollArea.NextSticky = animationEvent.ScrollArea.FirstSticky; + end + + -- Not a sticky. + else + -- Get the next available animation display object. + animDisplayInfo = animationEvent.ScrollArea.AnimationDisplayArray[animationEvent.ScrollArea.NextAnimationDisplay]; + + -- Increment to the next animation display object. + animationEvent.ScrollArea.NextAnimationDisplay = animationEvent.ScrollArea.NextAnimationDisplay + 1; + + -- Loop back around to the first animation display object. + if (animationEvent.ScrollArea.NextAnimationDisplay > (animationEvent.ScrollArea.FirstSticky - 1)) then + animationEvent.ScrollArea.NextAnimationDisplay = 1; + end + end + + -- Return the animation display info object. + return animDisplayInfo; +end + + +-- ********************************************************************************** +-- This function gets the scroll function to use for the event. +-- ********************************************************************************** +function MikSBT.GetScrollFunction(animationEvent) + -- Straight animation style. + if (animationEvent.ScrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_STRAIGHT) then + -- Text is scrolling down. + if (animationEvent.ScrollArea.DisplaySettings.ScrollDirection == SCROLL_DIRECTION_DOWN) then + -- Return the appropriate animation function. + return MikSBT.ScrollStraightDown; + -- Text is scrolling up. + else + -- Return the appropriate animation function. + return MikSBT.ScrollStraightUp; + end + + -- Left parabola animation style. + elseif (animationEvent.ScrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_LEFT_PARABOLA) then + -- Text is scrolling down. + if (animationEvent.ScrollArea.DisplaySettings.ScrollDirection == SCROLL_DIRECTION_DOWN) then + -- Return the appropriate animation function. + return MikSBT.ScrollLeftParabolaDown; + -- Text is scrolling up. + else + -- Return the appropriate animation function. + return MikSBT.ScrollLeftParabolaUp; + end + + -- Right parabola animation style. + elseif (animationEvent.ScrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_RIGHT_PARABOLA) then + -- Text is scrolling down. + if (animationEvent.ScrollArea.DisplaySettings.ScrollDirection == SCROLL_DIRECTION_DOWN) then + -- Return the appropriate animation function. + return MikSBT.ScrollRightParabolaDown; + -- Text is scrolling up. + else + -- Return the appropriate animation function. + return MikSBT.ScrollRightParabolaUp; + end + + end + + -- Default to the straight up animation style although the function should never get + -- to this point. + return MikSBT.ScrollStraightUp; +end + + +-- ********************************************************************************** +-- This function gets the starting animation x and and y position. +-- ********************************************************************************** +function MikSBT.GetAnimationStartingPosition(animationEvent) + -- Holds where to start the animation at. + local startPositionX, startPositionY; + + -- Check which animation style is selected for the scroll area. + if (animationEvent.ScrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_STRAIGHT or + animationEvent.ScrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_LEFT_PARABOLA or + animationEvent.ScrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_RIGHT_PARABOLA) then + + -- Check if the text is scrolling down. + if (animationEvent.ScrollArea.DisplaySettings.ScrollDirection == SCROLL_DIRECTION_DOWN) then + -- Set the starting X and Y positions. + startPositionX = 0; + startPositionY = animationEvent.ScrollArea.DisplaySettings.ScrollHeight; + -- Text is scrolling up. + else + -- Set the starting X and Y positions. + startPositionX = 0; + startPositionY = 0; + end + + -- Check if it's a sticky or a crit and sticky crits are enabled. + if (animationEvent.IsSticky or (animationEvent.IsCrit and MikSBT.CurrentProfile.UseStickyCrits)) then + -- Set the sticky to the center. + startPositionY = ceil(animationEvent.ScrollArea.DisplaySettings.ScrollHeight / 2); + end + end + + -- Return the x and y starting positions. + return startPositionX, startPositionY; +end + + +-- ********************************************************************************** +-- This function makes sure there is no overlapping text. +-- ********************************************************************************** +function MikSBT.RepositionAnimDisplayInfo(scrollArea) + -- Construct a table with the non sticky animation display info in the same order the events were added. + for x = scrollArea.NextAnimationDisplay, scrollArea.FirstSticky-1 do + local animDisplayInfo = scrollArea.AnimationDisplayArray[x]; + if (animDisplayInfo.IsActive) then + table.insert(activeNonStickies, animDisplayInfo); + end + end + + for x = 1, scrollArea.NextAnimationDisplay-1 do + local animDisplayInfo = scrollArea.AnimationDisplayArray[x]; + if (animDisplayInfo.IsActive) then + table.insert(activeNonStickies, animDisplayInfo); + end + end + + -- Get the number of active non sticky data objects. + local numNonStickies = table.getn(activeNonStickies); + + -- Animation style straight, left parabola, or right parabola. + if (scrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_STRAIGHT or + scrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_LEFT_PARABOLA or + scrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_RIGHT_PARABOLA) then + + -- Scrolling down. + if (scrollArea.DisplaySettings.ScrollDirection == SCROLL_DIRECTION_DOWN) then + + -- Check if there is more than 1 active non sticky animation. + if (numNonStickies > 1) then + -- Loop backwards through the active non sticky animation display objects and move the ones that are colliding. + for x = numNonStickies, 2, -1 do + local bottomPointCurrent = activeNonStickies[x].PositionY; + local topPointNext = activeNonStickies[x-1].PositionY + activeNonStickies[x-1].FontSize + MIN_VERTICAL_SPACING; + if (topPointNext > bottomPointCurrent) then + activeNonStickies[x-1].PositionY = bottomPointCurrent - activeNonStickies[x-1].FontSize - MIN_VERTICAL_SPACING; + end + end + end + + -- Scrolling up. + else + + -- Check if there is more than 1 active non sticky animation. + if (numNonStickies > 1) then + -- Loop backwards through the active non sticky animation display objects and move the ones that are colliding. + for x = numNonStickies, 2, -1 do + local topPointCurrent = activeNonStickies[x].PositionY + activeNonStickies[x].FontSize + MIN_VERTICAL_SPACING; + local bottomPointNext = activeNonStickies[x-1].PositionY; + if (bottomPointNext < topPointCurrent) then + activeNonStickies[x-1].PositionY = topPointCurrent; + end + end + end + + end -- Scrolling Direction + end -- Animation Style + + -- Clear the temp non stickies table. + for x = 1, numNonStickies do + table.remove(activeNonStickies, 1); + end +end + + +-- ********************************************************************************** +-- This function makes sure stickies don't overlap. +-- ********************************************************************************** +function MikSBT.RepositionStickyAnimDisplayInfo(scrollArea) + -- Construct a table with the sticky animation display info in the same order the events were added. + for x = scrollArea.NextSticky, NUM_FONT_STRINGS do + local animDisplayInfo = scrollArea.AnimationDisplayArray[x]; + if (animDisplayInfo.IsActive) then + table.insert(activeStickies, animDisplayInfo); + end + end + + for x = scrollArea.FirstSticky, scrollArea.NextSticky-1 do + local animDisplayInfo = scrollArea.AnimationDisplayArray[x]; + if (animDisplayInfo.IsActive) then + table.insert(activeStickies, animDisplayInfo); + end + end + + -- Get the number of active stickies. + local numStickies = table.getn(activeStickies); + + + -- Animation style straight, left parabola, or right parabola. + if (scrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_STRAIGHT or + scrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_LEFT_PARABOLA or + scrollArea.DisplaySettings.AnimationStyle == ANIMATION_STYLE_RIGHT_PARABOLA) then + + -- Scrolling down. + if (scrollArea.DisplaySettings.ScrollDirection == SCROLL_DIRECTION_DOWN) then + + -- Check if there is more than 1 active sticky animation. + if (numStickies > 1) then + -- Get the middle active sticky. + local middleSticky = floor((numStickies + 1) / 2); + + -- Set the middle sticky to the center of the scroll area. + activeStickies[middleSticky].PositionY = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Loop backwards from the middle sticky. + for x = middleSticky - 1, 1, -1 do + activeStickies[x].PositionY = activeStickies[x+1].PositionY - activeStickies[x].FontSize - MIN_VERTICAL_SPACING; + end + + -- Loop forwards from the middle sticky. + for x = middleSticky + 1, numStickies do + activeStickies[x].PositionY = activeStickies[x-1].PositionY + activeStickies[x-1].FontSize + MIN_VERTICAL_SPACING; + end + end + + -- Scrolling up. + else + + -- Check if there is more than 1 active sticky animation. + if (numStickies > 1) then + -- Get the middle active sticky. + local middleSticky = ceil((numStickies + 1) / 2); + + -- Set the middle sticky to the center of the scroll area. + activeStickies[middleSticky].PositionY = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Loop backwards from the middle sticky. + for x = middleSticky - 1, 1, -1 do + activeStickies[x].PositionY = activeStickies[x+1].PositionY + activeStickies[x+1].FontSize + MIN_VERTICAL_SPACING; + end + + -- Loop forwards from the middle sticky. + for x = middleSticky + 1, numStickies do + activeStickies[x].PositionY = activeStickies[x-1].PositionY - activeStickies[x].FontSize - MIN_VERTICAL_SPACING; + end + end + + end -- Scrolling Direction + end -- Animation Style + + -- Clear the temp stickies table. + for x = 1, numStickies do + table.remove(activeStickies, 1); + end +end + + +-- ********************************************************************************** +-- Animates the passed animation display data. +-- ********************************************************************************** +function MikSBT.DoAnimation(animDisplayInfo, scrollArea) + + -- Get the current and elapsed times since the last update. + local currentTime = GetTime(); + local elapsedTime = currentTime - animDisplayInfo.LastUpdated; + + -- Check if it's a sticky or a crit and sticky crits are enabled. + if (animDisplayInfo.IsSticky or (animDisplayInfo.IsCrit and MikSBT.CurrentProfile.UseStickyCrits)) then + -- Move the sticky in case it was repositioned. + animDisplayInfo.FontObject:SetPoint(animDisplayInfo.AnchorPoint, animDisplayInfo.PositionX, animDisplayInfo.PositionY); + + if (elapsedTime <= STICKY_FADE_IN_TIME) then + local textDelta = animDisplayInfo.FontSize * (1 - (elapsedTime / STICKY_FADE_IN_TIME)); + animDisplayInfo.FontObject:SetTextHeight(animDisplayInfo.FontSize + textDelta); + animDisplayInfo.FontObject:SetAlpha(1); + return; + elseif (elapsedTime <= (STICKY_DISPLAY_TIME + STICKY_FADE_IN_TIME)) then + animDisplayInfo.FontObject:SetTextHeight(animDisplayInfo.FontSize); + return; + elseif (elapsedTime <= (STICKY_DISPLAY_TIME + STICKY_FADE_IN_TIME + STICKY_FADE_OUT_TIME)) then + local alpha = 1 - ((elapsedTime - STICKY_DISPLAY_TIME - STICKY_FADE_IN_TIME) / STICKY_FADE_OUT_TIME); + animDisplayInfo.FontObject:SetAlpha(alpha); + return; + end + + -- Deactivate the animation. + animDisplayInfo.FontObject:SetAlpha(0); + animDisplayInfo.IsActive = false; + + return; + end + + + -- Check if it's time to update. + if (elapsedTime > ANIMATION_SPEED) then + -- Scroll the text and set its alpha appropriately. + local animationComplete = animDisplayInfo.ScrollFunction(animDisplayInfo, scrollArea); + + + -- Check if the animation is complete. + if (animationComplete) then + -- Set the font object's alpha. + animDisplayInfo.FontObject:SetAlpha(0); + + -- Deactive the animation. + animDisplayInfo.IsActive = false; + else + -- Move the font object and set its alpha. + animDisplayInfo.FontObject:SetPoint(animDisplayInfo.AnchorPoint, animDisplayInfo.PositionX, animDisplayInfo.PositionY); + animDisplayInfo.FontObject:SetAlpha(animDisplayInfo.Alpha); + end + + -- Set the last update to the current time. + animDisplayInfo.LastUpdated = currentTime; + end -- Time for update. +end + + +-- ********************************************************************************** +-- Moves the passed animation display info upward and fades the alpha correctly. +-- Returns true when the animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollUp(animDisplayInfo, scrollArea) + -- Calculate the animation midpoint. + local animationMidPoint = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Increment the Y position by the step. + animDisplayInfo.PositionY = animDisplayInfo.PositionY + MikSBT.CurrentProfile.AnimationStep; + + -- Start fading out the text on the upper half. + if (animDisplayInfo.PositionY >= animationMidPoint) then + animDisplayInfo.Alpha = 1 - ((animDisplayInfo.PositionY - animationMidPoint) / animationMidPoint); + else + animDisplayInfo.Alpha = 1; + end + + -- Check if the animation is complete. + if (animDisplayInfo.PositionY >= scrollArea.DisplaySettings.ScrollHeight) then + -- Return true to indicated the animation is complete. + return true; + end + + -- Return false to indicate the animation is NOT complete. + return false; +end + + +-- ********************************************************************************** +-- Moves the passed animation display info downward and fades the alpha correctly. +-- Returns true when the animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollDown(animDisplayInfo, scrollArea) + -- Calculate the animation midpoint. + local animationMidPoint = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Decrement the Y position by the step. + animDisplayInfo.PositionY = animDisplayInfo.PositionY - MikSBT.CurrentProfile.AnimationStep; + + -- Start fading out the text on the bottom half. + if (animDisplayInfo.PositionY < animationMidPoint) then + animDisplayInfo.Alpha = 1 - ((animationMidPoint - animDisplayInfo.PositionY) / animationMidPoint); + else + animDisplayInfo.Alpha = 1; + end + + -- Check if the animation is complete. + if (animDisplayInfo.PositionY <= 0) then + -- Return true to indicated the animation is complete. + return true; + end + + -- Return false to indicate the animation is NOT complete. + return false; +end + + +-- ********************************************************************************** +-- Moves the passed animation display info straight up. Returns true when the +-- animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollStraightUp(animDisplayInfo, scrollArea) + -- Returns the result of the scroll up function. + return MikSBT.ScrollUp(animDisplayInfo, scrollArea); +end + + +-- ********************************************************************************** +-- Moves the passed animation display info straight down. Returns true when the +-- animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollStraightDown(animDisplayInfo, scrollArea) + -- Returns the result of the scroll down function. + return MikSBT.ScrollDown(animDisplayInfo, scrollArea); +end + + +-- ********************************************************************************** +-- Moves the passed animation display info in a left parabolic shape going upwards. +-- Returns true when the animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollLeftParabolaUp(animDisplayInfo, scrollArea) + -- Scroll the animation display info straight up and set the alpha correctly. + local animationComplete = MikSBT.ScrollUp(animDisplayInfo, scrollArea); + + -- Calculate the animation midpoint. + local animationMidPoint = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Calculate the new x position based on equation of a parabola. + -- Equation of a parabola at vertex 0,0: x = y^2 / 4a + local parabolaMaxX = ceil((animationMidPoint * animationMidPoint) / (4 * animationMidPoint)); + local parabolaY = animDisplayInfo.PositionY - animationMidPoint; + local newX = parabolaMaxX - ceil((parabolaY * parabolaY) / (4 * animationMidPoint)); + + -- Set the new x position. + animDisplayInfo.PositionX = newX * -1; + + -- Return whether or not the animation is complete. + return animationComplete; +end + + +-- ********************************************************************************** +-- Moves the passed animation display info in a left parabolic shape going downwards. +-- Returns true when the animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollLeftParabolaDown(animDisplayInfo, scrollArea) + -- Scroll the animation display info straight up and set the alpha correctly. + local animationComplete = MikSBT.ScrollDown(animDisplayInfo, scrollArea); + + -- Calculate the animation midpoint. + local animationMidPoint = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Calculate the new x position based on equation of a parabola. + -- Equation of a parabola at vertex 0,0: x = y^2 / 4a + local parabolaMaxX = ceil((animationMidPoint * animationMidPoint) / (4 * animationMidPoint)); + local parabolaY = animDisplayInfo.PositionY - animationMidPoint; + local newX = parabolaMaxX - ceil((parabolaY * parabolaY) / (4 * animationMidPoint)); + + -- Set the new x position. + animDisplayInfo.PositionX = newX * -1; + + -- Return whether or not the animation is complete. + return animationComplete; +end + + +-- ********************************************************************************** +-- Moves the passed animation display info in a right parabolic shape going upwards. +-- Returns true when the animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollRightParabolaUp(animDisplayInfo, scrollArea) + -- Scroll the animation display info straight up and set the alpha correctly. + local animationComplete = MikSBT.ScrollUp(animDisplayInfo, scrollArea); + + -- Calculate the animation midpoint. + local animationMidPoint = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Calculate the new x position based on equation of a parabola. + -- Equation of a parabola at vertex 0,0: x = y^2 / 4a + local parabolaMaxX = ceil((animationMidPoint * animationMidPoint) / (4 * animationMidPoint)); + local parabolaY = animDisplayInfo.PositionY - animationMidPoint; + local newX = parabolaMaxX - ceil((parabolaY * parabolaY) / (4 * animationMidPoint)); + + -- Set the new x position. + animDisplayInfo.PositionX = newX; + + -- Return whether or not the animation is complete. + return animationComplete; +end + + +-- ********************************************************************************** +-- Moves the passed animation display info in a right parabolic shape going downwards. +-- Returns true when the animation is complete. +-- ********************************************************************************** +function MikSBT.ScrollRightParabolaDown(animDisplayInfo, scrollArea) + -- Scroll the animation display info straight up and set the alpha correctly. + local animationComplete = MikSBT.ScrollDown(animDisplayInfo, scrollArea); + + -- Calculate the animation midpoint. + local animationMidPoint = scrollArea.DisplaySettings.ScrollHeight / 2; + + -- Calculate the new x position based on equation of a parabola. + -- Equation of a parabola at vertex 0,0: x = y^2 / 4a + local parabolaMaxX = ceil((animationMidPoint * animationMidPoint) / (4 * animationMidPoint)); + local parabolaY = animDisplayInfo.PositionY - animationMidPoint; + local newX = parabolaMaxX - ceil((parabolaY * parabolaY) / (4 * animationMidPoint)); + + -- Set the new x position. + animDisplayInfo.PositionX = newX; + + -- Return whether or not the animation is complete. + return animationComplete; +end + + +------------------------------------------------------------------------------------- +-- Command handler functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Returns the current and remaining parameters from the passed string. +-- ********************************************************************************** +function MikSBT.GetNextParameter(paramString) + local remainingParams; + local currentParam = paramString; + + -- Look for a space. + local idx = string.find(paramString, " "); + + if (idx) then + -- Get the current and remaing parameters. + currentParam = string.sub(paramString, 1, idx-1); + remainingParams = string.sub(paramString, idx+1); + end + + -- Return the current parameter and the remaining ones. + return currentParam, remainingParams; +end + + +-- ********************************************************************************** +-- Called to handle commands. +-- ********************************************************************************** +function MikSBT.CommandHandler(params) + -- Get the parameter. + local currentParam; + local remainingParams; + currentParam, remainingParams = MikSBT.GetNextParameter(params); + + -- Flag for whether or not to show usage info. + local showUsage = true; + + -- Make sure there is a current parameter and lower case it. + if (currentParam) then + currentParam = string.lower(currentParam); + end + + -- Look for the recognized parameters. + if (currentParam == "") then + -- Check if the load on demand options are not loaded. + if (not IsAddOnLoaded(MikSBT.MOD_NAME .. "Options")) then + -- Load the on demand options. + UIParentLoadAddOn(MikSBT.MOD_NAME .. "Options"); + end + + -- Make sure the on demand options actually loaded. + if (IsAddOnLoaded(MikSBT.MOD_NAME .. "Options")) then + -- Make sure the scroll area mover control frame is not shown. + if (not MSBTScrollAreaMoverControlFrame:IsShown()) then + -- Show the options interface. + MSBTFrameOptions:Show(); + end + end + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_RESET) then + -- Reset the current profile. + MikSBT.ResetProfile(MikSBT_Save.CurrentProfile, true); + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_DISABLE) then + -- Set the user disabled option. + MikSBT.SetOptionUserDisabled(true); + + -- Output an informative message. + MikSBT.Print(MikSBT.MSG_DISABLE, 1,1,1); + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_ENABLE) then + -- Unset the user disabled option. + MikSBT.SetOptionUserDisabled(false); + + -- Output an informative message. + MikSBT.Print(MikSBT.MSG_ENABLE, 1,1,1); + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_SHOWVER) then + -- Output the current version number. + MikSBT.Print(MikSBT.VERSION_STRING, 1,1,1); + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_STATS) then + -- Print out stats about the event recycler. + eventsRecycler:PrintStats(); + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_SEARCH) then + -- Check if there is any filter text + if (remainingParams and remainingParams ~= "") then + -- Turn on event searching mode. + MikCEH.EnableEventSearching(remainingParams); + MikSBT.Print(MikSBT.MSG_SEARCH_ENABLE .. remainingParams, 1, 1, 1); + else + -- Turn off event searching mode. + MikCEH.DisableEventSearching(); + MikSBT.Print(MikSBT.MSG_SEARCH_DISABLE, 1, 1, 1); + end + + -- Don't show the usage info. + showUsage = false; + + elseif (currentParam == MikSBT.COMMAND_DEBUG) then + -- Toggle the debug mode option. + debugMode = not debugMode; + + -- Check if debug mode was enabled or disabled and output the appropriate message. + if (debugMode) then + MikSBT.Print(MikSBT.MSG_DEBUG_ENABLE, 1,1,1); + else + MikSBT.Print(MikSBT.MSG_DEBUG_DISABLE, 1,1,1); + end + + -- Don't show the usage info. + showUsage = false; + + end + + -- Check if the usage information should be shown. + if (showUsage) then + MikSBT.PrintUsage(); + end +end + + +-- ********************************************************************************** +-- Prints out the usage information. +-- ********************************************************************************** +function MikSBT.PrintUsage() + -- Loop through all of the entries in the command usage list. + for _, msg in MikSBT.COMMAND_USAGE do + MikSBT.Print(msg, 1, 1, 1); + end +end + + + +------------------------------------------------------------------------------------- +-- Public Access functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Displays the passed message in the passed scroll area with the passed parameters. +-- This function is for easy displaying of messages from external sources. +-- ********************************************************************************** +function MikSBT.DisplayMessage(message, displayType, isSticky, colorR, colorG, colorB, fontSize, fontIndex, outlineIndex) + -- Check if no message was passed and do nothing. + if (message == nil) then + return; + end + + -- Check if the passed display type is invalid.. + if (displayType ~= MikSBT.DISPLAYTYPE_INCOMING and displayType ~= MikSBT.DISPLAYTYPE_OUTGOING and + displayType ~= MikSBT.DISPLAYTYPE_NOTIFICATION) then + + -- Set the display type to the default. + displayType = MikSBT.DISPLAYTYPE_NOTIFICATION; + end + + -- Get the correct scroll area for the event. + local scrollArea; + if (displayType == MikSBT.DISPLAYTYPE_INCOMING) then + scrollArea = scrollAreas.Incoming; + elseif (displayType == MikSBT.DISPLAYTYPE_OUTGOING) then + scrollArea = scrollAreas.Outgoing; + elseif (displayType == MikSBT.DISPLAYTYPE_NOTIFICATION) then + scrollArea = scrollAreas.Notification; + end + + -- Check if the scroll area is hiding events. + if (not scrollArea.DisplaySettings.Show) then + return; + end + + -- Check if the red color value is invalid. + if (colorR == nil or colorR < 0 or colorR > 255) then + -- Set the red color value to the default. + colorR = 255; + end + + -- Check if the green color value is invalid. + if (colorG == nil or colorG < 0 or colorG > 255) then + -- Set the green color value to the default. + colorG = 255; + end + + -- Check if the blue color value is invalid. + if (colorB == nil or colorB < 0 or colorB > 255) then + -- Set the blue color value to the default. + colorB = 255; + end + + -- Check if the font size is invalid. + if (fontSize == nil or fontSize < 12 or fontSize > 32) then + fontSize = MikSBT.GetFontSize(scrollArea, nil, false); + end + + local fontPath; + -- Check if the font index in invalid. + if (fontIndex == nil or MikSBT.AVAILABLE_FONTS[fontIndex] == nil) then + fontPath = MikSBT.GetFontPath(scrollArea, nil, false); + -- Font index is valid. + else + fontPath = MikSBT.AVAILABLE_FONTS[fontIndex].Path; + end + + local fontOutline; + -- Check if the outline index in invalid. + if (outlineIndex == nil or MikSBT.AVAILABLE_OUTLINES[outlineIndex] == nil) then + fontOutline = MikSBT.GetFontOutline(scrollArea, nil, false); + else + fontOutline = MikSBT.AVAILABLE_OUTLINES[outlineIndex].Style; + end + + + -- Get a recycled table from the events recycler object. + local animationEvent = eventsRecycler:AcquireTable(); + + -- Populate the animation event data. + animationEvent.ScrollArea = scrollArea; + animationEvent.IsCrit = false; + animationEvent.IsSticky = isSticky; + animationEvent.Text = message; + animationEvent.FontSize = fontSize; + animationEvent.FontPath = fontPath; + animationEvent.FontOutline = fontOutline; + animationEvent.ColorR = colorR; + animationEvent.ColorG = colorG; + animationEvent.ColorB = colorB; + + + -- Add the event to the animation system. + MikSBT.AddAnimation(animationEvent); +end \ No newline at end of file diff --git a/MikScrollingBattleText/MikScrollingBattleText.toc b/MikScrollingBattleText/MikScrollingBattleText.toc new file mode 100644 index 0000000..878bc23 --- /dev/null +++ b/MikScrollingBattleText/MikScrollingBattleText.toc @@ -0,0 +1,11 @@ +## Interface: 11200 +## Version: 3.01 +## Title: Mik's Scrolling Battle Text +## Notes: Scrolls battle information around the character model. +## SavedVariables: MikSBT_Save +localization.lua +MikTableRecyclerObject.lua +MikCombatEventHelper.lua +MikScrollingBattleText.lua +MikCombatEventHelper.xml +MikScrollingBattleText.xml \ No newline at end of file diff --git a/MikScrollingBattleText/MikScrollingBattleText.xml b/MikScrollingBattleText/MikScrollingBattleText.xml new file mode 100644 index 0000000..c6b75ac --- /dev/null +++ b/MikScrollingBattleText/MikScrollingBattleText.xml @@ -0,0 +1,346 @@ + + + + + + MikSBT.OnEvent(); + MikSBT.OnLoad(); + MikSBT.OnUpdate(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MikSBT.OnUpdateAnimationFrame(this); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MikSBT.OnUpdateAnimationFrame(this); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MikSBT.OnUpdateAnimationFrame(this); + + + \ No newline at end of file diff --git a/MikScrollingBattleText/MikTableRecyclerObject.lua b/MikScrollingBattleText/MikTableRecyclerObject.lua new file mode 100644 index 0000000..9c35d6b --- /dev/null +++ b/MikScrollingBattleText/MikTableRecyclerObject.lua @@ -0,0 +1,173 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Table Recyler Object +-- Author: Mik +-- Credits: +-- Thanks to tekkub, the author of compostLib. I adapted much of his code for +-- this object. +------------------------------------------------------------------------------------- + +-- Create "namespace." +MikTRO = {}; + +-- Set the __index property to the recycler object namespace so +-- when a new recycler object sets it metatable to the namespace +-- the recycler object inherits the functions in the namespace. +MikTRO.__index = MikTRO; + + +-- ********************************************************************************** +-- Creates a recycler object. +-- ********************************************************************************** +function MikTRO:NewRecyclerObject(maxEntries) + -- Create a table to use. + recyclerObject = {}; + + -- Create a table to hold the cache entries and set its max permanent size. + recyclerObject.PrimaryCache = {}; + recyclerObject.MaxEntries = maxEntries; + + -- Create some variables to hold stats. + recyclerObject.NumNew = 0; + recyclerObject.NumRecycled = 0; + recyclerObject.NumErased = 0; + recyclerObject.NumReclaimed = 0; + recyclerObject.AmountMemFreed = 0; + + + -- Create a table to hold overflow entries. If more tables are reclaimed + -- than the size of the max primary cache can hold, they will be reclaimed + -- into the overflow cache which is a weak table. This allows for spikes + -- in cache usage to still be optimized while not preventing garbage + -- collection on the extra data once there are no other references to them. + recyclerObject.OverflowCache = {}; + setmetatable(recyclerObject.OverflowCache, {__mode = "v"}); + + + -- Set the meta table for the cache object to the table recycler + -- obect's namespace so the cache object inherits the functions of + -- table recycler object. + setmetatable(recyclerObject, self); + + -- Return the recycler object. + return recyclerObject; +end + + + +-- ********************************************************************************** +-- Removes a table from the cache and returns it. If there are no tables available +-- in the cache a new one is returned. +-- ********************************************************************************** +function MikTRO:AcquireTable() + -- Check if there are any tables available in the primary cache and if so + -- return one. + if (table.getn(self.PrimaryCache) > 0) then + -- Increment the number of recycled tables. + self.NumRecycled = self.NumRecycled + 1; + + return table.remove(self.PrimaryCache); + + -- Check if there are any tables available in the overflow cache and if so + -- return one. + elseif self:TablesInOverflow() then + -- Increment the number of recycled tables. + self.NumRecycled = self.NumRecycled + 1; + + -- Loop through the tables in the overflow and return one. + for i in pairs(self.OverflowCache) do + return table.remove(self.OverflowCache, i); + end + + -- No tables available so return a new one. + else + -- Increment the number of new tables created. + self.NumNew = self.NumNew + 1; + return {}; + end +end + + +-- ********************************************************************************** +-- Reclaims a table into the cache. +-- ********************************************************************************** +function MikTRO:ReclaimTable(t) + -- Check if the passed value isn't valid. + if (type(t) ~= "table") then + return; + end + + -- Erase the passed table. + self:EraseTable(t); + + -- Check if the primary cache is already full. + if (table.getn(self.PrimaryCache) >= self.MaxEntries) then + -- Insert the table into the overflow cache. + table.insert(self.OverflowCache, t); + else + -- Insert the table into the primary cache. + table.insert(self.PrimaryCache, t); + end + + -- Increment the number of reclaimed tables. + self.NumReclaimed = self.NumReclaimed + 1; +end + + +-- ********************************************************************************** +-- Erases the passed table. Subtables are NOT erased. +-- ********************************************************************************** +function MikTRO:EraseTable(t) + -- Check if the passed value isn't valid. + if (type(t) ~= "table") then + return; + end + + -- Get the amount of memory used before the clear. + local currentMem = gcinfo(); + + -- 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. + table.setn(t, 0); + + -- Increment the amount of memory freed. + self.AmountMemFreed = self.AmountMemFreed + math.abs(gcinfo() - currentMem); + + -- Incremnt the number of tables erased. + self.NumErased = self.NumErased + 1; +end + + +-- ********************************************************************************** +-- Prints stats. +-- ********************************************************************************** +function MikTRO:PrintStats() + local overflowSize = 0; + for i in pairs(self.OverflowCache) do + overflowSize = overflowSize + 1; + end + + DEFAULT_CHAT_FRAME:AddMessage(string.format( + "|cff00ff00New Tables: %d|r |cffffff00Recycled Tables: %d|r |cff00ffffCached Tables: %d|r |cffff0000Overflow Tables: %d|r |cff888888Erased Tables: %d|r |cffff00ffMemory Saved: %d KiB|r |cffff0088Tables Lost to GC: %d", + self.NumNew, + self.NumRecycled, + table.getn(self.PrimaryCache), + overflowSize, + self.NumErased, + self.AmountMemFreed + ((32/1024) * self.NumRecycled), + self.NumReclaimed - self.NumRecycled - table.getn(self.PrimaryCache))); +end + + +-- ********************************************************************************** +-- Checks if there are any tables in the overflow cache. +-- ********************************************************************************** +function MikTRO:TablesInOverflow() + -- Loop through all items in the overflow cache. + for i in pairs(self.OverflowCache) do + return true; + end +end \ No newline at end of file diff --git a/MikScrollingBattleText/localization.lua b/MikScrollingBattleText/localization.lua new file mode 100644 index 0000000..f3f5b70 --- /dev/null +++ b/MikScrollingBattleText/localization.lua @@ -0,0 +1,1030 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Scrolling Battle Text +-- Author: Mik +------------------------------------------------------------------------------------- + +-- Create "namespace." +MikSBT = {}; + +------------------------------------------------------------------------------- +-- Mod Constants +------------------------------------------------------------------------------- + +MikSBT.MOD_NAME = "MikScrollingBattleText" +MikSBT.VERSION_NUMBER = 3.01; +MikSBT.VERSION_STRING = "v3.01"; +MikSBT.WINDOW_TITLE = "Mik's Scrolling Battle Text " .. MikSBT.VERSION_STRING; + +MikSBT.COMMAND = "/msbt"; + + +------------------------------------------------------------------------------- +-- 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 = "Debug mode has been enabled."; +MikSBT.MSG_DEBUG_DISABLE = "Debug mode has been disabled."; +MikSBT.MSG_SEARCH_ENABLE = "Event search mode has been enabled. Searching for: "; +MikSBT.MSG_SEARCH_DISABLE = "Event search mode has been disabled."; +MikSBT.MSG_DISABLE = "The mod is now disabled."; +MikSBT.MSG_ENABLE = "The mod is now enabled."; +MikSBT.MSG_HITS = "Hits"; +MikSBT.MSG_CRIT = "Crit"; +MikSBT.MSG_CRITS = "Crits"; +MikSBT.MSG_MULTIPLE_TARGETS = "Multiple"; +MikSBT.MSG_VULERNABLE_TRAILER = " (%d vulnerability)"; +MikSBT.MSG_OVERHEAL_TRAILER = " <%d>"; +MikSBT.MSG_PROFILE_RESET = "Profile Reset"; +MikSBT.MSG_ENVIRONMENTAL_DROWNING = "Drowning"; +MikSBT.MSG_ENVIRONMENTAL_FALLING = "Falling"; +MikSBT.MSG_ENVIRONMENTAL_FATIGUE = "Fatigue"; +MikSBT.MSG_ENVIRONMENTAL_FIRE = "Fire"; +MikSBT.MSG_ENVIRONMENTAL_LAVA = "Lava"; +MikSBT.MSG_ENVIRONMENTAL_SLIME = "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"}, +}; + +-- 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"}, +}; + + +------------------------------ +-- 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 = false, + UseStickyCrits = true, + AnimationStep = 2, + MasterFontSettings = { + Normal = {FontIndex=1, OutlineIndex=3, FontSize=18}, + Crit = {FontIndex=1, OutlineIndex=3, FontSize=26}, + }, + DisplaySettings = { + Incoming = { + Show = true, + FrameOffsets = {x=-60, y=-160}, + AnimationStyle = 2, + ScrollDirection = 2, + ScrollHeight = 260, + FontSettings = { + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + TextAlignIndex = 3, + }, + }, + Outgoing = { + Show = true, + FrameOffsets = {x=60, y=-160}, + AnimationStyle = 3, + ScrollDirection = 2, + ScrollHeight = 260, + FontSettings = { + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + Crit = {FontIndex=0, OutlineIndex=0, FontSize=0}, + TextAlignIndex = 1, + }, + }, + Notification = { + Show = true, + FrameOffsets = {x=0, y=100}, + 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 = "(%n) -%a", + FontSettings = { + Color = {r=1, g=0, b=0}, + 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 = "(%s) -%a", + 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_SPELL_DOT = { + Show = true, + Message = "(%s) -%a", + FontSettings = { + Color = {r=0.502, g=0, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_MISS = { + Show = true, + Message = "(%s) Miss!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_DODGE = { + Show = true, + Message = "(%s) Dodge!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_PARRY = { + Show = true, + Message = "(%s) Parry!", + FontSettings = { + Color = {r=0, g=0, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_INCOMING_SPELL_BLOCK = { + Show = true, + Message = "(%s) 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 = "(%s - %n) +%a", + 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 = "(%s - %n) +%a", + 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_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=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_DODGE = { + Show = true, + Message = "Dodge!", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_PARRY = { + Show = true, + Message = "Parry!", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_BLOCK = { + Show = true, + Message = "Block!", + FontSettings = { + Color = {r=1, g=1, 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 (%s)", + 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 (%s)", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_MISS = { + Show = true, + Message = "Miss! (%s)", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_DODGE = { + Show = true, + Message = "Dodge! (%s)", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_PARRY = { + Show = true, + Message = "Parry! (%s)", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_BLOCK = { + Show = true, + Message = "Block! (%s)", + FontSettings = { + Color = {r=1, g=1, b=1}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_RESIST = { + Show = true, + Message = "Resist! (%s)", + 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 = "Absorb! (%s)", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_IMMUNE = { + Show = true, + Message = "Immune! (%s)", + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_SPELL_REFLECT = { + Show = true, + Message = "Reflect! (%s)", + 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 = "Evade! (%s)", + FontSettings = { + Color = {r=1, g=0.502, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=22}, + }, + }, + MSBT_EVENTTYPE_OUTGOING_HEAL = { + Show = true, + Message = "+%a (%s - %n)", + 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 (%s - %n)", + 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 %s", + IsSticky = false, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=0}, + }, + }, + MSBT_EVENTTYPE_NOTIFICATION_POWER_LOSS = { + Show = true, + Message = "-%a %s", + IsSticky = false, + FontSettings = { + Color = {r=1, g=1, b=0}, + 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 Reputation (%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 Reputation (%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, + }, + }, + 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, + }, + }, + MSBT_TRIGGER_EXECUTE = { + EventSettings = { + Show = true, + Message = "Execute!", + IsSticky = true, + FontSettings = { + Color = {r=1, g=1, b=0}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + Classes = {PALADIN=true,WARRIOR=true}, + TriggerType = 4, + Threshold = 20, + }, + }, + MSBT_TRIGGER_COUNTER_ATTACK = { + EventSettings = { + Show = true, + Message = "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"}, + }, + }, + MSBT_TRIGGER_MONGOOSE_BITE = { + EventSettings = { + Show = true, + Message = "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"}, + }, + }, + MSBT_TRIGGER_CLEARCAST = { + EventSettings = { + Show = true, + Message = "Clearcast!", + 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 = {"You gain Clearcast"}, + }, + }, + MSBT_TRIGGER_RIPOSTE = { + EventSettings = { + Show = true, + Message = "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"}, + }, + }, + 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 = {"You gain %d extra attacks through Windfury"}, + }, + }, + MSBT_TRIGGER_NIGHTFALL = { + EventSettings = { + Show = true, + Message = "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 = {"You gain Shadow Trance"}, + }, + }, + MSBT_TRIGGER_OVERPOWER = { + EventSettings = { + Show = true, + Message = "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"}, + }, + }, + }, + + Suppressions = { + MSBT_SUPPRESSION_WISDOM_MANA_GAINS = { + Enabled = true, + SearchPattern = "+[35][039] " .. MANA, + }, + }, +}; diff --git a/MikScrollingBattleText/readme.html b/MikScrollingBattleText/readme.html new file mode 100644 index 0000000..e06fa0e --- /dev/null +++ b/MikScrollingBattleText/readme.html @@ -0,0 +1,808 @@ + + + Mik's Scrolling Battle Text Readme + + + +
Mik's Scrolling Battle Text Readme
+
+ Version: 3.01
+ Author: Mik

+ Credits: +
    +
  • Thanks to grayhoof, the author of Scrolling Combat Text, for the original mod which inspired this mod.
  • +
  • Thanks to kergoth and Wobin for their suggestions on optimizations via reduced GC churn and namespacing tips.
  • +
  • Thanks to tekkub for compostLib which I borrowed some of the table recycling code from.
  • +
+
+ +
+
+ +
Table of Contents:
+ + + +
+ Return to TOC

+
+ Unzip the contents into the AddOns directory of your WoW game directory.

+ This is typically C:\Program Files\World of Warcraft\Interface\AddOns.

+ If your are upgrading the mod from a previous version, make sure to delete the old version prior to installing + the new version. +
+ + +
+ Return to TOC

+
+ This mod makes it easier to see combat information by scrolling it on the screen in 3 configurable scrolling areas. + There is a scroll area for incoming heals/damage, one for outgoing heals/damage, and a third for notifications. + Each scroll area is completely customizable.

+ + Features: +
    +
  • Scroll incoming damage/heals, outgoing damage/heals, and notifications in separate configurable scroll areas on the playing field.
  • +
  • Customize the position, animation style, show state, font style, font size, and font outline for each of the scroll areas.
  • +
  • Customize each individual event's color, font style, font size, font outline, output message, and show state.
  • +
  • Set "Master Font" settings that will be inherited by all of the scroll areas and the events in them unless they are overriden at the scroll area or event level.
  • +
  • Merge AoE data into one event with cumulative damage/healing done with number of normal and crits specified.
  • +
  • Show overhealing amounts against yourself or party/raid members.
  • +
  • Add triggers that will allow you to show notifications based on various health and mana thresholds, or custom search patterns. Triggers are also set to only fire for specific classes.
  • +
  • Suppress particular messages you do not want to see such as mana gains from Blessing/Judgement of Wisdom.
  • +
  • Load on demand options.
  • +
+ +
+ Supported Events: +
    +
  • Incoming:
  • +
      +
    • Melee Damage
    • +
    • Melee Misses, Dodges, Parries, Blocks, Absorbs, and Immunes
    • +
    • Ability/Spell Damage
    • +
    • Ability/Spell Damage Over Time (DoTs)
    • +
    • Ability/Spell Misses, Dodges, Parries, Blocks, Absorbs, Immunes, and Reflects
    • +
    • Spell Resists
    • +
    • Heals
    • +
    • Heals Over Time (HoTs)
    • +
    • Environmental Damage
    • +
    +
    +
  • Outgoing:
  • +
      +
    • Melee Damage
    • +
    • Melee Misses, Dodges, Parries, Blocks, Absorbs, and Evades
    • +
    • Ability/Spell Damage
    • +
    • Ability/Spell Damage Over Time (DoTs)
    • +
    • Ability/Spell Misses, Dodges, Parries, Blocks, Absorbs, Immunes, Reflects, and Evades
    • +
    • Spell Resists
    • +
    • Heals
    • +
    • Heals Over Time (HoTs)
    • +
    • Pet Melee Damage
    • +
    • Pet Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes, and Evades
    • +
    • Pet Ability/Spell Damage
    • +
    • Pet Ability/Spell Misses, Dodges, Parries, and Blocks, Absorbs, Immunes, Reflects, and Evades
    • +
    • Pet Spell Resists
    • +
    + +
    +
  • Notification:
  • +
      +
    • Debuffs
    • +
    • Buffs
    • +
    • Item Buffs
    • +
    • Buff Fades
    • +
    • Enter/Leave Combat
    • +
    • Power Gains and Losses
    • +
    • Combo Point Gains
    • +
    • Combo Points Full
    • +
    • Honor Gains
    • +
    • Reputation Gains and Losses
    • +
    • Skill Gains
    • +
    • Experience Gains
    • +
    • Killing Blows (Player and NPC)
    • +
    + +
    +
  • Default Triggers:
  • +
      +
    • Low Health
    • +
    • Low Mana
    • +
    • Low Pet Health
    • +
    • Execute
    • +
    • Counter Attack
    • +
    • Mongoose Bite +
    • Clearcast
    • +
    • Riposte
    • +
    • Windfury
    • +
    • Nightfall
    • +
    • Overpower
    • +
    + +
    +
  • Default Suppressions:
  • +
      +
    • Blessing/Judgement of Wisdom (+30 Mana, +33 Mana, +59 Mana)
    • +
    +
+
+ + +
+ Return to TOC

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
/msbtShows the options interface.
/msbt resetResets the current profile to the default settings.
/msbt disableDisables the mod.
/msbt enableEnables the mod.
/msbt versionShows the current version.
/msbt statsReports stats about table recycling.
/msbt search patternSets a pattern for searching event types.
/msbt debugToggles debug mode.
/msbt helpShows the command usage.
+
+ + +
+ Return to TOC

+
+ The trigger system allows you to specify notifications based on events that are not already covered by + MSBT's native events. The trigger system has been optimized so that having extra triggers which are + not enabled or which do not apply to the class you are currently playing add no overhead. +

+ + Due to this optimization, a lot of triggers that people will want (Windfury, Nightfall, Mongoose Bite, etc) + are probably already there by default, and will not waste resources for those which don't apply to your + class or are not enabled. +

+ + The following is a description of the fields unique to the Trigger Settings interface (see the Frequently Asked + Questions section for a tutorial on how to create a new trigger): +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Output Message + This is the message that will be displayed when the trigger fires. If you are using a Search Pattern + with captures you can enter %1 through %9 to use the data that was captured. Also, for trigger types + with thresholds, the amount of health or mana will be in %1. +
Applicable Trigger Classes: + These are the classes that you want the trigger to apply to. NOTE: THIS IS YOUR CLASS NOT THE TARGET CLASS. + You may look at the Execute trigger for an example. Since Warrior and Paladin are selected, the trigger + will only apply when you are playing on a warrior or paladin. +
Trigger Type: + This is the type of trigger. +
    +
  • Self Health - This will trigger when your health falls below the selected threshold.
  • +
  • Self Mana - This will trigger when your mana falls below the selected threshold.
  • +
  • Pet Health - This will trigger when your pet's health falls below the selected threshold.
  • +
  • Enemy Target Health - This will trigger when your target is an enemy and their health falls below the selected threshold.
  • +
  • Friendly Target Health - This will trigger when your target is friendly and their health falls below the selected threshold.
  • +
  • Search Pattern - This will trigger when the search pattern you enter is found.
  • +
Threshold: + This slider will only be present if the selected trigger type is one of the types that support it. + It specifies the threshold percentage that must be crossed for the trigger to fire. +
Trigger Event Types: + This list will only be present if the selected trigger type is Search Pattern.

+ + This exists solely for optimization and is probably the most difficult field to enter. While not having this + field would make the trigger system easier to use, it would also make it extremely inefficient since searching + through an array of triggers which don't apply every time ANY event happens wastes precious resources. In order + to find the appropriate event types to use, type /msbt search pattern, where the word pattern is a lua search string. + Only combat messages that match the search string will display their associated event type. Once you are done you + can type /msbt search to toggle event searching mode back off. +
Search Pattern 1 & 2: + These fields will only be present if the selected trigger type is Search Pattern.

+ + These fields can either contain a global string from globalstrings.lua (if you happen to know which one you want) or a + lua formatted search string. The mod also supports data captured from the pattern. The output message specified for + the trigger can use the captured data by putting %1 - %9 where the number is the position of the captured data. +
+
+ + + +
+ Return to TOC

+
+ The suppression system allows you to suppress specific text that MSBT is displaying to the screen via lua search patterns. +

+ + The text next to enable checkbox is the search pattern that will be checked for matches against the output text. If a match is made, + the output text will be suppressed.
+ See the Frequently Asked Questions section for a tutorial on how to create a + new suppression. +

+ + Here is the official lua reference for patterns: +
+ Character Class: +

+ + A character class is used to represent a set of characters. The following combinations are allowed in describing a character class: +
    +
  • x: (where x is not one of the magic characters ^$()%.[]*+-?) represents the character x itself.
  • +
  • .: (a dot) represents all characters.
  • +
  • %a: represents all letters.
  • +
  • %c: represents all control characters.
  • +
  • %d: represents all digits.
  • +
  • %l: represents all lowercase letters.
  • +
  • %p: represents all punctuation characters.
  • +
  • %s: represents all space characters.
  • +
  • %u: represents all uppercase letters.
  • +
  • %w: represents all alphanumeric characters.
  • +
  • %x: represents all hexadecimal digits.
  • +
  • %z: represents the character with representation 0.
  • +
  • + %x: (where x is any non-alphanumeric character) represents the character x. This is the standard way to escape the magic characters. Any punctuation character (even the non magic) + can be preceded by a '%' when used to represent itself in a pattern. +
  • +
  • + [set]: represents the class which is the union of all characters in set. A range of characters may be specified by separating the end characters of the range with a '-'. All classes + %x described above may also be used as components in set. All other characters in set represent themselves. For example, [%w_] (or [_%w]) represents all alphanumeric characters plus + the underscore, [0-7] represents the octal digits, and [0-7%l%-] represents the octal digits plus the lowercase letters plus the '-' character. +

    + The interaction between ranges and classes is not defined. Therefore, patterns like [%a-z] or [a-%%] have no meaning. +
  • +
    +
  • [^set]: represents the complement of set, where set is interpreted as above.
  • +
+ For all classes represented by single letters (%a, %c, etc.), the corresponding uppercase letter represents the complement of the class. For instance, %S represents all non-space characters. +

+ The definitions of letter, space, and other character groups depend on the current locale. In particular, the class [a-z] may not be equivalent to %l. +

+ + Pattern Item: +

+ + A pattern item may be: +
    +
  • a single character class, which matches any single character in the class;
  • +
  • a single character class followed by '*', which matches 0 or more repetitions of characters in the class. These repetition items will always match the longest possible sequence;
  • +
  • a single character class followed by '+', which matches 1 or more repetitions of characters in the class. These repetition items will always match the longest possible sequence;
  • +
  • a single character class followed by '-', which also matches 0 or more repetitions of characters in the class. Unlike '*', these repetition items will always match the shortest possible sequence;
  • +
  • a single character class followed by '?', which matches 0 or 1 occurrence of a character in the class;
  • +
  • %n, for n between 1 and 9; such item matches a substring equal to the n-th captured string (see below);
  • +
  • + %bxy, where x and y are two distinct characters; such item matches strings that start with x, end with y, and where the x and y are balanced. This means that, if one reads the string from left to right, + counting +1 for an x and -1 for a y, the ending y is the first y where the count reaches 0. For instance, the item %b() matches expressions with balanced parentheses. +
  • +
+ + Pattern: +

+ A pattern is a sequence of pattern items. A '^' at the beginning of a pattern anchors the match at the beginning of the subject string. A '$' at the end of a pattern anchors the match at the end of the subject string. + At other positions, '^' and '$' have no special meaning and represent themselves. +

+ + Captures: +

+ A pattern may contain sub-patterns enclosed in parentheses; they describe captures. When a match succeeds, the substrings of the subject string that match captures are stored (captured) for future use. Captures are numbered + according to their left parentheses. For instance, in the pattern "(a*(.)%w(%s*))", the part of the string matching "a*(.)%w(%s*)" is stored as the first capture (and therefore has number 1); the character matching "." is + captured with number 2, and the part matching "%s*" has number 3. +

+ + As a special case, the empty capture () captures the current string position (a number). For instance, if we apply the pattern "()aa()" on the string "flaaap", there will be two captures: 3 and 5. +

+ A pattern cannot contain embedded zeros. Use %z instead. +
+
+ + + +
+ Return to TOC

+
+
MikSBT.DisplayMessage(message [, displayType, isSticky, colorR, colorG, colorB, fontSize, fontIndex, outlineIndex])
+

+
Parameters
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
message (Required)The string to display.
displayType (Optional) + Specifies the scroll area in which to display the message. This may be one of the following values:

+ + MikSBT.DISPLAYTYPE_INCOMING
+ MikSBT.DISPLAYTYPE_OUTGOING
+ MikSBT.DISPLAYTYPE_NOTIFICATION

+ + If the parameter is omitted or invalid a default of MikSBT.DISPLAYTYPE_NOTIFICATION will be used. +
isSticky (Optional) + Specifies whether or not the message should be displayed sticky style. This must be either true or false.

+ If the parameter is omitted a default of false will be used. +
colorR (Optional) + The red component of the color to display the message with. Value range is 0-255.

+ If the parameter is omitted a default of 255 will be used. +
colorG (Optional) + The green component of the color to display the message with. Value range is 0-255.

+ If the parameter is omitted a default of 255 will be used. +
colorB (Optional) + The blue component of the color to display the message with. Value range is 0-255.

+ If the parameter is omitted a default of 255 will be used. +
fontSize (Optional) + The font size to use. Value range is 12-32.

+ If this value is omitted or an invalid value is passed the settings for the scroll area will be used. +
fontIndex (Optional) + The index of the font to use. The valid indices can be seen by looking at the MikSBT.AVAILABLE_FONTS + table in the localization.lua file.

+ If the parameter is omitted or an invalid index is passed the font index for the scroll area will be used. +
outlineIndex (Optional) + The index of the outline to use. The valid indices can be seen by looking at the MikSBT.AVAILABLE_OUTLINES table in the localization.lua file.

+ If the parameter is omitted or an invalid index is passed the outline index for the scroll area will be used. +
+
+ +
Return Values
+
None.
+ +
Examples
+
+
+
+ -- Displays "Test Message" in the notification scroll area in white.
+ MikSBT.DisplayMessage("Test Message");
+ 
+ -- Displays "Another Message" in the incoming scroll area in white as a sticky.
+ MikSBT.DisplayMessage("Another Message", MikSBT.DISPLAYTYPE_INCOMING, true);
+
+ -- Displays "Uber Damage!" in the outgoing scroll area in blue.
+ MikSBT.DisplayMessage("Uber Damage!", MikSBT.DISPLAYTYPE_OUTGOING, false, 0, 0, 255);
+
+ -- Displays "Enemy begins to flee in fear" in the notification scroll area in green with the scroll area's
+ -- font size, the font set to index 2 and the outline set to index 1.
+ MikSBT.DisplayMessage("Enemy begins to flee in fear", nil, false, 0, 255, 0, nil, 2, 1);
+    
+
+
+ + + +
+ Return to TOC

+
+
    +
  1. + I don't like any of the fonts supplied with MSBT. How do I use my own fonts? +

    + + WARNING: ALL FONT MODIFICATION WILL REQUIRE THAT YOU COMPLETELY QUIT THE GAME AND RESTART IT. + RELOADING THE CONSOLE ALONE WILL NOT WORK. +

    + + NOTE: Keep in mind that if you use your own font files that you need to make sure the font + supports the characters that will be displayed. For example the typical way to display buff + gains is [BuffName]. If the font you use does not have a character glyph for the [ or ] + characters you will likely see a big ugly block instead of the intended characters. This is + especially important with non-English clients since a lot of characters aren't supported by + English fonts. +

    + + There are two ways to use custom fonts: +
      +
    • + The first, and easiest, method is to simply replace one of the font files in the + MikScrollingBattleText\Fonts directory with the truetype font (.ttf) that you want. The drawback + to this approach is that the mod's option screen will still reference the font with the old font's name. +

      + + For example, if you had a truetype font file "MyUberFont.ttf" you wanted to use. Rename MyUberFont.ttf to Adventure.ttf + and replace the Adventure.ttf font file in the MikScrollingBattleText\Fonts directory. In the MSBT options interface + it would still be called "Adventure," but it would actually be using the new font. +
    • +

      +
    • + The second method is to open the file localization.lua in the MikScrollingBattleText directory and find the + MikSBT.AVAILABLE_FONTS entry. You can add your font with the appropriate name and path. If you don't know + anything about programming, I wouldn't suggest trying this method. Another thing to keep in mind with this + approach is that due to the way WoW loads its data, I would suggest you put in new fonts you want to add in + the same place as the current fonts for the mod (MikScrollingBattleText\Fonts) to avoid problems with + loading. +
    • +
    +
  2. +

    + +
  3. + How do I create a new trigger? +

    + + Let's go through creating a trigger for a warrior's overpower ability which already exists, but will be used for illustrative purposes. +

    + + A warrior's overpower ability becomes available once the enemy they are fighting dodges. So, the first that needs to be done is to + ascertain the appropriate event types. +
    + Since we want the trigger to fire when the enemy dodges we need to take a look at what the combat log says when an enemy dodges. The combat + log message says "You attack. Mob dodges." To find out what event type this is you can type /msbt search dodge. What this will do is make + it so that every time an entry is added to the combat log with the word dodge in it, you will get a message + with the event type in your normal chat frame. In this case you would probably see something like: +

    + MSBT: CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES - Mob attacks. You dodge.
    + MSBT: CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES - Player attacks. You dodge.
    + MSBT: CHAT_MSG_COMBAT_SELF_MISSES - You attack. Mob dodges. <--- Aha, there is one.
    + MSBT: CHAT_MSG_SPELL_SELF_DAMAGE - Your hamstring is dodged by Mob. <--- There is another one.
    +

    + So the correct event types to choose are CHAT_MSG_COMBAT_SELF_MISSES and CHAT_MSG_SPELL_SELF_DAMAGE. Type /msbt search + to turn the event searching mode back off. +


    + Now that we know the correct event types we can create the trigger. Here are the steps to do so: +

    +
      +
    1. Since a new trigger is being created, click the Add Trigger button. A new trigger will appear in the list of triggers.
    2. +

      +
    3. Click the output message for the new trigger and enter Overpower!.
    4. +

      +
    5. Click the Trigger Settings button.
    6. +

      +
    7. + Uncheck "All" classes, and check Warrior for the class the trigger will apply to. It is better to select the + specific classes the trigger applies to instead of selecting all classes since by specifying classes, the mod + can completely ignore the trigger and save resources for the classes to which it does not apply.
    8. +
    9. Select Search Pattern for the Trigger Type.
    10. +

      +
    11. Choose the correct event types we ascertained above. CHAT_MSG_COMBAT_SELF_MISSES and CHAT_MSG_SPELL_SELF_DAMAGE.
    12. +

      +
    13. + Last, since there are two different combat log messages, you'll need to enter two different search patterns. In the + search pattern 1 field, enter "You attack. .+ dodges" to cover the first message and in the search pattern 2 field, + enter "Your .+ is dodged by .+". Both of those are entered without the quotes. There are other variations of the + search patterns that could be used, but these would work fine. +

      + NOTE: The trigger that is already setup uses the global strings VSDODGESELFOTHER and SPELLDODGEDSELFOTHER. Messages + that go to the combat log have an associated global string that is defined in globalstrings.lua by blizzard and can + be used. Global strings are really only useful in this sense if you are looking for a general event like dodges, + parries, misses, etc. Looking specifically for something like a buff name, for example, wouldn't have an available + global string and you'd need to enter a lua search pattern as demonstrated. +
    14. +

      +
    15. Click the Save Trigger button.
    16. +
    + +

    + The trigger is now set to fire any time a mob or player dodges one of your attacks (only if you are a warrior). +

    + The color, font settings, and sticky state for the trigger can be set like any other event. + +
  4. +

    + +
  5. + Some of the default triggers are not working for me while others are. What's up with that? +

    + Most likely, you are using a non-English client. Some of the search patterns for the triggers + use Blizzard's global strings which are translated by Blizzard and hence will work on your client + by default. Some other triggers have an English search pattern for when to fire the trigger. Simply + edit the trigger settings for the trigger that is not working and change the search pattern to an + appropriate one for your language. +
  6. +

    + +
  7. + How do I create a new suppression? +

    + As an example, let's create a suppression for warrior stance switching. +

    + Unless you have changed the output message format for buff gains and fades, the warrior stance switch + messages should be: +

    + [Battle Stance]
    + -[Battle Stance]
    + [Defensive Stance]
    + -[Defensive Stance]
    + [Bersker Stance]
    + -[Bersker Stance]
    +
    + What we want to do is create a search pattern that will match those output messages. +

    + Here is a search pattern that will match all of them: +

    + %[.+ Stance%] +

    + Now that we have a search pattern that will match all of the stance switching messages, we can + do the following to add the suppression: +

    +
      +
    1. Go to the Suppressions tab.
    2. +
    3. Click the Add New Suppression button.
    4. +
    5. Click the label where it says "New Suppression: Click here to edit."
    6. +
    7. Enter the seach pattern from above.
    8. +
    +
    + All of the warrior stance switch messages should now be suppressed. +
  8. +
+
+ + +
+ Return to TOC

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
3.01 +
    +
  • Fixed the bug where you could only add one suppression and adding a new one was overwriting it.
  • +
+
3.0 +
    +
  • Implemented a low overhead trigger system. See documentation above.
  • +
  • Implemented a suppression system that allows you to suppress particular messages you don't want to see.
  • +
  • + Made significant changes to the scroll area settings interface. The new interface shows all three + scroll areas at the same time. Each scroll area is now titled with its coordinates and there are + editboxes to enter specific coordinates in addition to being able to drag the scroll areas around. +
  • +
  • + Changed the core animation routines to use function pointers in order to eliminate the need for a + ton of conditional testing. Previously, every time the animation routine was called (typically + 60-70 times per second for every scrolling item), the animation style and scroll direction were + checked to calculate how to move the text. Now those items are only checked during the initial + animation object setup phase. This should help performance on slower PCs. +
  • +
  • Added the command "/msbt search pattern" to enable an event searching mode which helps identify event types used in the trigger system.
  • +
  • + Moved the Low Health, Low Mana, and Execute events into the trigger system. Due to this change + the Low Mana and Execute triggers will no longer waste resources checking on classes that don't + use mana or have an execute ability. Also moving them into the trigger system made the thresholds + at which to show the Low Health, and Low Mana warnings selectable. +
  • +
  • Made a few minor code performance tweaks to the animation setup "pipeline."
  • +
  • Added Killing Blow notification events for both players and NPCs.
  • +
  • Added crushing and glancing hit partial effects.
  • +
  • The font settings frame can now be moved.
  • +
  • Fixed a bug where messages added via the MikSBT.DisplayMessage function were being shown even if the scroll area was set to not show messages.
  • +
+
2.11 +
    +
  • Disabled Blizzard's new floating text in Patch 1.12 when MSBT is enabled.
  • +
  • Updated for Patch 1.12.
  • +
+
2.1 +
    +
  • Reduced memory usage a little by removing the underlying tables used to store crit information for events that can never be crits. (Down to around 477KB)
  • +
  • To go along with the above change, the font settings interface for events was reworked so that you can no longer set crit information for events that don't support them.
  • +
  • Added a title at the top of the font settings frame to help distinguish what is being modified.
  • +
  • Added an Experience Gains notification event.
  • +
  • Added the capability for notifications to be displayed "sticky" style.
  • +
  • Fixed the bug induced by patch 1.11 where reputation gains and losses weren't being displayed.
  • +
+
2.0 +
    +
  • + MAJOR optimization code rewrite: +
      +
    • Made options load on demand to significantly reduce memory usage and start up time.
    • +
    • + Implemented a table recycling system to reuse tables created during combat instead + of creating new ones and allowing them to be garbage collected. The net effect is that a + ton of garbage collection churn has been eliminated. Thanks go to kergoth and Wobin for + tips on how to achieve this. +
    • +
    • The combat event parser has been changed to reuse one single table for storing the results of the parsed data.
    • +
    • Streamlined the combat event to screen animation "pipeline."
    • +
    • Namespaced all code to reduce global namespace pollution.
    • +
    • + Load up KB Usage in v1.03 was approximately 1050 KB. This has been reduced to around + 488 KB. In addition, due to the table recycling system, memory usage during operation is + much more consistent instead of continuously increasing until the next garbage collection + cycle. +
    • +
    +
  • +
    +
  • + Added a function to allow messages to be easily displayed through MSBT from external sources.
    + This function is MikSBT.DisplayMessage and is detailed above. +
  • +
  • Added the command "/msbt stats" to report statistics about the table recycling system.
  • +
  • Changed the functionality of AoE merges so that the name reported is "Multiple" instead of the last affected unit.
  • +
  • Added the option to disable "game damage" instead of just always doing it.
  • +
  • Added the option to disable "sticky crits."
  • +
  • Added Execute/Hammer of Wrath notification event.
  • +
  • Separated Heals over time (HoTs) and Damage over time (DoTs) into their own events.
  • +
  • Added the option to display overhealing.v +
  • Fixed a bug where crits were not being recognized in non-English clients.
  • +
+
1.03 +
    +
  • Fixed a bug where options set with a checkbox were not being properly saved on logout.
  • +
+
1.02 +
    +
  • Fixed a bug where the global string parser wasn't appropriately accounting for the argument order of non-English clients.
  • +
+
1.01 +
    +
  • Updated for Patch 1.11.
  • +
+
1.0 +
    +
  • Initial version.
  • +
+
+
+ + \ No newline at end of file diff --git a/MikScrollingBattleTextOptions/MSBTOptions.lua b/MikScrollingBattleTextOptions/MSBTOptions.lua new file mode 100644 index 0000000..16322af --- /dev/null +++ b/MikScrollingBattleTextOptions/MSBTOptions.lua @@ -0,0 +1,3561 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Scrolling Battle Text Options +-- Author: Mik +------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------- +-- Local Constants. +------------------------------------------------------------------------------------- + +-- The name of the options frame. +local OPTIONS_FRAME_NAME = "MSBTFrameOptions"; + +-- The name of the font settings frame. +local FONT_SETTINGS_FRAME_NAME = "MSBTFontSettingsFrame"; + +-- The name of the scroll area frames. +local SCROLL_AREA_MOVER_FRAME_NAME = "MSBTScrollAreaMoverFrame"; +local SCROLL_AREA_MOVER_CONTROL_FRAME_NAME = "MSBTScrollAreaMoverControlFrame"; +local SCROLL_AREA_MOVER_INCOMING_FRAME_NAME = "MSBTScrollAreaMoverIncomingFrame"; +local SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME = "MSBTScrollAreaMoverOutgoingFrame"; +local SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME = "MSBTScrollAreaMoverNotificationFrame"; + +-- The name of the trigger configuration frame. +local TRIGGER_CONFIGURATION_FRAME_NAME = "MSBTTriggerConfigFrame"; + + +-- The min and max font sizes allowed. +local MIN_FONT_SIZE = 12; +local MAX_FONT_SIZE = 32; + +-- The min and max animation steps. +local MIN_ANIMATION_STEP = 1; +local MAX_ANIMATION_STEP = 5; + +-- The min and max scroll heights. +local MIN_SCROLL_HEIGHT = 100; +local MAX_SCROLL_HEIGHT = 300; + +-- The size of an event settings line and number of event items per page. +local EVENT_ITEM_HEIGHT = 25; +local EVENT_ITEMS_PER_PAGE = 10; + +-- The size of a trigger settings line and number of trigger items per page. +local TRIGGER_ITEM_HEIGHT = 25; +local TRIGGER_ITEMS_PER_PAGE = 11; + +-- The size of a trigger event line and the number of trigger event items per page. +local TRIGGER_EVENT_ITEM_HEIGHT = 25; +local TRIGGER_EVENT_ITEMS_PER_PAGE = 4; + +-- The size of a suppressions settings line and number of suppression items per page. +local SUPPRESSION_ITEM_HEIGHT = 25; +local SUPPRESSION_ITEMS_PER_PAGE = 11; + +-- Tab indices. +local TAB_INDEX_GENERAL = 1; +local TAB_INDEX_INCOMING = 2; +local TAB_INDEX_OUTGOING = 3; +local TAB_INDEX_NOTIFICATION = 4; +local TAB_INDEX_TRIGGERS = 5; +local TAB_INDEX_SUPPRESSIONS = 6; + + +------------------------------------------------------------------------------------- +-- Local variables. +------------------------------------------------------------------------------------- + +-- Holds available font and outline selections. +local availableFontsInherit; +local availableOutlinesInherit; + +-- Holds a table of available trigger events. +local availableTriggerEvents = {}; + + +------------------------------------------------------------------------------------- +-- Core event handlers. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Called when the options frame is loaded. +-- ********************************************************************************** +function MikSBTOpt.OnLoad() + -- Register for the ADDON_LOADED event. + this:RegisterEvent("ADDON_LOADED"); +end + + +-- ********************************************************************************** +-- Called when the events we registered for occur. +-- ********************************************************************************** +function MikSBTOpt.OnEvent() + -- When an addon is loaded. + if (event == "ADDON_LOADED") then + -- Make sure it's this addon. + if (arg1 == string.gsub(MikSBT.MOD_NAME .. "Options", "%s", "")) then + + -- Don't get notification for other addons being loaded. + this:UnregisterEvent("ADDON_LOADED"); + + -- Initialize the options. + MikSBTOpt.Init(); + end + end +end + + +-- ********************************************************************************** +-- Called when the mod is fully loaded (variables loaded). +-- ********************************************************************************** +function MikSBTOpt.Init() + -- Set the options window title. + getglobal(OPTIONS_FRAME_NAME .. "TitleText"):SetText(MikSBT.WINDOW_TITLE); + + -- Set the normal and crit size font preview text. + getglobal(FONT_SETTINGS_FRAME_NAME .. "NormalPreviewText"):SetText(MikSBTOpt.MSG_NORMAL_PREVIEW_TEXT); + getglobal(FONT_SETTINGS_FRAME_NAME .. "CritPreviewText"):SetText(MikSBTOpt.MSG_CRIT_PREVIEW_TEXT); + + -- Set the trigger classes text + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerClassesText"):SetText(MikSBTOpt.MSG_TRIGGER_APPLICABLE_CLASSES_LABEL); + + + -- Initialize the dropdowns. + MikSBTOpt.InitDropdowns(); + + -- Initialize the checkboxes. + MikSBTOpt.InitCheckboxes(); + + -- Initialize the sliders. + MikSBTOpt.InitSliders(); + + -- Initialize the buttons. + MikSBTOpt.InitButtons(); + + -- Initialize the editboxes. + MikSBTOpt.InitEditboxes(); + + -- Initialize the events. + MikSBTOpt.InitEvents(); + + + + + + -- Populate the tabs and select the first one. + MikSBTOpt.PopulateTabs(); + MikSBTOpt.TabOnClick(1); + + + + -- Change the state of the profile buttons correctly, and set the current profile label. + MikSBTOpt.EnableDisableProfileButtons(); + getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameCurrentProfileLabel"):SetText(MikSBTOpt.MSG_CURRENT_PROFILE_TEXT .. ": " .. MikSBT_Save.CurrentProfile); + + + -- Setup the available trigger events. + MikSBTOpt.SetupAvailableTriggerEvents(); + + -- Populate the options for the current profile. + MikSBTOpt.PopulateProfileOptions(); + + -- Insert the frame name into the UISpecialFrames array so it closes when the escape key is pressed. + table.insert(UISpecialFrames, OPTIONS_FRAME_NAME); +end + + +-- ********************************************************************************** +-- Called when the options frame is shown. +-- ********************************************************************************** +function MikSBTOpt.OnShow() + -- Holds whether or not to play a sound. + local doPlaySound = true; + + -- Get the scroll area mover control frame and make sure it exists. + local frame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + -- Check if the scroll area mover frame is shown. + if (frame and frame:IsShown()) then + -- Don't play a sound. + doPlaySound = false; + end + + -- Check if a sound should be played. + if (doPlaySound) then + -- Play the opening sound + PlaySound("igCharacterInfoOpen"); + end +end + + +-- ********************************************************************************** +-- Called when the options frame is hidden. +-- ********************************************************************************** +function MikSBTOpt.OnHide() + -- Holds whether or not to play a sound. + local doPlaySound = true; + + -- Get the scroll area mover control frame and make sure it exists. + local frame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + -- Check if the scroll area mover frame is shown. + if (frame and frame:IsShown()) then + -- Don't play a sound. + doPlaySound = false; + end + + -- Check if a sound should be played. + if (doPlaySound) then + -- Play the closing sound + PlaySound("igCharacterInfoClose"); + end + + -- Hide the font settings frame when the main frame is closed. + getglobal(FONT_SETTINGS_FRAME_NAME):Hide(); + + -- Hide the trigger config frame when the main frame is closed. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); +end + + +-- ********************************************************************************** +-- Called when one of the scroll areas is scrolled. +-- ********************************************************************************** +function MikSBTOpt.OnVerticalScroll() + -- Uncomment once populate functions exist. + -- Check if it's the incoming event settings scroll frame. + if (this:GetName() == OPTIONS_FRAME_NAME .. "Tab2FrameEventsScrollFrame") then + FauxScrollFrame_OnVerticalScroll(EVENT_ITEM_HEIGHT, MikSBTOpt.PopulateIncomingEventSettings); + + -- Outgoing event settings scroll frame. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab3FrameEventsScrollFrame") then + FauxScrollFrame_OnVerticalScroll(EVENT_ITEM_HEIGHT, MikSBTOpt.PopulateOutgoingEventSettings); + + -- Notification event settings scroll frame. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab4FrameEventsScrollFrame") then + FauxScrollFrame_OnVerticalScroll(EVENT_ITEM_HEIGHT, MikSBTOpt.PopulateNotificationEventSettings); + + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab5FrameTriggersScrollFrame") then + FauxScrollFrame_OnVerticalScroll(TRIGGER_ITEM_HEIGHT, MikSBTOpt.PopulateTriggerEventSettings); + + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab6FrameSuppressionsScrollFrame") then + FauxScrollFrame_OnVerticalScroll(SUPPRESSION_ITEM_HEIGHT, MikSBTOpt.PopulateSuppressionSettings); + + elseif (this:GetName() == TRIGGER_CONFIGURATION_FRAME_NAME .. "EventsScrollFrame") then + FauxScrollFrame_OnVerticalScroll(TRIGGER_EVENT_ITEM_HEIGHT, MikSBTOpt.PopulateAvailableTriggerEvents); + + end +end + + + +------------------------------------------------------------------------------------- +-- Tab functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Populates the tabs. +-- ********************************************************************************** +function MikSBTOpt.PopulateTabs() + -- Set the number of tabs for the options frame. + PanelTemplates_SetNumTabs(getglobal(OPTIONS_FRAME_NAME), table.getn(MikSBTOpt.TABS)); + + -- Loop through all of the tabs. + for tabNumber, tabData in MikSBTOpt.TABS do + -- Get the associated tab frame. + local tabFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab" .. tabNumber); + if (tabFrame) then + -- Set the tooltip for the tab. + tabFrame.tooltipText = tabData.Tooltip; + + -- Set the text for the tab and resize it. + tabFrame:SetText(tabData.Label); + PanelTemplates_TabResize(1, tabFrame); + end + end +end + + +-- ********************************************************************************** +-- Called when a tab is clicked. +-- ********************************************************************************** +function MikSBTOpt.TabOnClick(tab) + -- Get the clicked tab + if (not tab) then + tab = this:GetID(); + end + + -- Set the selected tab + PanelTemplates_SetTab(getglobal(OPTIONS_FRAME_NAME), tab); + + -- Hide all of the tab frames. + for tabNumber, tabData in MikSBTOpt.TABS do + local associatedTabFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab" .. tabNumber .. "Frame"); + if (associatedTabFrame) then + associatedTabFrame:Hide(); + end + end + + -- Show the associated frame for the selected tab. + local associatedTabFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab" .. tab .. "Frame"); + if (associatedTabFrame) then + associatedTabFrame:Show(); + end + + -- Hide the font settings frame. + getglobal(FONT_SETTINGS_FRAME_NAME):Hide(); + + -- Hide the trigger config frame. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); +end + + + +------------------------------------------------------------------------------------- +-- Dropdown functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Initializes all the dropdowns. +-- ********************************************************************************** +function MikSBTOpt.InitDropdowns() + -- Setup the available fonts with the inherit option. + availableFontsInherit = MikSBT.CopyTable(MikSBT.AVAILABLE_FONTS); + table.insert(availableFontsInherit, 1, {Name=MikSBTOpt.MSG_INHERIT_TEXT}); + for key, data in availableFontsInherit do + data.Value = key - 1; + end + + -- Setup the available font outlines with the inherit option. + availableOutlinesInherit = MikSBT.CopyTable(MikSBT.AVAILABLE_OUTLINES); + table.insert(availableOutlinesInherit, 1, {Name=MikSBTOpt.MSG_INHERIT_TEXT}); + for key, data in availableOutlinesInherit do + data.Value = key - 1; + end + + -- Setup the available profiles. + local availableProfiles = {}; + for profileName, _ in MikSBT_Save.Profiles do + availableProfiles[profileName] = {Name=profileName}; + end + + -- Setup the available scroll areas. + local availableScrollAreas = {}; + availableScrollAreas["Incoming"] = {Name=MikSBTOpt.TABS[TAB_INDEX_INCOMING].Label}; + availableScrollAreas["Outgoing"] = {Name=MikSBTOpt.TABS[TAB_INDEX_OUTGOING].Label}; + availableScrollAreas["Notification"] = {Name=MikSBTOpt.TABS[TAB_INDEX_NOTIFICATION].Label}; + + + + -- Initialize the profile dropdown. + local key = "Tab1FrameProfileDropdown"; + MikSBTOpt.SetupDropdown(OPTIONS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.DROPDOWNS[key].Tooltip, MikSBTOpt.ProfileDropdownOnClick, availableProfiles, nil); + MikSBTOpt.PopulateDropdown(OPTIONS_FRAME_NAME .. key, MikSBT_Save.CurrentProfile); + + + -- Initialize the scroll area dropdown. + key = "ScrollAreaDropdown"; + MikSBTOpt.SetupDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.DROPDOWNS[key].Tooltip, MikSBTOpt.ScrollAreaDropdownOnClick, availableScrollAreas, nil); + + -- Initialize the text align dropdown. + key = "TextAlignDropdown"; + MikSBTOpt.SetupDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.DROPDOWNS[key].Tooltip, MikSBTOpt.DropdownOnClick, MikSBT.AVAILABLE_TEXT_ALIGNS, nil); + + -- Initialize the animation styles dropdown. + key = "AnimationStyleDropdown"; + MikSBTOpt.SetupDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.DROPDOWNS[key].Tooltip, MikSBTOpt.DropdownOnClick, MikSBT.AVAILABLE_ANIMATION_STYLES, nil); + + -- Initialize the scroll direction dropdown. + key = "ScrollDirectionDropdown"; + MikSBTOpt.SetupDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.DROPDOWNS[key].Tooltip, MikSBTOpt.DropdownOnClick, MikSBT.AVAILABLE_SCROLL_DIRECTIONS, nil); + + -- Initialize the trigger types dropdown. + key = "TriggerTypeDropdown"; + MikSBTOpt.SetupDropdown(TRIGGER_CONFIGURATION_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.DROPDOWNS[key].Tooltip, MikSBTOpt.TriggerTypeDropdownOnClick, MikSBT.AVAILABLE_TRIGGER_TYPES, nil); + UIDropDownMenu_SetWidth(150, getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. key)); +end + + +-- ********************************************************************************** +-- Sets up a dropdown with the passed information. +-- ********************************************************************************** +function MikSBTOpt.SetupDropdown(frameName, label, tooltip, clickFunc, options, varPath) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + associatedFrame.tooltipText = tooltip; + associatedFrame.clickFunction = clickFunc; + associatedFrame.menuOptions = options; + associatedFrame.associatedVarPath = varPath; + + -- Set the label for the dropdown. + getglobal(frameName .. "Label"):SetText(label); + + -- Initialize the dropdown. + UIDropDownMenu_Initialize(associatedFrame, MikSBTOpt.DropdownInit); + end +end + + +-- ********************************************************************************** +-- Sets the dropdown's selected value for the passed frame name. If a value is passed +-- it is used, otherwise the function will attempt to get the value from the +-- associatedVarPath field of the dropdown frame. +-- ********************************************************************************** +function MikSBTOpt.PopulateDropdown(frameName, selectValue) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + local tempValue = selectValue; + + -- Check if no selected value was passed. + if (tempValue == nil) then + -- Check if there is an associated variable path for the dropdown. + if (associatedFrame.associatedVarPath) then + -- Get the selected value from the current profile. + tempValue = MikSBT.GetOptionFromVarPath(associatedFrame.associatedVarPath); + end + end + + -- Make sure there is a value to be selected. + if (tempValue ~= nil) then + -- Set the default selected value. + UIDropDownMenu_SetSelectedValue(associatedFrame, tempValue); + + -- Set the text for the default selected value. + for key, optionData in associatedFrame.menuOptions do + -- If there is no value for the option data, use the key as the value. + local optionValue = optionData.Value; + if (optionValue == nil) then + optionValue = key; + end + local selectedValue = associatedFrame.selectedValue; + + if (optionValue == selectedValue) then + UIDropDownMenu_SetText(optionData.Name, associatedFrame); + end + end + end + end +end + + +-- ********************************************************************************** +-- Called when a drop down menu is initialized (shown). +-- ********************************************************************************** +function MikSBTOpt.DropdownInit() + local info = {}; + + -- Check if the dropdown frame is the options frame and exit. + local dropdownFrame = this; + if (dropdownFrame:GetName() == OPTIONS_FRAME_NAME) then + return; + end + + -- Check if dropdownFrame is a button and not the actual dropdown itself. + if (not dropdownFrame.menuOptions) then + dropdownFrame = this:GetParent(); + end + + -- Make sure there is dropdown data. + if (dropdownFrame.menuOptions) then + -- Loop through all of the options for dropdown. + for key, optionData in dropdownFrame.menuOptions do + + -- If the option data has a value use it otherwise use the key as the value. + local tempValue = optionData.Value; + if (tempValue == nil) then + tempValue = key; + end + + -- Add the option to the dropdown list. + info = {}; + info.text = optionData.Name; + info.value = tempValue; + info.func = dropdownFrame.clickFunction; + info.arg1 = dropdownFrame:GetName(); + UIDropDownMenu_AddButton(info); + end + end +end + + +-- ********************************************************************************** +-- Called when one of the dropdown menu options is clicked. +-- ********************************************************************************** +function MikSBTOpt.DropdownOnClick(frameName) + -- Set the selected dropdown option to the appropriate value. + UIDropDownMenu_SetSelectedValue(getglobal(frameName), this.value); + + -- Get the associated variable path. + local associatedVarPath = getglobal(frameName).associatedVarPath; + + -- Make sure there is a variable path. + if (associatedVarPath) then + -- Update current profile data. + MikSBT.SetOptionFromVarPath(associatedVarPath, this.value); + end +end + + +-- ********************************************************************************** +-- Called when one of the dropdown menu options on the font settings frame is clicked. +-- ********************************************************************************** +function MikSBTOpt.FontSettingsDropdownOnClick(frameName) + -- Call the normal dropdown OnClick handler. + MikSBTOpt.DropdownOnClick(frameName); + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +-- ********************************************************************************** +-- Called when one of the profile dropdown menu options is clicked. +-- ********************************************************************************** +function MikSBTOpt.ProfileDropdownOnClick(frameName) + -- Call the normal dropdown OnClick handler. + MikSBTOpt.DropdownOnClick(frameName); + + -- Enable and disable the load and delete profile buttons. + MikSBTOpt.EnableDisableProfileButtons(); +end + + +-- ********************************************************************************** +-- Called when one of the scroll area dropdown menu options is clicked. +-- ********************************************************************************** +function MikSBTOpt.ScrollAreaDropdownOnClick(frameName) + -- Save the settings in the controls to the control frame's current settings. + MikSBTOpt.SaveScrollAreaControlsToCurrentSettings(); + + -- Call the normal dropdown OnClick handler. + MikSBTOpt.DropdownOnClick(frameName); + + -- Get the selected scroll area. + scrollArea = UIDropDownMenu_GetSelectedValue(getglobal(frameName)); + if (scrollArea ~= nil) then + -- Populate the controls for the scroll area. + MikSBTOpt.PopulateScrollAreaControls(scrollArea); + end +end + +-- ********************************************************************************** +-- Called when the trigger type dropdown menu is clicked. +-- ********************************************************************************** +function MikSBTOpt.TriggerTypeDropdownOnClick(frameName) + -- Set the selected dropdown option to the appropriate value. + UIDropDownMenu_SetSelectedValue(getglobal(frameName), this.value); + + -- Setup the trigger controls appropriately depending on the trigger type. + MikSBTOpt.SetupVariableTriggerControls(getglobal(frameName):GetParent().TriggerKey, this.value == MikCEH.TRIGGERTYPE_SEARCH_PATTERN); +end + + +-- ********************************************************************************** +-- This function refreshes the options for the profiles dropdown. +-- ********************************************************************************** +function MikSBTOpt.RefreshProfilesDropdown() + -- Setup the available profiles. + local availableProfiles = {}; + for profileName, _ in MikSBT_Save.Profiles do + availableProfiles[profileName] = {Name=profileName}; + end + + -- Reset the available options for the profiles dropdown. + getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameProfileDropdown").menuOptions = availableProfiles; +end + + + +------------------------------------------------------------------------------------- +-- Checkbox functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Initializes all the checkboxes. +-- ********************************************************************************** +function MikSBTOpt.InitCheckboxes() + -- Initialize the Disable MSBT checkbox. + local key = "Tab1FrameDisableCheckbox"; + MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, nil); + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. key, MikSBT_Save.UserDisabled); + + -- Initialize the Show Partial Effects checkbox. + key = "Tab1FrameShowPartialEffectsCheckbox"; + MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "ShowPartialEffects"); + + -- Initialize the Use Sticky Crits checkbox. + key = "Tab1FrameUseStickyCritsCheckbox"; + MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "UseStickyCrits"); + + -- Initialize the Show Overheals checkbox. + key = "Tab1FrameShowOverhealsCheckbox"; + MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "ShowOverheals"); + + -- Initialize the Show Game Damage checkbox. + key = "Tab1FrameShowGameDamageCheckbox"; + MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "ShowGameDamage"); + + + -- Initialize the show scroll area events checkboxes. + key = "Tab2FrameShowEventsCheckbox"; MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "DisplaySettings.Incoming.Show"); + key = "Tab3FrameShowEventsCheckbox"; MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "DisplaySettings.Outgoing.Show"); + key = "Tab4FrameShowEventsCheckbox"; MikSBTOpt.SetupCheckbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.CHECKBOXES[key].Tooltip, "DisplaySettings.Notification.Show"); + + + -- Initialize the trigger config class checkboxes. + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "AllClassesCheckbox", ALL, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "DruidCheckbox", MikSBTOpt.MSG_CLASS_NAME_DRUID, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "HunterCheckbox", MikSBTOpt.MSG_CLASS_NAME_HUNTER, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "MageCheckbox", MikSBTOpt.MSG_CLASS_NAME_MAGE, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "PaladinCheckbox", MikSBTOpt.MSG_CLASS_NAME_PALADIN, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "PriestCheckbox", MikSBTOpt.MSG_CLASS_NAME_PRIEST, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "RogueCheckbox", MikSBTOpt.MSG_CLASS_NAME_ROGUE, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "ShamanCheckbox", MikSBTOpt.MSG_CLASS_NAME_SHAMAN, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarlockCheckbox", MikSBTOpt.MSG_CLASS_NAME_WARLOCK, nil, nil); + MikSBTOpt.SetupCheckbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarriorCheckbox", MikSBTOpt.MSG_CLASS_NAME_WARRIOR, nil, nil); + +end + + +-- ********************************************************************************** +-- Sets up a checkbox with the passed information. +-- ********************************************************************************** +function MikSBTOpt.SetupCheckbox(frameName, label, tooltip, varPath) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + associatedFrame.associatedVarPath = varPath; + associatedFrame.tooltipText = tooltip; + + -- Set the text for the checkbox. + if (label) then + getglobal(frameName .. "Text"):SetText(label); + end + end +end + + +-- ********************************************************************************** +-- Sets the checkbox's checked state for the passed frame name. If a value is passed +-- it is used, otherwise the function will attempt to get the value from the +-- associatedVarPath field of the checkbox frame. +-- ********************************************************************************** +function MikSBTOpt.PopulateCheckbox(frameName, isChecked) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + local tempValue = isChecked; + + -- Check if no value was passed. + if (tempValue == nil) then + -- Check if there is an associated variable path for the checkbox. + if (associatedFrame.associatedVarPath) then + -- Get the selected value from the current profile. + tempValue = MikSBT.GetOptionFromVarPath(associatedFrame.associatedVarPath); + end + end + + -- Make sure there is a value to set. + if (tempValue ~= nil) then + -- Set the checkbox checked state appropriately. + if (tempValue == true) then + associatedFrame:SetChecked(1); + else + associatedFrame:SetChecked(0); + end + end + end +end + + +-- ********************************************************************************** +-- Called when a checkbox is clicked. +-- ********************************************************************************** +function MikSBTOpt.CheckboxOnClick() + -- Disable MSBT checkbox. + if (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameDisableCheckbox") then + -- Check if the checkbox is checked. + if (this:GetChecked() == 1) then + MikSBT.SetOptionUserDisabled(true); + else + MikSBT.SetOptionUserDisabled(false); + end + + -- Return from the function. + return; + + -- Show Game Damage checkbox. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameShowGameDamageCheckbox") then + -- Check if the checkbox is checked. + if (this:GetChecked() == 1) then + MikSBT.SetOptionShowGameDamage(true); + else + MikSBT.SetOptionShowGameDamage(false); + end + + -- Return from the function. + return; + + -- Inherit font size, or inherit critical font size checkbox. + elseif (this:GetName() == FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox" or + this:GetName() == FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox") then + + -- Call the font settings checkbox OnClick handler. + MikSBTOpt.FontSettingsCheckboxOnClick(); + + -- Return from the function. + return; + + -- Trigger frame all classes checkbox. + elseif (this:GetName() == TRIGGER_CONFIGURATION_FRAME_NAME .. "AllClassesCheckbox") then + -- Hide or show the individual class checkboxes depending on the state of the all classes + -- checkbox. + if (this:GetChecked() == 1) then + MikSBTOpt.HideTriggerClassCheckboxes(); + else + MikSBTOpt.ShowTriggerClassCheckboxes(); + end + + --Trigger frame trigger event checkbox. + elseif (string.find(this:GetName(), TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent")) then + -- Make sure there is an associated trigger event. + if (this.TriggerEvent) then + local triggerFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME); + if (this:GetChecked()) then + triggerFrame.SelectedTriggerEvents[this.TriggerEvent] = true; + else + triggerFrame.SelectedTriggerEvents[this.TriggerEvent] = nil; + end + end + + end + + -- Check if there is an associated variable path and set it to the correct value. + if (this.associatedVarPath) then + if (this:GetChecked() == 1) then + -- Set the associated variable in the current profile to the inherited value. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, true); + else + -- Set the associated variable in the current profile to the inherited value. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, false); + end + end +end + + +-- ********************************************************************************** +-- Called when an enable trigger checkbox is clicked. +-- ********************************************************************************** +function MikSBTOpt.EnableTriggerCheckboxOnClick() + -- Check if there is an associated variable path and set it to the correct value. + if (this.associatedVarPath) then + if (this:GetChecked() == 1) then + -- Set the associated variable in the current profile to the inherited value. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, true); + else + -- Set the associated variable in the current profile to the inherited value. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, false); + end + end + + -- Reregister all of the triggers. + MikSBT.RegisterTriggers(); +end + + +-- ********************************************************************************** +-- Called when a checkbox in the font settings area is clicked. +-- ********************************************************************************** +function MikSBTOpt.FontSettingsCheckboxOnClick() + local associatedSlider; + local inheritedValue; + local sliderKey; + + -- Check if it's the inherit font size checkbox. + if (this:GetName() == FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox") then + sliderKey = "FontSizeSlider"; + associatedSlider = getglobal(FONT_SETTINGS_FRAME_NAME .. sliderKey); + inheritedValue = this:GetParent().InheritedFontSize; + elseif (this:GetName() == FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox") then + sliderKey = "FontSizeCritSlider"; + associatedSlider = getglobal(FONT_SETTINGS_FRAME_NAME .. sliderKey); + inheritedValue = this:GetParent().InheritedFontSizeCrit; + end + + -- Make sure there is an associated variable path. + if (this.associatedVarPath) then + if (this:GetChecked() == 1) then + -- Set the associated variable in the current profile to inherit. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, 0); + + -- Hide the associated slider. + associatedSlider:Hide(); + else + -- Set the associated variable in the current profile to the inherited value. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, inheritedValue); + + -- Show the associated slider. + associatedSlider:Show(); + + -- Populate the slider. + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. sliderKey); + end + end + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +------------------------------------------------------------------------------------- +-- Slider functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Initializes all the sliders. +-- ********************************************************************************** +function MikSBTOpt.InitSliders() + -- Initialize the Animation Step slider. + local key = "Tab1FrameAnimationStepSlider"; + MikSBTOpt.SetupSlider(OPTIONS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.SLIDERS[key].Tooltip, MIN_ANIMATION_STEP, MAX_ANIMATION_STEP, 1, "AnimationStep"); + + -- Initialize the scroll height slider. + key = "ScrollHeightSlider"; + MikSBTOpt.SetupSlider(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.SLIDERS[key].Tooltip, MIN_SCROLL_HEIGHT, MAX_SCROLL_HEIGHT, 2, nil); + + key = "ThresholdSlider"; + MikSBTOpt.SetupSlider(TRIGGER_CONFIGURATION_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.SLIDERS[key].Tooltip, 1, 100, 1, nil); +end + + +-- ********************************************************************************** +-- Sets up a slider with the passed data. +-- ********************************************************************************** +function MikSBTOpt.SetupSlider(frameName, label, tooltip, minValue, maxValue, step, varPath) + local associatedSliderFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedSliderFrame) then + associatedSliderFrame.savedLabel = label; + associatedSliderFrame.tooltipText = tooltip; + associatedSliderFrame.associatedVarPath = varPath; + + -- Set the text for the slider. + getglobal(frameName .. "Text"):SetText(label); + + -- Set the min/max values for the slider. + associatedSliderFrame:SetMinMaxValues(minValue, maxValue); + associatedSliderFrame:SetValueStep(step); + end +end + + +-- ********************************************************************************** +-- Sets the slider's value for the passed frame name. If a value is passed +-- it is used, otherwise the function will attempt to get the value from the +-- associatedVarPath field of the slider frame. +-- ********************************************************************************** +function MikSBTOpt.PopulateSlider(frameName, sliderValue) + local associatedSliderFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedSliderFrame) then + local tempValue = sliderValue; + + -- Check if no value was passed. + if (tempValue == nil) then + -- Check if there is an associated variable path for the slider. + if (associatedSliderFrame.associatedVarPath) then + -- Get the selected value from the current profile. + tempValue = MikSBT.GetOptionFromVarPath(associatedSliderFrame.associatedVarPath); + end + end + + -- Make sure there is a value to set. + if (tempValue ~= nil) then + -- Set the slider's value to the appropriate value. + associatedSliderFrame:SetValue(tempValue); + + -- Update the label to reflect the new value. + getglobal(frameName .. "Text"):SetText(associatedSliderFrame.savedLabel .. ": " .. associatedSliderFrame:GetValue()); + end + end +end + + +-- ********************************************************************************** +-- Called when a slider values changes. +-- ********************************************************************************** +function MikSBTOpt.SliderOnValueChanged() + -- Update the label to reflect the new value. + getglobal(this:GetName() .. "Text"):SetText(this.savedLabel .. ": " .. this:GetValue()); + + -- Check if the slider has an associated variable path. + if (this.associatedVarPath) then + -- Update current profile data. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, this:GetValue()); + end +end + + +-- ********************************************************************************** +-- Called when a font settings slider values changes. +-- ********************************************************************************** +function MikSBTOpt.FontSettingsSliderOnValueChanged() + -- Call the normal slider OnValueChanged handler. + MikSBTOpt.SliderOnValueChanged(); + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +-- ********************************************************************************** +-- Called when the scroll area mover control slider value changes. +-- ********************************************************************************** +function MikSBTOpt.ScrollAreaMoverControlSliderOnValueChanged() + -- Call the normal slider OnValueChanged handler. + MikSBTOpt.SliderOnValueChanged(); + + -- Get the currently selected scroll area and the scroll area mover control frame. + local scrollArea = UIDropDownMenu_GetSelectedValue(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollAreaDropdown")); + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + + -- Save the scroll height for the selected scroll area. + controlFrame.MSBTCurrentSettings[scrollArea .. "ScrollHeight"] = this:GetValue(); + + -- Reposition the scroll area mover frames. + MikSBTOpt.RepositionScrollAreaMoverFrames(); +end + + +------------------------------------------------------------------------------------- +-- Button Functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Initializes all the buttons. +-- ********************************************************************************** +function MikSBTOpt.InitButtons() + -- Initialize the edit font settings buttons for master, incoming, outgoing, and notification. + local key = "Tab1FrameEditFontSettingsButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "Tab2FrameEditFontSettingsButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "Tab3FrameEditFontSettingsButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "Tab4FrameEditFontSettingsButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + + + -- Initialize the profile manipulation buttons. + key = "Tab1FrameLoadProfileButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "Tab1FrameDeleteProfileButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "Tab1FrameResetProfileButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "Tab1FrameCreateProfileButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + + + -- Initialize the configure scroll area buttons for incoming, outgoing, and notification. + key = "Tab2FrameConfigureScrollAreaButton"; + local commonLabel = MikSBTOpt.BUTTONS[key].Label; + local commonTooltip = MikSBTOpt.BUTTONS[key].Tooltip; + MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, commonLabel, commonTooltip); + key = "Tab3FrameConfigureScrollAreaButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, commonLabel, commonTooltip); + key = "Tab4FrameConfigureScrollAreaButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, commonLabel, commonTooltip); + + + -- Initialize the scroll area mover buttons. + key = "PreviewButton"; MikSBTOpt.SetupButton(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "SaveButton"; MikSBTOpt.SetupButton(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "CancelButton"; MikSBTOpt.SetupButton(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + + -- Initialize the add trigger button. + key = "Tab5FrameAddTriggerButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + + -- Initialize the save and cancel trigger buttons. + key = "SaveTriggerButton"; MikSBTOpt.SetupButton(TRIGGER_CONFIGURATION_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + key = "CancelTriggerButton"; MikSBTOpt.SetupButton(TRIGGER_CONFIGURATION_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); + + -- Initialize the add suppression button. + key = "Tab6FrameAddSuppressionButton"; MikSBTOpt.SetupButton(OPTIONS_FRAME_NAME .. key, MikSBTOpt.BUTTONS[key].Label, MikSBTOpt.BUTTONS[key].Tooltip); +end + + +-- ********************************************************************************** +-- Sets up a button with the passed data. +-- ********************************************************************************** +function MikSBTOpt.SetupButton(frameName, label, tooltip) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + associatedFrame.tooltipText = tooltip; + + -- Set the text for the button. + associatedFrame:SetText(label); + associatedFrame:SetWidth(associatedFrame:GetTextWidth() + 40); + end +end + + +-- ********************************************************************************** +-- Called when a button is clicked. +-- ********************************************************************************** +function MikSBTOpt.ButtonOnClick() + -- Edit master font settings button. + if (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameEditFontSettingsButton") then + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].FontSettingsTitle); + MikSBTOpt.ShowMasterFontSettings(); + + -- Edit incoming font settings button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab2FrameEditFontSettingsButton") then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab2FrameMessageEditbox"); + + -- Get the font settings frame, anchor it to the buton pushed, raise it, and change the title. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(MikSBTOpt.TABS[TAB_INDEX_INCOMING].Label); + MikSBTOpt.ShowScrollAreaFontSettings("Incoming"); + + + -- Edit outgoing font settings button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab3FrameEditFontSettingsButton") then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab3FrameMessageEditbox"); + + -- Get the font settings frame, anchor it to the buton pushed, raise it, and change the title. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(MikSBTOpt.TABS[TAB_INDEX_OUTGOING].Label); + MikSBTOpt.ShowScrollAreaFontSettings("Outgoing"); + + + -- Edit notification font settings button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab4FrameEditFontSettingsButton") then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab4FrameMessageEditbox"); + + -- Get the font settings frame, anchor it to the buton pushed, raise it, and change the title. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(MikSBTOpt.TABS[TAB_INDEX_NOTIFICATION].Label); + MikSBTOpt.ShowScrollAreaFontSettings("Notification"); + + + -- Load profile button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameLoadProfileButton") then + -- Select the appropriate profile and update the current profile label. + MikSBT.SelectProfile(UIDropDownMenu_GetSelectedValue(getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameProfileDropdown"))); + getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameCurrentProfileLabel"):SetText(MikSBTOpt.MSG_CURRENT_PROFILE_TEXT .. ": " .. MikSBT_Save.CurrentProfile); + + -- Enable and disable the load and delete profile buttons. + MikSBTOpt.EnableDisableProfileButtons(); + + -- Repopulate the options. + MikSBTOpt.PopulateProfileOptions(); + + + -- Delete profile button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameDeleteProfileButton") then + local profileName = UIDropDownMenu_GetSelectedValue(getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameProfileDropdown")); + MikSBT.DeleteProfile(profileName); + + -- Refresh the available profiles for the dropdown. + MikSBTOpt.RefreshProfilesDropdown(); + + -- Select the current profile in the dropdown and update the current profile label. + MikSBTOpt.PopulateDropdown(OPTIONS_FRAME_NAME .. "Tab1FrameProfileDropdown", MikSBT_Save.CurrentProfile); + getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameCurrentProfileLabel"):SetText(MikSBTOpt.MSG_CURRENT_PROFILE_TEXT .. ": " .. MikSBT_Save.CurrentProfile); + + -- Enable and disable the load and delete profile buttons. + MikSBTOpt.EnableDisableProfileButtons(); + + -- Repopulate the options. + MikSBTOpt.PopulateProfileOptions(); + + -- Reset profile button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameResetProfileButton") then + local profileName = UIDropDownMenu_GetSelectedValue(getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameProfileDropdown")); + MikSBT.ResetProfile(profileName, true); + + -- Repopulate the options. + MikSBTOpt.PopulateProfileOptions(); + + + -- Create profile button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameCreateProfileButton") then + local profileNameEditbox = getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameNewProfileNameEditbox"); + local profileName = profileNameEditbox:GetText(); + + -- Clear the text in the profile name editbox. + profileNameEditbox:SetText(""); + + -- Make sure there is a profile name. + if (profileName ~= "") then + MikSBT.CreateProfile(profileName); + + -- Refresh the available profiles for the dropdown. + MikSBTOpt.RefreshProfilesDropdown(); + + -- Enable and disable the load and delete profile buttons. + MikSBTOpt.EnableDisableProfileButtons(); + + -- Hide the font settings frame when profile options are populated. + getglobal(FONT_SETTINGS_FRAME_NAME):Hide(); + + -- Hide the trigger frame when profile options are populated. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); + end + + + -- Configure incoming scroll area button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab2FrameConfigureScrollAreaButton") then + -- Setup and show the scroll area mover frames. + MikSBTOpt.ShowScrollAreaMoverFrames("Incoming"); + + + -- Configure outgoing scroll area button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab3FrameConfigureScrollAreaButton") then + -- Setup and show the scroll area mover frames. + MikSBTOpt.ShowScrollAreaMoverFrames("Outgoing"); + + + -- Configure notification scroll area button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab4FrameConfigureScrollAreaButton") then + -- Setup and show the scroll area mover frames. + MikSBTOpt.ShowScrollAreaMoverFrames("Notification"); + + + -- Scroll area mover preview button. + elseif (this:GetName() == SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "PreviewButton") then + -- Preview a test message in the new position. + MikSBTOpt.ScrollAreaMoverPreviewOnClick(); + + + -- Scroll area mover save button. + elseif (this:GetName() == SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "SaveButton") then + -- Save the new position for the scroll area. + MikSBTOpt.ScrollAreaMoverSaveOnClick(); + + + -- Scroll area mover cancel button. + elseif (this:GetName() == SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "CancelButton") then + -- Cancel the scroll area movement. + MikSBTOpt.ScrollAreaMoverCancelOnClick(); + + + -- Incoming event font settings button. + elseif (this:GetParent().EventArea == "Incoming") then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab2FrameMessageEditbox"); + + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + + -- Make sure the event type is set for the button. + if (this:GetParent().EventType ~= nil) then + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(this:GetParent().FontFrameTitle); + MikSBTOpt.ShowEventFontSettings("Incoming", this:GetParent().EventType); + end + + + -- Outgoing event font settings button. + elseif (this:GetParent().EventArea == "Outgoing") then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab3FrameMessageEditbox"); + + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + + -- Make sure the event type is set for the button. + if (this:GetParent().EventType ~= nil) then + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(this:GetParent().FontFrameTitle); + MikSBTOpt.ShowEventFontSettings("Outgoing", this:GetParent().EventType); + end + + + -- Notification event font settings button. + elseif (this:GetParent().EventArea == "Notification") then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab4FrameMessageEditbox"); + + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + + -- Make sure the event type is set for the button. + if (this:GetParent().EventType ~= nil) then + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(this:GetParent().FontFrameTitle); + MikSBTOpt.ShowEventFontSettings("Notification", this:GetParent().EventType); + end + + + -- Add trigger button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab5FrameAddTriggerButton") then + -- Hide the trigger configuration frame. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); + + -- Add a new trigger. + MikSBTOpt.AddNewTrigger(); + + + -- Trigger event font settings button. + elseif (this:GetParent().EventArea == "Trigger" and string.find(this:GetName(), "EditFontSettingsButton")) then + -- Hide the trigger event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab5FrameMessageEditbox"); + + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + fontSettingsFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + fontSettingsFrame:Raise(); + + -- Make sure the trigger key is set for the button. + if (this:GetParent().TriggerKey ~= nil) then + getglobal(FONT_SETTINGS_FRAME_NAME .. "Title"):SetText(this:GetParent().FontFrameTitle); + MikSBTOpt.ShowTriggerFontSettings(this:GetParent().TriggerKey); + end + + + -- Trigger edit config button. + elseif (this:GetParent().EventArea == "Trigger" and string.find(this:GetName(), "EditTriggerSettingsButton")) then + -- Hide the trigger event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab5FrameMessageEditbox"); + + local triggerConfigFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME); + triggerConfigFrame:SetPoint("TOPLEFT", this, "BOTTOMLEFT"); + triggerConfigFrame:Raise(); + + -- Make sure the trigger key is set for the button. + if (this:GetParent().TriggerKey ~= nil) then + MikSBTOpt.ShowTriggerConfig(this:GetParent().TriggerKey); + end + + + -- Delete trigger button. + elseif (string.find(this:GetName(), "DeleteTriggerButton")) then + -- Hide the trigger event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab5FrameMessageEditbox"); + + -- Hide the trigger configuration frame. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); + + -- Make sure the trigger key is set for the button. + if (this:GetParent().TriggerKey ~= nil) then + MikSBTOpt.DeleteTrigger(this:GetParent().TriggerKey); + end + + + -- Save trigger config button. + elseif (this:GetName() == TRIGGER_CONFIGURATION_FRAME_NAME .. "SaveTriggerButton") then + -- Save the trigger config and hide the frame. + MikSBTOpt.SaveTriggerConfig(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); + + -- Cancel trigger config button. + elseif (this:GetName() == TRIGGER_CONFIGURATION_FRAME_NAME .. "CancelTriggerButton") then + -- Hide the frame. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME):Hide(); + + + -- Add suppression button. + elseif (this:GetName() == OPTIONS_FRAME_NAME .. "Tab6FrameAddSuppressionButton") then + -- Add a new suppression. + MikSBTOpt.AddNewSuppression(); + + -- Delete suppression button. + elseif (string.find(this:GetName(), "DeleteSuppressionButton")) then + -- Hide the suppression event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab6FrameMessageEditbox"); + + -- Make sure the suppression key is set for the button. + local suppressionKey = this:GetParent().SuppressionKey; + if (suppressionKey ~= nil) then + -- Remove the suppression. + MikSBT.CurrentProfile.Suppressions[suppressionKey] = nil; + + -- Repopulate the suppression settings. + MikSBTOpt.PopulateSuppressionSettings(); + end + + end +end + + + + +-- ********************************************************************************** +-- This function will enable and disable the load and delete profile +-- buttons according to which profile is selected. +-- ********************************************************************************** +function MikSBTOpt.EnableDisableProfileButtons() + local profileDropdown = getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameProfileDropdown"); + local deleteProfileButton = getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameDeleteProfileButton"); + local loadProfileButton = getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameLoadProfileButton"); + + -- Enable the profile button manipulation buttons. + deleteProfileButton:Enable(); + loadProfileButton:Enable(); + + -- Check if the default profile is selected and disable the delete profile button. + if (UIDropDownMenu_GetSelectedValue(profileDropdown) == MikSBT.DEFAULT_PROFILE_NAME) then + deleteProfileButton:Disable(); + end + + -- Check if it's the currently loaded profile and disable the load button. + if (UIDropDownMenu_GetSelectedValue(profileDropdown) == MikSBT_Save.CurrentProfile) then + loadProfileButton:Disable(); + end +end + + +------------------------------------------------------------------------------------- +-- Editbox Functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Initializes all the editboxes. +-- ********************************************************************************** +function MikSBTOpt.InitEditboxes() + -- Setup the new profile name editbox. + local key = "Tab1FrameNewProfileNameEditbox"; + MikSBTOpt.SetupEditbox(OPTIONS_FRAME_NAME .. key, MikSBTOpt.EDITBOXES[key].Label, MikSBTOpt.EDITBOXES[key].Tooltip, nil); + + -- Setup the scroll area X and Y offset editboxes. + key = "XOffsetEditbox"; MikSBTOpt.SetupEditbox(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.EDITBOXES[key].Label, MikSBTOpt.EDITBOXES[key].Tooltip, nil); + key = "YOffsetEditbox"; MikSBTOpt.SetupEditbox(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. key, MikSBTOpt.EDITBOXES[key].Label, MikSBTOpt.EDITBOXES[key].Tooltip, nil); + + key = "SearchPattern1Editbox"; MikSBTOpt.SetupEditbox(TRIGGER_CONFIGURATION_FRAME_NAME .. key, MikSBTOpt.EDITBOXES[key].Label, MikSBTOpt.EDITBOXES[key].Tooltip, nil); + key = "SearchPattern2Editbox"; MikSBTOpt.SetupEditbox(TRIGGER_CONFIGURATION_FRAME_NAME .. key, MikSBTOpt.EDITBOXES[key].Label, MikSBTOpt.EDITBOXES[key].Tooltip, nil); +end + + +-- ********************************************************************************** +-- Sets up an editbox with the passed data. +-- ********************************************************************************** +function MikSBTOpt.SetupEditbox(frameName, label, tooltip, varPath) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + associatedFrame.tooltipText = tooltip; + associatedFrame.associatedVarPath = varPath; + + -- Set the label for the editbox. + getglobal(frameName .. "Label"):SetText(label); + end +end + + +-- ********************************************************************************** +-- Sets the editbox's text for the passed frame name. If a value is passed +-- it is used, otherwise the function will attempt to get the value from the +-- associatedVarPath field of the editbox frame. +-- ********************************************************************************** +function MikSBTOpt.PopulateEditbox(frameName, editboxValue) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + local tempValue = editboxValue; + + -- Check if no value was passed. + if (tempValue == nil) then + -- Check if there is an associated variable path for the editbox. + if (associatedFrame.associatedVarPath) then + tempValue = MikSBT.GetOptionFromVarPath(associatedFrame.associatedVarPath); + end + end + + -- Make sure there is a value to set. + if (tempValue) then + -- Set the value. + associatedFrame:SetText(tempValue); + end + end +end + + +-- ********************************************************************************** +-- Called when the text changes in an edit box. +-- ********************************************************************************** +function MikSBTOpt.EditboxOnTextChanged() + -- New Profile Name. + if (this:GetName() == OPTIONS_FRAME_NAME .. "Tab1FrameNewProfileNameEditbox") then + local createProfileButton = getglobal(OPTIONS_FRAME_NAME .. "Tab1FrameCreateProfileButton"); + + -- Enable the create profile button. + createProfileButton:Enable(); + + -- Check if there is no text and disable the create profile button. + if (this:GetText() == "") then + createProfileButton:Disable(); + -- Check if the profile already exists. + elseif (MikSBT.ProfileExists(this:GetText())) then + createProfileButton:Disable(); + end + + + -- Scroll Area X Offset. + elseif (this:GetName() == SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "XOffsetEditbox") then + -- Get the number in the x offset editbox and make sure it's valid. + local xOffset = tonumber(this:GetText()); + if (xOffset ~= nil) then + MikSBTOpt.SaveScrollAreaControlsToCurrentSettings(); + MikSBTOpt.RepositionScrollAreaMoverFrames(); + end + + + -- Scroll Area Y Offset. + elseif (this:GetName() == SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "YOffsetEditbox") then + -- Get the number in the y offset editbox and make sure it's valid. + local yOffset = tonumber(this:GetText()); + if (yOffset ~= nil) then + MikSBTOpt.SaveScrollAreaControlsToCurrentSettings(); + MikSBTOpt.RepositionScrollAreaMoverFrames(); + end + + + else + -- Check if there is an associated variable path and set it to the correct value. + if (this.associatedVarPath) then + -- Set the associated variable in the current profile to the value. + MikSBT.SetOptionFromVarPath(this.associatedVarPath, this:GetText()); + end + end +end + + + + + +------------------------------------------------------------------------------------- +-- Color swatch functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Sets up a color swatch with the passed data. +-- ********************************************************************************** +function MikSBTOpt.SetupColorSwatch(frameName, tooltip, varPath) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + associatedFrame.tooltipText = tooltip; + associatedFrame.associatedVarPath = varPath; + end +end + + +-- ********************************************************************************** +-- Sets the color swatch's color for the passed frame name. If color values are +-- passed they are used, otherwise the function will attempt to get the values from +-- the associatedVarPath field of the color swatch frame. +-- ********************************************************************************** +function MikSBTOpt.PopulateColorSwatch(frameName, colorR, colorG, colorB) + local associatedFrame = getglobal(frameName); + + -- Make sure the associated frame exists. + if (associatedFrame) then + local tempR = colorR; + local tempG = colorG; + local tempB = colorB; + + -- Check if no r value was passed. + if (tempR == nil) then + -- Check if there is an associated variable path for the color swatch. + if (associatedFrame.associatedVarPath) then + tempR = MikSBT.GetOptionFromVarPath(associatedFrame.associatedVarPath .. ".r"); + end + end + + -- Check if no g value was passed. + if (tempG == nil) then + -- Check if there is an associated variable path for the color swatch. + if (associatedFrame.associatedVarPath) then + tempG = MikSBT.GetOptionFromVarPath(associatedFrame.associatedVarPath .. ".g"); + end + end + + -- Check if no b value was passed. + if (tempB == nil) then + -- Check if there is an associated variable path for the color swatch. + if (associatedFrame.associatedVarPath) then + tempB = MikSBT.GetOptionFromVarPath(associatedFrame.associatedVarPath .. ".b"); + end + end + + -- Make sure there are values to set. + if (tempR ~= nil and tempG ~= nil and tempB ~= nil) then + -- Set the values. + local objColorSwatchTexture = getglobal(associatedFrame:GetName() .. "NormalTexture"); + objColorSwatchTexture:SetVertexColor(tempR, tempG, tempB); + end + end +end + + +-- ********************************************************************************** +-- Called when on of the color swatches for an event is clicked. +-- ********************************************************************************** +function MikSBTOpt.ColorSwatchOnClick() + -- Check if the color swatch has an associated variable path. + if (this.associatedVarPath) then + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(this:GetParent():GetParent():GetName().. "MessageEditbox"); + + -- Get the r, g, and b values from the associated variable path and show the color picker. + this.r = MikSBT.GetOptionFromVarPath(this.associatedVarPath .. ".r"); + this.g = MikSBT.GetOptionFromVarPath(this.associatedVarPath .. ".g"); + this.b = MikSBT.GetOptionFromVarPath(this.associatedVarPath .. ".b"); + this.swatchFunc = MikSBTOpt.ColorSwatchSetColor; + this.cancelFunc = MikSBTOpt.ColorSwatchCancel; + ColorPickerFrame.associatedVarPath = this.associatedVarPath; + ColorPickerFrame.associatedColorSwatch = this; + UIDropDownMenuButton_OpenColorPicker(this); + end +end + + +-- ********************************************************************************** +-- Caled when a color is selected for an event in the color picker dialog. +-- ********************************************************************************** +function MikSBTOpt.ColorSwatchSetColor() + -- Get the red, green, and blue color values. + local r, g, b = ColorPickerFrame:GetColorRGB(); + + -- Make sure there is an associated color swatch. + if (ColorPickerFrame.associatedColorSwatch) then + -- Get the associated color swatch texture and make sure it's valid. + local objColorSwatchTexture = getglobal(ColorPickerFrame.associatedColorSwatch:GetName() .. "NormalTexture"); + if (objColorSwatchTexture) then + -- Set the associated + objColorSwatchTexture:SetVertexColor(r, g, b); + end + end + + + -- Check if the color swatch has an associated variable path. + if (ColorPickerFrame.associatedVarPath) then + -- Update current profile data. + MikSBT.SetOptionFromVarPath(ColorPickerFrame.associatedVarPath .. ".r", r); + MikSBT.SetOptionFromVarPath(ColorPickerFrame.associatedVarPath .. ".g", g); + MikSBT.SetOptionFromVarPath(ColorPickerFrame.associatedVarPath .. ".b", b); + end +end + + +-- ********************************************************************************** +-- Called when the color picker dialog is canceled. +-- ********************************************************************************** +function MikSBTOpt.ColorSwatchCancel(previousValues) + -- Make sure there is an associated color swatch. + if (ColorPickerFrame.associatedColorSwatch) then + -- Get the associated color swatch texture and make sure it's valid. + local objColorSwatchTexture = getglobal(ColorPickerFrame.associatedColorSwatch:GetName() .. "NormalTexture"); + if (objColorSwatchTexture) then + -- Set the associated + objColorSwatchTexture:SetVertexColor(previousValues.r, previousValues.g, previousValues.b); + end + end + + -- Check if the color swatch has an associated variable path. + if (ColorPickerFrame.associatedVarPath) then + -- Update current profile data. + MikSBT.SetOptionFromVarPath(ColorPickerFrame.associatedVarPath .. ".r", previousValues.r); + MikSBT.SetOptionFromVarPath(ColorPickerFrame.associatedVarPath .. ".g", previousValues.g); + MikSBT.SetOptionFromVarPath(ColorPickerFrame.associatedVarPath .. ".b", previousValues.b); + end +end + + + +------------------------------------------------------------------------------------- +-- Event settings functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Initializes all the events. +-- ********************************************************************************** +function MikSBTOpt.InitEvents() + local index; + local evtData; + + evtData = MikSBTOpt.INCOMING_EVENTS; + index = 1; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_DAMAGE"; + index = 2; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_MISS"; + index = 3; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_DODGE"; + index = 4; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_PARRY"; + index = 5; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_BLOCK"; + index = 6; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_ABSORB"; + index = 7; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_IMMUNE"; + index = 8; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_DAMAGE"; + index = 9; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_DOT"; + index = 10; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_MISS"; + index = 11; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_DODGE"; + index = 12; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_PARRY"; + index = 13; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_BLOCK"; + index = 14; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_RESIST"; + index = 15; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_ABSORB"; + index = 16; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_IMMUNE"; + index = 17; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_SPELL_REFLECT"; + index = 18; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_HEAL"; + index = 19; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_HOT"; + index = 20; evtData[index].EventType = "MSBT_EVENTTYPE_INCOMING_ENVIRONMENTAL"; + + + evtData = MikSBTOpt.OUTGOING_EVENTS; + index = 1; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_DAMAGE"; + index = 2; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_MISS"; + index = 3; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_DODGE"; + index = 4; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PARRY"; + index = 5; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_BLOCK"; + index = 6; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_ABSORB"; + index = 7; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_IMMUNE"; + index = 8; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_EVADE"; + index = 9; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_DAMAGE"; + index = 10; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_DOT"; + index = 11; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_MISS"; + index = 12; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_DODGE"; + index = 13; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_PARRY"; + index = 14; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_BLOCK"; + index = 15; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_RESIST"; + index = 16; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_ABSORB"; + index = 17; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_IMMUNE"; + index = 18; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_REFLECT"; + index = 19; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_SPELL_EVADE"; + index = 20; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_HEAL"; + index = 21; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_HOT"; + index = 22; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_DAMAGE"; + index = 23; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_MISS"; + index = 24; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_DODGE"; + index = 25; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_PARRY"; + index = 26; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_BLOCK"; + index = 27; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_ABSORB"; + index = 28; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_IMMUNE"; + index = 29; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_EVADE"; + index = 30; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_DAMAGE"; + index = 31; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_MISS"; + index = 32; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_DODGE"; + index = 33; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_PARRY"; + index = 34; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_BLOCK"; + index = 35; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_RESIST"; + index = 36; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_ABSORB"; + index = 37; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_IMMUNE"; + index = 38; evtData[index].EventType = "MSBT_EVENTTYPE_OUTGOING_PET_SPELL_EVADE"; + + + evtData = MikSBTOpt.NOTIFICATION_EVENTS; + index = 1; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_DEBUFF"; + index = 2; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_BUFF"; + index = 3; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_ITEM_BUFF"; + index = 4; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_BUFF_FADE"; + index = 5; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_COMBAT_ENTER"; + index = 6; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_COMBAT_LEAVE"; + index = 7; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_POWER_GAIN"; + index = 8; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_POWER_LOSS"; + index = 9; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_CP_GAIN"; + index = 10; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_CP_FULL"; + index = 11; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_HONOR_GAIN"; + index = 12; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_REP_GAIN"; + index = 13; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_REP_LOSS"; + index = 14; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_SKILL_GAIN"; + index = 15; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_EXPERIENCE_GAIN"; + index = 16; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_PC_KILLING_BLOW"; + index = 17; evtData[index].EventType = "MSBT_EVENTTYPE_NOTIFICATION_NPC_KILLING_BLOW"; +end + + +-- ********************************************************************************** +-- This function populates the incoming event settings. +-- ********************************************************************************** +function MikSBTOpt.PopulateIncomingEventSettings() + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab2FrameMessageEditbox"); + + -- Get the total number of events. + totalItems = table.getn(MikSBTOpt.INCOMING_EVENTS); + + -- Get the incoming event settings scroll frame and its offset. + local objFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab2FrameEventsScrollFrame"); + FauxScrollFrame_Update(objFrame, totalItems, EVENT_ITEMS_PER_PAGE, EVENT_ITEM_HEIGHT); + local skipCount = FauxScrollFrame_GetOffset(objFrame); + + local objEventSettings = nil; + -- Loop through all of the visible items. + for i = 1, EVENT_ITEMS_PER_PAGE do + objEventSettings = getglobal(OPTIONS_FRAME_NAME .. "Tab2FrameEvent" .. i); + + -- Make sure the event settings object exists. + if (objEventSettings) then + -- Check if there is not event settings data for the event settings slot. + if (i > totalItems) then + -- Hide the event settings. + objEventSettings:Hide(); + + -- There is data for the event settings slot. + else + -- Get the data for the event. + local evtData = MikSBTOpt.INCOMING_EVENTS[i+skipCount]; + + -- Make sure there is an associated event type and set it. + if (evtData.EventType ~= nil) then + objEventSettings.EventType = evtData.EventType; + end + + -- Set the font frame title to the checkbox's label and the event area. + objEventSettings.FontFrameTitle = evtData.Label; + objEventSettings.EventArea = "Incoming"; + + + -- Setup the color swatch and populate it. + MikSBTOpt.SetupColorSwatch(objEventSettings:GetName() .. "ColorSwatch", MikSBTOpt.MSG_EVENT_COLOR_SWATCH_TOOLTIP, "EventSettings." .. objEventSettings.EventType .. ".FontSettings.Color"); + MikSBTOpt.PopulateColorSwatch(objEventSettings:GetName() .. "ColorSwatch"); + + -- Setup and populate the event settings show checkbox. + MikSBTOpt.SetupCheckbox(objEventSettings:GetName() .. "ShowEventCheckbox", evtData.Label, evtData.Tooltips.ShowCheckbox, "EventSettings." .. objEventSettings.EventType .. ".Show"); + MikSBTOpt.PopulateCheckbox(objEventSettings:GetName() .. "ShowEventCheckbox"); + + + -- Set the message label to the appropriate text and the editbox's tooltip. + local objMessageLabel = getglobal(objEventSettings:GetName() .. "MessageLabel"); + if (objMessageLabel) then + -- Set the tooltip for the label. + objMessageLabel:GetParent().tooltipText = MikSBTOpt.MSG_EVENT_MESSAGE_LABEL_TOOLTIP; + if (objEventSettings.EventType ~= nil) then + objMessageLabel:SetText(MikSBT.GetOptionFromVarPath("EventSettings." .. objEventSettings.EventType .. ".Message")); + end + end + objEventSettings.EditboxTooltip = evtData.Tooltips.Editbox; + + -- Hide the sticky event checkbox. + getglobal(objEventSettings:GetName() .. "StickyEventCheckbox"):Hide(); + + + -- Setup the edit font settings button for the event. + MikSBTOpt.SetupButton(objEventSettings:GetName() .. "EditFontSettingsButton", MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_LABEL, MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_TOOLTIP); + + -- Show the event settings. + objEventSettings:Show(); + end + end + end + +end + + +-- ********************************************************************************** +-- This function populates the outgoing event settings. +-- ********************************************************************************** +function MikSBTOpt.PopulateOutgoingEventSettings() + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab3FrameMessageEditbox"); + + -- Get the total number of events. + totalItems = table.getn(MikSBTOpt.OUTGOING_EVENTS); + + -- Get the outgoing event settings scroll frame and its offset. + local objFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab3FrameEventsScrollFrame"); + FauxScrollFrame_Update(objFrame, totalItems, EVENT_ITEMS_PER_PAGE, EVENT_ITEM_HEIGHT); + local skipCount = FauxScrollFrame_GetOffset(objFrame); + + local objEventSettings = nil; + -- Loop through all of the visible items. + for i = 1, EVENT_ITEMS_PER_PAGE do + objEventSettings = getglobal(OPTIONS_FRAME_NAME .. "Tab3FrameEvent" .. i); + + -- Make sure the event settings object exists. + if (objEventSettings) then + -- Check if there is not event settings data for the event settings slot. + if (i > totalItems) then + -- Hide the event settings. + objEventSettings:Hide(); + + -- There is data for the event settings slot. + else + -- Get the data for the event. + local evtData = MikSBTOpt.OUTGOING_EVENTS[i+skipCount]; + + -- Make sure there is an associated event type and set it. + if (evtData.EventType ~= nil) then + objEventSettings.EventType = evtData.EventType; + end + + -- Set the font frame title to the checkbox's label and the event area. + objEventSettings.FontFrameTitle = evtData.Label; + objEventSettings.EventArea = "Outgoing"; + + + -- Setup the color swatch and populate it. + MikSBTOpt.SetupColorSwatch(objEventSettings:GetName() .. "ColorSwatch", MikSBTOpt.MSG_EVENT_COLOR_SWATCH_TOOLTIP, "EventSettings." .. objEventSettings.EventType .. ".FontSettings.Color"); + MikSBTOpt.PopulateColorSwatch(objEventSettings:GetName() .. "ColorSwatch"); + + -- Setup and populate the event settings show checkbox. + MikSBTOpt.SetupCheckbox(objEventSettings:GetName() .. "ShowEventCheckbox", evtData.Label, evtData.Tooltips.ShowCheckbox, "EventSettings." .. objEventSettings.EventType .. ".Show"); + MikSBTOpt.PopulateCheckbox(objEventSettings:GetName() .. "ShowEventCheckbox"); + + + -- Set the message label to the appropriate text and the editbox's tooltip. + local objMessageLabel = getglobal(objEventSettings:GetName() .. "MessageLabel"); + if (objMessageLabel) then + -- Set the tooltip for the label. + objMessageLabel:GetParent().tooltipText = MikSBTOpt.MSG_EVENT_MESSAGE_LABEL_TOOLTIP; + if (objEventSettings.EventType ~= nil) then + objMessageLabel:SetText(MikSBT.GetOptionFromVarPath("EventSettings." .. objEventSettings.EventType .. ".Message")); + end + end + objEventSettings.EditboxTooltip = evtData.Tooltips.Editbox; + + -- Hide the sticky event checkbox. + getglobal(objEventSettings:GetName() .. "StickyEventCheckbox"):Hide(); + + + -- Setup the edit font settings button for the event. + MikSBTOpt.SetupButton(objEventSettings:GetName() .. "EditFontSettingsButton", MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_LABEL, MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_TOOLTIP); + + -- Show the event settings. + objEventSettings:Show(); + end + end + end + +end + + +-- ********************************************************************************** +-- This function populates the notification event settings. +-- ********************************************************************************** +function MikSBTOpt.PopulateNotificationEventSettings() + -- Hide the event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab4FrameMessageEditbox"); + + -- Get the total number of events. + totalItems = table.getn(MikSBTOpt.NOTIFICATION_EVENTS); + + -- Get the notification event settings scroll frame and its offset. + local objFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab4FrameEventsScrollFrame"); + FauxScrollFrame_Update(objFrame, totalItems, EVENT_ITEMS_PER_PAGE, EVENT_ITEM_HEIGHT); + local skipCount = FauxScrollFrame_GetOffset(objFrame); + + local objEventSettings = nil; + -- Loop through all of the visible items. + for i = 1, EVENT_ITEMS_PER_PAGE do + objEventSettings = getglobal(OPTIONS_FRAME_NAME .. "Tab4FrameEvent" .. i); + + -- Make sure the event settings object exists. + if (objEventSettings) then + -- Check if there is not event settings data for the event settings slot. + if (i > totalItems) then + -- Hide the event settings. + objEventSettings:Hide(); + + -- There is data for the event settings slot. + else + -- Get the data for the event. + local evtData = MikSBTOpt.NOTIFICATION_EVENTS[i+skipCount]; + + -- Make sure there is an associated event type and set it. + if (evtData.EventType ~= nil) then + objEventSettings.EventType = evtData.EventType; + end + + -- Set the font frame title to the checkbox's label and the event area. + objEventSettings.FontFrameTitle = evtData.Label; + objEventSettings.EventArea = "Outgoing"; + + + -- Setup the color swatch and populate it. + MikSBTOpt.SetupColorSwatch(objEventSettings:GetName() .. "ColorSwatch", MikSBTOpt.MSG_EVENT_COLOR_SWATCH_TOOLTIP, "EventSettings." .. objEventSettings.EventType .. ".FontSettings.Color"); + MikSBTOpt.PopulateColorSwatch(objEventSettings:GetName() .. "ColorSwatch"); + + -- Setup and populate the event settings show checkbox. + MikSBTOpt.SetupCheckbox(objEventSettings:GetName() .. "ShowEventCheckbox", evtData.Label, evtData.Tooltips.ShowCheckbox, "EventSettings." .. objEventSettings.EventType .. ".Show"); + MikSBTOpt.PopulateCheckbox(objEventSettings:GetName() .. "ShowEventCheckbox"); + + + -- Set the message label to the appropriate text and the editbox's tooltip. + local objMessageLabel = getglobal(objEventSettings:GetName() .. "MessageLabel"); + if (objMessageLabel) then + -- Set the tooltip for the label. + objMessageLabel:GetParent().tooltipText = MikSBTOpt.MSG_EVENT_MESSAGE_LABEL_TOOLTIP; + if (objEventSettings.EventType ~= nil) then + objMessageLabel:SetText(MikSBT.GetOptionFromVarPath("EventSettings." .. objEventSettings.EventType .. ".Message")); + end + end + objEventSettings.EditboxTooltip = evtData.Tooltips.Editbox; + + -- Setup the sticky checkbox for the event settings and populate it. + MikSBTOpt.SetupCheckbox(objEventSettings:GetName() .. "StickyEventCheckbox", nil, MikSBTOpt.MSG_EVENT_STICKY_CHECKBOX_TOOLTIP, "EventSettings." .. objEventSettings.EventType .. ".IsSticky"); + MikSBTOpt.PopulateCheckbox(objEventSettings:GetName() .. "StickyEventCheckbox"); + getglobal(objEventSettings:GetName() .. "StickyEventCheckbox"):Show(); + + + + -- Setup the edit font settings button for the event. + MikSBTOpt.SetupButton(objEventSettings:GetName() .. "EditFontSettingsButton", MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_LABEL, MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_TOOLTIP); + + -- Show the event settings. + objEventSettings:Show(); + end + end + end + +end + + +-- ********************************************************************************** +-- This function populates the trigger event settings. +-- ********************************************************************************** +function MikSBTOpt.PopulateTriggerEventSettings() + -- Hide the trigger event message editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab5FrameMessageEditbox"); + + -- Get the total number of events. + totalItems = MikSBTOpt.CountTriggers(); + + -- Get the trigger settings scroll frame and its offset. + local objFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab5FrameTriggersScrollFrame"); + FauxScrollFrame_Update(objFrame, totalItems, TRIGGER_ITEMS_PER_PAGE, TRIGGER_ITEM_HEIGHT); + local skipCount = FauxScrollFrame_GetOffset(objFrame); + + local objTriggerEventSettings = nil; + -- Loop through all of the visible items. + for i = 1, TRIGGER_ITEMS_PER_PAGE do + objTriggerEventSettings = getglobal(OPTIONS_FRAME_NAME .. "Tab5FrameTrigger" .. i); + + -- Make sure the trigger settings object exists. + if (objTriggerEventSettings) then + -- Check if there is not trigger settings data for the trigger settings slot. + if (i > totalItems) then + -- Hide the trigger settings. + objTriggerEventSettings:Hide(); + + -- There is data for the trigger settings slot. + else + -- Get the data for the trigger. + local triggerKey, triggerSettings = MikSBTOpt.GetTriggerSettings(i+skipCount); + + -- Set the trigger key, font frame title to the checkbox's label and the event area. + objTriggerEventSettings.TriggerKey = triggerKey; + objTriggerEventSettings.FontFrameTitle = triggerSettings.EventSettings.Message; + objTriggerEventSettings.EventArea = "Trigger"; + + + -- Setup the color swatch and populate it. + MikSBTOpt.SetupColorSwatch(objTriggerEventSettings:GetName() .. "ColorSwatch", MikSBTOpt.MSG_EVENT_COLOR_SWATCH_TOOLTIP, "Triggers." .. triggerKey .. ".EventSettings.FontSettings.Color"); + MikSBTOpt.PopulateColorSwatch(objTriggerEventSettings:GetName() .. "ColorSwatch"); + + + -- Setup and populate the trigger event settings enable checkbox. + MikSBTOpt.SetupCheckbox(objTriggerEventSettings:GetName() .. "EnableTriggerCheckbox", nil, MikSBTOpt.MSG_TRIGGER_ENABLE_TOOLTIP, "Triggers." .. triggerKey .. ".EventSettings.Show"); + MikSBTOpt.PopulateCheckbox(objTriggerEventSettings:GetName() .. "EnableTriggerCheckbox"); + + + -- Set the message label to the appropriate text and the editbox's tooltip. + local objMessageLabel = getglobal(objTriggerEventSettings:GetName() .. "MessageLabel"); + if (objMessageLabel) then + -- Set the tooltip for the label. + objMessageLabel:GetParent().tooltipText = MikSBTOpt.MSG_TRIGGER_MESSAGE_LABEL_TOOLTIP; + objMessageLabel:SetText(triggerSettings.EventSettings.Message); + end + objTriggerEventSettings.EditboxTooltip = MikSBTOpt.MSG_TRIGGER_MESSAGE_EDITBOX_TOOLTIP; + + + -- Setup the sticky checkbox for the trigger event settings and populate it. + MikSBTOpt.SetupCheckbox(objTriggerEventSettings:GetName() .. "StickyEventCheckbox", nil, MikSBTOpt.MSG_TRIGGER_STICKY_CHECKBOX_TOOLTIP, "Triggers." .. triggerKey .. ".EventSettings.IsSticky"); + MikSBTOpt.PopulateCheckbox(objTriggerEventSettings:GetName() .. "StickyEventCheckbox"); + + -- Setup the edit font settings button for the event. + MikSBTOpt.SetupButton(objTriggerEventSettings:GetName() .. "EditFontSettingsButton", MikSBTOpt.MSG_TRIGGER_EDIT_FONT_SETTINGS_BUTTON_LABEL, MikSBTOpt.MSG_TRIGGER_EDIT_FONT_SETTINGS_BUTTON_TOOLTIP); + + -- Setup the edit trigger settings button for the event. + MikSBTOpt.SetupButton(objTriggerEventSettings:GetName() .. "EditTriggerSettingsButton", MikSBTOpt.MSG_TRIGGER_EDIT_TRIGGER_SETTINGS_BUTTON_LABEL, MikSBTOpt.MSG_TRIGGER_EDIT_TRIGGER_SETTINGS_BUTTON_TOOLTIP); + + -- Setup the delete trigger button for the event. + MikSBTOpt.SetupButton(objTriggerEventSettings:GetName() .. "DeleteTriggerButton", "X", MikSBTOpt.MSG_TRIGGER_DELETE_TRIGGER_BUTTON_TOOLTIP); + + -- Show the trigger settings. + objTriggerEventSettings:Show(); + end + end + end + +end + + +-- ********************************************************************************** +-- This function populates the suppression settings. +-- ********************************************************************************** +function MikSBTOpt.PopulateSuppressionSettings() + -- Hide the suppression editbox if it's shown. + MikSBTOpt.HideMessageEditbox(OPTIONS_FRAME_NAME .. "Tab6FrameMessageEditbox"); + + -- Get the total number of suppressions. + totalItems = MikSBTOpt.CountSuppressions(); + + -- Get the trigger settings scroll frame and its offset. + local objFrame = getglobal(OPTIONS_FRAME_NAME .. "Tab6FrameSuppressionsScrollFrame"); + FauxScrollFrame_Update(objFrame, totalItems, SUPPRESSION_ITEMS_PER_PAGE, SUPPRESSION_ITEM_HEIGHT); + local skipCount = FauxScrollFrame_GetOffset(objFrame); + + -- Loop through all of the visible items. + for i = 1, SUPPRESSION_ITEMS_PER_PAGE do + objSuppressionItemSettings = getglobal(OPTIONS_FRAME_NAME .. "Tab6FrameSuppression" .. i); + + -- Make sure the suppression settings object exists. + if (objSuppressionItemSettings) then + -- Check if there is not suppression settings data for the suppression settings slot. + if (i > totalItems) then + -- Hide the suppression settings. + objSuppressionItemSettings:Hide(); + + -- There is data for the suppression settings slot. + else + local suppressionKey, suppressionSettings = MikSBTOpt.GetSuppressionSettings(i+skipCount); + + -- Set the suppression key. + objSuppressionItemSettings.SuppressionKey = suppressionKey; + + -- Setup and populate the enable suppression checkbox. + MikSBTOpt.SetupCheckbox(objSuppressionItemSettings:GetName() .. "EnableSuppressionCheckbox", nil, MikSBTOpt.MSG_SUPPRESSION_ENABLE_TOOLTIP, "Suppressions." .. suppressionKey .. ".Enabled"); + MikSBTOpt.PopulateCheckbox(objSuppressionItemSettings:GetName() .. "EnableSuppressionCheckbox"); + + + -- Set the message label to the appropriate text and the editbox's tooltip. + local objMessageLabel = getglobal(objSuppressionItemSettings:GetName() .. "MessageLabel"); + if (objMessageLabel) then + -- Set the tooltip for the label. + objMessageLabel:GetParent().tooltipText = MikSBTOpt.MSG_SUPPRESSION_MESSAGE_LABEL_TOOLTIP; + objMessageLabel:SetText(suppressionSettings.SearchPattern); + end + objSuppressionItemSettings.EditboxTooltip = MikSBTOpt.MSG_SUPPRESSION_MESSAGE_EDITBOX_TOOLTIP; + + + -- Setup the delete suppression button for the item. + MikSBTOpt.SetupButton(objSuppressionItemSettings:GetName() .. "DeleteSuppressionButton", "X", MikSBTOpt.MSG_SUPPRESSION_DELETE_SUPPRESSION_BUTTON_TOOLTIP); + + -- Show the suppression settings. + objSuppressionItemSettings:Show(); + end + end + end +end + + +-- ********************************************************************************** +-- Returns the number of triggers defined. +-- ********************************************************************************** +function MikSBTOpt.CountTriggers() + -- Declare a variable to count the number of triggers. + local numTriggers = 0; + + -- Loop through all of the trigger entries. + for _, triggerSettings in MikSBT.CurrentProfile.Triggers do + numTriggers = numTriggers + 1; + end + + -- Return the number of triggers. + return numTriggers; +end + + +-- ********************************************************************************** +-- Returns the number of suppressions defined. +-- ********************************************************************************** +function MikSBTOpt.CountSuppressions() + -- Declare a variable to count the number of suppressions. + local numSuppressions = 0; + + -- Loop through all of the suppression entries. + for _, suppressionSettings in MikSBT.CurrentProfile.Suppressions do + numSuppressions = numSuppressions + 1; + end + + -- Return the number of suppressions. + return numSuppressions; +end + + + +-- ********************************************************************************** +-- Returns the trigger settings for the passed trigger number. +-- ********************************************************************************** +function MikSBTOpt.GetTriggerSettings(triggerNumber) + -- Declare a variable to count hold the number of the current trigger. + local curTrigger = 0; + + -- Loop through all of the trigger entries. + for triggerKey, triggerSettings in MikSBT.CurrentProfile.Triggers do + curTrigger = curTrigger + 1; + + -- Check if this is the requested trigger. + if (curTrigger == triggerNumber) then + return triggerKey, triggerSettings; + end + end + + return nil; +end + + +-- ********************************************************************************** +-- Returns the suppression settings for the passed suppression number. +-- ********************************************************************************** +function MikSBTOpt.GetSuppressionSettings(suppressionNumber) + -- Declare a variable to count hold the number of the current suppression. + local curSuppression = 0; + + -- Loop through all of the suppression entries. + for suppressionKey, suppressionSettings in MikSBT.CurrentProfile.Suppressions do + curSuppression = curSuppression + 1; + + -- Check if this is the requested suppression. + if (curSuppression == suppressionNumber) then + return suppressionKey, suppressionSettings; + end + end + + return nil; +end + + +-- ********************************************************************************** +-- Called when one of the event message labels is clicked. +-- ********************************************************************************** +function MikSBTOpt.EventMessageLabelOnClick() + -- Get the event settings frame. + local objEventSettings = this:GetParent(); + + -- Make sure the event type is set for the label and there is an event data index. + if (objEventSettings.EventType ~= nil) then + -- Get the clicked label and hide it. + local labelFrameName = this:GetName() .. "Label"; + local objLabel = getglobal(labelFrameName); + objLabel:Hide(); + + -- Get the message editbox for the scroll area. + local editboxFrameName = objEventSettings:GetParent():GetName() .. "MessageEditbox"; + local objEditbox = getglobal(editboxFrameName); + + -- Hide the editbox to make sure everything is updated correctly. + objEditbox:Hide(); + + -- Show the editbox where the clicked label is, set the clicked label frame name for the editbox, and + -- set the previous text to the label's text. + objEditbox:Show(); + objEditbox:ClearAllPoints(); + objEditbox:SetPoint("TOPLEFT", this, "TOPLEFT"); + objEditbox.ClickedLabelFrameName = labelFrameName; + objEditbox.PreviousText = objLabel:GetText(); + + + -- Setup the event setting editbox and populate it. + MikSBTOpt.SetupEditbox(editboxFrameName, nil, MikSBTOpt.MSG_EVENT_MESSAGE_EDITBOX_TOOLTIP_PREFIX .. objEventSettings.EditboxTooltip, "EventSettings." .. objEventSettings.EventType .. ".Message"); + MikSBTOpt.PopulateEditbox(editboxFrameName); + + -- Set the focus to the editbox. + objEditbox:SetFocus(); + end +end + + +-- ********************************************************************************** +-- Called when one of the trigger message labels is clicked. +-- ********************************************************************************** +function MikSBTOpt.TriggerMessageLabelOnClick() + -- Get the trigger event settings frame. + local objTriggerEventSettings = this:GetParent(); + + -- Make sure the trigger key is set for the label. + if (objTriggerEventSettings.TriggerKey ~= nil) then + -- Get the clicked label and hide it. + local labelFrameName = this:GetName() .. "Label"; + local objLabel = getglobal(labelFrameName); + objLabel:Hide(); + + -- Get the message editbox. + local editboxFrameName = objTriggerEventSettings:GetParent():GetName() .. "MessageEditbox"; + local objEditbox = getglobal(editboxFrameName); + + -- Hide the editbox to make sure everything is updated correctly. + objEditbox:Hide(); + + -- Show the editbox where the clicked label is, set the clicked label frame name for the editbox, and + -- set the previous text to the label's text. + objEditbox:Show(); + objEditbox:ClearAllPoints(); + objEditbox:SetPoint("TOPLEFT", this, "TOPLEFT"); + objEditbox.ClickedLabelFrameName = labelFrameName; + objEditbox.PreviousText = objLabel:GetText(); + + + -- Setup the event setting editbox and populate it. + MikSBTOpt.SetupEditbox(editboxFrameName, nil, objTriggerEventSettings.EditboxTooltip, "Triggers." .. objTriggerEventSettings.TriggerKey .. ".EventSettings.Message"); + MikSBTOpt.PopulateEditbox(editboxFrameName); + + -- Set the focus to the editbox. + objEditbox:SetFocus(); + end +end + + +-- ********************************************************************************** +-- Called when one of the suppression message labels is clicked. +-- ********************************************************************************** +function MikSBTOpt.SuppressionMessageLabelOnClick() + -- Get the suppression settings frame. + local objSuppressionSettings = this:GetParent(); + + -- Make sure the suppression key is set for the label. + if (objSuppressionSettings.SuppressionKey ~= nil) then + -- Get the clicked label and hide it. + local labelFrameName = this:GetName() .. "Label"; + local objLabel = getglobal(labelFrameName); + objLabel:Hide(); + + -- Get the message editbox. + local editboxFrameName = objSuppressionSettings:GetParent():GetName() .. "MessageEditbox"; + local objEditbox = getglobal(editboxFrameName); + + -- Hide the editbox to make sure everything is updated correctly. + objEditbox:Hide(); + + -- Show the editbox where the clicked label is, set the clicked label frame name for the editbox, and + -- set the previous text to the label's text. + objEditbox:Show(); + objEditbox:ClearAllPoints(); + objEditbox:SetPoint("TOPLEFT", this, "TOPLEFT"); + objEditbox.ClickedLabelFrameName = labelFrameName; + objEditbox.PreviousText = objLabel:GetText(); + + + -- Setup the event setting editbox and populate it. + MikSBTOpt.SetupEditbox(editboxFrameName, nil, objSuppressionSettings.EditboxTooltip, "Suppressions." .. objSuppressionSettings.SuppressionKey .. ".SearchPattern"); + MikSBTOpt.PopulateEditbox(editboxFrameName); + + -- Set the focus to the editbox. + objEditbox:SetFocus(); + end +end + + +-- ********************************************************************************** +-- Hides the message editbox. +-- ********************************************************************************** +function MikSBTOpt.HideMessageEditbox(editboxFrameName) + -- Check if the event message editbox is shown and hide it. + local objEditbox = getglobal(editboxFrameName); + if (objEditbox and objEditbox:IsVisible()) then + objEditbox:Hide(); + end +end + + + +------------------------------------------------------------------------------------- +-- Font settings functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Show master font settings. +-- ********************************************************************************** +function MikSBTOpt.ShowMasterFontSettings() + -- Show the crit font settings controls. + MikSBTOpt.ShowCritFontControls(); + + + -- Setup the normal font dropdown. + local key = "FontDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].Font, MikSBTOpt.FontSettingsDropdownOnClick, MikSBT.AVAILABLE_FONTS, "MasterFontSettings.Normal.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the normal font outline dropdown. + key = "FontOutlineDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].Outline, MikSBTOpt.FontSettingsDropdownOnClick, MikSBT.AVAILABLE_OUTLINES, "MasterFontSettings.Normal.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the crit font dropdown. + key = "FontCritDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].FontCrit, MikSBTOpt.FontSettingsDropdownOnClick, MikSBT.AVAILABLE_FONTS, "MasterFontSettings.Crit.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the crit font outline dropdown. + key = "FontOutlineCritDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].OutlineCrit, MikSBTOpt.FontSettingsDropdownOnClick, MikSBT.AVAILABLE_OUTLINES, "MasterFontSettings.Crit.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + + -- Setup the normal font size slider. + key = "FontSizeSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].FontSize, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "MasterFontSettings.Normal.FontSize"); + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the crit font size slider. + key = "FontSizeCritSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Master"].FontSizeCrit, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "MasterFontSettings.Crit.FontSize"); + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. key); + + + -- Hide the inherit font size and crit font size checkboxes. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):Hide(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):Hide(); + + -- Show the font size and crit size sliders. + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Show(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Show(); + + + -- Show the font settings frame. + getglobal(FONT_SETTINGS_FRAME_NAME):Show(); + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +-- ********************************************************************************** +-- Show scroll area font settings . +-- ********************************************************************************** +function MikSBTOpt.ShowScrollAreaFontSettings(scrollArea) + -- Get the font settings frame. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + + -- Get the inherited font settings. + fontSettingsFrame.InheritedFontIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.FontIndex"); + fontSettingsFrame.InheritedOutlineIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.OutlineIndex"); + fontSettingsFrame.InheritedFontSize = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.FontSize"); + fontSettingsFrame.InheritedFontCritIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Crit.FontIndex"); + fontSettingsFrame.InheritedOutlineCritIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Crit.OutlineIndex"); + fontSettingsFrame.InheritedFontSizeCrit = MikSBT.GetOptionFromVarPath("MasterFontSettings.Crit.FontSize"); + + + -- Show the crit font settings controls. + MikSBTOpt.ShowCritFontControls(); + + + -- Setup the normal font dropdown. + local key = "FontDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].Font, MikSBTOpt.FontSettingsDropdownOnClick, availableFontsInherit, "DisplaySettings." .. scrollArea .. ".FontSettings.Normal.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the normal font outline dropdown. + key = "FontOutlineDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].Outline, MikSBTOpt.FontSettingsDropdownOnClick, availableOutlinesInherit, "DisplaySettings." .. scrollArea .. ".FontSettings.Normal.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the crit font dropdown. + key = "FontCritDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].FontCrit, MikSBTOpt.FontSettingsDropdownOnClick, availableFontsInherit, "DisplaySettings." .. scrollArea .. ".FontSettings.Crit.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the crit font outline dropdown. + key = "FontOutlineCritDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].OutlineCrit, MikSBTOpt.FontSettingsDropdownOnClick, availableOutlinesInherit, "DisplaySettings." .. scrollArea .. ".FontSettings.Crit.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + + + -- Show the inherit font size and crit font size checkboxes. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):Show(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):Show(); + + + -- Setup the inherit normal font size checkbox. + key = "InheritFontSizeCheckbox"; + MikSBTOpt.SetupCheckbox(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].InheritFontSize, "DisplaySettings." .. scrollArea .. ".FontSettings.Normal.FontSize"); + + -- Setup the inherit crit font size checkbox. + key = "InheritFontSizeCritCheckbox"; + MikSBTOpt.SetupCheckbox(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].InheritFontSizeCrit, "DisplaySettings." .. scrollArea .. ".FontSettings.Crit.FontSize"); + + + -- Setup the normal font size slider. + key = "FontSizeSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].FontSize, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "DisplaySettings." .. scrollArea .. ".FontSettings.Normal.FontSize"); + + -- Setup the crit font size slider. + key = "FontSizeCritSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["ScrollArea"].FontSizeCrit, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "DisplaySettings." .. scrollArea .. ".FontSettings.Crit.FontSize"); + + + -- Check if the normal font size is inherited. + if (MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Normal.FontSize") == 0) then + -- Check the inherited checkbox and hide the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):SetChecked(true); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Hide(); + else + -- Uncheck the inherited checkbox and setup the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):SetChecked(false); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Show(); + + -- Populate the normal font size slider. + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"); + end + + + -- Check if the critical font size is inherited. + if (MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Crit.FontSize") == 0) then + -- Check the inherited checkbox and hide the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):SetChecked(true); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Hide(); + else + -- Uncheck the inherited checkbox and setup the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):SetChecked(false); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Show(); + + -- Populate the crit font size slider. + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"); + end + + + -- Show the font settings frame. + fontSettingsFrame:Show(); + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +-- ********************************************************************************** +-- Show event font settings. +-- ********************************************************************************** +function MikSBTOpt.ShowEventFontSettings(scrollArea, eventType) + -- Get the inherited font index. + local inheritedFontIndex = MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Normal.FontIndex"); + if (inheritedFontIndex == 0) then + inheritedFontIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.FontIndex"); + end + + -- Get the inherited outline index. + local inheritedOutlineIndex = MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Normal.OutlineIndex"); + if (inheritedOutlineIndex == 0) then + inheritedOutlineIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.OutlineIndex"); + end + + -- Get the inherited font size. + local inheritedFontSize = MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Normal.FontSize"); + if (inheritedFontSize == 0) then + -- Get the font size from the scroll area and check if it's inherited from the master font size. + inheritedFontSize = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.FontSize"); + end + + + + -- Get the inherited crit font index. + local inheritedFontCritIndex = MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Crit.FontIndex"); + if (inheritedFontCritIndex == 0) then + inheritedFontCritIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Crit.FontIndex"); + end + + -- Get the inherited crit outline index. + local inheritedOutlineCritIndex = MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Crit.OutlineIndex"); + if (inheritedOutlineCritIndex == 0) then + inheritedOutlineCritIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Crit.OutlineIndex"); + end + + -- Get the inherited crit font size. + local inheritedFontSizeCrit = MikSBT.GetOptionFromVarPath("DisplaySettings." .. scrollArea .. ".FontSettings.Crit.FontSize"); + if (inheritedFontSizeCrit == 0) then + -- Get the font size from the scroll area and check if it's inherited from the master font size. + inheritedFontSizeCrit = MikSBT.GetOptionFromVarPath("MasterFontSettings.Crit.FontSize"); + end + + + -- Get the font settings frame. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + + + -- Set the inherited settings to the frame. + fontSettingsFrame.InheritedFontIndex = inheritedFontIndex; + fontSettingsFrame.InheritedOutlineIndex = inheritedOutlineIndex; + fontSettingsFrame.InheritedFontSize = inheritedFontSize; + fontSettingsFrame.InheritedFontCritIndex = inheritedFontCritIndex; + fontSettingsFrame.InheritedOutlineCritIndex = inheritedOutlineCritIndex; + fontSettingsFrame.InheritedFontSizeCrit = inheritedFontSizeCrit; + + + -- Setup the normal font dropdown. + local key = "FontDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].Font, MikSBTOpt.FontSettingsDropdownOnClick, availableFontsInherit, "EventSettings." .. eventType .. ".FontSettings.Normal.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the normal font outline dropdown. + key = "FontOutlineDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].Outline, MikSBTOpt.FontSettingsDropdownOnClick, availableOutlinesInherit, "EventSettings." .. eventType .. ".FontSettings.Normal.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + + -- Show the inherit normal font size checkbox. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):Show(); + + + -- Setup the inherit normal font size checkbox. + key = "InheritFontSizeCheckbox"; + MikSBTOpt.SetupCheckbox(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].InheritFontSize, "EventSettings." .. eventType .. ".FontSettings.Normal.FontSize"); + + -- Setup the normal font size slider. + key = "FontSizeSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].FontSize, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "EventSettings." .. eventType .. ".FontSettings.Normal.FontSize"); + + + -- Check if the normal font size is inherited. + if (MikSBT.GetOptionFromVarPath("EventSettings." .. eventType .. ".FontSettings.Normal.FontSize") == 0) then + -- Check the inherited checkbox and hide the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):SetChecked(true); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Hide(); + else + -- Uncheck the inherited checkbox and setup the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):SetChecked(false); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Show(); + + -- Populate the normal font size slider. + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"); + end + + + + -- Check if the event type supports crits. + if (MikSBT.CurrentProfile.EventSettings[eventType].FontSettings.Crit ~= nil) then + -- Show the crit font settings controls. + MikSBTOpt.ShowCritFontControls(); + + -- Setup the crit font dropdown. + key = "FontCritDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].FontCrit, MikSBTOpt.FontSettingsDropdownOnClick, availableFontsInherit, "EventSettings." .. eventType .. ".FontSettings.Crit.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the crit font outline dropdown. + key = "FontOutlineCritDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].OutlineCrit, MikSBTOpt.FontSettingsDropdownOnClick, availableOutlinesInherit, "EventSettings." .. eventType .. ".FontSettings.Crit.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + + -- Show the inherit crit font size checkbox. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):Show(); + + + -- Setup the inherit crit font size checkbox. + key = "InheritFontSizeCritCheckbox"; + MikSBTOpt.SetupCheckbox(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].InheritFontSizeCrit, "EventSettings." .. eventType .. ".FontSettings.Crit.FontSize"); + + -- Setup the crit font size slider. + key = "FontSizeCritSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Event"].FontSizeCrit, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "EventSettings." .. eventType .. ".FontSettings.Crit.FontSize"); + + -- Check if the critical font size is inherited. + if (MikSBT.GetOptionFromVarPath("EventSettings." .. eventType .. ".FontSettings.Crit.FontSize") == 0) then + -- Check the inherited checkbox and hide the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):SetChecked(true); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Hide(); + else + -- Uncheck the inherited checkbox and setup the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):SetChecked(false); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Show(); + + -- Populate the crit font size slider. + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"); + end + + -- The event type does not support crits. + else + -- Hide the crit font settings controls. + MikSBTOpt.HideCritFontControls(); + end + + + -- Show the font settings frame. + fontSettingsFrame:Show(); + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +-- ********************************************************************************** +-- Show trigger font settings. +-- ********************************************************************************** +function MikSBTOpt.ShowTriggerFontSettings(triggerKey) + -- Get the inherited font index. + local inheritedFontIndex = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.FontSettings.Normal.FontIndex"); + if (inheritedFontIndex == 0) then + inheritedFontIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.FontIndex"); + end + + -- Get the inherited outline index. + local inheritedOutlineIndex = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.FontSettings.Normal.OutlineIndex"); + if (inheritedOutlineIndex == 0) then + inheritedOutlineIndex = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.OutlineIndex"); + end + + -- Get the inherited font size. + local inheritedFontSize = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.FontSettings.Normal.FontSize"); + if (inheritedFontSize == 0) then + -- Get the font size from the scroll area and check if it's inherited from the master font size. + inheritedFontSize = MikSBT.GetOptionFromVarPath("MasterFontSettings.Normal.FontSize"); + end + + + -- Get the font settings frame. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + + + -- Set the inherited settings to the frame. + fontSettingsFrame.InheritedFontIndex = inheritedFontIndex; + fontSettingsFrame.InheritedOutlineIndex = inheritedOutlineIndex; + fontSettingsFrame.InheritedFontSize = inheritedFontSize; + + + -- Setup the normal font dropdown. + local key = "FontDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Trigger"].Font, MikSBTOpt.FontSettingsDropdownOnClick, availableFontsInherit, "Triggers." .. triggerKey .. ".EventSettings.FontSettings.Normal.FontIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + -- Setup the normal font outline dropdown. + key = "FontOutlineDropdown"; + MikSBTOpt.SetupDropdown(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.DROPDOWNS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Trigger"].Outline, MikSBTOpt.FontSettingsDropdownOnClick, availableOutlinesInherit, "Triggers." .. triggerKey .. ".EventSettings.FontSettings.Normal.OutlineIndex"); + MikSBTOpt.PopulateDropdown(FONT_SETTINGS_FRAME_NAME .. key); + + + -- Show the inherit normal font size checkbox. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):Show(); + + + -- Setup the inherit normal font size checkbox. + key = "InheritFontSizeCheckbox"; + MikSBTOpt.SetupCheckbox(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.CHECKBOXES[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Trigger"].InheritFontSize, "Triggers." .. triggerKey .. ".EventSettings.FontSettings.Normal.FontSize"); + + -- Setup the normal font size slider. + key = "FontSizeSlider"; + MikSBTOpt.SetupSlider(FONT_SETTINGS_FRAME_NAME .. key, MikSBTOpt.SLIDERS[key].Label, MikSBTOpt.FONT_SETTINGS_TOOLTIPS["Trigger"].FontSize, MIN_FONT_SIZE, MAX_FONT_SIZE, 2, "Triggers." .. triggerKey .. ".EventSettings.FontSettings.Normal.FontSize"); + + + -- Check if the normal font size is inherited. + if (MikSBT.GetOptionFromVarPath("Triggers." .. triggerKey .. ".EventSettings.FontSettings.Normal.FontSize") == 0) then + -- Check the inherited checkbox and hide the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):SetChecked(true); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Hide(); + else + -- Uncheck the inherited checkbox and setup the slider. + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCheckbox"):SetChecked(false); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"):Show(); + + -- Populate the normal font size slider. + MikSBTOpt.PopulateSlider(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"); + end + + + -- Hide the crit font settings controls. + MikSBTOpt.HideCritFontControls(); + + + -- Show the font settings frame. + fontSettingsFrame:Show(); + + -- Update the font previews. + MikSBTOpt.UpdateFontPreviews(); +end + + +-- ********************************************************************************** +-- Updates the font previews with values selected. +-- ********************************************************************************** +function MikSBTOpt.UpdateFontPreviews() + -- Get the font settings frame. + local fontSettingsFrame = getglobal(FONT_SETTINGS_FRAME_NAME); + + -- Get the selected normal font index. + local fontIndex = UIDropDownMenu_GetSelectedValue(getglobal(FONT_SETTINGS_FRAME_NAME .. "FontDropdown")); + if (fontIndex == 0) then + fontIndex = fontSettingsFrame.InheritedFontIndex; + end + + -- Get the selected normal outline index. + local outlineIndex = UIDropDownMenu_GetSelectedValue(getglobal(FONT_SETTINGS_FRAME_NAME .. "FontOutlineDropdown")); + if (outlineIndex == 0) then + outlineIndex = fontSettingsFrame.InheritedOutlineIndex; + end + + -- Check if the normal font size slider is shown. + local slider = getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeSlider"); + local fontSize; + if (slider:IsShown()) then + -- Get the selected font size. + fontSize = slider:GetValue(); + else + -- Get the inherited font size. + fontSize = fontSettingsFrame.InheritedFontSize; + end + + + -- Make sure the normal font index is valid. + if (fontIndex ~= nil) then + local previewText = getglobal(FONT_SETTINGS_FRAME_NAME .. "NormalPreviewText"); + previewText:SetFont(MikSBT.AVAILABLE_FONTS[fontIndex].Path, fontSize, MikSBT.AVAILABLE_OUTLINES[outlineIndex].Style); + end + + + + -- Check if crits are to be shown. + if (getglobal(FONT_SETTINGS_FRAME_NAME .. "CritPreview"):IsShown()) then + -- Get the selected crit font index. + local fontCritIndex = UIDropDownMenu_GetSelectedValue(getglobal(FONT_SETTINGS_FRAME_NAME .. "FontCritDropdown")); + if (fontCritIndex == 0) then + fontCritIndex = fontSettingsFrame.InheritedFontCritIndex; + end + + + -- Get the selected crit outline index. + local outlineCritIndex = UIDropDownMenu_GetSelectedValue(getglobal(FONT_SETTINGS_FRAME_NAME .. "FontOutlineCritDropdown")); + if (outlineCritIndex == 0) then + outlineCritIndex = fontSettingsFrame.InheritedOutlineCritIndex; + end + + + -- Check if the crit font size slider is shown. + local slider = getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"); + local fontSizeCrit; + if (slider:IsShown()) then + -- Get the selected crit font size. + fontSizeCrit = slider:GetValue(); + else + -- Get the inherited crit font size. + fontSizeCrit = fontSettingsFrame.InheritedFontSizeCrit; + end + + -- Make sure the crit font index is valid. + if (fontCritIndex ~= nil) then + local previewText = getglobal(FONT_SETTINGS_FRAME_NAME .. "CritPreviewText"); + previewText:SetFont(MikSBT.AVAILABLE_FONTS[fontCritIndex].Path, fontSizeCrit, MikSBT.AVAILABLE_OUTLINES[outlineCritIndex].Style); + end + end +end + + +-- ********************************************************************************** +-- Shows the crit font settings controls. +-- ********************************************************************************** +function MikSBTOpt.ShowCritFontControls() + -- Show all of the crit font settings controls. + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontCritDropdown"):Show(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontOutlineCritDropdown"):Show(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):Show(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Show(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "CritPreview"):Show(); + + -- Move the normal font controls back to the left side. + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontDropdown"):SetPoint("TOPLEFT", FONT_SETTINGS_FRAME_NAME, "TOPLEFT", 0, -60); +end + + +-- ********************************************************************************** +-- Hides the crit font settings controls. +-- ********************************************************************************** +function MikSBTOpt.HideCritFontControls() + -- Hide all of the crit font settings controls. + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontCritDropdown"):Hide(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontOutlineCritDropdown"):Hide(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "InheritFontSizeCritCheckbox"):Hide(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontSizeCritSlider"):Hide(); + getglobal(FONT_SETTINGS_FRAME_NAME .. "CritPreview"):Hide(); + + -- Center the normal font controls. + getglobal(FONT_SETTINGS_FRAME_NAME .. "FontDropdown"):SetPoint("TOPLEFT", FONT_SETTINGS_FRAME_NAME, "TOPLEFT", 85, -60); +end + + + +------------------------------------------------------------------------------------- +-- Scroll area mover functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Setup and show the scroll area mover controls. +-- ********************************************************************************** +function MikSBTOpt.ShowScrollAreaMoverFrames(scrollArea) + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + + -- Check if the original settings settings table hasn't been created yet and create it if not. + if (not controlFrame.MSBTOriginalSettings) then controlFrame.MSBTOriginalSettings = {}; end + + + -- Save the original incoming scroll area settings into the control frame. + controlFrame.MSBTOriginalSettings.IncomingAnimationStyle = MikSBT.GetOptionFromVarPath("DisplaySettings.Incoming.AnimationStyle"); + controlFrame.MSBTOriginalSettings.IncomingScrollDirection = MikSBT.GetOptionFromVarPath("DisplaySettings.Incoming.ScrollDirection"); + controlFrame.MSBTOriginalSettings.IncomingTextAlignIndex = MikSBT.GetOptionFromVarPath("DisplaySettings.Incoming.FontSettings.TextAlignIndex"); + controlFrame.MSBTOriginalSettings.IncomingScrollHeight = MikSBT.GetOptionFromVarPath("DisplaySettings.Incoming.ScrollHeight"); + controlFrame.MSBTOriginalSettings.IncomingXOffset = MikSBT.GetOptionFromVarPath("DisplaySettings.Incoming.FrameOffsets.x"); + controlFrame.MSBTOriginalSettings.IncomingYOffset = MikSBT.GetOptionFromVarPath("DisplaySettings.Incoming.FrameOffsets.y"); + + -- Save the original outgoing scroll area settings into the control frame. + controlFrame.MSBTOriginalSettings.OutgoingAnimationStyle = MikSBT.GetOptionFromVarPath("DisplaySettings.Outgoing.AnimationStyle"); + controlFrame.MSBTOriginalSettings.OutgoingScrollDirection = MikSBT.GetOptionFromVarPath("DisplaySettings.Outgoing.ScrollDirection"); + controlFrame.MSBTOriginalSettings.OutgoingTextAlignIndex = MikSBT.GetOptionFromVarPath("DisplaySettings.Outgoing.FontSettings.TextAlignIndex"); + controlFrame.MSBTOriginalSettings.OutgoingScrollHeight = MikSBT.GetOptionFromVarPath("DisplaySettings.Outgoing.ScrollHeight"); + controlFrame.MSBTOriginalSettings.OutgoingXOffset = MikSBT.GetOptionFromVarPath("DisplaySettings.Outgoing.FrameOffsets.x"); + controlFrame.MSBTOriginalSettings.OutgoingYOffset = MikSBT.GetOptionFromVarPath("DisplaySettings.Outgoing.FrameOffsets.y"); + + -- Save the original notification scroll area settings into the control frame. + controlFrame.MSBTOriginalSettings.NotificationAnimationStyle = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.AnimationStyle"); + controlFrame.MSBTOriginalSettings.NotificationScrollDirection = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.ScrollDirection"); + controlFrame.MSBTOriginalSettings.NotificationTextAlignIndex = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.FontSettings.TextAlignIndex"); + controlFrame.MSBTOriginalSettings.NotificationScrollHeight = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.ScrollHeight"); + controlFrame.MSBTOriginalSettings.NotificationXOffset = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.FrameOffsets.x"); + controlFrame.MSBTOriginalSettings.NotificationYOffset = MikSBT.GetOptionFromVarPath("DisplaySettings.Notification.FrameOffsets.y"); + + + -- Set the starting current settings. + controlFrame.MSBTCurrentSettings = MikSBT.CopyTable(controlFrame.MSBTOriginalSettings); + + + -- Select the starting scroll area based on the passed scroll area. + MikSBTOpt.PopulateDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollAreaDropdown", scrollArea); + MikSBTOpt.PopulateScrollAreaControls(scrollArea); + + + -- Get the scroll area mover frames for each scroll area. + local incomingFrame = getglobal(SCROLL_AREA_MOVER_INCOMING_FRAME_NAME); + local outgoingFrame = getglobal(SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME); + local notificationFrame = getglobal(SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME); + + + -- Set the tooltip for the scroll area mover frames. + incomingFrame.tooltipText = MikSBTOpt.FRAMES[SCROLL_AREA_MOVER_INCOMING_FRAME_NAME].Tooltip; + outgoingFrame.tooltipText = MikSBTOpt.FRAMES[SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME].Tooltip; + notificationFrame.tooltipText = MikSBTOpt.FRAMES[SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME].Tooltip; + + -- Reposition the scroll area mover frames. + MikSBTOpt.RepositionScrollAreaMoverFrames(); + + -- Populates the scroll area mover coordinates. + MikSBTOpt.PopulateScrollAreaMoverCoordinates() + + -- Show the scroll area mover frames. + controlFrame:Show(); + incomingFrame:Show(); + outgoingFrame:Show(); + notificationFrame:Show(); + + -- Hide the options frame. + getglobal(OPTIONS_FRAME_NAME):Hide(); +end + + +-- ********************************************************************************** +-- Closes the scroll area mover controls and opens the options screen back up. +-- ********************************************************************************** +function MikSBTOpt.HideScrollAreaMoverFrames() + -- Show the options frame. + getglobal(OPTIONS_FRAME_NAME):Show(); + + -- Hide the scroll area mover frames. + getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME):Hide(); + getglobal(SCROLL_AREA_MOVER_INCOMING_FRAME_NAME):Hide(); + getglobal(SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME):Hide(); + getglobal(SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME):Hide(); +end + + +-- ********************************************************************************** +-- Repositions all of the scroll area mover frames with the values in the current +-- settings of the control frame. +-- ********************************************************************************** +function MikSBTOpt.RepositionScrollAreaMoverFrames() + -- Get the scroll area mover control frame and all of the scroll area mover frames. + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + local incomingFrame = getglobal(SCROLL_AREA_MOVER_INCOMING_FRAME_NAME); + local outgoingFrame = getglobal(SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME); + local notificationFrame = getglobal(SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME); + + -- Reposition the incoming scroll area frame. + incomingFrame:SetHeight(controlFrame.MSBTCurrentSettings.IncomingScrollHeight); + incomingFrame:ClearAllPoints(); + incomingFrame:SetPoint("BOTTOM", UIParent, "CENTER", controlFrame.MSBTCurrentSettings.IncomingXOffset, controlFrame.MSBTCurrentSettings.IncomingYOffset); + + -- Reposition the outgoing scroll area frame. + outgoingFrame:SetHeight(controlFrame.MSBTCurrentSettings.OutgoingScrollHeight); + outgoingFrame:ClearAllPoints(); + outgoingFrame:SetPoint("BOTTOM", UIParent, "CENTER", controlFrame.MSBTCurrentSettings.OutgoingXOffset, controlFrame.MSBTCurrentSettings.OutgoingYOffset); + + -- Reposition the notification scroll area frame. + notificationFrame:SetHeight(controlFrame.MSBTCurrentSettings.NotificationScrollHeight); + notificationFrame:ClearAllPoints(); + notificationFrame:SetPoint("BOTTOM", UIParent, "CENTER", controlFrame.MSBTCurrentSettings.NotificationXOffset, controlFrame.MSBTCurrentSettings.NotificationYOffset); + + -- Populate the scroll area mover coordinates. + MikSBTOpt.PopulateScrollAreaMoverCoordinates(); +end + + +-- ********************************************************************************** +-- Colorizes the scroll area mover frames. +-- ********************************************************************************** +function MikSBTOpt.ColorizeScrollAreaMoverFrames(scrollArea) + -- Get the scroll area mover frames for each scroll area. + local incomingFrame = getglobal(SCROLL_AREA_MOVER_INCOMING_FRAME_NAME); + local outgoingFrame = getglobal(SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME); + local notificationFrame = getglobal(SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME); + + -- Set the colors appropriately. + if (scrollArea == "Incoming") then + incomingFrame:SetBackdropColor(0.5, 0.05, 0.05, 1.0); + outgoingFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0); + notificationFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0); + elseif (scrollArea == "Outgoing") then + incomingFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0); + outgoingFrame:SetBackdropColor(0.5, 0.05, 0.05, 1.0); + notificationFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0); + elseif (scrollArea == "Notification") then + incomingFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0); + outgoingFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0); + notificationFrame:SetBackdropColor(0.5, 0.05, 0.05, 1.0); + end +end + + +-- ********************************************************************************** +-- Populates the controls for the selected scroll area. +-- ********************************************************************************** +function MikSBTOpt.PopulateScrollAreaControls(scrollArea) + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + MikSBTOpt.PopulateDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "AnimationStyleDropdown", controlFrame.MSBTCurrentSettings[scrollArea .. "AnimationStyle"]); + MikSBTOpt.PopulateDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollDirectionDropdown", controlFrame.MSBTCurrentSettings[scrollArea .. "ScrollDirection"]); + MikSBTOpt.PopulateDropdown(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "TextAlignDropdown", controlFrame.MSBTCurrentSettings[scrollArea .. "TextAlignIndex"]); + MikSBTOpt.PopulateSlider(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollHeightSlider", controlFrame.MSBTCurrentSettings[scrollArea .. "ScrollHeight"]); + MikSBTOpt.PopulateEditbox(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "XOffsetEditbox", controlFrame.MSBTCurrentSettings[scrollArea .. "XOffset"]); + MikSBTOpt.PopulateEditbox(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "YOffsetEditbox", controlFrame.MSBTCurrentSettings[scrollArea .. "YOffset"]); + + -- Colorize the selected scroll area mover frame. + MikSBTOpt.ColorizeScrollAreaMoverFrames(scrollArea) +end + + +-- ********************************************************************************** +-- Populates the scroll area mover labels with the scroll area's coordinates. +-- ********************************************************************************** +function MikSBTOpt.PopulateScrollAreaMoverCoordinates() + -- Get the scroll area mover control frame. + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + + -- Populate the labels. + getglobal(SCROLL_AREA_MOVER_INCOMING_FRAME_NAME .. "Title"):SetText(MikSBTOpt.TABS[TAB_INDEX_INCOMING].Label .. " (" .. controlFrame.MSBTCurrentSettings.IncomingXOffset .. ", " .. controlFrame.MSBTCurrentSettings.IncomingYOffset .. ")"); + getglobal(SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME .. "Title"):SetText(MikSBTOpt.TABS[TAB_INDEX_OUTGOING].Label .. " (" .. controlFrame.MSBTCurrentSettings.OutgoingXOffset .. ", " .. controlFrame.MSBTCurrentSettings.OutgoingYOffset .. ")"); + getglobal(SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME .. "Title"):SetText(MikSBTOpt.TABS[TAB_INDEX_NOTIFICATION].Label .. " (" .. controlFrame.MSBTCurrentSettings.NotificationXOffset .. ", " .. controlFrame.MSBTCurrentSettings.NotificationYOffset .. ")"); + + -- Populate the x and y offset editboxes. + local scrollArea = UIDropDownMenu_GetSelectedValue(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollAreaDropdown")); + MikSBTOpt.PopulateEditbox(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "XOffsetEditbox", controlFrame.MSBTCurrentSettings[scrollArea .. "XOffset"]); + MikSBTOpt.PopulateEditbox(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "YOffsetEditbox", controlFrame.MSBTCurrentSettings[scrollArea .. "YOffset"]); +end + + +-- ********************************************************************************** +-- Save the coordinates of the scroll area mover to the control frame's current +-- settings. +-- ********************************************************************************** +function MikSBTOpt.SaveScrollAreaMoverCoordinates(frameName) + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + + -- Get the UIParent center x and y coords. + local uiParentX, uiParentY = UIParent:GetCenter(); + local xOffset = math.floor(this:GetCenter() - uiParentX); + local yOffset = math.ceil(this:GetBottom() - uiParentY); + + -- Save the coordinates for the correct scroll area. + if (frameName == SCROLL_AREA_MOVER_INCOMING_FRAME_NAME) then + controlFrame.MSBTCurrentSettings.IncomingXOffset = xOffset; + controlFrame.MSBTCurrentSettings.IncomingYOffset = yOffset; + elseif (frameName == SCROLL_AREA_MOVER_OUTGOING_FRAME_NAME) then + controlFrame.MSBTCurrentSettings.OutgoingXOffset = xOffset; + controlFrame.MSBTCurrentSettings.OutgoingYOffset = yOffset; + elseif (frameName == SCROLL_AREA_MOVER_NOTIFICATION_FRAME_NAME) then + controlFrame.MSBTCurrentSettings.NotificationXOffset = xOffset; + controlFrame.MSBTCurrentSettings.NotificationYOffset = yOffset; + end + + -- Populate the scroll area mover coordinates. + MikSBTOpt.PopulateScrollAreaMoverCoordinates(); +end + + +-- ********************************************************************************** +-- Save the values selected in the scroll area mover controls to the current settings +-- of the sroll area mover control frame. +-- ********************************************************************************** +function MikSBTOpt.SaveScrollAreaControlsToCurrentSettings() + -- Get the currently selected scroll area and the scroll area mover control frame. + local scrollArea = UIDropDownMenu_GetSelectedValue(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollAreaDropdown")); + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + + -- Set the control frame's current settings to the values in the controls. + controlFrame.MSBTCurrentSettings[scrollArea .. "AnimationStyle"] = UIDropDownMenu_GetSelectedValue(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "AnimationStyleDropdown")); + controlFrame.MSBTCurrentSettings[scrollArea .. "ScrollDirection"] = UIDropDownMenu_GetSelectedValue(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollDirectionDropdown")); + controlFrame.MSBTCurrentSettings[scrollArea .. "TextAlignIndex"] = UIDropDownMenu_GetSelectedValue(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "TextAlignDropdown")); + controlFrame.MSBTCurrentSettings[scrollArea .. "ScrollHeight"] = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "ScrollHeightSlider"):GetValue(); + + -- Get the x offset from the x offset editbox and make sure it's a number. + local xOffset = tonumber(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "XOffsetEditbox"):GetText()); + if (xOffset ~= nil) then + controlFrame.MSBTCurrentSettings[scrollArea .. "XOffset"] = xOffset; + end + + -- Get the y offset from the y offset editbox and make sure it's a number. + local yOffset = tonumber(getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME .. "YOffsetEditbox"):GetText()); + if (yOffset ~= nil) then + controlFrame.MSBTCurrentSettings[scrollArea .. "YOffset"] = yOffset; + end +end + + +-- ********************************************************************************** +-- Save the settings of the scroll area mover control to the profile for the +-- passed settings type. +-- ********************************************************************************** +function MikSBTOpt.SaveScrollAreaSettings(settingsType) + -- Save the settings in the controls to the control frame's current settings. + MikSBTOpt.SaveScrollAreaControlsToCurrentSettings(); + + -- Get the scroll area mover control frame. + local controlFrame = getglobal(SCROLL_AREA_MOVER_CONTROL_FRAME_NAME); + + -- Save the incoming scroll area settings into the profile. + MikSBT.SetOptionFromVarPath("DisplaySettings.Incoming.AnimationStyle", controlFrame[settingsType].IncomingAnimationStyle); + MikSBT.SetOptionFromVarPath("DisplaySettings.Incoming.ScrollDirection", controlFrame[settingsType].IncomingScrollDirection); + MikSBT.SetOptionFromVarPath("DisplaySettings.Incoming.FontSettings.TextAlignIndex", controlFrame[settingsType].IncomingTextAlignIndex); + MikSBT.SetOptionFromVarPath("DisplaySettings.Incoming.ScrollHeight", controlFrame[settingsType].IncomingScrollHeight); + MikSBT.SetOptionFromVarPath("DisplaySettings.Incoming.FrameOffsets.x", controlFrame[settingsType].IncomingXOffset); + MikSBT.SetOptionFromVarPath("DisplaySettings.Incoming.FrameOffsets.y", controlFrame[settingsType].IncomingYOffset); + + -- Save the outgoing scroll area settings into the profile. + MikSBT.SetOptionFromVarPath("DisplaySettings.Outgoing.AnimationStyle", controlFrame[settingsType].OutgoingAnimationStyle); + MikSBT.SetOptionFromVarPath("DisplaySettings.Outgoing.ScrollDirection", controlFrame[settingsType].OutgoingScrollDirection); + MikSBT.SetOptionFromVarPath("DisplaySettings.Outgoing.FontSettings.TextAlignIndex", controlFrame[settingsType].OutgoingTextAlignIndex); + MikSBT.SetOptionFromVarPath("DisplaySettings.Outgoing.ScrollHeight", controlFrame[settingsType].OutgoingScrollHeight); + MikSBT.SetOptionFromVarPath("DisplaySettings.Outgoing.FrameOffsets.x", controlFrame[settingsType].OutgoingXOffset); + MikSBT.SetOptionFromVarPath("DisplaySettings.Outgoing.FrameOffsets.y", controlFrame[settingsType].OutgoingYOffset); + + -- Save the notification scroll area settings into the profile. + MikSBT.SetOptionFromVarPath("DisplaySettings.Notification.AnimationStyle", controlFrame[settingsType].NotificationAnimationStyle); + MikSBT.SetOptionFromVarPath("DisplaySettings.Notification.ScrollDirection", controlFrame[settingsType].NotificationScrollDirection); + MikSBT.SetOptionFromVarPath("DisplaySettings.Notification.FontSettings.TextAlignIndex", controlFrame[settingsType].NotificationTextAlignIndex); + MikSBT.SetOptionFromVarPath("DisplaySettings.Notification.ScrollHeight", controlFrame[settingsType].NotificationScrollHeight); + MikSBT.SetOptionFromVarPath("DisplaySettings.Notification.FrameOffsets.x", controlFrame[settingsType].NotificationXOffset); + MikSBT.SetOptionFromVarPath("DisplaySettings.Notification.FrameOffsets.y", controlFrame[settingsType].NotificationYOffset); + + + -- Reposition the animation frames. + MikSBT.RepositionAnimationFrames(); +end + + +-- ********************************************************************************** +-- Previews a test message in the new scroll area position. +-- ********************************************************************************** +function MikSBTOpt.ScrollAreaMoverPreviewOnClick() + -- Save the current settings for all the scroll areas. + MikSBTOpt.SaveScrollAreaSettings("MSBTCurrentSettings"); + + -- Display the preview messages. + MikSBT.DisplayMessage(MikSBTOpt.MSG_SCROLL_AREA_PREVIEW_TEXT, MikSBT.DISPLAYTYPE_INCOMING, false, 255, 0, 0); + MikSBT.DisplayMessage(MikSBTOpt.MSG_SCROLL_AREA_PREVIEW_TEXT, MikSBT.DISPLAYTYPE_NOTIFICATION, false); + MikSBT.DisplayMessage(MikSBTOpt.MSG_SCROLL_AREA_PREVIEW_TEXT, MikSBT.DISPLAYTYPE_OUTGOING, false, 0, 0, 255); +end + + +-- ********************************************************************************** +-- Called the save button on the scroll area mover is clicked. +-- ********************************************************************************** +function MikSBTOpt.ScrollAreaMoverSaveOnClick() + -- Save the current settings for all the scroll areas. + MikSBTOpt.SaveScrollAreaSettings("MSBTCurrentSettings"); + + -- Show the options frame and hide the scroll area mover frames. + MikSBTOpt.HideScrollAreaMoverFrames(); +end + + +-- ********************************************************************************** +-- Cancels the changes made to the scroll area mover. +-- ********************************************************************************** +function MikSBTOpt.ScrollAreaMoverCancelOnClick() + -- Save the original settings for all the scroll areas. + MikSBTOpt.SaveScrollAreaSettings("MSBTOriginalSettings"); + + -- Show the options frame and hide the scroll area mover frames. + MikSBTOpt.HideScrollAreaMoverFrames(); +end + + + +------------------------------------------------------------------------------------- +-- Trigger functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Sets up the available trigger events. +-- ********************************************************************************** +function MikSBTOpt.SetupAvailableTriggerEvents() + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_PARTY_HITS"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_PARTY_MISSES"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PARTY_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_SELF_HITS"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_SELF_MISSES"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_SELF_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_SELF_BUFF"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_PET_HITS"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_PET_MISSES"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_PET_DAMAGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS"); + table.insert(availableTriggerEvents, "CHAT_MSG_SPELL_AURA_GONE_SELF"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_HONOR_GAIN"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_FACTION_CHANGE"); + table.insert(availableTriggerEvents, "CHAT_MSG_SKILL"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_XP_GAIN"); + table.insert(availableTriggerEvents, "CHAT_MSG_COMBAT_HOSTILE_DEATH"); +end + + +-- ********************************************************************************** +-- Adds a new trigger. +-- ********************************************************************************** +function MikSBTOpt.AddNewTrigger() + -- Hold the next available custom trigger number. + local nextTriggerNum = 0; + local slotFound = false; + + -- Loop until an available slot is found. + while (true) do + -- Increment to the next trigger number. + nextTriggerNum = nextTriggerNum + 1; + + if (not MikSBT.CurrentProfile.Triggers["MSBT_TRIGGER_CUSTOM" .. nextTriggerNum]) then + break; + end + end + + -- Create the new default trigger. + MikSBT.CurrentProfile.Triggers["MSBT_TRIGGER_CUSTOM" .. nextTriggerNum] = { + EventSettings = { + Show = true, + Message = MikSBTOpt.MSG_TRIGGER_NEW_TRIGGER_DEFAULT_MESSAGE, + IsSticky = false, + FontSettings = { + Color = {r=1, g=0.502, b=0.502}, + Normal = {FontIndex=0, OutlineIndex=0, FontSize=26}, + }, + }, + TriggerSettings = { + TriggerType = 1, + Threshold = 40, + }, + } + + -- Reregister the trigger with the event helper. + MikSBT.RegisterTriggers(); + + -- Repopulate the trigger event settings so the new trigger shows up. + MikSBTOpt.PopulateTriggerEventSettings(); +end + +-- ********************************************************************************** +-- Deletes the passed trigger. +-- ********************************************************************************** +function MikSBTOpt.DeleteTrigger(triggerKey) + -- Remove the trigger. + MikSBT.CurrentProfile.Triggers[triggerKey] = nil; + + -- Reregister the trigger with the event helper. + MikSBT.RegisterTriggers(); + + -- Repopulate the trigger event settings. + MikSBTOpt.PopulateTriggerEventSettings(); +end + + +-- ********************************************************************************** +-- Shows the trigger settings for the passed trigger key. +-- ********************************************************************************** +function MikSBTOpt.ShowTriggerConfig(triggerKey) + -- Get the trigger configuration frame. + local triggerFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME); + + -- Set the trigger key for the config frame. + triggerFrame.TriggerKey = triggerKey; + + -- Get the trigger data for the passed trigger and make sure it exists. + local triggerData = MikSBT.CurrentProfile.Triggers[triggerKey]; + if (triggerData) then + -- Set the title to the output message. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "Title"):SetText(triggerData.EventSettings.Message); + + -- Check if all classes are selected. + if (not triggerData.TriggerSettings.Classes) then + -- Check the all classes checkbox and hide the individual class checkboxes. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "AllClassesCheckbox"):SetChecked(1); + MikSBTOpt.HideTriggerClassCheckboxes(); + else + -- Uncheck the all classes checkbox and show the individual class checkboxes. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "AllClassesCheckbox"):SetChecked(0); + MikSBTOpt.ShowTriggerClassCheckboxes(); + + -- Set the correct individual class checkbox settings. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "DruidCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.DRUID); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "HunterCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.HUNTER); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "MageCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.MAGE); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PaladinCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.PALADIN); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PriestCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.PRIEST); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "RogueCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.ROGUE); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ShamanCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.SHAMAN); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarlockCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.WARLOCK); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarriorCheckbox"):SetChecked(triggerData.TriggerSettings.Classes.WARRIOR); + end + + -- Populate the trigger type. + MikSBTOpt.PopulateDropdown(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerTypeDropdown", triggerData.TriggerSettings.TriggerType); + + -- Setup the trigger controls appropriately depending on the trigger type. + MikSBTOpt.SetupVariableTriggerControls(triggerKey, triggerData.TriggerSettings.TriggerType == MikCEH.TRIGGERTYPE_SEARCH_PATTERN); + + -- Show the trigger settings. + triggerFrame:Show(); + end +end + + +-- ********************************************************************************** +-- Show or hides the search pattern controls on the trigger config frame. +-- ********************************************************************************** +function MikSBTOpt.SetupVariableTriggerControls(triggerKey, showSearch) + -- Get the trigger configuration frame. + local triggerFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME); + + -- Get the trigger data for the passed trigger and make sure it exists. + local triggerData = MikSBT.CurrentProfile.Triggers[triggerKey]; + if (triggerData) then + + -- Check if the search pattern controls should be shown. + if (showSearch) then + -- Resize the trigger frame. + triggerFrame:SetHeight(420); + + -- Hide the threshold slider. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ThresholdSlider"):Hide(); + + -- TODO: Show the search pattern controls. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "EventsScrollFrame"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent1Checkbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent2Checkbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent3Checkbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent4Checkbox"):Show(); + + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern1Editbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern2Editbox"):Show(); + + -- Clear the selected trigger events. + if (not triggerFrame.SelectedTriggerEvents) then + triggerFrame.SelectedTriggerEvents = {}; + else + MikCEH.EraseTable(triggerFrame.SelectedTriggerEvents); + end + + -- Loop through all of the trigger events for the trigger. + for _, triggerEvent in triggerData.TriggerSettings.TriggerEvents do + triggerFrame.SelectedTriggerEvents[triggerEvent] = true; + end + + -- Populate the appropriate trigger events. + MikSBTOpt.PopulateAvailableTriggerEvents(); + + + -- Holds the search patterns. + local searchPattern1 = ""; + local searchPattern2 = ""; + + -- Check if there are search patterns defined. + if (triggerData.TriggerSettings.SearchPatterns) then + searchPattern1 = triggerData.TriggerSettings.SearchPatterns[1] or ""; + searchPattern2 = triggerData.TriggerSettings.SearchPatterns[2] or ""; + end + + -- Populate the search pattern settings. + MikSBTOpt.PopulateEditbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern1Editbox", searchPattern1); + MikSBTOpt.PopulateEditbox(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern2Editbox", searchPattern2); + + else + -- Resize the trigger frame. + triggerFrame:SetHeight(270); + + -- Show the threshold slider. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ThresholdSlider"):Show(); + + -- Hide the search pattern controls. + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "EventsScrollFrame"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent1Checkbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent2Checkbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent3Checkbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent4Checkbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern1Editbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern2Editbox"):Hide(); + + -- Populate the threshold slider. + MikSBTOpt.PopulateSlider(TRIGGER_CONFIGURATION_FRAME_NAME .. "ThresholdSlider", triggerData.TriggerSettings.Threshold or 1); + end + end +end + + +-- ********************************************************************************** +-- Hides the individual class checkboxes on the trigger config frame. +-- ********************************************************************************** +function MikSBTOpt.HideTriggerClassCheckboxes() + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "DruidCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "HunterCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "MageCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PaladinCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PriestCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "RogueCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ShamanCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarlockCheckbox"):Hide(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarriorCheckbox"):Hide(); +end + + +-- ********************************************************************************** +-- Shows the individual class checkboxes on the trigger config frame. +-- ********************************************************************************** +function MikSBTOpt.ShowTriggerClassCheckboxes() + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "DruidCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "HunterCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "MageCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PaladinCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PriestCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "RogueCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ShamanCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarlockCheckbox"):Show(); + getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarriorCheckbox"):Show(); +end + + +-- ********************************************************************************** +-- Saves the settings in the trigger config frame to the appropriate trigger. +-- ********************************************************************************** +function MikSBTOpt.SaveTriggerConfig() + -- Get the trigger configuration frame. + local triggerFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME); + + -- Get the trigger data for the trigger that is being saved and make sure it exists. + local triggerData = MikSBT.CurrentProfile.Triggers[triggerFrame.TriggerKey]; + if (triggerData) then + -- Check if the all classes checkbox is checked. + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "AllClassesCheckbox"):GetChecked()) then + -- Clear the selected classes table for the trigger to indicate all classes. + triggerData.TriggerSettings.Classes = nil; + + -- The all classes checkbox is not checked. + else + -- Create a table to hold the selected classes. + local selectedClasses = {}; + + -- Set the class in the selected classes array if it's checked. + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "DruidCheckbox"):GetChecked()) then selectedClasses.DRUID = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "HunterCheckbox"):GetChecked()) then selectedClasses.HUNTER = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "MageCheckbox"):GetChecked()) then selectedClasses.MAGE = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PaladinCheckbox"):GetChecked()) then selectedClasses.PALADIN = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "PriestCheckbox"):GetChecked()) then selectedClasses.PRIEST = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "RogueCheckbox"):GetChecked()) then selectedClasses.ROGUE = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ShamanCheckbox"):GetChecked()) then selectedClasses.SHAMAN = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarlockCheckbox"):GetChecked()) then selectedClasses.WARLOCK = true; end + if (getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "WarriorCheckbox"):GetChecked()) then selectedClasses.WARRIOR = true; end + + -- Save the selected classes for the trigger. + triggerData.TriggerSettings.Classes = selectedClasses; + end + + -- Save the trigger type. + triggerData.TriggerSettings.TriggerType = UIDropDownMenu_GetSelectedValue(getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerTypeDropdown")); + + + -- Check if the trigger type is search pattern. + if (triggerData.TriggerSettings.TriggerType == MikCEH.TRIGGERTYPE_SEARCH_PATTERN) then + -- Clear the threshold field. + triggerData.TriggerSettings.Threshold = nil; + + local trigEvents = {}; + for triggerEvent in triggerFrame.SelectedTriggerEvents do + table.insert(trigEvents, triggerEvent); + end + + -- Save the selected trigger events. + triggerData.TriggerSettings.TriggerEvents = trigEvents; + + -- Create a table to hold the search patterns. + local searchPatterns = {}; + + -- Get the first search pattern, check if it is set to anything, and add it to the search patterns table. + local searchPattern = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern1Editbox"):GetText(); + if (searchPattern ~= "") then + table.insert(searchPatterns, searchPattern); + end + + -- Get the second search pattern, check if it is set to anything, and add it to the search patterns table. + searchPattern = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "SearchPattern2Editbox"):GetText(); + if (searchPattern ~= "") then + table.insert(searchPatterns, searchPattern); + end + + -- Save the search patterns for the trigger. + triggerData.TriggerSettings.SearchPatterns = searchPatterns; + + -- Trigger type is a threshold. + else + -- Clear the search pattern fields. + triggerData.TriggerSettings.TriggerEvents = nil; + triggerData.TriggerSettings.SearchPatterns = nil; + + -- Save the threshold. + triggerData.TriggerSettings.Threshold = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "ThresholdSlider"):GetValue(); + end + + -- Reregister all of the triggers. + MikSBT.RegisterTriggers(); + end +end + + + +-- ********************************************************************************** +-- Populates the available trigger events scroll frame. +-- ********************************************************************************** +function MikSBTOpt.PopulateAvailableTriggerEvents() + local triggerFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME); + + -- Get the total number of available trigger events. + local totalItems = table.getn(availableTriggerEvents); + + -- Get the trigger settings scroll frame and its offset. + local objFrame = getglobal(TRIGGER_CONFIGURATION_FRAME_NAME .. "EventsScrollFrame"); + FauxScrollFrame_Update(objFrame, totalItems, TRIGGER_EVENT_ITEMS_PER_PAGE, TRIGGER_EVENT_ITEM_HEIGHT); + local skipCount = FauxScrollFrame_GetOffset(objFrame); + + -- Loop through all of the visible items. + for i = 1, TRIGGER_EVENT_ITEMS_PER_PAGE do + local checkboxName = TRIGGER_CONFIGURATION_FRAME_NAME .. "TriggerEvent" .. i .. "Checkbox"; + local objCheckbox = getglobal(checkboxName); + local triggerEvent = availableTriggerEvents[i+skipCount]; + + -- Set the checkbox's name to the trigger event. + MikSBTOpt.SetupCheckbox(checkboxName, triggerEvent, nil, nil); + if (objCheckbox) then objCheckbox.TriggerEvent = triggerEvent; end + + -- If the selected trigger events table exists get the appropriate value from it. + if (triggerFrame.SelectedTriggerEvents) then + MikSBTOpt.PopulateCheckbox(checkboxName, triggerFrame.SelectedTriggerEvents[triggerEvent] ~= nil); + -- Selected trigger events table doesn't exist so just uncheck the checkbox. + else + MikSBTOpt.PopulateCheckbox(checkboxName, false); + end + end +end + + +------------------------------------------------------------------------------------- +-- Suppression functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Adds a new suppression. +-- ********************************************************************************** +function MikSBTOpt.AddNewSuppression() + -- Hold the next available custom suppression number. + local nextSuppressionNum = 0; + local slotFound = false; + + -- Loop until an available slot is found. + while (true) do + -- Increment to the next suppression number. + nextSuppressionNum = nextSuppressionNum + 1; + + if (not MikSBT.CurrentProfile.Suppressions["MSBT_SUPPRESSION_CUSTOM" .. nextSuppressionNum]) then + break; + end + end + + -- Create the new default trigger. + MikSBT.CurrentProfile.Suppressions["MSBT_SUPPRESSION_CUSTOM" .. nextSuppressionNum] = { + Enabled = true, + SearchPattern = MikSBTOpt.MSG_SUPPRESSION_NEW_SUPPRESSION_DEFAULT_MESSAGE, + }; + + -- Repopulate the suppression settings so the new suppression shows up. + MikSBTOpt.PopulateSuppressionSettings(); +end + +------------------------------------------------------------------------------------- +-- Populate profile functions. +------------------------------------------------------------------------------------- + +-- ********************************************************************************** +-- Populates the options from the currently selected profile. +-- ********************************************************************************** +function MikSBTOpt.PopulateProfileOptions() + -- Hide the font settings frame when profile options are populated. + getglobal(FONT_SETTINGS_FRAME_NAME):Hide(); + + + -- Populate the Animation Step slider value. + MikSBTOpt.PopulateSlider(OPTIONS_FRAME_NAME .. "Tab1FrameAnimationStepSlider"); + + -- Populate the Show Partial Events checkbox. + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab1FrameShowPartialEffectsCheckbox"); + + -- Populate the Use Sticky Crits checkbox. + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab1FrameUseStickyCritsCheckbox"); + + -- Populate the Show Overheals checkbox. + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab1FrameShowOverhealsCheckbox"); + + -- Populate the Show Game Damage checkbox and update it appropriately. + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab1FrameShowGameDamageCheckbox"); + MikSBT.SetOptionShowGameDamage(MikSBT.CurrentProfile.ShowGameDamage); + + + -- Populate the scroll area show events checkboxes to the values in the profile. + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab2FrameShowEventsCheckbox"); + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab3FrameShowEventsCheckbox"); + MikSBTOpt.PopulateCheckbox(OPTIONS_FRAME_NAME .. "Tab4FrameShowEventsCheckbox"); + + + -- Populate the incoming, outgoing, and notification event settings. + MikSBTOpt.PopulateIncomingEventSettings(); + MikSBTOpt.PopulateOutgoingEventSettings(); + MikSBTOpt.PopulateNotificationEventSettings(); + + + -- Populate the trigger settings. + MikSBTOpt.PopulateTriggerEventSettings(); + + -- Populate the suppression settings. + MikSBTOpt.PopulateSuppressionSettings(); +end \ No newline at end of file diff --git a/MikScrollingBattleTextOptions/MSBTOptions.xml b/MikScrollingBattleTextOptions/MSBTOptions.xml new file mode 100644 index 0000000..3f46616 --- /dev/null +++ b/MikScrollingBattleTextOptions/MSBTOptions.xml @@ -0,0 +1,2645 @@ + + + + + + + + + + + + + + + + + + + + + + + + if (this.tooltipText ~= nil) then + GameTooltip:SetOwner(this, "ANCHOR_RIGHT"); + GameTooltip:SetText(this.tooltipText, nil, nil, nil, nil, 1); + end + + GameTooltip:Hide(); + + + + + + + + + + + if (this:GetChecked()) then + PlaySound("igMainMenuOptionCheckBoxOff"); + else + PlaySound("igMainMenuOptionCheckBoxOn"); + end + + -- Call the font settings checkbox OnClick handler. + MikSBTOpt.CheckboxOnClick(); + + + + + + + + + MikSBTOpt.SliderOnValueChanged(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (this.tooltipText ~= nil) then + GameTooltip:SetOwner(this, "ANCHOR_RIGHT"); + GameTooltip:SetText(this.tooltipText, nil, nil, nil, nil, 1); + end + + GameTooltip:Hide(); + MikSBTOpt.EditboxOnTextChanged(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (this:GetChecked()) then + PlaySound("igMainMenuOptionCheckBoxOff"); + else + PlaySound("igMainMenuOptionCheckBoxOn"); + end + + -- Call the trigger settings checkbox OnClick handler. + MikSBTOpt.EnableTriggerCheckboxOnClick(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + local scrollbar = getglobal(this:GetName().."ScrollBar"); + scrollbar:SetMinMaxValues(0, 1); + scrollbar:SetValue(1); + + MikSBTOpt.OnVerticalScroll(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + local scrollbar = getglobal(this:GetName().."ScrollBar"); + scrollbar:SetMinMaxValues(0, 1); + scrollbar:SetValue(1); + + MikSBTOpt.OnVerticalScroll(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + local scrollbar = getglobal(this:GetName().."ScrollBar"); + scrollbar:SetMinMaxValues(0, 1); + scrollbar:SetValue(1); + + MikSBTOpt.OnVerticalScroll(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (this.tooltipText ~= nil) then + GameTooltip:SetOwner(this, "ANCHOR_RIGHT"); + GameTooltip:SetText(this.tooltipText, nil, nil, nil, nil, 1); + end + + GameTooltip:Hide(); + + -- Start moving the frame if the left mouse button is pressed. + if (arg1 == "LeftButton") then + this:StartMoving(); + end + + + -- Stop moving the frame. + this:StopMovingOrSizing(); + + -- Save the scroll area's coordinates to the control frame. + MikSBTOpt.SaveScrollAreaMoverCoordinates(this:GetName()); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MikSBTOpt.FontSettingsSliderOnValueChanged(); + + + + + + + + + + + + + + MikSBTOpt.FontSettingsSliderOnValueChanged(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:SetBackdropColor(0.05, 0.05, 0.05, 1.0); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MikSBTOpt.ScrollAreaMoverControlSliderOnValueChanged(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:SetBackdropColor(0.05, 0.05, 0.05, 1.0); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + local scrollbar = getglobal(this:GetName().."ScrollBar"); + scrollbar:SetMinMaxValues(0, 1); + scrollbar:SetValue(1); + + MikSBTOpt.OnVerticalScroll(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:SetBackdropColor(0.05, 0.05, 0.05, 1.0); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- Start moving the parent frame if the left mouse button is pressed. + if (arg1 == "LeftButton") then + this:GetParent():StartMoving(); + end + + + -- Stop moving the parent frame. + this:GetParent():StopMovingOrSizing(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MikSBTOpt.OnEvent(); + MikSBTOpt.OnLoad(); + MikSBTOpt.OnHide(); + MikSBTOpt.OnShow(); + + + + \ No newline at end of file diff --git a/MikScrollingBattleTextOptions/MikScrollingBattleTextOptions.toc b/MikScrollingBattleTextOptions/MikScrollingBattleTextOptions.toc new file mode 100644 index 0000000..6621658 --- /dev/null +++ b/MikScrollingBattleTextOptions/MikScrollingBattleTextOptions.toc @@ -0,0 +1,9 @@ +## Interface: 11200 +## Version: 3.0 +## Title: Mik's Scrolling Battle Text Options +## Notes: Load on demand options for Mik's Scrolling Battle Text +## Dependencies: MikScrollingBattleText +## LoadOnDemand: 1 +localization_options.lua +MSBTOptions.lua +MSBTOptions.xml \ No newline at end of file diff --git a/MikScrollingBattleTextOptions/localization_options.lua b/MikScrollingBattleTextOptions/localization_options.lua new file mode 100644 index 0000000..a3f4a29 --- /dev/null +++ b/MikScrollingBattleTextOptions/localization_options.lua @@ -0,0 +1,259 @@ +------------------------------------------------------------------------------------- +-- Title: Mik's Scrolling Battle Text Options +-- Author: Mik +------------------------------------------------------------------------------------- + +-- Create Options "namespace." +MikSBTOpt = {}; + + +------------------------------ +-- Interface messages +------------------------------ + +MikSBTOpt.MSG_INHERIT_TEXT = "Inherit"; +MikSBTOpt.MSG_NORMAL_PREVIEW_TEXT = "Normal"; +MikSBTOpt.MSG_CRIT_PREVIEW_TEXT = "Crit"; +MikSBTOpt.MSG_CURRENT_PROFILE_TEXT = "Current Profile"; +MikSBTOpt.MSG_SCROLL_AREA_PREVIEW_TEXT = "Preview Message"; + + +MikSBTOpt.MSG_EVENT_MESSAGE_LABEL_TOOLTIP = "Click here to edit what output message will be displayed when the event occurs.\n\nAny event codes will be substituted with actual data." +MikSBTOpt.MSG_EVENT_MESSAGE_EDITBOX_TOOLTIP_PREFIX = "Available Event Codes:\n\n"; +MikSBTOpt.MSG_EVENT_COLOR_SWATCH_TOOLTIP = "Allows you to pick the display color for the event."; +MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_LABEL = "Edit Fonts"; +MikSBTOpt.MSG_EVENT_EDIT_FONT_SETTINGS_BUTTON_TOOLTIP = "Edits the event's font settings. These settings are inherited from the scroll area if they are set to inherit."; +MikSBTOpt.MSG_EVENT_STICKY_CHECKBOX_TOOLTIP = "Sets whether or not to display this event using the sticky style."; +MikSBTOpt.MSG_EVENT_EDIT_TRIGGER_BUTTON_LABEL = "Edit Trigger"; +MikSBTOpt.MSG_EVENT_EDIT_TRIGGER_BUTTON_TOOLTIP = "Edits the event's trigger settings."; +MikSBTOpt.MSG_EVENT_SOUND_BUTTON_TOOLTIP = "Allows you to pick a sound to play when the event occurs."; +MikSBTOpt.MSG_TRIGGER_ENABLE_TOOLTIP = "Sets whether or not the trigger is enabled." +MikSBTOpt.MSG_TRIGGER_MESSAGE_LABEL_TOOLTIP = "Click here to edit what output message will be displayed when the trigger occurs." +MikSBTOpt.MSG_TRIGGER_MESSAGE_EDITBOX_TOOLTIP = "Enter the output message to be displayed with the trigger occurs."; +MikSBTOpt.MSG_TRIGGER_STICKY_CHECKBOX_TOOLTIP = "Sets whether or not to display this trigger using the sticky style."; +MikSBTOpt.MSG_TRIGGER_EDIT_FONT_SETTINGS_BUTTON_LABEL = "Edit Fonts"; +MikSBTOpt.MSG_TRIGGER_EDIT_FONT_SETTINGS_BUTTON_TOOLTIP = "Edits the trigger's font settings. These settings are inherited from the notification scroll area if they are set to inherit."; +MikSBTOpt.MSG_TRIGGER_EDIT_TRIGGER_SETTINGS_BUTTON_LABEL = "Edit Trigger"; +MikSBTOpt.MSG_TRIGGER_EDIT_TRIGGER_SETTINGS_BUTTON_TOOLTIP = "Edits the trigger's settings."; +MikSBTOpt.MSG_TRIGGER_DELETE_TRIGGER_BUTTON_TOOLTIP = "Deletes the trigger."; +MikSBTOpt.MSG_TRIGGER_APPLICABLE_CLASSES_LABEL = "Applicable Trigger Classes:"; +MikSBTOpt.MSG_TRIGGER_NEW_TRIGGER_DEFAULT_MESSAGE = "New Trigger: Click here to edit."; +MikSBTOpt.MSG_SUPPRESSION_ENABLE_TOOLTIP = "Sets whether or not the suppression is enabled."; +MikSBTOpt.MSG_SUPPRESSION_DELETE_SUPPRESSION_BUTTON_TOOLTIP = "Deletes the suppression."; +MikSBTOpt.MSG_SUPPRESSION_MESSAGE_LABEL_TOOLTIP = "Click here to edit the search pattern to suppress." +MikSBTOpt.MSG_SUPPRESSION_MESSAGE_EDITBOX_TOOLTIP = "Enter the search pattern to be suppressed."; +MikSBTOpt.MSG_SUPPRESSION_NEW_SUPPRESSION_DEFAULT_MESSAGE = "New Suppression: Click here to edit."; + +-- Class Names. +MikSBTOpt.MSG_CLASS_NAME_DRUID = "Druid"; +MikSBTOpt.MSG_CLASS_NAME_HUNTER = "Hunter"; +MikSBTOpt.MSG_CLASS_NAME_MAGE = "Mage"; +MikSBTOpt.MSG_CLASS_NAME_PALADIN = "Paladin"; +MikSBTOpt.MSG_CLASS_NAME_PRIEST = "Priest"; +MikSBTOpt.MSG_CLASS_NAME_ROGUE = "Rogue"; +MikSBTOpt.MSG_CLASS_NAME_SHAMAN = "Shaman"; +MikSBTOpt.MSG_CLASS_NAME_WARLOCK = "Warlock"; +MikSBTOpt.MSG_CLASS_NAME_WARRIOR = "Warrior"; + + +------------------------------ +-- Interface objects info +------------------------------ + +MikSBTOpt.TABS = { + [1] = { Label="General", Tooltip="Display general options."}, + [2] = { Label="Incoming", Tooltip="Display options for the incoming scroll area."}, + [3] = { Label="Outgoing", Tooltip="Display options for the outgoing scroll area."}, + [4] = { Label="Notification", Tooltip="Display options for the notification scroll area."}, + [5] = { Label="Triggers", Tooltip="Display options for the trigger system."}, + [6] = { Label="Suppressions", Tooltip="Display options for the suppression system."}, +}; + +MikSBTOpt.DROPDOWNS = { + ["Tab1FrameProfileDropdown"] = { Label="Profile", Tooltip="Selects the current profile."}, + ["ScrollAreaDropdown"] = { Label="Scroll Area", Tooltip="Selects the scroll area to configure."}, + ["TextAlignDropdown"] = { Label="Text Align", Tooltip="The alignment of the text for the scroll area."}, + ["AnimationStyleDropdown"] = { Label="Animation Style", Tooltip="The animation style for the scroll area."}, + ["ScrollDirectionDropdown"] = { Label="Scroll Direction", Tooltip="The scroll direction for the scroll area."}, + ["FontDropdown"] = { Label="Normal Font"}, + ["FontOutlineDropdown"] = { Label="Normal Font Outline"}, + ["FontCritDropdown"] = { Label="Crit Font"}, + ["FontOutlineCritDropdown"] = { Label="Crit Font Outline"}, + ["TriggerTypeDropdown"] = { Label="Trigger Type", Tooltip="Select the type of trigger."}, +}; + +MikSBTOpt.CHECKBOXES = { + ["Tab1FrameDisableCheckbox"] = { Label="Disable Mik's Scrolling Battle Text", Tooltip="Sets whether or not MSBT is disabled."}, + ["Tab1FrameShowPartialEffectsCheckbox"] = { Label="Show Partial Effects", Tooltip="Sets whether or not to show partial resists, absorbs, blocks, etc."}, + ["Tab1FrameUseStickyCritsCheckbox"] = { Label="Display Crits Sticky", Tooltip="Sets whether or not to display crits using the sticky style."}, + ["Tab1FrameShowOverhealsCheckbox"] = { Label="Show Overheals", Tooltip="Sets whether or not to display overhealing information."}, + ["Tab1FrameShowGameDamageCheckbox"] = { Label="Show Game Damage", Tooltip="Sets whether or not to display the game's damage above the enemy's heads."}, + ["Tab2FrameShowEventsCheckbox"] = { Label="Enable Incoming Events", Tooltip="Sets whether or not to enable incoming events."}, + ["Tab3FrameShowEventsCheckbox"] = { Label="Enable Outgoing Events", Tooltip="Sets whether or not to enable outgoing events."}, + ["Tab4FrameShowEventsCheckbox"] = { Label="Enable Notification Events", Tooltip="Sets whether or not to enable notification events."}, + ["InheritFontSizeCheckbox"] = { Label="Inherit Font Size"}, + ["InheritFontSizeCritCheckbox"] = { Label="Inherit Crit Font Size"}, +}; + +MikSBTOpt.SLIDERS = { + ["Tab1FrameAnimationStepSlider"] = { Label="Animation Speed", Tooltip="Sets the speed of the animation."}, + ["ScrollHeightSlider"] = { Label="Scroll Height", Tooltip="The height of the scroll area."}, + ["FontSizeSlider"] = { Label="Font Size"}, + ["FontSizeCritSlider"] = { Label="Font Crit Size"}, + ["ThresholdSlider"] = { Label="Threshold Percentage", Tooltip="Sets the threshold percentage which must be passed for the trigger to fire."}, +}; + +MikSBTOpt.BUTTONS = { + ["Tab1FrameEditFontSettingsButton"] = { Label="Edit Master Font Settings", Tooltip="Edits the master font settings. These settings will be inherited by each scroll area and the events therein unless they are overridden."}, + ["Tab2FrameEditFontSettingsButton"] = { Label="Edit Scroll Area Fonts", Tooltip="Edits the incoming scroll area font settings. These settings will be inherited by each event in the incoming scroll area unless they are overridden."}, + ["Tab3FrameEditFontSettingsButton"] = { Label="Edit Scroll Area Fonts", Tooltip="Edits the outgoing scroll area font settings. These settings will be inherited by each event in the outgoing scroll area unless they are overridden."}, + ["Tab4FrameEditFontSettingsButton"] = { Label="Edit Scroll Area Fonts", Tooltip="Edits the notification scroll area font settings. These settings will be inherited by each event in the notification scroll area unless they are overridden."}, + ["Tab1FrameLoadProfileButton"] = { Label="Load Profile", Tooltip="Loads the selected profile."}, + ["Tab1FrameDeleteProfileButton"] = { Label="Delete Profile", Tooltip="Deletes the selected profile."}, + ["Tab1FrameResetProfileButton"] = { Label="Reset Profile", Tooltip="Resets the selected profile to the default settings."}, + ["Tab1FrameCreateProfileButton"] = { Label="Create Profile", Tooltip="Creates a new profile with the default settings."}, + ["Tab2FrameConfigureScrollAreaButton"] = { Label="Configure Scroll Area", Tooltip="Allows you to configure the animation style, scroll direction, text align, scroll height, and location of the scroll areas."}, + ["PreviewButton"] = { Label="Preview", Tooltip="Previews a test message in all the scrolling areas."}, + ["SaveButton"] = { Label="Save Settings", Tooltip="Saves the new settings for the scroll areas."}, + ["CancelButton"] = { Label="Cancel", Tooltip="Cancels the changes."}, + ["Tab5FrameAddTriggerButton"] = { Label="Add New Trigger", Tooltip="Adds a new trigger that can be configured."}, + ["SaveTriggerButton"] = { Label="Save Settings", Tooltip="Saves the settings for the trigger."}, + ["CancelTriggerButton"] = { Label="Cancel", Tooltip="Cancels the changes."}, + ["Tab6FrameAddSuppressionButton"] = { Label="Add New Suppression", Tooltip="Adds a new suppression."}, +}; + +MikSBTOpt.EDITBOXES = { + ["Tab1FrameNewProfileNameEditbox"] = { Label="New Profile Name", Tooltip="Allows you to enter the name of a new profile."}, + ["XOffsetEditbox"] = { Label="X Offset", Tooltip="Allows you to enter the X offset of the selected scroll frame."}, + ["YOffsetEditbox"] = { Label="Y Offset", Tooltip="Allows you to enter the Y offset of the selected scroll frame."}, + ["SearchPattern1Editbox"] = { Label="Search Pattern 1", Tooltip="Allows you to enter a search pattern that will cause the trigger to fire."}, + ["SearchPattern2Editbox"] = { Label="Search Pattern 2", Tooltip="Allows you to enter a search pattern that will cause the trigger to fire."}, +}; + +MikSBTOpt.FRAMES = { + ["MSBTScrollAreaMoverIncomingFrame"] = { Tooltip="Indicates the anchor column for the incoming scroll area. Drag this frame to the location you want or use the control frame x and y offset edit boxes."}, + ["MSBTScrollAreaMoverOutgoingFrame"] = { Tooltip="Indicates the anchor column for the outgoing scroll area. Drag this frame to the location you want or use the control frame x and y offset edit boxes."}, + ["MSBTScrollAreaMoverNotificationFrame"] = { Tooltip="Indicates the anchor column for the notification scroll area. Drag this frame to the location you want or use the control frame x and y offset edit boxes."}, +} + +MikSBTOpt.FONT_SETTINGS_TOOLTIPS = { + ["Master"] = { + FontSettingsTitle="Master", + Font="Set the master font for non-crits. This will be inherited by each scroll area and its events.", + FontCrit="Set the master font for crits. This will be inherited by each scroll area and its events.", + Outline="Set the master font outline for non-crits. This will be inherited by each scroll area and its events.", + OutlineCrit="Set the master font outline for crits. This will be inherited by each scroll area and its events.", + FontSize="Set the master font size for non-crits. This will be inherited by each scroll area and its events.", + FontSizeCrit="Set the master font size for crits. This will be inherited by each scroll area and its events.", + }, + ["ScrollArea"] = { + Font="Set the scroll area font for non-crits. This will be inherited by each event in the scroll area.", + FontCrit="Set the scroll area font for crits. This will be inherited by each event in the scroll area.", + Outline="Set the scroll area font outline for non-crits. This will be inherited by each event in the scroll area.", + OutlineCrit="Set the scroll area font outline for crits. This will be inherited by each event in the scroll area.", + InheritFontSize="Set whether or not to inherit the font size for non-crits from the master font settings.", + InheritFontSizeCrit="Set whether or not to inherit the font size for crits from the master font settings.", + FontSize="Set the scroll area font size for non-crits. This will be inherited by each event in the scroll area.", + FontSizeCrit="Set the scroll area font size for crits. This will be inherited by each event in the scroll area.", + }, + ["Event"] = { + Font="Set the event font for non-crits.", + FontCrit="Set the event font for crits.", + Outline="Set the event font outline for non-crits.", + OutlineCrit="Set the event font outline for crits.", + InheritFontSize="Set whether or not to inherit the font size for non-crits from the scroll area font settings.", + InheritFontSizeCrit="Set whether or not to inherit the font size for crits from the scroll area font settings.", + FontSize="Set the event font size for non-crits.", + FontSizeCrit="Set the event font size for crits.", + }, + ["Trigger"] = { + Font="Set the triger font.", + Outline="Set the trigger font outline.", + InheritFontSize="Set whether or not to inherit the font size from the notification scroll area font settings.", + FontSize="Set the trigger font size.", + } +} + +MikSBTOpt.INCOMING_EVENTS = { + [1] = { Label="Melee Damage", Tooltips={ShowCheckbox="Sets whether or not to show incoming melee damage.", Editbox="%a - Amount of damage taken.\n%n - Name of the attacker."}}, + [2] = { Label="Melee Misses", Tooltips={ShowCheckbox="Sets whether or not to show incoming melee misses.", Editbox="%n - Name of the attacker."}}, + [3] = { Label="Melee Dodges", Tooltips={ShowCheckbox="Sets whether or not to show incoming melee dodges.", Editbox="%n - Name of the attacker."}}, + [4] = { Label="Melee Parries", Tooltips={ShowCheckbox="Sets whether or not to show incoming melee parries.", Editbox="%n - Name of the attacker."}}, + [5] = { Label="Melee Blocks", Tooltips={ShowCheckbox="Sets whether or not to show incoming melee blocks.", Editbox="%n - Name of the attacker."}}, + [6] = { Label="Melee Absorbs", Tooltips={ShowCheckbox="Sets whether or not to show absorbed incoming melee damage.", Editbox="%n - Name of the attacker."}}, + [7] = { Label="Melee Immunes", Tooltips={ShowCheckbox="Sets whether or not to show incoming melee damage you are immune to.", Editbox="%n - Name of the attacker."}}, + [8] = { Label="Ability/Spell Damage", Tooltips={ShowCheckbox="Sets whether or not to show incoming spell and ability damage.", Editbox="%a - Amount of damage taken.\n%n - Name of the attacker.\n%s - Name of the spell or ability.\n%t - Type of damage taken."}}, + [9] = { Label="Ability/Spell DoTs", Tooltips={ShowCheckbox="Sets whether or not to show incoming spell and ability damage over time.", Editbox="%a - Amount of damage taken.\n%n - Name of the attacker.\n%s - Name of the spell or ability.\n%t - Type of damage taken."}}, + [10] = { Label="Ability Misses", Tooltips={ShowCheckbox="Sets whether or not to show incoming ability misses.", Editbox="%n - Name of the attacker.\n%s - Name of the ability."}}, + [11] = { Label="Ability Dodges", Tooltips={ShowCheckbox="Sets whether or not to show incoming ability dodges.", Editbox="%n - Name of the attacker.\n%s - Name of the ability."}}, + [12] = { Label="Ability Parries", Tooltips={ShowCheckbox="Sets whether or not to show incoming ability parries.", Editbox="%n - Name of the attacker.\n%s - Name of the ability."}}, + [13] = { Label="Ability Blocks", Tooltips={ShowCheckbox="Sets whether or not to show incoming ability blocks.", Editbox="%n - Name of the attacker.\n%s - Name of the ability."}}, + [14] = { Label="Spell Resists", Tooltips={ShowCheckbox="Sets whether or not to show incoming spell resists.", Editbox="%n - Name of the attacker.\n%s - Name of the spell."}}, + [15] = { Label="Ability/Spell Absorbs", Tooltips={ShowCheckbox="Sets whether or not to show absorbed damage from incoming spells and abilities.", Editbox="%n - Name of the attacker.\n%s - Name of the spell or ability."}}, + [16] = { Label="Ability/Spell Immunes", Tooltips={ShowCheckbox="Sets whether or not to show incoming spell and ability damage you are immune to.", Editbox="%n - Name of the attacker.\n%s - Name of the spell or ability."}}, + [17] = { Label="Ability/Spell Reflects", Tooltips={ShowCheckbox="Sets whether or not to show incoming spell and ability damage you reflected.", Editbox="%n - Name of the attacker.\n%s - Name of the spell or ability."}}, + [18] = { Label="Heals", Tooltips={ShowCheckbox="Sets whether or not to show incoming heals.", Editbox="%a - Amount of healing taken.\n%n - Name of the healer.\n%s - Name of the spell."}}, + [19] = { Label="Heals Over Time", Tooltips={ShowCheckbox="Sets whether or not to show incoming heals over time.", Editbox="%a - Amount of healing taken.\n%n - Name of the healer.\n%s - Name of the spell."}}, + [20] = { Label="Environmental Damage", Tooltips={ShowCheckbox="Sets whether or not to show environmental (falling, drowning, lava, etc...) damage.", Editbox="%a - Amount of damage taken.\n%s - Name of the source of the damage (falling, drowning, lava, etc...)"}}, +}; + +MikSBTOpt.OUTGOING_EVENTS = { + [1] = { Label="Melee Damage", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee damage.", Editbox="%a - Amount of damage done.\n%n - Name of the attacked unit."}}, + [2] = { Label="Melee Misses", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee misses.", Editbox="%n - Name of the attacked unit."}}, + [3] = { Label="Melee Dodges", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee dodges.", Editbox="%n - Name of the attacked unit."}}, + [4] = { Label="Melee Parries", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee parries.", Editbox="%n - Name of the attacked unit."}}, + [5] = { Label="Melee Blocks", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee blocks.", Editbox="%n - Name of the attacked unit."}}, + [6] = { Label="Melee Absorbs", Tooltips={ShowCheckbox="Sets whether or not to show absorbed outgoing melee damage.", Editbox="%n - Name of the attacked unit."}}, + [7] = { Label="Melee Immunes", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee damage the enemy is immune to.", Editbox="%n - Name of the attacked unit."}}, + [8] = { Label="Melee Evades", Tooltips={ShowCheckbox="Sets whether or not to show outgoing melee evades.", Editbox="%n - Name of the attacked unit."}}, + [9] = { Label="Ability/Spell Damage", Tooltips={ShowCheckbox="Sets whether or not to show outgoing spell and ability damage.", Editbox="%a - Amount of damage done.\n%n - Name of the attacked unit.\n%s - Name of the spell or ability.\n%t - Type of damage done."}}, + [10] = { Label="Ability/Spell DoTs", Tooltips={ShowCheckbox="Sets whether or not to show outgoing spell and ability damage over time.", Editbox="%a - Amount of damage done.\n%n - Name of the attacked unit.\n%s - Name of the spell or ability.\n%t - Type of damage done."}}, + [11] = { Label="Ability Misses", Tooltips={ShowCheckbox="Sets whether or not to show outgoing ability misses.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [12] = { Label="Ability Dodges", Tooltips={ShowCheckbox="Sets whether or not to show outgoing ability dodges.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [13] = { Label="Ability Parries", Tooltips={ShowCheckbox="Sets whether or not to show outgoing ability parries.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [14] = { Label="Ability Blocks", Tooltips={ShowCheckbox="Sets whether or not to show outgoing ability blocks.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [15] = { Label="Spell Resists", Tooltips={ShowCheckbox="Sets whether or not to show outgoing spell resists.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell."}}, + [16] = { Label="Ability/Spell Absorbs", Tooltips={ShowCheckbox="Sets whether or not to show absorbed damage from outgoing spells and abilities.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, + [17] = { Label="Ability/Spell Immunes", Tooltips={ShowCheckbox="Sets whether or not to show outgoing spell and ability damage the enemy is immune to.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, + [18] = { Label="Ability/Spell Reflects", Tooltips={ShowCheckbox="Sets whether or not to show outgoing spell and ability damage reflected back to you.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, + [19] = { Label="Ability/Spell Evades", Tooltips={ShowCheckbox="Sets whether or not to show outgoing spell and ability evades.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, + [20] = { Label="Heals", Tooltips={ShowCheckbox="Sets whether or not to show outgoing heals.", Editbox="%a - Amount of healing done.\n%n - Name of the healed unit.\n%s - Name of the spell."}}, + [21] = { Label="Heals Over Time", Tooltips={ShowCheckbox="Sets whether or not to show outgoing heals over time.", Editbox="%a - Amount of healing done.\n%n - Name of the healed unit.\n%s - Name of the spell."}}, + [22] = { Label="Pet Melee Damage", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee damage.", Editbox="%a - Amount of damage done.\n%n - Name of the attacked unit."}}, + [23] = { Label="Pet Melee Misses", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee misses.", Editbox="%n - Name of the attacked unit."}}, + [24] = { Label="Pet Melee Dodges", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee dodges.", Editbox="%n - Name of the attacked unit."}}, + [25] = { Label="Pet Melee Parries", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee parries.", Editbox="%n - Name of the attacked unit."}}, + [26] = { Label="Pet Melee Blocks", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee blocks.", Editbox="%n - Name of the attacked unit."}}, + [27] = { Label="Pet Melee Absorbs", Tooltips={ShowCheckbox="Sets whether or not to show your pet's absorbed outgoing melee damage.", Editbox="%n - Name of the attacked unit."}}, + [28] = { Label="Pet Melee Immunes", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee damage the enemy is immune to.", Editbox="%n - Name of the attacked unit."}}, + [29] = { Label="Pet Melee Evades", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing melee evades.", Editbox="%n - Name of the attacked unit."}}, + [30] = { Label="Pet Ability/Spell Damage", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing spell and ability damage.", Editbox="%a - Amount of damage done.\n%n - Name of the attacked unit.\n%s - Name of the spell or ability.\n%t - Type of damage done."}}, + [31] = { Label="Pet Ability Misses", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing ability misses.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [32] = { Label="Pet Ability Dodges", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing ability dodges.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [33] = { Label="Pet Ability Parries", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing ability parries.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [34] = { Label="Pet Ability Blocks", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing ability blocks.", Editbox="%n - Name of the attacked unit.\n%s - Name of the ability."}}, + [35] = { Label="Pet Spell Resists", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing spell resists.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell."}}, + [36] = { Label="Pet Ability/Spell Absorbs", Tooltips={ShowCheckbox="Sets whether or not to show your pet's absorbed damage from outgoing spells and abilities.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, + [37] = { Label="Pet Ability/Spell Immunes", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing spell and ability damage the enemy is immune to.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, + [38] = { Label="Pet Ability/Spell Evades", Tooltips={ShowCheckbox="Sets whether or not to show your pet's outgoing spell and ability evades.", Editbox="%n - Name of the attacked unit.\n%s - Name of the spell or ability."}}, +}; + +MikSBTOpt.NOTIFICATION_EVENTS = { + [1] = { Label="Debuffs", Tooltips={ShowCheckbox="Sets whether or not to show debuffs you are afflicted by.", Editbox="%s - Name of the debuff."}}, + [2] = { Label="Buffs", Tooltips={ShowCheckbox="Sets whether or not to show buffs you receive.", Editbox="%s - Name of the buff."}}, + [3] = { Label="Item Buffs", Tooltips={ShowCheckbox="Sets whether or not to show buffs your items receive.", Editbox="%s - Name of the item buff."}}, + [4] = { Label="Buff Fades", Tooltips={ShowCheckbox="Sets whether or not to show buffs and debuffs that have faded from you.", Editbox="%s - Name of the buff/debuff."}}, + [5] = { Label="Enter Combat", Tooltips={ShowCheckbox="Sets whether or not to show when you have entered combat.", Editbox="None."}}, + [6] = { Label="Leave Combat", Tooltips={ShowCheckbox="Sets whether or not to show when you have left combat.", Editbox="None."}}, + [7] = { Label="Power Gains", Tooltips={ShowCheckbox="Sets whether or not to show when you gain extra mana, rage, or energy.", Editbox="%a - Amount of energy gained.\n%s - Type of power gained (energy, rage, mana)."}}, + [8] = { Label="Power Losses", Tooltips={ShowCheckbox="Sets whether or not to show when you lose mana, rage, or energy from drains.", Editbox="%a - Amount of energy lost.\n%s - Type of power lost (energy, rage, mana)."}}, + [9] = { Label="Combo Point Gains", Tooltips={ShowCheckbox="Sets whether or not to show when you gain combo points.", Editbox="%a - Amount of combo points you have."}}, + [10] = { Label="Combo Points Full", Tooltips={ShowCheckbox="Sets whether or not to show when you attain full combo points.", Editbox="%a - Amount of combo points you have."}}, + [11] = { Label="Honor Gains", Tooltips={ShowCheckbox="Sets whether or not to show when you gain honor.", Editbox="%a - Amount of honor you gained."}}, + [12] = { Label="Reputation Gains", Tooltips={ShowCheckbox="Sets whether or not to show when you gain reputation.", Editbox="%a - Amount of reputation you gained.\n%s - Name of the faction."}}, + [13] = { Label="Reputation Losses", Tooltips={ShowCheckbox="Sets whether or not to show when you lose reputation.", Editbox="%a - Amount of reputation you lost.\n%s - Name of the faction."}}, + [14] = { Label="Skill Gains", Tooltips={ShowCheckbox="Sets whether or not to show when you gain skill points.", Editbox="%a - Amount of points you have in the skill.\n%s - Name of the skill."}}, + [15] = { Label="Experience Gains", Tooltips={ShowCheckbox="Sets whether or not to show when you gain experience points.", Editbox="%a - Amount of experience you gained."}}, + [16] = { Label="Player Killing Blows", Tooltips={ShowCheckbox="Sets whether or not to show when you get a killing blow against a hostile player.", Editbox="%s - Name of the unit killed."}}, + [17] = { Label="NPC Killing Blows", Tooltips={ShowCheckbox="Sets whether or not to show when you get a killing blow against an NPC.", Editbox="%s - Name of the unit killed."}}, +}; \ No newline at end of file