libpredict.lua changes!

* Removed SuperWoW dependency (UNIT_CASTEVENT removed)
* Replaced SPELLCAST_START/STOP/FAILED/INTERRUPTED/DELAYED with Nampower equivalents (SPELL_START_SELF, SPELL_GO_SELF, SPELL_FAILED_SELF, SPELL_DELAYED_SELF)
* Replaced CHAT_MSG_SPELL_SELF_BUFF text parsing with SPELL_HEAL_BY_SELF for accurate heal amounts including crit detection
* Routed SPELL_START_SELF/OTHER, SPELL_GO_SELF/OTHER, SPELL_FAILED_OTHER through existing libdebuff hook system to avoid duplicate event registration
* Added 60-second resurrection timer: symbol persists after successful cast even if caster attempts and cancels a second cast
* Added GUID->Name cache for reliable target resolution when casting resurrection on dead players (UnitExists returns nil for corpses)
* Auto-enables NP_EnableSpellHealEvents CVar required for SPELL_HEAL_BY_SELF
This commit is contained in:
Meow
2026-03-02 08:51:23 +01:00
parent 487ea2aca1
commit 3fbce5be8f
+363 -288
View File
@@ -31,10 +31,17 @@ if pfUI.api.libpredict then return end
local libdebuff_available = (pfUI.api.libdebuff and pfUI.api.libdebuff.GetBestAuraCast) and true or false
-- Check if Nampower is available for SPELL_FAILED events
local hasNampower = GetNampowerVersion ~= nil
local senttarget
local heals, ress, events, hots = {}, {}, {}, {}
local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
local player = UnitName("player")
local cache, gear_string = {}, ""
local rejuvDuration, renewDuration = 12, 15 --default durations
local ressGuidToName = {} -- [casterGuid] = casterName, for SPELL_FAILED_OTHER cleanup
local healGuidToName = {} -- [casterGuid] = casterName, for SPELL_FAILED_OTHER cleanup
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s Rez-Fenster)
local RESS_TIMEOUT = 60 -- Vanilla: Rez-Angebot läuft nach 60s ab
local PRAYER_OF_HEALING
do -- Prayer of Healing
@@ -96,10 +103,8 @@ do -- Regrowth
REGROWTH = locales[GetLocale()] or locales["enUS"]
end
-- SuperWoW detection
local superwow_active = SpellInfo ~= nil
-- Spell IDs für UNIT_CASTEVENT (SuperWoW)
-- Spell IDs für SPELL_GO_SELF callback (Nampower) - Instant HoT Detection
local SPELL_IDS = {
-- Rejuvenation (alle Ränge)
[774] = "Reju", [1058] = "Reju", [1430] = "Reju", [2090] = "Reju", [2091] = "Reju",
@@ -117,11 +122,6 @@ libpredict:RegisterEvent("CHAT_MSG_ADDON")
libpredict:RegisterEvent("PLAYER_TARGET_CHANGED")
libpredict:RegisterEvent("PLAYER_LOGOUT")
-- SuperWoW: Registriere UNIT_CASTEVENT für akkurate Instant-HoT Detection
if superwow_active then
libpredict:RegisterEvent("UNIT_CASTEVENT")
end
libpredict:SetScript("OnEvent", function()
-- Handle shutdown to prevent crash 132
if event == "PLAYER_LOGOUT" then
@@ -135,106 +135,208 @@ libpredict:SetScript("OnEvent", function()
elseif event == "UNIT_HEALTH" then
local name = UnitName(arg1)
if name and ress[name] and not UnitIsDeadOrGhost(arg1) then
ress[name] = nil -- Reuse 'name' variable instead of calling UnitName again
end
elseif event == "UNIT_CASTEVENT" and superwow_active then
-- arg1 = casterGUID, arg2 = targetGUID, arg3 = event type, arg4 = spellId, arg5 = castTime
local casterGUID, targetGUID, castEvent, spellId = arg1, arg2, arg3, arg4
-- Nur eigene Casts (player)
local _, playerGUID = UnitExists("player")
if casterGUID ~= playerGUID then return end
-- Nur "CAST" events (erfolgreiche Instant-Casts)
if castEvent ~= "CAST" then return end
-- Prüfe ob es ein Instant-HoT ist
local hotType = SPELL_IDS[spellId]
if not hotType then return end
-- Finde Target Name
local targetName
for i = 1, 40 do
local unit = "raid" .. i
if UnitExists(unit) then
local _, guid = UnitExists(unit)
if guid == targetGUID then
targetName = UnitName(unit)
break
end
end
end
if not targetName then
for i = 1, 4 do
local unit = "party" .. i
if UnitExists(unit) then
local _, guid = UnitExists(unit)
if guid == targetGUID then
targetName = UnitName(unit)
break
end
end
end
end
if not targetName then
local _, guid = UnitExists("player")
if guid == targetGUID then
targetName = UnitName("player")
end
end
if not targetName then
local _, guid = UnitExists("target")
if guid == targetGUID then
targetName = UnitName("target")
end
end
if not targetName then return end
-- Duration bestimmen
local duration
if hotType == "Reju" then
duration = rejuvDuration or 12
elseif hotType == "Renew" then
duration = renewDuration or 15
end
-- Extract rank from spellId (if SpellInfo available)
local rank = nil
if SpellInfo then
local _, rankString = SpellInfo(spellId)
if rankString and rankString ~= "" then
rank = tonumber((string.gsub(rankString, "Rank ", ""))) or nil
end
end
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ffff[UNIT_CASTEVENT]|r spell=%s target=%s dur=%s rank=%s",
hotType, targetName, tostring(duration), tostring(rank or "?")))
end
-- Sende HoT mit Rank
local playerName = UnitName("player")
libpredict:Hot(playerName, targetName, hotType, duration, nil, "UNIT_CASTEVENT", rank)
-- Sende HealComm Nachricht mit Rank (backwards compatible: rank optional)
-- Use "0" for unknown rank instead of empty string to avoid parsing issues
local rankStr = rank and tostring(rank) or "0"
if libpredict.sender and libpredict.sender.SendHealCommMsg then
libpredict.sender:SendHealCommMsg(hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/")
else
-- Fallback: direkt senden (smart channel selection)
local msg = hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/"
if GetNumRaidMembers() > 0 then
SendAddonMessage("HealComm", msg, "RAID")
elseif GetNumPartyMembers() > 0 then
SendAddonMessage("HealComm", msg, "PARTY")
end
-- Note: BATTLEGROUND channel not used (no reliable way to detect BG in Vanilla)
ress[name] = nil
end
end
end)
-- GUID->Name Cache für tote Spieler (UnitExists gibt nil für Tote)
local guidNameCache = {} -- [guid] = name
local function resolveNameFromGuid(guid)
if not guid then return nil end
if guidNameCache[guid] then return guidNameCache[guid] end
local unit = UnitExists(guid)
local name = unit and type(unit) == "string" and UnitName(unit) or nil
if name then guidNameCache[guid] = name end
return name
end
-- Register with libdebuff hooks
local function cacheRaidNames()
-- Alle Raid/Party Mitglieder cachen solange sie noch auffindbar sind
for i = 1, GetNumRaidMembers() do
local unit = "raid" .. i
if UnitExists(unit) then
local _, guid = UnitExists(unit)
local name = UnitName(unit)
if guid and name then guidNameCache[guid] = name end
end
end
for i = 1, GetNumPartyMembers() do
local unit = "party" .. i
if UnitExists(unit) then
local _, guid = UnitExists(unit)
local name = UnitName(unit)
if guid and name then guidNameCache[guid] = name end
end
end
-- Spieler selbst
local _, pguid = UnitExists("player")
if pguid then guidNameCache[pguid] = UnitName("player") end
end
local function isRezSpell(spellId)
if not L["resurrections"] then return false end
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
return spellName and L["resurrections"][spellName]
end
-- Cache beim Betreten der Welt aufbauen
libpredict:RegisterEvent("PLAYER_ENTERING_WORLD")
libpredict:RegisterEvent("RAID_ROSTER_UPDATE")
libpredict:RegisterEvent("PARTY_MEMBERS_CHANGED")
local origOnEvent = libpredict:GetScript("OnEvent")
libpredict:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" or event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then
cacheRaidNames()
end
if origOnEvent then origOnEvent() end
end)
-- SPELL_START_SELF: eigener Cast gestartet (Rez + Heals)
-- Signature: fn(spellId, casterGuid, targetGuid, castTime)
pfUI.libdebuff_spell_start_self_hooks = pfUI.libdebuff_spell_start_self_hooks or {}
pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
cacheRaidNames()
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
if not spellName then return end
local target = resolveNameFromGuid(targetGuid) or senttarget or spell_queue[3]
-- Rez
if isRezSpell(spellId) then
if player and target then
libpredict:Ress(player, target, casterGuid)
libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
libpredict.sender:SendResCommMsg("RES " .. target)
libpredict.sender.resurrecting = true
end
return
end
-- Heal prediction
if spell_queue[1] == spellName and cache[spell_queue[2]] then
local amount = cache[spell_queue[2]][1]
local casttime = castTime
if spellName == REGROWTH then
local fullSpell = spell_queue[2]
local _, _, rankStr = fullSpell and string.find(fullSpell, "Rank (%d+)")
local rankNum = rankStr and tonumber(rankStr) or nil
if libpredict.sender.regrowth_timer then
libpredict.sender.regrowth_target_next = target
libpredict.sender.regrowth_rank_next = rankNum
else
libpredict.sender.regrowth_target = target
libpredict.sender.regrowth_rank = rankNum
end
end
if spellName == PRAYER_OF_HEALING then
for i = 1, 4 do
if CheckInteractDistance("party"..i, 4) then
local pname = UnitName("party"..i)
libpredict:Heal(player, pname, amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. pname .. "/" .. amount .. "/" .. casttime .. "/")
libpredict.sender.healing = true
end
end
end
libpredict:Heal(player, target, amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. (target or "") .. "/" .. amount .. "/" .. casttime .. "/")
libpredict.sender.healing = true
end
-- current_cast für onCastFailed() setzen
libpredict.sender.current_cast = spellName
libpredict.sender.current_cast_target = target
end
-- SPELL_START_OTHER: fremder Rez-Cast gestartet
-- Signature: fn(spellId, casterGuid, targetGuid, castTime)
pfUI.libdebuff_spell_start_other_hooks = pfUI.libdebuff_spell_start_other_hooks or {}
pfUI.libdebuff_spell_start_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
if not isRezSpell(spellId) then return end
local casterName = resolveNameFromGuid(casterGuid)
local targetName = resolveNameFromGuid(targetGuid)
if casterName and targetName then
libpredict:Ress(casterName, targetName, casterGuid)
end
end
-- SPELL_GO_SELF: eigener Cast gelandet - HoTs + eigener Rez Timer
-- Signature: fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
pfUI.libdebuff_spell_go_hooks["libpredict"] = function(spellId, a1, a2, a3, a4, a5, a6, a7)
-- Instant HoTs
local hotType = SPELL_IDS[spellId]
if hotType then
local targetGuid = a4
local targetName = resolveNameFromGuid(targetGuid)
if targetName then
local duration
if hotType == "Reju" then duration = rejuvDuration or 12
elseif hotType == "Renew" then duration = renewDuration or 15
end
local rank = 0
if GetSpellRecField then
local rankStr = GetSpellRecField(spellId, "rank")
if rankStr and rankStr ~= "" then
rank = tonumber((string.gsub(rankStr, "Rank ", ""))) or 0
end
end
local playerName = UnitName("player")
libpredict:Hot(playerName, targetName, hotType, duration, nil, "SPELL_GO_SELF", rank)
local rankStr = tostring(rank)
if libpredict.sender and libpredict.sender.SendHealCommMsg then
libpredict.sender:SendHealCommMsg(hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/")
elseif GetNumRaidMembers() > 0 then
SendAddonMessage("HealComm", hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/", "RAID")
elseif GetNumPartyMembers() > 0 then
SendAddonMessage("HealComm", hotType .. "/" .. targetName .. "/" .. duration .. "/" .. rankStr .. "/", "PARTY")
end
end
end
-- Eigener Rez gelandet: Timer setzen
if isRezSpell(spellId) then
local targetGuid = a4
local targetName = resolveNameFromGuid(targetGuid)
local playerName = UnitName("player")
if playerName and targetName then
libpredict:RessSetTimer(playerName, targetName)
end
end
end
-- SPELL_GO_OTHER: fremder Rez gelandet - Timer setzen
-- Signature: fn(spellId, casterGuid, targetGuid)
pfUI.libdebuff_spell_go_other_hooks = pfUI.libdebuff_spell_go_other_hooks or {}
pfUI.libdebuff_spell_go_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid)
if not isRezSpell(spellId) then return end
local casterName = resolveNameFromGuid(casterGuid)
local targetName = resolveNameFromGuid(targetGuid)
if casterName and targetName then
libpredict:RessSetTimer(casterName, targetName)
end
end
-- SPELL_FAILED_OTHER: abgebrochene Resses/Heals entfernen
-- Signature: fn(casterGuid, spellId)
pfUI.libdebuff_spell_failed_other_hooks = pfUI.libdebuff_spell_failed_other_hooks or {}
pfUI.libdebuff_spell_failed_other_hooks["libpredict"] = function(casterGuid, spellId)
if not casterGuid then return end
local ressName = ressGuidToName[casterGuid]
if ressName then
libpredict:RessStop(ressName)
end
local healName = healGuidToName[casterGuid]
if healName then libpredict:HealStop(healName) end
end
libpredict:SetScript("OnUpdate", function()
-- throttle cleanup - no need to check every frame
local now = pfUI.uf.now or GetTime()
@@ -247,6 +349,16 @@ libpredict:SetScript("OnUpdate", function()
events[timestamp] = nil
end
end
-- Rez-Timeout: Angebot nach 60s entfernen wenn Spieler nicht annimmt
for target, senders in pairs(ress_timers) do
for sender, expiry in pairs(senders) do
if now >= expiry then
senders[sender] = nil
if ress[target] then ress[target][sender] = nil end
end
end
end
end)
function libpredict:ParseComm(sender, msg)
@@ -359,7 +471,30 @@ function libpredict:ParseChatMessage(sender, msg, comm)
libpredict:Heal(sender, target, heal, time)
end
elseif msgtype == "Ress" then
libpredict:Ress(sender, target)
-- HealComm: nur für fremde Spieler (eigene Resses via SPELL_START_SELF hook)
local playerName = UnitName("player")
if sender ~= playerName then
local senderGuid
for i = 1, GetNumRaidMembers() do
local unit = "raid" .. i
if UnitName(unit) == sender then
local _, guid = UnitExists(unit)
senderGuid = guid
break
end
end
if not senderGuid then
for i = 1, GetNumPartyMembers() do
local unit = "party" .. i
if UnitName(unit) == sender then
local _, guid = UnitExists(unit)
senderGuid = guid
break
end
end
end
libpredict:Ress(sender, target, senderGuid)
end
elseif msgtype == "Hot" then
-- Duplikat-Check: gleicher sender+target+spell innerhalb DUPLICATE_WINDOW ignorieren
local now = pfUI.uf.now or GetTime()
@@ -407,7 +542,7 @@ function libpredict:AddEvent(time, target)
table.insert(events[time], target)
end
function libpredict:Heal(sender, target, amount, duration)
function libpredict:Heal(sender, target, amount, duration, senderGuid)
if not sender or not target or not amount or not duration then
return
end
@@ -416,6 +551,7 @@ function libpredict:Heal(sender, target, amount, duration)
local timeout = duration/1000 + now
heals[target] = heals[target] or {}
heals[target][sender] = { amount, timeout }
if senderGuid then healGuidToName[senderGuid] = sender end
libpredict:AddEvent(timeout, target)
end
@@ -489,6 +625,10 @@ function libpredict:HealStop(sender)
end
end
end
-- cleanup reverse lookup
for guid, name in pairs(healGuidToName) do
if name == sender then healGuidToName[guid] = nil end
end
end
function libpredict:HealDelay(sender, delay)
@@ -503,19 +643,39 @@ function libpredict:HealDelay(sender, delay)
end
end
function libpredict:Ress(sender, target)
function libpredict:Ress(sender, target, senderGuid)
ress[target] = ress[target] or {}
ress[target][sender] = true
if senderGuid then ressGuidToName[senderGuid] = sender end
-- kein Timer hier - Timer wird erst gesetzt wenn Cast wirklich durchgeht
end
function libpredict:RessSetTimer(sender, target)
ress_timers[target] = ress_timers[target] or {}
local existing = ress_timers[target][sender]
if not existing or GetTime() >= existing then
ress_timers[target][sender] = GetTime() + RESS_TIMEOUT
else
end
end
function libpredict:RessStop(sender)
local now = GetTime()
for ttarget, t in pairs(ress) do
for tsender in pairs(ress[ttarget]) do
if sender == tsender then
ress[ttarget][tsender] = nil
local expiry = ress_timers[ttarget] and ress_timers[ttarget][tsender]
if not expiry or now >= expiry then
ress[ttarget][tsender] = nil
if ress_timers[ttarget] then ress_timers[ttarget][tsender] = nil end
else
end
end
end
end
for guid, name in pairs(ressGuidToName) do
if name == sender then ressGuidToName[guid] = nil end
end
end
function libpredict:UnitGetIncomingHeals(unit)
@@ -557,12 +717,8 @@ function libpredict:UnitHasIncomingResurrection(unit)
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")
local rejuvDuration, renewDuration = 12, 15 --default durations
local hotsetbonus = libtipscan:GetScanner("hotsetbonus")
resetcache:RegisterEvent("PLAYER_ENTERING_WORLD")
resetcache:RegisterEvent("LEARNED_SPELL_IN_TAB")
@@ -667,9 +823,7 @@ hooksecurefunc("CastSpell", function(id, bookType)
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
end
-- Instant-HoTs: Mit SuperWoW nutzen wir UNIT_CASTEVENT (akkurater)
-- Ohne SuperWoW: Fallback auf Hook-Methode mit Cooldown
if superwow_active then return end
-- Instant-HoTs: libdebuff/Nampower via GetHotDuration, Hook-Methode als Fallback
if effect == REJUVENATION then
local target = spell_queue[3]
@@ -735,9 +889,7 @@ hooksecurefunc("CastSpellByName", function(effect, target)
spell_queue[3] = target or mouseover or default
end
-- Instant-HoTs: Mit SuperWoW nutzen wir UNIT_CASTEVENT (akkurater)
-- Ohne SuperWoW: Fallback auf Hook-Methode mit Cooldown
if superwow_active then return end
-- Instant-HoTs: libdebuff/Nampower via GetHotDuration, Hook-Methode als Fallback
if effect == REJUVENATION then
local hotTarget = target or mouseover or default
@@ -793,9 +945,7 @@ hooksecurefunc("UseAction", function(slot, target, selfcast)
rankNum = tonumber((string.gsub(rank, "Rank ", ""))) or nil
end
-- Instant-HoTs: Mit SuperWoW nutzen wir UNIT_CASTEVENT (akkurater)
-- Ohne SuperWoW: Fallback auf Hook-Methode mit Cooldown
if superwow_active then return end
-- Instant-HoTs: libdebuff/Nampower via GetHotDuration, Hook-Methode als Fallback
if effect == REJUVENATION then
local hotTarget = spell_queue[3]
@@ -885,211 +1035,136 @@ libpredict.sender:SetScript("OnUpdate", function()
end
end)
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")
-- Nampower: Register SPELL_FAILED_SELF for more reliable cast fail detection
if hasNampower then
libpredict.sender:RegisterEvent("SPELL_FAILED_SELF")
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[libpredict]|r Nampower detected - SPELL_FAILED_SELF registered")
end
else
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libpredict]|r Nampower NOT detected - using vanilla SPELLCAST_FAILED only")
end
end
-- Nampower events (SPELL_START_SELF / SPELL_GO_SELF via libdebuff hooks)
libpredict.sender:RegisterEvent("SPELL_FAILED_SELF")
libpredict.sender:RegisterEvent("SPELL_DELAYED_SELF")
libpredict.sender:RegisterEvent("SPELL_HEAL_BY_SELF")
-- force cache updates
libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
-- Shared cleanup helper for failed/interrupted casts
local function onCastFailed()
local s = libpredict.sender
if s.healing then
libpredict:HealStop(UnitName("player"))
s:SendHealCommMsg("Healstop")
s.healing = nil
elseif s.resurrecting then
local target = s.current_cast_target or senttarget or spell_queue[3]
libpredict:RessStop(UnitName("player"))
s:SendHealCommMsg("Resurrection/stop/")
s:SendResCommMsg("RESNO " .. (target or ""))
s.resurrecting = nil
end
if s.current_cast == REGROWTH then
s.regrowth_timer = nil
s.regrowth_start = nil
s.regrowth_target_next = nil
s.regrowth_start_next = nil
end
s.current_cast = nil
s.current_cast_target = nil
end
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
-- ============================================================
-- NAMPOWER PATH
-- ============================================================
if event == "SPELL_HEAL_BY_SELF" then
-- arg1=targetGuid, arg2=casterGuid, arg3=spellId, arg4=amount, arg5=critical, arg6=periodic
local spellId = arg3
local amount = arg4
local isCrit = arg5 == 1
local isPeriodic = arg6 == 1
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
if spellName and spell_queue[1] == spellName then
UpdateCache(spell_queue[2], amount, isCrit)
end
elseif event == "SPELL_START_SELF" then
-- arg2=spellId, arg4=targetGuid, arg6=castTime
local spellId = arg2
local castTime = arg6
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
if not spellName then return end
-- target: via guidNameCache (tote Spieler), fallback spell_queue
local targetGuid = arg4
local target = resolveNameFromGuid(targetGuid) or senttarget or spell_queue[3]
this.current_cast = spellName
this.current_cast_target = target
-- Rez: HealComm senden (Ress() bereits via libdebuff_spell_start_self_hooks)
if L["resurrections"][spellName] then
libpredict.sender:SendHealCommMsg("Resurrection/" .. (target or "") .. "/start/")
libpredict.sender:SendResCommMsg("RES " .. (target or ""))
libpredict.sender.resurrecting = true
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
-- Heal prediction
if spell_queue[1] == spellName and cache[spell_queue[2]] then
local amount = cache[spell_queue[2]][1]
local casttime = castTime
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 == "SPELLCAST_START" then
local spell, time = arg1, arg2
-- Resolve target from SPELL_CAST_EVENT (via libdebuff) for Nampower queued casts.
-- SPELL_CAST_EVENT fires right before SPELLCAST_START with the actual targetGuid,
-- solving the problem where spell_queue[3] is stale because CastSpellByName hook
-- could not update it while current_cast was set (Nampower spell queuing).
local pending = pfUI.libpredict_pending_cast
local pendingTarget = nil
if pending and pending.spellName == spell and pending.targetGuid
and pending.time and (GetTime() - pending.time) < 1 then
pendingTarget = UnitName(pending.targetGuid)
-- Validate: must be a friendly unit for heal prediction
if pendingTarget and pendingTarget ~= UNKNOWNOBJECT and pendingTarget ~= UKNOWNBEING then
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage(string.format(
"|cff00ffff[libpredict]|r SPELL_CAST_EVENT target: %s (guid: %s) for %s",
pendingTarget, pending.targetGuid, spell))
end
else
pendingTarget = nil
end
-- Clear pending after consumption
pending.spellId = nil
pending.spellName = nil
pending.targetGuid = nil
pending.time = nil
end
-- Speichere aktuellen Cast (wird nicht von Instant-Hooks überschrieben)
this.current_cast = spell
this.current_cast_target = pendingTarget or senttarget or spell_queue[3]
if spell_queue[1] == spell and cache[spell_queue[2]] then
local sender = player
local target = pendingTarget or senttarget or spell_queue[3]
local amount = cache[spell_queue[2]][1]
local casttime = time
if spell == REGROWTH then
-- Extract rank from spell_queue[2] which contains "spell + rank"
if spellName == REGROWTH then
local fullSpell = spell_queue[2]
local _, _, rankStr = fullSpell and string.find(fullSpell, "Rank (%d+)")
local rankNum = rankStr and tonumber(rankStr) or nil
if this.regrowth_timer then
this.regrowth_target_next = pendingTarget or spell_queue[3]
this.regrowth_target_next = target
this.regrowth_rank_next = rankNum
else
this.regrowth_target = pendingTarget or spell_queue[3]
this.regrowth_target = target
this.regrowth_rank = rankNum
end
end
if spell == PRAYER_OF_HEALING then
target = sender
for i=1,4 do
if spellName == PRAYER_OF_HEALING then
for i = 1, 4 do
if CheckInteractDistance("party"..i, 4) then
libpredict:Heal(player, UnitName("party"..i), amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. UnitName("party"..i) .. "/" .. amount .. "/" .. casttime .. "/")
local pname = UnitName("party"..i)
libpredict:Heal(player, pname, amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. pname .. "/" .. amount .. "/" .. casttime .. "/")
libpredict.sender.healing = true
end
end
end
libpredict:Heal(player, target, amount, casttime)
libpredict.sender:SendHealCommMsg("Heal/" .. target .. "/" .. amount .. "/" .. casttime .. "/")
libpredict.sender:SendHealCommMsg("Heal/" .. (target or "") .. "/" .. amount .. "/" .. casttime .. "/")
libpredict.sender.healing = true
end
elseif spell_queue[1] == spell and L["resurrections"][spell] then
local target = pendingTarget or 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 event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
if libpredict.sender.healing then
libpredict:HealStop(player)
-- DEBUG: Log when sending HealStop
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff00ff[libpredict TX]|r Sending HealStop (via " .. event .. ") to group")
end
libpredict.sender:SendHealCommMsg("Healstop")
libpredict.sender.healing = nil
elseif libpredict.sender.resurrecting then
local target = this.current_cast_target or senttarget or spell_queue[3]
libpredict:RessStop(player)
libpredict.sender:SendHealCommMsg("Resurrection/stop/")
libpredict.sender:SendResCommMsg("RESNO " .. target)
libpredict.sender.resurrecting = nil
end
-- Nutze current_cast für Regrowth cleanup
if this.current_cast == REGROWTH then
this.regrowth_timer = nil
this.regrowth_start = nil
this.regrowth_target_next = nil
this.regrowth_start_next = nil
end
-- Cleanup
this.current_cast = nil
this.current_cast_target = nil
elseif event == "SPELL_FAILED_SELF" then
-- Nampower SPELL_FAILED_SELF: More reliable than vanilla SPELLCAST_FAILED
-- Same cleanup as SPELLCAST_FAILED
if libpredict.sender.healing then
libpredict:HealStop(player)
-- DEBUG: Log when sending HealStop
if libpredict.debug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff00ff[libpredict TX]|r Sending HealStop to group (SPELL_FAILED_SELF)")
end
libpredict.sender:SendHealCommMsg("Healstop")
libpredict.sender.healing = nil
elseif libpredict.sender.resurrecting then
local target = this.current_cast_target or senttarget or spell_queue[3]
libpredict:RessStop(player)
libpredict.sender:SendHealCommMsg("Resurrection/stop/")
libpredict.sender:SendResCommMsg("RESNO " .. target)
libpredict.sender.resurrecting = nil
end
-- Regrowth cleanup
if this.current_cast == REGROWTH then
this.regrowth_timer = nil
this.regrowth_start = nil
this.regrowth_target_next = nil
this.regrowth_start_next = nil
end
-- Cleanup
this.current_cast = nil
this.current_cast_target = nil
elseif event == "SPELLCAST_DELAYED" then
if libpredict.sender.healing then
libpredict:HealDelay(player, arg1)
libpredict.sender:SendHealCommMsg("Healdelay/" .. arg1 .. "/")
end
elseif event == "SPELLCAST_STOP" then
elseif event == "SPELL_GO_SELF" then
-- arg2=spellId
libpredict:HealStop(player)
-- Nur Regrowth wird hier verarbeitet (hat Cast-Zeit)
-- Nutze this.current_cast (wird bei SPELLCAST_START gesetzt, nicht von Instant-Hooks überschrieben)
if this.current_cast == REGROWTH then
local spellName = GetSpellRecField and GetSpellRecField(arg2, "name")
if spellName == REGROWTH then
local now = pfUI.uf.now or GetTime()
if this.regrowth_timer then
-- Bereits ein Regrowth aktiv, speichere für den nächsten
this.regrowth_start_next = now
else
this.regrowth_start = now
end
this.regrowth_timer = now + 0.1
end
-- Cleanup
this.current_cast = nil
this.current_cast_target = nil
elseif event == "SPELL_FAILED_SELF" then
onCastFailed()
elseif event == "SPELL_DELAYED_SELF" then
-- arg1=casterGuid, arg2=delayMs
if libpredict.sender.healing then
libpredict:HealDelay(player, arg2)
libpredict.sender:SendHealCommMsg("Healdelay/" .. arg2 .. "/")
end
end
end)
@@ -1172,7 +1247,7 @@ _G.SlashCmdList.HOTDEBUG = function()
end
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[FALLBACK]|r Legacy prediction system: ACTIVE")
DEFAULT_CHAT_FRAME:AddMessage(" Using UNIT_CASTEVENT + HealComm messages")
DEFAULT_CHAT_FRAME:AddMessage(" Using Hook-Methode + HealComm messages")
-- Show active HoTs in tracking
local hotCount = 0