diff --git a/api/config.lua b/api/config.lua index 420941ee..23f8832a 100644 --- a/api/config.lua +++ b/api/config.lua @@ -241,6 +241,23 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("unitframes", nil, "swingtimerrangedcolor",".3,.6,1,1") pfUI:UpdateConfig("unitframes", nil, "swingtimerrangedwarncolor",".9,0,0,1") pfUI:UpdateConfig("unitframes", nil, "swingtimerhsqueue","1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkerwidth", "80") + pfUI:UpdateConfig("unitframes", nil, "raidmarkerheight", "14") + pfUI:UpdateConfig("unitframes", nil, "raidmarkergrow", "down") + pfUI:UpdateConfig("unitframes", nil, "raidmarkertexture", "Interface\\AddOns\\pfUI\\img\\bar") + pfUI:UpdateConfig("unitframes", nil, "raidmarkerfontsize","12") + pfUI:UpdateConfig("unitframes", nil, "raidmarkershowname","1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkershowpct","1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkershownumhp","0") + pfUI:UpdateConfig("unitframes", nil, "raidmarkershowportrait","0") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_star", "1,.9,0,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_circle", "1,.5,0,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_diamond", ".8,0,.8,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_triangle", "0,.8,0,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_moon", ".7,.7,.7,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_square", "0,.4,.9,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_cross", ".9,0,0,1") + pfUI:UpdateConfig("unitframes", nil, "raidmarkercolor_skull", "1,1,1,1") pfUI:UpdateConfig("unitframes", nil, "abbrevnum", "1") pfUI:UpdateConfig("unitframes", nil, "castbardecimals", "2") pfUI:UpdateConfig("unitframes", nil, "abbrevname", "1") diff --git a/init/modules.xml b/init/modules.xml index b516d941..92a385b4 100644 --- a/init/modules.xml +++ b/init/modules.xml @@ -18,6 +18,7 @@ + diff --git a/libs/libdebuff.lua b/libs/libdebuff.lua index fd0ffe03..6f4ef10b 100644 --- a/libs/libdebuff.lua +++ b/libs/libdebuff.lua @@ -44,95 +44,68 @@ if GetNampowerVersion then end end --- Delayed Nampower version check (5 seconds after PLAYER_ENTERING_WORLD) +-- Nampower startup check: show version info and ensure CVars are set. +-- Runs on first OnUpdate after PLAYER_ENTERING_WORLD to give Nampower time to initialize. local nampowerCheckFrame = CreateFrame("Frame") -local nampowerCheckTimer = 0 -local nampowerCheckDone = false nampowerCheckFrame:RegisterEvent("PLAYER_ENTERING_WORLD") -nampowerCheckFrame:RegisterEvent("PLAYER_LOGOUT") nampowerCheckFrame:SetScript("OnEvent", function() - -- Handle shutdown to prevent crash 132 - if event == "PLAYER_LOGOUT" then + -- Defer to next frame so Nampower is fully initialized + this:SetScript("OnUpdate", function() + this:SetScript("OnUpdate", nil) this:UnregisterAllEvents() this:SetScript("OnEvent", nil) - this:SetScript("OnUpdate", nil) - return - end - - nampowerCheckFrame:SetScript("OnUpdate", function() - nampowerCheckTimer = nampowerCheckTimer + arg1 - if nampowerCheckTimer >= 5 and not nampowerCheckDone then - nampowerCheckDone = true - - if GetNampowerVersion then - local major, minor, patch = GetNampowerVersion() - patch = patch or 0 - local versionString = major .. "." .. minor .. "." .. patch - - if major > 2 or (major == 2 and minor > 38) or (major == 2 and minor == 38 and patch >= 0) then - DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Nampower v" .. versionString .. " detected - GetUnitField mode enabled!") - - -- Enable required Nampower CVars - if SetCVar and GetCVar then - local cvarsToEnable = { - "NP_EnableSpellStartEvents", - "NP_EnableSpellGoEvents", - "NP_EnableAuraCastEvents", - "NP_EnableAutoAttackEvents" - } - - local totalCvars = table.getn(cvarsToEnable) - local enabledCount = 0 - local alreadyEnabledCount = 0 - local failedCount = 0 - - for _, cvar in ipairs(cvarsToEnable) do - local success, currentValue = pcall(GetCVar, cvar) - if success and currentValue then - if currentValue == "1" then - alreadyEnabledCount = alreadyEnabledCount + 1 - else - local setSuccess = pcall(SetCVar, cvar, "1") - if setSuccess then - enabledCount = enabledCount + 1 - else - failedCount = failedCount + 1 - end - end + + if GetNampowerVersion then + local major, minor, patch = GetNampowerVersion() + patch = patch or 0 + local versionString = major .. "." .. minor .. "." .. patch + + if major > 2 or (major == 2 and minor > 38) or (major == 2 and minor == 38 and patch >= 0) then + DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Nampower v" .. versionString .. " detected - GetUnitField mode enabled!") + + if SetCVar and GetCVar then + local cvarsToEnable = { + "NP_EnableSpellStartEvents", + "NP_EnableSpellGoEvents", + "NP_EnableAuraCastEvents", + "NP_EnableAutoAttackEvents", + } + local enabledCount = 0 + local alreadyEnabledCount = 0 + local failedCount = 0 + + for _, cvar in ipairs(cvarsToEnable) do + local success, currentValue = pcall(GetCVar, cvar) + if success and currentValue then + if currentValue == "1" then + alreadyEnabledCount = alreadyEnabledCount + 1 else - failedCount = failedCount + 1 + local setSuccess = pcall(SetCVar, cvar, "1") + if setSuccess then enabledCount = enabledCount + 1 + else failedCount = failedCount + 1 end end - end - - if enabledCount > 0 then - DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Enabled " .. enabledCount .. " Nampower CVars") - end - - if alreadyEnabledCount == totalCvars then - DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r All required Nampower CVars already enabled") - elseif alreadyEnabledCount > 0 then - DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r " .. alreadyEnabledCount .. " CVars were already enabled") - end - - if failedCount > 0 then - DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libdebuff]|r Warning: Could not check/set " .. failedCount .. " CVars") + else + failedCount = failedCount + 1 end end - - elseif major == 2 and minor == 38 and patch == 0 then - DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libdebuff] WARNING: Nampower v2.38.0 detected!|r") - DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libdebuff] Please update to v2.38.0 or higher!|r") - StaticPopup_Show("LIBDEBUFF_NAMPOWER_UPDATE", versionString) - else - DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[libdebuff] Debuff tracking disabled! Please update Nampower to v2.38.0 or higher.|r") - StaticPopup_Show("LIBDEBUFF_NAMPOWER_UPDATE", versionString) + + if enabledCount > 0 then + DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r Enabled " .. enabledCount .. " Nampower CVars") + elseif alreadyEnabledCount == table.getn(cvarsToEnable) then + DEFAULT_CHAT_FRAME:AddMessage("|cff33ff99[libdebuff]|r All required Nampower CVars already enabled") + end + if failedCount > 0 then + DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00[libdebuff]|r Warning: Could not check/set " .. failedCount .. " CVars") + end end + else - DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[libdebuff] Nampower not found! Debuff tracking disabled.|r") - StaticPopup_Show("LIBDEBUFF_NAMPOWER_MISSING") + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[libdebuff] Debuff tracking disabled! Please update Nampower to v2.38.0 or higher.|r") + StaticPopup_Show("LIBDEBUFF_NAMPOWER_UPDATE", versionString) end - - nampowerCheckFrame:SetScript("OnUpdate", nil) + else + DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[libdebuff] Nampower not found! Debuff tracking disabled.|r") + StaticPopup_Show("LIBDEBUFF_NAMPOWER_MISSING") end end) end) @@ -1588,10 +1561,14 @@ if hasNampower then elseif event == "DEBUFF_ADDED_OTHER" then local guid = arg1 - local displaySlot = arg2 -- This is DISPLAY slot (1-16), NOT aura slot! + local displaySlot = arg2 -- Display slot (1-16), compacted local spellId = arg3 local stacks = arg4 - + local auraSlot_0based = arg6 -- Nampower 2.29+: raw slot 0-based (32-47) + + -- Convert 0-based (Nampower event) to 1-based (Lua GetUnitField array) + local auraSlot = auraSlot_0based and (auraSlot_0based + 1) or nil + -- Invalidate slot map cache for this GUID slotMapCache[guid] = nil @@ -1608,11 +1585,13 @@ if hasNampower then return end - -- Find the REAL aura slot (33-48) via GetUnitField - local auraSlot = nil - local slotMap = GetDebuffSlotMap(guid) - if slotMap and slotMap[displaySlot] then - auraSlot = slotMap[displaySlot].auraSlot + -- Get auraSlot from event parameter (Nampower 2.29+) + -- Fallback to GetUnitField lookup if not available + if not auraSlot then + local slotMap = GetDebuffSlotMap(guid) + if slotMap and slotMap[displaySlot] then + auraSlot = slotMap[displaySlot].auraSlot + end end -- Fallback: Calculate aura slot if GetUnitField didn't work @@ -1714,9 +1693,13 @@ if hasNampower then elseif event == "DEBUFF_REMOVED_OTHER" then local guid = arg1 - local displaySlot = arg2 -- This is DISPLAY slot (1-16), NOT aura slot! + local displaySlot = arg2 -- Display slot (1-16), compacted local spellId = arg3 - + local auraSlot_0based = arg6 -- Nampower 2.29+: raw slot 0-based (32-47) + + -- Convert 0-based (Nampower event) to 1-based (Lua GetUnitField array) + local auraSlot = auraSlot_0based and (auraSlot_0based + 1) or nil + -- Invalidate slot map cache for this GUID slotMapCache[guid] = nil @@ -1725,8 +1708,8 @@ if hasNampower then if debugStats.enabled then debugStats.debuff_removed = debugStats.debuff_removed + 1 if IsCurrentTarget(guid) then - DEFAULT_CHAT_FRAME:AddMessage(string.format("%s |cffff9900[DEBUFF_REMOVED]|r display=%d %s", - GetDebugTimestamp(), displaySlot, spellName)) + DEFAULT_CHAT_FRAME:AddMessage(string.format("%s |cffff9900[DEBUFF_REMOVED]|r display=%d aura=%d (0based=%d) %s", + GetDebugTimestamp(), displaySlot, auraSlot or -1, auraSlot_0based or -1, spellName)) end end @@ -1736,15 +1719,17 @@ if hasNampower then return end - -- Find the auraSlot using displaySlot mapping + -- Get auraSlot from event parameter (Nampower 2.29+) + -- Fallback to displayToAura mapping if not available + local foundAuraSlot = auraSlot + if not foundAuraSlot and displayToAura[guid] and displayToAura[guid][displaySlot] then + foundAuraSlot = displayToAura[guid][displaySlot] + end + local wasOurs = false local removedCasterGuid = nil - local foundAuraSlot = nil - - -- Use displayToAura mapping to find the correct auraSlot - if displayToAura[guid] and displayToAura[guid][displaySlot] then - foundAuraSlot = displayToAura[guid][displaySlot] - + + if foundAuraSlot then -- Get ownership info for this specific slot if slotOwnership[guid] and slotOwnership[guid][foundAuraSlot] then local ownership = slotOwnership[guid][foundAuraSlot] @@ -1761,7 +1746,7 @@ if hasNampower then end if debugStats.enabled and IsCurrentTarget(guid) then - DEFAULT_CHAT_FRAME:AddMessage(string.format("%s |cffff9900[SLOT CLEARED]|r aura=%d %s wasOurs=%s caster=%s", + DEFAULT_CHAT_FRAME:AddMessage(string.format("%s |cffff9900[SLOT CLEARED]|r aura=%d [arg6] %s wasOurs=%s caster=%s", GetDebugTimestamp(), foundAuraSlot, spellName, tostring(wasOurs), DebugGuid(removedCasterGuid))) end end @@ -1796,11 +1781,13 @@ if hasNampower then end elseif event == "PLAYER_TARGET_CHANGED" then - -- Nothing special needed - GetUnitField will get fresh data on next query if not UnitExists then return end local _, targetGuid = UnitExists("target") if targetGuid and targetGuid ~= "" then + -- Invalidate slot map cache on retarget + -- Prevents stale slot mappings after untarget/retarget cycles + slotMapCache[targetGuid] = nil -- Cleanup expired timers for new target CleanupExpiredTimers(targetGuid) end diff --git a/modules/focus.lua b/modules/focus.lua index 979dbedc..55e42012 100644 --- a/modules/focus.lua +++ b/modules/focus.lua @@ -242,4 +242,4 @@ function SlashCmdList.PFSWAPFOCUS(msg) pfUI.uf.focus.unitname = oldunit end end -end \ No newline at end of file +end diff --git a/modules/gui.lua b/modules/gui.lua index d03af9f5..90ac9f73 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1032,6 +1032,10 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () "default:" .. T["Default"], "tukui:TukUI" }, + ["raidmarker_grow"] = { + "down:Down", + "up:Up", + }, ["uf_color"] = { "0:" .. T["Class"], "1:" .. T["Custom"], @@ -1969,6 +1973,24 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(nil, T["Ranged Warn Color (Hunter)"], C.unitframes, "swingtimerrangedwarncolor", "color") CreateConfig(nil, T["Show HS/Cleave Queue Color (Warrior)"], C.unitframes, "swingtimerhsqueue", "checkbox") + CreateConfig(nil, "Raid Markers", nil, nil, "header") + CreateConfig(nil, "Raid Marker Width", C.unitframes, "raidmarkerwidth") + CreateConfig(nil, "Raid Marker Height", C.unitframes, "raidmarkerheight") + CreateConfig(nil, "Raid Marker Grow", C.unitframes, "raidmarkergrow", "dropdown", pfUI.gui.dropdowns.raidmarker_grow) + CreateConfig(nil, "Raid Marker Texture", C.unitframes, "raidmarkertexture", "dropdown", pfUI.gui.dropdowns.uf_bartexture) + CreateConfig(nil, "Raid Marker Font Size", C.unitframes, "raidmarkerfontsize") + CreateConfig(nil, "Show Name", C.unitframes, "raidmarkershowname", "checkbox") + CreateConfig(nil, "Show Percent HP", C.unitframes, "raidmarkershowpct", "checkbox") + CreateConfig(nil, "Show Portrait", C.unitframes, "raidmarkershowportrait", "checkbox") + CreateConfig(nil, "Star Color", C.unitframes, "raidmarkercolor_star", "color") + CreateConfig(nil, "Circle Color", C.unitframes, "raidmarkercolor_circle", "color") + CreateConfig(nil, "Diamond Color", C.unitframes, "raidmarkercolor_diamond", "color") + CreateConfig(nil, "Triangle Color", C.unitframes, "raidmarkercolor_triangle", "color") + CreateConfig(nil, "Moon Color", C.unitframes, "raidmarkercolor_moon", "color") + CreateConfig(nil, "Square Color", C.unitframes, "raidmarkercolor_square", "color") + CreateConfig(nil, "Cross Color", C.unitframes, "raidmarkercolor_cross", "color") + CreateConfig(nil, "Skull Color", C.unitframes, "raidmarkercolor_skull", "color") + CreateConfig(U[c], T["Font Options"], nil, nil, "header") CreateConfig(nil, T["Unit Frame Text Font"], C.global, "font_unit", "dropdown", pfUI.gui.dropdowns.fonts) CreateConfig(nil, T["Unit Frame Text Size"], C.global, "font_unit_size") diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 278638de..a598afab 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -2,8 +2,21 @@ pfUI:RegisterModule("nameplates", "vanilla", function () -- disable original castbars pcall(SetCVar, "ShowVKeyCastbar", 0) - -- check for SuperWoW support (use SUPERWOW_VERSION global) - local superwow_active = SUPERWOW_VERSION ~= nil + -- Check for Nampower support (preferred) + local hasNampower = false + if GetNampowerVersion then + local major, minor, patch = GetNampowerVersion() + patch = patch or 0 + if major > 2 or (major == 2 and minor > 27) or (major == 2 and minor == 27 and patch >= 2) then + hasNampower = true + end + end + + -- Check for SuperWoW support (fallback) + local hasSuperwow = SUPERWOW_VERSION ~= nil + + -- Use either Nampower or SuperWoW for GetName(1) GUID support + local superwow_active = hasNampower or hasSuperwow -- Local function references for performance local GetTime = GetTime diff --git a/modules/raidmarkers.lua b/modules/raidmarkers.lua new file mode 100644 index 00000000..e1ba31c8 --- /dev/null +++ b/modules/raidmarkers.lua @@ -0,0 +1,305 @@ +pfUI:RegisterModule("raidmarkers", "vanilla:tbc", function () + -- Requires mark1-mark8 unit tokens (Turtle WoW / Nampower) + if not UnitExists("mark1") and not UnitExists("mark8") then + if not pcall(function() UnitExists("mark1") end) then return end + end + + local rawborder, border = GetBorderSize() + + -- Parse color strings "r,g,b,a" into components + local function ParseColor(str, dr, dg, db, da) + if not str or str == "" then return dr, dg, db, da end + local _, _, r, g, b, a = string.find(str, "([%d%.]+),([%d%.]+),([%d%.]+),([%d%.]+)") + if r then + return tonumber(r) or dr, tonumber(g) or dg, tonumber(b) or db, tonumber(a) or da + end + return dr, dg, db, da + end + + local markerOrder = { 8, 7, 6, 5, 4, 3, 2, 1 } -- skull, cross, square, moon, triangle, diamond, circle, star + local markerTokens = {} + for i = 1, 8 do markerTokens[i] = "mark" .. i end + + -- Default colors per marker + local defaultColors = { + [1] = { 1.0, 0.9, 0.0, 1 }, -- star: yellow + [2] = { 1.0, 0.5, 0.0, 1 }, -- circle: orange + [3] = { 0.8, 0.0, 0.8, 1 }, -- diamond: purple + [4] = { 0.0, 0.8, 0.0, 1 }, -- triangle: green + [5] = { 0.7, 0.7, 0.7, 1 }, -- moon: silver + [6] = { 0.0, 0.4, 0.9, 1 }, -- square: blue + [7] = { 0.9, 0.0, 0.0, 1 }, -- cross: red + [8] = { 1.0, 1.0, 1.0, 1 }, -- skull: white + } + + local markerConfigKeys = { + [1] = "raidmarkercolor_star", + [2] = "raidmarkercolor_circle", + [3] = "raidmarkercolor_diamond", + [4] = "raidmarkercolor_triangle", + [5] = "raidmarkercolor_moon", + [6] = "raidmarkercolor_square", + [7] = "raidmarkercolor_cross", + [8] = "raidmarkercolor_skull", + } + + local markerColors = {} + for i = 1, 8 do + local d = defaultColors[i] + local r, g, b, a = ParseColor(C.unitframes[markerConfigKeys[i]], d[1], d[2], d[3], d[4]) + markerColors[i] = { r, g, b, a } + end + + local FALLBACK_INTERVAL = 1.0 -- safety net for units that come into range after marker was set + local elapsed = 0 + local isUnlocked = false + local ROW_HEIGHT = tonumber(C.unitframes.raidmarkerheight) or 14 + local BAR_WIDTH = tonumber(C.unitframes.raidmarkerwidth) or 80 + local GROW = C.unitframes.raidmarkergrow or "down" + local rm_texture = C.unitframes.raidmarkertexture or "Interface\\AddOns\\pfUI\\img\\bar" + local rm_fontsize = tonumber(C.unitframes.raidmarkerfontsize) or 12 + local rm_showpct = C.unitframes.raidmarkershowpct ~= "0" + local rm_showname = C.unitframes.raidmarkershowname ~= "0" + local rm_showportrait = C.unitframes.raidmarkershowportrait ~= "0" + local PORTRAIT_SIZE = ROW_HEIGHT + + -- Cache for shortened names: markerIndex -> { name, short } + local nameCache = {} + + local function ShortenName(name, row) + if not name or name == "" then return "" end + local barWidth = row.health:GetWidth() + if barWidth < 1 then barWidth = BAR_WIDTH end + local available = barWidth - 4 + if rm_showpct then available = available - 32 end + if available < 10 then return nil end + row.nametext:SetText(name) + if row.nametext:GetStringWidth() <= available then return name end + for len = strlen(name) - 1, 1, -1 do + local short = strsub(name, 1, len) .. "." + row.nametext:SetText(short) + if row.nametext:GetStringWidth() <= available then return short end + end + return strsub(name, 1, 1) .. "." + end + + local TOTAL_ROW_WIDTH = BAR_WIDTH + 20 + (rm_showportrait and (PORTRAIT_SIZE + 2) or 0) + + -- Container frame + pfUI.raidmarkers = CreateFrame("Frame", "pfMarkerTracker", UIParent) + pfUI.raidmarkers:SetFrameStrata("MEDIUM") + if GROW == "up" then + pfUI.raidmarkers:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -20, 200) + else + pfUI.raidmarkers:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -20, -200) + end + pfUI.raidmarkers:SetWidth(TOTAL_ROW_WIDTH) + pfUI.raidmarkers:SetHeight(8 * (ROW_HEIGHT + 1) + border * 2 - 1) + pfUI.raidmarkers:Hide() + + CreateBackdrop(pfUI.raidmarkers) + CreateBackdropShadow(pfUI.raidmarkers) + UpdateMovable(pfUI.raidmarkers) + + pfUI.raidmarkers:SetScript("OnMouseUp", function() + if pfUI.unlock and pfUI.unlock:IsShown() then + this:StopMovingOrSizing() + local _, _, _, x, y = this:GetPoint() + this:ClearAllPoints() + this:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", math.floor(x + 0.5), math.floor(y + 0.5)) + C.position["pfMarkerTracker"] = C.position["pfMarkerTracker"] or {} + C.position["pfMarkerTracker"]["anchor"] = "BOTTOMRIGHT" + C.position["pfMarkerTracker"]["xpos"] = math.floor(x + 0.5) + C.position["pfMarkerTracker"]["ypos"] = math.floor(y + 0.5) + end + end) + + -- Create 8 marker rows + pfUI.raidmarkers.rows = {} + for idx = 1, 8 do + local i = markerOrder[idx] + + local row = CreateFrame("Button", nil, pfUI.raidmarkers) + row:SetWidth(TOTAL_ROW_WIDTH) + row:SetHeight(ROW_HEIGHT) + row:Hide() + + row:RegisterForClicks("LeftButtonUp") + row:SetScript("OnClick", function() + TargetUnit(markerTokens[this.markerIndex]) + end) + + -- raid icon + row.icon = row:CreateTexture(nil, "ARTWORK") + row.icon:SetWidth(ROW_HEIGHT) + row.icon:SetHeight(ROW_HEIGHT) + row.icon:SetPoint("LEFT", row, "LEFT", 1, 0) + row.icon:SetTexture(pfUI.media["img:raidicons"]) + SetRaidTargetIconTexture(row.icon, i) + + -- portrait (right side) + row.portrait = row:CreateTexture(nil, "ARTWORK") + row.portrait:SetWidth(PORTRAIT_SIZE) + row.portrait:SetHeight(PORTRAIT_SIZE) + row.portrait:SetPoint("RIGHT", row, "RIGHT", -1, 0) + row.portrait:SetTexCoord(.1, .9, .1, .9) + if not rm_showportrait then row.portrait:Hide() end + + -- health bar + row.health = CreateFrame("StatusBar", nil, row) + row.health:SetPoint("LEFT", row.icon, "RIGHT", 2, 0) + if rm_showportrait then + row.health:SetPoint("RIGHT", row.portrait, "LEFT", -2, 0) + else + row.health:SetPoint("RIGHT", row, "RIGHT", -1, 0) + end + row.health:SetHeight(ROW_HEIGHT) + row.health:SetMinMaxValues(0, 1) + row.health:SetValue(1) + row.health:SetStatusBarTexture(rm_texture) + local c = markerColors[i] + row.health:SetStatusBarColor(c[1] * 0.5, c[2] * 0.5, c[3] * 0.5, c[4] or 0.9) + + -- name text (left) + row.nametext = row.health:CreateFontString(nil, "OVERLAY", "GameFontNormal") + row.nametext:SetPoint("LEFT", row.health, "LEFT", 2, 0) + row.nametext:SetFont(pfUI.font_default, rm_fontsize, "OUTLINE") + row.nametext:SetTextColor(1, 1, 1, 1) + row.nametext:SetJustifyH("LEFT") + row.nametext:SetText("") + if not rm_showname then row.nametext:Hide() end + + -- hp text (right) + row.hptext = row.health:CreateFontString(nil, "OVERLAY", "GameFontNormal") + row.hptext:SetPoint("RIGHT", row.health, "RIGHT", -2, 0) + row.hptext:SetFont(pfUI.font_default, rm_fontsize, "OUTLINE") + row.hptext:SetTextColor(1, 1, 1, 1) + row.hptext:SetJustifyH("RIGHT") + row.hptext:SetText("") + if not rm_showpct then row.hptext:Hide() end + + CreateBackdrop(row.health) + + row.markerIndex = i + row.label = "mark" -- enables /pfcast mouseover support via GetMouseFocus() + row.id = i + pfUI.raidmarkers.rows[i] = row + end + + local function UpdateDisplay() + if isUnlocked then return end + local anyActive = false + local visibleCount = 0 + local prevRow + + for idx = 1, 8 do + local i = markerOrder[idx] + local row = pfUI.raidmarkers.rows[i] + local token = markerTokens[i] + + if UnitExists(token) and not UnitIsDead(token) then + local hp = UnitHealth(token) + local maxhp = UnitHealthMax(token) + + if hp and maxhp and maxhp > 0 and hp > 0 then + local pct = hp / maxhp + row.health:SetValue(pct) + + if rm_showname then + local name = UnitName(token) + local cached = nameCache[i] + if not cached or cached.name ~= name then + local short = ShortenName(name, row) + if short then + nameCache[i] = { name = name, short = short } + end + end + row.nametext:SetText(nameCache[i] and nameCache[i].short or "") + end + + if rm_showpct then + row.hptext:SetText(math.floor(pct * 100) .. "%") + end + + if rm_showportrait then + SetPortraitTexture(row.portrait, token) + end + + row:ClearAllPoints() + if GROW == "up" then + if prevRow then + row:SetPoint("BOTTOM", prevRow, "TOP", 0, 1) + else + row:SetPoint("BOTTOM", pfUI.raidmarkers, "BOTTOM", 0, border) + end + else + if prevRow then + row:SetPoint("TOP", prevRow, "BOTTOM", 0, -1) + else + row:SetPoint("TOP", pfUI.raidmarkers, "TOP", 0, -border) + end + end + row:Show() + prevRow = row + anyActive = true + visibleCount = visibleCount + 1 + else + row:Hide() + end + else + nameCache[i] = nil + row:Hide() + end + end + + if anyActive then + pfUI.raidmarkers:SetHeight(visibleCount * (ROW_HEIGHT + 1) + border * 2 - 1) + pfUI.raidmarkers:Show() + elseif not (pfUI.unlock and pfUI.unlock:IsShown()) then + pfUI.raidmarkers:Hide() + end + end + + -- Unlock mode: show fixed 1-row placeholder so positioning works correctly + if pfUI.unlock then + local origShow = pfUI.unlock:GetScript("OnShow") + pfUI.unlock:SetScript("OnShow", function() + if origShow then origShow() end + isUnlocked = true + -- hide all rows, show container at 1-row height as drag handle + for i = 1, 8 do + pfUI.raidmarkers.rows[i]:Hide() + end + pfUI.raidmarkers:SetHeight(ROW_HEIGHT + border * 2) + pfUI.raidmarkers:Show() + end) + + local origHide = pfUI.unlock:GetScript("OnHide") + pfUI.unlock:SetScript("OnHide", function() + if origHide then origHide() end + isUnlocked = false + UpdateDisplay() + end) + end + + -- Event-driven scanner frame + local scanner = CreateFrame("Frame") + + -- RAID_TARGET_UPDATE: fires when a raid marker is set/cleared + -- PLAYER_ENTERING_WORLD: fires on login, reload, and zone transitions + scanner:RegisterEvent("RAID_TARGET_UPDATE") + scanner:RegisterEvent("PLAYER_ENTERING_WORLD") + + scanner:SetScript("OnEvent", function() + UpdateDisplay() + end) + + -- Fallback poll at 1s: catches units that come into range AFTER a marker was set + -- (no event fires for that case, so we need this safety net) + scanner:SetScript("OnUpdate", function() + elapsed = elapsed + arg1 + if elapsed < FALLBACK_INTERVAL then return end + elapsed = 0 + UpdateDisplay() + end) +end) \ No newline at end of file