diff --git a/Conditionals.lua b/Conditionals.lua index 083484f..a702032 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -173,7 +173,8 @@ local function BuildEquipmentCache() if items then for nampowerSlot, itemInfo in pairs(items) do -- Nampower uses 0-indexed slots, WoW API uses 1-indexed - local slot = nampowerSlot + 1 + -- tonumber() handles both string and numeric keys from different Nampower versions + local slot = tonumber(nampowerSlot) + 1 if slot >= 1 and slot <= 19 and itemInfo.itemId then _equippedItemIDs[slot] = itemInfo.itemId @@ -4721,5 +4722,44 @@ CleveRoids.Keywords = { return NegatedMulti(conditionals.nomycc, function(ccType) return not CleveRoids.ValidateUnitCC("player", ccType) end, conditionals, "nomycc") + end, + + -- ======================================================================== + -- RESIST TRACKING CONDITIONALS + -- ======================================================================== + + -- [resisted] - Check if last spell was resisted by current target + -- [resisted:type] - Check for specific resist type (full, partial) + -- [resisted:full/partial] - OR logic for multiple types + -- Examples: [resisted] [@target,resisted:full] [resisted:partial] + resisted = function(conditionals) + -- If no specific resist type, check for ANY resist on current target + if not conditionals.resisted or + conditionals.resisted == true or + (type(conditionals.resisted) == "table" and table.getn(conditionals.resisted) == 0) then + return CleveRoids.CheckResistState(nil) + end + + -- Check for specific resist type(s) - OR logic + return Multi(conditionals.resisted, function(resistType) + return CleveRoids.CheckResistState(resistType) + end, conditionals, "resisted") + end, + + -- [noresisted] - Check if last spell was NOT resisted by current target + -- [noresisted:type] - Check target does NOT have specific resist type + -- [noresisted:full/partial] - AND logic: not full AND not partial + noresisted = function(conditionals) + -- If no specific resist type, check for NO resist on current target + if not conditionals.noresisted or + conditionals.noresisted == true or + (type(conditionals.noresisted) == "table" and table.getn(conditionals.noresisted) == 0) then + return not CleveRoids.CheckResistState(nil) + end + + -- Check for absence of specific resist type(s) - AND logic + return NegatedMulti(conditionals.noresisted, function(resistType) + return not CleveRoids.CheckResistState(resistType) + end, conditionals, "noresisted") end } diff --git a/Console.lua b/Console.lua index 49bdb97..ea6e8c6 100644 --- a/Console.lua +++ b/Console.lua @@ -99,10 +99,26 @@ SlashCmdList.STOPATTACK = StopAttack SLASH_STOPCASTING1 = "/stopcasting" SlashCmdList.STOPCASTING = SpellStopCasting +SLASH_CLEARTARGET1 = "/cleartarget" +SlashCmdList.CLEARTARGET = ClearTarget + ---------------------------------- -- HOOK DEFINITIONS START ---------------------------------- +-- /cleartarget hook +CleveRoids.Hooks.CLEARTARGET_SlashCmd = SlashCmdList.CLEARTARGET +SlashCmdList.CLEARTARGET = function(msg) + msg = msg or "" + if string.find(msg, "%[") then + -- If conditionals are present, let the function handle it. + CleveRoids.DoConditionalClearTarget(msg) + else + -- If no conditionals, run the original command. + CleveRoids.Hooks.CLEARTARGET_SlashCmd() + end +end + -- /startattack hook CleveRoids.Hooks.STARTATTACK_SlashCmd = SlashCmdList.STARTATTACK SlashCmdList.STARTATTACK = function(msg) diff --git a/Core.lua b/Core.lua index 3fce803..a56c65f 100644 --- a/Core.lua +++ b/Core.lua @@ -2517,6 +2517,10 @@ local function _stopCastingAction() SpellStopCasting() end +local function _clearTargetAction() + ClearTarget() +end + -- Attempts to conditionally stop an attack. Returns false if no conditionals are found. function CleveRoids.DoConditionalStopAttack(msg) if not string.find(msg, "%[") then return false end @@ -2545,6 +2549,20 @@ function CleveRoids.DoConditionalStopCasting(msg) return false end +-- Attempts to conditionally clear target. Returns false if no conditionals are found. +function CleveRoids.DoConditionalClearTarget(msg) + if not string.find(msg, "%[") then return false end + + -- PERFORMANCE: Use numeric iteration to avoid pairs() iterator allocation + local parts = CleveRoids.splitStringIgnoringQuotes(msg) + for i = 1, table.getn(parts) do + if CleveRoids.DoWithConditionals(parts[i], nil, CleveRoids.FixEmptyTarget, false, _clearTargetAction) then + return true + end + end + return false +end + -- Attempts to use or equip an item by a set of conditionals -- Also checks if a condition is a spell so that you can mix item and spell use @@ -4265,6 +4283,9 @@ function CleveRoids.Frame:PLAYER_TARGET_CHANGED() CleveRoids.CurrentSpell.autoAttack = false CleveRoids.CurrentSpell.autoAttackLock = false + -- Clear resist state when target changes + CleveRoids.ClearResistState() + -- Reset any sequence with reset=target that has progressed past the first step for _, sequence in pairs(CleveRoids.Sequences) do if sequence.index > 1 and sequence.reset and sequence.reset.target then diff --git a/Init.lua b/Init.lua index 328c140..769c649 100644 --- a/Init.lua +++ b/Init.lua @@ -42,6 +42,10 @@ CleveRoids.spell_tracking = {} CleveRoids.lastComboPoints = 0 CleveRoids.lastComboPointsTime = 0 +-- Resist tracking state +-- Structure: { resistType = "full"|"partial", targetGUID = guid } +CleveRoids.resistState = nil + -- Holds information about the currently cast spell CleveRoids.CurrentSpell = { -- "channeled" or "cast" diff --git a/Utility.lua b/Utility.lua index 8f568be..28ce31b 100644 --- a/Utility.lua +++ b/Utility.lua @@ -4575,9 +4575,10 @@ if originalUnitCastEvent then originalUnitCastEvent(unpack(arg)) end - -- Clear reactive proc on spell cast start + -- Clear reactive proc and resist state on spell cast start if arg1 == "player" and arg2 == "START" and arg4 then CleveRoids.ClearReactiveProcOnCast(arg4) + CleveRoids.ClearResistState() end end end @@ -4598,3 +4599,111 @@ reactiveFrame:SetScript("OnEvent", function() CleveRoids.ParseReactiveCombatLog() end end) + +-- ============================================================================ +-- RESIST TRACKING SYSTEM +-- Tracks full and partial spell resists for [resisted] and [noresisted] conditionals +-- ============================================================================ + +-- Set resist state with target GUID matching +function CleveRoids.SetResistState(resistType, targetGUID) + CleveRoids.resistState = { + resistType = resistType, + targetGUID = targetGUID + } + CleveRoids.QueueActionUpdate() + + if CleveRoids.debug then + local targetName = UnitName("target") or "Unknown" + DEFAULT_CHAT_FRAME:AddMessage( + string.format("|cffff6600[Resist Track]|r %s resist on %s", + string.upper(resistType), targetName) + ) + end +end + +-- Clear resist state +function CleveRoids.ClearResistState() + if CleveRoids.resistState then + CleveRoids.resistState = nil + CleveRoids.QueueActionUpdate() + end +end + +-- Check resist state (used by conditionals) +-- Returns true if resist matches the type AND current target matches the GUID +function CleveRoids.CheckResistState(resistType) + local state = CleveRoids.resistState + if not state then return false end + + -- Must have current target that matches the GUID from resist event + local _, currentTargetGUID = UnitExists("target") + if not currentTargetGUID or currentTargetGUID ~= state.targetGUID then + return false + end + + -- If no specific type requested, any resist matches + if not resistType or resistType == "" then + return true + end + + -- Check specific resist type + return state.resistType == string.lower(resistType) +end + +-- Parse combat log for resist messages +local function ParseResistCombatLog() + local message = arg1 -- Formatted message (localized) + + if not message then return end + + -- PERFORMANCE: Quick length check (minimum resist message ~15 chars) + if string.len(message) < 15 then return end + + -- PERFORMANCE: Quick keyword check before expensive pattern matching + local lowerMsg = string.lower(message) + if not string.find(lowerMsg, "resist") then return end + + -- Get current target info for matching + local _, targetGUID = UnitExists("target") + if not targetGUID then return end + + local targetName = UnitName("target") + if not targetName then return end + + -- Verify the resist was against current target by checking if target name appears in message + if not string.find(message, targetName, 1, true) then + -- Target name not found in message - resist was against different mob + return + end + + -- Detect partial resist: "Your X hit Y for Z. (N resisted)" + -- Key pattern: "resisted)" with closing parenthesis indicates damage was dealt + local isPartial = string.find(message, "resisted%)") + + -- Detect full resist patterns: + -- Pattern 1: "Your X was resisted by Y." + -- Pattern 2: "Y resists your X." + local isFull = false + if not isPartial then + if string.find(message, "was resisted by") or string.find(message, "resists your") then + isFull = true + end + end + + -- Set resist state based on type detected + if isPartial then + CleveRoids.SetResistState("partial", targetGUID) + elseif isFull then + CleveRoids.SetResistState("full", targetGUID) + end +end + +-- Register for RAW_COMBATLOG (SuperWoW event) for resist tracking +local resistFrame = CreateFrame("Frame", "CleveRoidsResistFrame") +resistFrame:RegisterEvent("RAW_COMBATLOG") +resistFrame:SetScript("OnEvent", function() + if event == "RAW_COMBATLOG" then + ParseResistCombatLog() + end +end)