Compare with Previous | Blame | View Log
local MAJOR_VERSION = "LibHealComm-3.0"; local MINOR_VERSION = 90000 + tonumber(("$Revision: 48 $"):match("%d+")); local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION); if not lib then return end local playerName = UnitName('player'); local playerRealm = GetRealmName(); local playerClass = select(2, UnitClass('player')); local isHealer = (playerClass == "PRIEST") or (playerClass == "SHAMAN") or (playerClass == "DRUID") or (playerClass == "PALADIN"); ----------------- -- Event Frame -- ----------------- lib.EventFrame = lib.EventFrame or CreateFrame("Frame"); lib.EventFrame:SetScript("OnEvent", function (this, event, ...) lib[event](lib, ...) end); lib.EventFrame:UnregisterAllEvents(); -- Register Events lib.EventFrame:RegisterEvent("PLAYER_ALIVE"); lib.EventFrame:RegisterEvent("LEARNED_SPELL_IN_TAB"); lib.EventFrame:RegisterEvent("CHAT_MSG_ADDON"); lib.EventFrame:RegisterEvent("UNIT_SPELLCAST_DELAYED"); lib.EventFrame:RegisterEvent("UNIT_AURA"); lib.EventFrame:RegisterEvent("UNIT_TARGET"); lib.EventFrame:RegisterEvent("PLAYER_TARGET_CHANGED"); lib.EventFrame:RegisterEvent("PLAYER_FOCUS_CHANGED"); lib.EventFrame:RegisterEvent("GLYPH_ADDED"); lib.EventFrame:RegisterEvent("GLYPH_REMOVED"); lib.EventFrame:RegisterEvent("GLYPH_UPDATED"); -- For keeping track of versions lib.EventFrame:RegisterEvent("PARTY_MEMBERS_CHANGED"); lib.EventFrame:RegisterEvent("RAID_ROSTER_UPDATE"); -- Prune data at zone change lib.EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD"); -- Only listen to these events if player is healing class if (isHealer) then lib.EventFrame:RegisterEvent("UNIT_SPELLCAST_SENT"); lib.EventFrame:RegisterEvent("UNIT_SPELLCAST_START"); lib.EventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED"); lib.EventFrame:RegisterEvent("UNIT_SPELLCAST_STOP"); end ---------------------- -- Scanning Tooltip -- ---------------------- if (not lib.Tooltip) then lib.Tooltip = CreateFrame("GameTooltip"); lib.Tooltip:SetOwner(UIParent, "ANCHOR_NONE"); for i = 1, 4 do lib["TooltipTextLeft" .. i] = lib.Tooltip:CreateFontString(); lib["TooltipTextRight" .. i] = lib.Tooltip:CreateFontString(); lib.Tooltip:AddFontStrings(lib["TooltipTextLeft" .. i], lib["TooltipTextRight" .. i]); end end ------------------------------- -- Embed CallbackHandler-1.0 -- ------------------------------- lib.Callbacks = LibStub("CallbackHandler-1.0"):New(lib); ----------------- -- Static Data -- ----------------- -- Cache of spells and heal sizes local SpellCache = {}; -- Cache of glyphs local GlyphCache = {}; -- Info about spells being cast by other players local HealTime = {}; local HealTarget = {}; local HealSize = {}; -- Healing Modifiers (by name) local HealModifier = {}; -- Last target name from UNIT_SPELLCAST_SENT local SentTargetName; -- Last spellCastIndex from UNIT_SPELLCAST_STOP local LastSpellCastIndex; -- Info about the spell being cast by the player local CastInfoIsCasting; local CastInfoHealingTargetUnitID; local CastInfoHealingTargetNames; local CastInfoHealingSize; local CastInfoEndTime; -- Latency Measurement local SentTime = 0; local Latency = 0; -- Version Information Table local Versions = {}; -- Battleground/Arena/Group Indicators local InBattlegroundOrArena; local InRaidOrParty; --------------------------------- -- Frequently Accessed Globals -- --------------------------------- local type = type; local tonumber = tonumber; local math = math; local string = string; local select = select; local pairs = pairs; local unpack = unpack; local UnitName = UnitName; local SendAddonMessage = SendAddonMessage; local IsInInstance = IsInInstance; local UnitBuff = UnitBuff; local UnitDebuff = UnitDebuff; local UnitLevel = UnitLevel; local GetInventoryItemLink = GetInventoryItemLink; local GetTime = GetTime; local UnitCastingInfo = UnitCastingInfo; local GetSpellBonusHealing = GetSpellBonusHealing; local GetTalentInfo = GetTalentInfo; local UnitExists = UnitExists; local tinsert = table.insert; local tconcat = table.concat; --------------- -- Utilities -- --------------- local function unitFullName(unit) local name, realm = UnitName(unit); if (realm and realm ~= "") then return name .. "-" .. realm; else return name; end end local function extractRealm(fullName) return fullName:match("^[^%-]+%-(.+)$"); end -- Convert a remotely generated fully qualified name to -- a local fully qualified name. local function convertRealm(fullName, remoteRealm) if (remoteRealm) then local name, realm = fullName:match("^([^%-]+)%-(.+)$"); if (not realm) then -- Apply remote realm if there is no realm on the target return fullName .. "-" .. remoteRealm; elseif (realm == playerRealm) then -- Strip realm if it is equal to the local realm return name; end end return fullName; end local function commSend(contents, distribution, target) SendAddonMessage("HealComm", contents, distribution or (InBattlegroundOrArena and "BATTLEGROUND" or "RAID"), target); end -- Spellbook Scanner -- local function getBaseHealSize(name) -- Check if info is already cached if (SpellCache[name]) then return SpellCache[name]; end SpellCache[name] = {}; -- Gather info (only done if not in cache) local i = 1; while true do local spellName, spellRank = GetSpellName(i, BOOKTYPE_SPELL); if (not spellName) then break end if (spellName == name) then -- This is the spell we're looking for, gather info -- Determine rank spellRank = tonumber(spellRank:match("(%d+)")); lib.Tooltip:SetSpell(i, BOOKTYPE_SPELL); -- Determine healing local HealMin, HealMax = select(3, string.find(lib.TooltipTextLeft4:GetText() or lib.TooltipTextLeft3:GetText() or "", "(%d+) ?[\195\160tobisaå°~\-]+ ?(%d+)")); HealMin, HealMax = tonumber(HealMin) or 0, tonumber(HealMax) or 0; local Heal = (HealMin + HealMax) / 2; SpellCache[spellName][spellRank] = Heal; end i = i + 1; end return SpellCache[name]; end local function detectGlyph(id) -- Check if info is already cached if (GlyphCache[id] ~= nil) then return GlyphCache[id]; end GlyphCache[id] = false; -- Gather info (only done if not in cache) for i = 1, GetNumGlyphSockets() do local enabled, _, glyphId = GetGlyphSocketInfo(i); if (enabled and glyphId) then GlyphCache[glyphId] = true; end end return GlyphCache[id]; end -- Detects if a buff is present on the unit and returns the application number. -- Optionally, if the third argument is provided and is true, then only return -- true if the buff was placed by the player. local function detectBuff(unit, buffName, mineOnly) local name, _, _, count, _, _, _, isMine = UnitBuff(unit, buffName); return name and (not mineOnly or isMine) and count or false; end --[[ [GetSpellInfo(604)] = -20, -- Dampen Magic (Rank 1) [GetSpellInfo(8450)] = -40, -- Dampen Magic (Rank 2) [GetSpellInfo(8451)] = -80, -- Dampen Magic (Rank 3) [GetSpellInfo(10173)] = -120, -- Dampen Magic (Rank 4) [GetSpellInfo(10174)] = -180, -- Dampen Magic (Rank 5) [GetSpellInfo(33944)] = -240, -- Dampen Magic (Rank 6) [GetSpellInfo(1008)] = 30, -- Amplify Magic (Rank 1) [GetSpellInfo(8455)] = 60, -- Amplify Magic (Rank 2) [GetSpellInfo(10169)] = 100, -- Amplify Magic (Rank 3) [GetSpellInfo(10170)] = 150, -- Amplify Magic (Rank 4) [GetSpellInfo(27130)] = 180, -- Amplify Magic (Rank 5) [GetSpellInfo(33946)] = 240, -- Amplify Magic (Rank 6) [GetSpellInfo(32858)] = -345 -- Touch of the Forgotten (Auchenai Crypts) [GetSpellInfo(38377)] = -690 -- Touch of the Forgotten (Auchenai Crypts) ]]-- local healingBuffs = { [GetSpellInfo(706)] = 1.20, -- Demon Armor [GetSpellInfo(45234)] = function (count, rank) return (1.0 + (0.04 + 0.03 * (rank - 1)) * count) end, -- Focused Will [GetSpellInfo(34123)] = 1.06, -- Tree of Life [GetSpellInfo(58549)] = function (count, rank, texture) return ((texture == "Interface\\Icons\\Ability_Warrior_StrengthOfArms") and (1.18 ^ count) or 1.0) end, -- Tenacity (Wintergrasp) } local healingDebuffs = { [GetSpellInfo(25646)] = function (count) return (1.0 - count * 0.10) end, -- Mortal Wound (Temporus - The Black Morass) [GetSpellInfo(45347)] = function (count) return (1.0 - count * 0.04) end, -- Dark Touched (Grand Warlock Alythess - Sunwell Plateau) [GetSpellInfo(30423)] = function (count) return (1.0 - count * 0.01) end, -- Nether Portal - Dominance (Netherspite - Karazhan) [GetSpellInfo(13218)] = function (count) return (1.0 - count * 0.10) end, -- Wound Poison [GetSpellInfo(19434)] = 0.50, -- Aimed Shot [GetSpellInfo(12294)] = 0.50, -- Mortal Strike [GetSpellInfo(40599)] = 0.50, -- Arcing Smash (Gurtogg Bloodboil) [GetSpellInfo(23169)] = 0.50, -- Brood Affliction: Green (Chromaggus) [GetSpellInfo(34073)] = 0.85, -- Curse of the Bleeding Hollow (Hellfire Peninsula) [GetSpellInfo(13583)] = 0.50, -- Curse of the Deadwood (Deadwood Furbolgs - Felwood) [GetSpellInfo(36023)] = 0.50, -- Deathblow [GetSpellInfo(34625)] = 0.25, -- Demolish (Negatron - Netherstorm) [GetSpellInfo(34366)] = 0.75, -- Ebon Poison (Black Morass) [GetSpellInfo(32378)] = 0.50, -- Filet (Spectral Chef - Karazhan) [GetSpellInfo(19716)] = 0.25, -- Gehennas' Curse (Gehennas - Molten Core) [GetSpellInfo(36917)] = 0.50, -- Magma-Thrower's Curse (Sulfuron Magma-Thrower - The Arcatraz) [GetSpellInfo(22859)] = 0.50, -- Mortal Cleave (High Priestess Thekal - Zul'Gurub) [GetSpellInfo(38572)] = 0.50, -- Mortal Cleave (High Priestess Thekal - Zul'Gurub) [GetSpellInfo(39595)] = 0.50, -- Mortal Cleave (High Priestess Thekal - Zul'Gurub) [GetSpellInfo(28776)] = 0.10, -- Necrotic Poison (Maexxna - Naxxramas) [GetSpellInfo(35189)] = 0.50, -- Solar Strike (The Mechanar) [GetSpellInfo(32315)] = 0.50, -- Soul Strike (Ethereal Crypt Raiders - Mana-Tombs) [GetSpellInfo(7068)] = 0.25, -- Veil of Shadow (Nefarian - Blackwing Lair) [GetSpellInfo(38387)] = 1.50, -- Bane of Infinity (CoT: Escape from Durholde) [GetSpellInfo(31977)] = 1.50, -- Curse of Infinity (CoT: Escape from Durholde) [GetSpellInfo(41292)] = 0.00, -- Aura of Suffering (Essence of Souls - Black Temple) [GetSpellInfo(41350)] = 2.00, -- Aura of Desire (Essence of Souls - Black Temple) [GetSpellInfo(30843)] = 0.00, -- Enfeeble (Prince Malchezaar - Karazhan) } local function calculateHealModifier(unit) local modifier = 1.0; for i = 1, 40 do local name, rank, texture, count = UnitDebuff(unit, i); if (not name) then break; end local mark = healingDebuffs[name]; if (mark) then if (type(mark) == "function") then mark = mark(count); end if (mark < modifier) then modifier = mark; end end end for i = 1, 40 do local name, rank, texture, count = UnitBuff(unit, i); if (not name) then break; end local mark = healingBuffs[name]; if (mark) then if (type(mark) == "function") then mark = mark(count, rank and tonumber(rank:match("(%d+)")), texture); end modifier = modifier * mark; end end return modifier; end local function getDownrankingFactor(spellLevel, playerLevel) local factor = 0.05 * ((spellLevel + 7) - playerLevel) + 1; if (factor > 1.0) then return 1; elseif (factor < 0.0) then return 0; else return factor; end end local relicSlotNumber = GetInventorySlotInfo("RangedSlot"); local function getEquippedRelicID() local itemLink = GetInventoryItemLink('player', relicSlotNumber); if (itemLink) then return tonumber(itemLink:match("(%d+):")); end end ----------------------------- -- Healing Data Management -- ----------------------------- local function entryDelete(healerName) local targetNames = HealTarget[healerName]; HealTime[healerName] = nil; HealTarget[healerName] = nil; if (type(targetNames) == "table") then for i, targetName in pairs(targetNames) do if HealSize[targetName] then HealSize[targetName][healerName] = nil; end end elseif (targetNames and HealSize[targetNames]) then HealSize[targetNames][healerName] = nil; end end local function entryUpdate(healerName, targetNames, healSize, healTime) entryDelete(healerName); HealTime[healerName] = healTime; HealTarget[healerName] = targetNames; if (type(targetNames) == "table") then for i, targetName in pairs(targetNames) do if (not HealSize[targetName]) then HealSize[targetName] = {}; end HealSize[targetName][healerName] = healSize; end elseif (targetNames) then if (not HealSize[targetNames]) then HealSize[targetNames] = {}; end HealSize[targetNames][healerName] = healSize; end end local function entryRetrieve(healerName) local healTime = HealTime[healerName]; if (healTime) then local targetNames = HealTarget[healerName]; if (type(targetNames) == "table") then return targetNames, HealSize[targetNames[1]][healerName], healTime; elseif (targetNames) then return targetNames, HealSize[targetNames][healerName], healTime; end end end ---------------------- -- Public Functions -- ---------------------- --[[ UnitIncomingHealGet(unit, time) Description: Retrieve info about the incoming heals to a specific target. The second argument specifies a boundary time, relative to the current time. Examples: UnitIncomingHealGet("Kaki", GetTime() + 3) UnitIncomingHealGet("Kaki-Emerald Dream", GetTime() + 3) UnitIncomingHealGet("player", GetTime() + 3) UnitIncomingHealGet("raid10", GetTime() + 3) UnitIncomingHealGet("target", GetTime() + 3) Retrieves info about the incoming heals on the specified target. incomingHealBefore will contain the sum of heals that will land within the next 3 seconds, and incomingHealAfter will contain the sum of heals that will land after 3 seconds. Input: unit - The exact name or UnitID of the unit to retrieve information about. time - the desired boundary time of the inquiry. Output: incomingHealBefore - The total size of the incoming heals before the boundary time. incomingHealAfter - The total size of the incoming heals after the boundary time. nextTime - the time left until the next incoming heal will land. nextSize - the size of the next incoming heal. nextName - the name of the healer casting the next incoming heal. ]]-- function lib:UnitIncomingHealGet(unit, time) if (type(unit) ~= "string") then return end if (type(time) ~= "number") then return end local targetName = unitFullName(unit); if (HealSize[targetName]) then local now = GetTime(); local incomingHealBefore, incomingHealAfter = 0, 0; local nextTime, nextSize, nextName; for healerName, size in pairs(HealSize[targetName]) do local healTime = HealTime[healerName]; if (size and healTime) then healTime = healTime + Latency; if (healTime > now) then if (healTime < time) then -- Due before boundary time incomingHealBefore = incomingHealBefore + size; else -- Due after boundary time incomingHealAfter = incomingHealAfter + size; end if ((not nextTime) or (healTime < nextTime)) then nextTime = healTime; nextSize = size; nextName = healerName; end end end end if ((incomingHealBefore > 0) or (incomingHealAfter > 0)) then return incomingHealBefore, incomingHealAfter, nextTime, nextSize, nextName; end end end --[[ UnitCastingHealGet(unit) Description: Retrieve info about the direct healing spell currently being cast by any unit. Examples: UnitCastingHealGet("Kaki"); UnitCastingHealGet("Kaki-Emerald Dream"); UnitCastingHealGet("player") UnitCastingHealGet("raid10") UnitCastingHealGet("target") Input: unit - The name or UnitID of the unit to retrieve information about. Output: healSize - Size of the healing being cast. endTime - The time when the healing completes. targetName - Name of the unit(s) being targeted for heal. ]]-- function lib:UnitCastingHealGet(unit) if (type(unit) ~= "string") then return end local healerName = unitFullName(unit); if (healerName == playerName) then if (CastInfoIsCasting) then return CastInfoHealingSize, CastInfoEndTime, CastInfoHealingTargetNames; end else local targetNames, healSize, endTime = entryRetrieve(healerName); if (targetNames) then return healSize, endTime, targetNames; end end end --[[ UnitHealModifierGet(unit) Description: Returns the modifier to healing (as a factor) caused by buffs and debuffs. Examples: UnitHealModifierGet("Kaki"); UnitHealModifierGet("Kaki-Emerald Dream"); UnitHealModifierGet("player", 3) UnitHealModifierGet("raid10", 3) UnitHealModifierGet("target", 3) Input: unit - The name or UnitID of the unit to retrieve information about. Output: factor - Always a fractional number - will be 1.0 if no buffs/debuffs affect healing. ]]-- function lib:UnitHealModifierGet(unit) if (type(unit) ~= "string") then return end local targetName = unitFullName(unit); return HealModifier[targetName] or calculateHealModifier(unit); end function lib:GetRaidOrPartyVersions() local tab = {}; if (GetNumRaidMembers() > 0) then for i = 1, GetNumRaidMembers() do local name = unitFullName('raid' .. i); if (not (name == playerName)) then tab[name] = Versions[name] or false; end end elseif (GetNumPartyMembers() > 0) then for i = 1, GetNumPartyMembers() do local name = unitFullName('party' .. i); tab[name] = Versions[name] or false; end end tab[playerName] = MINOR_VERSION; return tab; end function lib:GetGuildVersions() local tab = {}; if (IsInGuild()) then GuildRoster(); for i = 1, GetNumGuildMembers(false) do local name, rank, rankIndex, level, class, zone, note, officernote, online, status = GetGuildRosterInfo(i); if (online and not (name == playerName)) then tab[name] = Versions[name] or false; end end end tab[playerName] = MINOR_VERSION; return tab; end function lib:GetUnitVersion(unit) if (type(unit) ~= "string") then return end local targetName = unitFullName(unit); if (targetName == playerName) then return MINOR_VERSION end return Versions[targetName] or false; end -------------------- -- Class Specific -- -------------------- local HealingSpells; --local HotSpells; local GetHealSize; -- Druid -- -- TODO: -- Talent: Empowered Rejuvenation. Increase effect of all HOTs by 4%-20% -- Idol: Idol of Rejuvenation if (playerClass == "DRUID") then local tHealingTouch = GetSpellInfo(5185); local tRegrowth = GetSpellInfo(8936); local tNourish = GetSpellInfo(50464); local tRejuvenation = GetSpellInfo(774); local tLifebloom = GetSpellInfo(33763); local tWildGrowth = GetSpellInfo(48438); --[[HotSpells = { [tRegrowth] = { Level = {17, 23, 29, 35, 41, 47, 53, 59, 65, 70, 76, 80}, Duration = 21, Ticks = 7, Pattern = "(%d+)[^0-9]+%d+[^0-9]+$", Type = "HoT", }, [tRejuvenation] = { Level = {9, 15, 21, 27, 33, 39, 45, 51, 57, 59, 62, 68, 74, 79, 80}, Duration = 12, Ticks = 4, Pattern = "(%d+)", Type = "HoT", }, [tLifebloom] = { Level = {71, 79, 80}, Duration = 7, Ticks = 7, Pattern = "(%d+)" Type = "Lifebloom", }, }]]-- HealingSpells = { [tHealingTouch] = { Level = {1, 8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 62, 69, 74, 79}, Type = "Direct", }, [tRegrowth] = { Level = {12, 18, 24, 30, 36, 42, 48, 54, 60, 65, 71, 77}, Type = "Direct", }, [tNourish] = { Level = {80}; Type = "Direct", } } local idolsHealingTouch = { [28568] = 136, -- Idol of the Avian Heart [22399] = 100, -- Idol of Health } GetHealSize = function(name, rank, target) local i, effectiveHeal; -- Get static spell info local baseHealSize = getBaseHealSize(name)[rank]; local nBonus = 0; local effectiveHealModifier = 1.0; if (not baseHealSize) then return nil; end -- Get +healing bonus local bonus = GetSpellBonusHealing(); local spellTab = HealingSpells[name]; -- Gift of Nature Talent - Increases effective healing by 2% per rank on all spells effectiveHealModifier = effectiveHealModifier * (2 * select(5, GetTalentInfo(3, 13)) / 100 + 1); -- Process individual spells if (name == tHealingTouch) then local idolBonus = idolsHealingTouch[getEquippedRelicID()] or 0; baseHealSize = baseHealSize + idolBonus; -- Glyph of Healing Touch (decreases amount healed by 50%) if (detectGlyph(54825)) then effectiveHealModifier = effectiveHealModifier * 0.5 end -- Empowered Touch Talent (increases bonus healing effects by 20% per rank) local talentEmpoweredTouch = 20 * select(5, GetTalentInfo(3, 15)) / 100; if (rank < 5) then nBonus = bonus * (1.88 * (1.0 + rank * 0.5) / 3.5 + talentEmpoweredTouch); else nBonus = bonus * (1.88 + talentEmpoweredTouch); end elseif (name == tRegrowth) then -- Glyph of Regrowth (increases effective healing by 20% if player's Regrowth is on target) if (detectGlyph(54743) and detectBuff(target, tRegrowth, true)) then effectiveHealModifier = effectiveHealModifier * 1.2; end nBonus = bonus * 1.88 * (2.0 / 3.5) * 0.5; elseif (name == tNourish) then -- Nourish heals for 20% more if player's HoT is on the target. if (detectBuff(target, tRejuvenation, true) or detectBuff(target, tRegrowth, true) or detectBuff(target, tLifebloom, true) or detectBuff(target, tWildGrowth, true)) then effectiveHealModifier = effectiveHealModifier * 1.2 end nBonus = bonus * 1.88 * (1.5 / 3.5); end effectiveHeal = effectiveHealModifier * (baseHealSize + nBonus * getDownrankingFactor(spellTab.Level[rank], UnitLevel('player'))); return effectiveHeal; end end -- Paladin -- -- TODO: Track Beacon of Light (GetSpellInfo(53563) for name). if (playerClass == "PALADIN") then local tHolyLight = GetSpellInfo(635); local tFlashOfLight = GetSpellInfo(19750); local tDivineFavor = GetSpellInfo(20216); local tSealOfLight = GetSpellInfo(20167); local tAvengingWrath = GetSpellInfo(31884); local tDivinePlea = GetSpellInfo(54428); HealingSpells = { [tHolyLight] = { Level = {1, 6, 14, 22, 30, 38, 46, 54, 60, 62, 70, 75, 80}, Type = "Direct", }, [tFlashOfLight] = { Level = {20, 26, 34, 42, 50, 58, 66, 74, 79}, Type = "Direct", }, } local libramsFlashOfLight = { [42614] = 267, -- Deadly Gladiator's Libram of Justice [42613] = 236, -- Hateful Gladiator's Libram of Justice [42612] = 204, -- Savage Gladiator's Libram of Justice [28592] = 89, -- Libram of Souls Redeemed (TODO: may be changed to affect Holy Light in 3.0.3) [25644] = 79, -- Blessed Book of Nagrand [23006] = 43, -- Libram of Light [23201] = 28, -- Libram of Divinity } local libramsHolyLight = { [40268] = 141, -- Libram of Tolerance [28296] = 47, -- Libram of the Lightbringer } GetHealSize = function(name, rank, target) local i, effectiveHeal; -- Get static spell info local baseHealSize = getBaseHealSize(name)[rank]; local nBonus = 0; local effectiveHealModifier = 1.0; if (not baseHealSize) then return nil; end -- Get +healing bonus local bonus = GetSpellBonusHealing(); local spellTab = HealingSpells[name]; -- Divine Favor (100% crit chance on heal spell) if (detectBuff('player', tDivineFavor)) then effectiveHealModifier = effectiveHealModifier * 1.5; end -- Avenging Wrath (increase all healing by 20%) if (detectBuff('player', tAvengingWrath)) then effectiveHealModifier = effectiveHealModifier * 1.2; end -- Divine Plea (decrease all healing by 50%) if (detectBuff('player', tDivinePlea)) then effectiveHealModifier = effectiveHealModifier * 0.5; end -- Glyph of Seal of Light (increases healing by 5% if Seal of Light is active) if (detectGlyph(54943) and detectBuff('player', tSealOfLight)) then effectiveHealModifier = effectiveHealModifier * 1.05; end -- Healing Light - Increases healing by 4% per rank on all spells effectiveHealModifier = effectiveHealModifier * (4 * select(5, GetTalentInfo(1, 3)) / 100 + 1); -- Process individual spells if (name == tFlashOfLight) then local libramBonus = libramsFlashOfLight[getEquippedRelicID()] or 0; nBonus = (bonus + libramBonus) * 1.88 * (1.5 / 3.5) * 1.25; elseif (name == tHolyLight) then local libramBonus = libramsHolyLight[getEquippedRelicID()] or 0; nBonus = (bonus + libramBonus) * 1.88 * (2.5 / 3.5) * 1.25; end effectiveHeal = effectiveHealModifier * (baseHealSize + nBonus * getDownrankingFactor(spellTab.Level[rank], UnitLevel('player'))); return effectiveHeal; end end -- Priest -- -- TODO: Talent: Improved Renew: increases renew by 5%-10%-15% -- Healing_Done = (Renew_Base + (Healbonus * Downrankfactor) ) * Improved_Renew * Spiritual_Healing if (playerClass == "PRIEST") then local tLesserHeal = GetSpellInfo(2050); local tHeal = GetSpellInfo(2054); local tGreaterHeal = GetSpellInfo(2060); local tFlashHeal = GetSpellInfo(2061); local tBindingHeal = GetSpellInfo(32546); local tPrayerOfHealing = GetSpellInfo(596); local tPowerWordFortitude = GetSpellInfo(1243); --local tRenew = GetSpellInfo(139); local tGrace = GetSpellInfo(47930); --[[HotSpells = { [tRenew] = { Level = {8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 65, 74, 79, 80}, Duration = 15, Ticks = 5, Pattern = "(%d+)", Type = "HoT", }, }]]-- HealingSpells = { [tLesserHeal] = { Level = {1, 4, 10}, Type = "Direct" }, [tHeal] = { Level = {16, 22, 28, 34}, Type = "Direct" }, [tGreaterHeal] = { Level = {40, 46, 52, 58, 60, 63, 68, 73, 78}, Type = "Direct", }, [tFlashHeal] = { Level = {20, 26, 32, 38, 44, 50, 56, 61, 67, 73, 79}, Type = "Direct", }, [tBindingHeal] = { Level = {64, 72, 78}, Type = "Binding" }, [tPrayerOfHealing] = { Level = {30, 40, 50, 60, 60, 68, 76}, Type = "Party", InRange = function(unit) return IsSpellInRange(tPowerWordFortitude, unit) == 1 end }, } GetHealSize = function(name, rank, target) local i, effectiveHeal; -- Get static spell info local baseHealSize = getBaseHealSize(name)[rank]; local nBonus = 0; local effectiveHealModifier = 1.0; if (not baseHealSize) then return nil; end -- Get +healing bonus local bonus = GetSpellBonusHealing(); local spellTab = HealingSpells[name]; -- Focused Power - Increases healing by 2% per rank on all spells effectiveHealModifier = effectiveHealModifier * (2 * select(5, GetTalentInfo(1, 16)) / 100 + 1); -- Spiritual Healing - Increases healing by 2% per rank on all spells effectiveHealModifier = effectiveHealModifier * (2 * select(5, GetTalentInfo(2, 16)) / 100 + 1); -- Grace (increases healing by 2% per application on target if buff was placed by the player) if (target) then local grace = detectBuff(target, tGrace, true); if (grace) then effectiveHealModifier = effectiveHealModifier * (1.0 + 0.02 * grace); end end -- Process individual spells if (name == tLesserHeal) then nBonus = bonus * 1.88 * (1.0 + rank * 0.5) / 3.5; elseif (name == tHeal) then nBonus = bonus * 1.88 * (3.0 / 3.5); elseif (name == tGreaterHeal) then local empoweredHealing = 8 * select(5, GetTalentInfo(2, 20)) / 100; nBonus = bonus * (1.88 * (3.0 / 3.5) + empoweredHealing); elseif (name == tFlashHeal) then local empoweredHealing = 4 * select(5, GetTalentInfo(2, 20)) / 100; nBonus = bonus * (1.88 * (1.5 / 3.5) + empoweredHealing); elseif (name == tBindingHeal) then local empoweredHealing = 4 * select(5, GetTalentInfo(2, 20)) / 100; nBonus = bonus * (1.88 * (1.5 / 3.5) + empoweredHealing); elseif (name == tPrayerOfHealing) then nBonus = bonus * 1.88 * (3.0 / 3.5) * 0.5; end effectiveHeal = effectiveHealModifier * (baseHealSize + nBonus * getDownrankingFactor(spellTab.Level[rank], UnitLevel('player'))); return effectiveHeal; end end -- Shaman -- -- TODO: Nature's Blessing (GetTalentInfo(3, 21)) is probably not accounted for automatically anymore (or is it?) -- TODO: Riptide 51point resto spell (instant cast regrowth (direct + hot)) -- TODO: Glyph of Healing Wave (binding heal, but self-heal is percentage of actual target healed) if (playerClass == "SHAMAN") then local tLesserHealingWave = GetSpellInfo(8004); local tHealingWave = GetSpellInfo(331); local tChainHeal = GetSpellInfo(1064); local tHealingWay = GetSpellInfo(29206); local tTidalWaves = GetSpellInfo(51562); local tRiptide = GetSpellInfo(61295); local tEarthShield = GetSpellInfo(974); --[[HotSpells = { [tRiptide] = { Level = {60, 70, 75, 80}, Duration = 15, Ticks = 5, Pattern = "(%d+)", Type = "HoT", }, }]]-- HealingSpells = { [tLesserHealingWave] = { Level = {20, 28, 36, 44, 52, 60, 66, 72, 77}, Type = "Direct", }, [tHealingWave] = { Level = {1, 6, 12, 18, 24, 32, 40, 48, 56, 60, 63, 70, 75, 80}, Type = "Direct", }, [tChainHeal] = { Level = {40, 46, 54, 61, 68, 74, 80}, Type = "Direct", }, } local totemsLesserHealingWave = { [42597] = 267, -- Deadly Gladiator's Totem of the Third Wind [42596] = 236, -- Hateful Gladiator's Totem of the Third Wind [42595] = 204, -- Savage Gladiator's Totem of the Third Wind [25645] = 79, -- Totem of The Plains [22396] = 80, -- Totem of Life [23200] = 53, -- Totem of Sustaining } local totemsHealingWave = { [27544] = 88, -- Totem of Spontaneous Regrowth } local totemsChainHeal = { [38368] = 102, -- Totem of the Bay [28523] = 87, -- Totem of Healing Rains } GetHealSize = function(name, rank, target) local i, effectiveHeal; -- Get static spell info local baseHealSize = getBaseHealSize(name)[rank]; local nBonus = 0; local effectiveHealModifier = 1.0; if (not baseHealSize) then return nil; end -- Get +healing bonus local bonus = GetSpellBonusHealing(); -- Purification Talent (increases healing by 2% per rank). -- This is factored into effectiveHealModifier in the individual spell section below due to interaction with Improved Chain Heal. local talentPurification = 2 * select(5, GetTalentInfo(3, 15)) / 100 + 1; local spellTab = HealingSpells[name]; -- Process individual spells if (name == tLesserHealingWave) then local totemBonus = totemsLesserHealingWave[getEquippedRelicID()] or 0; effectiveHealModifier = effectiveHealModifier * talentPurification; -- Glyph of Lesser Healing Wave (increases effective healing by 20% if player's Earth Shield is on target) if (detectGlyph(55438) and detectBuff(target, tEarthShield, true)) then effectiveHealModifier = effectiveHealModifier * 1.2; end -- Tidal Waves Talent (increases bonus healing effects by 2% per rank) local talentTidalWaves = 2 * select(5, GetTalentInfo(3, 25)) / 100; nBonus = (bonus + totemBonus) * (1.88 * (1.5 / 3.5) + talentTidalWaves); elseif (name == tHealingWave) then local totemBonus = totemsHealingWave[getEquippedRelicID()] or 0; effectiveHealModifier = effectiveHealModifier * talentPurification; -- Healing Way Buff (target buff that increases effective healing by 18%) if (detectBuff(target, tHealingWay)) then effectiveHealModifier = effectiveHealModifier * 1.18; end; -- Tidal Waves Talent (increases bonus healing effects by 4% per rank) local talentTidalWaves = 4 * select(5, GetTalentInfo(3, 25)) / 100; -- Determine normalisation if (rank < 4) then nBonus = (bonus + totemBonus) * (1.88 * (1.0 + rank * 0.5) / 3.5 + talentTidalWaves); else nBonus = (bonus + totemBonus) * (1.88 * (3.0 / 3.5) + talentTidalWaves); end elseif (name == tChainHeal) then local totemBonus = totemsChainHeal[getEquippedRelicID()] or 0; baseHealSize = baseHealSize + totemBonus; -- Improved Chain Heal Talent (increases healing by 10% per rank) local talentImprovedChainHeal = 10 * select(5, GetTalentInfo(3, 20)) / 100; effectiveHealModifier = effectiveHealModifier * (talentPurification + talentImprovedChainHeal); -- Riptide Buff (target buff that increases effective healing by 25%) if (detectBuff(target, tRiptide, true)) then effectiveHealModifier = effectiveHealModifier * 1.25; end nBonus = bonus * 1.88 * (2.5 / 3.5); end effectiveHeal = effectiveHealModifier * (baseHealSize + nBonus * getDownrankingFactor(spellTab.Level[rank], UnitLevel('player'))); return effectiveHeal; end end -------------------- -- Event Handlers -- -------------------- function lib:PLAYER_FOCUS_CHANGED() if (UnitExists('focus')) then self:UNIT_AURA('focus'); end if (UnitExists('focustarget')) then self:UNIT_AURA('focustarget'); end end function lib:PLAYER_TARGET_CHANGED() if (UnitExists('target')) then self:UNIT_AURA('target'); end if (UnitExists('targettarget')) then self:UNIT_AURA('targettarget'); end end function lib:UNIT_TARGET(unit) if ((unit == 'target') or (unit == 'focus')) then local unitTarget = unit .. "target"; if (UnitExists(unitTarget)) then self:UNIT_AURA(unitTarget); end end end function lib:UNIT_AURA(unit) local targetName = unitFullName(unit); local oldModifier = HealModifier[targetName]; local newModifier = calculateHealModifier(unit); if (oldModifier) then if (newModifier == oldModifier) then return end else if (newModifier == 1.0) then return end end HealModifier[targetName] = newModifier; self.Callbacks:Fire("HealComm_HealModifierUpdate", unit, targetName, newModifier); end function lib:LEARNED_SPELL_IN_TAB() -- Invalidate cached spell data when learning new spells SpellCache = {}; end function lib:GLYPH_ADDED() -- Invalidate cached glyph data when updating glyphs GlyphCache = {}; end function lib:GLYPH_REMOVED() -- Invalidate cached glyph data when updating glyphs GlyphCache = {}; end function lib:GLYPH_UPDATED() -- Invalidate cached glyph data when updating glyphs GlyphCache = {}; end function lib:UNIT_SPELLCAST_SENT(unit, spellName, spellRank, targetName) if (unit ~= 'player') then return end -- Latency measurement SentTime = GetTime(); SentTargetName = targetName; end function lib:UNIT_SPELLCAST_START(unit, spellName, spellRank) if (unit ~= 'player') then return end -- Latency measurement local currentLatency = GetTime() - SentTime; if (currentLatency > 1) then -- Limit to 1 sec currentLatency = 1; end Latency = 0.5 * Latency + 0.70 * currentLatency; local spellInfo = HealingSpells[spellName]; -- Only process healing spells if (spellInfo) then if (spellInfo.Type == "Direct") then CastInfoHealingTargetNames = SentTargetName; CastInfoHealingSize = GetHealSize(spellName, tonumber(spellRank:match("(%d+)")), SentTargetName) or 0; CastInfoIsCasting = true; CastInfoEndTime = (select(6, UnitCastingInfo('player')) or 0) / 1000; self.Callbacks:Fire("HealComm_DirectHealStart", playerName, CastInfoHealingSize, CastInfoEndTime, SentTargetName); commSend(string.format("000%05d%s", math.min(CastInfoHealingSize, 99999), SentTargetName)); elseif (spellInfo.Type == "Binding") then CastInfoHealingTargetNames = {playerName, SentTargetName}; CastInfoHealingSize = GetHealSize(spellName, tonumber(spellRank:match("(%d+)")), SentTargetName) or 0; CastInfoIsCasting = true; CastInfoEndTime = (select(6, UnitCastingInfo('player')) or 0) / 1000; self.Callbacks:Fire("HealComm_DirectHealStart", playerName, CastInfoHealingSize, CastInfoEndTime, unpack(CastInfoHealingTargetNames)); commSend(string.format("002%05d%s", math.min(CastInfoHealingSize, 99999), SentTargetName)); elseif (spellInfo.Type == "Party") then CastInfoHealingTargetNames = {}; if (spellInfo.InRange('party1')) then tinsert(CastInfoHealingTargetNames, unitFullName('party1')) end if (spellInfo.InRange('party2')) then tinsert(CastInfoHealingTargetNames, unitFullName('party2')) end if (spellInfo.InRange('party3')) then tinsert(CastInfoHealingTargetNames, unitFullName('party3')) end if (spellInfo.InRange('party4')) then tinsert(CastInfoHealingTargetNames, unitFullName('party4')) end CastInfoHealingSize = GetHealSize(spellName, tonumber(spellRank:match("(%d+)"))) or 0; CastInfoIsCasting = true; CastInfoEndTime = (select(6, UnitCastingInfo('player')) or 0) / 1000; commSend(string.format("002%05d%s", math.min(CastInfoHealingSize, 99999), tconcat(CastInfoHealingTargetNames, ":"))); tinsert(CastInfoHealingTargetNames, 1, playerName); self.Callbacks:Fire("HealComm_DirectHealStart", playerName, CastInfoHealingSize, CastInfoEndTime, unpack(CastInfoHealingTargetNames)); end end end function lib:CHAT_MSG_ADDON(prefix, msg, distribution, sender) if (prefix ~= "HealComm") then return end if (sender == playerName) then return end -- Workaround: Sometimes in battlegrounds the sender argument is not a -- fully qualified name (the realm is missing), even though the sender is -- from a different realm. if (distribution == "BATTLEGROUND") then sender = unitFullName(sender) or sender; end -- Get message type local msgtype = tonumber(msg:sub(1, 3)); if (not msgtype) then return end if (msgtype == 0) then -- DirectHealStart local healSize = tonumber(msg:sub(4, 8)); local targetName = msg:sub(9, -1); if (healSize and targetName) then local endTime = select(6, UnitCastingInfo(sender)); if (endTime) then if (distribution == "BATTLEGROUND") then targetName = convertRealm(targetName, extractRealm(sender)); end endTime = endTime / 1000; entryUpdate(sender, targetName, healSize, endTime); self.Callbacks:Fire("HealComm_DirectHealStart", sender, healSize, endTime, targetName); end end elseif (msgtype == 1) then -- HealStop local targetNames, healSize = entryRetrieve(sender); entryDelete(sender); if (type(targetNames) == "table") then self.Callbacks:Fire("HealComm_DirectHealStop", sender, healSize, msg:sub(4, 4) == "S", unpack(targetNames)); elseif (targetNames) then self.Callbacks:Fire("HealComm_DirectHealStop", sender, healSize, msg:sub(4, 4) == "S", targetNames); end elseif (msgtype == 2) then -- MultiTargetHealStart local healSize = tonumber(msg:sub(4, 8)); local targetNames = {strsplit(":", msg:sub(9, -1))}; if (healSize) then local endTime = select(6, UnitCastingInfo(sender)); if (endTime) then if (distribution == "BATTLEGROUND") then local senderRealm = extractRealm(sender); for k, targetName in pairs(targetNames) do targetNames[k] = convertRealm(targetName, senderRealm); end end endTime = endTime / 1000; tinsert(targetNames, 1, sender); entryUpdate(sender, targetNames, healSize, endTime); self.Callbacks:Fire("HealComm_DirectHealStart", sender, healSize, endTime, unpack(targetNames)); end end elseif (msgtype >= 998) then -- AnnounceVersion local version = tonumber(msg:sub(4, -1)); if (version) then Versions[sender] = version; if (msgtype == 999) then -- RequestVersion if (distribution ~= "BATTLEGROUND") then -- Reply in whisper if possible commSend("998" .. tostring(MINOR_VERSION), "WHISPER", sender); else -- Reply to inbound distribution channel commSend("998" .. tostring(MINOR_VERSION), distribution); end end end end end function lib:UNIT_SPELLCAST_DELAYED(unit) if (unit == 'player') then if (CastInfoIsCasting) then local endTime = select(6, UnitCastingInfo('player')); if (endTime) then CastInfoEndTime = endTime / 1000; if (type(CastInfoHealingTargetNames) == "table") then self.Callbacks:Fire("HealComm_DirectHealDelayed", playerName, CastInfoHealingSize, CastInfoEndTime, unpack(CastInfoHealingTargetNames)); elseif (CastInfoHealingTargetNames) then self.Callbacks:Fire("HealComm_DirectHealDelayed", playerName, CastInfoHealingSize, CastInfoEndTime, CastInfoHealingTargetNames); end end end elseif (unit ~= 'target' and unit ~= 'focus') then local healerName = unitFullName(unit); local targetNames, healSize = entryRetrieve(healerName) if (targetNames) then local endTime = select(6, UnitCastingInfo(healerName)); if (endTime) then endTime = endTime / 1000; HealTime[healerName] = endTime; if (type(targetNames) == "table") then self.Callbacks:Fire("HealComm_DirectHealDelayed", healerName, healSize, endTime, unpack(targetNames)); elseif (targetNames) then self.Callbacks:Fire("HealComm_DirectHealDelayed", healerName, healSize, endTime, targetNames); end end end end end function lib:UNIT_SPELLCAST_SUCCEEDED(unit, spellName, spellRank, spellCastIndex) if (unit ~= 'player') then return end if (CastInfoIsCasting) then CastInfoIsCasting = false; commSend("001S"); if (type(CastInfoHealingTargetNames) == "table") then self.Callbacks:Fire("HealComm_DirectHealStop", playerName, CastInfoHealingSize, true, unpack(CastInfoHealingTargetNames)); elseif (CastInfoHealingTargetNames) then self.Callbacks:Fire("HealComm_DirectHealStop", playerName, CastInfoHealingSize, true, CastInfoHealingTargetNames); end else if (LastSpellCastIndex ~= spellCastIndex) then -- Instant Cast Spells end end end function lib:UNIT_SPELLCAST_STOP(unit, spellName, spellRank, spellCastIndex) if (unit == 'player' and CastInfoIsCasting) then LastSpellCastIndex = spellCastIndex; CastInfoIsCasting = false; commSend("001F"); if (type(CastInfoHealingTargetNames) == "table") then self.Callbacks:Fire("HealComm_DirectHealStop", playerName, CastInfoHealingSize, false, unpack(CastInfoHealingTargetNames)); elseif (CastInfoHealingTargetNames) then self.Callbacks:Fire("HealComm_DirectHealStop", playerName, CastInfoHealingSize, false, CastInfoHealingTargetNames); end end end function lib:PLAYER_ALIVE() -- This event is only fired at initial login, not at reloadui or load-on-demand loading. -- The initialisation is triggered again, since none of the initialisation had any effect -- prior to this event firing (no messages sent and InBattlegroundOrArena and InRaidOrParty -- are probably not correctly initialised). lib:Initialise(); -- Only receive once self.EventFrame:UnregisterEvent("PLAYER_ALIVE"); end function lib:PLAYER_ENTERING_WORLD() HealTime = {}; HealTarget = {}; HealSize = {}; HealModifier = {}; end function lib:PARTY_MEMBERS_CHANGED() local wasInRaidOrParty = InRaidOrParty; InRaidOrParty = (GetNumRaidMembers() > 0) or (GetNumPartyMembers() > 0); -- Announce and request version when joining a group if (not wasInRaidOrParty and InRaidOrParty) then commSend("999" .. tostring(MINOR_VERSION)); end end function lib:RAID_ROSTER_UPDATE() self:PARTY_MEMBERS_CHANGED(); end function lib:Initialise() local it = select(2, IsInInstance()); InBattlegroundOrArena = (it == "pvp") or (it == "arena"); InRaidOrParty = (GetNumRaidMembers() > 0) or (GetNumPartyMembers() > 0); -- Announce and request version in group and in guild commSend("999" .. tostring(MINOR_VERSION)); if (IsInGuild()) then commSend("999" .. tostring(MINOR_VERSION), "GUILD"); end end lib:Initialise();