--[[-------------------------------------------------------------------------- ManiaxNamePlates - the add-on entry point and its settings store. A fork of Ascension's Ascension_NamePlates, which is what actually draws the compact plates on their client. The plate rendering itself is theirs, ported file by file; this file replaces their front end. WHY THIS IS NOT ACE Upstream builds its whole options tree on LibStub("LibAscensionConfig"), an Ascension library that ships nowhere in their client that could be found - it is referenced by their add-on and defined in no archive - and it drives AceDB, AceConfig and AceConfigDialog underneath. Rewriting that onto the Ace3 in this bundle was the obvious move and is the wrong one. Ace3 here is whatever the highest LibStub version among ~90 add-ons happens to be, and pulling this add-on into that graph means it inherits every version-escalation problem the pack already has - the same mechanism that broke AtlasLoot on a comm prefix its own bundled AceComm accepted. A nameplate add-on does not need a config framework to hold four tables of numbers. So: plain SavedVariables, a defaults tree merged on load, and the four accessors the driver calls. The values below are Ascension's own defaults, read out of their NamePlateOptions.lua, so an untouched install looks like an untouched install of theirs. ----------------------------------------------------------------------------]] local addonName, addonTable = ... local Addon = {} addonTable[1] = Addon _G.ManiaxNamePlates = Addon --------------------------------------------------------------------------- -- Conflicting add-ons. -- -- Three implementations of one frame cannot coexist: they all hide the -- client's plate and draw their own, so whichever runs last wins and the -- others leave orphaned frames behind. The pack's own conflict list names -- exactly this set. -- -- Reported, never auto-disabled. This pack is opt-in - the player ticked -- these deliberately - and silently turning off something someone chose is -- worse than telling them what to choose. --------------------------------------------------------------------------- local CONFLICTS = { "TidyPlates_ThreatPlates", "TurboPlates", "ManiaxPlates", "Kui_Nameplates", } --------------------------------------------------------------------------- -- Defaults, as Ascension ships them. -- -- The clickable box defaults come from the CVars rather than from constants, -- because those are the client's own idea of a sensible plate size and it -- differs between builds. C_CVar is provided by !Maniax335Compat. --------------------------------------------------------------------------- local function cvarDefault(name, fallback) if C_CVar and C_CVar.GetDefaultNumber then local ok, v = pcall(C_CVar.GetDefaultNumber, name) if ok and type(v) == "number" and v > 0 then return v end end return fallback end local FONT = "Friz Quadrata TT" local function unitDefaults(isEnemy) return { name = { displayName = true, font = FONT, fontSize = 8, fontFlags = "NONE", }, health = { nameOnly = false, -- only meaningful for friendly width = 110, height = 4, textFormat = "perc", showTextFormat = false, font = FONT, fontSize = 8, fontFlags = "NONE", statusBar = "Blizzard2", useClassColor = true, backgroundColor = { 0.2, 0.2, 0.2, 0.85 }, }, castBar = { enabled = isEnemy, font = FONT, fontSize = 8, fontFlags = "NONE", statusBar = "Blizzard2", height = 10, }, levelIndicator = { showPlayerLevel = true, showNPCLevel = isEnemy, }, objectiveIcons = { -- Quest icons are inert on this client: the feature reads -- C_QuestLog.GetUnitQuestInfo, which has no data source here. The -- settings are kept so the shape of the table matches upstream and the -- driver's reads all resolve. showQuestNPCs = true, showQuestObjectives = true, iconScale = 0.65, anchor = "LEFT", }, } end local DEFAULTS = { general = { useClassicStyle = false, clickable = { height = cvarDefault("nameplateHeight", 36), width = cvarDefault("nameplateWidth", 110), targetScale = 1.1, }, }, friendly = unitDefaults(false), enemy = unitDefaults(true), personal = { health = { width = 80, height = 8, textFormat = "perc", showTextFormat = false, font = FONT, fontSize = 8, fontFlags = "NONE", statusBar = "Blizzard2", backgroundColor = { 0.2, 0.2, 0.2, 0.85 }, }, }, } --------------------------------------------------------------------------- -- Settings store. -- -- Copies missing keys in rather than replacing the saved table, so a profile -- saved before a new option existed gains that option instead of being reset. -- Arrays (the colours) are copied whole: a half-merged colour is worse than -- either version of it. --------------------------------------------------------------------------- local function applyDefaults(target, defaults) for k, v in pairs(defaults) do if type(v) == "table" then if v[1] ~= nil then if type(target[k]) ~= "table" then local copy = {} for i = 1, #v do copy[i] = v[i] end target[k] = copy end else if type(target[k]) ~= "table" then target[k] = {} end applyDefaults(target[k], v) end elseif target[k] == nil then target[k] = v end end return target end function Addon:GetOption(option, ...) local value = self.db and self.db[option] for i = 1, select("#", ...) do if value == nil then return nil end value = value[select(i, ...)] end return value end function Addon:GetGeneralOption(option, ...) return self:GetOption("general", option, ...) end function Addon:GetFriendlyOption(option, ...) return self:GetOption("friendly", option, ...) end function Addon:GetEnemyOption(option, ...) return self:GetOption("enemy", option, ...) end function Addon:GetPlayerOption(option, ...) return self:GetOption("personal", option, ...) end --------------------------------------------------------------------------- -- Applying settings. --------------------------------------------------------------------------- function Addon:UpdateAll() if not NamePlateDriverFrame then return end local wantHeight = self:GetGeneralOption("clickable", "height") local wantWidth = self:GetGeneralOption("clickable", "width") -- Upstream compares height against nameplateWidth and width against -- nameplateHeight here. That is transposed, and it makes the comparison -- fail whenever the two differ, so the resize path runs on every update -- rather than only when the size actually changed. Compared the right way -- round; SetNamePlateSize still takes (width, height). local haveWidth = C_CVar and C_CVar.GetNumber and C_CVar.GetNumber("nameplateWidth") local haveHeight = C_CVar and C_CVar.GetNumber and C_CVar.GetNumber("nameplateHeight") if wantWidth ~= haveWidth or wantHeight ~= haveHeight then C_NamePlateManager.SetNamePlateSize(wantWidth, wantHeight) else C_NamePlateManager.RefreshNamePlateSize() end NamePlateDriverFrame:UpdateNamePlateOptions() end --------------------------------------------------------------------------- -- Startup. --------------------------------------------------------------------------- local function warnConflicts() local found = {} for _, name in ipairs(CONFLICTS) do local _, _, _, enabled = GetAddOnInfo(name) if enabled then found[#found + 1] = name end end if #found == 0 then return end DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r is enabled alongside " .. table.concat(found, ", ") .. ". These all replace the same frame - enable only one.") end local loader = CreateFrame("Frame") loader:RegisterEvent("ADDON_LOADED") loader:RegisterEvent("PLAYER_LOGIN") loader:SetScript("OnEvent", function(self, event, arg1) if event == "ADDON_LOADED" then if arg1 ~= addonName then return end ManiaxNamePlatesDB = applyDefaults(ManiaxNamePlatesDB or {}, DEFAULTS) Addon.db = ManiaxNamePlatesDB return end -- PLAYER_LOGIN: the driver frame exists by now and the plate CVars are -- readable. RunNextFrame so this lands after every other add-on's login -- handler has had its turn at the same CVars. RunNextFrame(function() warnConflicts() Addon:UpdateAll() end) end) SLASH_MANIAXNAMEPLATES1 = "/mnp" SlashCmdList["MANIAXNAMEPLATES"] = function(input) local cmd = string.lower(string.gsub(input or "", "^%s*(.-)%s*$", "%1")) if cmd == "" or cmd == "options" or cmd == "config" then if Addon.OpenOptions then Addon:OpenOptions() else DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r options panel " .. "did not load - check that Options.lua is listed in the toc.") end return end if cmd == "debug" then -- Reports what a live plate actually looks like. The plates now throw -- nothing and still draw nothing, which is the silent failure class: the -- frame is built, the code completes, and some value along the way is zero -- or hidden. Guessing at that from source has already cost several rounds, -- so ask the frame instead. local function say2(...) DEFAULT_CHAT_FRAME:AddMessage(" " .. table.concat({...}, " ")) end local n = 0 for i = 1, 40 do local unit = "nameplate" .. i local plate = C_NamePlate and C_NamePlate.GetNamePlateForUnit and C_NamePlate.GetNamePlateForUnit(unit) if plate then n = n + 1 if n == 1 then DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r " .. unit .. " (" .. tostring(UnitName(unit)) .. ")") say2("plate", tostring(plate:GetWidth()) .. "x" .. tostring(plate:GetHeight()), "shown=" .. tostring(plate:IsShown()), "alpha=" .. tostring(plate:GetAlpha())) -- Is the CLIENT's own plate still drawing underneath ours? -- -- Two different faults produce one symptom - a stock border, a second -- level number and a stock bar showing through - and they need -- opposite fixes, so ask rather than guess. DisableBlizzPlate hides -- the plate's regions and children, but only after a parentage check: -- it requires the plate's parent to be WorldFrame and silently -- returns otherwise, because handing it any other frame blanks -- whatever that frame was drawing (that mistake cost a character -- panel and a spellbook). -- -- So: "parent=WorldFrame no, ..." means the guard is refusing and it -- has never hidden anything on any plate. "parent=WorldFrame yes" -- with a nonzero shown count means it ran and the client re-showed -- them, which is the recycled-plate case. local pp = plate.GetParent and plate:GetParent() local pname = pp and pp.GetName and pp:GetName() or nil local shownRegions, shownKids = 0, 0 for _, r in ipairs({ plate:GetRegions() }) do if r.IsShown and r:IsShown() then shownRegions = shownRegions + 1 end end for _, c in ipairs({ plate:GetChildren() }) do -- Ours is parented to the plate too; it is supposed to be shown. if c ~= plate.UnitFrame and c.IsShown and c:IsShown() then shownKids = shownKids + 1 end end say2("blizz plate: parent=WorldFrame " .. (pp == WorldFrame and "yes" or ("no (" .. tostring(pname or pp) .. ")")) .. ", still-shown regions=" .. shownRegions .. " children=" .. shownKids .. ", marked=" .. tostring(plate.GetAttribute and plate:GetAttribute("disabled-blizz-plate"))) if shownRegions > 0 or shownKids > 0 then say2("|cffff4040the client's plate is still drawing under ours|r") end local uf = plate.UnitFrame if not uf then say2("|cffff4040UnitFrame is nil|r - the driver never attached one") else say2("UnitFrame", tostring(uf:GetWidth()) .. "x" .. tostring(uf:GetHeight()), "shown=" .. tostring(uf:IsShown()), "alpha=" .. tostring(uf:GetAlpha())) -- Anchor count, because a frame with none is the failure this -- whole command missed the first time: shown, correctly sized, -- correctly populated, and drawing nowhere because it has no -- position. Size and IsShown look identical either way. local pts = uf.GetNumPoints and uf:GetNumPoints() or -1 say2("anchors=" .. tostring(pts) .. (pts == 0 and " |cffff4040(none - it has no position, nothing will draw)|r" or "") .. " parent=" .. tostring(uf:GetParent() == plate and "the plate" or "NOT the plate")) say2("unit=" .. tostring(uf.unit), "displayed=" .. tostring(uf.displayedUnit), "optionTable=" .. tostring(uf.optionTable ~= nil)) local hb = uf.healthBar if not hb then say2("|cffff4040healthBar is nil|r - parentKey did not resolve") else local mn, mx = hb:GetMinMaxValues() say2("healthBar", tostring(hb:GetWidth()) .. "x" .. tostring(hb:GetHeight()), "shown=" .. tostring(hb:IsShown()), "value=" .. tostring(hb:GetValue()) .. "/" .. tostring(mx)) local tex = hb.GetStatusBarTexture and hb:GetStatusBarTexture() say2("bar texture=" .. tostring(tex and tex.GetTexture and tex:GetTexture())) end local nm = uf.name or (uf.Elements and uf.Elements.name) say2("name fontstring=" .. tostring(nm ~= nil) .. (nm and (" text='" .. tostring(nm:GetText()) .. "' shown=" .. tostring(nm:IsShown())) or "")) end end end end if n == 0 then DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r no active plates - " .. "target something with a plate up, then run this again.") else DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r " .. n .. " active plate(s).") end return end if cmd == "plate" then -- Dump the target's plate subtree, whatever is still drawing. -- -- The suppression pass walks the plate's own regions and its direct -- children. If something survives, it is because it is neither of those - -- a grandchild, or a frame the client anchors to the plate without -- parenting it - and no amount of reading the client's disassembly settles -- which, because the answer is a runtime object graph. So ask the graph. -- -- Reports only what would actually be visible: shown, with a non-zero -- alpha. Our own frame is labelled so it is not mistaken for a leftover. local plate = C_NamePlate and C_NamePlate.GetNamePlateForUnit and C_NamePlate.GetNamePlateForUnit("target") if not plate then DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r target something " .. "with a plate up first.") return end local uf = plate.UnitFrame local shown = 0 local function describe(obj, depth, index) local ok, kind = pcall(obj.GetObjectType, obj) kind = ok and kind or "?" -- IsVisible, not IsShown. IsShown answers for the object alone, so a -- texture inside a hidden parent still reports true - which is how the -- first run of this listed three textures without establishing that they -- were actually rendering. IsVisible walks the ancestor chain. local vis = obj.IsVisible and obj:IsVisible() local a = obj.GetAlpha and obj:GetAlpha() or 1 if not vis or (a or 0) <= 0 then return end -- A blanked texture is not a leftover. The suppression pass clears the -- file precisely because the client restores alpha and the shown bit -- behind us - a texture reporting alpha 1.00 with tex=nil is the fix -- working, not failing. Counting it as "still drawing" would send the -- next person after a bug that is not there. if obj.GetTexture and not obj:GetTexture() then return end shown = shown + 1 local name = obj.GetName and obj:GetName() or nil local extra = "" if kind == "Texture" and obj.GetTexture then extra = " tex=" .. tostring(obj:GetTexture()) elseif kind == "FontString" and obj.GetText then extra = " text='" .. tostring(obj:GetText()) .. "'" end DEFAULT_CHAT_FRAME:AddMessage(string.rep(" ", depth) .. "|cffffd100" .. kind .. "|r[" .. index .. "] " .. (name and (name .. " ") or "") .. "alpha=" .. string.format("%.2f", a) .. extra) end local function walk(frame, depth) if depth > 3 then return end for i, r in ipairs({ frame:GetRegions() }) do describe(r, depth, i) end for i, c in ipairs({ frame:GetChildren() }) do if c == uf then DEFAULT_CHAT_FRAME:AddMessage(string.rep(" ", depth) .. "|cff00ff00[" .. i .. "] our UnitFrame|r (skipped)") else describe(c, depth, i) walk(c, depth + 1) end end end DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r target plate - " .. "everything still visible:") walk(plate, 1) DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r " .. shown .. " client object(s) still drawing" .. (shown == 0 and " - the plate is clean, the glow is not parented to it" or "")) return end if cmd == "status" then DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r " .. "enemy bar " .. tostring(Addon:GetEnemyOption("health", "width")) .. "x" .. tostring(Addon:GetEnemyOption("health", "height")) .. ", classic style " .. tostring(Addon:GetGeneralOption("useClassicStyle"))) return end DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00ManiaxNamePlates|r " .. "|cffaaaaaa/mnp|r settings |cffaaaaaa/mnp status|r values " .. "|cffaaaaaa/mnp debug|r inspect a live plate " .. "|cffaaaaaa/mnp plate|r what is still drawing on the target's plate") end