-- load pfUI environment setfenv(1, pfUI:GetEnvironment()) pfUI.uf = CreateFrame("Frame", nil, UIParent) pfUI.uf.frames = {} -- Reusable buffer for C_UnitAuras.GetAuraSlots slot ids (filled by -- ScanAuraSlots, api.lua). Each aura scan in RefreshUnit fills it once, then -- reads every aura by slot id: one array walk per scan instead of one per index. -- Scans run back to back and consume the buffer before the next refill. local auraSlots = {} -- Separate buffer for the tooltip handlers. They run from OnEnter, which can -- fire while RefreshUnit is showing/hiding icons under the cursor, so they must -- not share the refresh buffer. local tooltipSlots = {} -- ============================================================================ -- GUID-based Roster Tracking for Smart Updates -- Only updates frames where the unit actually changed, not ALL 40 frames -- ============================================================================ pfUI.uf.guidTracker = { -- Maps frame to its last known GUID: frame -> guid frameToGuid = {}, } -- Clear all GUID tracking (forces full update next time) function pfUI.uf.ClearGuidTracking() pfUI.uf.guidTracker.frameToGuid = {} end -- slash command to toggle unitframe test mode pfUI.api.RegisterSlashCommand("PFTEST", { "/pftest", "/pfuftest" }, function() pfUI.uf.showall = not pfUI.uf.showall if pfUI.uf.raid and pfUI.uf.raid.LayoutPets then pfUI.uf.raid:LayoutPets() end end, true) -- Buff indicators are identified by a spell id, resolved once into the aura's -- localized name plus its icon. A match needs both to agree. -- -- Icon alone is ambiguous: Spell.dbc reuses icons across unrelated spells, so -- an icon-only filter lights the indicator for the wrong buff (Blessing of -- Sanctuary shares its icon with Lightning Shield and Shadowguard, Blessing of -- Kings with Mage Armor and Commanding Shout, Totemic Power with the Blessed -- Sunfruit food buff). Name alone is ambiguous too -- creature and item auras -- reuse player spell names ("Renew", "Rejuvenation", "Fire Resistance"). -- -- Every rank of a spell carries the same name and icon, so one id per buff -- covers the whole rank ladder. Ids missing from this client resolve to nil and -- drop out of the list. 'predict' names the libpredict key of a HoT. local indicator_cache = {} local function AddIndicator(indicators, spellId, predict) local record = indicator_cache[spellId] if record == nil then local name = C_Spell.GetSpellName(spellId) local icon = name and C_Spell.GetSpellTexture(spellId) -- cache misses as false, so an absent spell is only looked up once record = icon and { name = name:lower(), icon = icon:lower(), predict = predict } or false indicator_cache[spellId] = record end if record then table.insert(indicators, record) end end local glow = { edgeFile = pfUI.media["img:glow"], edgeSize = 8, insets = {left = 0, right = 0, top = 0, bottom = 0}, } local glow2 = { edgeFile = pfUI.media["img:glow2"], edgeSize = 8, insets = {left = 0, right = 0, top = 0, bottom = 0}, } local function DoNothing() return end local function BuffOnEnter() local parent = this:GetParent() if not parent.label then return end local unit = parent.label .. parent.id GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") GameTooltip:SetUnitAura(unit, this.id, "HELPFUL") if IsShiftKeyDown() then local aura = C_UnitAuras.GetAuraDataByIndex(unit, this.id, "HELPFUL") local playerlist = aura and GetUnbuffedRoster(aura.name) or "" if strlen(playerlist) > 0 then GameTooltip:AddLine(" ") GameTooltip:AddLine(T["Unbuffed"] .. ":", .3, 1, .8) GameTooltip:AddLine(playerlist,1,1,1,1) GameTooltip:Show() end end end local function BuffOnLeave() GameTooltip:Hide() end local function BuffOnClick() if this:GetParent().label == "player" then local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, "HELPFUL") if aura and aura.spellId then C_Spell.CancelSpellByID(aura.spellId) end end end local function DebuffOnEnter() if not this:GetParent().label then return end local unitstr = this:GetParent().label .. this:GetParent().id GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") if this:GetParent().label ~= "player" then local parent = this:GetParent() -- selfdebuff filters the displayed list to player-cast harmful auras, but -- SetUnitAura's index has to be into the unfiltered HARMFUL list. Look up -- the displayed aura via the PLAYER filter, then find its position in the -- unfiltered list by name + sourceGUID. -- -- The unfiltered list is NOT capped at 16: once a unit's 16 debuff slots -- are full the server parks further debuffs in buff slots, and C_UnitAuras -- reports those as harmful too. So enumerate however many the unit has. if parent.config and parent.config.selfdebuff == "1" then local ownAura = C_UnitAuras.GetAuraDataByIndex(unitstr, this.id, "HARMFUL|PLAYER") if ownAura then local n = ScanAuraSlots(unitstr, "HARMFUL", tooltipSlots) for i = 1, n do local check = C_UnitAuras.GetAuraDataBySlot(unitstr, tooltipSlots[i]) if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then GameTooltip:SetUnitAura(unitstr, i, "HARMFUL") return end end end end end GameTooltip:SetUnitAura(unitstr, this.id, "HARMFUL") end local function DebuffOnLeave() GameTooltip:Hide() end local function DebuffOnClick() if this:GetParent().label == "player" then local aura = C_UnitAuras.GetAuraDataByIndex("player", this.id, "HARMFUL") if aura and aura.spellId then C_Spell.CancelSpellByID(aura.spellId) end end end local visibilityscan = CreateFrame("Frame", "pfUnitFrameVisibility", UIParent) visibilityscan.frames = {} visibilityscan:SetScript("OnUpdate", function() if ( this.limit or 1) > GetTime() then return else this.limit = GetTime() + .2 end for frame in pairs(this.frames) do frame:UpdateVisibility() end end) -- ============================================================================ -- GetUnitStats - health + power for a unit token. -- Returns: hp, maxHp, power, maxPower, powerType -- ============================================================================ function pfUI.api.GetUnitStats(unitstr) local powerType = UnitPowerType(unitstr) or 0 return UnitHealth(unitstr) or 0, UnitHealthMax(unitstr) or 1, UnitPower(unitstr, powerType) or 0, UnitPowerMax(unitstr, powerType) or 1, powerType end local aggrodata = { } local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing function pfUI.api.UnitHasAggro(unit) local now = GetTime() local data = aggrodata[unit] -- Cache positive results 1s (so aggro clears fast) AND negative results 0.3s -- (so we don't rescan the whole unit table on every call while nothing has aggro). if data and now < data.check + (data.state > 0 and 1 or 0.3) then return data.state end if not data then data = { }; aggrodata[unit] = data end data.check = now data.state = 0 if UnitExists(unit) and UnitIsFriend(unit, "player") then -- pfValidUnits never changes after load, so precompute the "target" / -- "targettarget" token strings once instead of concatenating per call. if not aggroScan then aggroScan = {} for u in pairs(pfValidUnits) do local t = u .. "target" table.insert(aggroScan, { u = u, t = t, tt = t .. "target" }) end end for i = 1, table.getn(aggroScan) do local s = aggroScan[i] if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then data.state = data.state + 1 end if UnitExists(s.tt) and UnitIsUnit(s.tt, unit) and UnitCanAttack(s.t, unit) then data.state = data.state + 1 end end end return data.state end pfUI.uf.glow = CreateFrame("Frame", nil, UIParent) pfUI.uf.glow:SetScript("OnUpdate", function() local fpsmod = GetFramerate() / 30 if not this.val or this.val >= .8 then this.mod = -0.01 / fpsmod elseif this.val <= .4 then this.mod = 0.01 / fpsmod end this.val = this.val + this.mod end) pfUI.uf.glow.mod = 0 pfUI.uf.glow.val = 0.6 function pfUI.uf.glow.UpdateGlowAnimation() local val = pfUI.uf.glow.val or 0.6 if val < 0.4 then val = 0.4 end if val > 0.8 then val = 0.8 end this:SetAlpha(val) end function pfUI.uf:UpdateVisibility() local self = self or this -- cache result of strsub to avoid repeating calls if not self.cache_raid then local name = self:GetName() if strsub(name,0,9) == "pfRaidPet" then -- pet grid slot (pfRaidPet1..40); used to mirror party pets below self.cache_raid = 0 self.cache_raidpet = tonumber(strsub(name,10)) or 0 elseif strsub(name,0,6) == "pfRaid" then self.cache_raid = tonumber(strsub(name,7,8)) or 0 else self.cache_raid = 0 end end -- show groupframes as raid if self.cache_raid > 0 then local id = self.cache_raid -- always show self in raidframes if not IsInGroup() and C.unitframes.selfinraid == "1" and id == 1 then self.id = "" self.label = "player" -- use raidframes for groups elseif not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then if id == 1 then self.id = "" self.label = "player" elseif id <= 5 then self.id = id - 1 self.label = "party" end -- reset to regular raid unitstrings elseif self.label == "party" or self.label == "player" then self.id = id self.label = "raid" end end -- show raidpet frames as party pets when a party is shown as a raid grid if self.cache_raidpet then if not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then local id = self.cache_raidpet if id == 1 then -- grid slot 1 mirrors the player, so its pet is the player's pet self.id = "" self.label = "pet" elseif id <= 5 then self.id = id - 1 self.label = "partypet" end -- reset to regular raidpet unitstrings after leaving party mode elseif self.label == "pet" or self.label == "partypet" then self.id = self.cache_raidpet self.label = "raidpet" end end -- display every unit as player while pfUI.uf.showall is set if pfUI.uf.showall then self._label = self._label or self.label self._id = self._id or self.id self.label, self.id = "player", "" elseif not pfUI.uf.showall and self._label and self._id then self.label, self.id = self._label, self._id self._label, self._id = nil, nil end local unitstr = ("%s%s"):format(self.label or "", self.id or "") self:SetAttribute("unit", unitstr ~= "" and unitstr or nil) local visibility = ("[target=%s,exists] show; hide"):format(unitstr) -- Group frames are redundant when the group is already shown as a raid grid: -- either an actual raid, or a party promoted to the raid grid via -- raidforgroup. hide_in_raid gates whether we hide them in that case. local hide_group = C["unitframes"]["group"]["hide_in_raid"] == "1" and (IsInRaid() or (C.unitframes.raidforgroup == "1" and IsInGroup())) if pfUI.unlock and pfUI.unlock:IsShown() then -- display during unlock mode visibility = "show" self.visible = true elseif self.config.visible == "0" then -- frame shall not be visible visibility = "hide" self.visible = nil elseif hide_group and self.cache_raid == 0 and not self.cache_raidpet and self.label and strsub(self.label,0,5) == "party" then -- hide group while shown as a raid grid and option is set (raid player -- frames carry label "party" and raid pet frames carry label "partypet" -- under raidforgroup, so exclude them by cache_raid / cache_raidpet) visibility = "hide" self.visible = nil elseif ( self.fname == "Group0" or self.fname == "PartyPet0" or self.fname == "Party0Target" ) and (not IsInGroup() or hide_group) then -- hide self in group if solo or shown as a raid grid visibility = "hide" self.visible = nil end -- vanilla visibility if self.unitname then self:Show() elseif visibility == "hide" then self:Hide() elseif visibility == "show" then self:Show() else if UnitName(unitstr) then -- hide existing but too far away pet and pets of old group members if self.label == "partypet" then if not UnitIsVisible(unitstr) or not UnitExists("party" .. self.id) then self:Hide() return end elseif self.label == "raidpet" then if not UnitIsVisible(unitstr) or not UnitExists("raid" .. self.id) then self:Hide() return end elseif self.label == "pettarget" then if not UnitIsVisible(unitstr) or not UnitExists("pet") then self:Hide() return end end self:Show() else self.lastUnit = nil self:Hide() end end end function pfUI.uf:UpdateFrameSize() local rawborder, default_border = GetBorderSize("unitframes") local spacing = self.config.pspace * GetPerfectPixel() local width = self.config.width local height = self.config.height local pheight = self.config.pheight local ptwidth = self.config.portraitwidth local ptheight = self.config.portraitheight local real_height = height + spacing + pheight + 2*default_border if spacing ~= abs(spacing) and abs(spacing) > tonumber(pheight) then real_height = height spacing = 0 end local portrait = 0 if self.config.portrait == "left" or self.config.portrait == "right" then if ptwidth == "-1" and ptheight == "-1" then -- align portrait size to frame self.portrait:SetSize(real_height, real_height) portrait = real_height + spacing + 2*default_border else -- use custom portrait size self.portrait:SetSize(ptwidth, ptheight) portrait = ptwidth + spacing + 2*default_border end end self:SetSize(width + portrait, real_height) end function pfUI.uf:UpdateConfig() local f = self local C = pfUI_config local rawborder, default_border = GetBorderSize("unitframes") local spacing = f.config.pspace * GetPerfectPixel() local cooldown_text = tonumber(f.config.cooldown_text) local cooldown_anim = tonumber(f.config.cooldown_anim) local relative_point = "BOTTOM" if f.config.panchor == "TOPLEFT" then relative_point = "BOTTOMLEFT" elseif f.config.panchor == "TOPRIGHT" then relative_point = "BOTTOMRIGHT" end f.dispellable = nil f.indicators = nil f.indicator_custom = nil f.alpha_visible = tonumber(f.config.alpha_visible) f.alpha_outrange = tonumber(f.config.alpha_outrange) f.alpha_offline = tonumber(f.config.alpha_offline) f:SetFrameStrata("MEDIUM") f.glow:SetFrameStrata("BACKGROUND") f.glow:SetFrameLevel(0) f.glow:SetBackdrop(glow2) f.glow:SetPoint("TOPLEFT", f, "TOPLEFT", -6 - default_border,6 + default_border) f.glow:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 6 + default_border,-6 - default_border) f.glow:SetScript("OnUpdate", pfUI.uf.glow.UpdateGlowAnimation) f.glow:Hide() f.combat:SetSize(tonumber(f.config.squaresize), tonumber(f.config.squaresize)) f.combat:ClearAllPoints() f.combat:SetPoint(f.config.squarepos, 0, 0) f.combat:Hide() f.hp:ClearAllPoints() f.hp:SetPoint("TOP", 0, 0) f.hp:SetSize(f.config.width, f.config.height) if tonumber(f.config.height) < 0 then f.hp:Hide() end pfUI.api.CreateBackdrop(f.hp, default_border) f.hp.bar:SetStatusBarTexture(pfUI.media[f.config.bartexture]) f.hp.bar:SetAllPoints(f.hp) if f.config.verticalbar == "1" then f.hp.bar:SetOrientation("VERTICAL") else f.hp.bar:SetOrientation("HORIZONTAL") end local custombg = f.config.defcolor == "0" and f.config.custombg or C.unitframes.custombg local custombgcolor = f.config.defcolor == "0" and f.config.custombgcolor or C.unitframes.custombgcolor if custombg == "1" then local cr, cg, cb, ca = GetStringColor(custombgcolor) cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca) f.hp.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca) end f.power:ClearAllPoints() f.power:SetPoint(f.config.panchor, f.hp, relative_point, f.config.poffx, -2 * default_border - spacing + f.config.poffy * GetPerfectPixel()) f.power:SetSize((f.config.pwidth ~= "-1" and f.config.pwidth or f.config.width), f.config.pheight) if tonumber(f.config.pheight) < 0 then f.power:Hide() end pfUI.api.CreateBackdrop(f.power, default_border) f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 1) f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture]) f.power.bar:SetAllPoints(f.power) local custompbg = f.config.defcolor == "0" and f.config.custompbg or C.unitframes.custompbg local custompbgcolor = f.config.defcolor == "0" and f.config.custompbgcolor or C.unitframes.custompbgcolor if custompbg == "1" then local cr, cg, cb, ca = GetStringColor(custompbgcolor) cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca) f.power.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca) end local fontname, fontsize, fontstyle if f.config.customfont == "1" then fontname = pfUI.media[f.config.customfont_name] fontsize = tonumber(f.config.customfont_size) fontstyle = f.config.customfont_style else fontname = pfUI.font_unit fontsize = tonumber(C.global.font_unit_size) fontstyle = C.global.font_unit_style end -- Druid secondary mana bar: texture/color/size/position below the power bar, -- using its own C.unitframes.druidmana* config. Values are read in -- UpdateDruidMana; here we only lay it out. if f.druidmana then local DC = C.unitframes local dmTexture = DC.druidmanatexture or "Interface\\AddOns\\pfUI\\img\\bar" f.druidmana:SetStatusBarTexture(pfUI.media[dmTexture] or dmTexture) f.druidmana:SetFrameLevel(f:GetFrameLevel() + 5) local manacolor = f.config.defcolor == "0" and f.config.manacolor or C.unitframes.manacolor f.druidmana:SetStatusBarColor(GetStringColor(manacolor)) local dmHeight = tonumber(DC.druidmanaheight) or 10 local dmWidth = DC.druidmanawidth or "-1" local dmOffX = tonumber(DC.druidmanaoffx) or 0 local dmOffY = tonumber(DC.druidmanaoffy) or 0 local dmSpace = tonumber(DC.druidmanaspace) or -3 local dmSpacing = -2 * default_border - dmSpace f.druidmana:SetHeight(dmHeight) f.druidmana:ClearAllPoints() local w = dmWidth ~= "-1" and tonumber(dmWidth) or nil if w then f.druidmana:SetWidth(w) f.druidmana:SetPoint("TOP", f.power, "BOTTOM", dmOffX, dmSpacing + dmOffY) else f.druidmana:SetPoint("TOPLEFT", f.power, "BOTTOMLEFT", dmOffX, dmSpacing + dmOffY) f.druidmana:SetPoint("TOPRIGHT", f.power, "BOTTOMRIGHT", dmOffX, dmSpacing + dmOffY) end if not f.druidmana._hasbd then CreateBackdrop(f.druidmana, default_border) CreateBackdropShadow(f.druidmana) f.druidmana._hasbd = true end local tr, tg, tb = ManaBarColor[Enum.PowerType.Mana].r, ManaBarColor[Enum.PowerType.Mana].g, ManaBarColor[Enum.PowerType.Mana].b if C.unitframes.pastel == "1" then tr, tg, tb = (tr + .75) * .5, (tg + .75) * .5, (tb + .75) * .5 end f.druidmana.text:SetFont(fontname, fontsize, fontstyle) f.druidmana.text:SetTextColor(tr, tg, tb, 1) end f.portrait.tex:SetAllPoints(f.portrait) f.portrait.tex:SetTexCoord(.1, .9, .1, .9) f.portrait.model:SetAllPoints(f.portrait) if f.config.portrait == "bar" then f.portrait:SetParent(f.hp.bar) f.portrait:SetAllPoints(f.hp.bar) f.portrait:SetAlpha(C.unitframes.portraitalpha) if f.portrait.backdrop then f.portrait.backdrop:Hide() end -- place portrait below fonts f.portrait.model:SetFrameLevel(3) f.portrait:Show() elseif f.config.portrait == "left" then f.portrait:SetParent(f) f.portrait:ClearAllPoints() f.portrait:SetPoint("LEFT", f, "LEFT", 0, 0) f.hp:ClearAllPoints() f.hp:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0) f.portrait:SetAlpha(f:GetAlpha()) -- make sure incHeal is above f.portrait:SetFrameStrata("BACKGROUND") f.portrait.model:SetFrameStrata("BACKGROUND") f.portrait.model:SetFrameLevel(1) pfUI.api.CreateBackdrop(f.portrait, default_border) f.portrait.backdrop:Show() f.portrait:Show() elseif f.config.portrait == "right" then f.portrait:SetParent(f) f.portrait:ClearAllPoints() f.portrait:SetPoint("RIGHT", f, "RIGHT", 0, 0) f.hp:ClearAllPoints() f.hp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) f.portrait:SetAlpha(f:GetAlpha()) -- make sure incHeal is above f.portrait:SetFrameStrata("BACKGROUND") f.portrait.model:SetFrameStrata("BACKGROUND") f.portrait.model:SetFrameLevel(1) pfUI.api.CreateBackdrop(f.portrait, default_border) f.portrait.backdrop:Show() f.portrait:Show() else f.portrait:Hide() end if f.group then f.group:SetShown(f.config.raidgrouplabel == "1") local xoff = tonumber(f.config.grouplabelxoff) or 0 local yoff = tonumber(f.config.grouplabelyoff) or 8 f.group:SetPoint("TOPLEFT", f, "BOTTOMLEFT", xoff, yoff) f.group:SetPoint("TOPRIGHT", f, "BOTTOMRIGHT", xoff, yoff) end if f.config.hitindicator == "1" then f.feedbackText:SetFont(pfUI.media[f.config.hitindicatorfont], f.config.hitindicatorsize, "OUTLINE") f.feedbackFontHeight = f.config.hitindicatorsize f.feedbackStartTime = GetTime() if f.config.portrait == "bar" or f.config.portrait == "off" then f.feedbackText:SetParent(f.texts) f.feedbackText:ClearAllPoints() f.feedbackText:SetPoint("CENTER", f.hp.bar, "CENTER") else f.feedbackText:SetParent(f.portrait) f.feedbackText:ClearAllPoints() f.feedbackText:SetPoint("CENTER", f.portrait, "CENTER") end f:RegisterEvent("UNIT_COMBAT") else f.feedbackText:Hide() f:UnregisterEvent("UNIT_COMBAT") end f.hpLeftText:SetFontObject(GameFontWhite) f.hpLeftText:SetFont(fontname, fontsize, fontstyle) f.hpLeftText:SetJustifyH("LEFT") f.hpLeftText:ClearAllPoints() f.hpLeftText:SetPoint("TOPLEFT",f.hp.bar, "TOPLEFT", 2*(default_border + f.config.txthpleftoffx), 1 + tonumber(f.config.txthpleftoffy)) f.hpLeftText:SetPoint("BOTTOMRIGHT",f.hp.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txthpleftoffx), f.config.txthpleftoffy) f.hpRightText:SetFontObject(GameFontWhite) f.hpRightText:SetFont(fontname, fontsize, fontstyle) f.hpRightText:SetJustifyH("RIGHT") f.hpRightText:ClearAllPoints() f.hpRightText:SetPoint("TOPLEFT",f.hp.bar, "TOPLEFT", 2*(default_border + f.config.txthprightoffx), 1 + tonumber(f.config.txthprightoffy)) f.hpRightText:SetPoint("BOTTOMRIGHT",f.hp.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txthprightoffx), f.config.txthprightoffy) f.hpCenterText:SetFontObject(GameFontWhite) f.hpCenterText:SetFont(fontname, fontsize, fontstyle) f.hpCenterText:SetJustifyH("CENTER") f.hpCenterText:ClearAllPoints() f.hpCenterText:SetPoint("TOPLEFT",f.hp.bar, "TOPLEFT", f.config.txthpcenteroffx, 1 + tonumber(f.config.txthpcenteroffy)) f.hpCenterText:SetPoint("BOTTOMRIGHT",f.hp.bar, "BOTTOMRIGHT", f.config.txthpcenteroffx, f.config.txthpcenteroffy) f.powerLeftText:SetFontObject(GameFontWhite) f.powerLeftText:SetFont(fontname, fontsize, fontstyle) f.powerLeftText:SetJustifyH("LEFT") f.powerLeftText:ClearAllPoints() f.powerLeftText:SetPoint("TOPLEFT",f.power.bar, "TOPLEFT", 2*(default_border + f.config.txtpowerleftoffx), 1 + tonumber(f.config.txtpowerleftoffy)) f.powerLeftText:SetPoint("BOTTOMRIGHT",f.power.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txtpowerleftoffx), f.config.txtpowerleftoffy) f.powerRightText:SetFontObject(GameFontWhite) f.powerRightText:SetFont(fontname, fontsize, fontstyle) f.powerRightText:SetJustifyH("RIGHT") f.powerRightText:ClearAllPoints() f.powerRightText:SetPoint("TOPLEFT",f.power.bar, "TOPLEFT", 2*(default_border + f.config.txtpowerrightoffx), 1 + tonumber(f.config.txtpowerrightoffy)) f.powerRightText:SetPoint("BOTTOMRIGHT",f.power.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txtpowerrightoffx), f.config.txtpowerrightoffy) f.powerCenterText:SetFontObject(GameFontWhite) f.powerCenterText:SetFont(fontname, fontsize, fontstyle) f.powerCenterText:SetJustifyH("CENTER") f.powerCenterText:ClearAllPoints() f.powerCenterText:SetPoint("TOPLEFT",f.power.bar, "TOPLEFT", f.config.txtpowercenteroffx, 1 + tonumber(f.config.txtpowercenteroffy)) f.powerCenterText:SetPoint("BOTTOMRIGHT",f.power.bar, "BOTTOMRIGHT", f.config.txtpowercenteroffx, f.config.txtpowercenteroffy) f.incHeal:SetSize(f.config.width, f.config.height) f.incHeal.texture:SetTexture(pfUI.media["img:bar"]) local cr, cg, cb, ca = GetStringColor(f.config.healcolor) cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca) f.incHeal.texture:SetVertexColor(cr, cg, cb, ca) f.incHeal:Hide() if f.config.verticalbar == "0" then f.incHeal:ClearAllPoints() f.incHeal:SetPoint("TOPLEFT", f.hp.bar, "TOPLEFT", 0, 0) else f.incHeal:ClearAllPoints() f.incHeal:SetPoint("BOTTOM", f.hp.bar, "BOTTOM", 0, 0) end f.ressIcon:SetFrameLevel(16) f.ressIcon:SetSize(32, 32) f.ressIcon:SetPoint("CENTER", f, "CENTER", 0, 4) f.ressIcon.texture:SetTexture(pfUI.media["img:ress"]) f.ressIcon.texture:SetAllPoints(f.ressIcon) f.ressIcon:Hide() f.leaderIcon:SetSize(10, 10) f.leaderIcon:SetPoint("CENTER", f, "TOPLEFT", 0, 0) f.leaderIcon.texture:SetTexture("Interface\\GROUPFRAME\\UI-Group-LeaderIcon") f.leaderIcon.texture:SetAllPoints(f.leaderIcon) f.leaderIcon:Hide() f.lootIcon:SetSize(10, 10) f.lootIcon:SetPoint("CENTER", f, "LEFT", 0, 0) f.lootIcon.texture:SetTexture("Interface\\GROUPFRAME\\UI-Group-MasterLooter") f.lootIcon.texture:SetAllPoints(f.lootIcon) f.lootIcon:Hide() f.pvpIcon:SetSize(f.config.pvpiconsize, f.config.pvpiconsize) f.pvpIcon:SetPoint(f.config.pvpiconalign, f, f.config.pvpiconalign, f.config.pvpiconoffx, f.config.pvpiconoffy) f.pvpIcon.texture:SetTexture(pfUI.media["img:pvp"]) f.pvpIcon.texture:SetAllPoints(f.pvpIcon) f.pvpIcon.texture:SetVertexColor(1,1,1,.5) f.pvpIcon:Hide() f.raidIcon:SetSize(f.config.raidiconsize, f.config.raidiconsize) f.raidIcon:SetPoint("CENTER", f, f.config.raidiconalign, f.config.raidiconoffx, f.config.raidiconoffy) local raidIconTex = C.unitframes.blizzard_raidicons == "1" and "Interface\\TargetingFrame\\UI-RaidTargetingIcons" or pfUI.media["img:raidicons"] f.raidIcon.texture:SetTexture(raidIconTex) f.raidIcon.texture:SetAllPoints(f.raidIcon) f.raidIcon:Hide() f.restIcon:SetSize(16, 16) f.restIcon:SetPoint("TOP", f, "TOPLEFT", 0, -1) f.restIcon.texture:SetTexture("Interface\\CharacterFrame\\UI-StateIcon", true) f.restIcon.texture:SetTexCoord(0, .5, 0, .421875) f.restIcon.texture:SetAllPoints(f.restIcon) f.restIcon:Hide() f.happinessIcon:SetSize(tonumber(C.unitframes.pet.happinesssize), tonumber(C.unitframes.pet.happinesssize)) f.happinessIcon:SetPoint("CENTER", f, "TOPLEFT", default_border, -default_border) f.happinessIcon.texture:SetTexture(pfUI.media["img:neutral"]) f.happinessIcon.texture:SetAllPoints(f.happinessIcon) f.happinessIcon.texture:SetVertexColor(1, 1, 0, 1) f.happinessIcon:Hide() if f.config.buffs == "off" then for i=1, 32 do if f.buffs and f.buffs[i] then f.buffs[i]:Hide() f.buffs[i] = nil end end f.buffs = nil else f.buffs = f.buffs or {} for i=1, 32 do if i > tonumber(f.config.bufflimit) then break end local perrow = f.config.buffperrow local row = floor((i-1) / perrow) f.buffs[i] = f.buffs[i] or CreateFrame("Button", "pfUI" .. f.fname .. "Buff" .. i, f) f.buffs[i].texture = f.buffs[i].texture or f.buffs[i]:CreateTexture() f.buffs[i].texture:SetTexCoord(.08, .92, .08, .92) f.buffs[i].texture:SetAllPoints() f.buffs[i].stacks = f.buffs[i].stacks or f.buffs[i]:CreateFontString(nil, "OVERLAY", f.buffs[i]) f.buffs[i].stacks:SetFont(pfUI.font_unit, C.global.font_unit_size, "OUTLINE") f.buffs[i].stacks:SetPoint("BOTTOMRIGHT", f.buffs[i], 2, -2) f.buffs[i].stacks:SetJustifyH("LEFT") f.buffs[i].stacks:SetShadowColor(0, 0, 0) f.buffs[i].stacks:SetShadowOffset(0.8, -0.8) f.buffs[i].stacks:SetTextColor(1,1,.5) f.buffs[i]:SetFrameLevel(12) f.buffs[i]:RegisterForClicks("RightButtonUp") f.buffs[i]:ClearAllPoints() local invert_h, invert_v, af if f.config.buffs == "TOPLEFT" then invert_h = 1 invert_v = 1 af = "BOTTOMLEFT" elseif f.config.buffs == "BOTTOMLEFT" then invert_h = -1 invert_v = 1 af = "TOPLEFT" elseif f.config.buffs == "TOPRIGHT" then invert_h = 1 invert_v = -1 af = "BOTTOMRIGHT" elseif f.config.buffs == "BOTTOMRIGHT" then invert_h = -1 invert_v = -1 af = "TOPRIGHT" end local anchor = f.config.portraitheight ~= "-1" and f.hp or f if anchor == f.hp and (f.config.buffs == "BOTTOMLEFT" or f.config.buffs == "BOTTOMRIGHT") then anchor = f.power end local multiply = C.appearance.border.force_blizz == "1" and 1 or 2 f.buffs[i]:SetPoint(af, anchor, f.config.buffs, invert_v * (i-1-row*perrow)*(multiply*default_border + f.config.buffsize + 1), invert_h * (row*(multiply*default_border + f.config.buffsize + 1) + (multiply*default_border + 1))) f.buffs[i]:SetSize(f.config.buffsize, f.config.buffsize) -- Create CD frame if it doesn't exist if not f.buffs[i].cd then if cooldown_anim == 1 then -- Animation enabled: Use Model frame with CooldownFrameTemplate f.buffs[i].cd = CreateFrame("Model", f.buffs[i]:GetName() .. "Cooldown", f.buffs[i], "CooldownFrameTemplate") else -- Animation disabled: Use regular Frame with dummy functions f.buffs[i].cd = CreateFrame("Frame", f.buffs[i]:GetName() .. "Cooldown", f.buffs[i]) f.buffs[i].cd.AdvanceTime = DoNothing f.buffs[i].cd.SetSequence = DoNothing f.buffs[i].cd.SetSequenceTime = DoNothing end end -- Always update CD properties (in case size changed) local cdScale = f.config.buffsize / 32 f.buffs[i].cd:ClearAllPoints() f.buffs[i].cd:SetScale(cdScale) f.buffs[i].cd:SetAllPoints(f.buffs[i]) f.buffs[i].cd:SetFrameLevel(14) f.buffs[i].cd.pfCooldownType = "ALL" f.buffs[i].cd.pfCooldownStyleText = cooldown_text f.buffs[i].cd.pfCooldownStyleAnimation = cooldown_anim f.buffs[i].cd:SetAlpha(cooldown_anim == 1 and 1 or 0) -- immediately show/hide existing cooldown text if f.buffs[i].cd.pfCooldownText then f.buffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1) end f.buffs[i].id = i f.buffs[i]:Hide() CreateBackdrop(f.buffs[i], default_border) f.buffs[i]:SetScript("OnEnter", BuffOnEnter) f.buffs[i]:SetScript("OnLeave", BuffOnLeave) f.buffs[i]:SetScript("OnClick", BuffOnClick) end end if f.config.debuffs == "off" then for i=1, 32 do if f.debuffs and f.debuffs[i] then f.debuffs[i]:Hide() f.debuffs[i] = nil end end f.debuffs = nil else f.debuffs = f.debuffs or {} for i=1, 32 do if i > tonumber(f.config.debufflimit) then break end f.debuffs[i] = f.debuffs[i] or CreateFrame("Button", "pfUI" .. f.fname .. "Debuff" .. i, f) f.debuffs[i].texture = f.debuffs[i].texture or f.debuffs[i]:CreateTexture() f.debuffs[i].texture:SetTexCoord(.08, .92, .08, .92) f.debuffs[i].texture:SetAllPoints() f.debuffs[i].stacks = f.debuffs[i].stacks or f.debuffs[i]:CreateFontString(nil, "OVERLAY", f.debuffs[i]) f.debuffs[i].stacks:SetFont(pfUI.font_unit, C.global.font_unit_size, "OUTLINE") f.debuffs[i].stacks:SetPoint("BOTTOMRIGHT", f.debuffs[i], 2, -2) f.debuffs[i].stacks:SetJustifyH("LEFT") f.debuffs[i].stacks:SetShadowColor(0, 0, 0) f.debuffs[i].stacks:SetShadowOffset(0.8, -0.8) f.debuffs[i].stacks:SetTextColor(1,1,.5) f.debuffs[i]:SetFrameLevel(12) f.debuffs[i]:RegisterForClicks("RightButtonUp") f.debuffs[i]:ClearAllPoints() f.debuffs[i]:SetSize(f.config.debuffsize, f.config.debuffsize) f.debuffs[i]:SetNormalTexture(nil) -- Create CD frame if it doesn't exist if not f.debuffs[i].cd then if cooldown_anim == 1 then -- Animation enabled: Use Model frame with CooldownFrameTemplate f.debuffs[i].cd = CreateFrame("Model", f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i], "CooldownFrameTemplate") else -- Animation disabled: Use regular Frame with dummy functions f.debuffs[i].cd = CreateFrame("Frame", f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i]) f.debuffs[i].cd.AdvanceTime = DoNothing f.debuffs[i].cd.SetSequence = DoNothing f.debuffs[i].cd.SetSequenceTime = DoNothing end end -- Always update CD properties (in case size changed) local cdScale = f.config.debuffsize / 32 f.debuffs[i].cd:ClearAllPoints() f.debuffs[i].cd:SetScale(cdScale) f.debuffs[i].cd:SetAllPoints(f.debuffs[i]) f.debuffs[i].cd:SetFrameLevel(14) f.debuffs[i].cd.pfCooldownType = "ALL" f.debuffs[i].cd.pfCooldownStyleText = cooldown_text f.debuffs[i].cd.pfCooldownStyleAnimation = cooldown_anim f.debuffs[i].cd:SetAlpha(cooldown_anim == 1 and 1 or 0) -- immediately show/hide existing cooldown text if f.debuffs[i].cd.pfCooldownText then f.debuffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1) end f.debuffs[i].id = i f.debuffs[i]:Hide() CreateBackdrop(f.debuffs[i], default_border) f.debuffs[i]:SetScript("OnEnter", DebuffOnEnter) f.debuffs[i]:SetScript("OnLeave", DebuffOnLeave) f.debuffs[i]:SetScript("OnClick", DebuffOnClick) end end if f.config.visible == "1" then pfUI.uf:RefreshUnit(f, "all") f:EnableScripts() f:EnableEvents() f:UpdateFrameSize() else f:UnregisterAllEvents() f:Hide() end end function pfUI.uf.OnShow() pfUI.uf:RefreshUnit(this, "portrait") pfUI.uf:RefreshUnit(this, "base") end -- GUID-keyed aura start-time tracker, fed by Nampower's BUFF_ADDED_OTHER / -- BUFF_REMOVED_OTHER. Non-player units have aura.expirationTime == 0 (server -- doesn't broadcast time), so we reconstruct the swirl from "when we saw it -- applied" + aura.duration (Spell.dbc base). Only auras we actually witnessed -- being cast get tracked — pre-existing auras (already on a unit when you -- target them) intentionally show no timer rather than a fabricated one. pfUI.uf.aura_starts = {} -- {[guid] = {[spellId] = startTime}} local auraTracker = CreateFrame("Frame", "pfUF_AuraTracker", UIParent) auraTracker:RegisterEvent("BUFF_ADDED_OTHER") auraTracker:RegisterEvent("BUFF_REMOVED_OTHER") auraTracker:SetScript("OnEvent", function() -- Nampower arg layout: arg1=guid, arg3=spellId. State code 0=added, 1=removed, -- 2=modified; we treat any added/modified as "reset start = now" and let the -- REMOVED event clear the entry. if not arg1 or not arg3 or arg3 == 0 then return end if event == "BUFF_REMOVED_OTHER" then if pfUI.uf.aura_starts[arg1] then pfUI.uf.aura_starts[arg1][arg3] = nil end else pfUI.uf.aura_starts[arg1] = pfUI.uf.aura_starts[arg1] or {} pfUI.uf.aura_starts[arg1][arg3] = GetTime() end end) function pfUI.uf.OnEvent() -- Handle shutdown to prevent crash 132 if event == "PLAYER_LOGOUT" then this:UnregisterAllEvents() this:SetScript("OnEvent", nil) this:SetScript("OnUpdate", nil) return end -- update indicators if event == "PARTY_LEADER_CHANGED" or event == "PARTY_LOOT_METHOD_CHANGED" or event == "PARTY_MEMBERS_CHANGED" or event == "RAID_TARGET_UPDATE" or event == "RAID_ROSTER_UPDATE" or event == "PLAYER_UPDATE_RESTING" then this.update_indicators = true end -- abort on broken unitframes (e.g focus) if not this.label then return end -- update regular frames if event == "PLAYER_ENTERING_WORLD" then this.update_full = true -- Clear GUID tracking on zone change for full rebuild if pfUI.uf.ClearGuidTracking then pfUI.uf.ClearGuidTracking() end elseif this.label == "target" and event == "PLAYER_TARGET_CHANGED" and not pfScanActive == true then this.update_full = true -- immediately update raid icon to avoid delay on target swap if this.raidIcon and this.config and this.config.raidicon == "1" then local raidIcon = UnitName("target") and GetRaidTargetIndex("target") if raidIcon then SetRaidTargetIconTexture(this.raidIcon.texture, raidIcon) this.raidIcon:Show() else this.raidIcon:Hide() end end elseif ( this.label == "raid" or this.label == "party" or this.label == "player" ) and event == "PARTY_MEMBERS_CHANGED" then -- Smart update: check if THIS frame's unit actually changed if pfUI.uf.guidTracker and this.id then local unit = this.label == "player" and "player" or (this.label .. this.id) local newGuid = UnitGUID(unit) local oldGuid = pfUI.uf.guidTracker.frameToGuid[this] if newGuid ~= oldGuid then pfUI.uf.guidTracker.frameToGuid[this] = newGuid this.update_full = true end else this.update_full = true end elseif ( this.label == "raid" or this.label == "party" ) and event == "PARTY_MEMBER_ENABLE" then this.update_full = true elseif ( this.label == "raid" or this.label == "party" ) and event == "PARTY_MEMBER_DISABLE" then this.update_full = true elseif ( this.label == "raid" or this.label == "party" ) and event == "RAID_ROSTER_UPDATE" then -- Note: Smart GUID-based updates are handled in raid.lua OnUpdate -- after frame IDs are reassigned. We don't set update_full here anymore -- for raid frames to avoid the freeze. if this.label == "party" then -- Party frames still need the old logic (no smart tracking yet) this.update_full = true end -- Raid frames: update_full is set by raid.lua GUID tracker elseif this.label == "pet" and event == "UNIT_PET" then this.update_full = true elseif this.label == "player" and (event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_EQUIPMENT_CHANGED") then this.update_aura = true elseif this.label == "pet" and event == "UNIT_HAPPINESS" then this.update_full = true -- UNIT_XXX Events elseif arg1 and (arg1 == this.label .. this.id or (UnitGUID and arg1 == UnitGUID(this.label .. this.id))) then if event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then this.update_portrait = true elseif event == "UNIT_AURA" then this.update_aura = true elseif event == "UNIT_FACTION" then this.update_pvp = true elseif event == "UNIT_COMBAT" then CombatFeedback_OnCombatEvent(arg2, arg3, arg4, arg5) else this.update_base = true end end end -- Local reference for performance local _GetTime = GetTime -- Global cached time for libpredict and other functions pfUI.uf.now = 0 -- ============================================================================ -- OnUpdate - eventless per-frame work (range check, aggro glow) and draining -- the event-set update flags. Frames refresh on events only; no polling. -- ============================================================================ function pfUI.uf.OnUpdate() local now = _GetTime() pfUI.uf.now = now -- update combat feedback (no throttle - needs immediate feedback) if this.feedbackText then CombatFeedback_OnUpdate(arg1) end -- Throttle raid/party frames for performance if this.label == "raid" or this.label == "party" then if (this.throttleTick or 0) > now then return end this.throttleTick = now + 0.1 -- Default: 10 FPS end -- ============================================================================ -- EVENTLESS ACTIONS (Range Check, Online/Offline, Aggro) - MUST RUN ALWAYS -- These run on their own timer, independent of event-based updates -- ============================================================================ if this.label then -- Combat/Aggro Indicators (throttled to 0.2s) if not this.lastCombatCheck then this.lastCombatCheck = now + 0.2 end if this.lastCombatCheck < now then this.lastCombatCheck = now + 0.2 if this.config and this.config.squareaggro == "1" and pfUI.api.UnitHasAggro(this.label .. this.id) > 0 then this.combat.tex:SetTexture(1,.2,0) this.combat:Show() elseif this.config and this.config.squarecombat == "1" and UnitAffectingCombat(this.label .. this.id) then this.combat.tex:SetTexture(1,1,.2) this.combat:Show() elseif this.combat then this.combat:Hide() end end -- Range Check / Online-Offline State (throttled) -- Validate tick value - it should be an interval (e.g. 0.5), not a timestamp local tickInterval = this.tick if tickInterval and tickInterval > 10 then -- tick is a timestamp, not an interval - ignore it tickInterval = nil end -- Reset lastTick if it's invalid (much larger than now, e.g. from corrupted state) if this.lastTick and this.lastTick > now + 10 then this.lastTick = nil end if not this.lastTick then this.lastTick = now + (tickInterval or .5) end if this.lastTick < now then local unitstr = this.label .. this.id this.lastTick = now + (tickInterval or .5) -- target target has a huge delay, make sure to not tick during range checks if this.label == "targettarget" or this.label == "targettargettarget" then local name = UnitName(this.label) if name ~= this.namebuf1 then this.namebuf1 = name elseif name ~= this.namebuf2 then this.namebuf2 = name else pfUI.uf:RefreshUnitState(this) pfUI.uf:RefreshIndicators(this) end else pfUI.uf:RefreshUnitState(this) pfUI.uf:RefreshIndicators(this) end if this.config and this.config.glowaggro == "1" and pfUI.api.UnitHasAggro(unitstr) > 0 then this.glow:SetBackdropBorderColor(1,.2,0) this.glow:Show() elseif this.config and this.config.glowcombat == "1" and UnitAffectingCombat(unitstr) then this.glow:SetBackdropBorderColor(1,1,.2) this.glow:Show() elseif this.glow then this.glow:Hide() end -- update everything on eventless frames (targettarget, etc) if this.tick then pfUI.uf:RefreshUnit(this, "all") end end -- Heal Prediction (throttled to 0.1s for responsiveness) if libpredict and this.incHeal then if not this.lastHealTick then this.lastHealTick = now end if this.lastHealTick < now then this.lastHealTick = now + 0.1 local unit = this.label .. this.id local heal = libpredict:UnitGetIncomingHeals(unit) local health, maxHealth = UnitHealth(unit), UnitHealthMax(unit) if heal - health - maxHealth ~= this.predictstate then local overhealperc = tonumber(this.config.overhealperc) this.predictstate = heal - health - maxHealth if heal > 0 and (health < maxHealth or overhealperc > 0 ) then local width = this.config.width local height = this.config.height if this.config.verticalbar == "0" then local healthWidth = width * (health / maxHealth) local incWidth = width * heal / maxHealth if healthWidth + incWidth > width * (1+(overhealperc/100)) then incWidth = width * (1+overhealperc/100) - healthWidth end if this.config.invert_healthbar == "1" then this.incHeal:SetWidth(incWidth) else this.incHeal:SetWidth(incWidth + healthWidth) end else local healthHeight = height * (health / maxHealth) local incHeight = height * heal / maxHealth if healthHeight + incHeight > height * (1+(overhealperc/100)) then incHeight = height * (1+overhealperc/100) - healthHeight end if this.config.invert_healthbar == "1" then this.incHeal:SetHeight(incHeight) else this.incHeal:SetHeight(incHeight + healthHeight) end end this.incHeal:Show() else this.incHeal:Hide() end end -- update ressurections local ress = libpredict:UnitHasIncomingResurrection(unit) this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit)) end end end -- ============================================================================ -- EVENT-BASED UPDATES (Health, Mana, Auras, etc.) -- ============================================================================ -- process indicator update events if this.update_indicators then pfUI.uf:RefreshIndicators(this) this.update_indicators = nil end -- process all queued unit events if this.update_full then -- process full updates pfUI.uf:RefreshUnit(this, "all") -- clear update caches this.update_full = nil this.update_base = nil this.update_aura = nil this.update_portrait = nil this.update_pvp = nil else -- process individual events if this.update_aura then pfUI.uf:RefreshUnit(this, "aura") this.update_aura = nil this.update_base = true end if this.update_portrait then pfUI.uf:RefreshUnit(this, "portrait") this.update_portrait = nil this.update_base = true end if this.update_pvp then pfUI.uf:RefreshUnit(this, "pvp") this.update_pvp = nil end if this.update_base then pfUI.uf:RefreshUnit(this, "base") this.update_base = nil end end if not this.label or this.label == "" then return end -- update portrait on first visible frame if this.portrait and this.portrait.model and this.portrait.model.update then this.portrait.model.lastUnit = UnitName(this.portrait.model.update) this.portrait.model:SetUnit(this.portrait.model.update) this.portrait.model:SetCamera(0) this.portrait.model.update = nil end end function pfUI.uf:EnableEvents() local f = self f:RegisterEvent("PLAYER_ENTERING_WORLD") f:RegisterEvent("PLAYER_LOGOUT") f:RegisterEvent("UNIT_DISPLAYPOWER") f:RegisterEvent("UNIT_HEALTH") f:RegisterEvent("UNIT_MAXHEALTH") f:RegisterEvent("UNIT_MANA") f:RegisterEvent("UNIT_MAXMANA") f:RegisterEvent("UNIT_RAGE") f:RegisterEvent("UNIT_MAXRAGE") f:RegisterEvent("UNIT_ENERGY") f:RegisterEvent("UNIT_MAXENERGY") f:RegisterEvent("UNIT_FOCUS") f:RegisterEvent("UNIT_PORTRAIT_UPDATE") f:RegisterEvent("UNIT_MODEL_CHANGED") f:RegisterEvent("UNIT_FACTION") f:RegisterEvent("UNIT_AURA") -- frame=buff, frame=debuff f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs) f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon f:RegisterEvent("PARTY_LEADER_CHANGED") -- frame=leaderIcon f:RegisterEvent("RAID_ROSTER_UPDATE") -- label=raidIcon f:RegisterEvent("PLAYER_UPDATE_RESTING") -- label=restIcon f:RegisterEvent("PLAYER_TARGET_CHANGED") -- label=target f:RegisterEvent("PARTY_LOOT_METHOD_CHANGED") -- frame=lootIcon f:RegisterEvent("RAID_TARGET_UPDATE") -- frame=raidIcon f:RegisterEvent("UNIT_PET") f:RegisterEvent("UNIT_HAPPINESS") f:RegisterForClicks('LeftButtonUp', 'RightButtonUp', 'MiddleButtonUp', 'Button4Up', 'Button5Up') end -- ============================================================ -- Clique compatibility -- Clique's pfUI plugin replaces pfUI.uf.ClickAction and expects an -- OnClick script to call it (with `this` = frame, `arg1` = button). The -- secure attribute path replaced that dispatch, so when Clique is loaded -- EnableScripts routes clicks through this legacy Lua path instead. The -- secure path is skipped there because type1="target" switches the target -- before Clique can cast. -- ============================================================ function pfUI.uf.OnClick() if not this.label and this.unitname then TargetByName(this.unitname, true) else pfUI.uf:ClickAction(arg1) end end function pfUI.uf:ClickAction(button) local label = this.label or "" local id = this.id or "" local unitstr = label .. id if SpellIsTargeting() and button == "RightButton" then SpellStopTargeting() return end if SpellIsTargeting() and button == "LeftButton" then SpellTargetUnit(unitstr) elseif CursorHasItem() then DropItemOnUnit(unitstr) end -- right-click opens the standard unit menu (ClassicAPI resolves the type) if button == "RightButton" then ClassicAPI_ToggleUnitMenu(unitstr) return end -- drop food on petframe if label == "pet" and CursorHasItem() then if UnitClassBase("player") == "HUNTER" then DropItemOnUnit("pet") return end end -- default click TargetUnit(unitstr) end function pfUI.uf:EnableScripts() local f = self if IsAddOnLoaded("Clique") then f:SetScript("OnClick", pfUI.uf.OnClick) else f:SetAttribute("type1", "target") f:SetAttribute("type2", "menu") f:EnableClickCast() end f:SetScript("OnShow", pfUI.uf.OnShow) f:SetScript("OnEvent", pfUI.uf.OnEvent) f:SetScript("OnUpdate", pfUI.uf.OnUpdate) -- add frame to visibility refresh handler visibilityscan.frames[f] = true end function pfUI.uf:CreateUnitFrame(unit, id, config, tick) local fname = (( unit == "Party" ) and "Group" or (unit or "")) .. (id or "") local unit = strlower(unit or "") local id = strlower(id or "") -- fake party0 units as self if unit == "party" and id == "0" then unit, id = "player", "" end if unit == "partypet" and id == "0" then unit, id = "pet", "" end if unit == "pettarget" and id == "0" then unit, id = "pettarget", "" end if unit == "party0target" then unit, id = "target", "" end local f = CreateFrame("Button", "pf" .. fname, UIParent) -- add unitframe functions f.UpdateFrameSize = pfUI.uf.UpdateFrameSize f.UpdateVisibility = pfUI.uf.UpdateVisibility f.UpdateConfig = pfUI.uf.UpdateConfig f.EnableScripts = pfUI.uf.EnableScripts f.EnableEvents = pfUI.uf.EnableEvents f.EnableClickCast = pfUI.uf.EnableClickCast f.GetColor = pfUI.uf.GetColor -- cache values to the frame f.label = strlower(unit) f.fname = fname f.id = id f.config = config or pfUI_config.unitframes.fallback f.tick = tick -- disable events for unknown unitstrings if not pfValidUnits[strlower(unit) .. id] then f.unitname = unit f.label, f.id = "", "" f.RegisterEvent = function() return end end CreateBackdropShadow(f) f.hp = CreateFrame("Frame",nil, f) f.hp.bar = CreateStatusBar(nil, f.hp) f.power = CreateFrame("Frame",nil, f) f.power.bar = CreateStatusBar(nil, f.power) -- Druid secondary mana bar: shows base mana (read via ClassicAPI -- UnitPower(unit, Enum.PowerType.Mana), which works regardless of the active -- power) while shapeshifted into a form that uses energy/rage. Player + -- target only; -- styled/positioned in UpdateConfig, driven in UpdateDruidMana. if C.unitframes.druidmanabar == "1" and (f.label == "player" or f.label == "target") then f.druidmana = CreateFrame("StatusBar", "pfDruidMana_" .. f.label .. f.id, f) f.druidmana.text = f.druidmana:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") f.druidmana.text:SetPoint("CENTER", f.druidmana, "CENTER", 0, 0) f.druidmana.text:SetJustifyH("CENTER") f.druidmana:Hide() end f.glow = CreateFrame("Frame", nil, f) f.combat = CreateFrame("Frame", nil, f.hp.bar) f.combat.tex = f.combat:CreateTexture(nil, "OVERLAY") f.combat.tex:SetAllPoints() f.texts = CreateFrame("Frame", nil, f) f.texts:SetFrameLevel(16) f.texts:SetAllPoints() f.hpLeftText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.hpRightText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.hpCenterText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.powerLeftText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.powerRightText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.powerCenterText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.incHeal = CreateFrame("Frame", nil, f.hp) -- Texture lives on hp.bar for correct draw order: -- hp.bar.bg (BACKGROUND) < incHeal (BORDER) < hp.bar.bar (NORMAL) f.incHeal.texture = f.hp.bar:CreateTexture(nil, "BORDER") f.incHeal.texture:SetAllPoints(f.incHeal) -- Override Show/Hide since texture lives on hp.bar, not incHeal f.incHeal._Show = f.incHeal.Show f.incHeal._Hide = f.incHeal.Hide f.incHeal.Show = function(self) self:_Show() self.texture:Show() end f.incHeal.Hide = function(self) self:_Hide() self.texture:Hide() end f.incHeal.texture:Hide() f.ressIcon = CreateFrame("Frame", nil, f.hp.bar) f.ressIcon.texture = f.ressIcon:CreateTexture(nil,"BACKGROUND") f.leaderIcon = CreateFrame("Frame", nil, f.hp.bar) f.leaderIcon.texture = f.leaderIcon:CreateTexture(nil,"BACKGROUND") f.lootIcon = CreateFrame("Frame",nil, f.hp.bar) f.lootIcon.texture = f.lootIcon:CreateTexture(nil,"BACKGROUND") f.pvpIcon = CreateFrame("Frame", nil, f.hp.bar) f.pvpIcon.texture = f.pvpIcon:CreateTexture(nil,"BACKGROUND") f.raidIcon = CreateFrame("Frame", nil, f.hp.bar) f.raidIcon.texture = f.raidIcon:CreateTexture(nil,"ARTWORK") f.restIcon = CreateFrame("Frame", nil, f.hp.bar) f.restIcon.texture = f.restIcon:CreateTexture(nil, "BACKGROUND") f.happinessIcon = CreateFrame("Frame", nil, f.hp.bar) f.happinessIcon.texture = f.happinessIcon:CreateTexture(nil, "BACKGROUND") f.portrait = CreateFrame("Frame", "pfPortrait" .. f.label .. f.id, f) f.portrait.tex = f.portrait:CreateTexture("pfPortraitTexture" .. f.label .. f.id, "OVERLAY") f.portrait.model = CreateFrame("PlayerModel", "pfPortraitModel" .. f.label .. f.id, f.portrait) f.portrait.model.next = CreateFrame("PlayerModel", nil, nil) f.feedbackText = f:CreateFontString("pfHitIndicator" .. f.label .. f.id, "OVERLAY", "NumberFontNormalHuge") if f.label == "raid" and mod(f.id, 5) == 1 then local group = math.ceil(f.id/5) f.group = f.group or f.hp.bar:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall") f.group:SetFont(pfUI.font_unit, 8, "OUTLINE") f.group:SetTextColor(1,1,1,.8) f.group:SetHeight(16) f.group:SetText(T["Group"] .. " " .. group) f.group:Hide() end f:Hide() f:UpdateConfig() f:UpdateFrameSize() f:EnableScripts() f:EnableEvents() if f.config.visible == "1" then pfUI.uf:RefreshUnit(f, "all") f:EnableScripts() f:EnableEvents() f:UpdateFrameSize() else f:UnregisterAllEvents() f:Hide() end if f.label ~= "" then f:SetAttribute("unit", f.label .. f.id) end -- register frame for clique _G.ClickCastFrames = ClickCastFrames or {} ClickCastFrames[f] = true table.insert(pfUI.uf.frames, f) return f end function pfUI.uf:RefreshUnitState(unit) local alpha = unit.alpha_visible local unlock = pfUI.unlock and pfUI.unlock:IsShown() or nil if not UnitIsConnected(unit.label .. unit.id) and not unlock then -- offline alpha = unit.alpha_offline unit.hp.bar:SetMinMaxValues(0, 100, true) unit.power.bar:SetMinMaxValues(0, 100, true) unit.hp.bar:SetValue(0) unit.power.bar:SetValue(0) elseif unit.config.faderange == "1" and not pfUI.api.UnitInRange(unit.label .. unit.id, 4) and not unlock then alpha = unit.alpha_outrange end -- skip if alpha is already correct if floor(unit:GetAlpha()*10+.5) == floor(alpha*10+.5) then return end -- set unitframe alpha unit:SetAlpha(alpha) -- refresh portrait alpha if unit.config.portrait == "bar" then unit.portrait:SetAlpha(pfUI_config.unitframes.portraitalpha) end -- refresh debuff indicator alpha local disptype = unit.config.debuff_indicator local indicator = unit.hp.bar.debuffindicators if indicator then indicator:SetAlpha(0) if ( disptype == "4" or disptype == "3" ) then indicator:SetAlpha(1) elseif disptype == "2" then indicator:SetAlpha(.4) elseif disptype == "1" then indicator:SetAlpha(.2) end end end function pfUI.uf:RefreshIndicators(unit) if not unit.label or not unit.id then return end local unitstr = unit.label .. unit.id if unit.leaderIcon then -- Leader Icon unit.leaderIcon:SetShown(unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup()) end if unit.lootIcon then -- Loot Icon if unit.config.looticon == "0" then unit.lootIcon:Hide() else -- no third return value here.. but leaving this as a hint local method, group, raid = GetLootMethod() local name = group and UnitName(group == 0 and "player" or "party"..group) or raid and UnitName("raid"..raid) or nil unit.lootIcon:SetShown(name and name == UnitName(unitstr)) end end if unit.pvpIcon then -- PvP Icon unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr)) end if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting()) end if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon local pclass = UnitClassBase("player") if unit.config.happinessicon == "0" or pclass ~= "HUNTER" then unit.happinessIcon:Hide() else if UnitIsVisible("pet") then local happiness = GetPetHappiness() if happiness == 1 then unit.happinessIcon.texture:SetTexture(pfUI.media["img:sad"..unit.config.happinessicon]) unit.happinessIcon.texture:SetVertexColor(1, 0, 0, 1) elseif happiness == 2 then unit.happinessIcon.texture:SetTexture(pfUI.media["img:neutral"..unit.config.happinessicon]) unit.happinessIcon.texture:SetVertexColor(1, 1, 0, 1) else unit.happinessIcon.texture:SetTexture(pfUI.media["img:happy"..unit.config.happinessicon]) unit.happinessIcon.texture:SetVertexColor(0, 1, 0, 1) end unit.happinessIcon:Show() else unit.happinessIcon:Hide() end end end if unit.raidIcon then -- Raid Icon local raidIcon = UnitName(unitstr) and GetRaidTargetIndex(unitstr) if unit.config.raidicon == "1" and raidIcon then SetRaidTargetIconTexture(unit.raidIcon.texture, raidIcon) unit.raidIcon:Show() else unit.raidIcon:Hide() end end end -- Druid secondary mana bar update. Reads base mana with ClassicAPI -- UnitPower/UnitPowerMax(unit, Enum.PowerType.Mana), which return the mana -- slot regardless of the unit's active power -- so it works while shapeshifted, -- with no nampower/GetUnitField dependency. Shown only while off mana -- (Cat=energy, Bear=rage); non-player frames only for druid units. function pfUI.uf:UpdateDruidMana(unit) local bar = unit.druidmana local unitstr = unit.label .. unit.id if not UnitExists(unitstr) then bar:Hide() return end if unit.label ~= "player" then local cls = UnitClassBase(unitstr) if cls ~= "DRUID" then bar:Hide() return end end if UnitPowerType(unitstr) == Enum.PowerType.Mana then bar:Hide() return end local mana, maxmana = UnitPower(unitstr, Enum.PowerType.Mana), UnitPowerMax(unitstr, Enum.PowerType.Mana) if not maxmana or maxmana == 0 then bar:Hide() return end bar:SetMinMaxValues(0, maxmana) bar:SetValue(mana) if C.unitframes.druidmanatext == "1" then bar.text:SetText(pfUI.api.Abbreviate(mana) .. "/" .. pfUI.api.Abbreviate(maxmana)) else bar.text:SetText("") end bar:Show() end function pfUI.uf:RefreshUnit(unit, component) -- break early on misconfigured UF's if not unit.label then return end if not unit.hp then return end if not unit.power then return end if not unit.id then unit.id = "" end component = component or "" -- don't update scanner activity if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then if pfScanActive == true then return end end -- hide unused and invalid frames unit:UpdateVisibility() -- return on invisible unit frames if not unit:IsShown() and not unit.visible then return end -- create required fields local unitstr = unit.label..unit.id local rawborder, default_border = GetBorderSize("unitframes") -- save current values unit.namecache = UnitName(unitstr) -- buffs if unit.buffs and ( component == "all" or component == "aura" ) then -- one GetAuraSlots enumeration per refresh, then a positional read per -- slot id: allocates nothing and never re-walks the aura array per icon ScanAuraSlots(unitstr, "HELPFUL", auraSlots, unit.config.bufflimit) for i=1, unit.config.bufflimit do if not unit.buffs[i] then break end local name, icon, count, _, duration, expirationTime, _, _, _, spellId = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if name then unit.buffs[i].texture:SetTexture(icon) unit.buffs[i]:Show() if count > 1 then unit.buffs[i].stacks:SetText(count) else unit.buffs[i].stacks:SetText("") end if expirationTime > 0 then -- pfUI's cooldown text falls into a 2^32 wraparound branch when start > GetTime(), -- which happens for talent-extended buffs where the real duration exceeds the -- Spell.dbc base. Anchor start to now in that case to keep the remaining math sane. local now = GetTime() local start = expirationTime - duration local dur = duration if start > now or dur <= 0 then start, dur = now, expirationTime - now end if dur > 0 then CooldownFrame_SetTimer(unit.buffs[i].cd, start, dur, 1) else CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0) end elseif duration > 0 then local guid = UnitGUID(unitstr) local guidStarts = guid and pfUI.uf.aura_starts[guid] local start = guidStarts and guidStarts[spellId] if start then CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1) else CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0) end else CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0) end else unit.buffs[i]:Hide() end end end -- debuffs if unit.debuffs and ( component == "all" or component == "aura" ) then local texture, stacks, dtype local perrow = unit.config.debuffperrow local bperrow = unit.config.buffperrow local selfdebuff = unit.config.selfdebuff local invert_h, invert_v, af if unit.config.debuffs == "TOPLEFT" then invert_h = 1 invert_v = 1 af = "BOTTOMLEFT" elseif unit.config.debuffs == "BOTTOMLEFT" then invert_h = -1 invert_v = 1 af = "TOPLEFT" elseif unit.config.debuffs == "TOPRIGHT" then invert_h = 1 invert_v = -1 af = "BOTTOMRIGHT" elseif unit.config.debuffs == "BOTTOMRIGHT" then invert_h = -1 invert_v = -1 af = "TOPRIGHT" end local buffrow, reposition = 0, ( component == "all" and true or nil ) if unit.config.buffs == unit.config.debuffs then if unit.buffs[0*bperrow+1] and unit.buffs[0*bperrow+1]:IsShown() then buffrow = buffrow + 1 end if unit.buffs[1*bperrow+1] and unit.buffs[1*bperrow+1]:IsShown() then buffrow = buffrow + 1 end if unit.buffs[2*bperrow+1] and unit.buffs[2*bperrow+1]:IsShown() then buffrow = buffrow + 1 end if unit.buffs[3*bperrow+1] and unit.buffs[3*bperrow+1]:IsShown() then buffrow = buffrow + 1 end end if buffrow ~= unit.lastbuffrow then unit.lastbuffrow = buffrow reposition = true end -- selfdebuff narrows to player-cast harmful auras via the PLAYER filter. -- Player-frame debuffs aren't gated on it (it'd hide most party-applied -- effects on you). One GetAuraSlots enumeration per refresh; the i-th slot -- is the i-th aura of the filtered list, so `i` stays the tooltip index. local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL" ScanAuraSlots(unitstr, filter, auraSlots, unit.config.debufflimit) for i=1, unit.config.debufflimit do if not unit.debuffs[i] then break end local row = floor((i-1) / unit.config.debuffperrow) if reposition then local anchor = unit.config.portraitheight ~= "-1" and unit.hp or unit if anchor == unit.hp and (unit.config.debuffs == "BOTTOMLEFT" or unit.config.debuffs == "BOTTOMRIGHT") then anchor = unit.power end local multiply = C.appearance.border.force_blizz == "1" and 1 or 2 unit.debuffs[i]:SetPoint(af, anchor, unit.config.debuffs, invert_v * (i-1-row*perrow)*(multiply*default_border + unit.config.debuffsize + 1), invert_h * ((row+buffrow)*(multiply*default_border + unit.config.debuffsize + 1) + (multiply*default_border + 1))) end -- positional read by slot id allocates nothing local name, icon, count, dispelType, duration, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if name then texture, stacks, dtype = icon, count, dispelType else texture, stacks, dtype = nil, 0, nil end unit.debuffs[i].texture:SetTexture(texture) local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(dtype or "") unit.debuffs[i].backdrop:SetBackdropBorderColor(dispelColor:GetRGBA()) if texture then unit.debuffs[i]:Show() if expirationTime > 0 then -- Cap start to now so talent-extended debuffs (expirationTime past -- the dbc base duration) don't push start into the future and trip -- CooldownFrame_SetTimer's 2^32-wraparound branch. local now = GetTime() local start = expirationTime - duration local dur = duration if start > now or dur <= 0 then start, dur = now, expirationTime - now end if dur > 0 then CooldownFrame_SetTimer(unit.debuffs[i].cd, start, dur, 1) else CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0) end else CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0) end if stacks > 1 then unit.debuffs[i].stacks:SetText(stacks) else unit.debuffs[i].stacks:SetText("") end else unit.debuffs[i]:Hide() end end end -- indicators if component == "all" or component == "aura" then if not unit.dispellable and unit.config.debuff_indicator ~= "0" then unit.dispellable = pfUI.uf:SetupDebuffFilter((unit.config.debuff_ind_class == "0" and true or nil)) elseif not unit.dispellable then unit.dispellable = {} end if table.getn(unit.dispellable) > 0 then unit.hp.bar.debuffindicators = unit.hp.bar.debuffindicators or CreateFrame("Frame", nil, unit.hp.bar) -- 0 = OFF, 1 = Legacy, 2 = Glow, 3 = Square, 4 = Icons local disptype = unit.config.debuff_indicator local indicator = unit.hp.bar.debuffindicators local indipos = unit.config.debuff_ind_pos local count = 0 local size if disptype == "4" or disptype == "3" then size = unit.hp.bar:GetHeight() * tonumber(unit.config.debuff_ind_size) if size ~= indicator.size or disptype ~= indicator.disp or indipos ~= indicator.ipos then indicator:ClearAllPoints() indicator:SetPoint(indipos, 0, 0) indicator:SetSize(size, size) indicator.size = size indicator.disp = disptype indicator.ipos = indipos end elseif disptype == "2" or disptype == "1" then size = "FULL" if size ~= indicator.size or disptype ~= indicator.disp or indipos ~= indicator.ipos then indicator:ClearAllPoints() indicator:SetAllPoints(unit.hp.bar) indicator.size = size indicator.disp = disptype indicator.ipos = indipos end end -- Scan the 16 harmful slots once into a reusable set, instead of -- re-scanning all 16 for every dispellable type below. Reused across -- frames (populated and consumed within this single RefreshUnit call). local present = pfUI.uf.dispelPresent or {} pfUI.uf.dispelPresent = present for k in pairs(present) do present[k] = nil end local n = ScanAuraSlots(unitstr, "HARMFUL", auraSlots) for i=1,n do local name, _, _, dispelType = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if name and dispelType and dispelType ~= "" then present[dispelType] = true end end for _, debuff in pairs(unit.dispellable) do indicator[debuff] = indicator[debuff] or CreateFrame("Frame", nil, indicator) indicator[debuff]:SetParent(indicator) indicator[debuff].tex = indicator[debuff].tex or indicator[debuff]:CreateTexture(nil) indicator[debuff].tex:SetAllPoints(indicator[debuff]) if indicator.size ~= indicator[debuff].size or disptype ~= indicator[debuff].disp then local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(debuff) if disptype == "4" then indicator[debuff].tex:SetTexture(pfUI.media["img:"..debuff]) indicator[debuff].tex:SetVertexColor(dispelColor:GetRGBA()) indicator[debuff].tex:Show() indicator[debuff]:ClearAllPoints() indicator[debuff]:SetSize(size, size) indicator[debuff]:SetBackdrop(nil) elseif disptype == "3" then indicator[debuff].tex:SetTexture(dispelColor:GetRGBA()) indicator[debuff].tex:SetVertexColor(1,1,1,1) indicator[debuff].tex:Show() indicator[debuff]:ClearAllPoints() indicator[debuff]:SetSize(size, size) indicator[debuff]:SetBackdrop(nil) elseif disptype == "2" then indicator[debuff].tex:Hide() indicator[debuff]:SetAllPoints(unit.hp.bar) indicator[debuff]:SetBackdrop(glow) indicator[debuff]:SetBackdropBorderColor(dispelColor:GetRGBA()) elseif disptype == "1" then indicator[debuff].tex:SetTexture(dispelColor:GetRGBA()) indicator[debuff].tex:SetVertexColor(1,1,1,1) indicator[debuff].tex:Show() indicator[debuff]:SetAllPoints(unit.hp.bar) indicator[debuff]:SetBackdrop(nil) end indicator[debuff].size = indicator.size indicator[debuff].disp = indicator.disp end indicator[debuff].visible = present[debuff] if indicator[debuff].visible then indicator[debuff]:Show() indicator:Show() indicator:SetAlpha(0) if disptype == "4" or disptype == "3" then indicator:SetAlpha(1) elseif disptype == "2" then indicator:SetAlpha(.4) elseif disptype == "1" then indicator:SetAlpha(.2) end if disptype == "4" or disptype == "3" then indicator[debuff]:SetPoint("LEFT", indicator, "LEFT", count*(size+1), 0) count = count + 1 end else indicator[debuff]:Hide() end end if disptype == "4" or disptype == "3" then indicator:SetWidth(count*(size+1)) end elseif unit.hp.bar.debuffindicators then unit.hp.bar.debuffindicators:Hide() end if not unit.indicators and unit.config.buff_indicator == "1" then unit.indicators = pfUI.uf:SetupBuffIndicators(unit.config) elseif not unit.indicators then unit.indicators = {} end if not unit.indicator_custom and unit.config.buff_indicator == "1" then unit.indicator_custom = {} for k, v in pairs({strsplit("#", unit.config.custom_indicator)}) do unit.indicator_custom[k] = v:lower() end elseif not unit.indicator_custom then unit.indicator_custom = {} end local pos = 1 if table.getn(unit.indicators) > 0 then local n = ScanAuraSlots(unitstr, "HELPFUL", auraSlots) for i=1,n do local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if not name then break end local texLower = icon:lower() local nameLower = name:lower() local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil for _, filter in pairs(unit.indicators) do if filter.icon == texLower and filter.name == nameLower then if filter.predict then local start, duration, prediction = libpredict:GetHotDuration(unitstr, filter.predict) pfUI.uf:AddIcon(unit, pos, icon, timeleft or prediction, count, tonumber(start), tonumber(duration)) else pfUI.uf:AddIcon(unit, pos, icon, timeleft, count) end pos = pos + 1 break end end end end if table.getn(unit.indicator_custom) > 0 then local n = ScanAuraSlots(unitstr, "HELPFUL", auraSlots) for i=1,n do local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if not name then break end local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil local lowerName = name:lower() for _, filter in pairs(unit.indicator_custom) do if filter == lowerName then pfUI.uf:AddIcon(unit, pos, icon, timeleft, count) pos = pos + 1 break end end end local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL" n = ScanAuraSlots(unitstr, debuffFilter, auraSlots) for i=1,n do -- scan for custom debuffs local name, icon, count, _, _, expirationTime = C_UnitAuras.UnitAuraBySlot(unitstr, auraSlots[i]) if name then local timeleft = expirationTime > 0 and (expirationTime - GetTime()) or nil for _, filter in pairs(unit.indicator_custom) do if filter == name:lower() then pfUI.uf:AddIcon(unit, pos, icon, timeleft, count) pos = pos + 1 break end end end end end -- hide unused icon slots for pos=pos, 6 do pfUI.uf:HideIcon(unit, pos) end end -- portrait if unit.portrait and ( component == "all" or component == "portrait" ) then if C.unitframes.always2dportrait == "1" then unit.portrait.tex:Show() unit.portrait.model:Hide() SetPortraitTexture(unit.portrait.tex, unitstr) else if not UnitIsVisible(unitstr) or not UnitIsConnected(unitstr) then if unit.config.portrait == "bar" then unit.portrait.tex:Hide() unit.portrait.model:Hide() elseif C.unitframes.portraittexture == "1" then unit.portrait.tex:Show() unit.portrait.model:Hide() SetPortraitTexture(unit.portrait.tex, unitstr) else unit.portrait.tex:Hide() unit.portrait.model:Show() unit.portrait.model:SetModelScale(4.25) unit.portrait.model:SetPosition(0, 0, -1) unit.portrait.model:SetModel("Interface\\Buttons\\talktomequestionmark.mdx") end else if unit.config.portrait == "bar" then unit.portrait:SetAlpha(C.unitframes.portraitalpha) else unit.portrait:SetAlpha(1) end unit.portrait.tex:Hide() unit.portrait.model:Show() if component == "portrait" then -- regular portrait update after event unit.portrait.model.update = unitstr else -- detect portrait change without events unit.portrait.model.next:SetUnit(unitstr) if unit.portrait.model.lastUnit ~= UnitName(unitstr) or unit.portrait.model:GetModel() ~= unit.portrait.model.next:GetModel() then unit.portrait.model.update = unitstr end end end end end -- base frame if component == "all" or component == "base" then -- Unit HP/MP with Nampower Integration local hp, hpmax, power, powermax, powerType = pfUI.api.GetUnitStats(unitstr) -- Store original values for color calculations (before invert_healthbar modifies hp) local hp_orig, hpmax_orig = hp, hpmax if unit.config.invert_healthbar == "1" then hp = hpmax - hp end unit.hp.bar:SetMinMaxValues(0, hpmax, true) unit.hp.bar:SetValue(hp) unit.power.bar:SetMinMaxValues(0, powermax, true) unit.power.bar:SetValue(power) -- Hide power bar text for NPCs without real power (power == 0) local isNPC = not UnitIsPlayer(unitstr) and not UnitPlayerControlled(unitstr) local npcNoPower = isNPC and (not power or power == 0) -- set healthbar color local custom_active = nil local customfullhp = unit.config.defcolor == "0" and unit.config.customfullhp or C.unitframes.customfullhp local customcolor = unit.config.defcolor == "0" and unit.config.customcolor or C.unitframes.customcolor local customfade = unit.config.defcolor == "0" and unit.config.customfade or C.unitframes.customfade local custom = unit.config.defcolor == "0" and unit.config.custom or C.unitframes.custom local r, g, b, a = .2, .2, .2, 1 -- O(1) optimization: use cached hp/hpmax instead of UnitHealth()/UnitHealthMax() API calls if customfullhp == "1" and hp_orig == hpmax_orig then r, g, b, a = GetStringColor(customcolor) custom_active = true elseif custom == "0" then if UnitIsPlayer(unitstr) then _, r, g, b = GetUnitColor(unitstr) elseif unit.label == "pet" then local happiness = GetPetHappiness() if happiness == 1 then r, g, b = 1, 0, 0 elseif happiness == 2 then r, g, b = 1, 1, 0 else r, g, b = 0, 1, 0 end else local color = UnitReactionColor[UnitReaction(unitstr, "player")] if color then r, g, b = color.r, color.g, color.b end end elseif custom == "1" then r, g, b, a = GetStringColor(customcolor) custom_active = true elseif custom == "2" then -- O(1) optimization: use cached hp/hpmax instead of UnitHealth()/UnitHealthMax() API calls if hpmax_orig > 0 then r, g, b = GetColorGradient(hp_orig / hpmax_orig) else r, g, b = 0, 0, 0 end end if C.unitframes.pastel == "1" and not custom_active then r, g, b = (r + .5) * .5, (g + .5) * .5, (b + .5) * .5 end if customfade == "1" then -- fade custom color into default color -- O(1) optimization: use cached hp/hpmax instead of UnitHealth()/UnitHealthMax() API calls local perc = hpmax_orig > 0 and (hp_orig / hpmax_orig) or 0 local cr, cg, cb, ca = GetStringColor(customcolor) r = (cr*perc) + (r*(1-perc)) g = (cg*perc) + (g*(1-perc)) b = (cb*perc) + (b*(1-perc)) end unit.hp.bar:SetStatusBarColor(r, g, b, a) -- set powerbar color local mana = unit.config.defcolor == "0" and unit.config.manacolor or C.unitframes.manacolor local rage = unit.config.defcolor == "0" and unit.config.ragecolor or C.unitframes.ragecolor local energy = unit.config.defcolor == "0" and unit.config.energycolor or C.unitframes.energycolor local focus = unit.config.defcolor == "0" and unit.config.focuscolor or C.unitframes.focuscolor local r, g, b, a = .5, .5, .5, 1 local utype = UnitPowerType(unitstr) if utype == Enum.PowerType.Mana then r, g, b, a = GetStringColor(mana) elseif utype == Enum.PowerType.Rage then r, g, b, a = GetStringColor(rage) elseif utype == Enum.PowerType.Focus then r, g, b, a = GetStringColor(focus) elseif utype == Enum.PowerType.Energy then r, g, b, a = GetStringColor(energy) end unit.power.bar:SetStatusBarColor(r, g, b, a) if UnitName(unitstr) then unit.hpLeftText:SetText(pfUI.uf:GetStatusValue(unit, "hpleft")) unit.hpCenterText:SetText(pfUI.uf:GetStatusValue(unit, "hpcenter")) unit.hpRightText:SetText(pfUI.uf:GetStatusValue(unit, "hpright")) -- Hide power text for NPCs without a real power system local cfgLeft = unit.config.txtpowerleft local cfgCenter = unit.config.txtpowercenter local cfgRight = unit.config.txtpowerright if npcNoPower and cfgLeft and strfind(cfgLeft, "power") then unit.powerLeftText:SetText("") else unit.powerLeftText:SetText(pfUI.uf:GetStatusValue(unit, "powerleft")) end if npcNoPower and cfgCenter and strfind(cfgCenter, "power") then unit.powerCenterText:SetText("") else unit.powerCenterText:SetText(pfUI.uf:GetStatusValue(unit, "powercenter")) end if npcNoPower and cfgRight and strfind(cfgRight, "power") then unit.powerRightText:SetText("") else unit.powerRightText:SetText(pfUI.uf:GetStatusValue(unit, "powerright")) end if UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then unit.hp.bar:SetStatusBarColor(.5,.5,.5,.5) end end if unit.druidmana then pfUI.uf:UpdateDruidMana(unit) end pfUI.uf:RefreshUnitState(unit) end end local modifiers = { [""] = "", ["alt"] = "_alt", ["ctrl"] = "_ctrl", ["shift"] = "_shift", } function pfUI.uf:EnableClickCast() if self.config.clickcast ~= "1" then return end for bid = 1, 5 do -- LeftButton, RightButton, MiddleButton, Button4, Button5 for modifier, mconf in pairs(modifiers) do local bconf = bid == 1 and "" or bid local action = pfUI_config.unitframes["clickcast"..bconf..mconf] if action and action ~= "" then local prefix = modifier ~= "" and (modifier .. "-") or "" local low = action:lower() if low == "menu" then self:SetAttribute(prefix .. "type" .. bid, "menu") elseif low == "target" then self:SetAttribute(prefix .. "type" .. bid, "target") elseif low == "focus" then self:SetAttribute(prefix .. "type" .. bid, "focus") elseif low:find("^macro:") then self:SetAttribute(prefix .. "type" .. bid, "macro") self:SetAttribute(prefix .. "macro" .. bid, action:sub(7):gsub("^%s+", "")) elseif action:find("^/") then self:SetAttribute(prefix .. "type" .. bid, "macro") self:SetAttribute(prefix .. "macrotext" .. bid, action) else self:SetAttribute(prefix .. "type" .. bid, "spell") self:SetAttribute(prefix .. "spell" .. bid, action) end end end end end function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration) local showtime = frame.config.indicator_time == "1" and true or nil local showstacks = frame.config.indicator_stacks == "1" and true or nil local position = frame.config.indicator_pos or "TOPLEFT" local iconsize = tonumber(frame.config.indicator_size) local spacing = tonumber(frame.config.indicator_spacing) if not frame.hp then return end local frame = frame.hp.bar if pos > 6 or pos > ceil(frame:GetWidth() / iconsize) then return end frame.icon = frame.icon or CreateFrame("Frame", nil, frame) if not frame.icon[pos] then frame.icon[pos] = CreateFrame("Frame", nil, frame.icon) frame.icon[pos]:SetParent(frame) frame.icon[pos].tex = frame.icon[pos]:CreateTexture("OVERLAY") frame.icon[pos].tex:SetAllPoints(frame.icon[pos]) frame.icon[pos].tex:SetTexCoord(.08, .92, .08, .92) frame.icon[pos].stacks = frame.icon[pos]:CreateFontString(nil, "OVERLAY") frame.icon[pos].stacks:SetPoint("BOTTOMRIGHT", 0, 0) frame.icon[pos].stacks:SetJustifyH("RIGHT") frame.icon[pos].stacks:SetJustifyV("BOTTOM") -- Check if parent frame has cooldown animation enabled local parent_cooldown_anim = frame.config and tonumber(frame.config.cooldown_anim) or 1 if parent_cooldown_anim == 1 then frame.icon[pos].cd = CreateFrame("Model", nil, frame.icon[pos]) else frame.icon[pos].cd = CreateFrame("Frame", nil, frame.icon[pos]) frame.icon[pos].cd.AdvanceTime = DoNothing frame.icon[pos].cd.SetSequence = DoNothing frame.icon[pos].cd.SetSequenceTime = DoNothing end frame.icon[pos].cd.pfCooldownStyleAnimation = 0 frame.icon[pos].cd.pfCooldownType = "ALL" frame.icon[pos].cd:SetFrameLevel(48) end -- update icon configuration if frame.icon[pos].iconsize ~= iconsize or frame.icon[pos].spacing ~= spacing then frame.icon[pos]:SetSize(iconsize, iconsize) frame.icon[pos]:SetPoint("TOPLEFT", frame.icon, "TOPLEFT", (pos-1)*(iconsize + spacing), 0) frame.icon[pos].stacks:SetFont(pfUI.font_unit, math.max(iconsize/3, 10), "OUTLINE") frame.icon[pos].iconsize = iconsize frame.icon[pos].spacing = spacing end -- update icon if frame.icon[pos].icon ~= icon then frame.icon[pos].tex:SetTexture(icon) frame.icon[pos].icon = icon end -- show remaining time if config is set if showtime and start and duration and timeleft < 100 and iconsize > 9 then CooldownFrame_SetTimer(frame.icon[pos].cd, start, duration, 1) elseif showtime and timeleft and timeleft < 100 and iconsize > 9 then CooldownFrame_SetTimer(frame.icon[pos].cd, GetTime(), timeleft, 1) else CooldownFrame_SetTimer(frame.icon[pos].cd, GetTime(), 0, 1) end -- show stacks if config is set if showstacks and stacks and stacks > 1 and iconsize > 9 then frame.icon[pos].stacks:SetText(stacks) else frame.icon[pos].stacks:SetText("") end -- update parent icon size if frame.icon.iconsize ~= iconsize then frame.icon:SetHeight(iconsize) frame.icon.iconsize = iconsize end -- update parent position if frame.icon.position ~= position then frame.icon:ClearAllPoints() frame.icon:SetPoint(position, frame, position, 0, 0) frame.icon.position = position end frame.icon[pos]:Show() frame.icon:SetWidth((pos-1)*(iconsize+spacing)+iconsize) end function pfUI.uf:HideIcon(frame, pos) if not frame or not frame.hp or not frame.hp.bar then return end local frame = frame.hp.bar if frame.icon and frame.icon[pos] then frame.icon[pos]:Hide() end end function pfUI.uf:SetupDebuffFilter(allclasses) local myclass = UnitClassBase("player") local debuffs = {} if myclass == "PALADIN" or myclass == "PRIEST" or myclass == "WARLOCK" or allclasses then table.insert(debuffs, "Magic") end if myclass == "DRUID" or myclass == "PALADIN" or myclass == "SHAMAN" or allclasses then table.insert(debuffs, "Poison") end if myclass == "PRIEST" or myclass == "PALADIN" or myclass == "SHAMAN" or allclasses then table.insert(debuffs, "Disease") end if myclass == "DRUID" or myclass == "MAGE" or allclasses then table.insert(debuffs, "Curse") end return debuffs end function pfUI.uf:SetupBuffIndicators(config) local myclass = UnitClassBase("player") local indicators = {} if config.show_buffs == "1" then -- buffs if myclass == "DRUID" then AddIndicator(indicators, 1126) -- Mark of the Wild AddIndicator(indicators, 21849) -- Gift of the Wild AddIndicator(indicators, 467) -- Thorns end if myclass == "PRIEST" then AddIndicator(indicators, 1243) -- Power Word: Fortitude AddIndicator(indicators, 21562) -- Prayer of Fortitude AddIndicator(indicators, 6386) -- Divine Spirit AddIndicator(indicators, 27681) -- Prayer of Spirit AddIndicator(indicators, 976) -- Shadow Protection AddIndicator(indicators, 27683) -- Prayer of Shadow Protection AddIndicator(indicators, 6346) -- Fear Ward end if myclass == "PALADIN" then AddIndicator(indicators, 1038) -- Blessing of Salvation AddIndicator(indicators, 25895) -- Greater Blessing of Salvation AddIndicator(indicators, 19742) -- Blessing of Wisdom AddIndicator(indicators, 25894) -- Greater Blessing of Wisdom AddIndicator(indicators, 20204) -- Blessing of Sanctuary AddIndicator(indicators, 25899) -- Greater Blessing of Sanctuary AddIndicator(indicators, 20217) -- Blessing of Kings AddIndicator(indicators, 25898) -- Greater Blessing of Kings AddIndicator(indicators, 19740) -- Blessing of Might AddIndicator(indicators, 25782) -- Greater Blessing of Might AddIndicator(indicators, 19977) -- Blessing of Light AddIndicator(indicators, 25890) -- Greater Blessing of Light AddIndicator(indicators, 6940) -- Hand of Sacrifice AddIndicator(indicators, 45801) -- Greater Blessing of Sacrifice AddIndicator(indicators, 1044) -- Hand of Freedom AddIndicator(indicators, 1022) -- Hand of Protection end if myclass == "WARLOCK" then AddIndicator(indicators, 1167) -- Fire Shield AddIndicator(indicators, 6307) -- Blood Pact AddIndicator(indicators, 20707) -- Soulstone Resurrection AddIndicator(indicators, 5697) -- Unending Breath AddIndicator(indicators, 2970) -- Detect Invisibility AddIndicator(indicators, 11743) -- Detect Greater Invisibility AddIndicator(indicators, 132) -- Detect Lesser Invisibility AddIndicator(indicators, 19480) -- Paranoia end if myclass == "WARRIOR" then AddIndicator(indicators, 5242) -- Battle Shout AddIndicator(indicators, 45580) -- Commanding Shout end if myclass == "MAGE" then AddIndicator(indicators, 1459) -- Arcane Intellect AddIndicator(indicators, 23028) -- Arcane Brilliance AddIndicator(indicators, 604) -- Dampen Magic AddIndicator(indicators, 1008) -- Amplify Magic end if myclass == "HUNTER" then AddIndicator(indicators, 20043) -- Aspect of the Wild AddIndicator(indicators, 13159) -- Aspect of the Pack end if myclass == "SHAMAN" then AddIndicator(indicators, 45525) -- Earth Shield end end if config.show_procs == "1" then -- procs if myclass == "SHAMAN" or config.all_procs == "1" then AddIndicator(indicators, 16177) -- Ancestral Fortitude AddIndicator(indicators, 29202) -- Healing Way -- Totemic Power is four auras, one per totem school, each with its own icon AddIndicator(indicators, 28824) AddIndicator(indicators, 28825) AddIndicator(indicators, 28826) AddIndicator(indicators, 28827) end if myclass == "PRIEST" or config.all_procs == "1" then AddIndicator(indicators, 14893) -- Inspiration end end if config.show_hots == "1" then -- hots if myclass == "PRIEST" or config.all_hots == "1" then AddIndicator(indicators, 139, "Renew") -- Renew AddIndicator(indicators, 17) -- Power Word: Shield end if myclass == "DRUID" or config.all_hots == "1" then AddIndicator(indicators, 8936, "Regr") -- Regrowth AddIndicator(indicators, 774, "Reju") -- Rejuvenation end end if config.show_totems == "1" and myclass == "SHAMAN" then -- totems -- the aura each totem applies, not the cast that drops it: they share an -- icon but the totem's own name carries a " Totem" suffix the aura lacks AddIndicator(indicators, 8076) -- Strength of Earth AddIndicator(indicators, 8072) -- Stoneskin AddIndicator(indicators, 5677) -- Mana Spring AddIndicator(indicators, 16191) -- Mana Tide AddIndicator(indicators, 5672) -- Healing Stream AddIndicator(indicators, 25909) -- Tranquil Air AddIndicator(indicators, 8836) -- Grace of Air AddIndicator(indicators, 8177) -- Grounding Totem AddIndicator(indicators, 10596) -- Nature Resistance AddIndicator(indicators, 8185) -- Fire Resistance AddIndicator(indicators, 8182) -- Frost Resistance end return indicators end local function abbrevname(t) return t:sub(1,1)..". " end function pfUI.uf:GetNameString(unitstr) local name = UnitName(unitstr) local abbrev = pfUI_config.unitframes.abbrevname == "1" or nil local size = 20 -- first try to only abbreviate the first word if abbrev and name and strlen(name) > size then name = name:gsub("^(%S+) ", abbrevname) end -- abbreviate all if it still doesn't fit if abbrev and name and strlen(name) > size then name = name:gsub("(%S+) ", abbrevname) end return name end function pfUI.uf:GetLevelString(unitstr) local level = UnitLevel(unitstr) if level == -1 then level = "??" end local elite = UnitClassification(unitstr) if elite == "worldboss" then level = level .. "B" elseif elite == "rareelite" then level = level .. "R+" elseif elite == "elite" then level = level .. "+" elseif elite == "rare" then level = level .. "R" end return level end function pfUI.uf:GetStatusValue(unit, pos) if not pos or not unit then return end local config = unit.config["txt"..pos] local unitstr = unit.label .. unit.id local frame = unit[pos .. "Text"] -- as a fallback, draw the name if pos == "center" and not config then config = "unit" end -- Get stats with Nampower Integration local hp, hpmax, mp, mpmax, powerType = pfUI.api.GetUnitStats(unitstr) local rhp, rhpmax = hp, hpmax -- Use libhealth for mob health estimation (overrides Nampower/Standard) if pfUI.libhealth and pfUI.libhealth.enabled then rhp, rhpmax = pfUI.libhealth:GetUnitHealth(unitstr) end if config == "unit" then local name = unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) local level = unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr) return level .. " " .. name elseif config == "unitrev" then local name = unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) local level = unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr) return name .. " " .. level elseif config == "name" then return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) elseif config == "nameshort" then return unit:GetColor("unit") .. strsub(UnitName(unitstr), 0, 3) elseif config == "level" then return unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr) elseif config == "class" then if UnitIsPlayer(unitstr) then return unit:GetColor("class") .. (UnitClass(unitstr) or UNKNOWN) else return "" end elseif config == "ownername" then local owner if unit.label == "raidpet" then owner = "raid" .. unit.id elseif unit.label == "partypet" then owner = "party" .. unit.id elseif unit.label == "pet" then owner = "player" end if not (owner and UnitExists(owner)) then return "" end local color = "" if unit.config["classcolor"] == "1" and UnitIsPlayer(owner) then local _, r, g, b = GetUnitColor(owner) if C.unitframes.pastel == "1" then r, g, b = (r+.75)*.5, (g+.75)*.5, (b+.75)*.5 end color = rgbhex(r, g, b) end return color .. pfUI.uf:GetNameString(owner) -- health elseif config == "health" then return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) elseif config == "healthmax" then return unit:GetColor("health") .. pfUI.api.Abbreviate(rhpmax) elseif config == "healthperc" then return unit:GetColor("health") .. ceil(hp / hpmax * 100) elseif config == "healthmiss" then local health = ceil(rhp - rhpmax) if UnitIsDead(unitstr) then return unit:GetColor("health") .. DEAD elseif health == 0 then return unit:GetColor("health") .. "0" else return unit:GetColor("health") .. pfUI.api.Abbreviate(health) end elseif config == "healthdyn" then if hp ~= hpmax then return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) .. " - " .. ceil(hp / hpmax * 100) .. "%" else return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) end elseif config == "namehealth" then local health = ceil(rhp - rhpmax) if UnitIsDead(unitstr) then return unit:GetColor("health") .. DEAD elseif health == 0 then return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) else return unit:GetColor("health") .. pfUI.api.Abbreviate(health) end elseif config == "namehealthbreak" then local health = ceil(rhp - rhpmax) if UnitIsDead(unitstr) then return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) .. "\n" .. unit:GetColor("health") .. DEAD elseif health == 0 then return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) else return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) .. "\n" .. unit:GetColor("health") .. pfUI.api.Abbreviate(-health) end elseif config == "shortnamehealth" then local health = ceil(rhp - rhpmax) if UnitIsDead(unitstr) then return unit:GetColor("health") .. DEAD elseif health == 0 then return unit:GetColor("unit") .. strsub(UnitName(unitstr), 0, 3) else return unit:GetColor("health") .. pfUI.api.Abbreviate(health) end elseif config == "healthminmax" then return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) .. "/" .. pfUI.api.Abbreviate(rhpmax) -- mana/power/focus elseif config == "power" then return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) elseif config == "powermax" then return unit:GetColor("power") .. pfUI.api.Abbreviate(mpmax) elseif config == "powerperc" then local perc = UnitPowerMax(unitstr) > 0 and ceil(mp / mpmax * 100) or 0 return unit:GetColor("power") .. perc elseif config == "powermiss" then local power = ceil(mp - mpmax) if power == 0 then return unit:GetColor("power") .. "0" else return unit:GetColor("power") .. pfUI.api.Abbreviate(power) end elseif config == "powerdyn" then -- show percentage when only mana is less than 100% if mp ~= mpmax and UnitPowerType(unitstr) == Enum.PowerType.Mana then return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) .. " - " .. ceil(mp / mpmax * 100) .. "%" else return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) end elseif config == "powerminmax" then return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) .. "/" .. pfUI.api.Abbreviate(mpmax) else return "" end end function pfUI.uf.GetColor(self, preset) local config = self.config local unitstr = self.label .. self.id local r, g, b = 1, 1, 1 if preset == "unit" and config["classcolor"] == "1" then if UnitIsPlayer(unitstr) then _, r, g, b = GetUnitColor(unitstr) elseif self.label == "pet" then local happiness = GetPetHappiness() if happiness == 1 then r, g, b = 1, 0, 0 elseif happiness == 2 then r, g, b = 1, 1, 0 else r, g, b = 0, 1, 0 end else local color = UnitReactionColor[UnitReaction(unitstr, "player")] if color then r, g, b = color.r, color.g, color.b end end elseif preset == "class" and config["classcolor"] == "1" then _, r, g, b = GetUnitColor(unitstr) elseif preset == "reaction" and config["classcolor"] == "1" then local color = UnitReactionColor[UnitReaction(unitstr, "player")] r, g, b = color.r, color.g, color.b elseif preset == "health" and config["healthcolor"] == "1" then local hp, hpmax = UnitHealth(unitstr), UnitHealthMax(unitstr) if hpmax and hpmax > 0 then r, g, b = GetColorGradient(hp / hpmax) else r, g, b = 0, 0, 0 end elseif preset == "power" and config["powercolor"] == "1" then local color = ManaBarColor[UnitPowerType(unitstr)] r, g, b = color.r, color.g, color.b elseif preset == "level" and config["levelcolor"] == "1" then local color = GetDifficultyColor(UnitLevel(unitstr)) r, g, b = color.r, color.g, color.b end if C.unitframes.pastel == "1" then r, g, b = (r + .75) * .5, (g + .75) * .5, (b + .75) * .5 end return rgbhex(r,g,b) end