From 21119c8bd5289236e2cf20cd13314433eadf01e1 Mon Sep 17 00:00:00 2001 From: Jrc13245 Date: Tue, 25 Nov 2025 16:58:44 -0500 Subject: [PATCH] fix reactive for overpower and revenge to see as active in any stance. --- Conditionals.lua | 8 +- Core.lua | 96 ++++++++++++++++- Extensions/Tooltip/Generic.lua | 26 ++++- Utility.lua | 188 +++++++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+), 6 deletions(-) diff --git a/Conditionals.lua b/Conditionals.lua index 683be23..d2921d7 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -1185,7 +1185,13 @@ function CleveRoids.GetActionButtonInfo(slot) end function CleveRoids.IsReactiveUsable(spellName) + -- First check combat log-based proc tracking (works across stances) + if CleveRoids.HasReactiveProc and CleveRoids.HasReactiveProc(spellName) then + return 1 + end + -- Use Nampower's IsSpellUsable if available (more accurate) + -- Note: IsSpellUsable doesn't actually exist in Nampower, but keeping for future compatibility if IsSpellUsable then local usable, oom = IsSpellUsable(spellName) if usable == 1 and oom ~= 1 then @@ -1195,7 +1201,7 @@ function CleveRoids.IsReactiveUsable(spellName) end end - -- Fallback to original method + -- Fallback to action bar slot checking (requires correct stance) if not CleveRoids.reactiveSlots[spellName] then return false end local actionSlot = CleveRoids.reactiveSlots[spellName] local isUsable, oom = CleveRoids.Hooks.OriginalIsUsableAction(actionSlot) diff --git a/Core.lua b/Core.lua index a1edf47..1352d91 100644 --- a/Core.lua +++ b/Core.lua @@ -594,8 +594,18 @@ function CleveRoids.TestForActiveAction(actions) local onCooldown = (start > 0 and duration > 0) if actions.active.isReactive then - -- Use Nampower's IsSpellUsable if available for better detection - if IsSpellUsable then + -- Check combat log-based proc tracking first (stance-independent) + if CleveRoids.HasReactiveProc and CleveRoids.HasReactiveProc(actions.active.action) then + -- Proc is active, show as usable if in range and have enough rage/mana + if actions.active.inRange ~= 0 and not actions.active.oom then + actions.active.usable = 1 + elseif pfUI and pfUI.bars and actions.active.oom then + actions.active.usable = 2 -- pfUI: out of mana/rage + else + actions.active.usable = nil + end + -- Use Nampower's IsSpellUsable if available (stance-aware fallback) + elseif IsSpellUsable then local usable, oom = IsSpellUsable(actions.active.action) if usable == 1 and oom ~= 1 then actions.active.usable = (pfUI and pfUI.bars) and nil or 1 @@ -3349,6 +3359,10 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage('/cleveroid diagnosetalent - Diagnose talent modifier issues') DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Equipment Modifiers:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid testequip - Test equipment modifier for a spell') + DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Reactive Proc Tracking:|r") + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid listprocs - Show active reactive ability procs') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid setproc [duration] - Manually set proc (testing)') + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid clearproc [spell|all] - Clear reactive proc(s)') DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Casting Detection:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid testcasting - Test [selfcasting]/[noselfcasting] conditionals') return @@ -3811,6 +3825,84 @@ SlashCmdList["CLEVEROID"] = function(msg) return end + -- listprocs (show active reactive procs) + if cmd == "listprocs" or cmd == "procs" or cmd == "reactive" then + CleveRoids.Print("=== Active Reactive Procs ===") + local found = false + local now = GetTime() + local _, currentTargetGUID = UnitExists("target") + + if CleveRoids.reactiveProcs then + for spellName, procData in pairs(CleveRoids.reactiveProcs) do + if procData and procData.expiry and procData.expiry > now then + local remaining = procData.expiry - now + local guidInfo = "" + + if procData.targetGUID then + local matches = currentTargetGUID and (currentTargetGUID == procData.targetGUID) + if matches then + guidInfo = " |cff00ff00[Current Target]|r" + else + local targetName = UnitName("target") + if targetName then + guidInfo = " |cffff0000[Wrong Target: " .. targetName .. "]|r" + else + guidInfo = " |cffff0000[No Target]|r" + end + end + end + + CleveRoids.Print(spellName .. ": " .. string.format("%.1fs", remaining) .. " remaining" .. guidInfo) + found = true + end + end + end + + if not found then + CleveRoids.Print("|cffffaa00No active reactive procs|r") + end + return + end + + -- setproc (manually set a reactive proc for testing) + if cmd == "setproc" or cmd == "procset" then + if val == "" then + CleveRoids.Print("Usage: /cleveroid setproc [duration]") + CleveRoids.Print("Example: /cleveroid setproc Overpower 5") + CleveRoids.Print("Note: Uses current target's GUID for target-specific procs") + return + end + + local duration = tonumber(val2) or 5.0 + local _, targetGUID = UnitExists("target") + + if CleveRoids.SetReactiveProc then + CleveRoids.SetReactiveProc(val, duration, targetGUID) + local guidMsg = targetGUID and (" for target [" .. (UnitName("target") or "Unknown") .. "]") or "" + CleveRoids.Print("Set " .. val .. " proc for " .. duration .. " seconds" .. guidMsg) + else + CleveRoids.Print("|cffff0000Reactive proc system not loaded!|r") + end + return + end + + -- clearproc (clear a reactive proc) + if cmd == "clearproc" or cmd == "procclear" then + if val == "" or val == "all" then + CleveRoids.reactiveProcs = {} + CleveRoids.Print("Cleared all reactive procs") + else + if CleveRoids.ClearReactiveProc then + CleveRoids.ClearReactiveProc(val) + CleveRoids.Print("Cleared " .. val .. " proc") + else + CleveRoids.Print("|cffff0000Reactive proc system not loaded!|r") + end + end + CleveRoids.QueueActionUpdate() + return + end + -- Unknown command fallback CleveRoids.Print("Usage:") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings") diff --git a/Extensions/Tooltip/Generic.lua b/Extensions/Tooltip/Generic.lua index d7f95dc..8f63a1d 100644 --- a/Extensions/Tooltip/Generic.lua +++ b/Extensions/Tooltip/Generic.lua @@ -231,18 +231,38 @@ end function CleveRoids.IndexActionSlot(slot) if not HasAction(slot) then CleveRoids.Actions[slot] = nil + -- When clearing a reactive slot, check if we need to find it elsewhere + local clearedReactiveName = CleveRoids.reactiveSlots[slot] CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot) CleveRoids.ClearSlot(CleveRoids.actionSlots, slot) + + -- If we cleared a reactive spell, rescan to find it in another slot + if clearedReactiveName then + for i = 1, 120 do + if i ~= slot and HasAction(i) then + local _, _, scanName = CleveRoids.GetActionButtonInfo(i) + if scanName == clearedReactiveName then + CleveRoids.reactiveSlots[clearedReactiveName] = i + CleveRoids.reactiveSlots[i] = clearedReactiveName + break + end + end + end + end else local actionType, _, name, rank = CleveRoids.GetActionButtonInfo(slot) if name then local reactiveName = CleveRoids.reactiveSpells[name] and name local actionSlotName = name..(rank and ("("..rank..")") or "") if reactiveName then - if not CleveRoids.reactiveSlots[reactiveName] then - CleveRoids.reactiveSlots[reactiveName] = slot - CleveRoids.reactiveSlots[slot] = reactiveName + -- Always update the mapping to ensure we track the correct slot + -- Clear old mapping for this spell name first + local oldSlot = CleveRoids.reactiveSlots[reactiveName] + if oldSlot and oldSlot ~= slot then + CleveRoids.reactiveSlots[oldSlot] = nil end + CleveRoids.reactiveSlots[reactiveName] = slot + CleveRoids.reactiveSlots[slot] = reactiveName elseif not reactiveName then CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot) end diff --git a/Utility.lua b/Utility.lua index e11ce31..f056b07 100644 --- a/Utility.lua +++ b/Utility.lua @@ -2535,3 +2535,191 @@ immunityFrame:SetScript("OnEvent", function() ParseImmunityCombatLog() end end) + +-- ============================================================================ +-- REACTIVE ABILITY PROC TRACKING SYSTEM +-- ============================================================================ +-- Tracks reactive ability procs independently of stance/usability +-- Allows detection of Overpower/Revenge/Riposte procs even when not in correct stance + +-- Table to store reactive proc states with expiry times and target GUIDs +-- Structure: { spellName = { expiry = time, targetGUID = guid } } +CleveRoids.reactiveProcs = CleveRoids.reactiveProcs or {} + +-- Proc durations (in seconds) +-- Overpower and Revenge: 4 seconds +-- Riposte: 5 seconds (keeping at 5 for safety, can be adjusted) +local REACTIVE_PROC_DURATION = 4.0 + +-- Reactive ability trigger patterns for combat log +local reactivePatterns = { + Overpower = { + -- Procs when ENEMY dodges YOUR attack (auto or ability) + patterns = { + -- Auto attack dodges + "(.+) dodges", -- English: "Target dodges" + "(.+) weicht aus", -- German + "(.+) esquive", -- French + "(.+)이%(가%) 회피", -- Korean + "躲闪了(.+)", -- Chinese Simplified + "躲閃了(.+)", -- Chinese Traditional + + -- Ability dodges + "was dodged by", -- English: "Your Mortal Strike was dodged by Target" + "wurde von (.+) ausgewichen", -- German + "a été esquivé par", -- French + "을%(를%) (.+)이%(가%) 회피", -- Korean + "被(.+)躲闪", -- Chinese Simplified + "被(.+)躲閃", -- Chinese Traditional + }, + type = "enemy_dodge", + requiresTargetGUID = true, + duration = 4.0 -- 4 second proc window + }, + Riposte = { + -- Procs when YOU parry an enemy attack + patterns = { + "You parry", -- English: "You parry X's Y" + "Ihr pariert", -- German + "Vous parez", -- French + "막아냈습니다", -- Korean + "你招架了", -- Chinese Simplified + "你招架了", -- Chinese Traditional + }, + type = "player_parry", + requiresTargetGUID = true, -- Track which enemy you parried + duration = 5.0 -- 5 second proc window (estimated, may be 4s) + }, + Revenge = { + -- Procs when YOU block, dodge, or parry an enemy attack (any stance) + patterns = { + "You block", -- English: "You block X's Y" + "You dodge", -- English: "You dodge X's Y" + "You parry", -- English: "You parry X's Y" + "Ihr blockt", -- German block + "Ihr weicht aus", -- German dodge + "Ihr pariert", -- German parry + "Vous bloquez", -- French block + "Vous esquivez", -- French dodge + "Vous parez", -- French parry + "막았습니다", -- Korean block + "회피했습니다", -- Korean dodge + "막아냈습니다", -- Korean parry + "你格挡了", -- Chinese Simplified block + "你躲闪了", -- Chinese Simplified dodge + "你招架了", -- Chinese Simplified parry + "你格擋了", -- Chinese Traditional block + "你躲閃了", -- Chinese Traditional dodge + "你招架了", -- Chinese Traditional parry + }, + type = "player_avoid", + requiresTargetGUID = true, -- Track which enemy triggered it + duration = 4.0 -- 4 second proc window + } +} + +-- Set a reactive proc state with optional target GUID +function CleveRoids.SetReactiveProc(spellName, duration, targetGUID) + duration = duration or REACTIVE_PROC_DURATION + CleveRoids.reactiveProcs[spellName] = { + expiry = GetTime() + duration, + targetGUID = targetGUID + } +end + +-- Check if a reactive proc is active (with optional GUID check) +function CleveRoids.HasReactiveProc(spellName) + local procData = CleveRoids.reactiveProcs[spellName] + if not procData or not procData.expiry then return false end + + local now = GetTime() + if now >= procData.expiry then + -- Expired, clear it + CleveRoids.reactiveProcs[spellName] = nil + return false + end + + -- If proc has a target GUID requirement, check if current target matches + if procData.targetGUID then + local _, targetGUID = UnitExists("target") + if not targetGUID or targetGUID ~= procData.targetGUID then + return false + end + end + + return true +end + +-- Clear a reactive proc +function CleveRoids.ClearReactiveProc(spellName) + CleveRoids.reactiveProcs[spellName] = nil +end + +-- Parse combat log for reactive ability triggers +function CleveRoids.ParseReactiveCombatLog() + if not arg1 then return end + + local message = arg1 + local _, targetGUID = UnitExists("target") + + -- Check each reactive ability's trigger patterns + for spellName, config in pairs(reactivePatterns) do + -- Skip if player doesn't know this spell + if CleveRoids.GetSpell(spellName) then + for _, pattern in ipairs(config.patterns) do + if strfind(message, pattern) then + -- Found a trigger event (works in any stance) + local guid = config.requiresTargetGUID and targetGUID or nil + local duration = config.duration or REACTIVE_PROC_DURATION + CleveRoids.SetReactiveProc(spellName, duration, guid) + + -- Update action buttons to reflect new state + CleveRoids.QueueActionUpdate() + break + end + end + end + end +end + +-- Clear reactive proc when spell is cast +function CleveRoids.ClearReactiveProcOnCast(spellName) + if not spellName then return end + + -- Check if this is a reactive spell + if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells[spellName] then + CleveRoids.ClearReactiveProc(spellName) + CleveRoids.QueueActionUpdate() + end +end + +-- Hook UNIT_CASTEVENT to clear reactive procs +local originalUnitCastEvent = CleveRoids.Frame and CleveRoids.Frame.UNIT_CASTEVENT +if originalUnitCastEvent then + CleveRoids.Frame.UNIT_CASTEVENT = function(...) + -- Call original handler first + if type(originalUnitCastEvent) == "function" then + originalUnitCastEvent(unpack(arg)) + end + + -- Clear reactive proc on spell cast start + if arg1 == "player" and arg2 == "START" and arg4 then + CleveRoids.ClearReactiveProcOnCast(arg4) + end + end +end + +-- Register combat log event for reactive proc tracking +local reactiveFrame = CreateFrame("Frame", "CleveRoidsReactiveFrame") +reactiveFrame:RegisterEvent("RAW_COMBATLOG") +reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS") +reactiveFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES") +reactiveFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE") +reactiveFrame:SetScript("OnEvent", function() + if event == "RAW_COMBATLOG" or + event == "CHAT_MSG_COMBAT_SELF_HITS" or + event == "CHAT_MSG_COMBAT_SELF_MISSES" or + event == "CHAT_MSG_SPELL_SELF_DAMAGE" then + CleveRoids.ParseReactiveCombatLog() + end +end)