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