You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2222 lines
67 KiB

local _, Cell = ...
local L = Cell.L
local F = Cell.funcs
local I = Cell.iFuncs
Cell.vars.playerFaction = UnitFactionGroup("player")
-------------------------------------------------
-- game version
-------------------------------------------------
Cell.isAsian = LOCALE_zhCN or LOCALE_zhTW or LOCALE_koKR
Cell.isRetail = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
Cell.isVanilla = WOW_PROJECT_ID == WOW_PROJECT_CLASSIC
-- Cell.isBCC = WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC and LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_BURNING_CRUSADE
-- Cell.isWrath = WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC and LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_WRATH_OF_THE_LICH_KING
Cell.isWrath = WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC
Cell.isCata = WOW_PROJECT_ID == WOW_PROJECT_CATACLYSM_CLASSIC
Cell.isTWW = LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_WAR_WITHIN
-------------------------------------------------
-- class
-------------------------------------------------
local localizedClass
if Cell.isRetail then
localizedClass = LocalizedClassList()
else
localizedClass = {}
FillLocalizedClassList(localizedClass)
end
local sortedClasses = {}
local classFileToID = {}
local classIDToFile = {}
do
-- WARRIOR = 1,
-- PALADIN = 2,
-- HUNTER = 3,
-- ROGUE = 4,
-- PRIEST = 5,
-- DEATHKNIGHT = 6,
-- SHAMAN = 7,
-- MAGE = 8,
-- WARLOCK = 9,
-- MONK = 10,
-- DRUID = 11,
-- DEMONHUNTER = 12,
-- EVOKER = 13,
for i = 1, GetNumClasses() do --! returns the highest class ID
local classFile = select(2, GetClassInfo(i))
if classFile then --! returns nil for classes that don't exist in Classic.
tinsert(sortedClasses, classFile)
classFileToID[classFile] = i
classIDToFile[i] = classFile
end
end
sort(sortedClasses)
end
function F:GetClassID(classFile)
return classFileToID[classFile]
end
function F:GetLocalizedClassName(classFileOrID)
if type(classFileOrID) == "string" then
return localizedClass[classFileOrID] or classFileOrID
elseif type(classFileOrID) == "number" and classIDToFile[classFileOrID] then
return localizedClass[classIDToFile[classFileOrID]] or classFileOrID
end
return ""
end
function F:IterateClasses()
local i = 0
return function()
i = i + 1
if i <= GetNumClasses() then
return sortedClasses[i], classFileToID[sortedClasses[i]], i
end
end
end
function F:GetSortedClasses()
return F:Copy(sortedClasses)
end
-------------------------------------------------
-- Classic
-------------------------------------------------
if Cell.isCata then
function F:GetActiveTalentInfo()
local which = GetActiveTalentGroup() == 1 and L["Primary Talents"] or L["Secondary Talents"]
return which, Cell.vars.playerSpecIcon, Cell.vars.playerSpecName
end
elseif Cell.isWrath or Cell.isVanilla then
function F:GetActiveTalentInfo()
local which = GetActiveTalentGroup() == 1 and L["Primary Talents"] or L["Secondary Talents"]
local maxPoints = 0
local specName, specIcon, specFileName
for i = 1, GetNumTalentTabs() do
local name, texture, pointsSpent, fileName = GetTalentTabInfo(i)
if pointsSpent > maxPoints then
maxPoints = pointsSpent
specIcon = texture
specName = fileName
-- elseif pointsSpent == maxPoints then
-- specIcon = 132148
end
end
return which, specIcon or 134400, specName or L["No Spec"]
end
end
-- local specRoles = {
-- ["DeathKnightBlood"] = "DAMAGER",
-- ["DeathKnightFrost"] = "TANK",
-- ["DeathKnightUnholy"] = "DAMAGER",
-- ["DruidRestoration"] = "HEALER",
-- ["DruidBalance"] = "DAMAGER",
-- -- ["DruidFeralCombat"] = nil,
-- ["HunterBeastMastery"] = "DAMAGER",
-- ["HunterSurvival"] = "DAMAGER",
-- ["HunterMarksmanship"] = "DAMAGER",
-- ["MageFrost"] = "DAMAGER",
-- ["MageArcane"] = "DAMAGER",
-- ["MageFire"] = "DAMAGER",
-- ["PaladinHoly"] = "HEALER",
-- ["PaladinCombat"] = "DAMAGER",
-- ["PaladinProtection"] = "TANK",
-- ["PriestShadow"] = "DAMAGER",
-- ["PriestHoly"] = "HEALER",
-- ["PriestDiscipline"] = "HEALER",
-- ["RogueCombat"] = "DAMAGER",
-- ["RogueSubtlety"] = "DAMAGER",
-- ["RogueAssassination"] = "DAMAGER",
-- ["ShamanElementalCombat"] = "DAMAGER",
-- ["ShamanEnhancement"] = "DAMAGER",
-- ["ShamanRestoration"] = "HEALER",
-- ["WarlockSummoning"] = "DAMAGER",
-- ["WarlockDestruction"] = "DAMAGER",
-- ["WarlockCurses"] = "DAMAGER",
-- ["WarriorArms"] = "DAMAGER",
-- ["WarriorFury"] = "DAMAGER",
-- ["WarriorProtection"] = "TANK",
-- }
-- function F:GetPlayerRole()
-- end
-------------------------------------------------
-- color
-------------------------------------------------
function F:ConvertRGB(r, g, b, desaturation)
if not desaturation then desaturation = 1 end
r = r / 255 * desaturation
g = g / 255 * desaturation
b = b / 255 * desaturation
return r, g, b
end
function F:ConvertRGB_256(r, g, b)
return floor(r * 255), floor(g * 255), floor(b * 255)
end
function F:ConvertRGBToHEX(r, g, b)
local result = ""
for key, value in pairs({r, g, b}) do
local hex = ""
while(value > 0)do
local index = math.fmod(value, 16) + 1
value = math.floor(value / 16)
hex = string.sub("0123456789ABCDEF", index, index) .. hex
end
if(string.len(hex) == 0)then
hex = "00"
elseif(string.len(hex) == 1)then
hex = "0" .. hex
end
result = result .. hex
end
return result
end
function F:ConvertHEXToRGB(hex)
hex = hex:gsub("#","")
return tonumber("0x"..hex:sub(1,2)), tonumber("0x"..hex:sub(3,4)), tonumber("0x"..hex:sub(5,6))
end
-- https://wowpedia.fandom.com/wiki/ColorGradient
-- function F:ColorGradient(perc, r1,g1,b1, r2,g2,b2, r3,g3,b3)
-- perc = perc or 1
-- if perc >= 1 then
-- return r3, g3, b3
-- elseif perc <= 0 then
-- return r1, g1, b1
-- end
-- local segment, relperc = math.modf(perc * 2)
-- local rr1, rg1, rb1, rr2, rg2, rb2 = select((segment * 3) + 1, r1,g1,b1, r2,g2,b2, r3,g3,b3)
-- return rr1 + (rr2 - rr1) * relperc, rg1 + (rg2 - rg1) * relperc, rb1 + (rb2 - rb1) * relperc
-- end
function F:ColorGradient(perc, c1, c2, c3, lowBound, highBound)
local r1, g1, b1 = c1[1], c1[2], c1[3]
local r2, g2, b2 = c2[1], c2[2], c2[3]
local r3, g3, b3 = c3[1], c3[2], c3[3]
lowBound = lowBound or 0
highBound = highBound or 1
perc = perc or 1
if perc >= highBound then
return r3, g3, b3
elseif perc <= lowBound then
return r1, g1, b1
end
perc = (perc - lowBound) / (highBound - lowBound)
local segment, relperc = math.modf(perc * 2)
local rr1, rg1, rb1, rr2, rg2, rb2 = select((segment * 3) + 1, r1,g1,b1, r2,g2,b2, r3,g3,b3)
return rr1 + (rr2 - rr1) * relperc, rg1 + (rg2 - rg1) * relperc, rb1 + (rb2 - rb1) * relperc
end
--! From ColorPickerAdvanced by Feyawen-Llane
--[[ Convert RGB to HSV ---------------------------------------------------
Inputs:
r = Red [0, 1]
g = Green [0, 1]
b = Blue [0, 1]
Outputs:
H = Hue [0, 360]
S = Saturation [0, 1]
B = Brightness [0, 1]
]]--
function F:ConvertRGBToHSB(r, g, b)
local colorMax = max(max(r, g), b)
local colorMin = min(min(r, g), b)
local delta = colorMax - colorMin
local H, S, B
-- WoW's LUA doesn't handle floating point numbers very well (Somehow 1.000000 != 1.000000 WTF?)
-- So we do this weird conversion of, Number to String back to Number, to make the IF..THEN work correctly!
colorMax = tonumber(format("%f", colorMax))
r = tonumber(format("%f", r))
g = tonumber(format("%f", g))
b = tonumber(format("%f", b))
if (delta > 0) then
if (colorMax == r) then
H = 60 * (((g - b) / delta) % 6)
elseif (colorMax == g) then
H = 60 * (((b - r) / delta) + 2)
elseif (colorMax == b) then
H = 60 * (((r - g) / delta) + 4)
end
if (colorMax > 0) then
S = delta / colorMax
else
S = 0
end
B = colorMax
else
H = 0
S = 0
B = colorMax
end
if (H < 0) then
H = H + 360
end
return H, S, B
end
--[[ Convert HSB to RGB ---------------------------------------------------
Inputs:
h = Hue [0, 360]
s = Saturation [0, 1]
b = Brightness [0, 1]
Outputs:
R = Red [0,1]
G = Green [0,1]
B = Blue [0,1]
]]--
function F:ConvertHSBToRGB(h, s, b)
local chroma = b * s
local prime = (h / 60) % 6
local X = chroma * (1 - abs((prime % 2) - 1))
local M = b - chroma
local R, G, B
if (0 <= prime) and (prime < 1) then
R = chroma
G = X
B = 0
elseif (1 <= prime) and (prime < 2) then
R = X
G = chroma
B = 0
elseif (2 <= prime) and (prime < 3) then
R = 0
G = chroma
B = X
elseif (3 <= prime) and (prime < 4) then
R = 0
G = X
B = chroma
elseif (4 <= prime) and (prime < 5) then
R = X
G = 0
B = chroma
elseif (5 <= prime) and (prime < 6) then
R = chroma
G = 0
B = X
else
R = 0
G = 0
B = 0
end
R = R + M
G = G + M
B = B + M
return R, G, B
end
function F:InvertColor(r, g, b)
return 1 - r, 1 - g, 1 - b
end
-------------------------------------------------
-- number
-------------------------------------------------
local symbol_1K, symbol_10K, symbol_1B
if LOCALE_zhCN then
symbol_1K, symbol_10K, symbol_1B = "", "", "亿"
elseif LOCALE_zhTW then
symbol_1K, symbol_10K, symbol_1B = "", "", ""
elseif LOCALE_koKR then
symbol_1K, symbol_10K, symbol_1B = "", "", ""
end
if Cell.isAsian then
function F:FormatNumber(n)
if abs(n) >= 100000000 then
return string.format("%.3f"..symbol_1B, n/100000000)
elseif abs(n) >= 10000 then
return string.format("%.2f"..symbol_10K, n/10000)
-- elseif abs(n) >= 1000 then
-- return string.format("%.1f"..symbol_1K, n/1000)
else
return n
end
end
else
function F:FormatNumber(n)
if abs(n) >= 1000000000 then
return string.format("%.3fB", n/1000000000)
elseif abs(n) >= 1000000 then
return string.format("%.2fM", n/1000000)
elseif abs(n) >= 1000 then
return string.format("%.1fK", n/1000)
else
return n
end
end
end
function F:KeepDecimals(num, n)
if num < 0 then
return -(abs(num) - abs(num) % 0.1 ^ n)
else
return num - num % 0.1 ^ n
end
end
-------------------------------------------------
-- string
-------------------------------------------------
function F:UpperFirst(str, lowerOthers)
if lowerOthers then
str = strlower(str)
end
return (str:gsub("^%l", string.upper))
end
function F:SplitToNumber(sep, str)
if not str then return end
local ret = {strsplit(sep, str)}
for i, v in ipairs(ret) do
ret[i] = tonumber(v) or ret[i] -- keep non number
end
return unpack(ret)
end
local function Chsize(char)
if not char then
return 0
elseif char > 240 then
return 4
elseif char > 225 then
return 3
elseif char > 192 then
return 2
else
return 1
end
end
function F:Utf8sub(str, startChar, numChars)
if not str then return "" end
local startIndex = 1
while startChar > 1 do
local char = string.byte(str, startIndex)
startIndex = startIndex + Chsize(char)
startChar = startChar - 1
end
local currentIndex = startIndex
while numChars > 0 and currentIndex <= #str do
local char = string.byte(str, currentIndex)
currentIndex = currentIndex + Chsize(char)
numChars = numChars -1
end
return str:sub(startIndex, currentIndex - 1)
end
function F:FitWidth(fs, text, alignment)
fs:SetText(text)
if fs:IsTruncated() then
for i = 1, string.utf8len(text) do
if strlower(alignment) == "right" then
fs:SetText("..."..string.utf8sub(text, i))
else
fs:SetText(string.utf8sub(text, i).."...")
end
if not fs:IsTruncated() then
break
end
end
end
end
-------------------------------------------------
-- table
-------------------------------------------------
function F:Getn(t)
local count = 0
for k, v in pairs(t) do
count = count + 1
end
return count
end
function F:GetIndex(t, e)
for i, v in pairs(t) do
if e == v then
return i
end
end
return nil
end
function F:GetKeys(t)
local keys = {}
for k in pairs(t) do
tinsert(keys, k)
end
return keys
end
function F:Copy(t)
local newTbl = {}
for k, v in pairs(t) do
if type(v) == "table" then
newTbl[k] = F:Copy(v)
else
newTbl[k] = v
end
end
return newTbl
end
function F:TContains(t, v)
for _, value in pairs(t) do
if value == v then return true end
end
return false
end
function F:TInsert(t, v)
local i, done = 1
repeat
if not t[i] then
t[i] = v
done = true
end
i = i + 1
until done
end
function F:TRemove(t, v)
for i = #t, 1, -1 do
if t[i] == v then
table.remove(t, i)
end
end
end
function F:TMergeOverwrite(...)
local n = select("#", ...)
if n == 0 then return {} end
local temp = F:Copy(...)
for i = 2, n do
local t = select(i, ...)
for k, v in pairs(t) do
temp[k] = v
end
end
return temp
end
function F:RemoveElementsExceptKeys(tbl, ...)
local keys = {}
for i = 1, select("#", ...) do
local k = select(i, ...)
keys[k] = true
end
for k in pairs(tbl) do
if not keys[k] then
tbl[k] = nil
end
end
end
function F:RemoveElementsByKeys(tbl, ...)
for i = 1, select("#", ...) do
local k = select(i, ...)
tbl[k] = nil
end
end
function F:Sort(t, k1, order1, k2, order2, k3, order3)
table.sort(t, function(a, b)
if a[k1] ~= b[k1] then
if order1 == "ascending" then
return a[k1] < b[k1]
else -- "descending"
return a[k1] > b[k1]
end
elseif k2 and order2 and a[k2] ~= b[k2] then
if order2 == "ascending" then
return a[k2] < b[k2]
else -- "descending"
return a[k2] > b[k2]
end
elseif k3 and order3 and a[k3] ~= b[k3] then
if order3 == "ascending" then
return a[k3] < b[k3]
else -- "descending"
return a[k3] > b[k3]
end
end
end)
end
function F:StringToTable(s, sep, convertToNum)
local t = {}
for i, v in pairs({string.split(sep, s)}) do
v = strtrim(v)
if v ~= "" then
if convertToNum then
v = tonumber(v)
if v then tinsert(t, v) end
else
tinsert(t, v)
end
end
end
return t
end
function F:TableToString(t, sep)
return table.concat(t, sep)
end
function F:ConvertTable(t, value)
local temp = {}
for k, v in ipairs(t) do
temp[v] = value or k
end
return temp
end
function F:ConvertSpellTable(t, convertIdToName)
if not convertIdToName then
return F:ConvertTable(t)
end
local temp = {}
for k, v in ipairs(t) do
local name = F:GetSpellInfo(v)
if name then
temp[name] = k
end
end
return temp
end
function F:ConvertSpellTable_WithColor(t, convertIdToName)
local temp = {}
for k, st in ipairs(t) do
local index
if convertIdToName then
index = F:GetSpellInfo(st[1])
else
index = st[1]
end
if index then
temp[index] = {k, st[2]}
end
end
return temp
end
function F:ConvertSpellTable_WithClass(t)
local temp = {}
for class, ct in pairs(t) do
for _, id in ipairs(ct) do
local name = F:GetSpellInfo(id)
if name then
temp[id] = true
end
end
end
return temp
end
function F:ConvertSpellDurationTable(t, convertIdToName)
local temp = {}
for _, v in ipairs(t) do
local id, duration = strsplit(":", v)
local name = F:GetSpellInfo(id)
if name then
if convertIdToName then
temp[name] = tonumber(duration)
else
temp[tonumber(id)] = tonumber(duration)
end
end
end
return temp
end
function F:ConvertSpellDurationTable_WithClass(t)
local temp = {}
for class, ct in pairs(t) do
for k, v in ipairs(ct) do
local id, duration = strsplit(":", v)
local name, icon = F:GetSpellInfo(id)
if name then
temp[tonumber(id)] = {tonumber(duration), icon}
end
end
end
return temp
end
function F:CheckTableRemoved(previous, after)
local aa = {}
local ret = {}
for k,v in pairs(previous) do aa[v] = true end
for k,v in pairs(after) do aa[v] = nil end
for k,v in pairs(previous) do
if aa[v] then
tinsert(ret, v)
end
end
return ret
end
function F:FilterInvalidSpells(t)
if not t then return end
for i = #t, 1, -1 do
local spellId
if type(t[i]) == "number" then
spellId = t[i]
else -- table
spellId = t[i][1]
end
if not F:GetSpellInfo(spellId) then
tremove(t, i)
end
end
end
-------------------------------------------------
-- general
-------------------------------------------------
-- function F:GetRealmName()
-- return string.gsub(GetRealmName(), " ", "")
-- end
function F:UnitFullName(unit)
if not unit or not UnitIsPlayer(unit) then return end
local name = GetUnitName(unit, true)
--? name might be nil in some cases?
if name and not string.find(name, "-") then
local server = GetNormalizedRealmName()
--? server might be nil in some cases?
if server then
name = name.."-"..server
end
end
return name
end
function F:ToShortName(fullName)
if not fullName then return "" end
local shortName = strsplit("-", fullName)
return shortName
end
function F:FormatTime(s)
if s >= 3600 then
return "%dh", ceil(s / 3600)
elseif s >= 60 then
return "%dm", ceil(s / 60)
end
return "%ds", floor(s)
end
-- function F:SecondsToTime(seconds)
-- local m = seconds / 60
-- local s = seconds % 60
-- return format("%d:%02d", m, s)
-- end
local SEC = _G.SPELL_DURATION_SEC
local MIN = _G.SPELL_DURATION_MIN
local PATTERN_SEC
local PATTERN_MIN
if strfind(SEC, "1f") then
PATTERN_SEC = "%.0"
elseif strfind(SEC, "2f") then
PATTERN_SEC = "%.00"
end
if strfind(MIN, "1f") then
PATTERN_MIN = "%.0"
elseif strfind(MIN, "2f") then
PATTERN_MIN = "%.00"
end
function F:SecondsToTime(seconds)
if seconds > 60 then
return gsub(format(MIN, seconds / 60), PATTERN_MIN, "")
else
return gsub(format(SEC, seconds), PATTERN_SEC, "")
end
end
-------------------------------------------------
-- unit buttons
-------------------------------------------------
local combinedHeader = "CellRaidFrameHeader0"
local separatedHeaders = {"CellRaidFrameHeader1", "CellRaidFrameHeader2", "CellRaidFrameHeader3", "CellRaidFrameHeader4", "CellRaidFrameHeader5", "CellRaidFrameHeader6", "CellRaidFrameHeader7", "CellRaidFrameHeader8"}
-- REVIEW:
-- Cell.clickCastFrames = {}
-- Cell.clickCastFrameQueue = {}
-- function F:RegisterFrame(frame)
-- Cell.clickCastFrames[frame] = true
-- Cell.clickCastFrameQueue[frame] = true -- put into queue
-- Cell:Fire("UpdateQueuedClickCastings")
-- end
-- function F:UnregisterFrame(frame)
-- Cell.clickCastFrames[frame] = nil -- ignore
-- Cell.clickCastFrameQueue[frame] = false -- mark for only cleanup
-- Cell:Fire("UpdateQueuedClickCastings")
-- end
-- REVIEW: updateBlizzardFrames
function F:IterateAllUnitButtons(func, updateCurrentGroupOnly, updateQuickAssist, updateBlizzardFrames)
-- solo
if not updateCurrentGroupOnly or (updateCurrentGroupOnly and Cell.vars.groupType == "solo") then
for _, b in pairs(Cell.unitButtons.solo) do
func(b)
end
end
-- party
if not updateCurrentGroupOnly or (updateCurrentGroupOnly and Cell.vars.groupType == "party") then
for index, b in pairs(Cell.unitButtons.party) do
if index ~= "units" then
func(b)
end
end
end
-- raid
if not updateCurrentGroupOnly or (updateCurrentGroupOnly and Cell.vars.groupType == "raid") then
if not updateCurrentGroupOnly or Cell.vars.currentLayoutTable.main.combineGroups then
for _, b in ipairs(Cell.unitButtons.raid[combinedHeader]) do
func(b)
end
end
if not updateCurrentGroupOnly or not Cell.vars.currentLayoutTable.main.combineGroups then
for _, header in ipairs(separatedHeaders) do
for _, b in ipairs(Cell.unitButtons.raid[header]) do
func(b)
end
end
end
-- arena pet
for _, b in pairs(Cell.unitButtons.arena) do
func(b)
end
-- raid pet
for index, b in pairs(Cell.unitButtons.raidpet) do
if index ~= "units" then
func(b)
end
end
end
-- npc
for _, b in ipairs(Cell.unitButtons.npc) do
func(b)
end
-- spotlight
for _, b in pairs(Cell.unitButtons.spotlight) do
func(b)
end
if Cell.isRetail and updateQuickAssist then
for i = 1, 40 do
func(Cell.unitButtons.quickAssist[i])
end
end
end
function F:IterateSharedUnitButtons(func)
-- npc
for _, b in ipairs(Cell.unitButtons.npc) do
func(b)
end
-- spotlight
for _, b in pairs(Cell.unitButtons.spotlight) do
func(b)
end
end
function F:GetUnitButtonByUnit(unit, getSpotlights, getQuickAssist)
if not unit then return end
local normal, spotlights, quickAssist
if Cell.vars.groupType == "raid" then
if Cell.vars.inBattleground == 5 then
normal = Cell.unitButtons.raid.units[unit] or Cell.unitButtons.npc.units[unit] or Cell.unitButtons.arena[unit]
else
normal = Cell.unitButtons.raid.units[unit] or Cell.unitButtons.npc.units[unit] or Cell.unitButtons.raidpet.units[unit]
end
elseif Cell.vars.groupType == "party" then
normal = Cell.unitButtons.party.units[unit] or Cell.unitButtons.npc.units[unit]
else -- solo
normal = Cell.unitButtons.solo[unit] or Cell.unitButtons.npc.units[unit]
end
if getSpotlights then
spotlights = {}
for _, b in pairs(Cell.unitButtons.spotlight) do
if b.unit and UnitIsUnit(b.unit, unit) then
tinsert(spotlights, b)
end
end
end
if getQuickAssist then
quickAssist = Cell.unitButtons.quickAssist.units[unit]
end
return normal, spotlights, quickAssist
end
function F:GetUnitButtonByGUID(guid, getSpotlights, getQuickAssist)
return F:GetUnitButtonByUnit(Cell.vars.guids[guid], getSpotlights, getQuickAssist)
end
function F:GetUnitButtonByName(name, getSpotlights, getQuickAssist)
return F:GetUnitButtonByUnit(Cell.vars.names[name], getSpotlights, getQuickAssist)
end
function F:HandleUnitButton(type, unit, func, ...)
if not unit then return end
if type == "guid" then
unit = Cell.vars.guids[unit]
elseif type == "name" then
unit = Cell.vars.names[unit]
end
if not unit then return end
local handled, normal
if Cell.vars.groupType == "raid" then
if Cell.vars.inBattleground == 5 then
normal = Cell.unitButtons.raid.units[unit] or Cell.unitButtons.npc.units[unit] or Cell.unitButtons.arena[unit]
else
normal = Cell.unitButtons.raid.units[unit] or Cell.unitButtons.npc.units[unit] or Cell.unitButtons.raidpet.units[unit]
end
elseif Cell.vars.groupType == "party" then
normal = Cell.unitButtons.party.units[unit] or Cell.unitButtons.npc.units[unit]
else -- solo
normal = Cell.unitButtons.solo[unit] or Cell.unitButtons.npc.units[unit]
end
if normal then
func(normal, ...)
handled = true
end
for _, b in pairs(Cell.unitButtons.spotlight) do
if b.states.unit and UnitIsUnit(b.states.unit, unit) then
func(b, ...)
handled = true
end
end
return handled
end
function F:UpdateTextWidth(fs, text, width, relativeTo)
if not text or not width then return end
if width == "unlimited" then
fs:SetText(text)
elseif width[1] == "percentage" then
local percent = width[2] or 0.75
local width = relativeTo:GetWidth() - 2
for i = string.utf8len(text), 0, -1 do
fs:SetText(string.utf8sub(text, 1, i))
if fs:GetWidth() / width <= percent then
break
end
end
elseif width[1] == "length" then
if string.len(text) == string.utf8len(text) then -- en
fs:SetText(string.utf8sub(text, 1, width[2]))
else -- non-en
fs:SetText(string.utf8sub(text, 1, width[3]))
end
end
end
function F:GetMarkEscapeSequence(index)
index = index - 1
local left, right, top, bottom
local coordIncrement = 64 / 256
left = mod(index , 4) * coordIncrement
right = left + coordIncrement
top = floor(index / 4) * coordIncrement
bottom = top + coordIncrement
return string.format("|TInterface\\TargetingFrame\\UI-RaidTargetingIcons:0:0:0:0:64:64:%d:%d:%d:%d|t", left*64, right*64, top*64, bottom*64)
end
-- local scriptObjects = {}
-- local frame = CreateFrame("Frame")
-- frame:RegisterEvent("PLAYER_REGEN_DISABLED")
-- frame:RegisterEvent("PLAYER_REGEN_ENABLED")
-- frame:SetScript("OnEvent", function(self, event)
-- if event == "PLAYER_REGEN_ENABLED" then
-- for _, obj in pairs(scriptObjects) do
-- obj:Show()
-- end
-- else
-- for _, obj in pairs(scriptObjects) do
-- obj:Hide()
-- end
-- end
-- end)
-- function F:SetHideInCombat(obj)
-- tinsert(scriptObjects, obj)
-- end
-------------------------------------------------
-- global functions
-------------------------------------------------
local UnitGUID = UnitGUID
local GetNumGroupMembers = GetNumGroupMembers
local GetRaidRosterInfo = GetRaidRosterInfo
local IsInRaid = IsInRaid
local IsInGroup = IsInGroup
local UnitIsPlayer = UnitIsPlayer
local UnitIsUnit = UnitIsUnit
local UnitInParty = UnitInParty
local UnitInRaid = UnitInRaid
local UnitPlayerOrPetInParty = UnitPlayerOrPetInParty
local UnitPlayerOrPetInRaid = UnitPlayerOrPetInRaid
local UnitClass = UnitClass
local UnitClassBase = UnitClassBase
local UnitName = UnitName
local UnitIsGroupLeader = UnitIsGroupLeader
local UnitIsGroupAssistant = UnitIsGroupAssistant
local UnitInPartyIsAI = UnitInPartyIsAI or function() end
-------------------------------------------------
-- frame colors
-------------------------------------------------
local RAID_CLASS_COLORS = RAID_CLASS_COLORS
function F:GetClassColor(class)
if class and class ~= "" and RAID_CLASS_COLORS[class] then
if CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[class] then
return CUSTOM_CLASS_COLORS[class].r, CUSTOM_CLASS_COLORS[class].g, CUSTOM_CLASS_COLORS[class].b
else
return RAID_CLASS_COLORS[class]:GetRGB()
end
else
return 1, 1, 1
end
end
function F:GetClassColorStr(class)
if class and class ~= "" and RAID_CLASS_COLORS[class] then
if CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[class] then
return "|c"..CUSTOM_CLASS_COLORS[class].colorStr
else
return "|c"..RAID_CLASS_COLORS[class].colorStr
end
else
return "|cffffffff"
end
end
function F:GetUnitClassColor(unit, class, guid)
class = class or select(2, UnitClass(unit))
guid = guid or UnitGUID(unit)
if UnitIsPlayer(unit) or UnitInPartyIsAI(unit) then -- player
return F:GetClassColor(class)
elseif F:IsPet(guid, unit) then -- pet
return 0.5, 0.5, 1
else -- npc / vehicle
return 0, 1, 0.2
end
end
function F:GetPowerColor(unit)
local r, g, b, t
-- https://wow.gamepedia.com/API_UnitPowerType
local powerType, powerToken, altR, altG, altB = UnitPowerType(unit)
t = powerType
local info = PowerBarColor[powerToken]
if powerType == 0 then -- MANA
info = {r=0, g=0.5, b=1} -- default mana color is too dark!
elseif powerType == 13 then -- INSANITY
info = {r=0.6, g=0.2, b=1}
end
if info then
--The PowerBarColor takes priority
r, g, b = info.r, info.g, info.b
else
if not altR then
-- Couldn't find a power token entry. Default to indexing by power type or just mana if we don't have that either.
info = PowerBarColor[powerType] or PowerBarColor["MANA"]
r, g, b = info.r, info.g, info.b
else
r, g, b = altR, altG, altB
end
end
return r, g, b, t
end
function F:GetPowerBarColor(unit, class)
local r, g, b, lossR, lossG, lossB, t
r, g, b, t = F:GetPowerColor(unit)
if not Cell.loaded then
return r, g, b, r*0.2, g*0.2, b*0.2, t
end
if CellDB["appearance"]["powerColor"][1] == "power_color_dark" then
lossR, lossG, lossB = r, g, b
r, g, b = r*0.2, g*0.2, b*0.2
elseif CellDB["appearance"]["powerColor"][1] == "class_color" then
r, g, b = F:GetClassColor(class)
lossR, lossG, lossB = r*0.2, g*0.2, b*0.2
elseif CellDB["appearance"]["powerColor"][1] == "custom" then
r, g, b = unpack(CellDB["appearance"]["powerColor"][2])
lossR, lossG, lossB = r*0.2, g*0.2, b*0.2
else
lossR, lossG, lossB = r*0.2, g*0.2, b*0.2
end
return r, g, b, lossR, lossG, lossB, t
end
function F:GetHealthBarColor(percent, isDeadOrGhost, r, g, b)
if not Cell.loaded then
return r, g, b, r*0.2, g*0.2, b*0.2
end
local barR, barG, barB, lossR, lossG, lossB
-- bar
if percent == 1 and Cell.vars.useFullColor then
barR = CellDB["appearance"]["fullColor"][2][1]
barG = CellDB["appearance"]["fullColor"][2][2]
barB = CellDB["appearance"]["fullColor"][2][3]
else
if CellDB["appearance"]["barColor"][1] == "class_color" then
barR, barG, barB = r, g, b
elseif CellDB["appearance"]["barColor"][1] == "class_color_dark" then
barR, barG, barB = r*0.2, g*0.2, b*0.2
elseif CellDB["appearance"]["barColor"][1] == "gradient" then
barR, barG, barB = F:ColorGradient(percent, CellDB["appearance"]["gradientColors"][1], CellDB["appearance"]["gradientColors"][2], CellDB["appearance"]["gradientColors"][3], CellDB["appearance"]["gradientColors"][4], CellDB["appearance"]["gradientColors"][5])
elseif CellDB["appearance"]["barColor"][1] == "gradient2" then
if percent == 1 then
barR, barG, barB = r, g, b -- full: class color
else
barR, barG, barB = F:ColorGradient(percent, CellDB["appearance"]["gradientColors"][1], CellDB["appearance"]["gradientColors"][2], CellDB["appearance"]["gradientColors"][3], CellDB["appearance"]["gradientColors"][4], CellDB["appearance"]["gradientColors"][5])
end
else
barR = CellDB["appearance"]["barColor"][2][1]
barG = CellDB["appearance"]["barColor"][2][2]
barB = CellDB["appearance"]["barColor"][2][3]
end
end
-- loss
if isDeadOrGhost and Cell.vars.useDeathColor then
lossR = CellDB["appearance"]["deathColor"][2][1]
lossG = CellDB["appearance"]["deathColor"][2][2]
lossB = CellDB["appearance"]["deathColor"][2][3]
else
if CellDB["appearance"]["lossColor"][1] == "class_color" then
lossR, lossG, lossB = r, g, b
elseif CellDB["appearance"]["lossColor"][1] == "class_color_dark" then
lossR, lossG, lossB = r*0.2, g*0.2, b*0.2
elseif CellDB["appearance"]["lossColor"][1] == "gradient" then
lossR, lossG, lossB = F:ColorGradient(percent, CellDB["appearance"]["gradientColors"][1], CellDB["appearance"]["gradientColors"][2], CellDB["appearance"]["gradientColors"][3])
elseif CellDB["appearance"]["lossColor"][1] == "gradient2" then
if isDeadOrGhost then
lossR, lossG, lossB = r*0.2, g*0.2, b*0.2 -- dead: class color dark
else
lossR, lossG, lossB = F:ColorGradient(percent, CellDB["appearance"]["gradientColors"][1], CellDB["appearance"]["gradientColors"][2], CellDB["appearance"]["gradientColors"][3])
end
else
lossR = CellDB["appearance"]["lossColor"][2][1]
lossG = CellDB["appearance"]["lossColor"][2][2]
lossB = CellDB["appearance"]["lossColor"][2][3]
end
end
return barR, barG, barB, lossR, lossG, lossB
end
-------------------------------------------------
-- units
-------------------------------------------------
function F:GetNumSubgroupMembers(group)
local n = 0
for i = 1, GetNumGroupMembers() do
local name, _, subgroup = GetRaidRosterInfo(i)
if subgroup == group then
n = n + 1
end
end
return n
end
function F:GetUnitsInSubGroup(group)
local units = {}
for i = 1, GetNumGroupMembers() do
-- name, rank, subgroup, level, class, fileName, zone, online, isDead, role, isML, combatRole = GetRaidRosterInfo(raidIndex)
local name, _, subgroup = GetRaidRosterInfo(i)
if subgroup == group then
tinsert(units, "raid"..i)
end
end
return units
end
function F:GetRaidInfoByName(fullName)
for i = 1, GetNumGroupMembers() do
-- rank: Returns 2 if the raid member is the leader of the raid, 1 if the raid member is promoted to assistant, and 0 otherwise.
local name, rank, subgroup = GetRaidRosterInfo(i)
if name == fullName then
return i, subgroup, rank
end
end
end
function F:GetRaidInfoBySubgroupIndex(group, index)
local currentIndex = 0
for i = 1, GetNumGroupMembers() do
local name, rank, subgroup = GetRaidRosterInfo(i)
if subgroup == group then
currentIndex = currentIndex + 1
if currentIndex == index then
return i, name, rank -- found
end
elseif subgroup > group and currentIndex ~= 0 then
return -- nil if not found
end
end
end
function F:GetPetUnit(playerUnit)
if Cell.vars.groupType == "party" then
if playerUnit == "player" then
return "pet"
else
return "partypet"..select(3, strfind(playerUnit, "^party(%d+)$"))
end
elseif Cell.vars.groupType == "raid" then
return "raidpet"..select(3, strfind(playerUnit, "^raid(%d+)$"))
else
return "pet"
end
end
function F:GetPlayerUnit(petUnit)
if petUnit == "pet" then
return "player"
else
return petUnit:gsub("pet", "")
end
end
function F:IterateGroupMembers()
local groupType = IsInRaid() and "raid" or "party"
local numGroupMembers = GetNumGroupMembers()
local i
if groupType == "party" then
i = 0
numGroupMembers = numGroupMembers - 1
else
i = 1
end
return function()
local ret
if i == 0 then
ret = "player"
elseif i <= numGroupMembers and i > 0 then
ret = groupType .. i
end
i = i + 1
return ret
end
end
function F:IterateGroupPets()
local groupType = IsInRaid() and "raid" or "party"
local numGroupMembers = GetNumGroupMembers()
local i = groupType == "party" and 0 or 1
return function()
local ret
if i == 0 and groupType == "party" then
ret = "pet"
elseif i <= numGroupMembers and i > 0 then
ret = groupType .. "pet" .. i
end
i = i + 1
return ret
end
end
function F:GetGroupType()
if IsInRaid() then
return "raid"
elseif IsInGroup() then
return "party"
else
return "solo"
end
end
function F:UnitInGroup(unit, ignorePets)
if ignorePets then
return UnitIsUnit(unit, "player") or UnitInParty(unit) or UnitInRaid(unit) or UnitInPartyIsAI(unit)
else
return UnitIsUnit(unit, "player") or UnitIsUnit(unit, "pet") or UnitPlayerOrPetInParty(unit) or UnitPlayerOrPetInRaid(unit) or UnitInPartyIsAI(unit)
end
end
-- UnitTokenFromGUID
function F:GetTargetUnitID(target)
if UnitIsUnit(target, "player") then
return "player"
elseif UnitIsUnit(target, "pet") then
return "pet"
end
if not F:UnitInGroup(target) then return end
if UnitIsPlayer(target) or UnitInPartyIsAI(target) then
for unit in F:IterateGroupMembers() do
if UnitIsUnit(target, unit) then
return unit
end
end
else
for unit in F:IterateGroupPets() do
if UnitIsUnit(target, unit) then
return unit
end
end
end
end
function F:GetTargetPetID(target)
if UnitIsUnit(target, "player") then
return "pet"
end
if not F:UnitInGroup(target) then return end
if UnitIsPlayer(target) or UnitInPartyIsAI(target) then
for unit in F:IterateGroupMembers() do
if UnitIsUnit(target, unit) then
return F:GetPetUnit(unit)
end
end
end
end
-- https://wowpedia.fandom.com/wiki/UnitFlag
local OBJECT_AFFILIATION_MINE = 0x00000001
local OBJECT_AFFILIATION_PARTY = 0x00000002
local OBJECT_AFFILIATION_RAID = 0x00000004
function F:IsFriend(unitFlags)
if not unitFlags then return false end
return (bit.band(unitFlags, OBJECT_AFFILIATION_MINE) ~= 0) or (bit.band(unitFlags, OBJECT_AFFILIATION_RAID) ~= 0) or (bit.band(unitFlags, OBJECT_AFFILIATION_PARTY) ~= 0)
end
function F:IsPlayer(guid)
if guid then
return string.find(guid, "^Player")
end
end
function F:IsPet(guid, unit)
if unit then
return strfind(unit, "pet%d*$")
end
if guid then
return string.find(guid, "^Pet")
end
end
function F:IsNPC(guid)
if guid then
return string.find(guid, "^Creature")
end
end
function F:IsVehicle(guid)
if guid then
return string.find(guid, "^Vehicle")
end
end
function F:GetTargetUnitInfo()
if UnitIsUnit("target", "player") then
return "player", UnitName("player"), UnitClassBase("player")
elseif UnitIsUnit("target", "pet") then
return "pet", UnitName("pet")
end
if not F:UnitInGroup("target") then return end
if IsInRaid() then
for i = 1, GetNumGroupMembers() do
if UnitIsUnit("target", "raid"..i) then
return "raid"..i, UnitName("raid"..i), UnitClassBase("raid"..i)
end
if UnitIsUnit("target", "raidpet"..i) then
return "raidpet"..i, UnitName("raidpet"..i)
end
end
elseif IsInGroup() then
for i = 1, GetNumGroupMembers()-1 do
if UnitIsUnit("target", "party"..i) then
return "party"..i, UnitName("party"..i), UnitClassBase("party"..i)
end
if UnitIsUnit("target", "partypet"..i) then
return "partypet"..i, UnitName("partypet"..i)
end
end
end
end
function F:HasPermission(isPartyMarkPermission)
if isPartyMarkPermission and IsInGroup() and not IsInRaid() then return true end
return UnitIsGroupLeader("player") or (IsInRaid() and UnitIsGroupAssistant("player"))
end
-------------------------------------------------
-- range check
-------------------------------------------------
local UnitIsVisible = UnitIsVisible
local UnitInRange = UnitInRange
local UnitCanAssist = UnitCanAssist
local UnitCanAttack = UnitCanAttack
local UnitCanCooperate = UnitCanCooperate
local IsSpellInRange = (C_Spell and C_Spell.IsSpellInRange) and C_Spell.IsSpellInRange or IsSpellInRange
local IsItemInRange = (C_Spell and C_Item.IsItemInRange) and C_Item.IsItemInRange or IsItemInRange
local CheckInteractDistance = CheckInteractDistance
local UnitIsDead = UnitIsDead
local IsSpellKnown = IsSpellKnown
-- local GetSpellTabInfo = GetSpellTabInfo
-- local GetNumSpellTabs = GetNumSpellTabs
-- local GetSpellBookItemName = GetSpellBookItemName
-- local BOOKTYPE_SPELL = BOOKTYPE_SPELL
local UnitInSamePhase
if Cell.isRetail then
UnitInSamePhase = function(unit)
return not UnitPhaseReason(unit)
end
else
UnitInSamePhase = UnitInPhase
end
local playerClass = UnitClassBase("player")
local friendSpells = {
-- ["DEATHKNIGHT"] = 47541,
-- ["DEMONHUNTER"] = ,
["DRUID"] = (Cell.isWrath or Cell.isVanilla) and 5185 or 8936, -- 治疗之触 / 愈合
["EVOKER"] = 361469, -- 活化烈焰
-- ["HUNTER"] = 136,
["MAGE"] = 1459, -- 奥术智慧 / 奥术光辉
["MONK"] = 116670, -- 活血术
["PALADIN"] = Cell.isRetail and 19750 or 635, -- 圣光闪现 / 圣光术
["PRIEST"] = (Cell.isWrath or Cell.isVanilla) and 2050 or 2061, -- 次级治疗术 / 快速治疗
-- ["ROGUE"] = Cell.isWrath and 57934,
["SHAMAN"] = Cell.isRetail and 8004 or 331, -- 治疗之涌 / 治疗波
["WARLOCK"] = 5697, -- 无尽呼吸
-- ["WARRIOR"] = 3411,
}
local deadSpells = {
["EVOKER"] = 361227, -- resurrection range, need separately for evoker
}
local petSpells = {
["HUNTER"] = 136,
}
local harmSpells = {
["DEATHKNIGHT"] = 47541,
["DEMONHUNTER"] = 185123,
["DRUID"] = 5176,
["EVOKER"] = 361469,
["HUNTER"] = 75,
["MAGE"] = Cell.isRetail and 116 or 133,
["MONK"] = 117952,
["PALADIN"] = 20271,
["PRIEST"] = Cell.isRetail and 589 or 585,
["ROGUE"] = 1752,
["SHAMAN"] = Cell.isRetail and 188196 or 403,
["WARLOCK"] = 686,
["WARRIOR"] = 355,
}
-- local friendItems = {
-- ["DEATHKNIGHT"] = 34471,
-- ["DEMONHUNTER"] = 34471,
-- ["DRUID"] = 34471,
-- ["EVOKER"] = 1180, -- 30y
-- ["HUNTER"] = 34471,
-- ["MAGE"] = 34471,
-- ["MONK"] = 34471,
-- ["PALADIN"] = 34471,
-- ["PRIEST"] = 34471,
-- ["ROGUE"] = 34471,
-- ["SHAMAN"] = 34471,
-- ["WARLOCK"] = 34471,
-- ["WARRIOR"] = 34471,
-- }
local harmItems = {
["DEATHKNIGHT"] = 28767, -- 40y
["DEMONHUNTER"] = 28767, -- 40y
["DRUID"] = 28767, -- 40y
["EVOKER"] = 24268, -- 25y
["HUNTER"] = 28767, -- 40y
["MAGE"] = 28767, -- 40y
["MONK"] = 28767, -- 40y
["PALADIN"] = 835, -- 30y
["PRIEST"] = 28767, -- 40y
["ROGUE"] = 28767, -- 40y
["SHAMAN"] = 28767, -- 40y
["WARLOCK"] = 28767, -- 40y
["WARRIOR"] = 28767, -- 40y
}
-- local FindSpellIndex
-- if C_SpellBook and C_SpellBook.FindSpellBookSlotForSpell then
-- FindSpellIndex = function(spellName)
-- if not spellName or spellName == "" then return end
-- return C_SpellBook.FindSpellBookSlotForSpell(spellName)
-- end
-- else
-- local function GetNumSpells()
-- local _, _, offset, numSpells = GetSpellTabInfo(GetNumSpellTabs())
-- return offset + numSpells
-- end
-- FindSpellIndex = function(spellName)
-- if not spellName or spellName == "" then return end
-- for i = 1, GetNumSpells() do
-- local spell = GetSpellBookItemName(i, BOOKTYPE_SPELL)
-- if spell == spellName then
-- return i
-- end
-- end
-- end
-- end
local UnitInSpellRange
if C_Spell and C_Spell.IsSpellInRange then
UnitInSpellRange = function(spellName, unit)
return IsSpellInRange(spellName, unit)
end
else
UnitInSpellRange = function(spellName, unit)
return IsSpellInRange(spellName, unit) == 1
end
end
local rc = CreateFrame("Frame")
rc:RegisterEvent("SPELLS_CHANGED")
local spell_friend, spell_pet, spell_harm, spell_dead
rc:SetScript("OnEvent", function()
if friendSpells[playerClass] and IsSpellKnown(friendSpells[playerClass]) then
spell_friend = F:GetSpellInfo(friendSpells[playerClass])
end
if petSpells[playerClass] and IsSpellKnown(petSpells[playerClass]) then
spell_pet = F:GetSpellInfo(petSpells[playerClass])
end
if harmSpells[playerClass] and IsSpellKnown(harmSpells[playerClass]) then
spell_harm = F:GetSpellInfo(harmSpells[playerClass])
end
if deadSpells[playerClass] and IsSpellKnown(deadSpells[playerClass]) then
spell_dead = F:GetSpellInfo(deadSpells[playerClass])
end
F:Debug(
"[RANGE CHECK]",
"\nfriend:", spell_friend or "nil",
"\npet:", spell_pet or "nil",
"\nharm:", spell_harm or "nil",
"\ndead:", spell_dead or "nil"
)
end)
function F:IsInRange(unit)
if not UnitIsVisible(unit) then
return false
end
if UnitIsUnit("player", unit) then
return true
-- elseif not check and F:UnitInGroup(unit) then
-- -- NOTE: UnitInRange only works with group players/pets --! but not available for PLAYER PET when SOLO
-- local inRange, checked = UnitInRange(unit)
-- if not checked then
-- return F:IsInRange(unit, true)
-- end
-- return inRange
else
if UnitCanAssist("player", unit) then
if not (UnitIsConnected(unit) and UnitInSamePhase(unit)) then
return false
end
if UnitIsDead(unit) then
if spell_dead then
return UnitInSpellRange(spell_dead, unit)
end
elseif spell_friend then
return UnitInSpellRange(spell_friend, unit)
end
local inRange, checked = UnitInRange(unit)
if checked then
return inRange
end
if UnitIsUnit(unit, "pet") and spell_pet then
-- no spell_friend, use spell_pet
return UnitInSpellRange(spell_pet, unit)
end
elseif UnitCanAttack("player", unit) then
if UnitIsDead(unit) then
return CheckInteractDistance(unit, 4) -- 28 yards
elseif spell_harm then
return UnitInSpellRange(spell_harm, unit)
end
return IsItemInRange(harmItems[playerClass], unit)
end
if not InCombatLockdown() then
return CheckInteractDistance(unit, 4) -- 28 yards
end
return true
end
end
-------------------------------------------------
-- RangeCheck debug
-------------------------------------------------
local debug = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
debug:SetBackdrop({bgFile = Cell.vars.whiteTexture})
debug:SetBackdropColor(0.1, 0.1, 0.1, 0.9)
debug:SetBackdropBorderColor(0, 0, 0, 1)
debug:SetSize(1, 1)
debug:SetPoint("LEFT", 300, 0)
debug:Hide()
debug.text = debug:CreateFontString(nil, "OVERLAY")
debug.text:SetFont(GameFontNormal:GetFont(), 13, "")
debug.text:SetShadowColor(0, 0, 0)
debug.text:SetShadowOffset(1, -1)
debug.text:SetJustifyH("LEFT")
debug.text:SetSpacing(5)
debug.text:SetPoint("LEFT", 5, 0)
local function GetResult1()
local inRange, checked = UnitInRange("target")
return "UnitID: " .. (F:GetTargetUnitID("target") or "target") ..
"\n|cffffff00F.IsInRange:|r " .. (F:IsInRange("target") and "true" or "false") ..
"\nUnitInRange: " .. (checked and "checked" or "unchecked") .. " " .. (inRange and "true" or "false") ..
"\nUnitIsVisible: " .. (UnitIsVisible("target") and "true" or "false") ..
"\n\nUnitCanAssist: " .. (UnitCanAssist("player", "target") and "true" or "false") ..
"\nUnitCanAttack: " .. (UnitCanAttack("player", "target") and "true" or "false") ..
"\n\nUnitIsConnected: " .. (UnitIsConnected("target") and "true" or "false") ..
"\nUnitInSamePhase: " .. (UnitInSamePhase("target") and "true" or "false") ..
"\nUnitIsDead: " .. (UnitIsDead("target") and "true" or "false") ..
"\n\nspell_friend: " .. (spell_friend and (spell_friend .. " " .. (UnitInSpellRange(spell_friend, "target") and "true" or "false")) or "none") ..
"\nspell_dead: " .. (spell_dead and (spell_dead .. " " .. (UnitInSpellRange(spell_dead, "target") and "true" or "false")) or "none") ..
"\nspell_pet: " .. (spell_pet and (spell_pet .. " " .. (UnitInSpellRange(spell_pet, "target") and "true" or "false")) or "none") ..
"\nspell_harm: " .. (spell_harm and (spell_harm .. " " .. (UnitInSpellRange(spell_harm, "target") and "true" or "false")) or "none")
end
local function GetResult2()
if UnitCanAttack("player", "target") then
return "IsItemInRange: " .. (IsItemInRange(harmItems[playerClass], "target") and "true" or "false") ..
"\nCheckInteractDistance(28y): " .. (CheckInteractDistance("target", 4) and "true" or "false")
else
return "IsItemInRange: " .. (InCombatLockdown() and "notAvailable" or (IsItemInRange(harmItems[playerClass], "target") and "true" or "false")) ..
"\nCheckInteractDistance(28y): " .. (InCombatLockdown() and "notAvailable" or (CheckInteractDistance("target", 4) and "true" or "false"))
end
end
debug:SetScript("OnUpdate", function(self, elapsed)
self.elapsed = (self.elapsed or 0) + elapsed
if self.elapsed >= 0.25 then
self.elapsed = 0
local result = GetResult1() .. "\n\n" .. GetResult2()
result = string.gsub(result, "none", "|cffabababnone|r")
result = string.gsub(result, "true", "|cff00ff00true|r")
result = string.gsub(result, "false", "|cffff0000false|r")
result = string.gsub(result, " checked", " |cff00ff00checked|r")
result = string.gsub(result, "unchecked", "|cffff0000unchecked|r")
debug.text:SetText("|cffff0066Cell Range Check (Target)|r\n\n" .. result)
debug:SetSize(debug.text:GetStringWidth() + 10, debug.text:GetStringHeight() + 20)
end
end)
debug:SetScript("OnEvent", function()
if not UnitExists("target") then
debug:Hide()
return
end
debug:Show()
end)
SLASH_CELLRC1 = "/cellrc"
function SlashCmdList.CELLRC()
if debug:IsEventRegistered("PLAYER_TARGET_CHANGED") then
debug:UnregisterEvent("PLAYER_TARGET_CHANGED")
debug:Hide()
else
debug:RegisterEvent("PLAYER_TARGET_CHANGED")
if UnitExists("target") then
debug:Show()
end
end
end
-------------------------------------------------
-- LibSharedMedia
-------------------------------------------------
Cell.vars.texture = "Interface\\AddOns\\Cell\\Media\\statusbar.tga"
Cell.vars.emptyTexture = "Interface\\AddOns\\Cell\\Media\\empty.tga"
Cell.vars.whiteTexture = "Interface\\AddOns\\Cell\\Media\\white.tga"
local LSM = LibStub("LibSharedMedia-3.0", true)
LSM:Register("statusbar", "Cell ".._G.DEFAULT, Cell.vars.texture)
LSM:Register("font", "visitor", [[Interface\Addons\Cell\Media\Fonts\visitor.ttf]], 255)
function F:GetBarTexture()
--! update Cell.vars.texture for further use in UnitButton_OnLoad
if LSM:IsValid("statusbar", CellDB["appearance"]["texture"]) then
Cell.vars.texture = LSM:Fetch("statusbar", CellDB["appearance"]["texture"])
else
Cell.vars.texture = "Interface\\AddOns\\Cell\\Media\\statusbar.tga"
end
return Cell.vars.texture
end
function F:GetBarTextureByName(name)
if LSM:IsValid("statusbar", name) then
return LSM:Fetch("statusbar", name)
end
return "Interface\\AddOns\\Cell\\Media\\statusbar.tga"
end
function F:GetFont(font)
if font and LSM:IsValid("font", font) then
return LSM:Fetch("font", font)
elseif type(font) == "string" and strfind(strlower(font), ".ttf$") then
return font
else
if CellDB["appearance"]["useGameFont"] then
return GameFontNormal:GetFont()
else
return "Interface\\AddOns\\Cell\\Media\\Fonts\\Accidental_Presidency.ttf"
end
end
end
local defaultFontName = "Cell ".._G.DEFAULT
local defaultFont
function F:GetFontItems()
if CellDB["appearance"]["useGameFont"] then
defaultFont = GameFontNormal:GetFont()
else
defaultFont = "Interface\\AddOns\\Cell\\Media\\Fonts\\Accidental_Presidency.ttf"
end
local items = {}
local fonts, fontNames
-- if LSM then
fonts, fontNames = F:Copy(LSM:HashTable("font")), F:Copy(LSM:List("font"))
-- insert default font
tinsert(fontNames, 1, defaultFontName)
fonts[defaultFontName] = defaultFont
for _, name in pairs(fontNames) do
tinsert(items, {
["text"] = name,
["font"] = fonts[name],
-- ["onClick"] = function()
-- CellDB["appearance"]["font"] = name
-- Cell:Fire("UpdateAppearance", "font")
-- end,
})
end
-- else
-- fontNames = {defaultFontName}
-- fonts = {[defaultFontName] = defaultFont}
-- tinsert(items, {
-- ["text"] = defaultFontName,
-- ["font"] = defaultFont,
-- -- ["onClick"] = function()
-- -- CellDB["appearance"]["font"] = defaultFontName
-- -- Cell:Fire("UpdateAppearance", "font")
-- -- end,
-- })
-- end
return items, fonts, defaultFontName, defaultFont
end
-------------------------------------------------
-- texture
-------------------------------------------------
function F:GetTexCoord(width, height)
-- ULx,ULy, LLx,LLy, URx,URy, LRx,LRy
local texCoord = {0.12, 0.12, 0.12, 0.88, 0.88, 0.12, 0.88, 0.88}
local aspectRatio = width / height
local xRatio = aspectRatio < 1 and aspectRatio or 1
local yRatio = aspectRatio > 1 and 1 / aspectRatio or 1
for i, coord in ipairs(texCoord) do
local aspectRatio = (i % 2 == 1) and xRatio or yRatio
texCoord[i] = (coord - 0.5) * aspectRatio + 0.5
end
return texCoord
end
-- function F:RotateTexture(tex, degrees)
-- local angle = math.rad(degrees)
-- local cos, sin = math.cos(angle), math.sin(angle)
-- tex:SetTexCoord((sin - cos), -(cos + sin), -cos, -sin, sin, -cos, 0, 0)
-- end
-- https://wowpedia.fandom.com/wiki/Applying_affine_transformations_using_SetTexCoord
local s2 = sqrt(2)
local function CalculateCorner(degrees)
local r = math.rad(degrees)
return 0.5 + math.cos(r) / s2, 0.5 + math.sin(r) / s2
end
function F:RotateTexture(texture, degrees)
local LRx, LRy = CalculateCorner(degrees + 45)
local LLx, LLy = CalculateCorner(degrees + 135)
local ULx, ULy = CalculateCorner(degrees + 225)
local URx, URy = CalculateCorner(degrees - 45)
texture:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)
end
-- wow atlases
local wowAtlases = {
"playerpartyblip",
"Artifacts-PerkRing-WhiteGlow",
"AftLevelup-WhiteIconGlow",
"LootBanner-IconGlow",
"AftLevelup-WhiteStarBurst",
"ChallengeMode-WhiteSpikeyGlow",
"UI-QuestPoiCampaign-OuterGlow",
"vignettekill",
"PetJournal-FavoritesIcon",
"dungeonskull",
"questnormal",
"questturnin",
"bags-icon-addslots",
"communities-chat-icon-plus",
"communities-chat-icon-minus",
}
-- wow textures
local wowTextures = {
}
-- shapes
local shapes = {
"circle_blurred",
"circle_filled",
"circle_thin",
"circle",
"heart_filled",
"heart",
"rhombus",
"rhombus_filled",
"square_filled",
"square",
"star_filled",
"star",
"starburst_filled",
"starburst",
"triangle_filled",
"triangle",
}
-- weakauras
local powaTextures = {
9, 10, 12, 13, 14, 15, 21, 22, 25, 27, 29,
37, 38, 39, 40, 41, 42, 43, 44,
49, 51, 52, 53, 58, 78, 118, 84,
96, 97, 98, 99, 100, 114, 115, 116, 132, 138, 143
}
function F:GetTextures()
local builtIns = #wowAtlases + #wowTextures + #shapes
local t = {}
-- wow atlases
for _, wa in pairs(wowAtlases) do
tinsert(t, wa)
end
-- wow textures
for _, wt in pairs(wowTextures) do
tinsert(t, wt)
end
-- built-ins
for _, s in pairs(shapes) do
tinsert(t, "Interface\\AddOns\\Cell\\Media\\Shapes\\"..s..".tga")
end
-- add weakauras textures
if WeakAuras then
builtIns = builtIns + #powaTextures
for _, powa in pairs(powaTextures) do
tinsert(t, "Interface\\AddOns\\WeakAuras\\PowerAurasMedia\\Auras\\Aura"..powa..".tga")
end
end
-- customs
for _, path in pairs(CellDB["customTextures"]) do
tinsert(t, path)
end
return builtIns, t
end
function F:GetDefaultRoleIcon(role)
if not role or role == "NONE" then return "" end
return "Interface\\AddOns\\Cell\\Media\\Roles\\Default_" .. role
end
function F:GetDefaultRoleIconEscapeSequence(role, size)
if not role or role == "NONE" then return "" end
return "|TInterface\\AddOns\\Cell\\Media\\Roles\\Default_" .. role .. ":" .. (size or 0) .. "|t"
end
-------------------------------------------------
-- frame
-------------------------------------------------
function F:GetMouseFocus()
if Cell.isTWW then
return GetMouseFoci()[1] -- Latest Beta build changed this to return the table under key `1`
else
return GetMouseFocus()
end
end
-------------------------------------------------
-- instance
-------------------------------------------------
function F:GetInstanceName()
if IsInInstance() then
local name = GetInstanceInfo()
if not name then name = GetRealZoneText() end
return name
else
local mapID = C_Map.GetBestMapForUnit("player")
if type(mapID) ~= "number" or mapID < 1 then
return ""
end
local info = MapUtil.GetMapParentInfo(mapID, Enum.UIMapType.Continent, true)
if info then
return info.name, info.mapID
end
return ""
end
end
-------------------------------------------------
-- spell
-------------------------------------------------
-- https://wow.gamepedia.com/UIOBJECT_GameTooltip
-- local function EnumerateTooltipLines_helper(...)
-- for i = 1, select("#", ...) do
-- local region = select(i, ...)
-- if region and region:GetObjectType() == "FontString" then
-- local text = region:GetText() -- string or nil
-- print(region:GetName(), text)
-- end
-- end
-- end
-- https://wowpedia.fandom.com/wiki/Patch_10.0.2/API_changes
local lines = {}
function F:GetSpellTooltipInfo(spellId)
wipe(lines)
local name, icon = F:GetSpellInfo(spellId)
if not name then return end
local data = C_TooltipInfo.GetSpellByID(spellId)
for i, line in ipairs(data.lines) do
TooltipUtil.SurfaceArgs(line)
-- line.leftText
-- line.rightText
end
return name, icon, table.concat(lines, "\n")
end
if Cell.isRetail then
function F:GetSpellInfo(spellId)
if C_Spell and C_Spell.GetSpellInfo then
local info = C_Spell.GetSpellInfo(spellId)
if not info then return end
if not info.iconID then -- FIXME:
info.iconID = C_Spell.GetSpellTexture(spellId)
end
return info.name, info.iconID
end
-- TODO: remove once 11.0 prepatch hits
local name, _, icon = GetSpellInfo(spellId)
return name, icon
end
else
function F:GetSpellInfo(spellId)
local name, _, icon = GetSpellInfo(spellId)
return name, icon
end
end
-------------------------------------------------
-- macro
-------------------------------------------------
local mc = CreateFrame("Frame")
mc:RegisterEvent("UPDATE_MACROS")
local macroIndices = {}
mc:SetScript("OnEvent", function()
wipe(macroIndices)
local global, perChar = GetNumMacros()
for i = 1, global do
tinsert(macroIndices, i)
end
for i = 1, perChar do
tinsert(macroIndices, 120 + i)
end
end)
function F:GetMacroIndices()
return macroIndices
end
-------------------------------------------------
-- auras
-------------------------------------------------
-- name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId, canApplyAura, isBossDebuff, castByPlayer, nameplateShowAll, timeMod = UnitAura
-- NOTE: FrameXML/AuraUtil.lua
-- AuraUtil.FindAura(predicate, unit, filter, predicateArg1, predicateArg2, predicateArg3)
-- predicate(predicateArg1, predicateArg2, predicateArg3, ...)
local function predicate(...)
local idToFind = ...
local id = select(13, ...)
return idToFind == id
end
function F:FindAuraById(unit, type, spellId)
if type == "BUFF" then
return AuraUtil.FindAura(predicate, unit, "HELPFUL", spellId)
else
return AuraUtil.FindAura(predicate, unit, "HARMFUL", spellId)
end
end
if Cell.isRetail then
function F:FindDebuffByIds(unit, spellIds)
local debuffs = {}
AuraUtil.ForEachAura(unit, "HARMFUL", nil, function(name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId)
if spellIds[spellId] then
debuffs[spellId] = I.CheckDebuffType(debuffType, spellId)
end
end)
return debuffs
end
function F:FindAuraByDebuffTypes(unit, types)
local debuffs = {}
AuraUtil.ForEachAura(unit, "HARMFUL", nil, function(name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId)
if types == "all" or types[debuffType] then
debuffs[spellId] = I.CheckDebuffType(debuffType, spellId)
end
end)
return debuffs
end
else
function F:FindDebuffByIds(unit, spellIds)
local debuffs = {}
for i = 1, 40 do
local name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId = UnitDebuff(unit, i)
if not name then
break
end
if spellIds[spellId] then
debuffs[spellId] = I.CheckDebuffType(debuffType, spellId)
end
end
return debuffs
end
function F:FindAuraByDebuffTypes(unit, types)
local debuffs = {}
for i = 1, 40 do
local name, icon, count, debuffType, duration, expirationTime, source, isStealable, nameplateShowPersonal, spellId = UnitDebuff(unit, i)
if not name then
break
end
if types == "all" or types[debuffType] then
debuffs[spellId] = I.CheckDebuffType(s, spellId)
end
end
return debuffs
end
end
-------------------------------------------------
-- OmniCD
-------------------------------------------------
function F:UpdateOmniCDPosition(frame)
if OmniCD and OmniCD[1].db.position.uf == frame then
C_Timer.After(0.5, function()
OmniCD[1].Party:UpdatePosition()
end)
end
end
-------------------------------------------------
-- LibGetFrame
-------------------------------------------------
function Cell.GetUnitFramesForLGF(unit, frames, priorities)
frames = frames or {}
local normal, spotlights, quickAssist = F:GetUnitButtonByUnit(unit, true, true)
if normal and normal:IsVisible() and normal.widgets and normal.widgets.highLevelFrame then
frames[normal.widgets.highLevelFrame] = "CellNormalUnitFrame"
end
if spotlights then
for _, spotlight in pairs(spotlights) do
if not strfind(spotlight.unit, "target$") and spotlight.widgets and spotlight.widgets.highLevelFrame then
frames[spotlight.widgets.highLevelFrame] = "CellSpotlightUnitFrame"
break
end
end
end
if quickAssist and quickAssist:IsVisible() then
frames[quickAssist] = "CellQuickAssistUnitFrame"
end
if CellDB["general"]["framePriority"] == "normal_spotlight_quickassist" then
tinsert(priorities, 1, "^CellNormalUnitFrame$")
tinsert(priorities, 2, "^CellSpotlightUnitFrame$")
tinsert(priorities, 3, "^CellQuickAssistUnitFrame$")
elseif CellDB["general"]["framePriority"] == "spotlight_normal_quickassist" then
tinsert(priorities, 1, "^CellSpotlightUnitFrame$")
tinsert(priorities, 2, "^CellNormalUnitFrame$")
tinsert(priorities, 3, "^CellQuickAssistUnitFrame$")
else -- "quickassist_normal_spotlight"
tinsert(priorities, 1, "^CellQuickAssistUnitFrame$")
tinsert(priorities, 2, "^CellNormalUnitFrame$")
tinsert(priorities, 3, "^CellSpotlightUnitFrame$")
end
return frames
end