diff --git a/api/unitframes.lua b/api/unitframes.lua
index 6786b1cf..02289e91 100644
--- a/api/unitframes.lua
+++ b/api/unitframes.lua
@@ -556,12 +556,6 @@ function pfUI.uf:UpdateConfig()
f.incHeal.texture:SetVertexColor(0, 1, 0, 0.5)
f.incHeal:Hide()
- f.incHeal:SetScript("OnShow", function()
- if pfUI.prediction and f.label and f.id then
- pfUI.prediction:TriggerUpdate(UnitName(f.label .. f.id))
- end
- end)
-
if f.config.verticalbar == "0" then
f.incHeal:ClearAllPoints()
f.incHeal:SetPoint("TOPLEFT", f.hp.bar, "TOPLEFT", 0, 0)
@@ -864,6 +858,61 @@ function pfUI.uf.OnUpdate()
this.portrait.model.update = nil
end
+ -- get incoming heals and resurections
+ if libpredict then
+ local unit = this.label .. this.id
+ local heal = libpredict:UnitGetIncomingHeals(unit)
+ local ress = libpredict:UnitHasIncomingResurrection(unit)
+
+ if heal ~= this.lastheal then
+ local overhealperc = tonumber(this.config.overhealperc)
+ local health, maxHealth = UnitHealth(unit), UnitHealthMax(unit)
+ this.lastheal = heal
+
+ if heal > 0 and (health < maxHealth or overhealperc > 0 ) then
+ local width = this.config.width
+ local height = this.config.height
+
+ if this.config.verticalbar == "0" then
+ local healthWidth = width * (health / maxHealth)
+ local incWidth = width * heal / maxHealth
+ if healthWidth + incWidth > width * (1+(overhealperc/100)) then
+ incWidth = width * (1+overhealperc/100) - healthWidth
+ end
+
+ if this.config.invert_healthbar == "1" then
+ this.incHeal:SetWidth(incWidth)
+ else
+ this.incHeal:SetWidth(incWidth + healthWidth)
+ end
+ else
+ local healthHeight = height * (health / maxHealth)
+ local incHeight = height * heal / maxHealth
+ if healthHeight + incHeight > height * (1+(overhealperc/100)) then
+ incHeight = height * (1+overhealperc/100) - healthHeight
+ end
+
+ if this.config.invert_healthbar == "1" then
+ this.incHeal:SetHeight(incHeight)
+ else
+ this.incHeal:SetHeight(incHeight + healthHeight)
+ end
+ end
+
+ this.incHeal:Show()
+ else
+ this.incHeal:Hide()
+ end
+ end
+
+ -- update ressurections
+ if ress and UnitIsDeadOrGhost(unit) then
+ this.ressIcon:Show()
+ else
+ this.ressIcon:Hide()
+ end
+ end
+
-- trigger eventless actions (online/offline/range)
if not this.lastTick then this.lastTick = GetTime() + (this.tick or .2) end
if this.lastTick and this.lastTick < GetTime() then
diff --git a/init/libs.xml b/init/libs.xml
index 99b31c29..01d76ce1 100644
--- a/init/libs.xml
+++ b/init/libs.xml
@@ -8,4 +8,5 @@
+
diff --git a/init/modules.xml b/init/modules.xml
index f62af00a..a2711765 100644
--- a/init/modules.xml
+++ b/init/modules.xml
@@ -12,7 +12,6 @@
-
diff --git a/libs/libcast.lua b/libs/libcast.lua
index fae2e270..509499f5 100644
--- a/libs/libcast.lua
+++ b/libs/libcast.lua
@@ -180,7 +180,6 @@ libcast:RegisterEvent("SPELLCAST_DELAYED")
libcast:RegisterEvent("SPELLCAST_CHANNEL_START")
libcast:RegisterEvent("SPELLCAST_CHANNEL_STOP")
libcast:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
-libcast:RegisterEvent("PLAYER_TARGET_CHANGED")
libcast:SetScript("OnEvent", function()
-- Fill database with player casts
diff --git a/libs/libpredict.lua b/libs/libpredict.lua
new file mode 100644
index 00000000..2272ef50
--- /dev/null
+++ b/libs/libpredict.lua
@@ -0,0 +1,476 @@
+-- load pfUI environment
+setfenv(1, pfUI:GetEnvironment())
+
+--[[ libpredict ]]--
+-- A pfUI library that detects, receives and sends heal and resurrection predictions.
+-- Healing predictions are done by caching the last known "normal" heal value of the
+-- spell when last being used. Those chaches are cleared when new talents are detected.
+-- The API provides function calls similar to later WoW expansions such as:
+-- UnitGetIncomingHeals(unit)
+-- UnitHasIncomingResurrection(unit)
+--
+-- The library is able to receive and send compatible messages to HealComm (vanilla)
+-- and HealComm (tbc) including the ressurections of both versions. It has an option
+-- to disable the sending of those messages in case one of the mentioned libraries
+-- is already active.
+
+-- return instantly when another libpredict is already active
+if pfUI.api.libpredict then return end
+
+local senttarget
+local heals, ress, events = {}, {}, {}
+
+local PRAYER_OF_HEALING
+do -- Prayer of Healing
+ local locales = {
+ ["deDE"] = "Gebet der Heilung",
+ ["enUS"] = "Prayer of Healing",
+ ["esES"] = "Rezo de curación",
+ ["frFR"] = "Prière de soins",
+ ["koKR"] = "치유의 기원",
+ ["ruRU"] = "Молитва исцеления",
+ ["zhCN"] = "治疗祷言",
+ }
+
+ PRAYER_OF_HEALING = locales[GetLocale()] or locales["enUS"]
+end
+
+local libpredict = CreateFrame("Frame")
+libpredict:RegisterEvent("UNIT_HEALTH")
+libpredict:RegisterEvent("CHAT_MSG_ADDON")
+libpredict:RegisterEvent("PLAYER_TARGET_CHANGED")
+libpredict:SetScript("OnEvent", function()
+ if event == "CHAT_MSG_ADDON" and (arg1 == "HealComm" or arg1 == "CTRA") then
+ this:ParseChatMessage(arg4, arg2, arg1)
+ elseif event == "UNIT_HEALTH" then
+ local name = UnitName(arg1)
+ if ress[name] and not UnitIsDeadOrGhost(arg1) then
+ ress[UnitName(arg1)] = nil
+ end
+ end
+end)
+
+libpredict:SetScript("OnUpdate", function()
+ -- update on timeout events
+ for timestamp, targets in pairs(events) do
+ if GetTime() >= timestamp then
+ events[timestamp] = nil
+ end
+ end
+end)
+
+function libpredict:ParseComm(sender, msg)
+ local msgtype, target, heal, time
+
+ if msg == "Healstop" or msg == "GrpHealstop" then
+ msgtype = "Stop"
+ elseif msg == "Resurrection/stop/" then
+ msgtype = "RessStop"
+ else
+ local msgobj = {strsplit("/", msg)}
+
+ if msgobj and msgobj[1] and msgobj[2] then -- legacy healcomm object
+ if msgobj[1] == "GrpHealdelay" or msgobj[1] == "Healdelay" then
+ msgtype, time = "Delay", msgobj[2]
+ end
+
+ if msgobj[1] and msgobj[1] == "Resurrection" and msgobj[2] then
+ msgtype, target = "Ress", msgobj[2]
+ end
+
+ if msgobj[1] == "Heal" and msgobj[2] then
+ msgtype, target, heal, time = "Heal", msgobj[2], msgobj[3], msgobj[4]
+ end
+
+ if msgobj[1] == "GrpHeal" and msgobj[2] then
+ msgtype, target, heal, time = "Heal", {}, msgobj[2], msgobj[3]
+ for i=4,8 do
+ if msgobj[i] then table.insert(target, msgobj[i]) end
+ end
+ end
+ elseif select and UnitCastingInfo then -- latest healcomm
+ msgtype = tonumber(string.sub(msg, 1, 3))
+ if not msgtype then return end
+
+ if msgtype == 0 then
+ msgtype = "Heal"
+ heal = tonumber(string.sub(msg, 4, 8))
+ target = string.sub(msg,9, -1)
+
+ local starttime = select(5, UnitCastingInfo(sender))
+ local endtime = select(6, UnitCastingInfo(sender))
+ if not starttime or not endtime then return end
+ time = endtime - starttime
+ elseif msgtype == 1 then
+ msgtype = "Stop"
+ elseif msgtype == 2 then
+ msgtype = "Heal"
+ heal = tonumber(string.sub(msg,4, 8))
+ target = {strsplit(":", string.sub(msg,9, -1))}
+ local starttime = select(5, UnitCastingInfo(sender))
+ local endtime = select(6, UnitCastingInfo(sender))
+ if not starttime or not endtime then return end
+ time = endtime - starttime
+ end
+ end
+ end
+
+ return msgtype, target, heal, time
+end
+
+function libpredict:ParseChatMessage(sender, msg, comm)
+ local msgtype, target, heal, time
+
+ if comm == "HealComm" then
+ msgtype, target, heal, time = libpredict:ParseComm(sender, msg)
+ elseif comm == "CTRA" then
+ local _, _, cmd, ctratarget = string.find(msg, "(%a+)%s?([^#]*)")
+ if cmd and ctratarget and cmd == "RES" and ctratarget ~= "" and ctratarget ~= UNKNOWN then
+ msgtype = "Ress"
+ target = ctratarget
+ end
+ end
+
+ if msgtype == "Stop" and sender then
+ libpredict:HealStop(sender)
+ return
+ elseif ( msg == "RessStop" or msg == "RESNO" ) and sender then
+ libpredict:RessStop(sender)
+ return
+ elseif msgtype == "Delay" and time then
+ libpredict:HealDelay(sender, time)
+ elseif msgtype == "Heal" and target and heal and time then
+ if type(target) == "table" then
+ for _, name in pairs(target) do
+ libpredict:Heal(sender, name, heal, time)
+ end
+ else
+ libpredict:Heal(sender, target, heal, time)
+ end
+ elseif msgtype == "Ress" then
+ libpredict:Ress(sender, target)
+ end
+end
+
+function libpredict:AddEvent(time, target)
+ events[time] = events[time] or {}
+ table.insert(events[time], target)
+end
+
+function libpredict:Heal(sender, target, amount, duration)
+ if not sender or not target or not amount or not duration then
+ return
+ end
+
+ local timeout = duration/1000 + GetTime()
+ heals[target] = heals[target] or {}
+ heals[target][sender] = { amount, timeout }
+ libpredict:AddEvent(timeout, target)
+end
+
+function libpredict:HealStop(sender)
+ for ttarget, t in pairs(heals) do
+ for tsender in pairs(heals[ttarget]) do
+ if sender == tsender then
+ heals[ttarget][tsender] = nil
+ end
+ end
+ end
+end
+
+function libpredict:HealDelay(sender, delay)
+ local delay = delay/1000
+ for target, t in pairs(heals) do
+ for tsender, amount in pairs(heals[target]) do
+ if sender == tsender then
+ amount[2] = amount[2] + delay
+ libpredict:AddEvent(amount[2], target)
+ end
+ end
+ end
+end
+
+function libpredict:Ress(sender, target)
+ ress[target] = ress[target] or {}
+ ress[target][sender] = true
+end
+
+function libpredict:RessStop(sender)
+ for ttarget, t in pairs(ress) do
+ for tsender in pairs(ress[ttarget]) do
+ if sender == tsender then
+ ress[ttarget][tsender] = nil
+ end
+ end
+ end
+end
+
+function libpredict:UnitGetIncomingHeals(unit)
+ if not unit or not UnitName(unit) then return 0 end
+ if UnitIsDeadOrGhost(unit) then return 0 end
+ local name = UnitName(unit)
+
+ local sumheal = 0
+ if not heals[name] then
+ return sumheal
+ else
+ for sender, amount in pairs(heals[name]) do
+ if amount[2] <= GetTime() then
+ heals[name][sender] = nil
+ else
+ sumheal = sumheal + amount[1]
+ end
+ end
+ end
+ return sumheal
+end
+
+function libpredict:UnitHasIncomingResurrection(unit)
+ if not unit or not UnitName(unit) then return nil end
+ local name = UnitName(unit)
+
+ if not ress[name] then
+ return nil
+ else
+ for sender, val in pairs(ress[name]) do
+ if val == true then
+ return val
+ end
+ end
+ end
+ return nil
+end
+
+local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
+local realm = GetRealmName()
+local player = UnitName("player")
+local cache, gear_string = {}, ""
+local resetcache = CreateFrame("Frame")
+resetcache:RegisterEvent("PLAYER_ENTERING_WORLD")
+resetcache:RegisterEvent("SKILL_LINES_CHANGED")
+resetcache:RegisterEvent("UNIT_INVENTORY_CHANGED")
+resetcache:SetScript("OnEvent", function()
+ if event == "PLAYER_ENTERING_WORLD" then
+ -- load and initialize previous caches of spell amounts
+ pfUI_cache["prediction"] = pfUI_cache["prediction"] or {}
+ pfUI_cache["prediction"][realm] = pfUI_cache["prediction"][realm] or {}
+ pfUI_cache["prediction"][realm][player] = pfUI_cache["prediction"][realm][player] or {}
+ pfUI_cache["prediction"][realm][player]["heals"] = pfUI_cache["prediction"][realm][player]["heals"] or {}
+ cache = pfUI_cache["prediction"][realm][player]["heals"]
+ return
+ end
+
+ if event == "UNIT_INVENTORY_CHANGED" then
+ -- skip non-player events
+ if arg1 and arg1 ~= "player" then return end
+
+ local gear = ""
+ for id = 1, 18 do
+ gear = gear .. (GetInventoryItemLink("player",id) or "")
+ end
+
+ -- abort when inventory didn't change
+ if gear == gear_string then return end
+ gear_string = gear
+ end
+
+ -- flag all cached heals for renewal
+ for k in pairs(cache) do
+ if type(cache[k]) == "number" or type(cache[k]) == "string" then
+ -- migrate old data
+ local oldval = cache[k]
+ cache[k] = { [1] = oldval }
+ end
+
+ -- flag for reset
+ cache[k][2] = true
+ end
+end)
+
+local function UpdateCache(spell, heal, crit)
+ if not spell or not heal then return end
+ local heal = tonumber(heal)
+
+ if not cache[spell] or cache[spell][2] then
+ -- skills or equipment changed, save whatever is detected
+ cache[spell] = cache[spell] or {}
+ cache[spell][1] = crit and heal*2/3 or heal
+ cache[spell][2] = crit
+ elseif not crit and cache[spell][1] < heal then
+ -- safe the best heal we can get
+ cache[spell][1] = heal
+ cache[spell][2] = nil
+ end
+end
+
+-- Gather Data by User Actions
+hooksecurefunc("CastSpell", function(id, bookType)
+ if not libpredict.sender.enabled then return end
+ local effect, rank = libspell.GetSpellInfo(id, bookType)
+ if not effect then return end
+ spell_queue[1] = effect
+ spell_queue[2] = effect.. ( rank or "" )
+ spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
+end, true)
+
+hooksecurefunc("CastSpellByName", function(effect, target)
+ if not libpredict.sender.enabled then return end
+ local effect, rank = libspell.GetSpellInfo(effect)
+ if not effect then return end
+ spell_queue[1] = effect
+ spell_queue[2] = effect.. ( rank or "" )
+ spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
+end, true)
+
+local scanner = libtipscan:GetScanner("prediction")
+hooksecurefunc("UseAction", function(slot, target, selfcast)
+ if not libpredict.sender.enabled then return end
+ if GetActionText(slot) or not IsCurrentAction(slot) then return end
+ scanner:SetAction(slot)
+ local effect, rank = scanner:Line(1)
+ if not effect then return end
+ spell_queue[1] = effect
+ spell_queue[2] = effect.. ( rank or "" )
+ spell_queue[3] = selfcast and UnitName("player") or UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
+end, true)
+
+libpredict.sender = CreateFrame("Frame", "pfPredictionSender", UIParent)
+libpredict.sender.enabled = true
+libpredict.sender.SendHealCommMsg = function(self, msg)
+ SendAddonMessage("HealComm", msg, "RAID")
+ SendAddonMessage("HealComm", msg, "BATTLEGROUND")
+end
+libpredict.sender.SendResCommMsg = function(self, msg)
+ SendAddonMessage("CTRA", msg, "RAID")
+ SendAddonMessage("CTRA", msg, "BATTLEGROUND")
+end
+
+-- tbc
+libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
+libpredict.sender:RegisterEvent("UNIT_SPELLCAST_START")
+libpredict.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
+libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
+libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
+libpredict.sender:RegisterEvent("UNIT_SPELLCAST_SENT")
+
+-- vanilla
+libpredict.sender:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF")
+libpredict.sender:RegisterEvent("SPELLCAST_START")
+libpredict.sender:RegisterEvent("SPELLCAST_STOP")
+libpredict.sender:RegisterEvent("SPELLCAST_FAILED")
+libpredict.sender:RegisterEvent("SPELLCAST_INTERRUPTED")
+libpredict.sender:RegisterEvent("SPELLCAST_DELAYED")
+
+-- force cache updates
+libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
+libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
+
+libpredict.sender:SetScript("OnEvent", function()
+ if event == "CHAT_MSG_SPELL_SELF_BUFF" then -- vanilla
+ local spell, _, heal = cmatch(arg1, HEALEDSELFOTHER) -- "Your %s heals %s for %d."
+ if spell and heal then
+ if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
+ return
+ end
+
+ local spell, heal = cmatch(arg1, HEALEDSELFSELF) -- "Your %s heals you for %d."
+ if spell and heal then
+ if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
+ return
+ end
+
+ local spell, heal = cmatch(arg1, HEALEDCRITSELFOTHER) -- "Your %s critically heals %s for %d."
+ if spell and heal then
+ if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
+ return
+ end
+
+ local spell, _, heal = cmatch(arg1, HEALEDCRITSELFSELF) -- "Your %s critically heals you for %d."
+ if spell and heal then
+ if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
+ return
+ end
+ elseif event == "COMBAT_LOG_EVENT_UNFILTERED" and arg2 == "SPELL_HEAL" and arg4 == player then -- tbc
+ local spell, heal, crit = arg10, arg12, arg13
+ if spell and heal and crit then
+ if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
+ elseif spell and heal then
+ if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
+ end
+ elseif event == "UNIT_SPELLCAST_SENT" and arg4 then -- fix tbc mouseover macros
+ senttarget = arg4
+ elseif strfind(event, "SPELLCAST_START", 1) then
+ local spell, time = arg1, arg2
+
+ if strfind(event, "UNIT_", 1) then -- tbc
+ if arg1 ~= "player" then return end
+ local spellname, _, _, _, starttime, endtime = UnitCastingInfo("player")
+ spell, time = spellname, endtime - starttime
+ end
+
+ if spell_queue[1] == spell and cache[spell_queue[2]] then
+ local sender = player
+ local target = senttarget or spell_queue[3]
+ local amount = cache[spell_queue[2]][1]
+ local casttime = time
+
+ if spell == PRAYER_OF_HEALING then
+ target = sender
+
+ for i=1,4 do
+ if CheckInteractDistance("party"..i, 4) then
+ libpredict:Heal(player, UnitName("party"..i), amount, casttime)
+ if pfUI.client < 20000 then -- vanilla
+ libpredict.sender:SendHealCommMsg("Heal/" .. UnitName("party"..i) .. "/" .. amount .. "/" .. casttime .. "/")
+ else -- tbc
+ libpredict.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), UnitName("party"..i)))
+ end
+ libpredict.sender.healing = true
+ end
+ end
+ end
+
+ libpredict:Heal(player, target, amount, casttime)
+ if pfUI.client < 20000 then -- vanilla
+ libpredict.sender:SendHealCommMsg("Heal/" .. target .. "/" .. amount .. "/" .. casttime .. "/")
+ else -- tbc
+ libpredict.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), target))
+ end
+ libpredict.sender.healing = true
+
+ elseif spell_queue[1] == spell and L["resurrections"][spell] then
+ local target = senttarget or spell_queue[3]
+ libpredict:Ress(player, target)
+ libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
+ libpredict.sender:SendResCommMsg("RES " .. target)
+ libpredict.sender.resurrecting = true
+ end
+ elseif strfind(event, "SPELLCAST_FAILED", 1) or strfind(event, "SPELLCAST_INTERRUPTED", 1) then
+ if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
+ if libpredict.sender.healing then
+ libpredict:HealStop(player)
+ if pfUI.client < 20000 then -- vanilla
+ libpredict.sender:SendHealCommMsg("HealStop")
+ else -- tbc
+ libpredict.sender:SendHealCommMsg("001F")
+ end
+ libpredict.sender.healing = nil
+ elseif libpredict.sender.resurrecting then
+ local target = senttarget or spell_queue[3]
+ libpredict:RessStop(player)
+ libpredict.sender:SendHealCommMsg("Resurrection/stop/")
+ libpredict.sender:SendResCommMsg("RESNO " .. target)
+ libpredict.sender.resurrecting = nil
+ end
+ elseif event == "SPELLCAST_DELAYED" then
+ if libpredict.sender.healing then
+ libpredict:HealDelay(player, arg1)
+ libpredict.sender:SendHealCommMsg("Healdelay/" .. arg1 .. "/")
+ end
+ elseif strfind(event, "SPELLCAST_STOP", 1) then
+ if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
+ libpredict:HealStop(player)
+ end
+end)
+
+pfUI.api.libpredict = libpredict
diff --git a/modules/prediction.lua b/modules/prediction.lua
deleted file mode 100644
index b0b7f56d..00000000
--- a/modules/prediction.lua
+++ /dev/null
@@ -1,523 +0,0 @@
-pfUI:RegisterModule("prediction", "vanilla:tbc", function ()
- local heals = {}
- local ress = {}
- local events = {}
- local senttarget
-
- local PRAYER_OF_HEALING
- do -- Prayer of Healing
- local locales = {
- ["deDE"] = "Gebet der Heilung",
- ["enUS"] = "Prayer of Healing",
- ["esES"] = "Rezo de curación",
- ["frFR"] = "Prière de soins",
- ["koKR"] = "치유의 기원",
- ["ruRU"] = "Молитва исцеления",
- ["zhCN"] = "治疗祷言",
- }
-
- PRAYER_OF_HEALING = locales[GetLocale()] or locales["enUS"]
- end
-
- pfUI.prediction = CreateFrame("Frame")
- pfUI.prediction:RegisterEvent("UNIT_HEALTH")
- pfUI.prediction:RegisterEvent("CHAT_MSG_ADDON")
- pfUI.prediction:RegisterEvent("PLAYER_TARGET_CHANGED")
- pfUI.prediction:SetScript("OnEvent", function()
- if event == "CHAT_MSG_ADDON" and (arg1 == "HealComm" or arg1 == "CTRA") then
- this:ParseChatMessage(arg4, arg2, arg1)
- elseif event == "UNIT_HEALTH" then
- local name = UnitName(arg1)
- if ress[name] and not UnitIsDeadOrGhost(arg1) then
- ress[UnitName(arg1)] = nil
- this:TriggerUpdate(name)
- end
-
- if heals[name] then
- this:TriggerUpdate(name)
- end
- elseif event == "PLAYER_TARGET_CHANGED" then
- this:TriggerUpdate(UnitName("target"))
- end
- end)
-
- pfUI.prediction:SetScript("OnUpdate", function()
- for timestamp, targets in pairs(events) do
- if GetTime() >= timestamp then
- for id, target in pairs(targets) do
- pfUI.prediction:TriggerUpdate(target)
- end
- events[timestamp] = nil
- end
- end
- end)
-
- local function ParseComm(sender, msg)
- local msgtype, target, heal, time
-
- if msg == "Healstop" or msg == "GrpHealstop" then
- msgtype = "Stop"
- elseif msg == "Resurrection/stop/" then
- msgtype = "RessStop"
- else
- local msgobj = {strsplit("/", msg)}
-
- if msgobj and msgobj[1] and msgobj[2] then -- legacy healcomm object
- if msgobj[1] == "GrpHealdelay" or msgobj[1] == "Healdelay" then
- msgtype, time = "Delay", msgobj[2]
- end
-
- if msgobj[1] and msgobj[1] == "Resurrection" and msgobj[2] then
- msgtype, target = "Ress", msgobj[2]
- end
-
- if msgobj[1] == "Heal" and msgobj[2] then
- msgtype, target, heal, time = "Heal", msgobj[2], msgobj[3], msgobj[4]
- end
-
- if msgobj[1] == "GrpHeal" and msgobj[2] then
- msgtype, target, heal, time = "Heal", {}, msgobj[2], msgobj[3]
- for i=4,8 do
- if msgobj[i] then table.insert(target, msgobj[i]) end
- end
- end
- elseif select and UnitCastingInfo then -- latest healcomm
- msgtype = tonumber(string.sub(msg, 1, 3))
- if not msgtype then return end
-
- if msgtype == 0 then
- msgtype = "Heal"
- heal = tonumber(string.sub(msg, 4, 8))
- target = string.sub(msg,9, -1)
-
- local starttime = select(5, UnitCastingInfo(sender))
- local endtime = select(6, UnitCastingInfo(sender))
- if not starttime or not endtime then return end
- time = endtime - starttime
- elseif msgtype == 1 then
- msgtype = "Stop"
- elseif msgtype == 2 then
- msgtype = "Heal"
- heal = tonumber(string.sub(msg,4, 8))
- target = {strsplit(":", string.sub(msg,9, -1))}
- local starttime = select(5, UnitCastingInfo(sender))
- local endtime = select(6, UnitCastingInfo(sender))
- if not starttime or not endtime then return end
- time = endtime - starttime
- end
- end
- end
-
- return msgtype, target, heal, time
- end
-
- function pfUI.prediction:ParseChatMessage(sender, msg, comm)
- local msgtype, target, heal, time
-
- if comm == "HealComm" then
- msgtype, target, heal, time = ParseComm(sender, msg)
- elseif comm == "CTRA" then
- local _, _, cmd, ctratarget = string.find(msg, "(%a+)%s?([^#]*)")
- if cmd and ctratarget and cmd == "RES" and ctratarget ~= "" and ctratarget ~= UNKNOWN then
- msgtype = "Ress"
- target = ctratarget
- end
- end
-
- if msgtype == "Stop" and sender then
- pfUI.prediction:HealStop(sender)
- return
- elseif ( msg == "RessStop" or msg == "RESNO" ) and sender then
- pfUI.prediction:RessStop(sender)
- return
- elseif msgtype == "Delay" and time then
- pfUI.prediction:HealDelay(sender, time)
- elseif msgtype == "Heal" and target and heal and time then
- if type(target) == "table" then
- for _, name in pairs(target) do
- pfUI.prediction:Heal(sender, name, heal, time)
- end
- else
- pfUI.prediction:Heal(sender, target, heal, time)
- end
- elseif msgtype == "Ress" then
- pfUI.prediction:Ress(sender, target)
- end
- end
-
- function pfUI.prediction:AddEvent(time, target)
- events[time] = events[time] or {}
- table.insert(events[time], target)
- end
-
- function pfUI.prediction:Heal(sender, target, amount, duration)
- if not sender or not target or not amount or not duration then
- return
- end
-
- local timeout = duration/1000 + GetTime()
- heals[target] = heals[target] or {}
- heals[target][sender] = { amount, timeout }
- pfUI.prediction:TriggerUpdate(target)
- pfUI.prediction:AddEvent(timeout, target)
- end
-
- function pfUI.prediction:HealStop(sender)
- for ttarget, t in pairs(heals) do
- for tsender in pairs(heals[ttarget]) do
- if sender == tsender then
- heals[ttarget][tsender] = nil
- pfUI.prediction:TriggerUpdate(ttarget)
- end
- end
- end
- end
-
- function pfUI.prediction:HealDelay(sender, delay)
- local delay = delay/1000
- for target, t in pairs(heals) do
- for tsender, amount in pairs(heals[target]) do
- if sender == tsender then
- amount[2] = amount[2] + delay
- pfUI.prediction:TriggerUpdate(target)
- pfUI.prediction:AddEvent(amount[2], target)
- end
- end
- end
- end
-
- function pfUI.prediction:GetHeal(target)
- local sumheal = 0
- if not heals[target] then
- return sumheal
- else
- for sender, amount in pairs(heals[target]) do
- if amount[2] <= GetTime() then
- heals[target][sender] = nil
- else
- sumheal = sumheal + amount[1]
- end
- end
- end
- return sumheal
- end
-
- function pfUI.prediction:Ress(sender, target)
- ress[target] = ress[target] or {}
- ress[target][sender] = true
- pfUI.prediction:TriggerUpdate(target)
- end
-
- function pfUI.prediction:RessStop(sender)
- for ttarget, t in pairs(ress) do
- for tsender in pairs(ress[ttarget]) do
- if sender == tsender then
- ress[ttarget][tsender] = nil
- pfUI.prediction:TriggerUpdate(ttarget)
- end
- end
- end
- end
-
- function pfUI.prediction:GetRess(target)
- if not ress[target] then
- return nil
- else
- for sender, val in pairs(ress[target]) do
- if val == true then
- return val
- end
- end
- end
- return nil
- end
-
- function pfUI.prediction:TriggerUpdate(target)
- if not target then return end
- local heal = pfUI.prediction:GetHeal(target)
- local ress = pfUI.prediction:GetRess(target)
-
- if pfUI.uf then
- pfUI.prediction:UpdateUnitFrames(target, heal, ress)
- end
- end
-
- function pfUI.prediction:UpdateUnitFrames(name, heal, ress)
- for id, frame in pairs(_G.pfUI.uf.frames) do
- if frame:IsVisible() and frame.label and frame.id then
- local overhealperc = tonumber(frame.config.overhealperc)
- local unit = frame.label .. frame.id
- local health, maxHealth = UnitHealth(unit), UnitHealthMax(unit)
-
- if UnitName(unit) == name then
- if ress and UnitIsDeadOrGhost(unit) then
- frame.ressIcon:Show()
- elseif heal and heal > 0 and (health < maxHealth or overhealperc > 0 ) then
- local width = frame.config.width
- local height = frame.config.height
-
- if frame.config.verticalbar == "0" then
- local healthWidth = width * (health / maxHealth)
- local incWidth = width * heal / maxHealth
- if healthWidth + incWidth > width * (1+(overhealperc/100)) then
- incWidth = width * (1+overhealperc/100) - healthWidth
- end
-
- if frame.config.invert_healthbar == "1" then
- frame.incHeal:SetWidth(incWidth)
- else
- frame.incHeal:SetWidth(incWidth + healthWidth)
- end
- else
- local healthHeight = height * (health / maxHealth)
- local incHeight = height * heal / maxHealth
- if healthHeight + incHeight > height * (1+(overhealperc/100)) then
- incHeight = height * (1+overhealperc/100) - healthHeight
- end
-
- if frame.config.invert_healthbar == "1" then
- frame.incHeal:SetHeight(incHeight)
- else
- frame.incHeal:SetHeight(incHeight + healthHeight)
- end
- end
-
- frame.incHeal:Show()
- else
- frame.incHeal:Hide()
- frame.ressIcon:Hide()
- end
- end
- end
- end
- end
-
- local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
-
- local realm = GetRealmName()
- local player = UnitName("player")
- pfUI_cache["prediction"] = pfUI_cache["prediction"] or {}
- pfUI_cache["prediction"][realm] = pfUI_cache["prediction"][realm] or {}
- pfUI_cache["prediction"][realm][player] = pfUI_cache["prediction"][realm][player] or {}
- pfUI_cache["prediction"][realm][player]["heals"] = pfUI_cache["prediction"][realm][player]["heals"] or {}
-
- local cache = pfUI_cache["prediction"][realm][player]["heals"]
- local gear_string = ""
- local resetcache = CreateFrame("Frame")
-
- resetcache:RegisterEvent("SKILL_LINES_CHANGED")
- resetcache:RegisterEvent("UNIT_INVENTORY_CHANGED")
- resetcache:SetScript("OnEvent", function()
- if event == "UNIT_INVENTORY_CHANGED" then
- -- skip non-player events
- if arg1 and arg1 ~= "player" then return end
-
- local gear = ""
- for id = 1, 18 do
- gear = gear .. (GetInventoryItemLink("player",id) or "")
- end
-
- -- abort when inventory didn't change
- if gear == gear_string then return end
- gear_string = gear
- end
-
- -- flag all cached heals for renewal
- for k in pairs(cache) do
- if type(cache[k]) == "number" or type(cache[k]) == "string" then
- -- migrate old data
- local oldval = cache[k]
- cache[k] = { [1] = oldval }
- end
-
- -- flag for reset
- cache[k][2] = true
- end
- end)
-
- local function UpdateCache(spell, heal, crit)
- if not spell or not heal then return end
- local heal = tonumber(heal)
-
- if not cache[spell] or cache[spell][2] then
- -- skills or equipment changed, save whatever is detected
- cache[spell] = cache[spell] or {}
- cache[spell][1] = crit and heal*2/3 or heal
- cache[spell][2] = crit
- elseif not crit and cache[spell][1] < heal then
- -- safe the best heal we can get
- cache[spell][1] = heal
- cache[spell][2] = nil
- end
- end
-
- -- Gather Data by User Actions
- hooksecurefunc("CastSpell", function(id, bookType)
- if not pfUI.prediction.sender.enabled then return end
- local effect, rank = libspell.GetSpellInfo(id, bookType)
- if not effect then return end
- spell_queue[1] = effect
- spell_queue[2] = effect.. ( rank or "" )
- spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
- end, true)
-
- hooksecurefunc("CastSpellByName", function(effect, target)
- if not pfUI.prediction.sender.enabled then return end
- local effect, rank = libspell.GetSpellInfo(effect)
- if not effect then return end
- spell_queue[1] = effect
- spell_queue[2] = effect.. ( rank or "" )
- spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
- end, true)
-
- local scanner = libtipscan:GetScanner("prediction")
- hooksecurefunc("UseAction", function(slot, target, selfcast)
- if not pfUI.prediction.sender.enabled then return end
- if GetActionText(slot) or not IsCurrentAction(slot) then return end
- scanner:SetAction(slot)
- local effect, rank = scanner:Line(1)
- if not effect then return end
- spell_queue[1] = effect
- spell_queue[2] = effect.. ( rank or "" )
- spell_queue[3] = selfcast and UnitName("player") or UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
- end, true)
-
- pfUI.prediction.sender = CreateFrame("Frame", "pfPredictionSender", UIParent)
- pfUI.prediction.sender.enabled = true
- pfUI.prediction.sender.SendHealCommMsg = function(self, msg)
- SendAddonMessage("HealComm", msg, "RAID")
- SendAddonMessage("HealComm", msg, "BATTLEGROUND")
- end
- pfUI.prediction.sender.SendResCommMsg = function(self, msg)
- SendAddonMessage("CTRA", msg, "RAID")
- SendAddonMessage("CTRA", msg, "BATTLEGROUND")
- end
-
- -- tbc
- pfUI.prediction.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
- pfUI.prediction.sender:RegisterEvent("UNIT_SPELLCAST_START")
- pfUI.prediction.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
- pfUI.prediction.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
- pfUI.prediction.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
- pfUI.prediction.sender:RegisterEvent("UNIT_SPELLCAST_SENT")
-
- -- vanilla
- pfUI.prediction.sender:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF")
- pfUI.prediction.sender:RegisterEvent("SPELLCAST_START")
- pfUI.prediction.sender:RegisterEvent("SPELLCAST_STOP")
- pfUI.prediction.sender:RegisterEvent("SPELLCAST_FAILED")
- pfUI.prediction.sender:RegisterEvent("SPELLCAST_INTERRUPTED")
- pfUI.prediction.sender:RegisterEvent("SPELLCAST_DELAYED")
-
- -- force cache updates
- pfUI.prediction.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
- pfUI.prediction.sender:RegisterEvent("SKILL_LINES_CHANGED")
-
- pfUI.prediction.sender:SetScript("OnEvent", function()
- if event == "CHAT_MSG_SPELL_SELF_BUFF" then -- vanilla
- local spell, _, heal = cmatch(arg1, HEALEDSELFOTHER) -- "Your %s heals %s for %d."
- if spell and heal then
- if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
- return
- end
-
- local spell, heal = cmatch(arg1, HEALEDSELFSELF) -- "Your %s heals you for %d."
- if spell and heal then
- if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
- return
- end
-
- local spell, heal = cmatch(arg1, HEALEDCRITSELFOTHER) -- "Your %s critically heals %s for %d."
- if spell and heal then
- if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
- return
- end
-
- local spell, _, heal = cmatch(arg1, HEALEDCRITSELFSELF) -- "Your %s critically heals you for %d."
- if spell and heal then
- if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
- return
- end
- elseif event == "COMBAT_LOG_EVENT_UNFILTERED" and arg2 == "SPELL_HEAL" and arg4 == player then -- tbc
- local spell, heal, crit = arg10, arg12, arg13
- if spell and heal and crit then
- if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
- elseif spell and heal then
- if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
- end
- elseif event == "UNIT_SPELLCAST_SENT" and arg4 then -- fix tbc mouseover macros
- senttarget = arg4
- elseif strfind(event, "SPELLCAST_START", 1) then
- local spell, time = arg1, arg2
-
- if strfind(event, "UNIT_", 1) then -- tbc
- if arg1 ~= "player" then return end
- local spellname, _, _, _, starttime, endtime = UnitCastingInfo("player")
- spell, time = spellname, endtime - starttime
- end
-
- if spell_queue[1] == spell and cache[spell_queue[2]] then
- local sender = player
- local target = senttarget or spell_queue[3]
- local amount = cache[spell_queue[2]][1]
- local casttime = time
-
- if spell == PRAYER_OF_HEALING then
- target = sender
-
- for i=1,4 do
- if CheckInteractDistance("party"..i, 4) then
- pfUI.prediction:Heal(player, UnitName("party"..i), amount, casttime)
- if pfUI.client < 20000 then -- vanilla
- pfUI.prediction.sender:SendHealCommMsg("Heal/" .. UnitName("party"..i) .. "/" .. amount .. "/" .. casttime .. "/")
- else -- tbc
- pfUI.prediction.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), UnitName("party"..i)))
- end
- pfUI.prediction.sender.healing = true
- end
- end
- end
-
- pfUI.prediction:Heal(player, target, amount, casttime)
- if pfUI.client < 20000 then -- vanilla
- pfUI.prediction.sender:SendHealCommMsg("Heal/" .. target .. "/" .. amount .. "/" .. casttime .. "/")
- else -- tbc
- pfUI.prediction.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), target))
- end
- pfUI.prediction.sender.healing = true
-
- elseif spell_queue[1] == spell and L["resurrections"][spell] then
- local target = senttarget or spell_queue[3]
- pfUI.prediction:Ress(player, target)
- pfUI.prediction.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
- pfUI.prediction.sender:SendResCommMsg("RES " .. target)
- pfUI.prediction.sender.resurrecting = true
- end
- elseif strfind(event, "SPELLCAST_FAILED", 1) or strfind(event, "SPELLCAST_INTERRUPTED", 1) then
- if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
- if pfUI.prediction.sender.healing then
- pfUI.prediction:HealStop(player)
- if pfUI.client < 20000 then -- vanilla
- pfUI.prediction.sender:SendHealCommMsg("HealStop")
- else -- tbc
- pfUI.prediction.sender:SendHealCommMsg("001F")
- end
- pfUI.prediction.sender.healing = nil
- elseif pfUI.prediction.sender.resurrecting then
- local target = senttarget or spell_queue[3]
- pfUI.prediction:RessStop(player)
- pfUI.prediction.sender:SendHealCommMsg("Resurrection/stop/")
- pfUI.prediction.sender:SendResCommMsg("RESNO " .. target)
- pfUI.prediction.sender.resurrecting = nil
- end
- elseif event == "SPELLCAST_DELAYED" then
- if pfUI.prediction.sender.healing then
- pfUI.prediction:HealDelay(player, arg1)
- pfUI.prediction.sender:SendHealCommMsg("Healdelay/" .. arg1 .. "/")
- end
- elseif strfind(event, "SPELLCAST_STOP", 1) then
- if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
- pfUI.prediction:HealStop(player)
- end
- end)
-end)