mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
Fixed a critical bug where UnitBuff() calls in Utility.lua were incorrectly capturing 4 return values instead of 3, causing the spell ID variable to receive nil instead of the actual spell ID. This broke debuff overflow detection where debuffs shown as buffs would not be properly identified. According to SuperWoW documentation, the API returns: - UnitBuff(unit, index) → texture, stacks, spellID (3 values) - UnitDebuff(unit, index) → texture, stacks, debuffType, spellID (4 values) Changes: - Fixed lib:UnitDebuff() overflow fallback in Utility.lua:758 - Fixed GetUnitBuffs() helper function in Utility.lua:1108 - Fixed buff checking in CheckImmunity() function in Utility.lua:1282 - Reverted incorrect changes to Conditionals.lua (was already correct) This resolves the issue where macros like [nodebuff:Moonfire] would spam cast when the debuff had overflowed and was showing as a buff on the target.
1396 lines
43 KiB
Lua
1396 lines
43 KiB
Lua
--[[
|
|
Author: Dennis Werner Garske (DWG) / brian / Mewtiny
|
|
License: MIT License
|
|
]]
|
|
local _G = _G or getfenv(0)
|
|
local CleveRoids = _G.CleveRoids or {}
|
|
local _G = getfenv(0)
|
|
local strfind = string.find
|
|
local find = string.find -- Add this line
|
|
local gsub = string.gsub
|
|
local lower = string.lower
|
|
local floor = math.floor
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local type = type
|
|
local tonumber = tonumber
|
|
local tostring = tostring
|
|
local GetTime = GetTime
|
|
|
|
if type(hooksecurefunc) ~= "function" then
|
|
function hooksecurefunc(arg1, arg2, arg3)
|
|
local tgt, fname, post
|
|
|
|
if type(arg1) == "string" and type(arg2) == "function" and arg3 == nil then
|
|
fname, post = arg1, arg2
|
|
local orig = _G[fname]
|
|
if type(orig) ~= "function" then return end
|
|
_G[fname] = function(...)
|
|
local args = arg or {}
|
|
local n = (type(args) == "table" and args.n) or 0
|
|
local r = { orig(unpack(args, 1, n)) }
|
|
post(unpack(args, 1, n))
|
|
return unpack(r, 1, table.getn(r))
|
|
end
|
|
return
|
|
end
|
|
|
|
if type(arg1) == "table" and type(arg2) == "string" and type(arg3) == "function" then
|
|
tgt, fname, post = arg1, arg2, arg3
|
|
local orig = tgt[fname]
|
|
if type(orig) ~= "function" then return end
|
|
tgt[fname] = function(...)
|
|
local args = arg or {}
|
|
local n = (type(args) == "table" and args.n) or 0
|
|
local r = { orig(unpack(args, 1, n)) }
|
|
post(unpack(args, 1, n))
|
|
return unpack(r, 1, table.getn(r))
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if type(string.match) ~= "function" then
|
|
function string.match(s, pattern, init)
|
|
if s == nil or pattern == nil then return nil end
|
|
local i, j, c1, c2, c3, c4, c5 = string.find(s, pattern, init)
|
|
if not i then return nil end
|
|
if c1 ~= nil then return c1, c2, c3, c4, c5 end
|
|
return string.sub(s, i, j)
|
|
end
|
|
end
|
|
|
|
if type(string.gmatch) ~= "function" then
|
|
function string.gmatch(s, pattern)
|
|
local pos = 1
|
|
return function()
|
|
if s == nil or pattern == nil then return nil end
|
|
local i, j, c1, c2, c3, c4, c5 = string.find(s, pattern, pos)
|
|
if not i then return nil end
|
|
pos = j + 1
|
|
if c1 ~= nil then return c1, c2, c3, c4, c5 end
|
|
return string.sub(s, i, j)
|
|
end
|
|
end
|
|
end
|
|
|
|
function CleveRoids.Seq(_, i)
|
|
return (i or 0) + 1
|
|
end
|
|
|
|
function CleveRoids.Trim(str)
|
|
if not str then
|
|
return nil
|
|
end
|
|
return string.gsub(str,"^%s*(.-)%s*$", "%1")
|
|
end
|
|
|
|
do
|
|
local _G = _G or getfenv(0)
|
|
local CleveRoids = _G.CleveRoids or {}
|
|
_G.CleveRoids = CleveRoids
|
|
|
|
CleveRoids.__mo = CleveRoids.__mo or { sources = {}, current = nil }
|
|
|
|
local PRIORITY = {
|
|
pfui = 3,
|
|
blizz = 3,
|
|
tooltip = 1,
|
|
}
|
|
|
|
local function getBest()
|
|
local bestSource, bestUnit, bestP = nil, nil, -1
|
|
for source, unit in CleveRoids.__mo.sources do
|
|
if unit and unit ~= "" then
|
|
local p = PRIORITY[source] or 0
|
|
if p > bestP then
|
|
bestP, bestSource, bestUnit = p, source, unit
|
|
end
|
|
end
|
|
end
|
|
return bestSource, bestUnit
|
|
end
|
|
|
|
local function apply(unit)
|
|
if CleveRoids.hasSuperwow and _G.SetMouseoverUnit then
|
|
_G.SetMouseoverUnit(unit)
|
|
else
|
|
CleveRoids.mouseoverUnit = unit
|
|
end
|
|
if CleveRoids.QueueActionUpdate then CleveRoids.QueueActionUpdate() end
|
|
end
|
|
|
|
function CleveRoids.SetMouseoverFrom(source, unit)
|
|
if not source then return end
|
|
CleveRoids.__mo.sources[source] = unit
|
|
local _, bestUnit = getBest()
|
|
if bestUnit ~= CleveRoids.__mo.current then
|
|
CleveRoids.__mo.current = bestUnit
|
|
apply(bestUnit)
|
|
end
|
|
end
|
|
|
|
function CleveRoids.ClearMouseoverFrom(source, unitIfMatch)
|
|
if not source then return end
|
|
if unitIfMatch and CleveRoids.__mo.sources[source] ~= unitIfMatch then
|
|
return
|
|
end
|
|
CleveRoids.__mo.sources[source] = nil
|
|
local _, bestUnit = getBest()
|
|
if bestUnit ~= CleveRoids.__mo.current then
|
|
CleveRoids.__mo.current = bestUnit
|
|
apply(bestUnit)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- TODO: Get rid of one Split function. CleveRoids.splitString is ~10% slower
|
|
function CleveRoids.Split(s, p, trim)
|
|
local r, o = {}, 1
|
|
|
|
if not p or p == "" then
|
|
if trim then
|
|
s = CleveRoids.Trim(s)
|
|
end
|
|
for i = 1, string.len(s) do
|
|
table.insert(r, string.sub(s, i, 1))
|
|
end
|
|
return r
|
|
end
|
|
|
|
repeat
|
|
local b, e = string.find(s, p, o)
|
|
if b == nil then
|
|
local sub = string.sub(s, o)
|
|
table.insert(r, trim and CleveRoids.Trim(sub) or sub)
|
|
return r
|
|
end
|
|
if b > 1 then
|
|
local sub = string.sub(s, o, b - 1)
|
|
table.insert(r, trim and CleveRoids.Trim(sub) or sub)
|
|
else
|
|
table.insert(r, "")
|
|
end
|
|
o = e + 1
|
|
until false
|
|
end
|
|
|
|
function CleveRoids.splitString(str, seperatorPattern)
|
|
local tbl = {}
|
|
if not str then
|
|
return tbl
|
|
end
|
|
local pattern = "(.-)" .. seperatorPattern
|
|
local lastEnd = 1
|
|
local s, e, cap = string.find(str, pattern, 1)
|
|
|
|
while s do
|
|
if s ~= 1 or cap ~= "" then
|
|
table.insert(tbl,cap)
|
|
end
|
|
lastEnd = e + 1
|
|
s, e, cap = string.find(str, pattern, lastEnd)
|
|
end
|
|
|
|
if lastEnd <= string.len(str) then
|
|
cap = string.sub(str, lastEnd)
|
|
table.insert(tbl, cap)
|
|
end
|
|
|
|
return tbl
|
|
end
|
|
|
|
function CleveRoids.splitStringIgnoringQuotes(str, separator)
|
|
local result = {}
|
|
local temp = ""
|
|
local insideQuotes = false
|
|
local separators = {}
|
|
|
|
if type(separator) == "table" then
|
|
for _, s in separator do
|
|
separators[s] = s
|
|
end
|
|
else
|
|
separators[separator or ";"] = separator or ";"
|
|
end
|
|
|
|
for i = 1, string.len(str) do
|
|
local char = string.sub(str, i, i)
|
|
|
|
if char == "\"" then
|
|
insideQuotes = not insideQuotes
|
|
temp = temp .. char
|
|
elseif char == separators[char] and not insideQuotes then
|
|
temp = CleveRoids.Trim(temp)
|
|
if temp ~= "" then table.insert(result, temp) end
|
|
temp = ""
|
|
else
|
|
temp = temp .. char
|
|
end
|
|
end
|
|
|
|
if temp ~= "" then
|
|
temp = CleveRoids.Trim(temp)
|
|
table.insert(result, temp)
|
|
end
|
|
|
|
return (next(result) and result or {""})
|
|
end
|
|
|
|
function CleveRoids.Print(...)
|
|
local c = "|cFF4477FFCleveR|r|cFFFFFFFFoid :: |r"
|
|
local out = ""
|
|
|
|
for i=1, arg.n, 1 do
|
|
out = out..tostring(arg[i]).." "
|
|
end
|
|
if WowLuaFrameOutput then
|
|
WowLuaFrameOutput:AddMessage(out)
|
|
else
|
|
if not DEFAULT_CHAT_FRAME:IsVisible() then
|
|
FCF_SelectDockFrame(DEFAULT_CHAT_FRAME)
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(c..out)
|
|
end
|
|
end
|
|
|
|
function CleveRoids.PrintI(msg, depth)
|
|
depth = depth or 0
|
|
msg = string.rep(" ", depth) .. tostring(msg)
|
|
CleveRoids.Print(msg)
|
|
end
|
|
|
|
function CleveRoids.PrintT(t, depth)
|
|
depth = depth or 0
|
|
local cs = "|cffc8c864"
|
|
local ce = "|r"
|
|
|
|
if t == nil or type(t) ~= "table" then
|
|
CleveRoids.PrintI(t, depth)
|
|
else
|
|
for k, v in pairs(t) do
|
|
if type(v) == "table" then
|
|
CleveRoids.PrintI(cs..tostring(k)..":"..ce.." <TABLE>", depth)
|
|
CleveRoids.PrintT(v, depth + 1)
|
|
else
|
|
CleveRoids.PrintI(cs..tostring(k)..ce..": "..tostring(v), depth)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
CleveRoids.kmods = {
|
|
ctrl = IsControlKeyDown,
|
|
alt = IsAltKeyDown,
|
|
shift = IsShiftKeyDown,
|
|
mod = function() return (IsControlKeyDown() or IsAltKeyDown() or IsShiftKeyDown()) end,
|
|
nomod = function() return (not IsControlKeyDown() and not IsAltKeyDown() and not IsShiftKeyDown()) end,
|
|
}
|
|
|
|
CleveRoids.operators = {
|
|
["<"] = "lt",
|
|
["lt"] = "<",
|
|
[">"] = "gt",
|
|
["gt"] = ">",
|
|
["="] = "eq",
|
|
["eq"] = "=",
|
|
["<="] = "lte",
|
|
["lte"] = "<=",
|
|
[">="] = "gte",
|
|
["gte"] = ">=",
|
|
["~="] = "ne",
|
|
["ne"] = "~=",
|
|
}
|
|
|
|
CleveRoids.comparators = {
|
|
lt = function(a, b) return (a < b) end,
|
|
gt = function(a, b) return (a > b) end,
|
|
eq = function(a, b) return (a == b) end,
|
|
lte = function(a, b) return (a <= b) end,
|
|
gte = function(a, b) return (a >= b) end,
|
|
ne = function(a, b) return (a ~= b) end,
|
|
}
|
|
CleveRoids.comparators["<"] = CleveRoids.comparators.lt
|
|
CleveRoids.comparators[">"] = CleveRoids.comparators.gt
|
|
CleveRoids.comparators["="] = CleveRoids.comparators.eq
|
|
CleveRoids.comparators["<="] = CleveRoids.comparators.lte
|
|
CleveRoids.comparators[">="] = CleveRoids.comparators.gte
|
|
CleveRoids.comparators["~="] = CleveRoids.comparators.ne
|
|
|
|
|
|
_G["CleveRoids"] = CleveRoids
|
|
|
|
local _G = _G or getfenv(0)
|
|
local CleveRoids = _G.CleveRoids or {}
|
|
_G.CleveRoids = CleveRoids
|
|
|
|
CleveRoids.libdebuff = CleveRoids.libdebuff or {}
|
|
local lib = CleveRoids.libdebuff
|
|
|
|
lib.objects = lib.objects or {}
|
|
lib.guidToName = lib.guidToName or {}
|
|
|
|
lib.durations = lib.durations or {
|
|
-- WARRIOR
|
|
[772] = 9, -- Rend (Rank 1)
|
|
[6546] = 12, -- Rend (Rank 2)
|
|
[6547] = 15, -- Rend (Rank 3)
|
|
[6548] = 18, -- Rend (Rank 4)
|
|
[11572] = 21, -- Rend (Rank 5)
|
|
[11573] = 21, -- Rend (Rank 6)
|
|
[11574] = 21, -- Rend (Rank 7)
|
|
|
|
[7386] = 30, -- Sunder Armor (Rank 1)
|
|
[7405] = 30, -- Sunder Armor (Rank 2)
|
|
[8380] = 30, -- Sunder Armor (Rank 3)
|
|
[8647] = 30, -- Sunder Armor (Rank 4)
|
|
[11597] = 30, -- Sunder Armor (Rank 5)
|
|
|
|
[7372] = 15, -- Hamstring (Rank 1)
|
|
[7373] = 15, -- Hamstring (Rank 2)
|
|
[1715] = 15, -- Hamstring (Rank 3)
|
|
|
|
[6343] = 10, -- Thunder Clap (Rank 1)
|
|
[8198] = 10, -- Thunder Clap (Rank 2)
|
|
[8205] = 10, -- Thunder Clap (Rank 3)
|
|
[11580] = 10, -- Thunder Clap (Rank 4)
|
|
[11581] = 10, -- Thunder Clap (Rank 5)
|
|
|
|
[1160] = 30, -- Demoralizing Shout (Rank 1)
|
|
[6190] = 30, -- Demoralizing Shout (Rank 2)
|
|
[11554] = 30, -- Demoralizing Shout (Rank 3)
|
|
[11555] = 30, -- Demoralizing Shout (Rank 4)
|
|
[11556] = 30, -- Demoralizing Shout (Rank 5)
|
|
|
|
[12323] = 6, -- Piercing Howl
|
|
|
|
-- ROGUE
|
|
[2094] = 10, -- Blind
|
|
[21060] = 10, -- Blind (alternate?)
|
|
|
|
[6770] = 25, -- Sap (Rank 1)
|
|
[2070] = 35, -- Sap (Rank 2)
|
|
[11297] = 45, -- Sap (Rank 3)
|
|
|
|
[1776] = 4, -- Gouge (Rank 1) - base duration
|
|
[1777] = 4, -- Gouge (Rank 2)
|
|
[8629] = 4, -- Gouge (Rank 3)
|
|
[11285] = 4, -- Gouge (Rank 4)
|
|
[11286] = 4, -- Gouge (Rank 5)
|
|
|
|
[1943] = 6, -- Rupture (Rank 1) - base duration (scales with combo points)
|
|
[8639] = 6, -- Rupture (Rank 2)
|
|
[8640] = 6, -- Rupture (Rank 3)
|
|
[11273] = 6, -- Rupture (Rank 4)
|
|
[11274] = 6, -- Rupture (Rank 5)
|
|
[11275] = 6, -- Rupture (Rank 6)
|
|
|
|
[408] = 1, -- Kidney Shot (Rank 1) - base duration (scales with combo points)
|
|
[8643] = 1, -- Kidney Shot (Rank 2)
|
|
|
|
[8647] = 30, -- Expose Armor (Rank 1)
|
|
[8649] = 30, -- Expose Armor (Rank 2)
|
|
[8650] = 30, -- Expose Armor (Rank 3)
|
|
[11197] = 30, -- Expose Armor (Rank 4)
|
|
[11198] = 30, -- Expose Armor (Rank 5)
|
|
|
|
[703] = 18, -- Garrote (Rank 1)
|
|
[8631] = 18, -- Garrote (Rank 2)
|
|
[8632] = 18, -- Garrote (Rank 3)
|
|
[8633] = 18, -- Garrote (Rank 4)
|
|
[11289] = 18, -- Garrote (Rank 5)
|
|
[11290] = 18, -- Garrote (Rank 6)
|
|
|
|
[2818] = 12, -- Deadly Poison (Rank 1)
|
|
[2819] = 12, -- Deadly Poison II (Rank 2)
|
|
[11353] = 12, -- Deadly Poison III (Rank 3)
|
|
[11354] = 12, -- Deadly Poison IV (Rank 4)
|
|
[25349] = 12, -- Deadly Poison V (Rank 5)
|
|
|
|
[16511] = 15, -- Hemorrhage
|
|
|
|
[1833] = 4, -- Cheap Shot
|
|
[14902] = 4, -- Cheap Shot (alternate?)
|
|
|
|
-- Rogue poisons (not in Cursive, estimates):
|
|
[3409] = 12, -- Crippling Poison
|
|
[5760] = 16, -- Mind-numbing Poison (Rank 1)
|
|
[8694] = 16, -- Mind-numbing Poison (Rank 2)
|
|
[11399] = 16, -- Mind-numbing Poison (Rank 3)
|
|
[13218] = 15, -- Wound Poison (Rank 1)
|
|
[13222] = 15, -- Wound Poison (Rank 2)
|
|
[13223] = 15, -- Wound Poison (Rank 3)
|
|
[13224] = 15, -- Wound Poison (Rank 4)
|
|
|
|
-- HUNTER
|
|
[3043] = 20, -- Scorpid Sting (Rank 1)
|
|
[14275] = 20, -- Scorpid Sting (Rank 2)
|
|
[14276] = 20, -- Scorpid Sting (Rank 3)
|
|
[14277] = 20, -- Scorpid Sting (Rank 4)
|
|
|
|
[1978] = 15, -- Serpent Sting (Rank 1)
|
|
[13549] = 15, -- Serpent Sting (Rank 2)
|
|
[13550] = 15, -- Serpent Sting (Rank 3)
|
|
[13551] = 15, -- Serpent Sting (Rank 4)
|
|
[13552] = 15, -- Serpent Sting (Rank 5)
|
|
[13553] = 15, -- Serpent Sting (Rank 6)
|
|
[13554] = 15, -- Serpent Sting (Rank 7)
|
|
[13555] = 15, -- Serpent Sting (Rank 8)
|
|
[25295] = 15, -- Serpent Sting (Rank 9)
|
|
|
|
[3034] = 8, -- Viper Sting (Rank 1)
|
|
[14279] = 8, -- Viper Sting (Rank 2)
|
|
[14280] = 8, -- Viper Sting (Rank 3)
|
|
|
|
[2974] = 10, -- Wing Clip (Rank 1)
|
|
[14267] = 10, -- Wing Clip (Rank 2)
|
|
[14268] = 10, -- Wing Clip (Rank 3)
|
|
|
|
[5116] = 4, -- Concussive Shot
|
|
|
|
[19386] = 12, -- Wyvern Sting (Rank 1)
|
|
[24132] = 12, -- Wyvern Sting (Rank 2)
|
|
[24133] = 12, -- Wyvern Sting (Rank 3)
|
|
|
|
[19306] = 5, -- Counterattack (Rank 1)
|
|
[20909] = 5, -- Counterattack (Rank 2)
|
|
[20910] = 5, -- Counterattack (Rank 3)
|
|
|
|
[1130] = 120, -- Hunter's Mark (Rank 1)
|
|
[14323] = 120, -- Hunter's Mark (Rank 2)
|
|
[14324] = 120, -- Hunter's Mark (Rank 3)
|
|
[14325] = 120, -- Hunter's Mark (Rank 4)
|
|
|
|
-- DRUID
|
|
[339] = 12, -- Entangling Roots (Rank 1)
|
|
[1062] = 15, -- Entangling Roots (Rank 2)
|
|
[5195] = 18, -- Entangling Roots (Rank 3)
|
|
[5196] = 21, -- Entangling Roots (Rank 4)
|
|
[9852] = 24, -- Entangling Roots (Rank 5)
|
|
[9853] = 27, -- Entangling Roots (Rank 6)
|
|
|
|
[700] = 20, -- Sleep (Rank 1)
|
|
[1090] = 30, -- Sleep (Rank 2)
|
|
[2937] = 40, -- Sleep (Rank 3)
|
|
|
|
[770] = 40, -- Faerie Fire (Rank 1)
|
|
[778] = 40, -- Faerie Fire (Rank 2)
|
|
[9749] = 40, -- Faerie Fire (Rank 3)
|
|
[9907] = 40, -- Faerie Fire (Rank 4)
|
|
|
|
[16855] = 40, -- Faerie Fire (Bear) (Rank 1)
|
|
[17387] = 40, -- Faerie Fire (Bear) (Rank 2)
|
|
[17388] = 40, -- Faerie Fire (Bear) (Rank 3)
|
|
[17389] = 40, -- Faerie Fire (Bear) (Rank 4)
|
|
|
|
[16857] = 40, -- Faerie Fire (Feral) (Rank 1)
|
|
[17390] = 40, -- Faerie Fire (Feral) (Rank 2)
|
|
[17391] = 40, -- Faerie Fire (Feral) (Rank 3)
|
|
[17392] = 40, -- Faerie Fire (Feral) (Rank 4)
|
|
|
|
[2637] = 20, -- Hibernate (Rank 1)
|
|
[18657] = 30, -- Hibernate (Rank 2)
|
|
[18658] = 40, -- Hibernate (Rank 3)
|
|
|
|
[5570] = 18, -- Insect Swarm (Rank 1) - 12s with talent
|
|
[24974] = 18, -- Insect Swarm (Rank 2)
|
|
[24975] = 18, -- Insect Swarm (Rank 3)
|
|
[24976] = 18, -- Insect Swarm (Rank 4)
|
|
[24977] = 18, -- Insect Swarm (Rank 5)
|
|
|
|
[8921] = 9, -- Moonfire (Rank 1) - 12s with talent
|
|
[8924] = 18, -- Moonfire (Rank 2) - 12s with talent
|
|
[8925] = 18, -- Moonfire (Rank 3)
|
|
[8926] = 18, -- Moonfire (Rank 4)
|
|
[8927] = 18, -- Moonfire (Rank 5)
|
|
[8928] = 18, -- Moonfire (Rank 6)
|
|
[8929] = 18, -- Moonfire (Rank 7)
|
|
[9833] = 18, -- Moonfire (Rank 8)
|
|
[9834] = 18, -- Moonfire (Rank 9)
|
|
[9835] = 18, -- Moonfire (Rank 10)
|
|
|
|
[1822] = 9, -- Rake (Rank 1)
|
|
[1823] = 9, -- Rake (Rank 2)
|
|
[1824] = 9, -- Rake (Rank 3)
|
|
[9904] = 9, -- Rake (Rank 4)
|
|
|
|
[1079] = 10, -- Rip (Rank 1) - base duration (scales with combo points)
|
|
[9492] = 10, -- Rip (Rank 2)
|
|
[9493] = 10, -- Rip (Rank 3)
|
|
[9752] = 10, -- Rip (Rank 4)
|
|
[9894] = 10, -- Rip (Rank 5)
|
|
[9896] = 10, -- Rip (Rank 6)
|
|
|
|
[2908] = 15, -- Soothe Animal (Rank 1)
|
|
[8955] = 15, -- Soothe Animal (Rank 2)
|
|
[9901] = 15, -- Soothe Animal (Rank 3)
|
|
|
|
[5211] = 2, -- Bash (Rank 1)
|
|
[6798] = 3, -- Bash (Rank 2)
|
|
[8983] = 4, -- Bash (Rank 3)
|
|
|
|
[99] = 30, -- Demoralizing Roar (Rank 1)
|
|
[1735] = 30, -- Demoralizing Roar (Rank 2)
|
|
[9490] = 30, -- Demoralizing Roar (Rank 3)
|
|
[9747] = 30, -- Demoralizing Roar (Rank 4)
|
|
[9898] = 30, -- Demoralizing Roar (Rank 5)
|
|
|
|
[5209] = 6, -- Challenging Roar
|
|
|
|
[9005] = 18, -- Pounce Bleed (Rank 1)
|
|
[9823] = 18, -- Pounce Bleed (Rank 2)
|
|
[9827] = 18, -- Pounce Bleed (Rank 3)
|
|
|
|
-- WARLOCK
|
|
[172] = 12, -- Corruption (Rank 1) - 18s with talent
|
|
[6222] = 15, -- Corruption (Rank 2) - 18s with talent
|
|
[6223] = 18, -- Corruption (Rank 3) - 18s with talent
|
|
[7648] = 18, -- Corruption (Rank 4)
|
|
[11671] = 18, -- Corruption (Rank 5)
|
|
[11672] = 18, -- Corruption (Rank 6)
|
|
[25311] = 18, -- Corruption (Rank 7)
|
|
|
|
[980] = 24, -- Curse of Agony (Rank 1)
|
|
[1014] = 24, -- Curse of Agony (Rank 2)
|
|
[6217] = 24, -- Curse of Agony (Rank 3)
|
|
[11711] = 24, -- Curse of Agony (Rank 4)
|
|
[11712] = 24, -- Curse of Agony (Rank 5)
|
|
[11713] = 24, -- Curse of Agony (Rank 6)
|
|
|
|
[18265] = 30, -- Siphon Life (Rank 1)
|
|
[18879] = 30, -- Siphon Life (Rank 2)
|
|
[18880] = 30, -- Siphon Life (Rank 3)
|
|
[18881] = 30, -- Siphon Life (Rank 4)
|
|
|
|
[52550] = 8, -- Dark Harvest (Rank 1)
|
|
[52551] = 8, -- Dark Harvest (Rank 2)
|
|
[52552] = 8, -- Dark Harvest (Rank 3)
|
|
|
|
[603] = 60, -- Curse of Doom
|
|
|
|
[704] = 120, -- Curse of Recklessness (Rank 1)
|
|
[7658] = 120, -- Curse of Recklessness (Rank 2)
|
|
[7659] = 120, -- Curse of Recklessness (Rank 3)
|
|
[11717] = 120, -- Curse of Recklessness (Rank 4)
|
|
|
|
[17862] = 300, -- Curse of Shadow (Rank 1)
|
|
[17937] = 300, -- Curse of Shadow (Rank 2)
|
|
|
|
[1490] = 300, -- Curse of Elements (Rank 1)
|
|
[11721] = 300, -- Curse of Elements (Rank 2)
|
|
[11722] = 300, -- Curse of Elements (Rank 3)
|
|
|
|
[1714] = 30, -- Curse of Tongues (Rank 1)
|
|
[11719] = 30, -- Curse of Tongues (Rank 2)
|
|
|
|
[702] = 120, -- Curse of Weakness (Rank 1)
|
|
[1108] = 120, -- Curse of Weakness (Rank 2)
|
|
[6205] = 120, -- Curse of Weakness (Rank 3)
|
|
[7646] = 120, -- Curse of Weakness (Rank 4)
|
|
[11707] = 120, -- Curse of Weakness (Rank 5)
|
|
[11708] = 120, -- Curse of Weakness (Rank 6)
|
|
|
|
[18223] = 12, -- Curse of Exhaustion
|
|
|
|
[348] = 15, -- Immolate (Rank 1)
|
|
[707] = 15, -- Immolate (Rank 2)
|
|
[1094] = 15, -- Immolate (Rank 3)
|
|
[2941] = 15, -- Immolate (Rank 4)
|
|
[11665] = 15, -- Immolate (Rank 5)
|
|
[11667] = 15, -- Immolate (Rank 6)
|
|
[11668] = 15, -- Immolate (Rank 7)
|
|
[25309] = 15, -- Immolate (Rank 8)
|
|
|
|
[6789] = 3, -- Death Coil (Rank 1)
|
|
[17925] = 3, -- Death Coil (Rank 2)
|
|
[17926] = 3, -- Death Coil (Rank 3)
|
|
|
|
[710] = 20, -- Banish (Rank 1)
|
|
[18647] = 30, -- Banish (Rank 2)
|
|
|
|
[5782] = 10, -- Fear (Rank 1)
|
|
[6213] = 15, -- Fear (Rank 2)
|
|
[6215] = 20, -- Fear (Rank 3)
|
|
|
|
-- MAGE
|
|
[118] = 20, -- Polymorph (Rank 1)
|
|
[12824] = 30, -- Polymorph (Rank 2)
|
|
[12825] = 40, -- Polymorph (Rank 3)
|
|
[12826] = 50, -- Polymorph (Rank 4)
|
|
|
|
[28270] = 50, -- Polymorph: Cow
|
|
[28271] = 50, -- Polymorph: Turtle
|
|
[28272] = 50, -- Polymorph: Pig
|
|
|
|
[116] = 5, -- Frostbolt slow
|
|
[120] = 5, -- Cone of Cold slow
|
|
[6136] = 15, -- Chilled
|
|
[12484] = 15, -- Improved Blizzard slow
|
|
[12486] = 15, -- Blizzard slow
|
|
|
|
-- PRIEST
|
|
[1425] = 30, -- Shackle Undead (Rank 1)
|
|
[9486] = 40, -- Shackle Undead (Rank 2)
|
|
[10956] = 50, -- Shackle Undead (Rank 3)
|
|
|
|
[453] = 15, -- Mind Soothe (Rank 1)
|
|
[8192] = 15, -- Mind Soothe (Rank 2)
|
|
[10953] = 15, -- Mind Soothe (Rank 3)
|
|
|
|
[605] = 60, -- Mind Control (Rank 1)
|
|
[10911] = 30, -- Mind Control (Rank 2)
|
|
[10912] = 30, -- Mind Control (Rank 3)
|
|
|
|
[2944] = 24, -- Devouring Plague (Rank 1)
|
|
[19276] = 24, -- Devouring Plague (Rank 2)
|
|
[19277] = 24, -- Devouring Plague (Rank 3)
|
|
[19278] = 24, -- Devouring Plague (Rank 4)
|
|
[19279] = 24, -- Devouring Plague (Rank 5)
|
|
[19280] = 24, -- Devouring Plague (Rank 6)
|
|
|
|
[9035] = 120, -- Hex of Weakness (Rank 1)
|
|
[19281] = 120, -- Hex of Weakness (Rank 2)
|
|
[19282] = 120, -- Hex of Weakness (Rank 3)
|
|
[19283] = 120, -- Hex of Weakness (Rank 4)
|
|
[19284] = 120, -- Hex of Weakness (Rank 5)
|
|
[19285] = 120, -- Hex of Weakness (Rank 6)
|
|
|
|
[589] = 24, -- Shadow Word: Pain (Rank 1) - 18s base, 24s with talent
|
|
[594] = 24, -- Shadow Word: Pain (Rank 2)
|
|
[970] = 24, -- Shadow Word: Pain (Rank 3)
|
|
[992] = 24, -- Shadow Word: Pain (Rank 4)
|
|
[2767] = 24, -- Shadow Word: Pain (Rank 5)
|
|
[10892] = 24, -- Shadow Word: Pain (Rank 6)
|
|
[10893] = 24, -- Shadow Word: Pain (Rank 7)
|
|
[10894] = 24, -- Shadow Word: Pain (Rank 8)
|
|
|
|
[15286] = 60, -- Vampiric Embrace
|
|
|
|
[14914] = 10, -- Holy Fire (Rank 1)
|
|
[15262] = 10, -- Holy Fire (Rank 2)
|
|
[15263] = 10, -- Holy Fire (Rank 3)
|
|
[15264] = 10, -- Holy Fire (Rank 4)
|
|
[15265] = 10, -- Holy Fire (Rank 5)
|
|
[15266] = 10, -- Holy Fire (Rank 6)
|
|
[15267] = 10, -- Holy Fire (Rank 7)
|
|
[15261] = 10, -- Holy Fire (Rank 8)
|
|
|
|
-- Mind Flay
|
|
[15407] = 3, -- Mind Flay (Rank 1)
|
|
[17311] = 3, -- Mind Flay (Rank 2)
|
|
[17312] = 3, -- Mind Flay (Rank 3)
|
|
[17313] = 3, -- Mind Flay (Rank 4)
|
|
[17314] = 3, -- Mind Flay (Rank 5)
|
|
[18807] = 3, -- Mind Flay (Rank 6)
|
|
|
|
-- PALADIN
|
|
[853] = 6, -- Hammer of Justice (Rank 1)
|
|
[5588] = 6, -- Hammer of Justice (Rank 2)
|
|
[5589] = 6, -- Hammer of Justice (Rank 3)
|
|
[10308] = 6, -- Hammer of Justice (Rank 4)
|
|
|
|
[20184] = 10, -- Judgement of Justice
|
|
[20185] = 10, -- Judgement of Light (Rank 1)
|
|
[20267] = 10, -- Judgement of Light (Rank 2)
|
|
[20268] = 10, -- Judgement of Light (Rank 3)
|
|
[20271] = 10, -- Judgement of Light (Rank 4)
|
|
[20186] = 10, -- Judgement of Wisdom (Rank 1)
|
|
[20354] = 10, -- Judgement of Wisdom (Rank 2)
|
|
[20355] = 10, -- Judgement of Wisdom (Rank 3)
|
|
|
|
-- SHAMAN
|
|
[8050] = 12, -- Flame Shock (Rank 1)
|
|
[8052] = 12, -- Flame Shock (Rank 2)
|
|
[8053] = 12, -- Flame Shock (Rank 3)
|
|
[10447] = 12, -- Flame Shock (Rank 4)
|
|
[10448] = 12, -- Flame Shock (Rank 5)
|
|
[29228] = 12, -- Flame Shock (Rank 6)
|
|
|
|
-- Frost Shock
|
|
[8056] = 8, -- Frost Shock (Rank 1)
|
|
[8058] = 8, -- Frost Shock (Rank 2)
|
|
[10472] = 8, -- Frost Shock (Rank 3)
|
|
[10473] = 8, -- Frost Shock (Rank 4)
|
|
}
|
|
|
|
lib.learnCastTimers = lib.learnCastTimers or {}
|
|
|
|
CleveRoids_LearnedDurations = CleveRoids_LearnedDurations or {}
|
|
|
|
function lib:GetDuration(spellID, casterGUID)
|
|
if casterGUID and CleveRoids_LearnedDurations[spellID] then
|
|
local learned = CleveRoids_LearnedDurations[spellID][casterGUID]
|
|
if learned and learned > 0 then
|
|
return learned
|
|
end
|
|
end
|
|
|
|
return self.durations[spellID] or 0
|
|
end
|
|
|
|
function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
|
|
if not guid or not spellID then return end
|
|
|
|
duration = duration or lib:GetDuration(spellID, caster)
|
|
if duration <= 0 then return end
|
|
|
|
lib.objects[guid] = lib.objects[guid] or {}
|
|
lib.guidToName[guid] = unitName
|
|
|
|
local rec = lib.objects[guid][spellID] or {}
|
|
rec.spellID = spellID
|
|
rec.start = GetTime()
|
|
rec.duration = duration
|
|
rec.stacks = stacks or 0
|
|
rec.caster = caster
|
|
|
|
lib.objects[guid][spellID] = rec
|
|
end
|
|
|
|
function lib:UnitDebuff(unit, id)
|
|
local _, guid = UnitExists(unit)
|
|
if not guid then return nil end
|
|
|
|
local texture, stacks, dtype, spellID = UnitDebuff(unit, id)
|
|
|
|
if not texture or not spellID then
|
|
texture, stacks, spellID = UnitBuff(unit, id)
|
|
if texture and spellID and not lib.durations[spellID] then
|
|
return nil
|
|
end
|
|
end
|
|
|
|
if not texture or not spellID then return nil end
|
|
|
|
local name = SpellInfo(spellID)
|
|
local duration, timeleft, caster = nil, -1, nil
|
|
|
|
local rec = lib.objects[guid] and lib.objects[guid][spellID]
|
|
|
|
if rec and rec.duration and rec.start then
|
|
local remaining = rec.duration + rec.start - GetTime()
|
|
if remaining > 0 then
|
|
duration = rec.duration
|
|
timeleft = remaining
|
|
caster = rec.caster
|
|
stacks = rec.stacks or stacks
|
|
else
|
|
lib.objects[guid][spellID] = nil
|
|
end
|
|
end
|
|
|
|
return name, nil, texture, stacks, dtype, duration, timeleft, caster
|
|
end
|
|
|
|
local function SeedUnit(unit)
|
|
local _, guid = UnitExists(unit)
|
|
if not guid then return end
|
|
local unitName = UnitName(unit)
|
|
|
|
for i=1, 16 do
|
|
local tex, stacks, dtype, spellID = UnitDebuff(unit, i)
|
|
if not tex then break end
|
|
|
|
if spellID and lib.durations[spellID] then
|
|
if not (lib.objects[guid] and lib.objects[guid][spellID]) then
|
|
lib:AddEffect(guid, unitName, spellID, lib:GetDuration(spellID), stacks)
|
|
end
|
|
end
|
|
end
|
|
|
|
for i=1, 32 do
|
|
local tex, stacks, _, spellID = UnitBuff(unit, i)
|
|
if not tex then break end
|
|
|
|
if spellID and lib.durations[spellID] then
|
|
if not (lib.objects[guid] and lib.objects[guid][spellID]) then
|
|
lib:AddEffect(guid, unitName, spellID, lib:GetDuration(spellID), stacks)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local ev = CreateFrame("Frame", "CleveRoidsLibDebuffFrame", UIParent)
|
|
ev:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
ev:RegisterEvent("UNIT_AURA")
|
|
|
|
if CleveRoids.hasSuperwow then
|
|
ev:RegisterEvent("UNIT_CASTEVENT")
|
|
end
|
|
|
|
ev:SetScript("OnEvent", function()
|
|
if event == "PLAYER_TARGET_CHANGED" then
|
|
SeedUnit("target")
|
|
|
|
elseif event == "UNIT_AURA" and arg1 == "target" then
|
|
SeedUnit("target")
|
|
|
|
elseif event == "UNIT_CASTEVENT" then
|
|
local casterGUID = arg1
|
|
local targetGUID = arg2
|
|
local eventType = arg3
|
|
local spellID = arg4
|
|
|
|
if eventType == "CAST" and spellID then
|
|
local _, playerGUID = UnitExists("player")
|
|
if casterGUID == playerGUID and targetGUID then
|
|
|
|
local duration = lib:GetDuration(spellID, casterGUID)
|
|
|
|
if duration > 0 then
|
|
local targetName = lib.guidToName[targetGUID]
|
|
if not targetName then
|
|
local _, currentTargetGUID = UnitExists("target")
|
|
if currentTargetGUID == targetGUID then
|
|
targetName = UnitName("target")
|
|
lib.guidToName[targetGUID] = targetName
|
|
else
|
|
targetName = "Unknown"
|
|
end
|
|
end
|
|
lib:AddEffect(targetGUID, targetName, spellID, duration, 0, "player")
|
|
|
|
else
|
|
lib.learnCastTimers[targetGUID] = lib.learnCastTimers[targetGUID] or {}
|
|
lib.learnCastTimers[targetGUID][spellID] = {
|
|
start = GetTime(),
|
|
caster = casterGUID
|
|
}
|
|
|
|
lib.objects[targetGUID] = lib.objects[targetGUID] or {}
|
|
lib.objects[targetGUID][spellID] = {
|
|
spellID = spellID,
|
|
start = GetTime(),
|
|
duration = 999, -- Placeholder
|
|
stacks = 0,
|
|
caster = casterGUID
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
local evLearn = CreateFrame("Frame", "CleveRoidsLibDebuffLearnFrame", UIParent)
|
|
evLearn:RegisterEvent("RAW_COMBATLOG")
|
|
|
|
evLearn:SetScript("OnEvent", function()
|
|
if event == "RAW_COMBATLOG" then
|
|
local raw = arg2
|
|
if not raw or not find(raw, "fades from") then return end
|
|
|
|
local _, _, spellName = find(raw, "^(.-) fades from ")
|
|
local _, _, targetGUID = find(raw, "from (.-).$")
|
|
|
|
if lower(targetGUID or "") == "you" then
|
|
_, targetGUID = UnitExists("player")
|
|
end
|
|
targetGUID = gsub(targetGUID or "", "^0x", "")
|
|
|
|
if not spellName or targetGUID == "" then return end
|
|
if not lib.objects[targetGUID] then return end
|
|
|
|
local timestamp = GetTime()
|
|
|
|
for spellID in pairs(lib.objects[targetGUID]) do
|
|
local name = SpellInfo(spellID)
|
|
if name then
|
|
name = gsub(name, "%s*%(%s*Rank%s+%d+%s*%)", "")
|
|
if name == spellName then
|
|
if lib.learnCastTimers[targetGUID] and
|
|
lib.learnCastTimers[targetGUID][spellID] then
|
|
|
|
local castTime = lib.learnCastTimers[targetGUID][spellID].start
|
|
local casterGUID = lib.learnCastTimers[targetGUID][spellID].caster
|
|
local actualDuration = timestamp - castTime
|
|
|
|
CleveRoids_LearnedDurations[spellID] = CleveRoids_LearnedDurations[spellID] or {}
|
|
CleveRoids_LearnedDurations[spellID][casterGUID] = floor(actualDuration + 0.5)
|
|
|
|
if CleveRoids.debug then
|
|
DEFAULT_CHAT_FRAME:AddMessage(
|
|
"|cff4b7dccCleveRoids:|r Learned " .. spellName ..
|
|
" (ID:" .. spellID .. ") = " .. floor(actualDuration + 0.5) .. "s"
|
|
)
|
|
end
|
|
|
|
lib.learnCastTimers[targetGUID][spellID] = nil
|
|
if not next(lib.learnCastTimers[targetGUID]) then
|
|
lib.learnCastTimers[targetGUID] = nil
|
|
end
|
|
end
|
|
|
|
if lib.objects[targetGUID][spellID].start +
|
|
lib.objects[targetGUID][spellID].duration <= timestamp then
|
|
lib.objects[targetGUID][spellID] = nil
|
|
end
|
|
|
|
if not next(lib.objects[targetGUID]) then
|
|
lib.objects[targetGUID] = nil
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Cleanup dead/invalid targets
|
|
local evCleanup = CreateFrame("Frame", "CleveRoidsLibDebuffCleanupFrame", UIParent)
|
|
evCleanup:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
evCleanup:RegisterEvent("PLAYER_DEAD")
|
|
evCleanup:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
|
|
local lastCleanup = 0
|
|
local CLEANUP_THROTTLE = 2 -- Only run cleanup every 2 seconds max
|
|
|
|
evCleanup:SetScript("OnEvent", function()
|
|
local timestamp = GetTime()
|
|
|
|
-- Cleanup on zone change / login / death
|
|
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_DEAD" then
|
|
-- Keep only current target's data
|
|
local _, currentGUID = UnitExists("target")
|
|
if currentGUID then
|
|
local temp = lib.objects[currentGUID]
|
|
lib.objects = {}
|
|
if temp then
|
|
lib.objects[currentGUID] = temp
|
|
end
|
|
else
|
|
lib.objects = {} -- Clear everything
|
|
end
|
|
lastCleanup = timestamp
|
|
return
|
|
end
|
|
|
|
-- Throttle cleanup on target change
|
|
if event == "PLAYER_TARGET_CHANGED" then
|
|
if (timestamp - lastCleanup) < CLEANUP_THROTTLE then
|
|
return -- Don't cleanup too frequently
|
|
end
|
|
lastCleanup = timestamp
|
|
|
|
-- Remove expired effects from all GUIDs
|
|
for guid, effects in pairs(lib.objects) do
|
|
local _, targetGUID = UnitExists("target")
|
|
local isCurrentTarget = (targetGUID == guid)
|
|
|
|
-- Check if current target is dead
|
|
if isCurrentTarget and UnitIsDead("target") then
|
|
lib.objects[guid] = nil
|
|
else
|
|
-- Remove expired effects
|
|
for spellID, effect in pairs(effects) do
|
|
if effect.start + effect.duration < timestamp then
|
|
effects[spellID] = nil
|
|
end
|
|
end
|
|
|
|
-- Remove GUID if no effects remain (but keep current target)
|
|
if not next(effects) and not isCurrentTarget then
|
|
lib.objects[guid] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- ============================================================================
|
|
-- IMMUNITY TRACKING SYSTEM
|
|
-- ============================================================================
|
|
|
|
-- Initialize SavedVariables for immunity tracking
|
|
CleveRoids_ImmunityData = CleveRoids_ImmunityData or {}
|
|
|
|
-- Spell school constants
|
|
local IMMUNITY_SCHOOLS = {
|
|
physical = 1,
|
|
holy = 2,
|
|
fire = 3,
|
|
nature = 4,
|
|
frost = 5,
|
|
shadow = 6,
|
|
arcane = 7,
|
|
}
|
|
|
|
-- Cache for spell school lookups
|
|
local spellSchoolCache = {}
|
|
|
|
-- Get the damage school of a spell
|
|
local function GetSpellSchool(spellName)
|
|
if not spellName then return nil end
|
|
|
|
-- Remove rank information for cache consistency
|
|
local baseName = string.gsub(spellName, "%s*%(.-%)%s*$", "")
|
|
|
|
-- Check cache first
|
|
if spellSchoolCache[baseName] then
|
|
return spellSchoolCache[baseName]
|
|
end
|
|
|
|
-- Try to find spell in player's spellbook
|
|
local spell = CleveRoids.GetSpell(baseName)
|
|
if not spell then
|
|
return nil
|
|
end
|
|
|
|
-- Create tooltip if needed
|
|
if not CleveRoidsSchoolTooltip then
|
|
CreateFrame("GameTooltip", "CleveRoidsSchoolTooltip", nil, "GameTooltipTemplate")
|
|
CleveRoidsSchoolTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
|
end
|
|
|
|
CleveRoidsSchoolTooltip:ClearLines()
|
|
CleveRoidsSchoolTooltip:SetSpell(spell.spellSlot, spell.bookType)
|
|
|
|
-- Scan tooltip for school keywords
|
|
local school = nil
|
|
for i = 1, CleveRoidsSchoolTooltip:NumLines() do
|
|
local line = _G["CleveRoidsSchoolTooltipTextLeft" .. i]
|
|
if line then
|
|
local text = string.lower(line:GetText() or "")
|
|
|
|
if string.find(text, "fire") or string.find(text, "flame") then
|
|
school = "fire"
|
|
break
|
|
elseif string.find(text, "frost") or string.find(text, "ice") then
|
|
school = "frost"
|
|
break
|
|
elseif string.find(text, "nature") or string.find(text, "poison") then
|
|
school = "nature"
|
|
break
|
|
elseif string.find(text, "shadow") or string.find(text, "dark") then
|
|
school = "shadow"
|
|
break
|
|
elseif string.find(text, "arcane") then
|
|
school = "arcane"
|
|
break
|
|
elseif string.find(text, "holy") or string.find(text, "divine") then
|
|
school = "holy"
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fallback: Use spell name patterns for common spells
|
|
if not school then
|
|
local lower = string.lower(baseName)
|
|
if string.find(lower, "fire") or string.find(lower, "flame") or string.find(lower, "immolat") or string.find(lower, "scorch") then
|
|
school = "fire"
|
|
elseif string.find(lower, "frost") or string.find(lower, "ice") or string.find(lower, "blizzard") then
|
|
school = "frost"
|
|
elseif string.find(lower, "nature") or string.find(lower, "poison") or string.find(lower, "sting") then
|
|
school = "nature"
|
|
elseif string.find(lower, "shadow") or string.find(lower, "curse") or string.find(lower, "corruption") then
|
|
school = "shadow"
|
|
elseif string.find(lower, "arcane") then
|
|
school = "arcane"
|
|
elseif string.find(lower, "holy") or string.find(lower, "smite") or string.find(lower, "exorcism") then
|
|
school = "holy"
|
|
else
|
|
school = "physical"
|
|
end
|
|
end
|
|
|
|
-- Cache the result
|
|
spellSchoolCache[baseName] = school
|
|
return school
|
|
end
|
|
|
|
-- Get current buffs on a unit
|
|
local function GetUnitBuffs(unit)
|
|
local buffs = {}
|
|
if not CleveRoids.hasSuperwow then return buffs end
|
|
|
|
for i = 1, 32 do
|
|
local texture, stacks, spellID = UnitBuff(unit, i)
|
|
if not texture then break end
|
|
|
|
if spellID then
|
|
local buffName = SpellInfo(spellID)
|
|
if buffName then
|
|
buffs[buffName] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
return buffs
|
|
end
|
|
|
|
-- Record an immunity (permanent or buff-based)
|
|
local function RecordImmunity(npcName, spellName, conditionalBuff)
|
|
if not npcName or not spellName or npcName == "" then
|
|
return
|
|
end
|
|
|
|
local school = GetSpellSchool(spellName)
|
|
if not school then
|
|
return
|
|
end
|
|
|
|
-- Initialize school table
|
|
if not CleveRoids_ImmunityData[school] then
|
|
CleveRoids_ImmunityData[school] = {}
|
|
end
|
|
|
|
-- Record immunity
|
|
if conditionalBuff then
|
|
-- Buff-based immunity
|
|
CleveRoids_ImmunityData[school][npcName] = {
|
|
buff = conditionalBuff
|
|
}
|
|
if CleveRoids.debug then
|
|
CleveRoids.Print("|cffff6600Immunity:|r " .. npcName .. " is immune to " .. school .. " when buffed with: " .. conditionalBuff)
|
|
end
|
|
else
|
|
-- Permanent immunity
|
|
if CleveRoids_ImmunityData[school][npcName] ~= true then
|
|
CleveRoids_ImmunityData[school][npcName] = true
|
|
if CleveRoids.debug then
|
|
CleveRoids.Print("|cffff6600Immunity:|r " .. npcName .. " is permanently immune to " .. school)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Combat log parser for immunity detection
|
|
local function ParseImmunityCombatLog()
|
|
local message = arg1 -- Formatted message
|
|
local rawMessage = arg2 -- Raw message
|
|
|
|
if not rawMessage then return end
|
|
|
|
-- Pattern: "X's SpellName fails. Y is immune."
|
|
local spellName = nil
|
|
local targetName = nil
|
|
|
|
-- Extract spell name from raw message
|
|
local _, _, extractedSpell = string.find(rawMessage, "'s%s+(.-)%s+fails%.")
|
|
if not extractedSpell then
|
|
_, _, extractedSpell = string.find(rawMessage, "Your%s+(.-)%s+fails%.")
|
|
end
|
|
|
|
-- Extract target name from formatted message (more reliable)
|
|
if message then
|
|
local _, _, extractedTarget = string.find(message, "fails%.%s+(.-)%s+is immune")
|
|
if extractedTarget then
|
|
targetName = extractedTarget
|
|
end
|
|
end
|
|
|
|
-- Fallback: Try to get target from current target
|
|
if not targetName and UnitExists("target") then
|
|
local _, targetGUID = UnitExists("target")
|
|
if targetGUID and rawMessage and string.find(rawMessage, targetGUID) then
|
|
targetName = UnitName("target")
|
|
end
|
|
end
|
|
|
|
if extractedSpell and targetName then
|
|
-- Check if target has any buffs (for conditional immunity)
|
|
local buffs = nil
|
|
if UnitExists("target") and UnitName("target") == targetName then
|
|
buffs = GetUnitBuffs("target")
|
|
end
|
|
|
|
-- If target has exactly one buff, assume it's causing the immunity
|
|
if buffs and next(buffs) then
|
|
local buffCount = 0
|
|
local singleBuff = nil
|
|
for buff, _ in pairs(buffs) do
|
|
buffCount = buffCount + 1
|
|
singleBuff = buff
|
|
if buffCount > 1 then
|
|
singleBuff = nil
|
|
break
|
|
end
|
|
end
|
|
|
|
if singleBuff then
|
|
RecordImmunity(targetName, extractedSpell, singleBuff)
|
|
return
|
|
end
|
|
end
|
|
|
|
-- No single buff detected, record as permanent immunity
|
|
RecordImmunity(targetName, extractedSpell, nil)
|
|
end
|
|
end
|
|
|
|
-- Check if a unit is immune to a spell or damage school
|
|
-- Supports: CheckImmunity(unitId, "Flame Shock") or CheckImmunity(unitId, "fire")
|
|
function CleveRoids.CheckImmunity(unitId, spellOrSchool)
|
|
if not unitId or not UnitExists(unitId) then
|
|
return false
|
|
end
|
|
|
|
-- Only works on NPCs
|
|
if UnitIsPlayer(unitId) then
|
|
return false
|
|
end
|
|
|
|
local targetName = UnitName(unitId)
|
|
if not targetName or targetName == "" then
|
|
return false
|
|
end
|
|
|
|
if not spellOrSchool or spellOrSchool == "" then
|
|
return false
|
|
end
|
|
|
|
-- Check if input is a spell school name directly
|
|
local inputLower = string.lower(spellOrSchool)
|
|
local school = nil
|
|
|
|
if IMMUNITY_SCHOOLS[inputLower] then
|
|
-- Input is a damage school name (fire, frost, nature, etc.)
|
|
school = inputLower
|
|
else
|
|
-- Input is a spell name, need to determine its school
|
|
school = GetSpellSchool(spellOrSchool)
|
|
if not school then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Check immunity data for this school
|
|
if not CleveRoids_ImmunityData[school] then
|
|
return false
|
|
end
|
|
|
|
local immunityData = CleveRoids_ImmunityData[school][targetName]
|
|
|
|
-- No immunity data for this NPC
|
|
if not immunityData then
|
|
return false
|
|
end
|
|
|
|
-- Permanent immunity
|
|
if immunityData == true then
|
|
return true
|
|
end
|
|
|
|
-- Buff-based immunity (check if NPC has the required buff)
|
|
if type(immunityData) == "table" and immunityData.buff then
|
|
local requiredBuff = immunityData.buff
|
|
|
|
-- Check target's buffs
|
|
if CleveRoids.hasSuperwow then
|
|
for i = 1, 32 do
|
|
local texture, stacks, spellID = UnitBuff(unitId, i)
|
|
if not texture then break end
|
|
|
|
if spellID then
|
|
local buffName = SpellInfo(spellID)
|
|
if buffName and buffName == requiredBuff then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Buff not found, not currently immune
|
|
return false
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Management functions for immunity data
|
|
function CleveRoids.ListImmunities(school)
|
|
if school then
|
|
school = string.lower(school)
|
|
if not CleveRoids_ImmunityData[school] then
|
|
CleveRoids.Print("No immunity data for school: " .. school)
|
|
return
|
|
end
|
|
|
|
CleveRoids.Print("|cff00ff00" .. school .. " immunities:|r")
|
|
local count = 0
|
|
for npc, data in pairs(CleveRoids_ImmunityData[school]) do
|
|
if data == true then
|
|
CleveRoids.Print(" - " .. npc .. " (permanent)")
|
|
elseif type(data) == "table" and data.buff then
|
|
CleveRoids.Print(" - " .. npc .. " (when buffed: " .. data.buff .. ")")
|
|
end
|
|
count = count + 1
|
|
end
|
|
CleveRoids.Print("Total: " .. count)
|
|
else
|
|
-- List all schools
|
|
CleveRoids.Print("|cff00ff00Immunity Data by School:|r")
|
|
for schoolName, npcs in pairs(CleveRoids_ImmunityData) do
|
|
local count = 0
|
|
for _ in pairs(npcs) do
|
|
count = count + 1
|
|
end
|
|
if count > 0 then
|
|
CleveRoids.Print(" " .. schoolName .. ": " .. count .. " NPCs")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CleveRoids.ClearImmunities(school)
|
|
if school then
|
|
school = string.lower(school)
|
|
CleveRoids_ImmunityData[school] = {}
|
|
CleveRoids.Print("Cleared " .. school .. " immunity data")
|
|
else
|
|
CleveRoids_ImmunityData = {}
|
|
CleveRoids.Print("Cleared all immunity data")
|
|
end
|
|
end
|
|
|
|
function CleveRoids.AddImmunity(npcName, school, buffName)
|
|
if not npcName or not school then
|
|
CleveRoids.Print("Usage: /cleveroid addimmune <npc name> <school> [buff name]")
|
|
CleveRoids.Print("Schools: fire, frost, nature, shadow, arcane, holy, physical")
|
|
return
|
|
end
|
|
|
|
school = string.lower(school)
|
|
if not IMMUNITY_SCHOOLS[school] then
|
|
CleveRoids.Print("Invalid school. Use: fire, frost, nature, shadow, arcane, holy, physical")
|
|
return
|
|
end
|
|
|
|
if not CleveRoids_ImmunityData[school] then
|
|
CleveRoids_ImmunityData[school] = {}
|
|
end
|
|
|
|
if buffName and buffName ~= "" then
|
|
CleveRoids_ImmunityData[school][npcName] = { buff = buffName }
|
|
CleveRoids.Print("Added: " .. npcName .. " is immune to " .. school .. " when buffed with: " .. buffName)
|
|
else
|
|
CleveRoids_ImmunityData[school][npcName] = true
|
|
CleveRoids.Print("Added: " .. npcName .. " is permanently immune to " .. school)
|
|
end
|
|
end
|
|
|
|
function CleveRoids.RemoveImmunity(npcName, school)
|
|
if not npcName or not school then
|
|
CleveRoids.Print("Usage: /cleveroid removeimmune <npc name> <school>")
|
|
return
|
|
end
|
|
|
|
school = string.lower(school)
|
|
if CleveRoids_ImmunityData[school] and CleveRoids_ImmunityData[school][npcName] then
|
|
CleveRoids_ImmunityData[school][npcName] = nil
|
|
CleveRoids.Print("Removed: " .. npcName .. " from " .. school .. " immunities")
|
|
else
|
|
CleveRoids.Print("Not found: " .. npcName .. " in " .. school .. " immunities")
|
|
end
|
|
end
|
|
|
|
-- Register combat log event for immunity tracking
|
|
local immunityFrame = CreateFrame("Frame", "CleveRoidsImmunityFrame")
|
|
immunityFrame:RegisterEvent("RAW_COMBATLOG")
|
|
immunityFrame:SetScript("OnEvent", function()
|
|
if event == "RAW_COMBATLOG" then
|
|
ParseImmunityCombatLog()
|
|
end
|
|
end)
|