From 87ea8bdd4147fbd1ed39b8e29a16f221b9ef943e Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:33:43 -0500 Subject: [PATCH] Delete the Lua paladin judgement refresh A paladin's white swing refreshes their own judgement debuff on the victim, and the server's edit is packet-silent, so pfUI#45 was the visible symptom: the timer ran to zero while the debuff stayed up. This addon worked around it three ways at once -- a UNIT_CASTEVENT MAINHAND/OFFHAND handler in Core.lua, an AUTO_ATTACK_OTHER fallback in Conditionals.lua, and a CHAT_MSG_COMBAT_SELF_HITS parser in Utility.lua that string-matched "hit"/"crit" and guessed at melee vs spell by looking for a parenthesis -- each rewriting rec.start and pushing the result into pfUI by hand. ClassicAPI mirrors the server rule in the DLL (src/aura/JudgementRefresh.cpp), keyed off SMSG_ATTACKERSTATEUPDATE, which is the visible trigger for the invisible edit. It matches judgements by their DBC marker -- SPELLFAMILY_PALADIN with SPELL_ATTR_EX3_ALWAYS_HIT -- for any paladin's judgements, not only ours. That last detail retires the auto-detection too. The hardcoded judgementSpells list could not cover Turtle's custom judgement ranks, so a cast queued a pending scan that read the target's debuffs 0.5s later, matched names against "^Judgement", and grew the list at runtime. Matching on the DBC attribute needs no list to grow. Gone with it: judgementSpells, pendingJudgements, detectedJudgementDebuffIDs, the pending-scan pass and its queue buffer, and both cast-site hooks. The scan also registered each ID it discovered into lib.sharedDebuffs so [debuff] fallback matching worked for custom IDs. That mattered when detection went through the tracking store; the read path resolves auras through C_UnitAuras now, which returns a debuff whether or not we have a row for it. --- Conditionals.lua | 37 -------- Core.lua | 55 +----------- Utility.lua | 220 +---------------------------------------------- 3 files changed, 5 insertions(+), 307 deletions(-) diff --git a/Conditionals.lua b/Conditionals.lua index 0cdc179..3081f8c 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -897,43 +897,6 @@ local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo, CleveRoids.LastSwing.resistAmount = totalResist or 0 CleveRoids.LastSwing.targetGuid = targetGuid - -- Paladin: refresh active Judgements on melee hit (Nampower fallback for UNIT_CASTEVENT) - if CleveRoids.playerClass == "PALADIN" and targetGuid then - local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil - if lib and lib.objects then - local normalizedTarget = CleveRoids.NormalizeGUID(targetGuid) - if normalizedTarget and lib.objects[normalizedTarget] then - for spellID, rec in pairs(lib.objects[normalizedTarget]) do - if lib.judgementSpells and lib.judgementSpells[spellID] and rec.start and rec.duration then - local remaining = rec.duration + rec.start - GetTime() - if remaining > 0 and rec.caster == "player" then - rec.start = GetTime() - - if CleveRoids.debug then - local spellName = C_Spell.GetSpellName(spellID) or "Unknown" - local baseName = CleveRoids.StripRank(spellName) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on melee hit - new duration: %ds", - baseName, spellID, rec.duration) - ) - end - - -- Sync to pfUI if loaded (pre-7.6 only) - if pfUI and pfUI.api and pfUI.api.libdebuff then - local spellName = C_Spell.GetSpellName(spellID) or nil - local baseName = CleveRoids.StripRank(spellName) - local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target") - local targetLevel = UnitLevel("target") or 0 - if targetName and baseName then - pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player") - end - end - end - end - end - end - end - end end -- Process AUTO_ATTACK_SELF event (player being attacked) diff --git a/Core.lua b/Core.lua index f3d9178..b7a1da5 100644 --- a/Core.lua +++ b/Core.lua @@ -4616,58 +4616,11 @@ function CleveRoids.Frame:ADDON_LOADED(addon) end function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time) - -- Handle melee swings for judgement refresh + -- Melee swings carry no spell to resolve. ClassicAPI mirrors the server's + -- paladin judgement-refresh rule off SMSG_ATTACKERSTATEUPDATE in the DLL + -- (src/aura/JudgementRefresh.cpp), so there is nothing to do here. if action == "MAINHAND" or action == "OFFHAND" then - -- Only process if this is the player's melee swing - if caster == CleveRoids.playerGuid and CleveRoids.playerClass == "PALADIN" then - -- Refresh judgements on the target - -- Defensive: verify libdebuff is a table before accessing properties - local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil - if target and lib and lib.objects then - local normalizedTarget = CleveRoids.NormalizeGUID(target) - if normalizedTarget and lib.objects[normalizedTarget] then - -- Refresh all active Judgements on the target - for spellID, rec in pairs(lib.objects[normalizedTarget]) do - -- Check if this is a judgement by spell ID - if lib.judgementSpells and lib.judgementSpells[spellID] and rec.start and rec.duration then - -- Only refresh if the Judgement is still active and was cast by player - local remaining = rec.duration + rec.start - GetTime() - if remaining > 0 and rec.caster == "player" then - -- Refresh the Judgement by updating the start time - rec.start = GetTime() - - local spellName = C_Spell.GetSpellName(spellID) - local baseName = CleveRoids.StripRank(spellName) or "Unknown" - - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on %s hit - new duration: %ds", - baseName, spellID, action, rec.duration) - ) - end - - -- Also sync to pfUI if it's loaded (pre-7.6 only) - if pfUI and pfUI.api and pfUI.api.libdebuff then - local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target") - local targetLevel = UnitLevel("target") or 0 - - if targetName then - pfUI.api.libdebuff:AddEffect(targetName, targetLevel, baseName, rec.duration, "player") - - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ffaa[pfUI Judgement Refresh]|r Synced %s refresh to pfUI", baseName) - ) - end - end - end - end - end - end - end - end - end - return -- Still return early after processing melee + return end -- Debug channel tracking diff --git a/Utility.lua b/Utility.lua index ea03849..42a5062 100644 --- a/Utility.lua +++ b/Utility.lua @@ -1772,43 +1772,6 @@ lib.sharedDebuffs = lib.sharedDebuffs or { [5209] = 6, -- Challenging Roar } --- JUDGEMENT SPELLS: These are refreshed by the paladin's melee attacks --- Track them by spell ID for refresh detection -lib.judgementSpells = lib.judgementSpells or { - -- Vanilla IDs - [20184] = true, -- Judgement of Justice - [20185] = true, -- Judgement of Light (Rank 1) - [20267] = true, -- Judgement of Light (Rank 2) - [20268] = true, -- Judgement of Light (Rank 3) - [20271] = true, -- Judgement of Light (Rank 4) - [20186] = true, -- Judgement of Wisdom (Rank 1) - [20354] = true, -- Judgement of Wisdom (Rank 2) - [20355] = true, -- Judgement of Wisdom (Rank 3) - [21183] = true, -- Judgement of the Crusader (Rank 1) - [20183] = true, -- Judgement of the Crusader (Rank 2) - [20300] = true, -- Judgement of the Crusader (Rank 3) - [20301] = true, -- Judgement of the Crusader (Rank 4) - [20302] = true, -- Judgement of the Crusader (Rank 5) - [20303] = true, -- Judgement of the Crusader (Rank 6) - - -- Turtle WoW custom judgement IDs (debuff IDs only, not cast spells) - [51751] = true, -- Judgement of Wisdom (Rank 4) - Turtle WoW - [51752] = true, -- Judgement of Wisdom (Rank 5) - Turtle WoW - -- Auto-detection will discover other Turtle WoW judgement debuffs at runtime -} - --- Pending judgement casts: Map cast spell ID to target for debuff ID detection --- When paladin casts Judgement, we need to find what debuff actually appears -lib.pendingJudgements = lib.pendingJudgements or {} - --- Detected judgement debuff IDs: Maps debuff name patterns to spell IDs --- This gets populated as we discover what debuffs actually appear after casting -lib.detectedJudgementDebuffIDs = lib.detectedJudgementDebuffIDs or {} - --- NOTE: Judgement refresh on melee hits is handled by evJudgement (below) --- It only uses chat-based detection when SuperWoW is unavailable; --- otherwise Core.lua handles it via UNIT_CASTEVENT (MAINHAND/OFFHAND) - -- Combined table for backwards compatibility (will be deprecated) lib.durations = lib.durations or {} for k, v in pairs(lib.personalDebuffs) do @@ -2413,7 +2376,6 @@ local _table_insert = table.insert -- Lua 5.0 compatible -- PERFORMANCE: Static buffers for removal tracking - reused every frame -- Using mark-and-sweep pattern instead of table.remove() for O(1) removal -local _pendingJudgementsBuffer = {} local _pendingPersonalBuffer = {} local _pendingCCBuffer = {} local _pendingSharedBuffer = {} @@ -2539,7 +2501,6 @@ delayedTrackingFrame:SetScript("OnUpdate", function() -- PERFORMANCE: Early exit if all queues are empty -- NOTE: Use next() instead of [1] to handle array holes from table.remove() -- Array holes can occur when combat log handlers remove entries mid-iteration - local hasJudgements = lib.pendingJudgements and _next(lib.pendingJudgements) local hasPersonal = lib.pendingPersonalDebuffs and _next(lib.pendingPersonalDebuffs) local hasCC = lib.pendingCCDebuffs and _next(lib.pendingCCDebuffs) local hasShared = lib.pendingSharedDebuffs and _next(lib.pendingSharedDebuffs) @@ -2565,7 +2526,7 @@ delayedTrackingFrame:SetScript("OnUpdate", function() end end - if not (hasJudgements or hasPersonal or hasCC or hasShared or hasOverrides) then + if not (hasPersonal or hasCC or hasShared or hasOverrides) then return end @@ -2590,69 +2551,6 @@ delayedTrackingFrame:SetScript("OnUpdate", function() return nil end - -- Process pending judgement scans to detect actual debuff IDs - if hasJudgements then - local writeIdx = 0 - local pendingList = lib.pendingJudgements - - for i = 1, _getn(pendingList) do - local pending = pendingList[i] - -- Guard against nil entries (can occur if combat log events remove items during iteration) - if pending then - local elapsed = currentTime - pending.timestamp - - -- Scan target after 0.5 seconds to find the actual judgement debuff - if elapsed >= 0.5 then - -- Check if this is still the current target - local currentTargetGUID = CleveRoids.GetGUID("target") - - if currentTargetGUID == pending.targetGUID then - -- Scan all debuffs on target to find judgement-type debuffs - for slot = 1, 16 do - local _, _, _, debuffSpellID = _UnitDebuff("target", slot) - if not debuffSpellID then break end - - local debuffName = C_Spell.GetSpellName(debuffSpellID) - -- Check if this is a judgement debuff (name starts with "Judgement") - if debuffName and _string_find(debuffName, "^Judgement") then - -- Found a judgement debuff! Store it for refresh tracking - if not lib.detectedJudgementDebuffIDs[debuffSpellID] then - lib.detectedJudgementDebuffIDs[debuffSpellID] = true - lib.judgementSpells[debuffSpellID] = true -- Add to refresh list - -- Also register as shared debuff so [debuff]/[nodebuff] fallback detection works - -- for custom Turtle WoW spell IDs not in the hardcoded sharedDebuffs table - if not lib.sharedDebuffs[debuffSpellID] then - lib.sharedDebuffs[debuffSpellID] = 10 -- Standard judgement duration - CleveRoids.InvalidateSpellNameCache() - end - - if debug then - DEFAULT_CHAT_FRAME:AddMessage( - _string_format("|cff00ffff[Judgement Detect]|r Found debuff %s (ID:%d) from cast (ID:%d) - added to refresh + shared list", - debuffName, debuffSpellID, pending.castSpellID) - ) - end - end - end - end - end - -- Item processed, don't copy to output - else - -- Item not ready, keep it - writeIdx = writeIdx + 1 - if writeIdx ~= i then - pendingList[writeIdx] = pending - end - end - end -- if pending - end - - -- PERFORMANCE: Clear remaining slots and update length - for i = writeIdx + 1, _getn(pendingList) do - pendingList[i] = nil - end - end - -- Process pending personal debuffs if hasPersonal then -- Count entries properly using pairs (handles array holes) @@ -3864,22 +3762,6 @@ ev:SetScript("OnEvent", function() comboPoints = comboPoints }) - -- If this is a Judgement spell cast by a Paladin, schedule a scan to find the actual debuff ID - -- Check by ID (known judgement debuffs) OR by name (Turtle WoW custom cast spell IDs) - local isJudgementCast = lib.judgementSpells[spellID] - if not isJudgementCast and CleveRoids.playerClass == "PALADIN" then - local castName = C_Spell.GetSpellName(spellID) - isJudgementCast = castName and _string_find(castName, "^Judgement") - end - if CleveRoids.playerClass == "PALADIN" and isJudgementCast then - table.insert(lib.pendingJudgements, { - timestamp = GetTime(), - castSpellID = spellID, - targetGUID = targetGUID, - targetName = targetName - }) - end - if CleveRoids.debug then local spellName = C_Spell.GetSpellName(trackingSpellID) or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( @@ -4577,21 +4459,6 @@ ev:SetScript("OnEvent", function() spellGoHit = true, -- Already confirmed hit from SPELL_GO }) - -- Judgement scan for Paladins - local isJudgementCast = lib.judgementSpells[spellId] - if not isJudgementCast and CleveRoids.playerClass == "PALADIN" then - local castName = C_Spell.GetSpellName(spellId) - isJudgementCast = castName and string.find(castName, "^Judgement") - end - if CleveRoids.playerClass == "PALADIN" and isJudgementCast then - table.insert(lib.pendingJudgements, { - timestamp = GetTime(), - castSpellID = spellId, - targetGUID = targetGuid, - targetName = targetName - }) - end - if CleveRoids.debug then local trackingName = C_Spell.GetSpellName(trackingSpellID) or "Unknown" DEFAULT_CHAT_FRAME:AddMessage( @@ -5441,91 +5308,6 @@ evCleanup:SetScript("OnEvent", function() end end) --- Judgement refresh on melee hits --- Priority: SuperWoW UNIT_CASTEVENT > Nampower AUTO_ATTACK_OTHER > Chat log fallback --- This chat-based fallback is only used if neither SuperWoW nor Nampower v2.24+ is available -local evJudgement = CreateFrame("Frame", "CleveRoidsLibDebuffJudgementRefreshFrame", UIParent) - --- Only use chat-based detection if SuperWoW and Nampower auto-attack events are not available -local hasAutoAttackEvents = CleveRoids.NampowerAPI and CleveRoids.NampowerAPI.features - and CleveRoids.NampowerAPI.features.hasAutoAttackEvents -if not CleveRoids.hasSuperwow and not hasAutoAttackEvents then - evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS") - evJudgement:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES") -end - -evJudgement:SetScript("OnEvent", function() - -- Skip if SuperWoW or Nampower auto-attack events handle this - if CleveRoids.hasSuperwow then return end - if hasAutoAttackEvents then return end - -- Only process for paladins - if CleveRoids.playerClass ~= "PALADIN" then return end - - if not arg1 then return end - - -- Debug: Show all combat hit events if debug is enabled - if CleveRoids.debug and event == "CHAT_MSG_COMBAT_SELF_HITS" then - DEFAULT_CHAT_FRAME:AddMessage("|cffaaaaaa[Combat Hit Event]|r " .. arg1) - end - - -- Check if this is a melee hit (not a spell hit) - -- CHAT_MSG_COMBAT_SELF_HITS contains both melee and spell hits - -- Filter out spell hits by checking for spell names in parentheses or common patterns - -- Melee hits look like: "You hit Target for X." or "You crit Target for X." - local isSpellHit = string.find(arg1, "%(") -- Spell hits often have parentheses - if isSpellHit then return end - - -- Must contain "hit" or "crit" to be a valid melee attack - local lowerMsg = string.lower(arg1) - local hasHit = string.find(lowerMsg, "hit") or string.find(lowerMsg, "crit") - if not hasHit then return end - - -- Get current target - local targetGUID = CleveRoids.GetGUID("target") - if not targetGUID then return end - - if not lib.objects[targetGUID] then return end - - -- Refresh all active Judgements on the target - for spellID, rec in pairs(lib.objects[targetGUID]) do - if lib.judgementSpells[spellID] and rec.start and rec.duration then - -- Only refresh if the Judgement is still active and was cast by player - local remaining = rec.duration + rec.start - GetTime() - if remaining > 0 and rec.caster == "player" then - -- Refresh the Judgement by updating the start time - rec.start = GetTime() - - if CleveRoids.debug then - local spellName = C_Spell.GetSpellName(spellID) or "Unknown" - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ffaa[Judgement Refresh]|r Refreshed %s (ID:%d) on melee hit - new duration: %ds", - spellName, spellID, rec.duration) - ) - end - - -- Also sync to pfUI if it's loaded (pre-7.6 only) - if pfUI and pfUI.api and pfUI.api.libdebuff then - local targetName = lib.guidToName[targetGUID] or UnitName("target") - local targetLevel = UnitLevel("target") or 0 - local spellName = C_Spell.GetSpellName(spellID) - - if spellName and targetName then - local effectName = CleveRoids.StripRank(spellName) - pfUI.api.libdebuff:AddEffect(targetName, targetLevel, effectName, rec.duration, "player") - - if CleveRoids.debug then - DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ffaa[pfUI Judgement Refresh]|r Synced %s refresh to pfUI", - effectName) - ) - end - end - end - end - end - end -end) - -- TALENT MODIFIER SYSTEM -- Database of talent modifiers for debuff durations