local ADDON_NAME, Addon = ... --------------------------------------------------------------------------------------------------- -- Define table that contains all addon-global variables and functions --------------------------------------------------------------------------------------------------- Addon.ThreatPlates = {} local ThreatPlates = Addon.ThreatPlates --------------------------------------------------------------------------------------------------- -- Imported functions and constants --------------------------------------------------------------------------------------------------- -- Lua APIs local string, floor = string, floor local rawset = rawset -- WoW APIs local UnitIsUnit, UnitClass, UnitExists = UnitIsUnit, UnitClass, UnitExists -- ThreatPlates APIs local UnitDetailedThreatSituation = UnitDetailedThreatSituation --------------------------------------------------------------------------------------------------- -- WoW Version Check --------------------------------------------------------------------------------------------------- Addon.IS_CLASSIC = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC) Addon.IS_CLASSIC_SOD = (Addon.IS_CLASSIC and (select(4, GetBuildInfo()) >= 11500)) Addon.IS_TBC_CLASSIC = (GetClassicExpansionLevel and GetClassicExpansionLevel() == LE_EXPANSION_BURNING_CRUSADE) Addon.IS_WRATH_CLASSIC = (GetClassicExpansionLevel and GetClassicExpansionLevel() == LE_EXPANSION_WRATH_OF_THE_LICH_KING) Addon.IS_CATA_CLASSIC = (GetClassicExpansionLevel and GetClassicExpansionLevel() == LE_EXPANSION_CATACLYSM) Addon.IS_MAINLINE = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE) -- Addon.IS_TBC_CLASSIC = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC and LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_BURNING_CRUSADE) -- Addon.IS_WRATH_CLASSIC = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC and LE_EXPANSION_LEVEL_CURRENT == LE_EXPANSION_WRATH_OF_THE_LICH_KING) Addon.WOW_USES_CLASSIC_NAMEPLATES = (Addon.IS_CLASSIC or Addon.IS_TBC_CLASSIC or Addon.IS_WRATH_CLASSIC or Addon.IS_CATA_CLASSIC) -- ? Addon.WOW_FEATURE_ABSORBS Addon.ExpansionIsClassicAndAtLeast = function(expansion_id) -- Method does not exist which means that this is the most recent / highest WoW version (Mainline) -- so we have to return true -- Method exists, so this is a Classic WoW version, but as the expansion id is unknown, the expansion is -- an older one, so we have to return false if Addon.IS_MAINLINE or not GetClassicExpansionLevel or not expansion_id then return false else return GetClassicExpansionLevel() >= expansion_id end end -- For Mainline, this always returns true. Addon.ExpansionIsAtLeast = function(expansion_id) return Addon.IS_MAINLINE or Addon.ExpansionIsClassicAndAtLeast(expansion_id) end --------------------------------------------------------------------------------------------------- -- Libraries --------------------------------------------------------------------------------------------------- local LibStub = LibStub ThreatPlates.L = LibStub("AceLocale-3.0"):GetLocale("TidyPlatesThreat") Addon.BackdropTemplate = BackdropTemplateMixin and "BackdropTemplate" local L = ThreatPlates.L -- Use this once SetBackdrop backwards compatibility is removed --if BackdropTemplateMixin then -- Shadowlands -- Addon.BackdropTemplate = "BackdropTemplate" -- -- Addon.SetBackdrop = function(frame, backdrop) -- frame.backdropInfo = backdrop -- frame:ApplyBackdrop() -- end --else -- Addon.SetBackdrop = function(frame, backdrop) -- frame:SetBackdrop(backdrop) -- end --end --------------------------------------------------------------------------------------------------- -- Define AceAddon TidyPlatesThreat --------------------------------------------------------------------------------------------------- TidyPlatesThreat = LibStub("AceAddon-3.0"):NewAddon("TidyPlatesThreat", "AceConsole-3.0", "AceEvent-3.0") -- Global for DBM to differentiate between Threat Plates and Tidy Plates: Threat TidyPlatesThreatDBM = true -- Returns if the currently active spec is tank (true) or dps/heal (false) Addon.PlayerClass = select(2, UnitClass("player")) Addon.PlayerName = select(1, UnitName("player")) -- Cache (weak table) for memoizing expensive text functions (like abbreviation, tranliteration) local CacheCreateEntry = function(table, key) local entry = {} rawset(table, key, entry) return entry end local CreateTextCache = function() local text_cache = {} setmetatable(text_cache, { __mode = "kv", __index = CacheCreateEntry, }) return text_cache end --------------------------------------------------------------------------------------------------- -- Caches to the reduce CPU load of expensive functions --------------------------------------------------------------------------------------------------- Addon.Animations = {} Addon.Cache = { Texts = CreateTextCache(), TriggerWildcardTests = {}, CustomPlateTriggers = { Name = {}, NameWildcard = {}, Aura = {}, Cast = {}, Script = {}, }, Styles = { ForAllInstances = {}, PerInstance = {}, ForCurrentInstance = {}, } } -- Internal API Addon.Data = {} Addon.Logging = {} Addon.Debug = {} --------------------------------------------------------------------------------------------------- -- Addon-wide wrapper functions and constants for WoW Classic --------------------------------------------------------------------------------------------------- -- UnitDetailedThreatSituation: WotLK - Patch 3.0.2 (2008-10-14): Added -- C_PvP.IsSoloShuffle: Shadowlands - Patch 9.2.0 (2022-02-22): Added. if Addon.IS_MAINLINE then Addon.UnitDetailedThreatSituationWrapper = UnitDetailedThreatSituation Addon.IsSoloShuffle = C_PvP.IsSoloShuffle else Addon.UnitDetailedThreatSituationWrapper = function(source, target) local is_tanking, status, threatpct, rawthreatpct, threat_value = UnitDetailedThreatSituation(source, target) if (threat_value) then threat_value = floor(threat_value / 100) end return is_tanking, status, threatpct, rawthreatpct, threat_value end -- Not available in Classic Addon.IsSoloShuffle = function() return false end end --------------------------------------------------------------------------------------------------- -- Aura Highlighting --------------------------------------------------------------------------------------------------- local function Wrapper_ButtonGlow_Start(frame, color, framelevel) Addon.LibCustomGlow.ButtonGlow_Start(frame, color, nil, framelevel) end local function Wrapper_PixelGlow_Start(frame, color, framelevel) Addon.LibCustomGlow.PixelGlow_Start(frame, color, nil, nil, nil, nil, nil, nil, nil, nil, framelevel) end local function Wrapper_AutoCastGlow_Start(frame, color, framelevel) Addon.LibCustomGlow.AutoCastGlow_Start(frame, color, nil, nil, nil, nil, nil, nil, framelevel) end Addon.CUSTOM_GLOW_FUNCTIONS = { Button = { "ButtonGlow_Start", "ButtonGlow_Stop", 8 }, Pixel = { "PixelGlow_Start", "PixelGlow_Stop", 3 }, AutoCast = { "AutoCastGlow_Start", "AutoCastGlow_Stop", 4 }, } Addon.CUSTOM_GLOW_WRAPPER_FUNCTIONS = { ButtonGlow_Start = Wrapper_ButtonGlow_Start, PixelGlow_Start = Wrapper_PixelGlow_Start, AutoCastGlow_Start = Wrapper_AutoCastGlow_Start, } -------------------------------------------------------------------------------------------------- -- General Functions --------------------------------------------------------------------------------------------------- Addon.LoadOnDemandLibraries = function() local db = Addon.db.profile -- Enable or disable LibDogTagSupport based on custom status text being actually used if db.HeadlineView.FriendlySubtext == "CUSTOM" or db.HeadlineView.EnemySubtext == "CUSTOM" or db.settings.customtext.FriendlySubtext == "CUSTOM" or db.settings.customtext.EnemySubtext == "CUSTOM" then if Addon.LibDogTag == nil then LoadAddOn("LibDogTag-3.0") Addon.LibDogTag = LibStub("LibDogTag-3.0", true) if not Addon.LibDogTag then Addon.LibDogTag = false Addon.Logging.Error(L["Custom status text requires LibDogTag-3.0 to function."]) else LoadAddOn("LibDogTag-Unit-3.0") if not LibStub("LibDogTag-Unit-3.0", true) then Addon.LibDogTag = false Addon.Logging.Error(L["Custom status text requires LibDogTag-Unit-3.0 to function."]) elseif not Addon.LibDogTag.IsLegitimateUnit or not Addon.LibDogTag.IsLegitimateUnit["nameplate1"] then Addon.LibDogTag = false Addon.Logging.Error(L["Your version of LibDogTag-Unit-3.0 does not support nameplates. You need to install at least v90000.3 of LibDogTag-Unit-3.0."]) end end end end end -------------------------------------------------------------------------------------------------- -- Utils: Handling of colors --------------------------------------------------------------------------------------------------- -- Create a percentage-based WoW color based on integer values from 0 to 255 with optional alpha value ThreatPlates.RGB = function(red, green, blue, alpha) local color = { r = red/255, g = green/255, b = blue/255 } if alpha then color.a = alpha end return color end ThreatPlates.RGB_WITH_HEX = function(red, green, blue, alpha) local color = ThreatPlates.RGB(red, green, blue, alpha) color.colorStr = CreateColor(color.r, color.g, color.b, color.a):GenerateHexColor() return color end ThreatPlates.RGB_P = function(red, green, blue, alpha) return { r = red, g = green, b = blue, a = alpha} end ThreatPlates.RGB_UNPACK = function(color) return color.r, color.g, color.b, color.a or 1 end -- thanks to https://github.com/Perkovec/colorise-lua ThreatPlates.HEX2RGB = function (hex) hex = hex:gsub("#","") return tonumber("0x"..hex:sub(1,2)), tonumber("0x"..hex:sub(3,4)), tonumber("0x"..hex:sub(5,6)) end Addon.ColorByClass = function(class_name, text) if class_name then return "|c" .. Addon.db.profile.Colors.Classes[class_name].colorStr .. text .. "|r" else return text end end -- Addon.ColorByClassUnitID = function(unitid, text) -- local _, class_name = UnitClass(unitid) -- return Addon.ColorByClass(class_name, text) -- end -------------------------------------------------------------------------------------------------- -- Utility functions --------------------------------------------------------------------------------------------------- ThreatPlates.Meta = function(value) local meta if strlower(value) == "titleshort" then meta = "|cff89F559TP|r" else meta = GetAddOnMetadata("TidyPlates_ThreatPlates",value) end return meta or "" end do ThreatPlates.HCC = {} for i=1,#CLASS_SORT_ORDER do local str = RAID_CLASS_COLORS[CLASS_SORT_ORDER[i]].colorStr; local str = gsub(str,"(ff)","",1) ThreatPlates.HCC[CLASS_SORT_ORDER[i]] = str; end end -- Helper Functions ThreatPlates.STT = function(...) local s = {} local i, l for i = 1, select("#", ...) do l = select(i, ...) if l ~= "" then s[l] = true end end return s end ThreatPlates.TTS = function(s) local list for i=1,#s do if not list then list = tostring(s[i]).."\n" else local nL = s[i] if nL ~= "" then list = list..tostring(nL).."\n" else list = list..tostring(nL) end end end return list end ThreatPlates.CopyTable = function(input) local output = {} for k,v in pairs(input) do if type(v) == "table" then output[k] = ThreatPlates.CopyTable(v) else output[k] = v end end return output end Addon.MergeIntoTable = function(target, source) if source == nil then return end for k,v in pairs(source) do if type(v) == "table" then target[k] = target[k] or {} Addon.MergeIntoTable(target[k], v) else target[k] = v end end end Addon.MergeDefaultsIntoTable = function(target, defaults) for k,v in pairs(defaults) do if type(v) == "table" then target[k] = target[k] or {} Addon.MergeDefaultsIntoTable(target[k], v) else target[k] = target[k] or v end end end Addon.ConcatTables = function(base_table, table_to_concat) local concat_result = ThreatPlates.CopyTable(base_table) for i = 1, #table_to_concat do concat_result[#concat_result + 1] = table_to_concat[i] end return concat_result end Addon.CheckTableStructure = function(reference_structure, table_to_check) if table_to_check == nil then return false end for k,v in pairs(reference_structure) do if table_to_check[k] == nil or type(table_to_check[k]) ~= type(v) then return false elseif type(v) == "table" then if not Addon.CheckTableStructure(v, table_to_check[k]) then return false end end end return true end Addon.Split = function(split_string) local result = {} for entry in string.gmatch(split_string, "[^;]+") do result[#result + 1] = entry:gsub("^%s*(.-)%s*$", "%1") end return result end Addon.SplitByWhitespace = function(split_string) local parts = {} for w in split_string:gmatch("%S+") do parts[#parts + 1] = w end return parts, #parts end -------------------------------------------------------------------------------------------------- -- Logging Functions --------------------------------------------------------------------------------------------------- local function LogMessage(channel, ...) -- Meta("titleshort") if channel == "DEBUG" then print("|cff89F559TP|r - |cff0000ff" .. channel .. "|r:", ...) elseif channel == "ERROR" then print("|cff89F559TP|r - |cffff0000" .. channel .. "|r:", ...) elseif channel == "WARNING" then print("|cff89F559TP|r - |cffff8000" .. channel .. "|r:", ...) else print("|cff89F559TP|r:", channel, ...) end end Addon.Logging.Debug = function(...) if Addon.DEBUG then LogMessage("DEBUG", ...) end end Addon.Logging.Error = function(...) LogMessage("ERROR", ...) end Addon.Logging.Warning = function(...) LogMessage("WARNING", ...) end Addon.Logging.Info = function(...) if Addon.db.profile.verbose or Addon.DEBUG then LogMessage(...) end end Addon.Logging.Print = function(...) LogMessage(...) end -------------------------------------------------------------------------------------------------- -- Debug functions --------------------------------------------------------------------------------------------------- -- Function from: https://coronalabs.com/blog/2014/09/02/tutorial-printing-table-contents/ Addon.Debug.PrintTable = function(data) local print_r_cache={} local function sub_print_r(data,indent) if (print_r_cache[tostring(data)]) then Addon.Logging.Debug(indent.."*"..tostring(data)) else print_r_cache[tostring(data)]=true if (type(data)=="table") then for pos,val in pairs(data) do if (type(val)=="table") then Addon.Logging.Debug(indent.."["..tostring(pos).."] => "..tostring(data).." {") sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) Addon.Logging.Debug(indent..string.rep(" ",string.len(pos)+6).."}") elseif (type(val)=="string") then Addon.Logging.Debug(indent.."["..tostring(pos)..'] => "'..val..'"') else Addon.Logging.Debug(indent.."["..tostring(pos).."] => "..tostring(val)) end end else Addon.Logging.Debug(indent..tostring(data)) end end end if (type(data)=="table") then Addon.Logging.Debug(tostring(data).." {") sub_print_r(data," ") Addon.Logging.Debug("}") else sub_print_r(data," ") end end Addon.Debug.PrintUnit = function(unitid) local plate = C_NamePlate.GetNamePlateForUnit(unitid) if not plate then return end Addon.Logging.Debug("Unit:", UnitName(unitid), "=>", unitid) Addon.Logging.Debug("-------------------------------------------------------------") Addon.Logging.Debug(" Show UnitFrame =", plate.UnitFrame:IsShown()) Addon.Logging.Debug(" Show TPFrame =", plate.TPFrame:IsShown()) Addon.Logging.Debug(" Active =", plate.TPFrame.Active) Addon.Logging.Debug("-------------------------------------------------------------") if unitid then local tp_frame = plate.TPFrame local unit = tp_frame and tp_frame.unit if Addon.IS_MAINLINE then Addon.Logging.Debug(" UnitNameplateShowsWidgetsOnly = ", UnitNameplateShowsWidgetsOnly(unitid)) end Addon.Logging.Debug(" Reaction = ", UnitReaction("player", unitid)) local r, g, b, a = UnitSelectionColor(unitid, true) Addon.Logging.Debug(" SelectionColor: r =", ceil(r * 255), ", g =", ceil(g * 255), ", b =", ceil(b * 255), ", a =", ceil(a * 255)) Addon.Logging.Debug(" -- Threat ---------------------------------") Addon.Logging.Debug(" UnitAffectingCombat = ", UnitAffectingCombat(unitid)) if unit then Addon.Logging.Debug(" Addon:OnThreatTable = ", Addon:OnThreatTable(unit)) end Addon.Logging.Debug(" UnitThreatSituation = ", UnitThreatSituation("player", unitid)) Addon.Logging.Debug(" Target Unit = ", UnitExists(unitid .. "target")) if unit then if unit.style == "unique" then Addon.Logging.Debug(" GetThreatSituation(Unique) = ", Addon.GetThreatSituation(unit, unit.style, Addon.db.profile.threat.toggle.OffTank)) else Addon.Logging.Debug(" GetThreatSituation = ", Addon.GetThreatSituation(unit, Addon:GetThreatStyle(unit), Addon.db.profile.threat.toggle.OffTank)) end end Addon.Logging.Debug(" -- Player Control ---------------------------------") Addon.Logging.Debug(" UnitPlayerControlled =", UnitPlayerControlled(unitid)) Addon.Logging.Debug(" Player is UnitIsOwnerOrControllerOfUnit =", UnitIsOwnerOrControllerOfUnit("player", unitid)) Addon.Logging.Debug(" Player Pet =", UnitIsUnit(unitid, "pet")) Addon.Logging.Debug(" IsOtherPlayersPet =", UnitIsOtherPlayersPet(unitid)) if Addon.IS_MAINLINE then Addon.Logging.Debug(" IsBattlePet =", UnitIsBattlePet(unitid)) end Addon.Logging.Debug(" -- PvP ---------------------------------") Addon.Logging.Debug(" PvP On =", UnitIsPVP(unitid)) Addon.Logging.Debug(" PvP Sanctuary =", UnitIsPVPSanctuary(unitid)) -- Addon.Logging.Debug(" isFriend = ", TidyPlatesUtilityInternal.IsFriend(unit.name)) -- Addon.Logging.Debug(" isGuildmate = ", TidyPlatesUtilityInternal.IsGuildmate(unit.name)) if unit then for key, val in pairs(unit) do Addon.Logging.Debug(key .. ":", val) end else Addon.Logging.Debug(" ") end end Addon.Logging.Debug("--------------------------------------------------------------") end --local function DEBUG_PRINT_TARGET(unit) -- if unit.isTarget then -- DEBUG_PRINT_UNIT(unit) -- end --end -- --local function DEBUG_AURA_LIST(data) -- local res = "" -- for pos,val in pairs(data) do -- local a = data[pos] -- if not a then -- res = res .. " nil" -- elseif not a.priority then -- res = res .. " nil(" .. a.name .. ")" -- else -- res = res .. a.name -- end -- if pos ~= #data then -- res = res .. " - " -- end -- end -- ThreatPlates.DEBUG("Aura List = [ " .. res .. " ]") --end Addon.Debug.PrintCaches = function() print ("Wildcard Unit Test Cache:") for k, v in pairs(Addon.Cache.TriggerWildcardTests) do print (" " .. k .. ":", v) end end