diff --git a/ClassicAPI.lua b/ClassicAPI.lua index be2da09..f2a9c63 100644 --- a/ClassicAPI.lua +++ b/ClassicAPI.lua @@ -208,6 +208,31 @@ function API.GetEnchantName(enchantID) return info and info.name or nil end +-------------------------------------------------------------------------------- +-- Loss Of Control +-------------------------------------------------------------------------------- + +-- Locked spell-school mask from an active SCHOOL_INTERRUPT (Counterspell / Kick / +-- Pummel / Earth Shock lockout) on the player, or 0 when not kicked. Read from +-- C_LossOfControl, which synthesizes the lockout from the server's own +-- SMSG_SPELL_COOLDOWN packet -- a state no debuff scan can see. Also returns the +-- seconds remaining (nil if ClassicAPI didn't observe the applying cast). Returns +-- 0 for a client without C_LossOfControl. Player-only (vanilla LoC is local-only). +function API.GetSchoolLockout() + if type(C_LossOfControl) ~= "table" + or type(C_LossOfControl.GetActiveLossOfControlDataCount) ~= "function" then + return 0, nil + end + local n = C_LossOfControl.GetActiveLossOfControlDataCount() or 0 + for i = 1, n do + local d = C_LossOfControl.GetActiveLossOfControlData(i) + if d and d.locType == "SCHOOL_INTERRUPT" then + return d.lockoutSchool or 0, d.timeRemaining + end + end + return 0, nil +end + -------------------------------------------------------------------------------- -- Spell -------------------------------------------------------------------------------- diff --git a/Conditionals.lua b/Conditionals.lua index 197c866..18fafa5 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -2953,11 +2953,8 @@ function CleveRoids.CountEnemiesMatching(checkFunc) tryUnit("targettarget") tryUnit("targettargettarget") tryUnit("pettarget") - if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id then - local focusUnit = pfUI.uf.focus.label .. pfUI.uf.focus.id - tryUnit(focusUnit) - tryUnit(focusUnit .. "target") - end + tryUnit("focus") + tryUnit("focustarget") for i = 1, 4 do tryUnit("party" .. i .. "target") end @@ -2967,22 +2964,8 @@ function CleveRoids.CountEnemiesMatching(checkFunc) end end - -- 3. Nameplate scan: visible nameplates give live GUIDs without target switching. - -- ClassicAPI's C_NamePlate.GetNamePlateGUIDs() lists every unit with an - -- allocated nameplate (including default vanilla nameplates), replacing the - -- old WorldFrame child-walk + frame:GetName(1) GUID-extraction. - local plateGuids = CleveRoids.ClassicAPI.GetNamePlateGUIDs() - if plateGuids then - for i = 1, table.getn(plateGuids) do - local guid = plateGuids[i] - if guid and not checked[guid] and UnitExists(guid) and UnitCanAttack("player", guid) then - checked[guid] = true - CleveRoids.knownEnemyGuids[guid] = true - if checkFunc(guid) then - count = count + 1 - end - end - end + for i = 1, 40 do + tryUnit("nameplate"..i) end -- During tooltip evaluation, skip known-enemy cache iteration. @@ -5228,6 +5211,33 @@ function CleveRoids.GetSpellMechanic(spellID) return CleveRoids.ClassicAPI.GetSpellMechanicByID(spellID) or 0 end +-- School name -> spell-school mask bit (WoW SPELL_SCHOOL_MASK_*). +local LOCKOUT_SCHOOL_MASK = { + physical = 1, holy = 2, fire = 4, nature = 8, frost = 16, shadow = 32, arcane = 64, +} + +-- Is `bit` set in `mask`? Shift right past `bit`, then test the low bit with +-- math.mod (the % operator is 5.1-only; 1.12 is Lua 5.0, but math.mod exists). +local function SchoolMaskHasBit(mask, bit) + if not mask or mask < bit then return false end + return math.mod(math.floor(mask / bit), 2) == 1 +end + +-- [locked] / [locked:school] -- player is under a spell-school interrupt lockout +-- (Counterspell / Kick / Pummel / Earth Shock), read from C_LossOfControl. This +-- is the lockout no debuff scan can detect; full silences stay on [mycc:silence]. +-- school nil/true = any school kicked; a name matches that school's mask bit. +function CleveRoids.ValidateSchoolLocked(school) + local mask = CleveRoids.ClassicAPI.GetSchoolLockout() + if not mask or mask == 0 then return false end + if school == nil or school == true or school == "" then + return true + end + local bit = LOCKOUT_SCHOOL_MASK[string.lower(school)] + if not bit then return false end + return SchoolMaskHasBit(mask, bit) +end + -- Validate CC on a unit (target, focus, player, etc.) -- Returns true if the unit has the specified CC mechanic active function CleveRoids.ValidateUnitCC(unit, ccType) @@ -8499,6 +8509,33 @@ CleveRoids.Keywords = { end, conditionals, "nomycc") end, + -- [locked] / [locked:school] - player is under a spell-school interrupt + -- lockout (Counterspell/Kick/Pummel/Earth Shock), via ClassicAPI + -- C_LossOfControl -- the server lockout no debuff scan can see. Bare = any + -- school kicked; [locked:frost] = that school; [locked:fire/frost] = either. + -- Full silences are separate ([mycc:silence]). Player-only. + locked = function(conditionals) + local v = conditionals.locked + if v == nil or v == true or (type(v) == "table" and table.getn(v) == 0) then + return CleveRoids.ValidateSchoolLocked(nil) + end + return Or(v, function(school) + return CleveRoids.ValidateSchoolLocked(school) + end) + end, + + -- [nolocked:school] - player is NOT school-locked. AND logic on negation: + -- [nolocked:fire/frost] = neither Fire nor Frost is kicked. + nolocked = function(conditionals) + local v = conditionals.nolocked + if v == nil or v == true or (type(v) == "table" and table.getn(v) == 0) then + return not CleveRoids.ValidateSchoolLocked(nil) + end + return NegatedMulti(v, function(school) + return not CleveRoids.ValidateSchoolLocked(school) + end, conditionals, "nolocked") + end, + -- ======================================================================== -- RESIST TRACKING CONDITIONALS -- ======================================================================== diff --git a/Core.lua b/Core.lua index 8d89db2..46e51f7 100644 --- a/Core.lua +++ b/Core.lua @@ -151,6 +151,8 @@ local BOOLEAN_CONDITIONALS = { nomhenchant = true, ohenchant = true, noohenchant = true, + locked = true, + nolocked = true, group = true, nogroup = true, moving = true, @@ -1581,18 +1583,9 @@ function CleveRoids.TryTargetFocus() return true end --- Resolves the player's focus to a usable unit token. --- Prefers pfUI's emulated focus (resolves to a real token like "party2"/"raid5") --- for pfUI users, then falls back to ClassicAPI's native "focus" token (which --- every UnitX function accepts) for everyone else. -- Returns the resolved token, or nil when no focus is set so @focus clauses -- silently fall through to the next macro alternative (no warning spam). function CleveRoids.GetFocusUnitId() - if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id - and UnitExists(pfUI.uf.focus.label .. pfUI.uf.focus.id) then - return pfUI.uf.focus.label .. pfUI.uf.focus.id - end - -- ClassicAPI native focus token (set via /focus or the FOCUSTARGET keybind) if UnitExists("focus") then return "focus" end @@ -4860,6 +4853,13 @@ CleveRoids.Frame:RegisterEvent("PLAYER_REGEN_ENABLED") -- Left actual combat (n CleveRoids.Frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM") CleveRoids.Frame:RegisterEvent("SPELL_UPDATE_COOLDOWN") CleveRoids.Frame:RegisterEvent("PLAYER_STARTED_MOVING") +-- ClassicAPI loss-of-control (school-interrupt lockout) for [locked]/[nolocked]. +-- Gated on the namespace so an older ClassicAPI without it doesn't error on an +-- unknown event. +if type(C_LossOfControl) == "table" then + CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_ADDED") + CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_UPDATE") +end -- Use GUID events when available (v2.39+), fall back to standard per-token events if CleveRoids.NampowerAPI.features.hasUnitGuidEvents then CleveRoids.Frame:RegisterEvent("UNIT_AURA_GUID") @@ -5799,6 +5799,13 @@ function CleveRoids.Frame:PLAYER_FOCUS_CHANGED() CleveRoids.QueueActionUpdate() end end +-- School-interrupt lockout applied/changed -> refresh [locked]/[nolocked] icons. +function CleveRoids.Frame:LOSS_OF_CONTROL_ADDED() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end +CleveRoids.Frame.LOSS_OF_CONTROL_UPDATE = CleveRoids.Frame.LOSS_OF_CONTROL_ADDED function CleveRoids.Frame:UPDATE_SHAPESHIFT_FORM() if CleveRoidMacros.realtime == 0 then CleveRoids.QueueActionUpdate()