You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3392 lines
133 KiB

4 years ago
local major = "LibHealComm-4.0"
3 years ago
local minor = 107
4 years ago
assert(LibStub, format("%s requires LibStub.", major))
local HealComm = LibStub:NewLibrary(major, minor)
if( not HealComm ) then return end
local COMM_PREFIX = "LHC40"
C_ChatInfo.RegisterAddonMessagePrefix(COMM_PREFIX)
local bit = bit
local ceil = ceil
local error = error
local floor = floor
local format = format
local gsub = gsub
local max = max
local min = min
local pairs = pairs
local ipairs = ipairs
local rawset = rawset
local select = select
local setmetatable = setmetatable
local strlen = strlen
local strmatch = strmatch
local strsplit = strsplit
local strsub = strsub
local tinsert = tinsert
local tonumber = tonumber
local tremove = tremove
local type = type
local unpack = unpack
local wipe = wipe
local Ambiguate = Ambiguate
local CastingInfo = CastingInfo
local ChannelInfo = ChannelInfo
local CreateFrame = CreateFrame
local GetInventoryItemLink = GetInventoryItemLink
local GetInventorySlotInfo = GetInventorySlotInfo
local GetNumGroupMembers = GetNumGroupMembers
local GetNumTalents = GetNumTalents
local GetNumTalentTabs = GetNumTalentTabs
local GetRaidRosterInfo = GetRaidRosterInfo
local GetSpellBonusHealing = GetSpellBonusHealing
local GetSpellCritChance = GetSpellCritChance
local GetSpellInfo = GetSpellInfo
local GetTalentInfo = GetTalentInfo
local GetTime = GetTime
local GetZonePVPInfo = GetZonePVPInfo
local hooksecurefunc = hooksecurefunc
local InCombatLockdown = InCombatLockdown
local IsEquippedItem = IsEquippedItem
local IsInGroup = IsInGroup
local IsInInstance = IsInInstance
local IsInRaid = IsInRaid
local IsLoggedIn = IsLoggedIn
local IsSpellInRange = IsSpellInRange
local SpellIsTargeting = SpellIsTargeting
local UnitAura = UnitAura
local UnitCanAssist = UnitCanAssist
local UnitExists = UnitExists
local UnitBuff = UnitBuff
local UnitGUID = UnitGUID
local UnitIsCharmed = UnitIsCharmed
local UnitIsVisible = UnitIsVisible
local UnitInRaid = UnitInRaid
local UnitLevel = UnitLevel
local UnitName = UnitName
local UnitPlayerControlled = UnitPlayerControlled
local CheckInteractDistance = CheckInteractDistance
local CombatLogGetCurrentEventInfo = CombatLogGetCurrentEventInfo
3 years ago
local UnitHasVehicleUI = UnitHasVehicleUI or function() end
local GetGlyphSocketInfo = GetGlyphSocketInfo or function() end
local GetNumGlyphSockets = GetNumGlyphSockets or function() return 0 end
4 years ago
local MAX_RAID_MEMBERS = MAX_RAID_MEMBERS
local MAX_PARTY_MEMBERS = MAX_PARTY_MEMBERS
local COMBATLOG_OBJECT_AFFILIATION_MINE = COMBATLOG_OBJECT_AFFILIATION_MINE
3 years ago
local build = floor(select(4,GetBuildInfo())/10000)
local isTBC = build == 2
local isWrath = build == 3
4 years ago
local spellRankTableData = {
3 years ago
[1] = { 774, 8936, 5185, 740, 635, 19750, 139, 2060, 596, 2061, 2054, 2050, 1064, 331, 8004, 136, 755, 689, 746, 33763, 32546, 37563, 48438, 61295, 51945, 50464, 47757 },
[2] = { 1058, 8938, 5186, 8918, 639, 19939, 6074, 10963, 996, 9472, 2055, 2052, 10622, 332, 8008, 3111, 3698, 699, 1159, 53248, 61299, 51990, 48450, 52986, 48119 },
[3] = { 1430, 8939, 5187, 9862, 647, 19940, 6075, 10964, 10960, 9473, 6063, 2053, 10623, 547, 8010, 3661, 3699, 709, 3267, 53249, 61300, 51997, 48451, 52987, 48120 },
[4] = { 2090, 8940, 5188, 9863, 1026, 19941, 6076, 10965, 10961, 9474, 6064, 913, 10466, 3662, 3700, 7651, 3268, 25422, 53251, 61301, 51998, 52988 },
[5] = { 2091, 8941, 5189, 1042, 19942, 6077, 22009, 25314, 25316, 10915, 939, 10467, 13542, 11693, 11699, 7926, 25423, 26983, 51999 },
[6] = { 3627, 9750, 6778, 3472, 19943, 6078, 10916, 959, 10468, 13543, 11694, 11700, 7927, 23569, 24412, 25210, 25308, 52000, 55458, 48446 },
[7] = { 8910, 9856, 8903, 10328, 10927, 10917, 8005, 13544, 11695, 10838, 27137, 25213, 25420, 27219, 55459, 48447, 48072 },
[8] = { 9839, 9857, 9758, 10329, 10928, 10395, 10839, 23568, 24413, 25233, 27259, 27220, 27046, 48784, 49275, 48062 },
[9] = { 9840, 9858, 9888, 25292, 10929, 10396, 18608, 25235, 48785, 49276, 48063, 48989, 47856, 47857 },
[10] = { 9841, 9889, 25315, 25357, 18610, 23567, 24414, 26980, 27135, 48070, 48990 },
[11] = { 25299, 25297, 30020, 27136, 25221, 25391, 27030, 48442, 48071 },
[12] = { 26981, 26978, 25222, 25396, 27031, 48781, 48443 },
[13] = { 26982, 26979, 48782, 49272, 48067, 45543 },
[14] = { 49273, 48377, 48440, 48068, 51827 },
[15] = { 48378, 48441, 45544 },
[16] = { 51803 },
4 years ago
}
local SpellIDToRank = {}
for rankIndex, spellIDTable in pairs(spellRankTableData) do
for _, spellID in pairs(spellIDTable) do
SpellIDToRank[spellID] = rankIndex
end
end
-- API CONSTANTS
local ALL_DATA = 0x0f
local DIRECT_HEALS = 0x01
local CHANNEL_HEALS = 0x02
local HOT_HEALS = 0x04
local ABSORB_SHIELDS = 0x08
local BOMB_HEALS = 0x10
local ALL_HEALS = bit.bor(DIRECT_HEALS, CHANNEL_HEALS, HOT_HEALS, BOMB_HEALS)
local CASTED_HEALS = bit.bor(DIRECT_HEALS, CHANNEL_HEALS)
local OVERTIME_HEALS = bit.bor(HOT_HEALS, CHANNEL_HEALS)
local OVERTIME_AND_BOMB_HEALS = bit.bor(HOT_HEALS, CHANNEL_HEALS, BOMB_HEALS)
HealComm.ALL_HEALS, HealComm.OVERTIME_HEALS, HealComm.OVERTIME_AND_BOMB_HEALS, HealComm.CHANNEL_HEALS, HealComm.DIRECT_HEALS, HealComm.HOT_HEALS, HealComm.CASTED_HEALS, HealComm.ABSORB_SHIELDS, HealComm.ALL_DATA, HealComm.BOMB_HEALS = ALL_HEALS, OVERTIME_HEALS, OVERTIME_AND_BOMB_HEALS, CHANNEL_HEALS, DIRECT_HEALS, HOT_HEALS, CASTED_HEALS, ABSORB_SHIELDS, ALL_DATA, BOMB_HEALS
local playerGUID, playerName, playerLevel
local playerHealModifier = 1
HealComm.callbacks = HealComm.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(HealComm)
HealComm.activeHots = HealComm.activeHots or {}
HealComm.activePets = HealComm.activePets or {}
HealComm.equippedSetCache = HealComm.equippedSetCache or {}
HealComm.guidToGroup = HealComm.guidToGroup or {}
HealComm.guidToUnit = HealComm.guidToUnit or {}
HealComm.hotData = HealComm.hotData or {}
HealComm.itemSetsData = HealComm.itemSetsData or {}
HealComm.pendingHeals = HealComm.pendingHeals or {}
HealComm.pendingHots = HealComm.pendingHots or {}
HealComm.spellData = HealComm.spellData or {}
HealComm.talentData = HealComm.talentData or {}
HealComm.tempPlayerList = HealComm.tempPlayerList or {}
3 years ago
HealComm.glyphCache = HealComm.glyphCache or {}
4 years ago
if( not HealComm.unitToPet ) then
HealComm.unitToPet = {["player"] = "pet"}
for i = 1, MAX_PARTY_MEMBERS do HealComm.unitToPet["party" .. i] = "partypet" .. i end
for i = 1, MAX_RAID_MEMBERS do HealComm.unitToPet["raid" .. i] = "raidpet" .. i end
end
local spellData, hotData, tempPlayerList, pendingHeals, pendingHots = HealComm.spellData, HealComm.hotData, HealComm.tempPlayerList, HealComm.pendingHeals, HealComm.pendingHots
local equippedSetCache, itemSetsData, talentData = HealComm.equippedSetCache, HealComm.itemSetsData, HealComm.talentData
local activeHots, activePets = HealComm.activeHots, HealComm.activePets
-- Figure out what they are now since a few things change based off of this
local playerClass = select(2, UnitClass("player"))
if( not HealComm.compressGUID ) then
HealComm.compressGUID = setmetatable({}, {
__index = function(tbl, guid)
local str
if strsub(guid,1,6) ~= "Player" then
for unit,pguid in pairs(activePets) do
if pguid == guid and UnitExists(unit) then
str = "p-" .. strmatch(UnitGUID(unit), "^%w*-([-%w]*)$")
end
end
if not str then
--assert(str, "Could not encode: "..guid)
return nil
end
else
str = strmatch(guid, "^%w*-([-%w]*)$")
end
rawset(tbl, guid, str)
return str
end})
HealComm.decompressGUID = setmetatable({}, {
__index = function(tbl, str)
if( not str ) then return nil end
local guid
if strsub(str,1,2) == "p-" then
local unit = HealComm.guidToUnit["Player-"..strsub(str,3)]
if not unit then
return nil
end
guid = activePets[unit]
else
guid = "Player-"..str
end
rawset(tbl, str, guid)
return guid
end})
end
local compressGUID, decompressGUID = HealComm.compressGUID, HealComm.decompressGUID
-- Handles caching of tables for variable tick spells, like Wild Growth
if( not HealComm.tableCache ) then
HealComm.tableCache = setmetatable({}, {__mode = "k"})
function HealComm:RetrieveTable()
return tremove(HealComm.tableCache, 1) or {}
end
function HealComm:DeleteTable(tbl)
wipe(tbl)
tinsert(HealComm.tableCache, tbl)
end
end
-- Validation for passed arguments
if( not HealComm.tooltip ) then
local tooltip = CreateFrame("GameTooltip")
tooltip:SetOwner(UIParent, "ANCHOR_NONE")
tooltip.TextLeft1 = tooltip:CreateFontString()
tooltip.TextRight1 = tooltip:CreateFontString()
tooltip:AddFontStrings(tooltip.TextLeft1, tooltip.TextRight1)
HealComm.tooltip = tooltip
end
-- Record management, because this is getting more complicted to deal with
local function updateRecord(pending, guid, amount, stack, endTime, ticksLeft)
if( pending[guid] ) then
local id = pending[guid]
pending[id] = guid
pending[id + 1] = amount
pending[id + 2] = stack
pending[id + 3] = endTime or 0
pending[id + 4] = ticksLeft or 0
else
pending[guid] = #(pending) + 1
tinsert(pending, guid)
tinsert(pending, amount)
tinsert(pending, stack)
tinsert(pending, endTime or 0)
tinsert(pending, ticksLeft or 0)
if( pending.bitType == HOT_HEALS ) then
activeHots[guid] = (activeHots[guid] or 0) + 1
HealComm.hotMonitor:Show()
end
end
end
local function getRecord(pending, guid)
local id = pending[guid]
if( not id ) then return nil end
-- amount, stack, endTime, ticksLeft
return pending[id + 1], pending[id + 2], pending[id + 3], pending[id + 4]
end
local function removeRecord(pending, guid)
local id = pending[guid]
if( not id ) then return nil end
-- ticksLeft, endTime, stack, amount, guid
tremove(pending, id + 4)
tremove(pending, id + 3)
tremove(pending, id + 2)
local amount = tremove(pending, id + 1)
tremove(pending, id)
pending[guid] = nil
-- Release the table
if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
if( pending.bitType == HOT_HEALS and activeHots[guid] ) then
activeHots[guid] = activeHots[guid] - 1
activeHots[guid] = activeHots[guid] > 0 and activeHots[guid] or nil
end
-- Shift any records after this ones index down 5 to account for the removal
for i=1, #(pending), 5 do
local guid = pending[i]
if( pending[guid] > id ) then
pending[guid] = pending[guid] - 5
end
end
end
local function removeRecordList(pending, inc, comp, ...)
for i=1, select("#", ...), inc do
local guid = select(i, ...)
guid = comp and decompressGUID[guid] or guid
if guid then
local id = pending[guid]
-- ticksLeft, endTime, stack, amount, guid
tremove(pending, id + 4)
tremove(pending, id + 3)
tremove(pending, id + 2)
local amount = tremove(pending, id + 1)
tremove(pending, id)
pending[guid] = nil
-- Release the table
if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
end
end
-- Redo all the id maps
for i=1, #(pending), 5 do
pending[pending[i]] = i
end
end
-- Removes every mention to the given GUID
local function removeAllRecords(guid)
local changed
for _, tbl in pairs({pendingHeals, pendingHots}) do
for _, spells in pairs(tbl) do
for _, pending in pairs(spells) do
if( pending.bitType and pending[guid] ) then
local id = pending[guid]
-- ticksLeft, endTime, stack, amount, guid
tremove(pending, id + 4)
tremove(pending, id + 3)
tremove(pending, id + 2)
local amount = tremove(pending, id + 1)
tremove(pending, id)
pending[guid] = nil
-- Release the table
if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
-- Shift everything back
if( #(pending) > 0 ) then
for i=1, #(pending), 5 do
local guid = pending[i]
if( pending[guid] > id ) then
pending[guid] = pending[guid] - 5
end
end
else
wipe(pending)
end
changed = true
end
end
end
end
activeHots[guid] = nil
if( changed ) then
HealComm.callbacks:Fire("HealComm_GUIDDisappeared", guid)
end
end
-- These are not public APIs and are purely for the wrapper to use
HealComm.removeRecordList = removeRecordList
HealComm.removeRecord = removeRecord
HealComm.getRecord = getRecord
HealComm.updateRecord = updateRecord
-- Removes all pending heals, if it's a group that is causing the clear then we won't remove the players heals on themselves
local function clearPendingHeals()
for _, tbl in pairs({pendingHeals, pendingHots}) do
for casterGUID, spells in pairs(tbl) do
for _, pending in pairs(spells) do
if( pending.bitType ) then
wipe(tempPlayerList)
for i=#(pending), 1, -5 do tinsert(tempPlayerList, pending[i - 4]) end
if( #(tempPlayerList) > 0 ) then
local spellID, bitType = pending.spellID, pending.bitType
wipe(pending)
HealComm.callbacks:Fire("HealComm_HealStopped", casterGUID, spellID, bitType, true, unpack(tempPlayerList))
end
end
end
end
end
end
-- APIs
-- Returns the players current heaing modifier
function HealComm:GetPlayerHealingMod()
return playerHealModifier or 1
end
-- Returns the current healing modifier for the GUID
function HealComm:GetHealModifier(guid)
return HealComm.currentModifiers[guid] or 1
end
-- Returns whether or not the GUID has casted a heal
function HealComm:GUIDHasHealed(guid)
return (pendingHeals[guid] or pendingHots[guid]) and true or nil
end
-- Returns the guid to unit table
function HealComm:GetGUIDUnitMapTable()
if( not HealComm.protectedMap ) then
HealComm.protectedMap = setmetatable({}, {
__index = function(tbl, key) return HealComm.guidToUnit[key] end,
__newindex = function() error("This is a read only table and cannot be modified.", 2) end,
__metatable = false
})
end
return HealComm.protectedMap
end
-- Gets the next heal landing on someone using the passed filters
function HealComm:GetNextHealAmount(guid, bitFlag, time, ignoreGUID, srcGUID)
local healTime, healAmount, healFrom
local currentTime = GetTime()
for _, tbl in pairs({pendingHeals, pendingHots}) do
for casterGUID, spells in pairs(tbl) do
if( not ignoreGUID or ignoreGUID ~= casterGUID ) and (not srcGUID or srcGUID == casterGUID) then
for _, pending in pairs(spells) do
if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
for i=1, #(pending), 5 do
local targetGUID = pending[i]
if(not guid or targetGUID == guid) then
local amount = pending[i + 1]
local stack = pending[i + 2]
local endTime = pending[i + 3]
endTime = endTime > 0 and endTime or pending.endTime
-- Direct heals are easy, if they match the filter then return them
if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or endTime <= time ) ) then
if( not healTime or endTime < healTime ) then
healTime = endTime
healAmount = amount * stack
healFrom = casterGUID
end
-- Channeled heals and hots, have to figure out how many times it'll tick within the given time band
elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) ) then
local secondsLeft = time and time - currentTime or endTime - currentTime
local nextTick = currentTime + (secondsLeft % pending.tickInterval)
if( not healTime or nextTick < healTime ) then
healTime = nextTick
3 years ago
if pending.hasVariableTicks then
healAmount = amount[pending.totalTicks - pending[i + 4] + 1]
else
healAmount = amount * stack
end
4 years ago
healFrom = casterGUID
end
end
end
end
end
end
end
end
end
return healTime, healFrom, healAmount
end
-- Get the healing amount that matches the passed filters
local function filterData(spells, filterGUID, bitFlag, time, ignoreGUID)
local healAmount = 0
local currentTime = GetTime()
if spells then
for _, pending in pairs(spells) do
if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
for i = 1, #(pending), 5 do
local guid = pending[i]
if( guid == filterGUID or ignoreGUID ) then
local amount = pending[i + 1]
local stack = pending[i + 2]
local endTime = pending[i + 3]
endTime = endTime > 0 and endTime or pending.endTime
if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or endTime <= time ) ) then
healAmount = healAmount + amount * stack
elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) and endTime > currentTime ) then
local ticksLeft = pending[i + 4]
if( not time or time >= endTime ) then
3 years ago
if( not pending.hasVariableTicks ) then
healAmount = healAmount + (amount * stack) * ticksLeft
else
local ticksPassed = pending.totalTicks - ticksLeft
for numTick, heal in pairs(amount) do
if numTick > ticksPassed then
healAmount = healAmount + (heal * stack)
end
end
end
4 years ago
else
local secondsLeft = endTime - currentTime
local bandSeconds = time - currentTime
local ticks = floor(min(bandSeconds, secondsLeft) / pending.tickInterval)
local nextTickIn = secondsLeft % pending.tickInterval
local fractionalBand = bandSeconds % pending.tickInterval
if( nextTickIn > 0 and nextTickIn < fractionalBand ) then
ticks = ticks + 1
end
3 years ago
if( not pending.hasVariableTicks ) then
healAmount = healAmount + (amount * stack) * min(ticks, ticksLeft)
else
local ticksPassed = pending.totalTicks - ticksLeft
for numTick, heal in ipairs(amount) do
if numTick > ticksPassed then
healAmount = healAmount + (heal * stack)
end
end
end
4 years ago
end
end
end
end
end
end
end
return healAmount
end
-- Gets healing amount using the passed filters
function HealComm:GetHealAmount(guid, bitFlag, time, casterGUID)
local amount = 0
if( casterGUID and (pendingHeals[casterGUID] or pendingHots[casterGUID]) ) then
amount = filterData(pendingHeals[casterGUID], guid, bitFlag, time) + filterData(pendingHots[casterGUID], guid, bitFlag, time)
elseif( not casterGUID ) then
for _, tbl in pairs({pendingHeals, pendingHots}) do
for _, spells in pairs(tbl) do
amount = amount + filterData(spells, guid, bitFlag, time)
end
end
end
return amount > 0 and amount or nil
end
-- Gets healing amounts for everyone except the player using the passed filters
function HealComm:GetOthersHealAmount(guid, bitFlag, time)
local amount = 0
for _, tbl in pairs({pendingHeals, pendingHots}) do
for casterGUID, spells in pairs(tbl) do
if( casterGUID ~= playerGUID ) then
amount = amount + filterData(spells, guid, bitFlag, time)
end
end
end
return amount > 0 and amount or nil
end
function HealComm:GetCasterHealAmount(guid, bitFlag, time)
local amount = pendingHeals[guid] and filterData(pendingHeals[guid], nil, bitFlag, time, true) or 0
amount = amount + (pendingHots[guid] and filterData(pendingHots[guid], nil, bitFlag, time, true) or 0)
return amount > 0 and amount or nil
end
function HealComm:GetHealAmountEx(dstGUID, dstBitFlag, dstTime, srcGUID, srcBitFlag, srcTime)
local dstAmount1 = 0
local dstAmount2 = 0
local srcAmount1 = 0
local srcAmount2 = 0
local currTime = GetTime()
dstBitFlag = dstBitFlag or ALL_HEALS
srcBitFlag = srcBitFlag or ALL_HEALS
for _, tbl in ipairs({pendingHeals, pendingHots}) do
for casterGUID, spells in pairs(tbl) do
local time
if casterGUID ~= srcGUID then
time = dstTime
else
time = srcTime
end
if spells then
for _, pending in pairs(spells) do
local bitType = pending.bitType or 0
if casterGUID ~= srcGUID then
bitType = bit.band(bitType, dstBitFlag)
else
bitType = bit.band(bitType, srcBitFlag)
end
if bitType > 0 then
for i = 1, #pending, 5 do
local targetGUID = pending[i]
if targetGUID == dstGUID then
3 years ago
local amount = 0
if not pending.hasVariableTicks then
amount = pending[i + 1]
end
4 years ago
local stack = pending[i + 2]
local endTime = pending[i + 3]
endTime = endTime > 0 and endTime or pending.endTime
if endTime > currTime then
amount = amount * stack
local amount1 = 0
local amount2 = 0
if bitType == DIRECT_HEALS or bitType == BOMB_HEALS then
if not time or endTime <= time then
amount1 = amount
end
amount2 = amount
elseif bitType == HOT_HEALS or bitType == CHANNEL_HEALS then
local ticksLeft = pending[i + 4]
local ticks
if not time then
ticks = 1
elseif time >= endTime then
ticks = ticksLeft
else
local tickInterval = pending.tickInterval
local secondsLeft = endTime - currTime
local bandSeconds = max(time - currTime, 0)
ticks = floor(min(bandSeconds, secondsLeft) / tickInterval)
local nextTickIn = secondsLeft % tickInterval
local fractionalBand = bandSeconds % tickInterval
if nextTickIn > 0 and nextTickIn < fractionalBand then
ticks = ticks + 1
end
end
if ticks > ticksLeft then
ticks = ticksLeft
end
3 years ago
if not pending.hasVariableTicks then
amount1 = amount * ticks
amount2 = amount * ticksLeft
else
amount = pending[i + 1]
amount1 = amount[pending.totalTicks - pending[i + 4] + 1] or 0
local ticksPassed = pending.totalTicks - ticksLeft
for numTick, heal in ipairs(amount) do
if numTick > ticksPassed then
amount2 = amount2 + (heal * stack)
end
end
end
4 years ago
end
if casterGUID ~= srcGUID then
dstAmount1 = dstAmount1 + amount1
dstAmount2 = dstAmount2 + amount2
else
srcAmount1 = srcAmount1 + amount1
srcAmount2 = srcAmount2 + amount2
end
end
end
end
end
end
end
end
end
dstAmount2 = dstAmount2 - dstAmount1
srcAmount2 = srcAmount2 - srcAmount1
dstAmount1 = dstAmount1 > 0 and dstAmount1 or nil
dstAmount2 = dstAmount2 > 0 and dstAmount2 or nil
srcAmount1 = srcAmount1 > 0 and srcAmount1 or nil
srcAmount2 = srcAmount2 > 0 and srcAmount2 or nil
return dstAmount1, dstAmount2, srcAmount1, srcAmount2
end
-- Get the number of direct heals on a target
function HealComm:GetNumHeals(filterGUID, time)
local numHeals = 0
for _, spells in pairs(pendingHeals) do
if spells then
for _, pending in pairs(spells) do
for i = 1, #(pending), 5 do
local guid = pending[i]
if( guid == filterGUID ) then
local endTime = pending[i + 3]
endTime = endTime > 0 and endTime or pending.endTime
if( pending.bitType == DIRECT_HEALS and ( not time or endTime <= time ) ) then
numHeals = numHeals + 1
end
end
end
end
end
end
return numHeals
end
-- Healing class data
-- Thanks to Gagorian (DrDamage) for letting me steal his formulas and such
local playerCurrentRelic
3 years ago
local guidToUnit, guidToGroup, glyphCache = HealComm.guidToUnit, HealComm.guidToGroup, HealComm.glyphCache
4 years ago
local unitHasAura
do
local findAura = AuraUtil.FindAura
local findAuraByName = AuraUtil.FindAuraByName
local function spellIdPredicate(spellIdToFind, _, _, _, _, _, _, _, _, _, _, _, spellId)
return spellIdToFind == spellId
end
local function findAuraBySpellId(spellId, unit, filter)
return findAura(spellIdPredicate, unit, filter, spellId)
end
function unitHasAura(unit, name)
if type(name) == "number" then
return findAuraBySpellId(name, unit)
else
return findAuraByName(name, unit)
end
end
end
-- Note because I always forget on the order:
-- Talents that effective the coeffiency of spell power to healing are first and are tacked directly onto the coeffiency (Empowered Rejuvenation)
-- Penalty modifiers (downranking/spell level too low) are applied directly to the spell power
-- Spell power modifiers are then applied to the spell power
-- Heal modifiers are applied after all of that
-- Crit modifiers are applied after
-- Any other modifiers such as Mortal Strike or Avenging Wrath are applied after everything else
local function calculateGeneralAmount(level, amount, spellPower, spModifier, healModifier)
local penalty = level > 20 and 1 or (1 - ((20 - level) * 0.0375))
3 years ago
if isWrath then
--https://wowwiki-archive.fandom.com/wiki/Downranking
penalty = min(1,max(0,(22+(level+5)-playerLevel)/20))
elseif isTBC then
4 years ago
-- TBC added another downrank penalty
penalty = penalty * min(1, (level + 11) / playerLevel)
end
spellPower = spellPower * penalty
return healModifier * (amount + (spellPower * spModifier))
end
local function DirectCoefficient(castTime)
return castTime / 3.5
end
local function HotCoefficient(duration)
return duration / 15
end
local function avg(a, b)
return (a + b) / 2
end
--[[
What the different callbacks do:
AuraHandler: Specific aura tracking needed for this class, who has Beacon up on them and such
ResetChargeData: Due to spell "queuing" you can't always rely on aura data for buffs that last one or two casts, for example Divine Favor (+100% crit, one spell)
if you cast Holy Light and queue Flash of Light the library would still see they have Divine Favor and give them crits on both spells. The reset means that the flag that indicates
they have the aura can be killed and if they interrupt the cast then it will call this and let you reset the flags.
What happens in terms of what the client thinks and what actually is, is something like this:
UNIT_SPELLCAST_START, Holy Light -> Divine Favor up
UNIT_SPELLCAST_SUCCEEDED, Holy Light -> Divine Favor up (But it was really used)
UNIT_SPELLCAST_START, Flash of Light -> Divine Favor up (It's not actually up but auras didn't update)
UNIT_AURA -> Divine Favor up (Split second where it still thinks it's up)
UNIT_AURA -> Divine Favor faded (Client catches up and realizes it's down)
CalculateHealing: Calculates the healing value, does all the formula calculations talent modifiers and such
CalculateHotHealing: Used specifically for calculating the heals of hots
GetHealTargets: Who the heal is going to hit, used for setting extra targets for Beacon of Light + Paladin heal or Prayer of Healing.
The returns should either be:
"compressedGUID1,compressedGUID2,compressedGUID3,compressedGUID4", healthAmount
Or if you need to set specific healing values for one GUID it should be
"compressedGUID1,healthAmount1,compressedGUID2,healAmount2,compressedGUID3,healAmount3", -1
The latter is for cases like Glyph of Healing Wave where you need a heal for 1,000 on A and a heal for 200 on the player for B without sending 2 events.
The -1 tells the library to look in the GUId list for the heal amounts
**NOTE** Any GUID returned from GetHealTargets must be compressed through a call to compressGUID[guid]
]]
local CalculateHealing, GetHealTargets, AuraHandler, CalculateHotHealing, ResetChargeData, LoadClassData
local function getBaseHealAmount(spellData, spellName, spellID, spellRank)
if spellID == 37563 then
spellData = spellData["37563"]
else
spellData = spellData[spellName]
end
local average = spellData.averages[spellRank]
if type(average) == "number" then
return average
end
local requiresLevel = spellData.levels[spellRank]
return average[min(playerLevel - requiresLevel + 1, #average)]
end
if( playerClass == "DRUID" ) then
LoadClassData = function()
local GiftofNature = GetSpellInfo(17104)
local HealingTouch = GetSpellInfo(5185)
local ImprovedRejuv = GetSpellInfo(17111)
local MarkoftheWild = GetSpellInfo(1126)
local Regrowth = GetSpellInfo(8936)
local Rejuvenation = GetSpellInfo(774)
local Tranquility = GetSpellInfo(740)
local Lifebloom = GetSpellInfo(33763) or "Lifebloom"
3 years ago
local EmpoweredRejuv = GetSpellInfo(33886) or "Empowered Rejuv"
local EmpoweredTouch = GetSpellInfo(33879) or "Empowered Touch"
local WildGrowth = GetSpellInfo(48438) or "Wild Growth"
local Nourish = GetSpellInfo(50464) or "Nourish"
local MasterShapeshifter = GetSpellInfo(48411) or "Master Shapeshifter"
local Genesis = GetSpellInfo(57810) or "Genesis"
local NaturesSplendor = GetSpellInfo(57865) or "Natures Splendor"
local TreeofLife = GetSpellInfo(33891) or "Tree of Life"
hotData[Regrowth] = { interval = 3, ticks = 7, coeff = (isTBC or isWrath) and 0.7 or 0.5, levels = { 12, 18, 24, 30, 36, 42, 48, 54, 60, 65, 71, 77 }, averages = { 98, 175, 259, 343, 427, 546, 686, 861, 1064, 1274, 1792, 2345 }}
if isWrath then
hotData[Rejuvenation] = { interval = 3, levels = { 4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 60, 63, 69, 75, 80 }, averages = { 40, 70, 145, 225, 305, 380, 485, 610, 760, 945, 1110, 1165, 1325, 1490, 1690 }}
hotData[Lifebloom] = {interval = 1, ticks = 7, coeff = 0.66626, dhCoeff = 0.517928287, levels = {64, 72, 80}, averages = {224, 287, 371}, bomb = {480, 616, 776}}
hotData[WildGrowth] = {interval = 1, ticks = 7, coeff = 0.8056, levels = {60, 70, 75, 80}, averages = {686, 861, 1239, 1442}}
else
hotData[Rejuvenation] = { interval = 3, levels = { 4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 60, 63, 69 }, averages = { 32, 56, 116, 180, 244, 304, 388, 488, 608, 756, 888, 932, 1060 }}
hotData[Lifebloom] = {interval = 1, ticks = 7, coeff = 0.52, dhCoeff = 0.34335, levels = {64}, averages = {273}, bomb = {600}}
end
if isWrath then
spellData[HealingTouch] = { levels = {1, 8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 62, 69, 74, 79}, averages = {
{avg(37, 51), avg(37, 52), avg(38, 53), avg(39, 54), avg(40, 55)},
{avg(88, 112), avg(89, 114), avg(90, 115), avg(91, 116), avg(93, 118), avg(94, 119)},
{avg(195, 243), avg(196, 245), avg(198, 247), avg(200, 249), avg(202, 251), avg(204, 253)},
{avg(363, 445), avg(365, 448), avg(368, 451), avg(371, 454), avg(373, 456), avg(376, 459)},
{avg(490, 594), avg(493, 597), avg(496, 600), avg(499, 603), avg(502, 606), avg(505, 609)},
{avg(636, 766), avg(639, 770), avg(642, 773), avg(646, 777), avg(649, 780), avg(653, 783)},
{avg(802, 960), avg(805, 964), avg(809, 968), avg(813, 972), avg(817, 976), avg(821, 980)},
{avg(1199, 1427), avg(1203, 1432), avg(1208, 1436), avg(1212, 1441), avg(1217, 1445), avg(1221, 1450)},
{avg(1299, 1539), avg(1304, 1545), avg(1309, 1550), avg(1314, 1555), avg(1319, 1560), avg(1324, 1565)},
{avg(1620, 1912), avg(1625, 1918), avg(1631, 1924), avg(1637, 1930), avg(1642, 1935), avg(1648, 1941)},
{avg(1944, 2294), avg(1950, 2301), avg(1956, 2307), avg(1962, 2313), avg(1968, 2319), avg(1975, 2325)},
{avg(2026, 2392), avg(2032, 2399), avg(2038, 2405), avg(2044, 2411), avg(2051, 2418), avg(2057, 2424)},
{avg(2321, 2739), avg(2328, 2746), avg(2335, 2753), avg(2342, 2760), avg(2349, 2767)},
{avg(3223, 3805), avg(3232, 3815), avg(3242, 3825), avg(3252, 3835), avg(3262, 3845)},
{avg(3750, 4428), avg(3761, 4440)} }}
else
spellData[HealingTouch] = { levels = {1, 8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 62, 69}, averages = {
{avg(37, 51), avg(37, 52), avg(38, 53), avg(39, 54), avg(40, 55)},
{avg(88, 112), avg(89, 114), avg(90, 115), avg(91, 116), avg(93, 118), avg(94, 119)},
{avg(195, 243), avg(196, 245), avg(198, 247), avg(200, 249), avg(202, 251), avg(204, 253)},
{avg(363, 445), avg(365, 448), avg(368, 451), avg(371, 454), avg(373, 456), avg(376, 459)},
{avg(572, 694), avg(575, 698), avg(579, 701), avg(582, 705), avg(586, 708), avg(589, 712)},
{avg(742, 894), avg(746, 898), avg(750, 902), avg(754, 906), avg(758, 910), avg(762, 914)},
{avg(936, 1120), avg(940, 1125), avg(945, 1129), avg(949, 1134), avg(954, 1138), avg(958, 1143)},
{avg(1199, 1427), avg(1204, 1433), avg(1209, 1438), avg(1214, 1443), avg(1219, 1448), avg(1225, 1453)},
{avg(1516, 1796), avg(1521, 1802), avg(1527, 1808), avg(1533, 1814), avg(1539, 1820), avg(1545, 1826)},
{avg(1890, 2230), avg(1896, 2237), avg(1903, 2244), avg(1909, 2250), avg(1916, 2257), avg(1923, 2263)},
{avg(2267, 2677), avg(2274, 2685), avg(2281, 2692), avg(2288, 2699), avg(2296, 2707), avg(2303, 2714)},
{avg(2364, 2790), avg(2371, 2798), avg(2378, 2805), avg(2386, 2813), avg(2393, 2820), avg(2401, 2827)},
{avg(2707, 3197), avg(2715, 3206)} }}
end
spellData[Regrowth] = {coeff = (isWrath and 0.6 or 0.5) * (2 / 3.5) , levels = hotData[Regrowth].levels, averages = {
4 years ago
{avg(84, 98), avg(85, 100), avg(87, 102), avg(89, 104), avg(91, 106), avg(93, 107)},
{avg(164, 188), avg(166, 191), avg(169, 193), avg(171, 196), avg(174, 198), avg(176, 201)},
{avg(240, 274), avg(243, 278), avg(246, 281), avg(249, 284), avg(252, 287), avg(255, 290)},
{avg(318, 360), avg(321, 364), avg(325, 368), avg(328, 371), avg(332, 375), avg(336, 378)},
{avg(405, 457), avg(409, 462), avg(413, 466), avg(417, 470), avg(421, 474), avg(425, 478)},
{avg(511, 575), avg(515, 580), avg(520, 585), avg(525, 590), avg(529, 594), avg(534, 599)},
{avg(646, 724), avg(651, 730), avg(656, 735), avg(661, 740), avg(667, 746), avg(672, 751)},
{avg(809, 905), avg(815, 911), avg(821, 917), avg(827, 923), avg(833, 929), avg(839, 935)},
{avg(1003, 1119), avg(1009, 1126), avg(1016, 1133), avg(1023, 1140), avg(1030, 1147), avg(1037, 1153)},
3 years ago
{avg(1215, 1355), avg(1222, 1363), avg(1230, 1371), avg(1238, 1379), avg(1245, 1386), avg(1253, 1394)},
{avg(1710, 1908), avg(1720, 1919), avg(1731, 1930), avg(1742, 1941), avg(1753, 1952), avg(1764, 1962)},
{avg(2234, 2494), avg(2241, 2502), avg(2249, 2510), avg(2257, 2518)} }}
if isTBC or isWrath then
spellData[Tranquility] = {_isChanneled = true, coeff = 1.145, ticks = 4, interval = 2, levels = {30, 40, 50, 60, 70, 75, 80}, averages = {
4 years ago
{351 * 4, 354 * 4, 356 * 4, 358 * 4, 360 * 4, 362 * 4, 365 * 4},
{515 * 4, 518 * 4, 521 * 4, 523 * 4, 526 * 4, 528 * 4, 531 * 4},
{765 * 4, 769 * 4, 772 * 4, 776 * 4, 779 * 4, 782 * 4, 786 * 4},
{1097 * 4, 1101 * 4, 1105 * 4, 1109 * 4, 1112 * 4, 1116 * 4, 1120 * 4},
3 years ago
{1518 * 4, 1523 * 4, 1527 * 4, 1532 * 4, 1536 * 4},
{2598 * 4, 2606 * 4, 2614 * 4, 2622 * 4, 2629 * 4},
{3035 * 4} }}
4 years ago
else
3 years ago
spellData[Tranquility] = {_isChanneled = true, coeff = 1/3, ticks = 5, interval = 2, levels = {30, 40, 50, 60}, averages = {
4 years ago
{94 * 5, 95 * 5, 96 * 5, 96 * 5, 97 * 5, 97 * 5, 98 * 5},
{138 * 5, 139 * 5, 140 * 5, 141 * 5, 141 * 5, 142 * 5, 143 * 5},
{205 * 5, 206 * 5, 207 * 5, 208 * 5, 209 * 5, 210 * 5, 211 * 5},
{294 * 5} }}
end
3 years ago
spellData[Nourish] = {coeff = 0.358005, levels = {80}, averages = {avg(1883, 2187)}}
4 years ago
talentData[GiftofNature] = {mod = 0.02, current = 0}
talentData[ImprovedRejuv] = {mod = 0.05, current = 0}
talentData[EmpoweredRejuv] = {mod = 0.04, current = 0}
talentData[EmpoweredTouch] = {mod = 0.1, current = 0}
3 years ago
talentData[Genesis] = {mod = 0.01, current = 0}
talentData[NaturesSplendor] = {mod = 1, current = 0}
talentData[MasterShapeshifter] = {mod = 0.02, current = 0}
4 years ago
itemSetsData["Stormrage"] = {16903, 16898, 16904, 16897, 16900, 16899, 16901, 16902}
itemSetsData["Nordrassil"] = {30216, 30217, 30219, 30220, 30221}
itemSetsData["Thunderheart"] = {31041, 31032, 31037, 31045, 31047, 34571, 34445, 34554}
3 years ago
itemSetsData["Dreamwalker"] = {40460, 40461, 40462, 40463, 40465, 39531, 39538, 39539, 39542, 39543}
itemSetsData["Lasherweave"] = {50106, 50107, 50108, 50109, 50113, 51139, 51138, 51137, 51136, 51135, 51300, 51301, 51302, 51303, 51304}
local bloomBombIdols = {[28355] = 87, [33076] = 105, [33841] = 116, [35021] = 131, [42576] = 188, [42577] = 217, [42578] = 246, [42579] = 294, [42580] = 376, [51423] = 448}
local rejuIdols = {[186054] = 15, [22398] = 50, [25643] = 86, [38366] = 33}
local bloomIdols = {[40711] = 125, [27886] = 47}
local hotTotals, hasRegrowth = {}, {}
AuraHandler = function(unit, guid)
hotTotals[guid] = 0
if( unitHasAura(unit, Rejuvenation) ) then hotTotals[guid] = hotTotals[guid] + 1 end
if( unitHasAura(unit, Lifebloom) ) then hotTotals[guid] = hotTotals[guid] + 1 end
if( unitHasAura(unit, WildGrowth) ) then hotTotals[guid] = hotTotals[guid] + 1 end
if( unitHasAura(unit, Regrowth) ) then
hasRegrowth[guid] = true
hotTotals[guid] = hotTotals[guid] + 1
else
hasRegrowth[guid] = nil
end
end
4 years ago
3 years ago
GetHealTargets = function(bitType, guid, spellID)
4 years ago
-- Tranquility pulses on everyone within 30 yards, if they are in range of Mark of the Wild they'll get Tranquility
local spellName = GetSpellInfo(spellID)
if( spellName == Tranquility ) then
local targets = compressGUID[playerGUID]
local playerGroup = guidToGroup[playerGUID]
for groupGUID, id in pairs(guidToGroup) do
3 years ago
if( id == playerGroup and playerGUID ~= groupGUID and not UnitHasVehicleUI(guidToUnit[groupID]) and IsSpellInRange(MarkoftheWild, guidToUnit[groupGUID]) == 1 ) then
4 years ago
targets = targets .. "," .. compressGUID[groupGUID]
end
end
3 years ago
return targets
4 years ago
end
3 years ago
return compressGUID[guid]
4 years ago
end
-- Calculate hot heals
3 years ago
local wgTicks = {}
4 years ago
CalculateHotHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
local bombAmount, totalTicks
-- Gift of Nature
3 years ago
if isTBC or isWrath then
4 years ago
healModifier = healModifier * (1 + talentData[GiftofNature].current)
else
-- Gift of Nature does only apply to base values in classic
3 years ago
healAmount = healAmount + talentData[GiftofNature].current
end
if( unitHasAura("player", TreeofLife) ) then
-- 32387 - Idol of the Raven Godess, +44 SP while in TOL
if( playerCurrentRelic == 32387 ) then
spellPower = spellPower + 44
end
4 years ago
end
-- Rejuvenation
if( spellName == Rejuvenation ) then
3 years ago
if isTBC or isWrath then
healModifier = healModifier + talentData[ImprovedRejuv].current
4 years ago
else
-- Improved Rejuvenation only applies to base values in classic
healAmount = healAmount * (1 + talentData[ImprovedRejuv].current)
end
if( playerCurrentRelic and rejuIdols[playerCurrentRelic] ) then
spellPower = spellPower + rejuIdols[playerCurrentRelic]
end
3 years ago
local duration = isWrath and 15 or 12
4 years ago
local ticks = duration / hotData[spellName].interval
if( equippedSetCache["Stormrage"] >= 8 ) then
healAmount = healAmount + (healAmount / ticks) -- Add Tick Amount Gained by Set.
3 years ago
duration = duration + 3
4 years ago
ticks = ticks + 1
end
totalTicks = ticks
3 years ago
spellPower = spellPower * (((duration / 15) * (isWrath and 1.88 or 1)) * (1 + talentData[EmpoweredRejuv].current))
4 years ago
spellPower = spellPower / ticks
healAmount = healAmount / ticks
elseif( spellName == Regrowth ) then
3 years ago
spellPower = spellPower * hotData[spellName].coeff * (isWrath and 1.88 or 1 ) * (1 + talentData[EmpoweredRejuv].current)
4 years ago
spellPower = spellPower / hotData[spellName].ticks
healAmount = healAmount / hotData[spellName].ticks
totalTicks = 7
3 years ago
if( talentData[NaturesSplendor].current >= 1 ) then totalTicks = totalTicks + 2 end
4 years ago
if( equippedSetCache["Nordrassil"] >= 2 ) then totalTicks = totalTicks + 2 end
elseif( spellName == Lifebloom ) then
-- Figure out the bomb heal, apparently Gift of Nature double dips and will heal 10% for the HOT + 10% again for the direct heal
local bombSpellPower = spellPower
if( playerCurrentRelic and bloomBombIdols[playerCurrentRelic] ) then
bombSpellPower = bombSpellPower + bloomBombIdols[playerCurrentRelic]
end
local bombSpell = bombSpellPower * hotData[spellName].dhCoeff * (1 + talentData[EmpoweredRejuv].current)
bombAmount = ceil(calculateGeneralAmount(hotData[spellName].levels[spellRank], hotData[spellName].bomb[spellRank], bombSpell, spModifier, healModifier))
-- Figure out the hot tick healing
spellPower = spellPower * (hotData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current))
3 years ago
spellPower = spellPower / hotData[spellName].ticks
healAmount = healAmount / hotData[spellName].ticks
-- Figure out total ticks
totalTicks = 7
4 years ago
3 years ago
if( playerCurrentRelic and bloomIdols[playerCurrentRelic] ) then
spellPower = spellPower + bloomIdols[playerCurrentRelic]
4 years ago
end
3 years ago
-- Glyph of Lifebloom, +1 second
if( glyphCache[54826] ) then totalTicks = totalTicks + 1 end
-- Nature's Splendor, +2 seconds
if( talentData[NaturesSplendor].current >= 1 ) then totalTicks = totalTicks + 2 end
-- Wild Growth
elseif( spellName == WildGrowth ) then
spellPower = spellPower * (hotData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current))
4 years ago
spellPower = spellPower / hotData[spellName].ticks
healAmount = healAmount / hotData[spellName].ticks
3 years ago
healModifier = healModifier + talentData[Genesis].current
table.wipe(wgTicks)
local tickModifier = equippedSetCache["Lasherweave"] >= 2 and 0.70 or 1
local tickAmount = healAmount / hotData[spellName].ticks
for i=1, hotData[spellName].ticks do
table.insert(wgTicks, math.ceil(healModifier * ((healAmount + tickAmount * (3 - (i - 1) * tickModifier)) + (spellPower * spModifier))))
end
4 years ago
3 years ago
if( isWrath and unitHasAura("player", MasterShapeshifter) ) then
healModifier = healModifier * (1 + talentData[MasterShapeshifter].current)
end
return HOT_HEALS, wgTicks, hotData[spellName].ticks, hotData[spellName].interval, nil, true
4 years ago
end
3 years ago
healModifier = healModifier + talentData[Genesis].current
4 years ago
3 years ago
if( isWrath and unitHasAura("player", MasterShapeshifter) ) then
healModifier = healModifier * (1 + talentData[MasterShapeshifter].current)
if bombAmount then
bombAmount = bombAmount * (1 + talentData[MasterShapeshifter].current)
end
end
healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
return HOT_HEALS, ceil(max(0, healAmount)), totalTicks, hotData[spellName].interval, bombAmount
4 years ago
end
-- Calcualte direct and channeled heals
CalculateHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
-- Gift of Nature
3 years ago
if isTBC or isWrath then
4 years ago
healModifier = healModifier * (1 + talentData[GiftofNature].current)
else
-- Gift of Nature does only apply to base values in classic
healAmount = healAmount * (1 + talentData[GiftofNature].current)
end
3 years ago
if( isWrath and unitHasAura("player", MasterShapeshifter) ) then
healModifier = healModifier * ( 1 + talentData[MasterShapeshifter].current)
-- 32387 - Idol of the Raven Godess, +44 SP while in TOL
if( playerCurrentRelic == 32387 ) then
spellPower = spellPower + 44
end
end
4 years ago
-- Regrowth
if( spellName == Regrowth ) then
3 years ago
-- Glyph of Regrowth - +20% if target has Regrowth
if( glyphCache[54743] and hasRegrowth[guid] ) then
healModifier = healModifier * 1.20
end
spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
-- Nourish
elseif( spellName == Nourish ) then
-- 46138 - Idol of Flourishing Life, +187 Nourish SP
if( playerCurrentRelic == 46138 ) then
spellPower = spellPower + 187
end
3 years ago
-- Apply any hot specific bonuses
local hots = hotTotals[guid]
if( hots and hots > 0 ) then
local bonus = 1.20
3 years ago
-- T7 Resto, +5% healing per each of the players hot on their target
if( equippedSetCache["Dreamwalker"] >= 2 ) then
bonus = bonus + 0.05 * hots
end
3 years ago
-- Glyph of Nourish - 6% per HoT
if( glyphCache[62971] ) then
bonus = bonus + 0.06 * hots
end
3 years ago
healModifier = healModifier * bonus
end
3 years ago
spellPower = spellPower * ((spellData[spellName].coeff * 1.88) + talentData[EmpoweredTouch].current)
-- Healing Touch
4 years ago
elseif( spellName == HealingTouch ) then
3 years ago
local castTime
if isWrath then
castTime = spellRank > 3 and 3 or spellRank == 3 and 2.5 or spellRank == 2 and 2 or 1.5
else
castTime = spellRank >= 5 and 3.5 or (spellRank == 4 and 3 or (spellRank == 3 and 2.5 or (spellRank == 2 and 2 or 1.5)))
end
4 years ago
3 years ago
-- Glyph of Healing Touch
if( glyphCache[54825] ) then
healAmount = healAmount / 2
castTime = max(castTime - 1.5,1.5)
end
spellPower = spellPower * (((castTime / 3.5) * (isWrath and 1.88 or 1)) + (talentData[EmpoweredTouch].current * (isWrath and 2 or 1)))
4 years ago
if( playerCurrentRelic == 22399 ) then
healAmount = healAmount + 100
elseif( playerCurrentRelic == 28568 ) then
healAmount = healAmount + 136
end
if equippedSetCache["Thunderheart"] >= 4 then
3 years ago
healModifier = healModifier * 1.05
4 years ago
end
-- Tranquility
elseif( spellName == Tranquility ) then
3 years ago
healModifier = healModifier + talentData[Genesis].current
spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1) * (1 + talentData[EmpoweredRejuv].current)
4 years ago
spellPower = spellPower / spellData[spellName].ticks
healAmount = healAmount / spellData[spellName].ticks
end
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
-- 100% chance to crit with Nature, this mostly just covers fights like Loatheb where you will basically have 100% crit
if( GetSpellCritChance(4) >= 100 ) then
healAmount = healAmount * 1.50
end
if( spellData[spellName].ticks ) then
return CHANNEL_HEALS, ceil(healAmount), spellData[spellName].ticks, spellData[spellName].interval
end
return DIRECT_HEALS, ceil(healAmount)
end
end
end
3 years ago
local hasDivineFavor, activeBeaconGUID
4 years ago
if( playerClass == "PALADIN" ) then
LoadClassData = function()
local DivineFavor = GetSpellInfo(20216)
local FlashofLight = GetSpellInfo(19750)
local HealingLight = GetSpellInfo(20237)
local HolyLight = GetSpellInfo(635)
3 years ago
local Divinity = GetSpellInfo(63646) or "Divinity"
local TouchedbytheLight = GetSpellInfo(53592) or "TouchedbytheLight"
local BeaconofLight = GetSpellInfo(53563) or "BeaconofLight"
local SealofLight = GetSpellInfo(20165) or "SealofLight"
local DivineIllumination = GetSpellInfo(31842) or "DivineIllumination"
if isWrath then
spellData[HolyLight] = { coeff = 2.5 / 3.5, levels = {1, 6, 14, 22, 30, 38, 46, 54, 60, 62, 70, 75, 80}, averages = {
{avg(50, 60), avg(50, 61), avg(51, 62), avg(52, 63), avg(53, 64)},
{avg(96, 116), avg(97, 118), avg(98, 119), avg(99, 120), avg(100, 121), avg(101, 122)},
{avg(203, 239), avg(204, 241), avg(206, 243), avg(208, 245), avg(209, 246), avg(211, 248)},
{avg(397, 455), avg(399, 458), avg(401, 460), avg(404, 463), avg(406, 465), avg(409, 467)},
{avg(628, 708), avg(631, 712), avg(634, 715), avg(637, 718), avg(640, 721), avg(643, 724)},
{avg(894, 998), avg(897, 1002), avg(901, 1006), avg(905, 1010), avg(909, 1014), avg(913, 1017)},
{avg(1209, 1349), avg(1213, 1354), avg(1218, 1359), avg(1222, 1363), avg(1227, 1368), avg(1232, 1372)},
{avg(1595, 1777), avg(1600, 1783), avg(1605, 1788), avg(1610, 1793), avg(1615, 1798), avg(1621, 1803)},
{avg(2034, 2266), avg(2039, 2272), avg(2045, 2278), avg(2051, 2284), avg(2057, 2290), avg(2063, 2295)},
{avg(2232, 2486), avg(2238, 2493), avg(2244, 2499), avg(2251, 2506), avg(2257, 2512), avg(2264, 2518)},
{avg(2818, 3138), avg(2825, 3145), avg(2832, 3152), avg(2839, 3159), avg(2846, 3166)},
{avg(4199, 4677), avg(4209, 4688), avg(4219, 4698), avg(4230, 4709), avg(4240, 4719)},
{avg(4888, 5444)} }}
spellData[FlashofLight] = { coeff = 1.5 / 3.5, levels = {20, 26, 34, 42, 50, 58, 66, 74, 79}, averages = {
{avg(81, 93), avg(82, 94), avg(83, 95), avg(84, 96), avg(85, 97), avg(86, 98)},
{avg(124, 144), avg(125, 146), avg(126, 147), avg(127, 148), avg(129, 150), avg(130, 151)},
{avg(189, 211), avg(190, 213), avg(192, 215), avg(193, 216), avg(195, 218), avg(197, 219)},
{avg(256, 288), avg(257, 290), avg(259, 292), avg(261, 294), avg(263, 296), avg(265, 298)},
{avg(346, 390), avg(348, 393), avg(350, 395), avg(352, 397), avg(354, 399), avg(357, 401)},
{avg(445, 499), avg(447, 502), avg(450, 505), avg(452, 507), avg(455, 510), avg(458, 512)},
{avg(588, 658), avg(591, 661), avg(594, 664), avg(597, 667), avg(600, 670)},
{avg(682, 764), avg(685, 768), avg(688, 771), avg(692, 775), avg(695, 778)},
{avg(785,879), avg(788,883)} }}
else
spellData[HolyLight] = { coeff = 2.5 / 3.5, levels = {1, 6, 14, 22, 30, 38, 46, 54, 60, 62, 70}, averages = {
{avg(39, 47), avg(39, 48), avg(40, 49), avg(41, 50), avg(42, 51)},
{avg(76, 90), avg(77, 92), avg(78, 93), avg(79, 94), avg(80, 95), avg(81, 96)},
{avg(159, 187), avg(160, 189), avg(162, 191), avg(164, 193), avg(165, 194), avg(167, 196)},
{avg(310, 356), avg(312, 359), avg(314, 361), avg(317, 364), avg(319, 366), avg(322, 368)},
{avg(491, 553), avg(494, 557), avg(497, 560), avg(500, 563), avg(503, 566), avg(506, 569)},
{avg(698, 780), avg(701, 784), avg(705, 788), avg(709, 792), avg(713, 796), avg(717, 799)},
{avg(945, 1053), avg(949, 1058), avg(954, 1063), avg(958, 1067), avg(963, 1072), avg(968, 1076)},
{avg(1246, 1388), avg(1251, 1394), avg(1256, 1399), avg(1261, 1404), avg(1266, 1409), avg(1272, 1414)},
{avg(1590, 1770), avg(1595, 1775), avg(1601, 1781), avg(1607, 1787), avg(1613, 1793), avg(1619, 1799)},
{avg(1741, 1939), avg(1747, 1946), avg(1753, 1952), avg(1760, 1959), avg(1766, 1965), avg(1773, 1971)},
{avg(2196, 2446)} }}
spellData[FlashofLight] = { coeff = 1.5 / 3.5, levels = {20, 26, 34, 42, 50, 58, 66}, averages = {
{avg(62, 72), avg(63, 73), avg(64, 74), avg(65, 75), avg(66, 76), avg(67, 77)},
{avg(96, 110), avg(97, 112), avg(98, 113), avg(99, 114), avg(101, 116), avg(102, 117)},
{avg(145, 163), avg(146, 165), avg(148, 167), avg(149, 168), avg(151, 170), avg(153, 171)},
{avg(197, 221), avg(198, 223), avg(200, 225), avg(202, 227), avg(204, 229), avg(206, 231)},
{avg(267, 299), avg(269, 302), avg(271, 304), avg(273, 306), avg(275, 308), avg(278, 310)},
{avg(343, 383), avg(345, 386), avg(348, 389), avg(350, 391), avg(353, 394), avg(356, 396)},
{avg(448, 502), avg(450, 505), avg(453, 508), avg(455, 510), avg(458, 513)} }}
end
4 years ago
talentData[HealingLight] = { mod = 0.04, current = 0 }
3 years ago
talentData[Divinity] = { mod = 0.01, current = 0 }
talentData[TouchedbytheLight] = {mod = 0.10, current = 0}
4 years ago
itemSetsData["Lightbringer"] = {30992, 30983, 30988, 30994, 30996, 34432, 34487, 34559}
3 years ago
itemSetsData["Lightsworn"] = {50865, 50866, 50867, 50868, 50869, 51270, 51271, 51272, 51273, 51274, 51165, 51166, 51167, 51168, 51169}
4 years ago
3 years ago
local flashLibrams
if isWrath then
flashLibrams = {[51472] = 510, [42616] = 436, [42615] = 375, [42614] = 331, [42613] = 293, [42612] = 204, [28592] = 89, [25644] = 79, [23006] = 43, [23201] = 28}
else
flashLibrams = {[23006] = 83, [23201] = 53, [186065] = 10, [25644] = 79}
end
local holyLibrams = {[45436] = 160, [40268] = 141, [28296] = 47}
4 years ago
local blessings = {
[19977] = {
[HolyLight] = 210,
[FlashofLight] = 60,
},
[19978] = {
[HolyLight] = 300,
[FlashofLight] = 85,
},
[19979] = {
[HolyLight] = 400,
[FlashofLight] = 115,
},
[25890] = {
[HolyLight] = 400,
[FlashofLight] = 115,
},
[27144] = {
[HolyLight] = 580,
[FlashofLight] = 185,
},
[27145] = {
[HolyLight] = 580,
[FlashofLight] = 185,
},
}
AuraHandler = function(unit, guid)
3 years ago
if( unitHasAura(unit, BeaconofLight) ) then
activeBeaconGUID = guid
elseif( activeBeaconGUID == guid ) then
activeBeaconGUID = nil
end
4 years ago
if( unit == "player" ) then
hasDivineFavor = unitHasAura("player", DivineFavor)
end
end
3 years ago
GetHealTargets = function(bitType, guid, spellID)
if( activeBeaconGUID and activeBeaconGUID ~= guid and guidToUnit[activeBeaconGUID] and UnitIsVisible(guidToUnit[activeBeaconGUID]) ) then
return string.format("%s,%s", compressGUID[guid], compressGUID[activeBeaconGUID])
end
3 years ago
return compressGUID[guid]
4 years ago
end
CalculateHealing = function(guid, spellID, unit)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
3 years ago
if( glyphCache[54943] and unitHasAura("player", SealofLight) ) then
healModifier = healModifier * 1.05
end
if isTBC or isWrath then
healModifier = healModifier * (1 + talentData[HealingLight].current) * (1 + (talentData[Divinity].current * (UnitIsUnit(unit,"player") and 2 or 1)))
4 years ago
else
healAmount = healAmount * (1 + talentData[HealingLight].current)
end
if playerCurrentRelic then
3 years ago
if( spellName == HolyLight and holyLibrams[playerCurrentRelic] ) then
spellPower = spellPower + holyLibrams[playerCurrentRelic]
elseif( spellName == FlashofLight and flashLibrams[playerCurrentRelic] ) then
4 years ago
spellPower = spellPower + flashLibrams[playerCurrentRelic]
end
end
3 years ago
if( equippedSetCache["Lightsworn"] >= 2 and unitHasAura("player", DivineIllumination) ) then
healModifier = healModifier * 1.35
end
4 years ago
if( equippedSetCache["Lightbringer"] >= 4 and spellName == FlashofLight ) then healModifier = healModifier + 0.05 end
3 years ago
spellPower = spellPower * spellData[spellName].coeff * (isWrath and 2.35 or 1)
4 years ago
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
for auraID, values in pairs(blessings) do
if unitHasAura(unit, auraID) then
if playerCurrentRelic == 28592 then
if spellName == FlashofLight then
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName] + 60, healModifier, 1)
else
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName] + 120, healModifier, 1)
end
else
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName], healModifier, 1)
end
break
end
end
if( hasDivineFavor or GetSpellCritChance(2) >= 100 ) then
3 years ago
healAmount = healAmount * (1.50 * (1 + talentData[TouchedbytheLight].current))
4 years ago
end
return DIRECT_HEALS, ceil(healAmount)
end
end
end
if( playerClass == "PRIEST" ) then
LoadClassData = function()
local Renew = GetSpellInfo(139)
local GreaterHeal = GetSpellInfo(2060)
local PrayerofHealing = GetSpellInfo(596)
local FlashHeal = GetSpellInfo(2061)
local Heal = GetSpellInfo(2054)
local LesserHeal = GetSpellInfo(2050)
local SpiritualHealing = GetSpellInfo(14898)
local ImprovedRenew = GetSpellInfo(14908)
local GreaterHealHot = GetSpellInfo(22009)
3 years ago
local DispelMagic = GetSpellInfo(527)
4 years ago
local BindingHeal = GetSpellInfo(32546) or "Binding Heal"
local EmpoweredHealing = GetSpellInfo(33158) or "Empowered Healing"
local Renewal = GetSpellInfo(37563) and "37563" -- T4 bonus
3 years ago
local Penance = GetSpellInfo(53007) or "Penance"
local Grace = GetSpellInfo(47517) or "Grace"
local BlessedResilience = GetSpellInfo(33142) or "Blessed Resilience"
local FocusedPower = GetSpellInfo(33190) or "Focused Power"
local DivineProvidence = GetSpellInfo(47567) or "Divine Providence"
local EmpoweredRenew = GetSpellInfo(63534) or "Empowered Renew"
local TwinDisciplines = GetSpellInfo(47586) or "Twin Disciplines"
hotData[Renew] = {coeff = 1, interval = 3, ticks = 5, levels = {8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 65, 70, 75, 80}, averages = {
45, 100, 175, 245, 315, 400, 510, 650, 810, 970, 1010, 1110, 1235, 1400 }}
4 years ago
hotData[GreaterHealHot] = hotData[Renew]
if Renewal then
hotData[Renewal] = {coeff = 0, interval = 3, ticks = 3, levels = {70}, averages = {150}}
end
3 years ago
spellData[FlashHeal] = {coeff = 1.5 / 3.5, levels = {20, 26, 32, 38, 44, 50, 56, 61, 67, 73, 79}, averages = {
4 years ago
{avg(193, 237), avg(194, 239), avg(196, 241), avg(198, 243), avg(200, 245), avg(202, 247)},
{avg(258, 314), avg(260, 317), avg(262, 319), avg(264, 321), avg(266, 323), avg(269, 325)},
{avg(327, 393), avg(329, 396), avg(332, 398), avg(334, 401), avg(337, 403), avg(339, 406)},
{avg(400, 478), avg(402, 481), avg(405, 484), avg(408, 487), avg(411, 490), avg(414, 492)},
{avg(518, 616), avg(521, 620), avg(524, 623), avg(527, 626), avg(531, 630), avg(534, 633)},
{avg(644, 764), avg(647, 768), avg(651, 772), avg(655, 776), avg(658, 779), avg(662, 783)},
{avg(812, 958), avg(816, 963), avg(820, 967), avg(824, 971), avg(828, 975), avg(833, 979)},
{avg(913, 1059), avg(917, 1064), avg(922, 1069), avg(927, 1074), avg(931, 1078)},
3 years ago
{avg(1101, 1279), avg(1106, 1285), avg(1111, 1290), avg(1116, 1295), avg(1121, 1300)},
{avg(1578, 1832), avg(1586, 1840), avg(1594, 1848), avg(1602, 1856), avg(1610, 1864), avg(1618, 1872)},
{avg(1887, 2193), avg(1896, 2203)} }}
spellData[GreaterHeal] = {coeff = 3 / 3.5, levels = {40, 46, 52, 58, 60, 63, 68, 73, 78}, averages = {
4 years ago
{avg(899, 1013), avg(904, 1019), avg(909, 1024), avg(914, 1029), avg(919, 1034), avg(924, 1039)},
{avg(1149, 1289), avg(1154, 1295), avg(1160, 1301), avg(1166, 1307), avg(1172, 1313), avg(1178, 1318)},
{avg(1437, 1609), avg(1443, 1616), avg(1450, 1623), avg(1456, 1629), avg(1463, 1636), avg(1470, 1642)},
{avg(1798, 2006), avg(1805, 2014), avg(1813, 2021), avg(1820, 2029), avg(1828, 2036), avg(1835, 2044)},
{avg(1966, 2194), avg(1974, 2203), avg(1982, 2211), avg(1990, 2219), avg(1998, 2227), avg(2006, 2235)},
{avg(2074, 2410), avg(2082, 2419), avg(2090, 2427), avg(2099, 2436), avg(2107, 2444)},
3 years ago
{avg(2396, 2784), avg(2405, 2794), avg(2414, 2803), avg(2423, 2812), avg(2433, 2822)},
{avg(3395, 3945), avg(3408, 3959), avg(3421, 3972), avg(3434, 3985), avg(3447, 3998)},
{avg(3950, 4590), avg(3965, 4606), avg(3980, 4621)} }}
4 years ago
spellData[Heal] = {coeff = 3 / 3.5, levels = {16, 22, 28, 34}, averages = {
{avg(295, 341), avg(297, 344), avg(299, 346), avg(302, 349), avg(304, 351), avg(307, 353)},
{avg(429, 491), avg(432, 495), avg(435, 498), avg(438, 501), avg(441, 504), avg(445, 507)},
{avg(566, 642), avg(570, 646), avg(574, 650), avg(578, 654), avg(582, 658), avg(586, 662)},
{avg(712, 804), avg(716, 809), avg(721, 813), avg(725, 818), avg(730, 822), avg(734, 827)} }}
spellData[LesserHeal] = {levels = {1, 4, 10}, averages = {
{avg(46, 56), avg(46, 57), avg(47, 58)},
{avg(71, 85), avg(72, 87), avg(73, 88), avg(74, 89), avg(75, 90), avg(76, 91)},
{avg(135, 157), avg(136, 159), avg(138, 161), avg(139, 162), avg(141, 164), avg(143, 165)} }}
3 years ago
spellData[PrayerofHealing] = {coeff = (isWrath and 0.2798 or isTBC and 0.431596 or (3/3.5/3)), levels = {30, 40, 50, 60, 60, 68, 76}, averages = {
4 years ago
{avg(301, 321), avg(302, 323), avg(303, 324), avg(304, 325), avg(306, 327), avg(307, 328), avg(308, 329), avg(310, 331), avg(311, 332), avg(312, 333)},
{avg(444, 472), avg(445, 474), avg(447, 476), avg(448, 477), avg(450, 479), avg(452, 480), avg(453, 482), avg(455, 484), avg(456, 485), avg(458, 487)},
{avg(657, 695), avg(659, 697), avg(661, 699), avg(663, 701), avg(665, 703), avg(667, 705), avg(669, 707), avg(671, 709), avg(673, 711), avg(675, 713)},
{avg(939, 991), avg(941, 994), avg(943, 996), avg(946, 999), avg(948, 1001), avg(951, 1003), avg(953, 1006), avg(955, 1008), avg(958, 1011), avg(960, 1013)},
{avg(997, 1053), avg(999, 1056), avg(1002, 1058), avg(1004, 1061), avg(1007, 1063), avg(1009, 1066), avg(1012, 1068), avg(1014, 1071), avg(1017, 1073), avg(1019, 1076)},
3 years ago
{avg(1246, 1316), avg(1248, 1319), avg(1251, 1322), avg(1254, 1325), avg(1257, 1328), avg(1260, 1330), avg(1262, 1333), avg(1265, 1336)},
{avg(2091, 2209), avg(2095, 2214), avg(2100, 2219), avg(2105, 2224), avg(2109, 2228)} }}
spellData[BindingHeal] = {coeff = 1.5 / 3.5, levels = {64, 72, 78}, averages = {
{avg(1042, 1338), avg(1043, 1340), avg(1045, 1342), avg(1047, 1344), avg(1049, 1346), avg(1051, 1348), avg(1053, 1350), avg(1055, 1352)},
{avg(1619, 2081), avg(1622, 2084), avg(1625, 2087), avg(1628, 2090), avg(1631, 2093)},
{avg(1952, 2508), avg(1955, 2512), avg(1959, 2516)} }}
spellData[Penance] = {_isChanneled = true, coeff = 0.857, ticks = 3, levels = {60, 70, 75, 80}, averages = {avg(670, 756), avg(805, 909), avg(1278, 1442), avg(1484, 1676)}}
4 years ago
talentData[ImprovedRenew] = {mod = 0.05, current = 0}
talentData[SpiritualHealing] = {mod = 0.02, current = 0}
talentData[EmpoweredHealing] = {mod = 0.02, current = 0}
3 years ago
if isWrath then
talentData[EmpoweredHealing].mod = 0.04
end
talentData[BlessedResilience] = {mod = 0.01, current = 0}
talentData[FocusedPower] = {mod = 0.02, current = 0}
talentData[DivineProvidence] = {mod = 0.02, current = 0}
talentData[EmpoweredRenew] = {mod = 0.05, current = 0}
talentData[TwinDisciplines] = {mod = 0.01, current = 0}
4 years ago
itemSetsData["Oracle"] = {21351, 21349, 21350, 21348, 21352}
itemSetsData["Absolution"] = {31068, 31063, 31060, 31069, 31066, 34562, 34527, 34435}
itemSetsData["Avatar"] = {30153, 30152, 30151, 30154, 30150}
3 years ago
local activeGraceGUID, activeGraceModifier = nil, 0
AuraHandler = function(unit, guid)
local stack, _, _, _, caster = select(3, unitHasAura(unit, Grace))
if( caster == "player" ) then
activeGraceModifier = stack * 0.03
activeGraceGUID = guid
elseif( activeGraceGUID == guid ) then
activeGraceGUID = nil
end
end
GetHealTargets = function(bitType, guid, spellID)
4 years ago
local spellName = GetSpellInfo(spellID)
if( spellName == BindingHeal ) then
if guid == playerGUID then
3 years ago
return format("%s", compressGUID[playerGUID])
4 years ago
else
3 years ago
return format("%s,%s", compressGUID[guid], compressGUID[playerGUID])
4 years ago
end
elseif( spellName == PrayerofHealing ) then
3 years ago
-- In Wrath PoH can be castet on other groups than your own
if not isWrath then
guid = UnitGUID("player")
end
4 years ago
local targets = compressGUID[guid]
local group = guidToGroup[guid]
for groupGUID, id in pairs(guidToGroup) do
3 years ago
--We skip the rangecheck in Wrath since range would have to be measured from our target to its party and we can't do that
local unit = isWrath and "player" or guidToUnit[groupGUID]
local testUnit = guidToUnit[groupGUID]
-- Dispel Magic is chosen because it's learned before PoH and maintains 30y range through the expansions
if( id == group and guid ~= groupGUID and (IsSpellInRange(DispelMagic, unit) == 1 or CheckInteractDistance(unit, 4)) and UnitIsVisible(testUnit) and not UnitHasVehicleUI(testUnit) ) then
4 years ago
targets = targets .. "," .. compressGUID[groupGUID]
end
end
3 years ago
return targets
4 years ago
end
3 years ago
return compressGUID[guid]
4 years ago
end
CalculateHotHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
if not spellRank then return end
4 years ago
local healAmount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
local totalTicks
3 years ago
if isWrath then
healModifier = healModifier + talentData[SpiritualHealing].current
elseif isTBC then
4 years ago
healModifier = healModifier * (1 + talentData[SpiritualHealing].current)
else
3 years ago
-- Spiritual Healing only applies to base value in classic
4 years ago
healAmount = healAmount * (1 + talentData[SpiritualHealing].current)
end
if( spellName == Renew or spellName == GreaterHealHot ) then
3 years ago
if isWrath then
healModifier = healModifier + talentData[ImprovedRenew].current
elseif isTBC then
4 years ago
healModifier = healModifier * (1 + talentData[ImprovedRenew].current)
else
-- Improved Renew only applies to the base value in classic
healAmount = healAmount * (1 + talentData[ImprovedRenew].current)
end
3 years ago
healModifier = healModifier + talentData[TwinDisciplines].current
4 years ago
3 years ago
totalTicks = hotData[spellName].ticks
if( glyphCache[55674] ) then
healModifier = healModifier + 0.25
-- completly lose a tick
healAmount = healAmount - (healAmount / totalTicks)
spellPower = spellPower - (spellPower / totalTicks)
totalTicks = totalTicks - 1
end
4 years ago
if( equippedSetCache["Oracle"] >= 5 or equippedSetCache["Avatar"] >= 4 ) then
3 years ago
healAmount = healAmount + (healAmount / totalTicks) -- Add Tick Amount gained by Set.
totalTicks = totalTicks + 1
4 years ago
end
3 years ago
spellPower = spellPower * ((isWrath and 1.88 or 1) * (1 + (talentData[EmpoweredRenew].current)))
spellPower = spellPower / totalTicks
healAmount = healAmount / totalTicks
end
4 years ago
3 years ago
if( activeGraceGUID == guid ) then
healModifier = healModifier * (1 + activeGraceModifier)
4 years ago
end
3 years ago
healModifier = healModifier * (1 + talentData[FocusedPower].current)
healModifier = healModifier * (1 + talentData[BlessedResilience].current)
4 years ago
healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
return HOT_HEALS, ceil(healAmount), totalTicks, hotData[spellName].interval
end
CalculateHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
if not spellRank then return end
4 years ago
local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
3 years ago
if isWrath then
healModifier = healModifier + talentData[SpiritualHealing].current
elseif isTBC then
4 years ago
healModifier = healModifier * (1 + talentData[SpiritualHealing].current)
else
healAmount = healAmount * (1 + talentData[SpiritualHealing].current)
end
-- Greater Heal
if( spellName == GreaterHeal ) then
if( equippedSetCache["Absolution"] >= 4 ) then healModifier = healModifier * 1.05 end
3 years ago
spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) * (1 + (talentData[EmpoweredHealing].current * 2)))
4 years ago
-- Flash Heal
elseif( spellName == FlashHeal ) then
3 years ago
spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) * (1 + talentData[EmpoweredHealing].current))
4 years ago
-- Binding Heal
elseif( spellName == BindingHeal ) then
3 years ago
healModifier = healModifier + talentData[DivineProvidence].current
spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) * (1 + talentData[EmpoweredHealing].current))
-- Penance
elseif( spellName == Penance ) then
spellPower = spellPower * (spellData[spellName].coeff * 1.88)
spellPower = spellPower / spellData[spellName].ticks
healModifier = healModifier + talentData[TwinDisciplines].current
4 years ago
-- Prayer of Healing
elseif( spellName == PrayerofHealing ) then
3 years ago
healModifier = healModifier + talentData[DivineProvidence].current
spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
4 years ago
-- Heal
elseif( spellName == Heal ) then
3 years ago
spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
4 years ago
-- Lesser Heal
elseif( spellName == LesserHeal ) then
local castTime = spellRank >= 3 and 2.5 or spellRank == 2 and 2 or 1.5
3 years ago
spellPower = spellPower * (castTime / 3.5) * (isWrath and 1.88 or 1)
end
if( activeGraceGUID == guid ) then
healModifier = healModifier * (1 + activeGraceModifier)
4 years ago
end
3 years ago
healModifier = healModifier * (1 + talentData[FocusedPower].current)
healModifier = healModifier * (1 + talentData[BlessedResilience].current)
4 years ago
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
-- Player has over a 100% chance to crit with Holy spells
if( GetSpellCritChance(2) >= 100 ) then
healAmount = healAmount * 1.50
end
3 years ago
-- Penance ticks 3 times, the player will see all 3 ticks, everyone else should only see the last 2
if( spellName == Penance ) then
return CHANNEL_HEALS, math.ceil(healAmount), spellData[spellName].ticks, 1
end
4 years ago
return DIRECT_HEALS, ceil(healAmount)
end
end
end
if( playerClass == "SHAMAN" ) then
LoadClassData = function()
local ChainHeal = GetSpellInfo(1064)
local HealingWave = GetSpellInfo(331)
local LesserHealingWave = GetSpellInfo(8004)
local ImpChainHeal = GetSpellInfo(30872) or "Improved Chain Heal"
3 years ago
local HealingWay = GetSpellInfo(29206) or "Healing Way"
local Purification = GetSpellInfo(16178) or "Purification"
local EarthShield = GetSpellInfo(49284) or "Earth Shield"
local TidalWaves = GetSpellInfo(51566) or "Tidal Waves"
local Riptide = GetSpellInfo(61295) or "Riptide"
local Earthliving = GetSpellInfo(52000) or "Earthliving"
hotData[Riptide] = {interval = 3, ticks = 5, coeff = 0.50, levels = {60, 70, 75, 80}, averages = {665, 885, 1435, 1670}}
hotData[Earthliving] = {interval = 3, ticks = 4, coeff = 0.80, levels = {30, 40, 50, 60, 70, 80}, averages = {116, 160, 220, 348, 456, 652}}
4 years ago
3 years ago
spellData[ChainHeal] = {coeff = 2.5 / 3.5, levels = {40, 46, 54, 61, 68, 74, 80}, averages = {
4 years ago
{avg(320, 368), avg(322, 371), avg(325, 373), avg(327, 376), avg(330, 378), avg(332, 381)},
{avg(405, 465), avg(407, 468), avg(410, 471), avg(413, 474), avg(416, 477), avg(419, 479)},
{avg(551, 629), avg(554, 633), avg(557, 636), avg(560, 639), avg(564, 643), avg(567, 646)},
{avg(605, 691), avg(608, 695), avg(612, 699), avg(616, 703), avg(620, 707), avg(624, 710)},
3 years ago
{avg(826, 942), avg(829, 946), avg(833, 950), avg(837, 954), avg(841, 958), avg(845, 961)},
{avg(906, 1034), avg(909, 1038), avg(913, 1042), avg(917, 1046), avg(921, 1050), avg(925, 1053)},
{avg(1055, 1205)} }}
spellData[HealingWave] = {levels = {1, 6, 12, 18, 24, 32, 40, 48, 56, 60, 63, 70, 75, 80}, averages = {
4 years ago
{avg(34, 44), avg(34, 45), avg(35, 46), avg(36, 47)},
{avg(64, 78), avg(65, 79), avg(66, 80), avg(67, 81), avg(68, 82), avg(69, 83)},
{avg(129, 155), avg(130, 157), avg(132, 158), avg(133, 160), avg(135, 161), avg(136, 163)},
{avg(268, 316), avg(270, 319), avg(272, 321), avg(274, 323), avg(277, 326), avg(279, 328)},
{avg(376, 440), avg(378, 443), avg(381, 446), avg(384, 449), avg(386, 451), avg(389, 454)},
{avg(536, 622), avg(539, 626), avg(542, 629), avg(545, 632), avg(549, 636), avg(552, 639)},
{avg(740, 854), avg(743, 858), avg(747, 862), avg(751, 866), avg(755, 870), avg(759, 874)},
{avg(1017, 1167), avg(1021, 1172), avg(1026, 1177), avg(1031, 1182), avg(1035, 1186), avg(1040, 1191)},
{avg(1367, 1561), avg(1372, 1567), avg(1378, 1572), avg(1383, 1578), avg(1389, 1583), avg(1394, 1589)},
{avg(1620, 1850), avg(1625, 1856), avg(1631, 1861), avg(1636, 1867), avg(1642, 1872), avg(1647, 1878)},
{avg(1725, 1969), avg(1731, 1976), avg(1737, 1982), avg(1743, 1988), avg(1750, 1995), avg(1756, 2001)},
3 years ago
{avg(2134, 2436), avg(2141, 2444), avg(2148, 2451), avg(2155, 2458), avg(2162, 2465)},
{avg(2624, 2996), avg(2632, 3004), avg(2640, 3012), avg(2648, 3020), avg(2656, 3028)},
{avg(3034, 3466)} }}
spellData[LesserHealingWave] = {coeff = 1.5 / 3.5, levels = {20, 28, 36, 44, 52, 60, 66, 72, 77}, averages = {
4 years ago
{avg(162, 186), avg(163, 188), avg(165, 190), avg(167, 192), avg(168, 193), avg(170, 195)},
{avg(247, 281), avg(249, 284), avg(251, 286), avg(253, 288), avg(255, 290), avg(257, 292)},
{avg(337, 381), avg(339, 384), avg(342, 386), avg(344, 389), avg(347, 391), avg(349, 394)},
{avg(458, 514), avg(461, 517), avg(464, 520), avg(467, 523), avg(470, 526), avg(473, 529)},
{avg(631, 705), avg(634, 709), avg(638, 713), avg(641, 716), avg(645, 720), avg(649, 723)},
{avg(832, 928), avg(836, 933), avg(840, 937), avg(844, 941), avg(848, 945), avg(853, 949)},
3 years ago
{avg(1039, 1185), avg(1043, 1190), avg(1047, 1194), avg(1051, 1198), avg(1055, 1202)},
{avg(1382, 1578), avg(1387, 1583), avg(1392, 1588), avg(1397, 1593), avg(1402, 1598)},
{avg(1606, 1834), avg(1612, 1840), avg(1618, 1846), avg(1624, 1852)} }}
4 years ago
3 years ago
talentData[HealingWay] = {mod = (0.25/3), current = 0}
4 years ago
talentData[ImpChainHeal] = {mod = 0.10, current = 0}
talentData[Purification] = {mod = 0.02, current = 0}
3 years ago
talentData[TidalWaves] = {mod = 0.04, current = 0}
4 years ago
itemSetsData["Skyshatter"] = {31016, 31007, 31012, 31019, 31022, 34543, 34438, 34565}
3 years ago
itemSetsData["T7 Resto"] = {40508, 40509, 40510, 40512, 40513, 39583, 39588, 39589, 39590, 39591}
itemSetsData["T9 Resto"] = {48280, 48281, 48282, 48283, 48284, 48295, 48296, 48297, 48298, 48299, 48301, 48302, 48303, 48304, 48300, 48306, 48307, 48308, 48309, 48305, 48286, 48287, 48288, 48289, 48285, 48293, 48292, 48291, 48290, 48294}
local lhwTotems = {[42598] = 320, [42597] = 267, [42596] = 236, [42595] = 204, [25645] = 79, [22396] = 80, [23200] = 53, [186072] = 10}
3 years ago
local chTotems = {[45114] = 243, [38368] = 102, [28523] = 87}
4 years ago
3 years ago
-- Keep track of who has riptide on them
local riptideData, earthshieldList = {}, {}
AuraHandler = function(unit, guid)
riptideData[guid] = unitHasAura(unit, Riptide) and true or nil
3 years ago
-- Currently, Glyph of Lesser Healing Wave + Any Earth Shield increase the healing not just the players own
if( unitHasAura(unit, EarthShield) ) then
earthshieldList[guid] = true
elseif( earthshieldList[guid] ) then
earthshieldList[guid] = nil
end
end
ResetChargeData = function(guid)
riptideData[guid] = guidToUnit[guid] and unitHasAura(guidToUnit[guid], Riptide) and true or nil
end
4 years ago
-- Lets a specific override on how many people this will hit
3 years ago
GetHealTargets = function(bitType, guid, spellID, amount)
local spellName = GetSpellInfo(spellID)
-- Glyph of Healing Wave, heals you for 20% of your heal when you heal someone else
if( glyphCache[55440] and guid ~= playerGUID and spellName == HealingWave ) then
if guidToUnit[guid] then
return string.format("%s,%d,%s,%d", compressGUID[guid], amount, compressGUID[playerGUID], amount * 0.20), -1
else
return compressGUID[UnitGUID("player")], amount * 0.20
end
end
3 years ago
return compressGUID[guid]
end
CalculateHotHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
local totalTicks
3 years ago
healModifier = healModifier * (1 + talentData[Purification].current)
3 years ago
-- Riptide
if( spellName == Riptide ) then
if( equippedSetCache["T9 Resto"] >= 2 ) then
spModifier = spModifier * 1.20
end
3 years ago
spellPower = spellPower * (hotData[spellName].coeff * 1.88)
spellPower = spellPower / hotData[spellName].ticks
healAmount = healAmount / hotData[spellName].ticks
3 years ago
totalTicks = hotData[spellName].ticks
-- Glyph of Riptide, +6 seconds
if( glyphCache[63273] ) then totalTicks = totalTicks + 2 end
3 years ago
-- Earthliving Weapon
elseif( spellName == Earthliving ) then
spellPower = (spellPower * (hotData[spellName].coeff * 1.88) * 0.45)
spellPower = spellPower / hotData[spellName].ticks
healAmount = healAmount / hotData[spellName].ticks
3 years ago
totalTicks = hotData[spellName].ticks
end
3 years ago
healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
return HOT_HEALS, healAmount, totalTicks, hotData[spellName].interval
4 years ago
end
CalculateHealing = function(guid, spellID, unit)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
3 years ago
if isTBC or isWrath then
4 years ago
healModifier = healModifier * (1 + talentData[Purification].current)
else
-- Purification only applies to base values in classic
healAmount = healAmount * (1 + talentData[Purification].current)
end
-- Chain Heal
if( spellName == ChainHeal ) then
3 years ago
spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
4 years ago
if( equippedSetCache["Skyshatter"] >= 4 ) then
healModifier = healModifier * 1.05
end
3 years ago
if( equippedSetCache["T7 Resto"] >= 4 ) then
healModifier = healModifier * 1.05
end
4 years ago
healModifier = healModifier * (1 + talentData[ImpChainHeal].current)
3 years ago
-- Add +25% from Riptide being up and reset the flag
if( riptideData[guid] ) then
healModifier = healModifier * 1.25
riptideData[guid] = nil
end
if playerCurrentRelic and chTotems[playerCurrentRelic] then
healAmount = healAmount + chTotems[playerCurrentRelic]
end
-- Healing Wave
4 years ago
elseif( spellName == HealingWave ) then
local hwStacks = select(3, unitHasAura(unit, 29203))
if( hwStacks ) then
healAmount = healAmount * ((hwStacks * 0.06) + 1)
end
3 years ago
if isWrath then
healModifier = healModifier * (1 + talentData[HealingWay].current)
end
if( equippedSetCache["T7 Resto"] >= 4 ) then
healModifier = healModifier * 1.05
end
4 years ago
local castTime = spellRank > 3 and 3 or spellRank == 3 and 2.5 or spellRank == 2 and 2 or 1.5
if playerCurrentRelic == 27544 then spellPower = spellPower + 88 end
3 years ago
spellPower = spellPower * ((castTime / 3.5) * (isWrath and 1.88 or 1) + talentData[TidalWaves].current)
4 years ago
-- Lesser Healing Wave
elseif( spellName == LesserHealingWave ) then
3 years ago
-- Glyph of Lesser Healing Wave, +20% healing on LHW if target has ES up
if( glyphCache[55438] and earthshieldList[guid] ) then
healModifier = healModifier * 1.20
end
4 years ago
spellPower = spellPower + (playerCurrentRelic and lhwTotems[playerCurrentRelic] or 0)
3 years ago
spellPower = spellPower * (spellData[spellName].coeff * (isWrath and 1.88 or 1) + (talentData[TidalWaves].current / 2))
4 years ago
end
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
-- Player has over a 100% chance to crit with Nature spells
if( GetSpellCritChance(4) >= 100 ) then
healAmount = healAmount * 1.50
end
-- Apply the final modifier of any MS or self heal increasing effects
return DIRECT_HEALS, ceil(healAmount)
end
end
end
if( playerClass == "HUNTER" ) then
LoadClassData = function()
local MendPet = GetSpellInfo(136)
3 years ago
if isTBC or isWrath then
hotData[MendPet] = { interval = 3, levels = { 12, 20, 28, 36, 44, 52, 60, 68, 74, 80 }, ticks = 5, averages = {125, 250, 450, 700, 1000, 1400, 1825, 2375, 4250, 5250 } }
4 years ago
else
spellData[MendPet] = { interval = 1, levels = { 12, 20, 28, 36, 44, 52, 60 }, ticks = 5, averages = {100, 190, 340, 515, 710, 945, 1225 } }
end
itemSetsData["Giantstalker"] = {16851, 16849, 16850, 16845, 16848, 16852, 16846, 16847}
3 years ago
GetHealTargets = function(bitType, guid, spellID)
4 years ago
local petGUID = UnitGUID("pet")
3 years ago
return petGUID and compressGUID[petGUID]
4 years ago
end
CalculateHotHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local amount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
if( equippedSetCache["Giantstalker"] >= 3 ) then amount = amount * 1.1 end
return HOT_HEALS, ceil(amount / hotData[spellName].ticks), hotData[spellName].ticks, hotData[spellName].interval
end
CalculateHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
if( equippedSetCache["Giantstalker"] >= 3 ) then healAmount = healAmount * 1.1 end
return CHANNEL_HEALS, ceil(healAmount / spellData[spellName].ticks), spellData[spellName].ticks, spellData[spellName].interval
end
end
end
3 years ago
--[[
-- While the groundwork is done, "CalculateHealing" needs to be theory crafted for classic / TBC / WotLK. Deactivating until someone steps up.
4 years ago
if( playerClass == "WARLOCK" ) then
LoadClassData = function()
local HealthFunnel = GetSpellInfo(755)
local DrainLife = GetSpellInfo(689)
local ImpHealthFunnel = GetSpellInfo(18703)
3 years ago
local ShadowMastery = GetSpellInfo(18271)
4 years ago
3 years ago
spellData[HealthFunnel] = {_isChanneled = true, coeff = 0.548, interval = 1, levels = { 12, 20, 28, 36, 44, 52, 60, 68, 76 }, ticks = 10, averages = { 120, 240, 430, 640, 890, 1190, 1530, 1880, 5200 } }
spellData[DrainLife] = {_isChanneled = true, coeff = 0.143, interval = 1, levels = { 14, 22, 30, 38, 46, 54, 62, 69, 78 }, ticks = 5, averages = { 10 * 5, 17 * 5, 29 * 5, 41 * 5, 55 * 5, 71 * 5, 87 * 5, 108 * 5, 133 * 5 } }
4 years ago
talentData[ImpHealthFunnel] = { mod = 0.1, current = 0 }
3 years ago
talentData[ShadowMastery] = { mod = 0.1, current = 0 }
4 years ago
3 years ago
GetHealTargets = function(bitType, guid, spellID)
local spellName = GetSpellInfo(spellID)
4 years ago
local petGUID = UnitGUID("pet")
3 years ago
if spellName == DrainLife then
return compressGUID[playerGUID]
else
return petGUID and compressGUID[petGUID]
end
4 years ago
end
CalculateHealing = function(guid, spellID)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
3 years ago
local spellPower = GetSpellBonusHealing()
local healModifier, spModifier = playerHealModifier, 1
if spellName == DrainLife then
healModifier = healModifier * (1 + talentData[ShadowMastery].current)
elseif spellName == HealthFunnel then
healModifier = healModifier * (1 + talentData[ImpHealthFunnel].current)
end
4 years ago
3 years ago
spellPower = spellPower * spellData[spellName].coeff
healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
4 years ago
return CHANNEL_HEALS, ceil(healAmount / spellData[spellName].ticks), spellData[spellName].ticks, spellData[spellName].interval
end
end
end
3 years ago
]]
4 years ago
-- Healing modifiers
if( not HealComm.aurasUpdated ) then
HealComm.aurasUpdated = true
HealComm.healingModifiers = nil
end
HealComm.currentModifiers = HealComm.currentModifiers or {}
-- The only spell in the game with a name conflict is Ray of Pain from the Nagrand Void Walkers
HealComm.healingModifiers = HealComm.healingModifiers or {
[28776] = 0.10, -- Necrotic Poison
[36693] = 0.55, -- Necrotic Poison
[46296] = 0.25, -- Necrotic Poison
[19716] = 0.25, -- Gehennas' Curse
[13737] = 0.50, -- Mortal Strike
[15708] = 0.50, -- Mortal Strike
[16856] = 0.50, -- Mortal Strike
[17547] = 0.50, -- Mortal Strike
[19643] = 0.50, -- Mortal Strike
[24573] = 0.50, -- Mortal Strike
[27580] = 0.50, -- Mortal Strike
[29572] = 0.50, -- Mortal Strike
[31911] = 0.50, -- Mortal Strike
[32736] = 0.50, -- Mortal Strike
[35054] = 0.50, -- Mortal Strike
[37335] = 0.50, -- Mortal Strike
[39171] = 0.50, -- Mortal Strike
[40220] = 0.50, -- Mortal Strike
[44268] = 0.50, -- Mortal Strike
[12294] = 0.50, -- Mortal Strike (Rank 1)
[21551] = 0.50, -- Mortal Strike (Rank 2)
[21552] = 0.50, -- Mortal Strike (Rank 3)
[21553] = 0.50, -- Mortal Strike (Rank 4)
[25248] = 0.50, -- Mortal Strike (Rank 5)
[30330] = 0.50, -- Mortal Strike (Rank 6)
[43441] = 0.50, -- Mortal Strike
[30843] = 0.00, -- Enfeeble
[19434] = 0.50, -- Aimed Shot (Rank 1)
[20900] = 0.50, -- Aimed Shot (Rank 2)
[20901] = 0.50, -- Aimed Shot (Rank 3)
[20902] = 0.50, -- Aimed Shot (Rank 4)
[20903] = 0.50, -- Aimed Shot (Rank 5)
[20904] = 0.50, -- Aimed Shot (Rank 6)
[27065] = 0.50, -- Aimed Shot (Rank 7)
[34625] = 0.25, -- Demolish
[35189] = 0.50, -- Solar Strike
[32315] = 0.50, -- Soul Strike
[32378] = 0.50, -- Filet
[36917] = 0.50, -- Magma-Thrower's Curse
[44534] = 0.50, -- Wretched Strike
[34366] = 0.75, -- Ebon Poison
[36023] = 0.50, -- Deathblow
[36054] = 0.50, -- Deathblow
[45885] = 0.50, -- Shadow Spike
[41292] = 0.00, -- Aura of Suffering
[40599] = 0.50, -- Arcing Smash
[9035] = 0.80, -- Hex of Weakness (Rank 1)
[19281] = 0.80, -- Hex of Weakness (Rank 2)
[19282] = 0.80, -- Hex of Weakness (Rank 3)
[19283] = 0.80, -- Hex of Weakness (Rank 4)
[19284] = 0.80, -- Hex of Weakness (Rank 5)
[25470] = 0.80, -- Hex of Weakness (Rank 6)
[34073] = 0.85, -- Curse of the Bleeding Hollow
[31306] = 0.25, -- Carrion Swarm
[44475] = 0.25, -- Magic Dampening Field
[23169] = 0.50, -- Brood Affliction: Green
[22859] = 0.50, -- Mortal Cleave
[38572] = 0.50, -- Mortal Cleave
[39595] = 0.50, -- Mortal Cleave
[45996] = 0.00, -- Darkness
[41350] = 2.00, -- Aura of Desire
[28176] = 1.20, -- Fel Armor
[7068] = 0.25, -- Veil of Shadow
[17820] = 0.25, -- Veil of Shadow
[22687] = 0.25, -- Veil of Shadow
[23224] = 0.25, -- Veil of Shadow
[24674] = 0.25, -- Veil of Shadow
[28440] = 0.25, -- Veil of Shadow
[13583] = 0.50, -- Curse of the Deadwood
[23230] = 0.50, -- Blood Fury
[31977] = 1.50, -- Curse of Infinity
}
3 years ago
if isWrath then
HealComm.healingModifiers[34123] = 1.06 -- Tree of Life
HealComm.healingModifiers[45237] = 1.03 -- Focused Will Rank 1
HealComm.healingModifiers[45241] = 1.04 -- Focused Will Rank 2
HealComm.healingModifiers[45242] = 1.05 -- Focused Will Rank 3
end
4 years ago
HealComm.healingStackMods = HealComm.healingStackMods or {
-- Mortal Wound
[25646] = function(stacks) return 1 - stacks * 0.10 end,
[28467] = function(stacks) return 1 - stacks * 0.10 end,
[30641] = function(stacks) return 1 - stacks * 0.05 end,
[31464] = function(stacks) return 1 - stacks * 0.10 end,
[36814] = function(stacks) return 1 - stacks * 0.10 end,
[38770] = function(stacks) return 1 - stacks * 0.05 end,
-- Dark Touched
[45347] = function(stacks) return 1 - stacks * 0.05 end,
-- Nether Portal - Dominance
[30423] = function(stacks) return 1 - stacks * 0.01 end,
-- Focused Will
[45242] = function(stacks) return 1 + stacks * 0.10 end,
}
3 years ago
if isTBC or isWrath then
4 years ago
HealComm.healingStackMods[13218] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 1)
HealComm.healingStackMods[13222] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 2)
HealComm.healingStackMods[13223] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 3)
HealComm.healingStackMods[13224] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 4)
HealComm.healingStackMods[27189] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 5)
end
local healingStackMods = HealComm.healingStackMods
local healingModifiers, currentModifiers = HealComm.healingModifiers, HealComm.currentModifiers
local distribution
local CTL = _G.ChatThrottleLib
local function sendMessage(msg)
if( distribution and strlen(msg) <= 240 ) then
if CTL then
CTL:SendAddonMessage("BULK", COMM_PREFIX, msg, distribution or 'GUILD')
end
end
end
-- Keep track of where all the data should be going
local instanceType
local function updateDistributionChannel()
if( instanceType == "pvp" or instanceType == "arena" ) then
distribution = "INSTANCE_CHAT"
elseif( IsInRaid() ) then
distribution = "RAID"
elseif( IsInGroup() ) then
distribution = "PARTY"
else
distribution = nil
end
end
-- Figure out where we should be sending messages and wipe some caches
function HealComm:PLAYER_ENTERING_WORLD()
HealComm.eventFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
HealComm:ZONE_CHANGED_NEW_AREA()
end
function HealComm:ZONE_CHANGED_NEW_AREA()
local pvpType = GetZonePVPInfo()
local instance = select(2, IsInInstance())
HealComm.zoneHealModifier = 1
if( pvpType == "combat" or instance == "arena" or instance == "pvp" ) then
HealComm.zoneHealModifier = 0.90
end
if( instance ~= instanceType ) then
instanceType = instance
updateDistributionChannel()
clearPendingHeals()
wipe(activeHots)
end
instanceType = instance
end
local alreadyAdded = {}
function HealComm:UNIT_AURA(unit)
local guid = UnitGUID(unit)
if( not guidToUnit[guid] ) then return end
local increase, decrease, playerIncrease, playerDecrease = 1, 1, 1, 1
-- Scan buffs
local id = 1
while( true ) do
local name, _, stack, _, _, _, _, _, _, spellID = UnitAura(unit, id, "HELPFUL")
if( not name ) then break end
-- Prevent buffs like Tree of Life that have the same name for the shapeshift/healing increase from being calculated twice
if( not alreadyAdded[name] ) then
alreadyAdded[name] = true
if( healingModifiers[spellID] ) then
increase = increase * healingModifiers[spellID]
3 years ago
4 years ago
elseif( healingStackMods[spellID] ) then
increase = increase * healingStackMods[spellID](stack)
end
end
id = id + 1
end
-- Scan debuffs
id = 1
while( true ) do
local name, _, stack, _, _, _, _, _, _, spellID = UnitAura(unit, id, "HARMFUL")
if( not name ) then break end
if( healingModifiers[spellID] ) then
decrease = min(decrease, healingModifiers[spellID])
elseif( healingStackMods[spellID] ) then
decrease = min(decrease, healingStackMods[spellID](stack))
end
id = id + 1
end
-- Check if modifier changed
local modifier = increase * decrease
if( modifier ~= currentModifiers[guid] ) then
if( currentModifiers[guid] or modifier ~= 1 ) then
currentModifiers[guid] = modifier
self.callbacks:Fire("HealComm_ModifierChanged", guid, modifier)
else
currentModifiers[guid] = modifier
end
end
wipe(alreadyAdded)
if( unit == "player" ) then
3 years ago
if unitHasAura("player", 10060) and not (isWrath or isTBC) then -- Power Infusion
4 years ago
playerIncrease = playerIncrease * 1.20
end
local npsStacks = select(3, unitHasAura("player", 30422)) -- Nether Portal - Serenity
if npsStacks then
playerIncrease = playerIncrease * (1 + npsStacks * 0.05)
end
if unitHasAura("player", 41406) then -- Dementia: +5%
playerIncrease = playerIncrease * 1.05
end
if unitHasAura("player", 41409) then -- Dementia: -5%
playerDecrease = playerDecrease * 0.95
end
if unitHasAura("player", 32346) then -- Stolen Soul
playerDecrease = playerDecrease * 0.50
end
if unitHasAura("player", 40099) then -- Vile Slime
playerDecrease = playerDecrease * 0.50
end
if unitHasAura("player", 38246) then -- Vile Sludge
playerDecrease = playerDecrease * 0.50
end
if unitHasAura("player", 45573) then -- Vile Sludge
playerDecrease = playerDecrease * 0.50
end
playerHealModifier = playerIncrease * playerDecrease
end
-- Class has a specific monitor it needs for auras
if( AuraHandler ) then
AuraHandler(unit, guid)
end
end
3 years ago
-- Monitor glyph changes
function HealComm:GlyphsUpdated(id)
local spellID = glyphCache[id]
3 years ago
-- Invalidate the old cache value
if( spellID ) then
glyphCache[spellID] = nil
glyphCache[id] = nil
end
3 years ago
-- Cache the new one if any
local enabled, _, glyphID = GetGlyphSocketInfo(id)
if( enabled and glyphID ) then
glyphCache[glyphID] = true
glyphCache[id] = glyphID
end
end
HealComm.GLYPH_ADDED = HealComm.GlyphsUpdated
HealComm.GLYPH_REMOVED = HealComm.GlyphsUpdated
HealComm.GLYPH_UPDATED = HealComm.GlyphsUpdated
4 years ago
function HealComm:PLAYER_LEVEL_UP(level)
playerLevel = tonumber(level) or UnitLevel("player")
end
-- Cache player talent data for spells we need
function HealComm:CHARACTER_POINTS_CHANGED()
for tabIndex=1, GetNumTalentTabs() do
for i=1, GetNumTalents(tabIndex) do
local name, _, _, _, spent = GetTalentInfo(tabIndex, i)
if( name and talentData[name] ) then
talentData[name].current = talentData[name].mod * spent
talentData[name].spent = spent
end
end
end
end
-- Save the currently equipped range weapon
local RANGED_SLOT = GetInventorySlotInfo("RangedSlot")
function HealComm:PLAYER_EQUIPMENT_CHANGED()
-- Caches set bonus info, as you can't reequip set bonus gear in combat no sense in checking it
if( not InCombatLockdown() ) then
for name, items in pairs(itemSetsData) do
equippedSetCache[name] = 0
for _, itemID in pairs(items) do
if( IsEquippedItem(itemID) ) then
equippedSetCache[name] = equippedSetCache[name] + 1
end
end
end
end
-- Check relic
local relic = GetInventoryItemLink("player", RANGED_SLOT)
playerCurrentRelic = relic and tonumber(strmatch(relic, "item:(%d+):")) or nil
end
-- COMM CODE
local function loadHealAmount(...)
local tbl = HealComm:RetrieveTable()
for i=1, select("#", ...) do
tbl[i] = tonumber((select(i, ...)))
end
return tbl
end
-- Direct heal started
local function loadHealList(pending, amount, stack, endTime, ticksLeft, ...)
wipe(tempPlayerList)
-- For the sake of consistency, even a heal doesn't have multiple end times like a hot, it'll be treated as such in the DB
if( amount ~= -1 and amount ~= "-1" ) then
amount = not pending.hasVariableTicks and amount or loadHealAmount(strsplit("@", amount))
for i=1, select("#", ...) do
local guid = select(i, ...)
local decompGUID = guid and decompressGUID[guid]
if( decompGUID ) then
updateRecord(pending, decompGUID, amount, stack, endTime, ticksLeft)
tinsert(tempPlayerList, decompGUID)
end
end
else
for i = 1, select("#", ...), 2 do
local guid = select(i, ...)
local decompGUID = guid and decompressGUID[guid]
3 years ago
amount = not pending.hasVariableTicks and tonumber((select(i + 1, ...))) or loadHealAmount(string.split("@", amount))
4 years ago
if( decompGUID and amount ) then
updateRecord(pending, decompGUID, amount, stack, endTime, ticksLeft)
tinsert(tempPlayerList, decompGUID)
end
end
end
end
local function parseDirectHeal(casterGUID, spellID, amount, castTime, ...)
local spellName = GetSpellInfo(spellID)
local unit = guidToUnit[casterGUID]
if( not unit or not spellName or not amount or select("#", ...) == 0 ) then return end
local endTime
if unit == "player" then
endTime = select(5, CastingInfo())
if not endTime then return end
endTime = endTime / 1000
else
endTime = GetTime() + (castTime or 1.5)
end
pendingHeals[casterGUID] = pendingHeals[casterGUID] or {}
pendingHeals[casterGUID][spellName] = pendingHeals[casterGUID][spellName] or {}
local pending = pendingHeals[casterGUID][spellName]
wipe(pending)
pending.endTime = endTime
pending.spellID = spellID
pending.bitType = DIRECT_HEALS
loadHealList(pending, amount, 1, pending.endTime, nil, ...)
HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end
HealComm.parseDirectHeal = parseDirectHeal
-- Channeled heal started
local function parseChannelHeal(casterGUID, spellID, amount, totalTicks, ...)
local spellName = GetSpellInfo(spellID)
local unit = guidToUnit[casterGUID]
if( not unit or not spellName or not totalTicks or not amount or select("#", ...) == 0 ) then return end
local tickInterval = spellName == GetSpellInfo(740) and 2 or 1
local startTime, endTime
if unit == "player" then
startTime, endTime = select(4, ChannelInfo())
if not startTime then return end
startTime = startTime / 1000
endTime = endTime / 1000
else
startTime = GetTime()
endTime = startTime + totalTicks * tickInterval
end
pendingHots[casterGUID] = pendingHots[casterGUID] or {}
pendingHots[casterGUID][spellName] = pendingHots[casterGUID][spellName] or {}
local inc = amount == -1 and 2 or 1
local pending = pendingHots[casterGUID][spellName]
wipe(pending)
pending.startTime = startTime
pending.endTime = endTime
pending.duration = endTime - startTime
pending.totalTicks = totalTicks
pending.tickInterval = pending.duration / totalTicks
pending.spellID = spellID
pending.isMultiTarget = (select("#", ...) / inc) > 1
pending.bitType = CHANNEL_HEALS
local ticksLeft = ceil((endTime - GetTime()) / pending.tickInterval)
3 years ago
loadHealList(pending, amount, 1, endTime, ticksLeft, ...)
4 years ago
HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end
-- Hot heal started
-- When the person is within visible range of us, the aura is available by the time the message reaches the target
-- as such, we can rely that at least one person is going to have the aura data on them (and that it won't be different, at least for this cast)
local function findAura(casterGUID, spellID, ...)
for i = 1, select("#", ...) do
local guid = decompressGUID[select(i, ...)]
local unit = guid and guidToUnit[guid]
if( unit and UnitIsVisible(unit) ) then
local id = 1
while true do
local name, _, stack, _, duration, endTime, caster, _, _, spell = UnitAura(unit, id, 'HELPFUL')
if( not spell ) then break end
if( spell == spellID and caster and UnitGUID(caster) == casterGUID ) then
return (stack and stack > 0 and stack or 1), duration or 0, endTime or 0
end
id = id + 1
end
end
end
end
local function parseHotHeal(casterGUID, wasUpdated, spellID, tickAmount, totalTicks, tickInterval, ...)
local spellName = GetSpellInfo(spellID)
-- If the user is on 3.3, then anything without a total ticks attached to it is rejected
if( not tickAmount or not spellName or select("#", ...) == 0 ) then return end
3 years ago
if type(tickAmount) == "table" then
tickAmount = table.concat(tickAmount, "@")
end
4 years ago
-- Retrieve the hot information
local inc = 2
local stack, duration, endTime = findAura(casterGUID, spellID, ...)
if not ( tickAmount == -1 or tickAmount == "-1" ) then
inc = 1
3 years ago
duration = totalTicks * (tickInterval or 1)
4 years ago
endTime = GetTime() + duration
end
if( not stack or stack == 0 or duration == 0 or endTime == 0 ) then return end
pendingHots[casterGUID] = pendingHots[casterGUID] or {}
pendingHots[casterGUID][spellName] = pendingHots[casterGUID][spellName] or {}
local pending = pendingHots[casterGUID][spellName]
pending.duration = duration
pending.endTime = endTime
pending.stack = stack
pending.totalTicks = totalTicks or duration / tickInterval
pending.tickInterval = totalTicks and duration / totalTicks or tickInterval
pending.spellID = spellID
pending.hasVariableTicks = type(tickAmount) == "string"
pending.isMutliTarget = (select("#", ...) / inc) > 1
pending.bitType = HOT_HEALS
-- As you can't rely on a hot being the absolutely only one up, have to apply the total amount now :<
local ticksLeft = ceil((endTime - GetTime()) / pending.tickInterval)
loadHealList(pending, tickAmount, stack, endTime, ticksLeft, ...)
if( not wasUpdated ) then
HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, endTime, unpack(tempPlayerList))
else
HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, spellID, pending.bitType, endTime, unpack(tempPlayerList))
end
end
local function parseHotBomb(casterGUID, wasUpdated, spellID, amount, ...)
local spellName, spellRank = GetSpellInfo(spellID)
if( not amount or not spellName or select("#", ...) == 0 ) then return end
-- If we don't have a pending hot then there is no bomb as far as were concerned
local hotPending = pendingHots[casterGUID] and pendingHots[casterGUID][spellName]
if( not hotPending or not hotPending.bitType ) then return end
hotPending.hasBomb = true
pendingHeals[casterGUID] = pendingHeals[casterGUID] or {}
pendingHeals[casterGUID][spellName] = pendingHeals[casterGUID][spellName] or {}
local pending = pendingHeals[casterGUID][spellName]
pending.endTime = hotPending.endTime
pending.spellID = spellID
pending.bitType = BOMB_HEALS
3 years ago
pending.stack = isWrath and hotPending.stack or 1 -- TBC Lifebloom bomb heal does not stack
4 years ago
loadHealList(pending, amount, pending.stack, pending.endTime, nil, ...)
if( not wasUpdated ) then
HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
else
HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end
end
-- Heal finished
local function parseHealEnd(casterGUID, pending, checkField, spellID, interrupted, ...)
local spellName = GetSpellInfo(spellID)
if( not spellName or not casterGUID ) then return end
if( not pending ) then
if pendingHeals[casterGUID] then
pending = pendingHeals[casterGUID][spellName]
end
if (not pending) and pendingHots[casterGUID] then
pending = pendingHots[casterGUID][spellName]
end
end
if( not pending or not pending.bitType ) then return end
wipe(tempPlayerList)
if( select("#", ...) == 0 ) then
for i=#(pending), 1, -5 do
tinsert(tempPlayerList, pending[i - 4])
removeRecord(pending, pending[i - 4])
end
else
for i=1, select("#", ...) do
local guid = decompressGUID[select(i, ...)]
if guid then
tinsert(tempPlayerList, guid)
removeRecord(pending, guid)
end
end
end
-- Double check and make sure we actually removed at least one person
if( #(tempPlayerList) == 0 ) then return end
-- Heals that also have a bomb associated to them have to end at this point, they will fire there own callback too
local bombPending = pending.hasBomb and pendingHeals[casterGUID][spellName]
if( bombPending and bombPending.bitType ) then
parseHealEnd(casterGUID, bombPending, "name", spellID, interrupted, ...)
end
local bitType = pending.bitType
-- Clear data if we're done
if( #(pending) == 0 ) then wipe(pending) end
HealComm.callbacks:Fire("HealComm_HealStopped", casterGUID, spellID, bitType, interrupted, unpack(tempPlayerList))
end
HealComm.parseHealEnd = parseHealEnd
-- Heal delayed
local function parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
local spellName = GetSpellInfo(spellID)
local startTime = startTimeRelative + GetTime()
local endTime = endTimeRelative + GetTime()
if not casterGUID then return end
local pending = (pendingHeals[casterGUID][spellName] or pendingHots[casterGUID][spellName])
-- It's possible to get duplicate interrupted due to raid1 = party1, player = raid# etc etc, just block it here
if( pending.endTime == endTime and pending.startTime == startTime ) then return end
-- Casted heal
if( pending.bitType == DIRECT_HEALS ) then
pending.startTime = startTime
pending.endTime = endTime
-- Channel heal
elseif( pending.bitType == CHANNEL_HEALS ) then
pending.startTime = startTime
pending.endTime = endTime
pending.duration = endTime - startTime
pending.tickInterval = pending.duration / pending.totalTicks
else
return
end
wipe(tempPlayerList)
for i=1, #(pending), 5 do
pending[i + 3] = endTime
tinsert(tempPlayerList, pending[i])
end
HealComm.callbacks:Fire("HealComm_HealDelayed", casterGUID, pending.spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end
-- After checking around 150-200 messages in battlegrounds, server seems to always be passed (if they are from another server)
-- Channels use tick total because the tick interval varies by haste
-- Hots use tick interval because the total duration varies but the tick interval stays the same
function HealComm:CHAT_MSG_ADDON(prefix, message, channel, sender)
if( prefix ~= COMM_PREFIX or channel ~= distribution ) then return end
local commType, extraArg, spellID, arg1, arg2, arg3, arg4, arg5, arg6 = strsplit(":", message)
local casterGUID = UnitGUID(Ambiguate(sender, "none"))
spellID = tonumber(spellID)
if( not commType or not spellID or not casterGUID or casterGUID == playerGUID) then return end
-- New direct heal - D:<extra>:<spellID>:<amount>:target1,target2...
if( commType == "D" and arg1 and arg2 ) then
parseDirectHeal(casterGUID, spellID, tonumber(arg1), tonumber(extraArg), strsplit(",", arg2))
-- New channel heal - C:<extra>:<spellID>:<amount>:<totalTicks>:target1,target2...
elseif( commType == "C" and arg1 and arg3 ) then
3 years ago
-- Penance heals its first tick instantly and by now has already happened
4 years ago
parseChannelHeal(casterGUID, spellID, tonumber(arg1), tonumber(arg2), strsplit(",", arg3))
-- New hot with a "bomb" component - B:<totalTicks>:<spellID>:<bombAmount>:target1,target2:<amount>:<isMulti>:<tickInterval>:target1,target2...
elseif( commType == "B" and arg1 and arg6 ) then
parseHotHeal(casterGUID, false, spellID, tonumber(arg3), tonumber(extraArg), tonumber(arg5), strsplit(",", arg6))
parseHotBomb(casterGUID, false, spellID, tonumber(arg1), strsplit(",", arg2))
-- New hot - H:<totalTicks>:<spellID>:<amount>:<isMulti>:<tickInterval>:target1,target2...
elseif( commType == "H" and arg1 and arg4 ) then
parseHotHeal(casterGUID, false, spellID, tonumber(arg1), tonumber(extraArg), tonumber(arg3), strsplit(",", arg4))
-- New updated heal somehow before ending - U:<totalTicks>:<spellID>:<amount>:<tickInterval>:target1,target2...
elseif( commType == "U" and arg1 and arg3 ) then
parseHotHeal(casterGUID, true, spellID, tonumber(arg1), tonumber(extraArg), tonumber(arg2), strsplit(",", arg3))
3 years ago
-- New variable tick hot - VH::<spellID>:<amount>:<isMulti>:<tickInterval>:target1,target2...
elseif( commType == "VH" and arg1 and arg4 ) then
parseHotHeal(casterGUID, false, spellID, arg1, tonumber(arg3), tonumber(extraArg), string.split(",", arg4))
-- New updated variable tick hot - U::<spellID>:amount1@amount2@amount3:<tickTotal>:target1,target2...
elseif( commType == "VU" and arg1 and arg3 ) then
parseHotHeal(casterGUID, true, spellID, arg1, tonumber(arg2), tonumber(extraArg), string.split(",", arg3))
4 years ago
-- New updated bomb hot - UB:<totalTicks>:<spellID>:<bombAmount>:target1,target2:<amount>:<tickInterval>:target1,target2...
elseif( commType == "UB" and arg1 and arg5 ) then
parseHotHeal(casterGUID, true, spellID, tonumber(arg3), tonumber(extraArg), tonumber(arg4), strsplit(",", arg5))
parseHotBomb(casterGUID, true, spellID, tonumber(arg1), strsplit(",", arg2))
-- Heal stopped - S:<extra>:<spellID>:<ended early: 0/1>:target1,target2...
elseif( commType == "S" or commType == "HS" ) then
local interrupted = arg1 == "1" and true or false
local checkType = commType == "HS" and "id" or "name"
local pending = commType == "HS" and pendingHots[casterGUID] and pendingHots[casterGUID][GetSpellInfo(spellID)]
if( arg2 and arg2 ~= "" ) then
parseHealEnd(casterGUID, pending, checkType, spellID, interrupted, strsplit(",", arg2))
else
parseHealEnd(casterGUID, pending, checkType, spellID, interrupted)
end
elseif commType == "F" then
parseHealDelayed(casterGUID, tonumber(arg1), tonumber(arg2), spellID)
end
end
-- Bucketing reduces the number of events triggered for heals such as Tranquility that hit multiple targets
-- instead of firing 5 events * ticks it will fire 1 (maybe 2 depending on lag) events
HealComm.bucketHeals = HealComm.bucketHeals or {}
local bucketHeals = HealComm.bucketHeals
local BUCKET_FILLED = 0.30
HealComm.bucketFrame = HealComm.bucketFrame or CreateFrame("Frame")
HealComm.bucketFrame:Hide()
HealComm.bucketFrame:SetScript("OnUpdate", function(self, elapsed)
local totalLeft = 0
for casterGUID, spells in pairs(bucketHeals) do
for _, data in pairs(spells) do
if( data.timeout ) then
data.timeout = data.timeout - elapsed
if( data.timeout <= 0 ) then
-- This shouldn't happen, on the offhand chance it does then don't bother sending an event
if( #(data) == 0 or not data.spellID or not data.spellName ) then
wipe(data)
-- We're doing a bucket for a tick heal like Tranquility or Wild Growth
elseif( data.type == "tick" ) then
local pending = pendingHots[casterGUID] and pendingHots[casterGUID][data.spellName]
if( pending and pending.bitType ) then
local endTime = select(3, getRecord(pending, data[1]))
HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, pending.spellID, pending.bitType, endTime, unpack(data))
end
wipe(data)
-- We're doing a bucket for a cast thats a multi-target heal like Wild Growth or Prayer of Healing
elseif( data.type == "heal" ) then
3 years ago
local bitType, amount, totalTicks, tickInterval, _, hasVariableTicks = CalculateHotHealing(data[1], data.spellID)
4 years ago
if( bitType ) then
3 years ago
local targets = GetHealTargets(bitType, data[1], data.spellID, data)
parseHotHeal(playerGUID, false, data.spellID, amount, totalTicks, tickInterval, strsplit(",", targets))
if( not hasVariableTicks ) then
sendMessage(format("H:%d:%d:%d::%d:%s", totalTicks, data.spellID, amount, tickInterval, targets))
else
sendMessage(format("VH:%d:%d:%s::%d:%s", tickInterval, data.spellID, table.concat(amount, "@"), totalTicks, targets))
end
4 years ago
end
wipe(data)
end
else
totalLeft = totalLeft + 1
end
end
end
end
if( totalLeft <= 0 ) then
self:Hide()
end
end)
-- Monitor aura changes as well as new hots being cast
local eventRegistered = {
SPELL_HEAL = true,
SPELL_PERIODIC_HEAL = true,
SPELL_AURA_REMOVED = true,
SPELL_AURA_APPLIED = true,
SPELL_AURA_REFRESH = true,
SPELL_AURA_APPLIED_DOSE = true,
SPELL_AURA_REMOVED_DOSE = true,
}
function HealComm:COMBAT_LOG_EVENT_UNFILTERED(...)
local timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = ...
if( not eventRegistered[eventType] ) then return end
local _, spellName = select(12, ...)
local destUnit = guidToUnit[destGUID]
local spellID = destUnit and select(10, unitHasAura(destUnit, spellName)) or select(7, GetSpellInfo(spellName))
-- Heal or hot ticked that the library is tracking
-- It's more efficient/accurate to have the library keep track of this locally, spamming the comm channel would not be a very good thing especially when a single player can have 4 - 8 hots/channels going on them.
if( eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" ) then
local pending = sourceGUID and pendingHots[sourceGUID] and pendingHots[sourceGUID][spellName]
if( pending and pending[destGUID] and pending.bitType and bit.band(pending.bitType, OVERTIME_HEALS) > 0 ) then
local amount, stack, _, ticksLeft = getRecord(pending, destGUID)
ticksLeft = ticksLeft - 1
local endTime = GetTime() + pending.tickInterval * ticksLeft
updateRecord(pending, destGUID, amount, stack, endTime, ticksLeft)
if( pending.isMultiTarget and sourceGUID ) then
4 years ago
bucketHeals[sourceGUID] = bucketHeals[sourceGUID] or {}
bucketHeals[sourceGUID][spellName] = bucketHeals[sourceGUID][spellName] or {}
4 years ago
local spellBucket = bucketHeals[sourceGUID][spellName]
4 years ago
if( not spellBucket[destGUID] ) then
spellBucket.timeout = BUCKET_FILLED
spellBucket.type = "tick"
spellBucket.spellName = spellName
spellBucket.spellID = spellID
spellBucket[destGUID] = true
tinsert(spellBucket, destGUID)
self.bucketFrame:Show()
end
else
HealComm.callbacks:Fire("HealComm_HealUpdated", sourceGUID, spellID, pending.bitType, endTime, destGUID)
end
end
-- New hot was applied
elseif( ( eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" or eventType == "SPELL_AURA_APPLIED_DOSE" ) and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
if( hotData[spellName] ) then
-- Single target so we can just send it off now thankfully
3 years ago
local bitType, amount, totalTicks, tickInterval, bombAmount, hasVariableTicks = CalculateHotHealing(destGUID, spellID)
4 years ago
if( bitType ) then
3 years ago
local targets = GetHealTargets(type, destGUID, spellID)
4 years ago
if targets then
3 years ago
parseHotHeal(sourceGUID, false, spellID, amount, totalTicks, tickInterval, strsplit(",", targets))
4 years ago
-- Hot with a bomb!
if( bombAmount ) then
3 years ago
local bombTargets = GetHealTargets(BOMB_HEALS, destGUID, spellName)
4 years ago
parseHotBomb(sourceGUID, false, spellID, bombAmount, strsplit(",", bombTargets))
sendMessage(format("B:%d:%d:%d:%s:%d::%d:%s", totalTicks, spellID, bombAmount, bombTargets, amount, tickInterval, targets))
3 years ago
elseif( hasVariableTicks ) then
sendMessage(format("VH:%d:%d:%s::%d:%s", tickInterval, spellID, table.concat(amount, "@"), totalTicks, targets))
4 years ago
else
sendMessage(format("H:%d:%d:%d::%d:%s", totalTicks, spellID, amount, tickInterval, targets))
end
end
end
end
-- Single stack of a hot was removed, this only applies when going from 2 -> 1, when it goes from 1 -> 0 it fires SPELL_AURA_REMOVED
elseif( eventType == "SPELL_AURA_REMOVED_DOSE" and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
local pending = sourceGUID and pendingHeals[sourceGUID] and pendingHeals[sourceGUID][spellID]
if( pending and pending.bitType ) then
local amount = getRecord(pending, destGUID)
if( amount ) then
parseHotHeal(sourceGUID, true, spellID, amount, pending.totalTicks, pending.tickInterval, compressGUID[destGUID])
-- Plant the bomb
local bombPending = pending.hasBomb and pendingHeals[sourceGUID][spellName]
if( bombPending and bombPending.bitType ) then
local bombAmount = getRecord(bombPending, destGUID)
if( bombAmount ) then
parseHotBomb(sourceGUID, true, spellID, bombAmount, compressGUID[destGUID])
sendMessage(format("UB:%s:%d:%d:%s:%d:%d:%s", pending.totalTicks, spellID, bombAmount, compressGUID[destGUID], amount, pending.tickInterval, compressGUID[destGUID]))
return
end
end
3 years ago
if( pending.hasVariableTicks ) then
sendMessage(format("VU:%d:%d:%s:%d:%s", pending.tickInterval, spellID, amount, pending.totalTicks, compressGUID[destGUID]))
else
sendMessage(format("U:%s:%d:%d:%d:%s", spellID, amount, pending.totalTicks, pending.tickInterval, compressGUID[destGUID]))
end
4 years ago
end
end
-- Aura faded
elseif( eventType == "SPELL_AURA_REMOVED" and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
if compressGUID[destGUID] then
-- Hot faded that we cast
local pending = pendingHots[playerGUID] and pendingHots[playerGUID][spellName]
if hotData[spellName] then
parseHealEnd(sourceGUID, pending, "id", spellID, false, compressGUID[destGUID])
sendMessage(format("HS::%d::%s", spellID, compressGUID[destGUID]))
elseif spellData[spellName] and spellData[spellName]._isChanneled then
parseHealEnd(sourceGUID, pending, "id", spellID, false, compressGUID[destGUID])
sendMessage(format("S::%d:0:%s", spellID, compressGUID[destGUID]))
end
end
end
end
-- Spell cast magic
-- When auto self cast is on, the UNIT_SPELLCAST_SENT event will always come first followed by the funciton calls
-- Otherwise either SENT comes first then function calls, or some function calls then SENT then more function calls
local castTarget, mouseoverGUID, mouseoverName, hadTargetingCursor, lastSentID, lastTargetGUID, lastTargetName
local lastFriendlyGUID, lastFriendlyName, lastGUID, lastName, lastIsFriend
local castGUIDs, guidPriorities = {}, {}
-- Deals with the fact that functions are called differently
-- Why a table when you can only cast one spell at a time you ask? When you factor in lag and mash clicking it's possible to:
-- cast A, interrupt it, cast B and have A fire SUCEEDED before B does, the tables keeps it from bugging out
local function setCastData(priority, name, guid)
if( not guid or not lastSentID ) then return end
if( guidPriorities[lastSentID] and guidPriorities[lastSentID] >= priority ) then return end
-- This is meant as a way of locking a cast in because which function has accurate data can be called into question at times, one of them always does though
-- this means that as soon as it finds a name match it locks the GUID in until another SENT is fired. Technically it's possible to get a bad GUID but it first requires
-- the functions to return different data and it requires the messed up call to be for another name conflict.
if( castTarget and castTarget == name ) then priority = 99 end
castGUIDs[lastSentID] = guid
guidPriorities[lastSentID] = priority
end
3 years ago
-- Penance sends different spellIDs on UNIT_SPELLCAST_SENT and UNIT_SPELLCAST_START. Reported on beta / remove this once fixed.
local penanceIDs = {
[47540] = 47757,
[53005] = 52986,
[53006] = 52987,
[53007] = 52988,
}
4 years ago
-- When the game tries to figure out the UnitID from the name it will prioritize players over non-players
-- if there are conflicts in names it will pull the one with the least amount of current health
function HealComm:UNIT_SPELLCAST_SENT(unit, targetName, castGUID, spellID)
local spellName = GetSpellInfo(spellID)
if(unit ~= "player") then return end
if hotData[spellName] or spellData[spellName] then
targetName = targetName or UnitName("player")
castTarget = gsub(targetName, "(.-)%-(.*)$", "%1")
3 years ago
lastSentID = penanceIDs[spellID] or spellID
4 years ago
-- Self cast is off which means it's possible to have a spell waiting for a target.
-- It's possible that it's the mouseover unit, but if a Target, TargetLast or AssistUnit call comes right after it means it's casting on that instead instead.
if( hadTargetingCursor ) then
hadTargetingCursor = nil
self.resetFrame:Show()
guidPriorities[lastSentID] = nil
setCastData(5, mouseoverName, mouseoverGUID)
else
-- If the player is ungrouped and healing, you can't take advantage of the name -> "unit" map, look in the UnitIDs that would most likely contain the information that's needed.
local guid = UnitGUID(targetName)
if( not guid ) then
guid = UnitName("target") == castTarget and UnitGUID("target") or UnitName("focus") == castTarget and UnitGUID("focus") or UnitName("mouseover") == castTarget and UnitGUID("mouseover") or UnitName("targettarget") == castTarget and UnitGUID("target") or UnitName("focustarget") == castTarget and UnitGUID("focustarget")
end
guidPriorities[lastSentID] = nil
setCastData(0, nil, guid)
end
end
end
3 years ago
local PlayerTargetSpells = {
[GetSpellInfo(689)] = true, -- Drain Life
[GetSpellInfo(740)] = true, -- Tranquility
[GetSpellInfo(331)] = true, -- Healing Wave
}
if isTBC or isWrath then
PlayerTargetSpells[GetSpellInfo(32546)] = true -- Binding Heal
end
4 years ago
function HealComm:UNIT_SPELLCAST_START(unit, cast, spellID)
if( unit ~= "player") then return end
local spellName = GetSpellInfo(spellID)
4 years ago
if (not spellData[spellName] or UnitIsCharmed("player") or not UnitPlayerControlled("player") ) then return end
local castGUID = castGUIDs[spellID]
local castUnit = guidToUnit[castGUID]
3 years ago
if (isTBC or isWrath) and not castUnit and PlayerTargetSpells[spellName] then
4 years ago
castGUID = UnitGUID("player")
castUnit = "player"
3 years ago
elseif glyphCache[55440] and not castUnit and PlayerTargetSpells[spellName] then -- Glyph of Healing Wave
castUnit = unit
4 years ago
end
if( not castGUID or not castUnit ) then
return
end
-- Figure out who we are healing and for how much
local bitType, amount, ticks, tickInterval = CalculateHealing(castGUID, spellID, castUnit)
if not amount then return end
3 years ago
local targets, fakeAmount = GetHealTargets(bitType, castGUID, spellID, amount)
amount = fakeAmount or amount
4 years ago
if not targets then return end -- only here until I compress/decompress npcs
if( bitType == DIRECT_HEALS ) then
local startTime, endTime = select(4, CastingInfo())
3 years ago
parseDirectHeal(playerGUID, spellID, amount, (endTime - startTime) / 1000, strsplit(",", targets))
sendMessage(format("D:%.3f:%d:%d:%s", (endTime - startTime) / 1000, spellID or 0, amount or "", targets))
4 years ago
elseif( bitType == CHANNEL_HEALS ) then
3 years ago
parseChannelHeal(playerGUID, spellID, amount, ticks, string.split(",", targets))
if spellName == GetSpellInfo(740) or spellName == GetSpellInfo(746) then
sendMessage(string.format("C::%d:%d:%s:%s", spellID, amount, ticks, targets))
else
-- Penance has its first tick already done by the time this arrives
sendMessage(string.format("C::%d:%d:%s:%s", spellID, amount, ticks - 1, targets))
end
4 years ago
end
end
HealComm.UNIT_SPELLCAST_CHANNEL_START = HealComm.UNIT_SPELLCAST_START
local spellCastSucceeded = {}
local function hasNS()
local i=1
repeat
local spellID = select(10, UnitBuff("player", i))
if spellID == 17116 or spellID == 16188 then
return true
end
i = i + 1
until not spellID
end
function HealComm:UNIT_SPELLCAST_SUCCEEDED(unit, cast, spellID)
if( unit ~= "player") then return end
local spellName = GetSpellInfo(spellID)
if spellID == 20216 then
hasDivineFavor = true
end
if spellData[spellName] and not spellData[spellName]._isChanneled and not hasNS() then
hasDivineFavor = nil
parseHealEnd(playerGUID, nil, "name", spellID, false)
sendMessage(format("S::%d:0", spellID or 0))
spellCastSucceeded[spellID] = true
elseif spellName == GetSpellInfo(20473) then -- Holy Shock
hasDivineFavor = nil
end
end
function HealComm:UNIT_SPELLCAST_STOP(unit, castGUID, spellID)
local spellName = GetSpellInfo(spellID)
if( unit ~= "player" or not spellData[spellName] or spellData[spellName]._isChanneled ) then return end
if not spellCastSucceeded[spellID] then
parseHealEnd(playerGUID, nil, "name", spellID, true)
sendMessage(format("S::%d:1", spellID or 0))
end
spellCastSucceeded[spellID] = nil
end
-- Cast didn't go through, recheck any charge data if necessary
function HealComm:UNIT_SPELLCAST_INTERRUPTED(unit, castGUID, spellID)
local spellName = GetSpellInfo(spellID)
if( unit ~= "player" or not spellData[spellName] ) then return end
local guid = castGUIDs[spellID]
if( guid ) then
ResetChargeData(guid, spellID)
end
end
function HealComm:UNIT_SPELLCAST_DELAYED(unit, castGUID, spellID)
local spellName = GetSpellInfo(spellID)
local casterGUID = UnitGUID(unit)
if( unit ~= "player" or not pendingHeals[casterGUID] or not pendingHeals[casterGUID][spellName] ) then return end
-- Direct heal delayed
if( pendingHeals[casterGUID][spellName].bitType == DIRECT_HEALS ) then
local startTime, endTime = select(4, CastingInfo())
if( startTime and endTime ) then
local startTimeRelative = startTime / 1000 - GetTime()
local endTimeRelative = endTime / 1000 - GetTime()
parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
sendMessage(format("F::%d:%.3f:%.3f", spellID, startTimeRelative, endTimeRelative))
end
-- Channel heal delayed
elseif( pendingHeals[casterGUID][spellName].bitType == CHANNEL_HEALS ) then
local startTime, endTime = select(4, ChannelInfo())
if( startTime and endTime ) then
local startTimeRelative = startTime / 1000 - GetTime()
local endTimeRelative = endTime / 1000 - GetTime()
parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
sendMessage(format("F::%d:%.3f:%.3f", spellID, startTimeRelative, endTimeRelative))
end
end
end
HealComm.UNIT_SPELLCAST_CHANNEL_UPDATE = HealComm.UNIT_SPELLCAST_DELAYED
-- Need to keep track of mouseover as it can change in the split second after/before casts
function HealComm:UPDATE_MOUSEOVER_UNIT()
mouseoverGUID = UnitCanAssist("player", "mouseover") and UnitGUID("mouseover")
mouseoverName = UnitCanAssist("player", "mouseover") and UnitName("mouseover")
end
-- Keep track of our last target/friendly target for the sake of /targetlast and /targetlastfriend
function HealComm:PLAYER_TARGET_CHANGED()
if( lastGUID and lastName ) then
if( lastIsFriend ) then
lastFriendlyGUID, lastFriendlyName = lastGUID, lastName
end
lastTargetGUID, lastTargetName = lastGUID, lastName
end
-- Despite the fact that it's called target last friend, UnitIsFriend won't actually work
lastGUID = UnitGUID("target")
lastName = UnitName("target")
lastIsFriend = UnitCanAssist("player", "target")
end
-- Unit was targeted through a function
function HealComm:Target(unit)
if( self.resetFrame:IsShown() and UnitCanAssist("player", unit) ) then
setCastData(6, UnitName(unit), UnitGUID(unit))
end
self.resetFrame:Hide()
hadTargetingCursor = nil
end
-- This is only needed when auto self cast is off, in which case this is called right after UNIT_SPELLCAST_SENT
-- because the player got a waiting-for-cast icon up and they pressed a key binding to target someone
HealComm.TargetUnit = HealComm.Target
-- Works the same as the above except it's called when you have a cursor icon and you click on a secure frame with a target attribute set
HealComm.SpellTargetUnit = HealComm.Target
-- Used in /assist macros
function HealComm:AssistUnit(unit)
if( self.resetFrame:IsShown() and UnitCanAssist("player", unit .. "target") ) then
setCastData(6, UnitName(unit .. "target"), UnitGUID(unit .. "target"))
end
self.resetFrame:Hide()
hadTargetingCursor = nil
end
-- Target last was used, the only reason this is called with reset frame being shown is we're casting on a valid unit
-- don't have to worry about the GUID no longer being invalid etc
function HealComm:TargetLast(guid, name)
if( name and guid and self.resetFrame:IsShown() ) then
setCastData(6, name, guid)
end
self.resetFrame:Hide()
hadTargetingCursor = nil
end
function HealComm:TargetLastFriend()
self:TargetLast(lastFriendlyGUID, lastFriendlyName)
end
function HealComm:TargetLastTarget()
self:TargetLast(lastTargetGUID, lastTargetName)
end
-- Spell was cast somehow
function HealComm:CastSpell(arg, unit)
-- If the spell is waiting for a target and it's a spell action button then we know that the GUID has to be mouseover or a key binding cast.
if( unit and UnitCanAssist("player", unit) ) then
setCastData(4, UnitName(unit), UnitGUID(unit))
-- No unit, or it's a unit we can't assist
elseif( not SpellIsTargeting() ) then
if( UnitCanAssist("player", "target") ) then
setCastData(4, UnitName("target"), UnitGUID("target"))
else
setCastData(4, playerName, playerGUID)
end
hadTargetingCursor = nil
else
hadTargetingCursor = true
end
end
HealComm.CastSpellByName = HealComm.CastSpell
HealComm.CastSpellByID = HealComm.CastSpell
HealComm.UseAction = HealComm.CastSpell
-- Make sure we don't have invalid units in this
local function sanityCheckMapping()
for guid, unit in pairs(guidToUnit) do
-- Unit no longer exists, remove all healing for them
if guid ~= UnitGUID(unit) then
-- Check for (and remove) any active heals
for _, tbl in pairs({ pendingHeals, pendingHots }) do
if tbl[guid] then
for _, pending in pairs(tbl[guid]) do
if( pending.bitType ) then
parseHealEnd(guid, pending, nil, pending.spellID, true)
end
end
end
end
pendingHeals[guid] = nil
pendingHots[guid] = nil
-- Remove any heals that are on them
removeAllRecords(guid)
guidToUnit[guid] = nil
guidToGroup[guid] = nil
end
end
end
-- 5s poll that tries to solve the problem of X running out of range while a HoT is ticking
-- this is not really perfect far from it in fact. If I can find a better solution I will switch to that.
if( not HealComm.hotMonitor ) then
HealComm.hotMonitor = CreateFrame("Frame")
HealComm.hotMonitor:Hide()
HealComm.hotMonitor.timeElapsed = 0
HealComm.hotMonitor:SetScript("OnUpdate", function(self, elapsed)
self.timeElapsed = self.timeElapsed + elapsed
if( self.timeElapsed < 5 ) then return end
self.timeElapsed = self.timeElapsed - 5
-- For the time being, it will only remove them if they don't exist and it found a valid unit
-- units that leave the raid are automatically removed
local found
for guid in pairs(activeHots) do
if( guidToUnit[guid] and not UnitIsVisible(guidToUnit[guid]) ) then
removeAllRecords(guid)
else
found = true
end
end
if( not found ) then
self:Hide()
end
end)
end
-- After the player leaves a group, tables are wiped out or released for GC
local function clearGUIDData()
clearPendingHeals()
wipe(compressGUID)
wipe(decompressGUID)
wipe(activePets)
playerGUID = playerGUID or UnitGUID("player")
HealComm.guidToUnit = {[playerGUID] = "player"}
guidToUnit = HealComm.guidToUnit
HealComm.guidToGroup = {}
guidToGroup = HealComm.guidToGroup
HealComm.activeHots = {}
activeHots = HealComm.activeHots
HealComm.pendingHeals = {}
pendingHeals = HealComm.pendingHeals
HealComm.pendingHots = {}
pendingHots = HealComm.pendingHots
HealComm.bucketHeals = {}
bucketHeals = HealComm.bucketHeals
end
-- Keeps track of pet GUIDs, as pets are considered vehicles this will also map vehicle GUIDs to unit
function HealComm:UNIT_PET(unit)
local guid = UnitGUID(unit)
unit = guidToUnit[guid]
if not unit then return end
local pet = self.unitToPet[unit]
local petGUID = pet and UnitGUID(pet)
-- We have an active pet guid from this user and it's different, kill it
local activeGUID = activePets[unit]
3 years ago
if activeGUID and petGUID and activeGUID ~= petGUID then
4 years ago
removeAllRecords(activeGUID)
rawset(self.compressGUID, activeGUID, nil)
3 years ago
rawset(self.decompressGUID, "p-"..strsub(guid,8), nil)
4 years ago
guidToUnit[activeGUID] = nil
guidToGroup[activeGUID] = nil
activePets[unit] = nil
end
-- Add the new record
if petGUID then
guidToUnit[petGUID] = pet
guidToGroup[petGUID] = guidToGroup[guid]
activePets[unit] = petGUID
end
end
-- Keep track of raid GUIDs
function HealComm:GROUP_ROSTER_UPDATE()
updateDistributionChannel()
wipe(activePets)
local function update(unit)
local guid = UnitGUID(unit)
if guid then
local raidID = UnitInRaid(unit)
local group = raidID and select(3, GetRaidRosterInfo(raidID)) or 1
guidToUnit[guid] = unit
guidToGroup[guid] = group
local pet = self.unitToPet[unit]
local petGUID = pet and UnitGUID(pet)
activePets[unit] = petGUID
if petGUID then
guidToUnit[petGUID] = pet
guidToGroup[petGUID] = group
end
end
end
if GetNumGroupMembers() == 0 then
clearGUIDData()
update("player")
elseif not IsInRaid() then
update("player")
for i = 1, MAX_PARTY_MEMBERS do
update(format("party%d", i))
end
else
for i = 1, MAX_RAID_MEMBERS do
update(format("raid%d", i))
end
end
sanityCheckMapping()
end
-- PLAYER_ALIVE = got talent data
function HealComm:PLAYER_ALIVE()
self:CHARACTER_POINTS_CHANGED()
self.eventFrame:UnregisterEvent("PLAYER_ALIVE")
end
-- Initialize the library
function HealComm:OnInitialize()
-- If another instance already loaded then the tables should be wiped to prevent old data from persisting
-- in case of a spell being removed later on, only can happen if a newer LoD version is loaded
wipe(spellData)
wipe(hotData)
wipe(itemSetsData)
wipe(talentData)
-- Load all of the classes formulas and such
if LoadClassData then
LoadClassData()
end
do
local FirstAid = GetSpellInfo(746)
spellData[FirstAid] = {
3 years ago
_isChanneled = true,
ticks = {6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8},
4 years ago
interval = 1,
3 years ago
averages = {66, 114, 161, 301, 400, 640, 800, 1104, 1360, 2000, 2800, 3400, 4800, 5800}
4 years ago
}
local _GetHealTargets = GetHealTargets
GetHealTargets = function(bitType, guid, spellID, amount)
4 years ago
local spellName = GetSpellInfo(spellID)
if spellName == FirstAid then
3 years ago
return compressGUID[guid]
4 years ago
end
if _GetHealTargets then
return _GetHealTargets(bitType, guid, spellID, amount)
4 years ago
end
end
local _CalculateHealing = CalculateHealing
CalculateHealing = function(guid, spellID, unit)
local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
if spellName == FirstAid then
local healAmount = spellData[spellName].averages[spellRank]
if not healAmount then return end
local ticks = spellData[spellName].ticks[spellRank]
return CHANNEL_HEALS, ceil(healAmount / ticks), ticks, spellData[spellName].interval
end
if _CalculateHealing then
return _CalculateHealing(guid, spellID, unit)
end
end
end
3 years ago
-- Cache glyphs initially
for id=1, GetNumGlyphSockets() do
local enabled, _, glyphID = GetGlyphSocketInfo(id)
if( enabled and glyphID ) then
glyphCache[glyphID] = true
glyphCache[id] = glyphID
end
end
4 years ago
self:PLAYER_EQUIPMENT_CHANGED()
-- When first logging in talent data isn't available until at least PLAYER_ALIVE, so if we don't have data
-- will wait for that event otherwise will just cache it right now
if( GetNumTalentTabs() == 0 ) then
self.eventFrame:RegisterEvent("PLAYER_ALIVE")
else
self:CHARACTER_POINTS_CHANGED()
end
if( ResetChargeData ) then
HealComm.eventFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
end
-- Finally, register it all
self.eventFrame:RegisterEvent("CHAT_MSG_ADDON")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_SENT")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_START")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_STOP")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_DELAYED")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
self.eventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
self.eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
self.eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
self.eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
self.eventFrame:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
self.eventFrame:RegisterEvent("PLAYER_LEVEL_UP")
self.eventFrame:RegisterEvent("CHARACTER_POINTS_CHANGED")
3 years ago
self.eventFrame:RegisterUnitEvent("UNIT_AURA", 'player')
if isWrath then
self.eventFrame:RegisterEvent("GLYPH_ADDED")
self.eventFrame:RegisterEvent("GLYPH_REMOVED")
self.eventFrame:RegisterEvent("GLYPH_UPDATED")
end
4 years ago
if( self.initialized ) then return end
self.initialized = true
self.resetFrame = CreateFrame("Frame")
self.resetFrame:Hide()
self.resetFrame:SetScript("OnUpdate", function(self) self:Hide() end)
-- You can't unhook secure hooks after they are done, so will hook once and the HealComm table will update with the latest functions
-- automagically. If a new function is ever used it'll need a specific variable to indicate those set of hooks.
-- By default most of these are mapped to a more generic function, but I call separate ones so I don't have to rehook
-- if it turns out I need to know something specific
hooksecurefunc("TargetUnit", function(...) HealComm:TargetUnit(...) end)
hooksecurefunc("SpellTargetUnit", function(...) HealComm:SpellTargetUnit(...) end)
hooksecurefunc("AssistUnit", function(...) HealComm:AssistUnit(...) end)
hooksecurefunc("UseAction", function(...) HealComm:UseAction(...) end)
hooksecurefunc("TargetLastFriend", function(...) HealComm:TargetLastFriend(...) end)
hooksecurefunc("TargetLastTarget", function(...) HealComm:TargetLastTarget(...) end)
hooksecurefunc("CastSpellByName", function(...) HealComm:CastSpellByName(...) end)
hooksecurefunc("CastSpellByID", function(...) HealComm:CastSpellByID(...) end)
end
-- General event handler
local function OnEvent(self, event, ...)
if event == 'COMBAT_LOG_EVENT_UNFILTERED' then
HealComm[event](HealComm, CombatLogGetCurrentEventInfo())
else
HealComm[event](HealComm, ...)
end
end
-- Event handler
HealComm.eventFrame = HealComm.frame or HealComm.eventFrame or CreateFrame("Frame")
HealComm.eventFrame:UnregisterAllEvents()
HealComm.eventFrame:RegisterEvent("UNIT_PET")
HealComm.eventFrame:SetScript("OnEvent", OnEvent)
HealComm.frame = nil
-- At PLAYER_LEAVING_WORLD (Actually more like MIRROR_TIMER_STOP but anyway) UnitGUID("player") returns nil, delay registering
-- events and set a playerGUID/playerName combo for all players on PLAYER_LOGIN not just the healers.
function HealComm:PLAYER_LOGIN()
playerGUID = UnitGUID("player")
playerName = UnitName("player")
playerLevel = UnitLevel("player")
-- Oddly enough player GUID is not available on file load, so keep the map of player GUID to themselves too
guidToUnit[playerGUID] = "player"
self:OnInitialize()
self.eventFrame:UnregisterEvent("PLAYER_LOGIN")
self.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
self.eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
self:ZONE_CHANGED_NEW_AREA()
self:GROUP_ROSTER_UPDATE()
end
if( not IsLoggedIn() ) then
HealComm.eventFrame:RegisterEvent("PLAYER_LOGIN")
else
HealComm:PLAYER_LOGIN()
end