mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-21 23:26:56 +00:00
5ed1d98ecc
commit f19d7402810637fc32460864104cb792ac5af863 Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Wed Jul 22 01:05:52 2026 -0500 remove comment commit 60f4968f06d19d42bdc6347f98ff4d5a34785e97 Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Wed Jul 22 01:05:06 2026 -0500 Bump ClassicAPI minimum version to 10705 Update the minimum required ClassicAPI version from 10704 (1.7.4) to 10705 (1.7.5). commit 088dba4c23f4aa7ce98b9ce9d75bc5892cd40520 Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Wed Jul 22 01:04:04 2026 -0500 Add raid-pet frames: an independent, roster-driven pet grid New "raidpet" unitframe type that shows raid members' pets (raidpet1..40) in their own movable block (pfRaidPetCluster), off by default. It's a flat pool of frames laid out by pfUI.uf.raid:LayoutPets straight from the raid roster -- cell N shows raidpet<N> -- so it's fully decoupled from how the raid grid arranges its own slots. Layout is independent of the raid grid: raidpet carries its own width/height plus a Layout section (raidlayout / raidpadding / raidfill), and its own Collapse Empty Slots toggle that packs only the pets that exist into the leading cells. The raid grid gets the same collapse option (sequential slot assignment in AddUnitToGroup instead of by subgroup). Collapsed pets re-pack on roster changes and on UNIT_PET so summons and dismisses track live without polling. Also: - unitframes: a "raidpet" branch in UpdateVisibility (hide when the pet is out of range or its raid<N> owner is gone), and fix cache_raid so the "pfRaid" prefix check doesn't misread pfRaidPet<n> frames (char 7 is non-numeric -> nil compare crash). - New "Owner Name" text option: on any pet frame (raidpet/partypet/pet) it shows the owner's class-colored name so you can tell whose pet it is. - unlock: a pfRaidPet drag cluster, a numeric-suffix guard so pfRaid no longer matches (and crashes on) pet frames, and RaidPet config mappings. - config/gui/translations for all of the above. commit 52f02c8963fe7ec90f36100ebe069c7807529301 Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Mon Jul 20 23:09:06 2026 -0500 Enumerate chat bubbles via ClassicAPI instead of scanning WorldFrame ClassicAPI's C_ChatBubbles.GetAllChatBubbles() walks the engine's own bubble list and returns the exact set of live bubble frames, with real GetRegions(), so the decoration idiom works unchanged. Replace the WorldFrame:GetChildren() sweep and drop the IsBubble heuristic (unnamed frame whose first region is the ChatBubble-Background texture) -- the API only ever hands back bubbles, so that guess is both redundant and more fragile than the engine list. Cheaper too: it iterates only live bubbles rather than every world child on each chat event.
3087 lines
110 KiB
Lua
3087 lines
110 KiB
Lua
-- load pfUI environment
|
|
setfenv(1, pfUI:GetEnvironment())
|
|
|
|
pfUI.uf = CreateFrame("Frame", nil, UIParent)
|
|
pfUI.uf.frames = {}
|
|
|
|
-- ============================================================================
|
|
-- 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
|
|
end, true)
|
|
|
|
-- HoT buff indicators that need name verification because their icons are
|
|
-- reused by other spells. Maps icon (lowercased) → expected aura name +
|
|
-- libpredict key for the prediction integration.
|
|
local HOT_INDICATORS = {
|
|
[strlower(C_Spell.GetSpellTexture(774))] = { name = strlower(C_Spell.GetSpellName(774)), predict = "Reju" },
|
|
[strlower(C_Spell.GetSpellTexture(139))] = { name = strlower(C_Spell.GetSpellName(139)), predict = "Renew" },
|
|
[strlower(C_Spell.GetSpellTexture(8936))] = { name = strlower(C_Spell.GetSpellName(8936)), predict = "Regr" },
|
|
}
|
|
|
|
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 engine's full HARMFUL list. Look
|
|
-- up the displayed aura via the PLAYER filter, then scan engine slots for
|
|
-- one whose name + sourceGUID match.
|
|
if parent.config and parent.config.selfdebuff == "1" then
|
|
local ownAura = C_UnitAuras.GetAuraDataByIndex(unitstr, this.id, "HARMFUL|PLAYER")
|
|
if ownAura then
|
|
for gameSlot = 1, 16 do
|
|
local check = C_UnitAuras.GetDebuffDataByIndex(unitstr, gameSlot)
|
|
if check and check.name == ownAura.name and check.sourceGUID == ownAura.sourceGUID then
|
|
GameTooltip:SetUnitAura(unitstr, gameSlot, "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 - Nampower Integration for Health + Power
|
|
-- Returns: hp, maxHp, power, maxPower, powerType
|
|
-- IMPORTANT: Uses _G.UnitExists directly to avoid conflicts with Nampower's
|
|
-- use UnitGUID(unit) for GUID lookup (Nampower 3.0.0+)
|
|
-- ============================================================================
|
|
|
|
-- Cache für Stats-Tracking (nur Änderungen zählen)
|
|
pfUI.api.lastUnitStats = pfUI.api.lastUnitStats or {}
|
|
|
|
function pfUI.api.GetUnitStats(unitstr, trackStats)
|
|
local hp, maxHp, power, maxPower, powerType
|
|
local usedNampower = false
|
|
|
|
|
|
-- Try GetUnitField first if available (for all units: players, pets, NPCs)
|
|
if GetUnitField then
|
|
-- Use the standard check first, then get guid separately
|
|
local exists = _G.UnitExists(unitstr)
|
|
if exists then
|
|
-- Get guid via the extended UnitExists for Nampower
|
|
local _, guid = _G.UnitExists(unitstr)
|
|
|
|
if guid then
|
|
hp = GetUnitField(guid, "health")
|
|
maxHp = GetUnitField(guid, "maxHealth")
|
|
|
|
-- Get power type from bytes0
|
|
local bytes0 = GetUnitField(guid, "bytes0")
|
|
if bytes0 then
|
|
local temp = math.floor(bytes0 / 16777216)
|
|
powerType = temp - math.floor(temp / 256) * 256
|
|
else
|
|
powerType = UnitPowerType(unitstr) or 0
|
|
end
|
|
|
|
-- Get power values based on type
|
|
if powerType == 1 then
|
|
-- Rage (Nampower stores rage * 10, maxPower2 stores maxRage * 10)
|
|
local rage = GetUnitField(guid, "power2")
|
|
local maxRage = GetUnitField(guid, "maxPower2")
|
|
power = rage and math.floor(rage / 10) or UnitMana(unitstr)
|
|
maxPower = maxRage and math.floor(maxRage / 10) or UnitManaMax(unitstr)
|
|
elseif powerType == 3 then
|
|
-- Energy
|
|
power = GetUnitField(guid, "power4") or UnitMana(unitstr)
|
|
if power then power = math.floor(power) end
|
|
maxPower = GetUnitField(guid, "maxPower4") or UnitManaMax(unitstr)
|
|
elseif powerType == 2 then
|
|
-- Focus (Hunter pets use power3)
|
|
power = GetUnitField(guid, "power3") or UnitMana(unitstr)
|
|
if power then power = math.floor(power) end
|
|
maxPower = GetUnitField(guid, "maxPower3") or UnitManaMax(unitstr)
|
|
|
|
else
|
|
-- Mana (default)
|
|
power = GetUnitField(guid, "power1") or UnitMana(unitstr)
|
|
maxPower = GetUnitField(guid, "maxPower1") or UnitManaMax(unitstr)
|
|
end
|
|
|
|
-- Check if Nampower gave valid health data
|
|
if hp and hp > 0 and maxHp and maxHp > 0 then
|
|
usedNampower = true
|
|
|
|
-- Track Nampower success - NUR bei echten Änderungen
|
|
if trackStats and pfUI.uf and pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
local lastStats = pfUI.api.lastUnitStats[unitstr]
|
|
if not lastStats or lastStats.hp ~= hp or lastStats.maxHp ~= maxHp or
|
|
lastStats.power ~= power or lastStats.maxPower ~= maxPower then
|
|
pfUI.uf.stats.nampowerUsed = (pfUI.uf.stats.nampowerUsed or 0) + 1
|
|
pfUI.api.lastUnitStats[unitstr] = {
|
|
hp = hp,
|
|
maxHp = maxHp,
|
|
power = power,
|
|
maxPower = maxPower
|
|
}
|
|
end
|
|
end
|
|
|
|
return hp, maxHp, power or 0, maxPower or 1, powerType
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fallback to standard API (for players when Nampower fails)
|
|
hp = UnitHealth(unitstr) or 0
|
|
maxHp = UnitHealthMax(unitstr) or 1
|
|
powerType = UnitPowerType(unitstr) or 0
|
|
power = UnitMana(unitstr) or 0
|
|
maxPower = UnitManaMax(unitstr) or 1
|
|
|
|
-- Track Fallback usage - NUR bei echten Änderungen
|
|
if trackStats and not usedNampower then
|
|
if pfUI.uf and pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
local lastStats = pfUI.api.lastUnitStats[unitstr]
|
|
if not lastStats or lastStats.hp ~= hp or lastStats.maxHp ~= maxHp or
|
|
lastStats.power ~= power or lastStats.maxPower ~= maxPower then
|
|
pfUI.uf.stats.fallbackUsed = (pfUI.uf.stats.fallbackUsed or 0) + 1
|
|
pfUI.api.lastUnitStats[unitstr] = {
|
|
hp = hp,
|
|
maxHp = maxHp,
|
|
power = power,
|
|
maxPower = maxPower
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
return hp, maxHp, power, maxPower, powerType
|
|
end
|
|
|
|
local aggrodata = { }
|
|
function pfUI.api.UnitHasAggro(unit)
|
|
-- Only cache positive results to allow instant detection when aggro changes
|
|
if aggrodata[unit] and aggrodata[unit].state > 0 and GetTime() < aggrodata[unit].check + 1 then
|
|
return aggrodata[unit].state
|
|
end
|
|
|
|
aggrodata[unit] = aggrodata[unit] or { }
|
|
aggrodata[unit].check = GetTime()
|
|
aggrodata[unit].state = 0
|
|
|
|
if UnitExists(unit) and UnitIsFriend(unit, "player") then
|
|
for u in pairs(pfValidUnits) do
|
|
local t = u .. "target"
|
|
local tt = t .. "target"
|
|
|
|
if UnitExists(t) and UnitIsUnit(t, unit) and UnitCanAttack(u, unit) then
|
|
aggrodata[unit].state = aggrodata[unit].state + 1
|
|
end
|
|
|
|
if UnitExists(tt) and UnitIsUnit(tt, unit) and UnitCanAttack(t, unit) then
|
|
aggrodata[unit].state = aggrodata[unit].state + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return aggrodata[unit].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
|
|
if strsub(self:GetName(),0,6) == "pfRaid" then
|
|
self.cache_raid = tonumber(strsub(self:GetName(),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 IsInRaid() and 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
|
|
|
|
-- 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 = string.format("%s%s", self.label or "", self.id or "")
|
|
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
|
|
|
|
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 C["unitframes"]["group"]["hide_in_raid"] == "1" and self.label and strsub(self.label,0,5) == "party" and IsInRaid() then
|
|
-- hide group while in raid and option is set
|
|
visibility = "hide"
|
|
self.visible = nil
|
|
elseif ( self.fname == "Group0" or self.fname == "PartyPet0" or self.fname == "Party0Target" )
|
|
and (not IsInGroup() or (C["unitframes"]["group"]["hide_in_raid"] == "1" and IsInRaid())) then
|
|
-- hide self in group if solo or hide in raid is set
|
|
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:SetWidth(real_height)
|
|
self.portrait:SetHeight(real_height)
|
|
portrait = real_height + spacing + 2*default_border
|
|
else
|
|
-- use custom portrait size
|
|
self.portrait:SetWidth(ptwidth)
|
|
self.portrait:SetHeight(ptheight)
|
|
portrait = ptwidth + spacing + 2*default_border
|
|
end
|
|
end
|
|
|
|
self:SetWidth(width + portrait)
|
|
self:SetHeight(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:SetWidth(tonumber(f.config.squaresize))
|
|
f.combat:SetHeight(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:SetWidth(f.config.width)
|
|
f.hp:SetHeight(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:SetWidth((f.config.pwidth ~= "-1" and f.config.pwidth or f.config.width))
|
|
f.power:SetHeight(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: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
|
|
|
|
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
|
|
if f.config.raidgrouplabel == "1" then
|
|
f.group:Show()
|
|
else
|
|
f.group:Hide()
|
|
end
|
|
|
|
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:SetHeight(f.config.height)
|
|
f.incHeal:SetWidth(f.config.width)
|
|
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:SetWidth(32)
|
|
f.ressIcon:SetHeight(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:SetWidth(10)
|
|
f.leaderIcon:SetHeight(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:SetWidth(10)
|
|
f.lootIcon:SetHeight(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:SetWidth(f.config.pvpiconsize)
|
|
f.pvpIcon:SetHeight(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:SetWidth(f.config.raidiconsize)
|
|
f.raidIcon:SetHeight(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:SetWidth(16)
|
|
f.restIcon:SetHeight(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:SetWidth(tonumber(C.unitframes.pet.happinesssize))
|
|
f.happinessIcon:SetHeight(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(COOLDOWN_FRAME_TYPE, 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]:SetWidth(f.config.debuffsize)
|
|
f.debuffs[i]:SetHeight(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(COOLDOWN_FRAME_TYPE, 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
|
|
if cooldown_text == 1 then
|
|
f.debuffs[i].cd.pfCooldownText:Show()
|
|
else
|
|
f.debuffs[i].cd.pfCooldownText:Hide()
|
|
end
|
|
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
|
|
this.lastEventUpdate = GetTime()
|
|
|
|
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
|
|
|
|
-- ============================================================================
|
|
-- GLOBAL FALLBACK THROTTLE - Limits total fallback updates across ALL frames
|
|
-- ============================================================================
|
|
pfUI.uf.fallbackThrottle = {
|
|
lastUpdate = 0,
|
|
interval = 0.1, -- 10 updates per second total (not per frame!)
|
|
updatesThisInterval = 0,
|
|
maxUpdatesPerInterval = 5 -- Max 5 frames can update per interval
|
|
}
|
|
|
|
-- ============================================================================
|
|
-- STATS SYSTEM - Performance tracking for Nampower vs Fallback
|
|
-- ============================================================================
|
|
pfUI.uf.stats = {
|
|
eventUpdates = 0,
|
|
heartbeatUpdates = 0,
|
|
earlyReturns = 0,
|
|
nampowerUsed = 0,
|
|
fallbackUsed = 0,
|
|
throttledSkips = 0,
|
|
startTime = 0,
|
|
enabled = true
|
|
}
|
|
|
|
-- Stats Frame (Live Display)
|
|
pfUI.uf.statsFrame = CreateFrame("Frame", "pfUIStatsFrame", UIParent)
|
|
pfUI.uf.statsFrame:SetWidth(200)
|
|
pfUI.uf.statsFrame:SetHeight(220)
|
|
pfUI.uf.statsFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -10, -200)
|
|
pfUI.uf.statsFrame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 8,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 }
|
|
})
|
|
pfUI.uf.statsFrame:SetBackdropColor(0, 0, 0, 0.8)
|
|
pfUI.uf.statsFrame:EnableMouse(true)
|
|
pfUI.uf.statsFrame:SetMovable(true)
|
|
pfUI.uf.statsFrame:SetClampedToScreen(true)
|
|
pfUI.uf.statsFrame:RegisterForDrag("LeftButton")
|
|
pfUI.uf.statsFrame:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
pfUI.uf.statsFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
pfUI.uf.statsFrame:Hide()
|
|
|
|
-- Stats Title
|
|
pfUI.uf.statsFrame.title = pfUI.uf.statsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
|
pfUI.uf.statsFrame.title:SetPoint("TOP", pfUI.uf.statsFrame, "TOP", 0, -8)
|
|
pfUI.uf.statsFrame.title:SetText("Performance")
|
|
|
|
-- Stats Text (multi-line)
|
|
pfUI.uf.statsFrame.text = pfUI.uf.statsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
|
pfUI.uf.statsFrame.text:SetPoint("TOPLEFT", pfUI.uf.statsFrame, "TOPLEFT", 10, -30)
|
|
pfUI.uf.statsFrame.text:SetWidth(180)
|
|
pfUI.uf.statsFrame.text:SetHeight(180)
|
|
pfUI.uf.statsFrame.text:SetJustifyH("LEFT")
|
|
pfUI.uf.statsFrame.text:SetJustifyV("TOP")
|
|
pfUI.uf.statsFrame.text:SetText("Initializing...")
|
|
|
|
-- Update function for stats display
|
|
pfUI.uf.UpdateStatsDisplay = function()
|
|
local elapsed = GetTime() - pfUI.uf.stats.startTime
|
|
if elapsed < 0.1 then return end
|
|
|
|
local eventRate = pfUI.uf.stats.eventUpdates / elapsed
|
|
local heartbeatRate = pfUI.uf.stats.heartbeatUpdates / elapsed
|
|
local totalFrameUpdates = eventRate + heartbeatRate
|
|
|
|
-- Calculate Nampower vs Fallback percentages (ONLY counts actual data changes!)
|
|
local totalDataChanges = pfUI.uf.stats.nampowerUsed + pfUI.uf.stats.fallbackUsed
|
|
local nampowerPct = totalDataChanges > 0 and math.floor((pfUI.uf.stats.nampowerUsed / totalDataChanges) * 100) or 0
|
|
local fallbackPct = totalDataChanges > 0 and math.floor((pfUI.uf.stats.fallbackUsed / totalDataChanges) * 100) or 0
|
|
|
|
-- Calculate data change rate (how often HP/Mana actually changes)
|
|
local dataChangeRate = totalDataChanges / elapsed
|
|
|
|
local statsText = string.format(
|
|
"Time: %.1fs\n" ..
|
|
"|cffaaaaaa--- Frame Updates ---|r\n" ..
|
|
"Event: %.1f/s (%d)\n" ..
|
|
"Heartbeat: %.1f/s (%d)\n" ..
|
|
"Total: %.1f/s\n" ..
|
|
"\n" ..
|
|
"|cffaaaaaa--- Data Changes ---|r\n" ..
|
|
"Rate: %.1f/s (%d)\n" ..
|
|
"|cff00ff00NP: %d%% (%d)|r\n" ..
|
|
"|cffff8800FB: %d%% (%d)|r",
|
|
elapsed,
|
|
eventRate,
|
|
pfUI.uf.stats.eventUpdates,
|
|
heartbeatRate,
|
|
pfUI.uf.stats.heartbeatUpdates,
|
|
totalFrameUpdates,
|
|
dataChangeRate,
|
|
totalDataChanges,
|
|
nampowerPct,
|
|
pfUI.uf.stats.nampowerUsed,
|
|
fallbackPct,
|
|
pfUI.uf.stats.fallbackUsed
|
|
)
|
|
|
|
pfUI.uf.statsFrame.text:SetText(statsText)
|
|
end
|
|
|
|
-- Stats update timer
|
|
pfUI.uf.statsUpdateTimer = 0
|
|
|
|
-- Cache cleanup timer (clean lastUnitStats every 30s to prevent memory leak)
|
|
pfUI.uf.cacheCleanupTimer = 0
|
|
|
|
-- ============================================================================
|
|
-- OnUpdate with Heartbeat Polling and Fallback
|
|
-- ============================================================================
|
|
function pfUI.uf.OnUpdate()
|
|
local now = _GetTime()
|
|
pfUI.uf.now = now
|
|
|
|
-- Update stats display (throttled to 0.2s)
|
|
if pfUI.uf.statsFrame and pfUI.uf.statsFrame:IsShown() then
|
|
if (pfUI.uf.statsUpdateTimer or 0) <= now then
|
|
pfUI.uf.statsUpdateTimer = now + 0.2
|
|
if pfUI.uf.stats.startTime > 0 then
|
|
pfUI.uf.UpdateStatsDisplay()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Cleanup lastUnitStats cache every 30 seconds to prevent memory leak
|
|
if (pfUI.uf.cacheCleanupTimer or 0) <= now then
|
|
pfUI.uf.cacheCleanupTimer = now + 30
|
|
|
|
-- Only keep cache for units that currently exist
|
|
if pfUI.api.lastUnitStats then
|
|
for unitstr in pairs(pfUI.api.lastUnitStats) do
|
|
if not _G.UnitExists(unitstr) then
|
|
pfUI.api.lastUnitStats[unitstr] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 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
|
|
if pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
pfUI.uf.stats.throttledSkips = pfUI.uf.stats.throttledSkips + 1
|
|
end
|
|
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)
|
|
|
|
-- O(1) Nampower lookup via GUID (same pattern as nameplates.lua)
|
|
local health, maxHealth
|
|
if GetUnitField then
|
|
local guid = UnitGUID(unit)
|
|
if guid then
|
|
health = GetUnitField(guid, "health")
|
|
maxHealth = GetUnitField(guid, "maxHealth")
|
|
end
|
|
end
|
|
-- Fallback to standard API
|
|
if not health or not maxHealth or maxHealth == 0 then
|
|
health, maxHealth = UnitHealth(unit), UnitHealthMax(unit)
|
|
end
|
|
|
|
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)
|
|
if ress and UnitIsDeadOrGhost(unit) then
|
|
this.ressIcon:Show()
|
|
else
|
|
this.ressIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- EVENT-BASED UPDATES (Health, Mana, Auras, etc.)
|
|
-- ============================================================================
|
|
|
|
-- Check if we have pending updates from events
|
|
local hasUpdates = this.update_full or this.update_base or
|
|
this.update_aura or this.update_portrait or
|
|
this.update_pvp or this.update_indicators
|
|
|
|
-- Track event-triggered updates (not API calls, just frame updates)
|
|
if hasUpdates and pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
pfUI.uf.stats.eventUpdates = pfUI.uf.stats.eventUpdates + 1
|
|
end
|
|
|
|
-- Heartbeat Polling: If no events pending, check if we need fallback
|
|
if not hasUpdates then
|
|
local timeSinceEvent = this.lastEventUpdate and (now - this.lastEventUpdate) or 999
|
|
|
|
-- If >0.5s since last event and unit exists, try heartbeat
|
|
if timeSinceEvent > 0.5 and this.label and _G.UnitExists(this.label .. this.id) then
|
|
local needsFallback = false
|
|
|
|
-- Check if Nampower can provide data
|
|
if GetUnitField then
|
|
-- Use _G.UnitExists to avoid conflicts with range checking
|
|
local unitstr = this.label .. this.id
|
|
local exists = _G.UnitExists(unitstr)
|
|
if exists then
|
|
local _, guid = _G.UnitExists(unitstr)
|
|
if guid then
|
|
local hp = GetUnitField(guid, "health")
|
|
if not hp or hp == 0 then
|
|
needsFallback = true
|
|
end
|
|
else
|
|
needsFallback = true
|
|
end
|
|
else
|
|
needsFallback = true
|
|
end
|
|
else
|
|
needsFallback = true
|
|
end
|
|
|
|
if needsFallback then
|
|
-- GLOBAL Throttle: Limit fallback updates across ALL frames
|
|
local throttle = pfUI.uf.fallbackThrottle
|
|
|
|
-- Reset counter each interval
|
|
if now - throttle.lastUpdate > throttle.interval then
|
|
throttle.lastUpdate = now
|
|
throttle.updatesThisInterval = 0
|
|
end
|
|
|
|
-- Check if we've exceeded max updates this interval
|
|
if throttle.updatesThisInterval >= throttle.maxUpdatesPerInterval then
|
|
if pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
pfUI.uf.stats.earlyReturns = pfUI.uf.stats.earlyReturns + 1
|
|
end
|
|
return
|
|
end
|
|
|
|
throttle.updatesThisInterval = throttle.updatesThisInterval + 1
|
|
|
|
-- Nampower not available or no data - trigger fallback update
|
|
this.update_base = true
|
|
if pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
pfUI.uf.stats.heartbeatUpdates = pfUI.uf.stats.heartbeatUpdates + 1
|
|
end
|
|
else
|
|
-- Nampower working fine, no update needed
|
|
if pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
pfUI.uf.stats.earlyReturns = pfUI.uf.stats.earlyReturns + 1
|
|
end
|
|
return
|
|
end
|
|
else
|
|
-- Too soon or unit doesn't exist
|
|
if pfUI.uf.stats and pfUI.uf.stats.enabled then
|
|
pfUI.uf.stats.earlyReturns = pfUI.uf.stats.earlyReturns + 1
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
-- 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.OnEnter()
|
|
if not this.label then return end
|
|
|
|
-- Nampower: Set native mouseover unit for macro/addon compatibility
|
|
if SetMouseoverUnit then
|
|
local unitstr = this.label .. this.id
|
|
-- For GUID-based frames (focus), use the GUID directly
|
|
if this.label and string.find(this.label, "^0x") then
|
|
SetMouseoverUnit(this.label)
|
|
elseif UnitExists(unitstr) then
|
|
SetMouseoverUnit(unitstr)
|
|
end
|
|
end
|
|
|
|
if this.config.showtooltip == "0" then return end
|
|
GameTooltip_SetDefaultAnchor(GameTooltip, this)
|
|
GameTooltip:SetUnit(this.label .. this.id)
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
function pfUI.uf.OnLeave()
|
|
-- Nampower: Clear native mouseover unit
|
|
if SetMouseoverUnit then
|
|
SetMouseoverUnit()
|
|
end
|
|
|
|
GameTooltip:FadeOut()
|
|
end
|
|
|
|
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:RightClickAction(unit)
|
|
if unit == "player" then
|
|
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor")
|
|
elseif unit == "target" then
|
|
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor")
|
|
elseif unit == "pet" then
|
|
ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor")
|
|
elseif unit == "party" or strfind(unit, "party%d") then
|
|
ToggleDropDownMenu(1, nil, getglobal("PartyMemberFrame" .. this.id .. "DropDown"), "cursor")
|
|
elseif unit == "raid" or strfind(unit, "raid%d") then
|
|
local name = this.lastUnit
|
|
local unitstr = this.label .. this.id
|
|
FriendsDropDown.displayMode = "MENU"
|
|
FriendsDropDown.initialize = function() UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PARTY", unitstr, name, id) end
|
|
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
|
|
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
|
|
|
|
function pfUI.uf:EnableScripts()
|
|
local f = self
|
|
|
|
f:SetScript("OnClick", pfUI.uf.OnClick)
|
|
|
|
f:SetScript("OnShow", pfUI.uf.OnShow)
|
|
f:SetScript("OnEvent", pfUI.uf.OnEvent)
|
|
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
|
|
f:SetScript("OnEnter", pfUI.uf.OnEnter)
|
|
f:SetScript("OnLeave", pfUI.uf.OnLeave)
|
|
f:EnableClickCast()
|
|
|
|
-- 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, UNITFRAME_SECURE_TEMPLATE)
|
|
|
|
-- 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)
|
|
|
|
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("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
|
|
|
|
-- 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
|
|
if unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup() then
|
|
unit.leaderIcon:Show()
|
|
else
|
|
unit.leaderIcon:Hide()
|
|
end
|
|
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
|
|
|
|
if name and name == UnitName(unitstr) then
|
|
unit.lootIcon:Show()
|
|
else
|
|
unit.lootIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
if unit.pvpIcon then -- PvP Icon
|
|
if unit.config.showPVP == "1" and UnitIsPVP(unitstr) then
|
|
unit.pvpIcon:Show()
|
|
else
|
|
unit.pvpIcon:Hide()
|
|
end
|
|
end
|
|
|
|
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
|
|
if C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting() then
|
|
unit.restIcon:Show()
|
|
else
|
|
unit.restIcon:Hide()
|
|
end
|
|
end
|
|
|
|
if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon
|
|
local _, pclass = UnitClass("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
|
|
|
|
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
|
|
local 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
|
|
for i=1, unit.config.bufflimit do
|
|
if not unit.buffs[i] then break end
|
|
|
|
local aura = C_UnitAuras.GetBuffDataByIndex(unitstr, i)
|
|
|
|
if aura then
|
|
unit.buffs[i].texture:SetTexture(aura.icon)
|
|
unit.buffs[i]:Show()
|
|
|
|
if aura.applications > 1 then
|
|
unit.buffs[i].stacks:SetText(aura.applications)
|
|
else
|
|
unit.buffs[i].stacks:SetText("")
|
|
end
|
|
|
|
if aura.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 aura.duration
|
|
-- (the Spell.dbc base). Anchor start to now in that case to keep the remaining math sane.
|
|
local now = GetTime()
|
|
local start = aura.expirationTime - aura.duration
|
|
local duration = aura.duration
|
|
if start > now or duration <= 0 then
|
|
start, duration = now, aura.expirationTime - now
|
|
end
|
|
if duration > 0 then
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
|
|
else
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
|
end
|
|
elseif aura.duration > 0 then
|
|
local guid = UnitGUID(unitstr)
|
|
local guidStarts = guid and pfUI.uf.aura_starts[guid]
|
|
local start = guidStarts and guidStarts[aura.spellId]
|
|
if start then
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, start, aura.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
|
|
|
|
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
|
|
|
|
-- 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).
|
|
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
|
|
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, filter)
|
|
if aura then
|
|
texture, stacks, dtype = aura.icon, aura.applications, aura.dispelName
|
|
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 aura and aura.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 = aura.expirationTime - aura.duration
|
|
local duration = aura.duration
|
|
if start > now or duration <= 0 then
|
|
start, duration = now, aura.expirationTime - now
|
|
end
|
|
if duration > 0 then
|
|
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, duration, 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:SetHeight(size)
|
|
indicator:SetWidth(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
|
|
|
|
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]:SetHeight(size)
|
|
indicator[debuff]:SetWidth(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]:SetHeight(size)
|
|
indicator[debuff]:SetWidth(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 = nil
|
|
|
|
for i=1,16 do
|
|
local a = C_UnitAuras.GetDebuffDataByIndex(unitstr, i)
|
|
local dtype = a and a.dispelName
|
|
if dtype == debuff then
|
|
indicator[debuff].visible = true
|
|
end
|
|
end
|
|
|
|
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] = string.lower(v)
|
|
end
|
|
elseif not unit.indicator_custom then
|
|
unit.indicator_custom = {}
|
|
end
|
|
|
|
local pos = 1
|
|
if table.getn(unit.indicators) > 0 then
|
|
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
|
|
local texLower = string.lower(aura.icon)
|
|
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
|
|
|
for _, filter in pairs(unit.indicators) do
|
|
if filter == texLower then
|
|
local hot = HOT_INDICATORS[texLower]
|
|
if hot and string.lower(aura.name) ~= hot.name then
|
|
break -- texture matches but name disambiguates (e.g. shared icon)
|
|
end
|
|
if hot then
|
|
local start, duration, prediction = libpredict:GetHotDuration(unitstr, hot.predict)
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft or prediction, aura.applications, tonumber(start), tonumber(duration))
|
|
else
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
|
end
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if table.getn(unit.indicator_custom) > 0 then
|
|
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
|
|
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
|
local lowerName = string.lower(aura.name)
|
|
for _, filter in pairs(unit.indicator_custom) do
|
|
if filter == lowerName then
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
|
|
for i=1,16 do -- scan for custom debuffs
|
|
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, debuffFilter)
|
|
if aura then
|
|
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
|
for _, filter in pairs(unit.indicator_custom) do
|
|
if filter == string.lower(aura.name) then
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
|
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, true)
|
|
|
|
-- 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 == 0 then
|
|
r, g, b, a = GetStringColor(mana)
|
|
elseif utype == 1 then
|
|
r, g, b, a = GetStringColor(rage)
|
|
elseif utype == 2 then
|
|
r, g, b, a = GetStringColor(focus)
|
|
elseif utype == 3 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
|
|
|
|
pfUI.uf:RefreshUnitState(unit)
|
|
end
|
|
end
|
|
|
|
local buttons = {
|
|
[1] = "LeftButton",
|
|
[2] = "RightButton",
|
|
[3] = "MiddleButton",
|
|
[4] = "Button4",
|
|
[5] = "Button5",
|
|
}
|
|
|
|
local modifiers = {
|
|
[""] = "",
|
|
["alt"] = "_alt",
|
|
["ctrl"] = "_ctrl",
|
|
["shift"] = "_shift",
|
|
}
|
|
|
|
function pfUI.uf:EnableClickCast()
|
|
if self.config.clickcast ~= "1" then return end
|
|
for bid, button in pairs(buttons) do
|
|
for modifier, mconf in pairs(modifiers) do
|
|
local bconf = bid == 1 and "" or bid
|
|
if pfUI_config.unitframes["clickcast"..bconf..mconf] ~= "" then
|
|
-- fill clickaction table
|
|
self.clickactions = self.clickactions or {}
|
|
self.clickactions[modifier..button] = pfUI_config.unitframes["clickcast"..bconf..mconf]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:ClickAction(button)
|
|
local label = this.label or ""
|
|
local id = this.id or ""
|
|
local unitstr = label .. id
|
|
local showmenu = button == "RightButton" and true or nil
|
|
if SpellIsTargeting() and button == "RightButton" then
|
|
SpellStopTargeting()
|
|
return
|
|
end
|
|
|
|
if SpellIsTargeting() and button == "LeftButton" then
|
|
SpellTargetUnit(unitstr)
|
|
elseif CursorHasItem() then
|
|
DropItemOnUnit(unitstr)
|
|
end
|
|
|
|
-- run click casting if enabled
|
|
local modstring = ""
|
|
modstring = IsAltKeyDown() and modstring.."alt" or modstring
|
|
modstring = IsControlKeyDown() and modstring.."ctrl" or modstring
|
|
modstring = IsShiftKeyDown() and modstring.."shift" or modstring
|
|
modstring = modstring..button
|
|
if this.clickactions and this.clickactions[modstring] then
|
|
local action_lower = string.lower(this.clickactions[modstring])
|
|
if string.find(action_lower, "^menu") then
|
|
-- show menu
|
|
showmenu = true
|
|
elseif string.find(action_lower, "^target") then
|
|
-- target unit
|
|
showmenu = nil
|
|
else
|
|
-- run click cast action
|
|
local is_macro = string.find(this.clickactions[modstring], "^%/(.+)")
|
|
|
|
local tswitch = UnitIsUnit(unitstr, "target")
|
|
TargetUnit(unitstr)
|
|
|
|
if is_macro then
|
|
RunMacroText(this.clickactions[modstring])
|
|
else
|
|
CastSpellByName(this.clickactions[modstring])
|
|
end
|
|
|
|
if not tswitch then TargetLastTarget() end
|
|
|
|
return
|
|
end
|
|
end
|
|
|
|
-- dropdown menus
|
|
if showmenu then
|
|
pfUI.uf:RightClickAction(label)
|
|
return
|
|
end
|
|
|
|
-- drop food on petframe
|
|
if label == "pet" and CursorHasItem() then
|
|
local _, playerClass = UnitClass("player")
|
|
if playerClass == "HUNTER" then
|
|
DropItemOnUnit("pet")
|
|
return
|
|
end
|
|
end
|
|
|
|
-- default click
|
|
TargetUnit(unitstr)
|
|
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(COOLDOWN_FRAME_TYPE, 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]:SetWidth(iconsize)
|
|
frame.icon[pos]:SetHeight(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 = UnitClass("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 = UnitClass("player")
|
|
local indicators = {}
|
|
|
|
if config.show_buffs == "1" then -- buffs
|
|
if myclass == "DRUID" then
|
|
-- Mark of the Wild
|
|
table.insert(indicators, "interface\\icons\\spell_nature_regeneration")
|
|
-- Gift of the Wild
|
|
table.insert(indicators, "interface\\icons\\spell_nature_giftofthewild")
|
|
-- Thorns
|
|
table.insert(indicators, "interface\\icons\\spell_nature_thorns")
|
|
end
|
|
|
|
if myclass == "PRIEST" then
|
|
-- Prayer Of Fortitude"
|
|
table.insert(indicators, "interface\\icons\\spell_holy_wordfortitude")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayeroffortitude")
|
|
-- Prayer of Spirit
|
|
table.insert(indicators, "interface\\icons\\spell_holy_divinespirit")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofspirit")
|
|
-- Shadow Protection
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_antishadow")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofshadowprotection")
|
|
-- Fear Ward
|
|
table.insert(indicators, "interface\\icons\\spell_holy_excorcism")
|
|
end
|
|
|
|
if myclass == "PALADIN" then
|
|
-- Blessing of Salvation
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsalvation")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofsalvation")
|
|
-- Blessing of Wisdom
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofwisdom")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofwisdom")
|
|
-- Blessing of Sanctuary
|
|
table.insert(indicators, "interface\\icons\\spell_nature_lightningshield")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsanctuary")
|
|
-- Blessing of Kings
|
|
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
|
|
table.insert(indicators, "interface\\icons\\spell_magic_greaterblessingofkings")
|
|
-- Blessing of Might
|
|
table.insert(indicators, "interface\\icons\\spell_holy_fistofjustice")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofkings")
|
|
-- Blessing of Light
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofhealing02")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingoflight")
|
|
-- Blessing of Sacrifice
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofsacrifice")
|
|
-- Blessing of Freedom
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofvalor")
|
|
-- Blessing of Protection
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofprotection")
|
|
end
|
|
|
|
if myclass == "WARLOCK" then
|
|
-- Fire Shield
|
|
table.insert(indicators, "interface\\icons\\spell_fire_firearmor")
|
|
-- Blood Pact
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_bloodboil")
|
|
-- Soulstone
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_soulgem")
|
|
-- Unending Breath
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_demonbreath")
|
|
-- Detect Greater Invisibility or Detect Invisibility
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_detectinvisibility")
|
|
-- Detect Lesser Invisibility
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_detectlesserinvisibility")
|
|
-- Paranoia
|
|
table.insert(indicators, "interface\\icons\\Spell_Shadow_AuraOfDarkness")
|
|
end
|
|
|
|
if myclass == "WARRIOR" then
|
|
-- Battle Shout
|
|
table.insert(indicators, "interface\\icons\\ability_warrior_battleshout")
|
|
-- Commanding Shout (TBC)
|
|
table.insert(indicators, "interface\\icons\\ability_warrior_rallyingcry")
|
|
end
|
|
|
|
if myclass == "MAGE" then
|
|
-- Arcane Intellect
|
|
table.insert(indicators, "interface\\icons\\spell_holy_magicalsentry")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_arcaneintellect")
|
|
-- Dampen Magic
|
|
table.insert(indicators, "interface\\icons\\spell_nature_abolishmagic")
|
|
-- Amplify Magic
|
|
table.insert(indicators, "interface\\icons\\spell_holy_flashheal")
|
|
end
|
|
|
|
if myclass == "HUNTER" then
|
|
-- Aspect of the Wild
|
|
table.insert(indicators, "interface\\icons\\spell_nature_protectionformnature")
|
|
|
|
-- Aspect of the Pack
|
|
table.insert(indicators, "interface\\icons\\ability_mount_whitetiger")
|
|
|
|
-- Misdirection (TBC)
|
|
table.insert(indicators, "interface\\icons\\ability_hunter_misdirection")
|
|
end
|
|
|
|
if myclass == "SHAMAN" then
|
|
-- Earth Shield (TBC)
|
|
table.insert(indicators, "interface\\icons\\spell_nature_skinofearth")
|
|
end
|
|
end
|
|
|
|
if config.show_procs == "1" then -- procs
|
|
if myclass == "SHAMAN" or config.all_procs == "1" then
|
|
-- Ancestral Fortitude
|
|
table.insert(indicators, "interface\\icons\\spell_nature_undyingstrength")
|
|
-- Healing Way
|
|
table.insert(indicators, "interface\\icons\\spell_nature_healingway")
|
|
-- Totemic Power (known issue: one conflicts with Blessed Sunfruit buff)
|
|
table.insert(indicators, "interface\\icons\\spell_holy_spiritualguidence")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_devotion")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_holynova")
|
|
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
|
|
end
|
|
|
|
if myclass == "PRIEST" or config.all_procs == "1" then
|
|
-- Inspiration
|
|
table.insert(indicators, "interface\\icons\\inv_shield_06")
|
|
end
|
|
end
|
|
|
|
if config.show_hots == "1" then -- hots
|
|
if myclass == "PRIEST" or config.all_hots == "1" then
|
|
-- Renew
|
|
table.insert(indicators, "interface\\icons\\spell_holy_renew")
|
|
-- Power Word: Shield
|
|
table.insert(indicators, "interface\\icons\\spell_holy_powerwordshield")
|
|
-- Prayer of Mending (TBC)
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofmendingtga")
|
|
end
|
|
|
|
if myclass == "DRUID" or config.all_hots == "1" then
|
|
-- Regrowth
|
|
table.insert(indicators, "interface\\icons\\spell_nature_resistnature")
|
|
-- Rejuvenation
|
|
table.insert(indicators, "interface\\icons\\spell_nature_rejuvenation")
|
|
-- Lifebloom
|
|
table.insert(indicators, "interface\\icons\\inv_misc_herb_felblossom")
|
|
end
|
|
end
|
|
|
|
if config.show_totems == "1" and myclass == "SHAMAN" then -- totems
|
|
-- Strength of Earth Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_earthbindtotem")
|
|
-- Stoneskin Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_stoneskintotem")
|
|
-- Mana Spring Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_manaregentotem")
|
|
-- Mana Tide Totem
|
|
table.insert(indicators, "interface\\icons\\spell_frost_summonwaterelemental")
|
|
-- Healing Spring Totem
|
|
table.insert(indicators, "interface\\icons\\inv_spear_04")
|
|
-- Tranquil Air Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_brilliance")
|
|
-- Grace of Air Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_invisibilitytotem")
|
|
-- Grounding Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_groundingtotem")
|
|
-- Nature Resistance Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_natureresistancetotem")
|
|
-- Fire Resistance Totem
|
|
table.insert(indicators, "interface\\icons\\spell_fireresistancetotem_01")
|
|
-- Frost Resistance Totem
|
|
table.insert(indicators, "interface\\icons\\spell_frostresistancetotem_01")
|
|
end
|
|
|
|
return indicators
|
|
end
|
|
|
|
local function abbrevname(t)
|
|
return string.sub(t,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 = string.gsub(name, "^(%S+) ", abbrevname)
|
|
end
|
|
|
|
-- abbreviate all if it still doesn't fit
|
|
if abbrev and name and strlen(name) > size then
|
|
name = string.gsub(name, "(%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, true)
|
|
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 = UnitManaMax(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) == 0 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
|
|
-- O(1) Nampower lookup for health gradient color
|
|
local hp, hpmax
|
|
if GetUnitField then
|
|
local guid = UnitGUID(unitstr)
|
|
if guid then
|
|
hp = GetUnitField(guid, "health")
|
|
hpmax = GetUnitField(guid, "maxHealth")
|
|
end
|
|
end
|
|
-- Fallback to standard API
|
|
if not hp or not hpmax then
|
|
hp, hpmax = UnitHealth(unitstr), UnitHealthMax(unitstr)
|
|
end
|
|
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
|
|
|
|
-- ============================================================================
|
|
-- Slash Commands for Stats Frame
|
|
-- ============================================================================
|
|
pfUI.api.RegisterSlashCommand("PFUISTATS", { "/pfuistats", "/ufstats" }, function(msg)
|
|
msg = string.lower(msg or "")
|
|
|
|
if not pfUI.uf.stats then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000ERROR:|r Stats not initialized!")
|
|
return
|
|
end
|
|
|
|
-- Initialize startTime on first use
|
|
if pfUI.uf.stats.startTime == 0 then
|
|
pfUI.uf.stats.startTime = GetTime()
|
|
end
|
|
|
|
if msg == "reset" then
|
|
pfUI.uf.stats.eventUpdates = 0
|
|
pfUI.uf.stats.heartbeatUpdates = 0
|
|
pfUI.uf.stats.earlyReturns = 0
|
|
pfUI.uf.stats.nampowerUsed = 0
|
|
pfUI.uf.stats.fallbackUsed = 0
|
|
pfUI.uf.stats.throttledSkips = 0
|
|
pfUI.uf.stats.startTime = GetTime()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00pfUI Stats:|r Reset!")
|
|
|
|
elseif msg == "toggle" then
|
|
pfUI.uf.stats.enabled = not pfUI.uf.stats.enabled
|
|
local status = pfUI.uf.stats.enabled and "|cff00ff00ON|r" or "|cffff0000OFF|r"
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00pfUI Stats:|r Tracking: " .. status)
|
|
|
|
elseif msg == "show" then
|
|
if pfUI.uf.statsFrame then
|
|
pfUI.uf.statsFrame:Show()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00pfUI Stats:|r Frame shown")
|
|
end
|
|
|
|
elseif msg == "hide" then
|
|
if pfUI.uf.statsFrame then
|
|
pfUI.uf.statsFrame:Hide()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00pfUI Stats:|r Frame hidden")
|
|
end
|
|
|
|
else
|
|
-- Toggle frame (default action)
|
|
if pfUI.uf.statsFrame then
|
|
if pfUI.uf.statsFrame:IsShown() then
|
|
pfUI.uf.statsFrame:Hide()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00pfUI Stats:|r Frame hidden")
|
|
else
|
|
pfUI.uf.statsFrame:Show()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00pfUI Stats:|r Frame shown")
|
|
end
|
|
end
|
|
end
|
|
end, true) |