diff --git a/settings.lua b/settings.lua index 7698f30..b2c7c61 100644 --- a/settings.lua +++ b/settings.lua @@ -19,6 +19,10 @@ Cursive:RegisterDefaults("profile", { gcdbarheight = 3, gcdbarwidth = 200, gcdbarcolor = { r = 1, g = 0.85, b = 0.1 }, + sacrificebarcolor = { r = 0.6, g = 0.2, b = 0.85 }, + sacrificebarwidth = 200, + sacrificebarheight = 14, + sacrificebarorientation = "HORIZONTAL", showghostcurse = true, showghostcorruption = true, showghostimmolate = true, @@ -787,6 +791,90 @@ Cursive.cmdtable = { Cursive.ui.PreviewGcdBar() end, }, + ["sacrificebarcolor"] = { + type = "color", + name = "Sacrifice Bar Color", + desc = "Color of the Sacrifice shield bar (drag the bar itself anywhere on screen to reposition it)", + order = 44, + get = function() + return Cursive.db.profile.sacrificebarcolor.r, Cursive.db.profile.sacrificebarcolor.g, Cursive.db.profile.sacrificebarcolor.b + end, + set = function(r, g, b) + Cursive.db.profile.sacrificebarcolor.r = r + Cursive.db.profile.sacrificebarcolor.g = g + Cursive.db.profile.sacrificebarcolor.b = b + if Cursive.ui.sacrificeBar then + Cursive.ui.sacrificeBar:SetStatusBarColor(r, g, b, 0.9) + end + Cursive.ui.PreviewSacrificeBar() + end, + }, + ["sacrificebarwidth"] = { + type = "range", + name = "Sacrifice Bar Width", + desc = "Width of the Sacrifice shield bar", + order = 45, + min = 80, + max = 400, + step = 5, + get = function() + return Cursive.db.profile.sacrificebarwidth + end, + set = function(v) + Cursive.db.profile.sacrificebarwidth = v + if Cursive.ui.sacrificeBar then + Cursive.ui.sacrificeBar:SetWidth(v) + end + Cursive.ui.PreviewSacrificeBar() + end, + }, + ["sacrificebarheight"] = { + type = "range", + name = "Sacrifice Bar Height", + desc = "Height of the Sacrifice shield bar", + order = 46, + min = 6, + max = 40, + step = 1, + get = function() + return Cursive.db.profile.sacrificebarheight + end, + set = function(v) + Cursive.db.profile.sacrificebarheight = v + if Cursive.ui.sacrificeBar then + Cursive.ui.sacrificeBar:SetHeight(v) + end + Cursive.ui.PreviewSacrificeBar() + end, + }, + ["sacrificebarvertical"] = { + type = "toggle", + name = "Sacrifice Bar Vertical", + desc = "Orient the Sacrifice bar vertically (fills top-to-bottom as it counts down) instead of horizontally. Swaps width/height so the bar's shape matches.", + order = 47, + get = function() + return Cursive.db.profile.sacrificebarorientation == "VERTICAL" + end, + set = function(v) + Cursive.db.profile.sacrificebarorientation = v and "VERTICAL" or "HORIZONTAL" + + -- Swap width/height so the bar's shape actually matches its + -- orientation -- otherwise it just changes the internal + -- fill direction while staying the same wide-short shape. + local w = Cursive.db.profile.sacrificebarwidth + local h = Cursive.db.profile.sacrificebarheight + Cursive.db.profile.sacrificebarwidth = h + Cursive.db.profile.sacrificebarheight = w + + if Cursive.ui.sacrificeBar then + Cursive.ui.sacrificeBar:SetOrientation(Cursive.db.profile.sacrificebarorientation) + Cursive.ui.sacrificeBar:SetWidth(Cursive.db.profile.sacrificebarwidth) + Cursive.ui.sacrificeBar:SetHeight(Cursive.db.profile.sacrificebarheight) + Cursive.ui.UpdateSacrificeBarTextLayout(Cursive.ui.sacrificeBar, v) + end + Cursive.ui.PreviewSacrificeBar() + end, + }, ["clickthrough"] = { type = "toggle", name = L["Allow clickthrough"], diff --git a/ui.lua b/ui.lua index b9bf782..0729b59 100644 --- a/ui.lua +++ b/ui.lua @@ -605,6 +605,29 @@ local GHOST_DOT_ICONS = { } local GHOST_CURSE_FALLBACK_ICON = "Interface\\Icons\\Spell_Shadow_CurseOfSargeras" -- Curse of Agony +-- UnitDebuff(guid, i) via the GUID extension has been confirmed (via live +-- /cgd diagnostic) to sometimes return nothing at all for a guid, even +-- when that guid IS the player's own current target. Unit tokens are +-- always reliable, so substitute one whenever the guid matches something +-- checkable -- covers being directly targeted, or being your target's +-- target (common on bosses when tanking/watching a tank). Other tracked +-- guids (raid members' own targets etc) still have no equivalent safe +-- substitute without a broader token scan. Note: "focus" was tried here +-- too but removed -- confirmed via live error that vanilla 1.12 has no +-- focus frame support at all (that's a later-expansion addition), and +-- UnitExists("focus") hard-errors here rather than just returning nil. +local function SafeUnitDebuff(guid, i) + local _, targetGuid = UnitExists("target") + if targetGuid and guid == targetGuid then + return UnitDebuff("target", i) + end + local _, totGuid = UnitExists("targettarget") + if totGuid and guid == totGuid then + return UnitDebuff("targettarget", i) + end + return UnitDebuff(guid, i) +end + local function GetGhostTextures(guid) local config = Cursive.db.profile local textures = {} @@ -636,7 +659,7 @@ local function GetGhostTextures(guid) local found = false local i = 1 while true do - local tex = UnitDebuff(guid, i) + local tex = SafeUnitDebuff(guid, i) if not tex then break end if string.find(tex, dot.texture, 1, true) then found = true @@ -743,7 +766,7 @@ local function DisplayGuid(guid) local hasShadowVuln = false local i = 1 while true do - local tex = UnitDebuff(guid, i) + local tex = SafeUnitDebuff(guid, i) if not tex then break end if string.find(tex, "Spell_Shadow_ShadowBolt", 1, true) then hasShadowVuln = true @@ -1182,6 +1205,399 @@ gcdTicker:SetScript("OnUpdate", function() end end) +------------------------------------------------------------------------------- +-- Sacrifice shield tracking (Voidwalker's damage-absorb shield on the +-- warlock). Detection and initial-absorb-value reading use a tooltip scan +-- of the player's own buff (robust across all ranks, since it reads the +-- actual computed value including any Demonic Brutality talent bonus, +-- rather than guessing based on rank). Duration is a fixed 30s regardless +-- of rank (confirmed). +-- +-- IMPORTANT CAVEAT: remaining-absorb tracking (damage taken while the +-- shield is up) is UNVERIFIED. Vanilla's combat log is chat-message-based +-- and its exact wording for absorbed hits hasn't been confirmed on this +-- server. Diagnostic mode (/cursivesacdebug) prints every combat message +-- received while the shield is active, so the real format can be seen and +-- the parsing refined -- until then, the absorb estimate may be inaccurate +-- or simply not update at all if the message format doesn't match what's +-- guessed below. +------------------------------------------------------------------------------- + +local SACRIFICE_DURATION = 30 +ui.sacrificeDebugMode = false +ui.cachedSacrificeAbsorb = nil + +local sacrificeState = { + active = false, + initialAbsorb = 0, + appliedAt = 0, + damageTaken = 0, +} + +-- The APPLIED buff's own tooltip is confirmed abbreviated ("Absorbs all +-- damage", no number) -- the real absorb value only appears in the full +-- spell description, shown on the Voidwalker's pet action bar. Since the +-- Voidwalker is consumed by the cast, this has to be scanned proactively +-- while it's still alive and cached, rather than read after the fact. +local function ScanPetBarForSacrifice() + local slot = 1 + while slot <= 10 do + local button = getglobal("pfActionBarPetButton"..slot) or getglobal("PetActionButton"..slot) + if button and button:IsVisible() then + Cursive.core.tooltipScan:SetOwner(UIParent, "ANCHOR_NONE") + Cursive.core.tooltipScan:SetPetAction(slot) + local nameLine = getglobal("CursiveTooltipScanTextLeft1") + local nameText = nameLine and nameLine:GetText() + if nameText and string.find(nameText, "Sacrifice", 1, true) then + local lineNum = 2 + while lineNum <= 6 do + local descLine = getglobal("CursiveTooltipScanTextLeft"..lineNum) + local desc = descLine and descLine:GetText() + if desc then + local _, _, num = string.find(desc, "absorb (%d+)") + if num then + if ui.sacrificeDebugMode and ui.cachedSacrificeAbsorb ~= tonumber(num) then + DEFAULT_CHAT_FRAME:AddMessage("|cff60ffff[SacDebug]|r Cached Sacrifice absorb value from pet bar: "..num) + end + ui.cachedSacrificeAbsorb = tonumber(num) + Cursive.core.tooltipScan:Hide() + return + end + end + lineNum = lineNum + 1 + end + end + Cursive.core.tooltipScan:Hide() + end + slot = slot + 1 + end +end + +-- One-shot diagnostic: dumps the full state of PetActionButton1-10 -- +-- whether each exists, is visible, and what its tooltip shows (regardless +-- of whether it matches "Sacrifice"). Run this with your Voidwalker +-- summoned if the pet-bar scan above isn't finding anything -- likely +-- explanation is a UI replacement addon (e.g. pfUI) using different pet +-- bar frame names than the vanilla default. +-- Guessing frame naming conventions has failed twice -- this reports +-- exactly which frame is under the mouse cursor, no guessing involved. +-- Run the command, THEN hover over the Sacrifice icon on the pet bar +-- within 3 seconds (gives time to move the mouse after typing/hitting +-- enter, since GetMouseFocus needs the cursor there at the exact moment +-- it's checked). +local whatFrameChecker = CreateFrame("Frame") +SLASH_CURSIVEWHATFRAME1 = "/cursivewhatframe" +SlashCmdList["CURSIVEWHATFRAME"] = function() + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00Cursive:|r hover over the Sacrifice pet icon now -- checking in 3 seconds...") + local elapsed = 0 + whatFrameChecker:SetScript("OnUpdate", function() + elapsed = elapsed + arg1 + if elapsed < 3 then return end + whatFrameChecker:SetScript("OnUpdate", nil) + + local frame = GetMouseFocus() + if not frame then + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00Cursive:|r no frame under mouse") + return + end + local name = frame.GetName and frame:GetName() + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00Cursive:|r frame under mouse: "..tostring(name)) + local parent = frame.GetParent and frame:GetParent() + if parent then + local parentName = parent.GetName and parent:GetName() + DEFAULT_CHAT_FRAME:AddMessage(" parent: "..tostring(parentName)) + end + end) +end + +SLASH_CURSIVEPETDEBUG1 = "/cursivepetdebug" +SlashCmdList["CURSIVEPETDEBUG"] = function() + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00Cursive pet bar debug|r") + local prefixes = { "pfActionBarPetButton", "PetActionButton", "ActionBarPetButton", "ActionBarPet" } + local anyFound = false + local pIdx = 1 + while pIdx <= 4 do + local prefix = prefixes[pIdx] + local slot = 1 + while slot <= 10 do + local buttonName = prefix..slot + local button = getglobal(buttonName) + if button then + anyFound = true + local visible = button:IsVisible() + DEFAULT_CHAT_FRAME:AddMessage(" "..buttonName..": exists, visible="..tostring(visible)) + if visible then + -- Try SetPetAction directly on this slot number (works + -- regardless of the button's frame naming convention, + -- since SetPetAction addresses pet action slots by + -- index, not frame name). + Cursive.core.tooltipScan:SetOwner(UIParent, "ANCHOR_NONE") + local ok = pcall(function() Cursive.core.tooltipScan:SetPetAction(slot) end) + local nameLine = getglobal("CursiveTooltipScanTextLeft1") + local nameText = nameLine and nameLine:GetText() + DEFAULT_CHAT_FRAME:AddMessage(" SetPetAction("..slot..") tooltip name: "..tostring(nameText).." (call ok: "..tostring(ok)..")") + Cursive.core.tooltipScan:Hide() + end + end + slot = slot + 1 + end + pIdx = pIdx + 1 + end + if not anyFound then + DEFAULT_CHAT_FRAME:AddMessage(" None of PetActionButton/ActionBarPetButton/ActionBarPet 1-10 exist -- naming convention is something else entirely.") + end +end + +local function ScanForSacrificeBuff() + local found = false + local i = 1 + while true do + local tex = UnitBuff("player", i) + if not tex then break end + Cursive.core.tooltipScan:SetOwner(UIParent, "ANCHOR_NONE") + Cursive.core.tooltipScan:SetUnitBuff("player", i) + local nameLine = getglobal("CursiveTooltipScanTextLeft1") + local name = nameLine and nameLine:GetText() + if name and string.find(name, "Sacrifice", 1, true) then + found = true + if not sacrificeState.active then + sacrificeState.active = true + sacrificeState.appliedAt = GetTime() + sacrificeState.damageTaken = 0 + sacrificeState.initialAbsorb = ui.cachedSacrificeAbsorb or 0 + + if ui.sacrificeDebugMode then + DEFAULT_CHAT_FRAME:AddMessage("|cff60ffff[SacDebug]|r Shield applied, using cached absorb value: "..tostring(ui.cachedSacrificeAbsorb)) + end + end + break + end + i = i + 1 + end + Cursive.core.tooltipScan:Hide() + if not found and sacrificeState.active then + if ui.sacrificeDebugMode then + DEFAULT_CHAT_FRAME:AddMessage("|cff60ffff[SacDebug]|r Shield no longer detected (expired or broken)") + end + sacrificeState.active = false + end + return found +end + +-- Diagnostic: logs every combat message received while the shield is +-- active, to determine the real absorb-message wording on this server. +-- Also attempts a best-effort parse (any number immediately preceding or +-- following "absorb") to estimate damage taken, but this is unverified. +local sacDebugFrame = CreateFrame("Frame") +-- Confirmed (via community-documented vanilla 1.12.1 event listings) as +-- the correct events for damage TAKEN by the player -- the original guess +-- above was wrong: CHAT_MSG_SPELL_SELF_DAMAGE and CHAT_MSG_COMBAT_SELF_HITS +-- report damage the player DEALS to others, not damage taken, which is why +-- zero debug messages ever fired despite the player clearly taking damage. +sacDebugFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS") -- melee from a creature to the player +sacDebugFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE") -- spell from a creature to the player +sacDebugFrame:RegisterEvent("CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS") -- melee from a hostile player (PvP) +sacDebugFrame:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE") -- spell from a hostile player (PvP) +sacDebugFrame:SetScript("OnEvent", function() + if not sacrificeState.active then return end + local msg = arg1 + if not msg then return end + + if ui.sacrificeDebugMode then + DEFAULT_CHAT_FRAME:AddMessage("|cff60ffff[SacDebug]|r ["..event.."] "..msg) + end + + if string.find(string.lower(msg), "absorb", 1, true) then + local _, _, num = string.find(msg, "%((%d+) absorbed%)") + if num then + sacrificeState.damageTaken = sacrificeState.damageTaken + tonumber(num) + if ui.sacrificeDebugMode then + DEFAULT_CHAT_FRAME:AddMessage("|cff60ffff[SacDebug]|r Parsed absorbed amount: "..num..", running total: "..sacrificeState.damageTaken) + end + end + end +end) + +SLASH_CURSIVESACDEBUG1 = "/cursivesacdebug" +SlashCmdList["CURSIVESACDEBUG"] = function() + ui.sacrificeDebugMode = not ui.sacrificeDebugMode + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00Cursive|r sacrifice debug mode: "..(ui.sacrificeDebugMode and "|cff00ff00ON|r" or "|cffff6060OFF|r")) +end + +-- Standalone, draggable Sacrifice shield bar. Depletes visually with time +-- remaining (same mechanic as the GCD bar); the damage-remaining estimate +-- is shown as text overlaid on the bar rather than driving the bar's fill, +-- since only the time value is fully reliable. +-- Repositions the label/absorb text based on orientation. Horizontal keeps +-- them side-by-side inside the bar; vertical stacks them below the bar +-- instead, since a narrow vertical bar can't fit readable text inside it. +local function UpdateSacrificeBarTextLayout(bar, isVertical) + bar.label:ClearAllPoints() + bar.absorbText:ClearAllPoints() + if isVertical then + bar.label:SetPoint("Top", bar, "Bottom", 0, -4) + bar.label:SetJustifyH("Center") + bar.absorbText:SetPoint("Top", bar.label, "Bottom", 0, -2) + bar.absorbText:SetJustifyH("Center") + else + bar.label:SetPoint("Left", bar, "Left", 4, 0) + bar.label:SetJustifyH("Left") + bar.absorbText:SetPoint("Right", bar, "Right", -4, 0) + bar.absorbText:SetJustifyH("Right") + end +end + +local function CreateSacrificeBar() + local p = Cursive.db.profile + + local bar = CreateFrame("StatusBar", "CursiveSacrificeBar", UIParent) + bar:SetWidth(p.sacrificebarwidth or 200) + bar:SetHeight(p.sacrificebarheight or 14) + + if p.sacrificebaranchor then + bar:SetPoint(p.sacrificebaranchor, UIParent, p.sacrificebaranchor, p.sacrificebarx or 0, p.sacrificebary or 0) + else + bar:SetPoint("Center", UIParent, "Center", 0, -100) + end + + bar:SetStatusBarTexture(Cursive.db.profile.bartexture) + bar:SetOrientation(p.sacrificebarorientation or "HORIZONTAL") + local c = p.sacrificebarcolor or { r = 0.6, g = 0.2, b = 0.85 } + bar:SetStatusBarColor(c.r, c.g, c.b, 0.9) + bar:SetMinMaxValues(0, 1) + bar:SetValue(0) + + -- Border color reflects % of absorb remaining (green -> red), as an + -- at-a-glance cue independent of the text number. + bar:SetBackdrop({ + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + edgeSize = 12, + }) + bar:SetBackdropBorderColor(0, 1, 0, 1) + + local bg = bar:CreateTexture(nil, "BACKGROUND") + bg:SetAllPoints(bar) + bg:SetTexture(0, 0, 0) + bg:SetAlpha(0.4) + + local label = bar:CreateFontString(nil, "OVERLAY") + label:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE") + label:SetTextColor(1, 1, 1, 1) + label:SetText("Sacrifice") + + local absorbText = bar:CreateFontString(nil, "OVERLAY") + absorbText:SetFont(STANDARD_TEXT_FONT, 10, "OUTLINE") + absorbText:SetTextColor(1, 1, 1, 1) + + bar.label = label + bar.absorbText = absorbText + UpdateSacrificeBarTextLayout(bar, (p.sacrificebarorientation == "VERTICAL")) + + bar:EnableMouse(true) + bar:SetMovable(true) + bar:RegisterForDrag("LeftButton") + bar:SetScript("OnDragStart", function() + this:StartMoving() + end) + bar:SetScript("OnDragStop", function() + this:StopMovingOrSizing() + local newAnchor = utils.GetBestAnchor(this) + local anchor, x, y = utils.ConvertFrameAnchor(this, newAnchor) + this:ClearAllPoints() + this:SetPoint(anchor, UIParent, anchor, x, y) + + Cursive.db.profile.sacrificebaranchor = anchor + Cursive.db.profile.sacrificebarx = x + Cursive.db.profile.sacrificebary = y + end) + + bar:Hide() + ui.sacrificeBar = bar + return bar +end + +ui.UpdateSacrificeBarTextLayout = UpdateSacrificeBarTextLayout + +local sacrificeTicker = CreateFrame("Frame", "CursiveSacrificeTicker", UIParent) +local sacrificeTickAccum = 0 +ui.sacrificeBarPreviewUntil = nil + +-- Call this from any Sacrifice bar setting's set() callback to show a +-- temporary static preview fill, since the real bar normally only +-- appears while the shield is actually active -- lets color/size changes +-- be seen immediately. +function ui.PreviewSacrificeBar() + ui.sacrificeBarPreviewUntil = GetTime() + 4 +end + +sacrificeTicker:SetScript("OnUpdate", function() + sacrificeTickAccum = sacrificeTickAccum + arg1 + if sacrificeTickAccum < 0.5 then return end + sacrificeTickAccum = 0 + + ScanPetBarForSacrifice() + ScanForSacrificeBuff() + + if not ui.sacrificeBar then + CreateSacrificeBar() + end + local bar = ui.sacrificeBar + + -- Preview mode: triggered directly by changing a Sacrifice bar + -- setting, rather than trying to detect whether the options menu is + -- open. + if ui.sacrificeBarPreviewUntil and GetTime() < ui.sacrificeBarPreviewUntil then + bar:SetMinMaxValues(0, 30) + bar:SetValue(18) + bar.absorbText:SetText("18s | 1200 absorb") + if not bar:IsShown() then bar:Show() end + return + end + + if not sacrificeState.active then + if bar:IsShown() then bar:Hide() end + return + end + + local remaining = SACRIFICE_DURATION - (GetTime() - sacrificeState.appliedAt) + if remaining <= 0 then + sacrificeState.active = false + bar:Hide() + return + end + + if not bar:IsShown() then bar:Show() end + bar:SetMinMaxValues(0, SACRIFICE_DURATION) + bar:SetValue(remaining) + + local remainingAbsorb = sacrificeState.initialAbsorb - sacrificeState.damageTaken + if remainingAbsorb < 0 then remainingAbsorb = 0 end + bar.absorbText:SetText(math.floor(remaining + 1).."s | "..remainingAbsorb.." absorb") + + if sacrificeState.initialAbsorb > 0 then + local pct = remainingAbsorb / sacrificeState.initialAbsorb + if pct > 1 then pct = 1 end + bar.borderR, bar.borderG, bar.borderB = 1 - pct, pct, 0 + else + bar.borderR, bar.borderG, bar.borderB = 0.5, 0.5, 0.5 + end +end) + +-- Glowing border: pulses the border alpha smoothly via sin() oscillation. +-- Runs every frame (unthrottled), separate from the main 0.5s update +-- above, since a visible pulse needs much more frequent updates than the +-- bar's actual data does. +local sacrificeGlowTicker = CreateFrame("Frame", "CursiveSacrificeGlowTicker", UIParent) +sacrificeGlowTicker:SetScript("OnUpdate", function() + local bar = ui.sacrificeBar + if not bar or not bar:IsShown() then return end + local r = bar.borderR or 0.5 + local g = bar.borderG or 0.5 + local b = bar.borderB or 0.5 + local alpha = 0.5 + 0.5 * math.sin(GetTime() * 4) + bar:SetBackdropBorderColor(r, g, b, alpha) +end) + -- Flashes any icon currently registered, via a smooth alpha oscillation. -- Single shared ticker for all rows rather than one per icon. Registry -- values are a speed multiplier (falls back to 3 if just `true`), so @@ -1437,4 +1853,50 @@ function ui.CreateSettingsWindow() return f end +------------------------------------------------------------------------------- +-- Diagnostic: /cgd prints exactly what UnitDebuff sees for the current +-- target, and whether each GHOST_DOT_ICONS entry matches -- run this while +-- a ghost icon is stuck showing despite the DoT clearly being active, to +-- get real data instead of guessing further. +------------------------------------------------------------------------------- + +SLASH_CURSIVEGHOSTDEBUG1 = "/cgd" +SlashCmdList["CURSIVEGHOSTDEBUG"] = function() + local _, guid = UnitExists("target") + if not guid then + DEFAULT_CHAT_FRAME:AddMessage("|cffff6060Cursive ghost debug:|r no target selected") + return + end + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00Cursive ghost debug|r -- guid: "..guid) + DEFAULT_CHAT_FRAME:AddMessage("(this is your current target, so the fix resolves it via the \"target\" token)") + DEFAULT_CHAT_FRAME:AddMessage("Raw UnitDebuff scan:") + local i = 1 + local foundAny = false + while true do + local tex = SafeUnitDebuff(guid, i) + if not tex then break end + foundAny = true + DEFAULT_CHAT_FRAME:AddMessage(" slot "..i..": "..tex) + i = i + 1 + end + if not foundAny then + DEFAULT_CHAT_FRAME:AddMessage(" (UnitDebuff returned nothing at all for this guid)") + end + DEFAULT_CHAT_FRAME:AddMessage("Checking against GHOST_DOT_ICONS:") + for _, dot in ipairs(GHOST_DOT_ICONS) do + local found = false + local j = 1 + while true do + local tex = SafeUnitDebuff(guid, j) + if not tex then break end + if string.find(tex, dot.texture, 1, true) then + found = true + break + end + j = j + 1 + end + DEFAULT_CHAT_FRAME:AddMessage(" "..dot.name..": "..(found and "|cff00ff00FOUND|r" or "|cffff6060NOT FOUND|r").." (looking for '"..dot.texture.."')") + end +end + Cursive.ui = ui