mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-22 07:36:56 +00:00
d75e280238
When "Use Raid Frames To Display Group Members" is on and in a party, raid pet frames tried to show raidpet1..40 (nonexistent in a party) instead of the player pet and partypet1..4. Remap raidpet grid slots to pet / partypet<N> in UpdateVisibility, keyed on a stable pfRaidPet<N> slot, mirroring the raid player frames. LayoutPets now mirror-places the party pet slots, and the raid updater runs LayoutPets when in a party (not just a raid). Also exclude raid pet frames (label "partypet", cache_raidpet set) from the hide_in_raid group-hiding clause so they are not hidden.
2719 lines
99 KiB
Lua
2719 lines
99 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
|
|
if pfUI.uf.raid and pfUI.uf.raid.LayoutPets then pfUI.uf.raid:LayoutPets() end
|
|
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 - health + power for a unit token.
|
|
-- Returns: hp, maxHp, power, maxPower, powerType
|
|
-- ============================================================================
|
|
function pfUI.api.GetUnitStats(unitstr)
|
|
local powerType = UnitPowerType(unitstr) or 0
|
|
return UnitHealth(unitstr) or 0,
|
|
UnitHealthMax(unitstr) or 1,
|
|
UnitPower(unitstr, powerType) or 0,
|
|
UnitPowerMax(unitstr, powerType) or 1,
|
|
powerType
|
|
end
|
|
|
|
local aggrodata = { }
|
|
local aggroScan = nil -- static { u, t, tt } triples, built once so the hot scan allocates nothing
|
|
function pfUI.api.UnitHasAggro(unit)
|
|
local now = GetTime()
|
|
local data = aggrodata[unit]
|
|
-- Cache positive results 1s (so aggro clears fast) AND negative results 0.3s
|
|
-- (so we don't rescan the whole unit table on every call while nothing has aggro).
|
|
if data and now < data.check + (data.state > 0 and 1 or 0.3) then
|
|
return data.state
|
|
end
|
|
|
|
if not data then data = { }; aggrodata[unit] = data end
|
|
data.check = now
|
|
data.state = 0
|
|
|
|
if UnitExists(unit) and UnitIsFriend(unit, "player") then
|
|
-- pfValidUnits never changes after load, so precompute the "<u>target" /
|
|
-- "<u>targettarget" token strings once instead of concatenating per call.
|
|
if not aggroScan then
|
|
aggroScan = {}
|
|
for u in pairs(pfValidUnits) do
|
|
local t = u .. "target"
|
|
table.insert(aggroScan, { u = u, t = t, tt = t .. "target" })
|
|
end
|
|
end
|
|
|
|
for i = 1, table.getn(aggroScan) do
|
|
local s = aggroScan[i]
|
|
if UnitExists(s.t) and UnitIsUnit(s.t, unit) and UnitCanAttack(s.u, unit) then
|
|
data.state = data.state + 1
|
|
end
|
|
if UnitExists(s.tt) and UnitIsUnit(s.tt, unit) and UnitCanAttack(s.t, unit) then
|
|
data.state = data.state + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return data.state
|
|
end
|
|
|
|
pfUI.uf.glow = CreateFrame("Frame", nil, UIParent)
|
|
pfUI.uf.glow:SetScript("OnUpdate", function()
|
|
local fpsmod = GetFramerate() / 30
|
|
if not this.val or this.val >= .8 then
|
|
this.mod = -0.01 / fpsmod
|
|
elseif this.val <= .4 then
|
|
this.mod = 0.01 / fpsmod
|
|
end
|
|
this.val = this.val + this.mod
|
|
end)
|
|
|
|
pfUI.uf.glow.mod = 0
|
|
pfUI.uf.glow.val = 0.6
|
|
|
|
function pfUI.uf.glow.UpdateGlowAnimation()
|
|
local val = pfUI.uf.glow.val or 0.6
|
|
if val < 0.4 then val = 0.4 end
|
|
if val > 0.8 then val = 0.8 end
|
|
this:SetAlpha(val)
|
|
end
|
|
|
|
function pfUI.uf:UpdateVisibility()
|
|
local self = self or this
|
|
|
|
-- cache result of strsub to avoid repeating calls
|
|
if not self.cache_raid then
|
|
local name = self:GetName()
|
|
if strsub(name,0,9) == "pfRaidPet" then
|
|
-- pet grid slot (pfRaidPet1..40); used to mirror party pets below
|
|
self.cache_raid = 0
|
|
self.cache_raidpet = tonumber(strsub(name,10)) or 0
|
|
elseif strsub(name,0,6) == "pfRaid" then
|
|
self.cache_raid = tonumber(strsub(name,7,8)) or 0
|
|
else
|
|
self.cache_raid = 0
|
|
end
|
|
end
|
|
|
|
-- show groupframes as raid
|
|
if self.cache_raid > 0 then
|
|
local id = self.cache_raid
|
|
|
|
-- always show self in raidframes
|
|
if not IsInGroup() and C.unitframes.selfinraid == "1" and id == 1 then
|
|
self.id = ""
|
|
self.label = "player"
|
|
|
|
-- use raidframes for groups
|
|
elseif not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
|
|
if id == 1 then
|
|
self.id = ""
|
|
self.label = "player"
|
|
elseif id <= 5 then
|
|
self.id = id - 1
|
|
self.label = "party"
|
|
end
|
|
|
|
-- reset to regular raid unitstrings
|
|
elseif self.label == "party" or self.label == "player" then
|
|
self.id = id
|
|
self.label = "raid"
|
|
end
|
|
end
|
|
|
|
-- show raidpet frames as party pets when a party is shown as a raid grid
|
|
if self.cache_raidpet then
|
|
if not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
|
|
local id = self.cache_raidpet
|
|
if id == 1 then
|
|
-- grid slot 1 mirrors the player, so its pet is the player's pet
|
|
self.id = ""
|
|
self.label = "pet"
|
|
elseif id <= 5 then
|
|
self.id = id - 1
|
|
self.label = "partypet"
|
|
end
|
|
|
|
-- reset to regular raidpet unitstrings after leaving party mode
|
|
elseif self.label == "pet" or self.label == "partypet" then
|
|
self.id = self.cache_raidpet
|
|
self.label = "raidpet"
|
|
end
|
|
end
|
|
|
|
-- display every unit as player while pfUI.uf.showall is set
|
|
if pfUI.uf.showall then
|
|
self._label = self._label or self.label
|
|
self._id = self._id or self.id
|
|
self.label, self.id = "player", ""
|
|
elseif not pfUI.uf.showall and self._label and self._id then
|
|
self.label, self.id = self._label, self._id
|
|
self._label, self._id = nil, nil
|
|
end
|
|
|
|
local unitstr = string.format("%s%s", self.label or "", self.id or "")
|
|
self:SetAttribute("unit", unitstr ~= "" and unitstr or nil)
|
|
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
|
|
|
|
-- Group frames are redundant when the group is already shown as a raid grid:
|
|
-- either an actual raid, or a party promoted to the raid grid via
|
|
-- raidforgroup. hide_in_raid gates whether we hide them in that case.
|
|
local hide_group = C["unitframes"]["group"]["hide_in_raid"] == "1"
|
|
and (IsInRaid() or (C.unitframes.raidforgroup == "1" and IsInGroup()))
|
|
|
|
if pfUI.unlock and pfUI.unlock:IsShown() then
|
|
-- display during unlock mode
|
|
visibility = "show"
|
|
self.visible = true
|
|
elseif self.config.visible == "0" then
|
|
-- frame shall not be visible
|
|
visibility = "hide"
|
|
self.visible = nil
|
|
elseif hide_group and self.cache_raid == 0 and not self.cache_raidpet and self.label and strsub(self.label,0,5) == "party" then
|
|
-- hide group while shown as a raid grid and option is set (raid player
|
|
-- frames carry label "party" and raid pet frames carry label "partypet"
|
|
-- under raidforgroup, so exclude them by cache_raid / cache_raidpet)
|
|
visibility = "hide"
|
|
self.visible = nil
|
|
elseif ( self.fname == "Group0" or self.fname == "PartyPet0" or self.fname == "Party0Target" )
|
|
and (not IsInGroup() or hide_group) then
|
|
-- hide self in group if solo or shown as a raid grid
|
|
visibility = "hide"
|
|
self.visible = nil
|
|
end
|
|
|
|
-- vanilla visibility
|
|
if self.unitname then
|
|
self:Show()
|
|
elseif visibility == "hide" then
|
|
self:Hide()
|
|
elseif visibility == "show" then
|
|
self:Show()
|
|
else
|
|
if UnitName(unitstr) then
|
|
-- hide existing but too far away pet and pets of old group members
|
|
if self.label == "partypet" then
|
|
if not UnitIsVisible(unitstr) or not UnitExists("party" .. self.id) then
|
|
self:Hide()
|
|
return
|
|
end
|
|
elseif self.label == "raidpet" then
|
|
if not UnitIsVisible(unitstr) or not UnitExists("raid" .. self.id) then
|
|
self:Hide()
|
|
return
|
|
end
|
|
elseif self.label == "pettarget" then
|
|
if not UnitIsVisible(unitstr) or not UnitExists("pet") then
|
|
self:Hide()
|
|
return
|
|
end
|
|
end
|
|
self:Show()
|
|
else
|
|
self.lastUnit = nil
|
|
self:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:UpdateFrameSize()
|
|
local rawborder, default_border = GetBorderSize("unitframes")
|
|
local spacing = self.config.pspace * GetPerfectPixel()
|
|
local width = self.config.width
|
|
local height = self.config.height
|
|
local pheight = self.config.pheight
|
|
local ptwidth = self.config.portraitwidth
|
|
local ptheight = self.config.portraitheight
|
|
|
|
local real_height = height + spacing + pheight + 2*default_border
|
|
if spacing ~= abs(spacing) and abs(spacing) > tonumber(pheight) then
|
|
real_height = height
|
|
spacing = 0
|
|
end
|
|
|
|
local portrait = 0
|
|
|
|
if self.config.portrait == "left" or self.config.portrait == "right" then
|
|
if ptwidth == "-1" and ptheight == "-1" then
|
|
-- align portrait size to frame
|
|
self.portrait:SetSize(real_height, real_height)
|
|
portrait = real_height + spacing + 2*default_border
|
|
else
|
|
-- use custom portrait size
|
|
self.portrait:SetSize(ptwidth, ptheight)
|
|
portrait = ptwidth + spacing + 2*default_border
|
|
end
|
|
end
|
|
|
|
self:SetSize(width + portrait, real_height)
|
|
end
|
|
|
|
function pfUI.uf:UpdateConfig()
|
|
local f = self
|
|
local C = pfUI_config
|
|
local rawborder, default_border = GetBorderSize("unitframes")
|
|
local spacing = f.config.pspace * GetPerfectPixel()
|
|
|
|
local cooldown_text = tonumber(f.config.cooldown_text)
|
|
local cooldown_anim = tonumber(f.config.cooldown_anim)
|
|
|
|
local relative_point = "BOTTOM"
|
|
if f.config.panchor == "TOPLEFT" then
|
|
relative_point = "BOTTOMLEFT"
|
|
elseif f.config.panchor == "TOPRIGHT" then
|
|
relative_point = "BOTTOMRIGHT"
|
|
end
|
|
|
|
f.dispellable = nil
|
|
f.indicators = nil
|
|
f.indicator_custom = nil
|
|
|
|
f.alpha_visible = tonumber(f.config.alpha_visible)
|
|
f.alpha_outrange = tonumber(f.config.alpha_outrange)
|
|
f.alpha_offline = tonumber(f.config.alpha_offline)
|
|
|
|
f:SetFrameStrata("MEDIUM")
|
|
|
|
f.glow:SetFrameStrata("BACKGROUND")
|
|
f.glow:SetFrameLevel(0)
|
|
f.glow:SetBackdrop(glow2)
|
|
f.glow:SetPoint("TOPLEFT", f, "TOPLEFT", -6 - default_border,6 + default_border)
|
|
f.glow:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 6 + default_border,-6 - default_border)
|
|
f.glow:SetScript("OnUpdate", pfUI.uf.glow.UpdateGlowAnimation)
|
|
f.glow:Hide()
|
|
|
|
f.combat:SetSize(tonumber(f.config.squaresize), tonumber(f.config.squaresize))
|
|
f.combat:ClearAllPoints()
|
|
f.combat:SetPoint(f.config.squarepos, 0, 0)
|
|
f.combat:Hide()
|
|
|
|
f.hp:ClearAllPoints()
|
|
f.hp:SetPoint("TOP", 0, 0)
|
|
|
|
f.hp:SetSize(f.config.width, f.config.height)
|
|
if tonumber(f.config.height) < 0 then f.hp:Hide() end
|
|
pfUI.api.CreateBackdrop(f.hp, default_border)
|
|
|
|
f.hp.bar:SetStatusBarTexture(pfUI.media[f.config.bartexture])
|
|
f.hp.bar:SetAllPoints(f.hp)
|
|
if f.config.verticalbar == "1" then
|
|
f.hp.bar:SetOrientation("VERTICAL")
|
|
else
|
|
f.hp.bar:SetOrientation("HORIZONTAL")
|
|
end
|
|
|
|
local custombg = f.config.defcolor == "0" and f.config.custombg or C.unitframes.custombg
|
|
local custombgcolor = f.config.defcolor == "0" and f.config.custombgcolor or C.unitframes.custombgcolor
|
|
|
|
if custombg == "1" then
|
|
local cr, cg, cb, ca = GetStringColor(custombgcolor)
|
|
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca)
|
|
f.hp.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca)
|
|
end
|
|
|
|
f.power:ClearAllPoints()
|
|
f.power:SetPoint(f.config.panchor, f.hp, relative_point, f.config.poffx, -2 * default_border - spacing + f.config.poffy * GetPerfectPixel())
|
|
f.power:SetSize((f.config.pwidth ~= "-1" and f.config.pwidth or f.config.width), f.config.pheight)
|
|
if tonumber(f.config.pheight) < 0 then f.power:Hide() end
|
|
|
|
pfUI.api.CreateBackdrop(f.power, default_border)
|
|
f.power.bar:SetFrameLevel(f.power.backdrop:GetFrameLevel() + 1)
|
|
f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
|
|
f.power.bar:SetAllPoints(f.power)
|
|
|
|
local custompbg = f.config.defcolor == "0" and f.config.custompbg or C.unitframes.custompbg
|
|
local custompbgcolor = f.config.defcolor == "0" and f.config.custompbgcolor or C.unitframes.custompbgcolor
|
|
|
|
if custompbg == "1" then
|
|
local cr, cg, cb, ca = GetStringColor(custompbgcolor)
|
|
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca)
|
|
f.power.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca)
|
|
end
|
|
|
|
local fontname, fontsize, fontstyle
|
|
if f.config.customfont == "1" then
|
|
fontname = pfUI.media[f.config.customfont_name]
|
|
fontsize = tonumber(f.config.customfont_size)
|
|
fontstyle = f.config.customfont_style
|
|
else
|
|
fontname = pfUI.font_unit
|
|
fontsize = tonumber(C.global.font_unit_size)
|
|
fontstyle = C.global.font_unit_style
|
|
end
|
|
|
|
-- Druid secondary mana bar: texture/color/size/position below the power bar,
|
|
-- using its own C.unitframes.druidmana* config. Values are read in
|
|
-- UpdateDruidMana; here we only lay it out.
|
|
if f.druidmana then
|
|
local DC = C.unitframes
|
|
local dmTexture = DC.druidmanatexture or "Interface\\AddOns\\pfUI\\img\\bar"
|
|
f.druidmana:SetStatusBarTexture(pfUI.media[dmTexture] or dmTexture)
|
|
f.druidmana:SetFrameLevel(f:GetFrameLevel() + 5)
|
|
|
|
local manacolor = f.config.defcolor == "0" and f.config.manacolor or C.unitframes.manacolor
|
|
f.druidmana:SetStatusBarColor(GetStringColor(manacolor))
|
|
|
|
local dmHeight = tonumber(DC.druidmanaheight) or 10
|
|
local dmWidth = DC.druidmanawidth or "-1"
|
|
local dmOffX = tonumber(DC.druidmanaoffx) or 0
|
|
local dmOffY = tonumber(DC.druidmanaoffy) or 0
|
|
local dmSpace = tonumber(DC.druidmanaspace) or -3
|
|
local dmSpacing = -2 * default_border - dmSpace
|
|
|
|
f.druidmana:SetHeight(dmHeight)
|
|
f.druidmana:ClearAllPoints()
|
|
local w = dmWidth ~= "-1" and tonumber(dmWidth) or nil
|
|
if w then
|
|
f.druidmana:SetWidth(w)
|
|
f.druidmana:SetPoint("TOP", f.power, "BOTTOM", dmOffX, dmSpacing + dmOffY)
|
|
else
|
|
f.druidmana:SetPoint("TOPLEFT", f.power, "BOTTOMLEFT", dmOffX, dmSpacing + dmOffY)
|
|
f.druidmana:SetPoint("TOPRIGHT", f.power, "BOTTOMRIGHT", dmOffX, dmSpacing + dmOffY)
|
|
end
|
|
|
|
if not f.druidmana._hasbd then
|
|
CreateBackdrop(f.druidmana, default_border)
|
|
CreateBackdropShadow(f.druidmana)
|
|
f.druidmana._hasbd = true
|
|
end
|
|
|
|
local tr, tg, tb = ManaBarColor[Enum.PowerType.Mana].r, ManaBarColor[Enum.PowerType.Mana].g, ManaBarColor[Enum.PowerType.Mana].b
|
|
if C.unitframes.pastel == "1" then
|
|
tr, tg, tb = (tr + .75) * .5, (tg + .75) * .5, (tb + .75) * .5
|
|
end
|
|
f.druidmana.text:SetFont(fontname, fontsize, fontstyle)
|
|
f.druidmana.text:SetTextColor(tr, tg, tb, 1)
|
|
end
|
|
|
|
f.portrait.tex:SetAllPoints(f.portrait)
|
|
f.portrait.tex:SetTexCoord(.1, .9, .1, .9)
|
|
f.portrait.model:SetAllPoints(f.portrait)
|
|
|
|
if f.config.portrait == "bar" then
|
|
f.portrait:SetParent(f.hp.bar)
|
|
f.portrait:SetAllPoints(f.hp.bar)
|
|
|
|
f.portrait:SetAlpha(C.unitframes.portraitalpha)
|
|
if f.portrait.backdrop then f.portrait.backdrop:Hide() end
|
|
|
|
-- place portrait below fonts
|
|
f.portrait.model:SetFrameLevel(3)
|
|
|
|
f.portrait:Show()
|
|
elseif f.config.portrait == "left" then
|
|
f.portrait:SetParent(f)
|
|
f.portrait:ClearAllPoints()
|
|
f.portrait:SetPoint("LEFT", f, "LEFT", 0, 0)
|
|
|
|
f.hp:ClearAllPoints()
|
|
f.hp:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
|
|
|
|
f.portrait:SetAlpha(f:GetAlpha())
|
|
|
|
-- make sure incHeal is above
|
|
f.portrait:SetFrameStrata("BACKGROUND")
|
|
f.portrait.model:SetFrameStrata("BACKGROUND")
|
|
f.portrait.model:SetFrameLevel(1)
|
|
|
|
pfUI.api.CreateBackdrop(f.portrait, default_border)
|
|
f.portrait.backdrop:Show()
|
|
f.portrait:Show()
|
|
elseif f.config.portrait == "right" then
|
|
f.portrait:SetParent(f)
|
|
f.portrait:ClearAllPoints()
|
|
f.portrait:SetPoint("RIGHT", f, "RIGHT", 0, 0)
|
|
|
|
f.hp:ClearAllPoints()
|
|
f.hp:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
|
|
|
f.portrait:SetAlpha(f:GetAlpha())
|
|
|
|
-- make sure incHeal is above
|
|
f.portrait:SetFrameStrata("BACKGROUND")
|
|
f.portrait.model:SetFrameStrata("BACKGROUND")
|
|
f.portrait.model:SetFrameLevel(1)
|
|
|
|
pfUI.api.CreateBackdrop(f.portrait, default_border)
|
|
f.portrait.backdrop:Show()
|
|
f.portrait:Show()
|
|
else
|
|
f.portrait:Hide()
|
|
end
|
|
|
|
if f.group then
|
|
f.group:SetShown(f.config.raidgrouplabel == "1")
|
|
|
|
local xoff = tonumber(f.config.grouplabelxoff) or 0
|
|
local yoff = tonumber(f.config.grouplabelyoff) or 8
|
|
f.group:SetPoint("TOPLEFT", f, "BOTTOMLEFT", xoff, yoff)
|
|
f.group:SetPoint("TOPRIGHT", f, "BOTTOMRIGHT", xoff, yoff)
|
|
end
|
|
|
|
if f.config.hitindicator == "1" then
|
|
f.feedbackText:SetFont(pfUI.media[f.config.hitindicatorfont], f.config.hitindicatorsize, "OUTLINE")
|
|
f.feedbackFontHeight = f.config.hitindicatorsize
|
|
f.feedbackStartTime = GetTime()
|
|
if f.config.portrait == "bar" or f.config.portrait == "off" then
|
|
f.feedbackText:SetParent(f.texts)
|
|
f.feedbackText:ClearAllPoints()
|
|
f.feedbackText:SetPoint("CENTER", f.hp.bar, "CENTER")
|
|
else
|
|
f.feedbackText:SetParent(f.portrait)
|
|
f.feedbackText:ClearAllPoints()
|
|
f.feedbackText:SetPoint("CENTER", f.portrait, "CENTER")
|
|
end
|
|
f:RegisterEvent("UNIT_COMBAT")
|
|
else
|
|
f.feedbackText:Hide()
|
|
f:UnregisterEvent("UNIT_COMBAT")
|
|
end
|
|
|
|
f.hpLeftText:SetFontObject(GameFontWhite)
|
|
f.hpLeftText:SetFont(fontname, fontsize, fontstyle)
|
|
f.hpLeftText:SetJustifyH("LEFT")
|
|
f.hpLeftText:ClearAllPoints()
|
|
f.hpLeftText:SetPoint("TOPLEFT",f.hp.bar, "TOPLEFT", 2*(default_border + f.config.txthpleftoffx), 1 + tonumber(f.config.txthpleftoffy))
|
|
f.hpLeftText:SetPoint("BOTTOMRIGHT",f.hp.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txthpleftoffx), f.config.txthpleftoffy)
|
|
|
|
f.hpRightText:SetFontObject(GameFontWhite)
|
|
f.hpRightText:SetFont(fontname, fontsize, fontstyle)
|
|
f.hpRightText:SetJustifyH("RIGHT")
|
|
f.hpRightText:ClearAllPoints()
|
|
f.hpRightText:SetPoint("TOPLEFT",f.hp.bar, "TOPLEFT", 2*(default_border + f.config.txthprightoffx), 1 + tonumber(f.config.txthprightoffy))
|
|
f.hpRightText:SetPoint("BOTTOMRIGHT",f.hp.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txthprightoffx), f.config.txthprightoffy)
|
|
|
|
f.hpCenterText:SetFontObject(GameFontWhite)
|
|
f.hpCenterText:SetFont(fontname, fontsize, fontstyle)
|
|
f.hpCenterText:SetJustifyH("CENTER")
|
|
f.hpCenterText:ClearAllPoints()
|
|
f.hpCenterText:SetPoint("TOPLEFT",f.hp.bar, "TOPLEFT", f.config.txthpcenteroffx, 1 + tonumber(f.config.txthpcenteroffy))
|
|
f.hpCenterText:SetPoint("BOTTOMRIGHT",f.hp.bar, "BOTTOMRIGHT", f.config.txthpcenteroffx, f.config.txthpcenteroffy)
|
|
|
|
f.powerLeftText:SetFontObject(GameFontWhite)
|
|
f.powerLeftText:SetFont(fontname, fontsize, fontstyle)
|
|
f.powerLeftText:SetJustifyH("LEFT")
|
|
f.powerLeftText:ClearAllPoints()
|
|
f.powerLeftText:SetPoint("TOPLEFT",f.power.bar, "TOPLEFT", 2*(default_border + f.config.txtpowerleftoffx), 1 + tonumber(f.config.txtpowerleftoffy))
|
|
f.powerLeftText:SetPoint("BOTTOMRIGHT",f.power.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txtpowerleftoffx), f.config.txtpowerleftoffy)
|
|
|
|
f.powerRightText:SetFontObject(GameFontWhite)
|
|
f.powerRightText:SetFont(fontname, fontsize, fontstyle)
|
|
f.powerRightText:SetJustifyH("RIGHT")
|
|
f.powerRightText:ClearAllPoints()
|
|
f.powerRightText:SetPoint("TOPLEFT",f.power.bar, "TOPLEFT", 2*(default_border + f.config.txtpowerrightoffx), 1 + tonumber(f.config.txtpowerrightoffy))
|
|
f.powerRightText:SetPoint("BOTTOMRIGHT",f.power.bar, "BOTTOMRIGHT", -2*(default_border + f.config.txtpowerrightoffx), f.config.txtpowerrightoffy)
|
|
|
|
f.powerCenterText:SetFontObject(GameFontWhite)
|
|
f.powerCenterText:SetFont(fontname, fontsize, fontstyle)
|
|
f.powerCenterText:SetJustifyH("CENTER")
|
|
f.powerCenterText:ClearAllPoints()
|
|
f.powerCenterText:SetPoint("TOPLEFT",f.power.bar, "TOPLEFT", f.config.txtpowercenteroffx, 1 + tonumber(f.config.txtpowercenteroffy))
|
|
f.powerCenterText:SetPoint("BOTTOMRIGHT",f.power.bar, "BOTTOMRIGHT", f.config.txtpowercenteroffx, f.config.txtpowercenteroffy)
|
|
|
|
f.incHeal:SetSize(f.config.width, f.config.height)
|
|
f.incHeal.texture:SetTexture(pfUI.media["img:bar"])
|
|
local cr, cg, cb, ca = GetStringColor(f.config.healcolor)
|
|
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca)
|
|
f.incHeal.texture:SetVertexColor(cr, cg, cb, ca)
|
|
f.incHeal:Hide()
|
|
|
|
if f.config.verticalbar == "0" then
|
|
f.incHeal:ClearAllPoints()
|
|
f.incHeal:SetPoint("TOPLEFT", f.hp.bar, "TOPLEFT", 0, 0)
|
|
else
|
|
f.incHeal:ClearAllPoints()
|
|
f.incHeal:SetPoint("BOTTOM", f.hp.bar, "BOTTOM", 0, 0)
|
|
end
|
|
|
|
f.ressIcon:SetFrameLevel(16)
|
|
f.ressIcon:SetSize(32, 32)
|
|
f.ressIcon:SetPoint("CENTER", f, "CENTER", 0, 4)
|
|
f.ressIcon.texture:SetTexture(pfUI.media["img:ress"])
|
|
f.ressIcon.texture:SetAllPoints(f.ressIcon)
|
|
f.ressIcon:Hide()
|
|
|
|
f.leaderIcon:SetSize(10, 10)
|
|
f.leaderIcon:SetPoint("CENTER", f, "TOPLEFT", 0, 0)
|
|
f.leaderIcon.texture:SetTexture("Interface\\GROUPFRAME\\UI-Group-LeaderIcon")
|
|
f.leaderIcon.texture:SetAllPoints(f.leaderIcon)
|
|
f.leaderIcon:Hide()
|
|
|
|
f.lootIcon:SetSize(10, 10)
|
|
f.lootIcon:SetPoint("CENTER", f, "LEFT", 0, 0)
|
|
f.lootIcon.texture:SetTexture("Interface\\GROUPFRAME\\UI-Group-MasterLooter")
|
|
f.lootIcon.texture:SetAllPoints(f.lootIcon)
|
|
f.lootIcon:Hide()
|
|
|
|
f.pvpIcon:SetSize(f.config.pvpiconsize, f.config.pvpiconsize)
|
|
f.pvpIcon:SetPoint(f.config.pvpiconalign, f, f.config.pvpiconalign, f.config.pvpiconoffx, f.config.pvpiconoffy)
|
|
f.pvpIcon.texture:SetTexture(pfUI.media["img:pvp"])
|
|
f.pvpIcon.texture:SetAllPoints(f.pvpIcon)
|
|
f.pvpIcon.texture:SetVertexColor(1,1,1,.5)
|
|
f.pvpIcon:Hide()
|
|
|
|
f.raidIcon:SetSize(f.config.raidiconsize, f.config.raidiconsize)
|
|
f.raidIcon:SetPoint("CENTER", f, f.config.raidiconalign, f.config.raidiconoffx, f.config.raidiconoffy)
|
|
local raidIconTex = C.unitframes.blizzard_raidicons == "1" and "Interface\\TargetingFrame\\UI-RaidTargetingIcons" or pfUI.media["img:raidicons"]
|
|
f.raidIcon.texture:SetTexture(raidIconTex)
|
|
f.raidIcon.texture:SetAllPoints(f.raidIcon)
|
|
f.raidIcon:Hide()
|
|
|
|
f.restIcon:SetSize(16, 16)
|
|
f.restIcon:SetPoint("TOP", f, "TOPLEFT", 0, -1)
|
|
f.restIcon.texture:SetTexture("Interface\\CharacterFrame\\UI-StateIcon", true)
|
|
f.restIcon.texture:SetTexCoord(0, .5, 0, .421875)
|
|
f.restIcon.texture:SetAllPoints(f.restIcon)
|
|
f.restIcon:Hide()
|
|
|
|
f.happinessIcon:SetSize(tonumber(C.unitframes.pet.happinesssize), tonumber(C.unitframes.pet.happinesssize))
|
|
f.happinessIcon:SetPoint("CENTER", f, "TOPLEFT", default_border, -default_border)
|
|
f.happinessIcon.texture:SetTexture(pfUI.media["img:neutral"])
|
|
f.happinessIcon.texture:SetAllPoints(f.happinessIcon)
|
|
f.happinessIcon.texture:SetVertexColor(1, 1, 0, 1)
|
|
f.happinessIcon:Hide()
|
|
|
|
if f.config.buffs == "off" then
|
|
for i=1, 32 do
|
|
if f.buffs and f.buffs[i] then
|
|
f.buffs[i]:Hide()
|
|
f.buffs[i] = nil
|
|
end
|
|
end
|
|
f.buffs = nil
|
|
else
|
|
f.buffs = f.buffs or {}
|
|
|
|
for i=1, 32 do
|
|
if i > tonumber(f.config.bufflimit) then break end
|
|
|
|
local perrow = f.config.buffperrow
|
|
local row = floor((i-1) / perrow)
|
|
|
|
f.buffs[i] = f.buffs[i] or CreateFrame("Button", "pfUI" .. f.fname .. "Buff" .. i, f)
|
|
f.buffs[i].texture = f.buffs[i].texture or f.buffs[i]:CreateTexture()
|
|
f.buffs[i].texture:SetTexCoord(.08, .92, .08, .92)
|
|
f.buffs[i].texture:SetAllPoints()
|
|
f.buffs[i].stacks = f.buffs[i].stacks or f.buffs[i]:CreateFontString(nil, "OVERLAY", f.buffs[i])
|
|
f.buffs[i].stacks:SetFont(pfUI.font_unit, C.global.font_unit_size, "OUTLINE")
|
|
f.buffs[i].stacks:SetPoint("BOTTOMRIGHT", f.buffs[i], 2, -2)
|
|
f.buffs[i].stacks:SetJustifyH("LEFT")
|
|
f.buffs[i].stacks:SetShadowColor(0, 0, 0)
|
|
f.buffs[i].stacks:SetShadowOffset(0.8, -0.8)
|
|
f.buffs[i].stacks:SetTextColor(1,1,.5)
|
|
|
|
f.buffs[i]:SetFrameLevel(12)
|
|
f.buffs[i]:RegisterForClicks("RightButtonUp")
|
|
f.buffs[i]:ClearAllPoints()
|
|
|
|
local invert_h, invert_v, af
|
|
if f.config.buffs == "TOPLEFT" then
|
|
invert_h = 1
|
|
invert_v = 1
|
|
af = "BOTTOMLEFT"
|
|
elseif f.config.buffs == "BOTTOMLEFT" then
|
|
invert_h = -1
|
|
invert_v = 1
|
|
af = "TOPLEFT"
|
|
elseif f.config.buffs == "TOPRIGHT" then
|
|
invert_h = 1
|
|
invert_v = -1
|
|
af = "BOTTOMRIGHT"
|
|
elseif f.config.buffs == "BOTTOMRIGHT" then
|
|
invert_h = -1
|
|
invert_v = -1
|
|
af = "TOPRIGHT"
|
|
end
|
|
|
|
local anchor = f.config.portraitheight ~= "-1" and f.hp or f
|
|
if anchor == f.hp and (f.config.buffs == "BOTTOMLEFT" or f.config.buffs == "BOTTOMRIGHT") then
|
|
anchor = f.power
|
|
end
|
|
local multiply = C.appearance.border.force_blizz == "1" and 1 or 2
|
|
f.buffs[i]:SetPoint(af, anchor, f.config.buffs,
|
|
invert_v * (i-1-row*perrow)*(multiply*default_border + f.config.buffsize + 1),
|
|
invert_h * (row*(multiply*default_border + f.config.buffsize + 1) + (multiply*default_border + 1)))
|
|
|
|
f.buffs[i]:SetSize(f.config.buffsize, f.config.buffsize)
|
|
|
|
-- Create CD frame if it doesn't exist
|
|
if not f.buffs[i].cd then
|
|
if cooldown_anim == 1 then
|
|
-- Animation enabled: Use Model frame with CooldownFrameTemplate
|
|
f.buffs[i].cd = CreateFrame(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]:SetSize(f.config.debuffsize, f.config.debuffsize)
|
|
f.debuffs[i]:SetNormalTexture(nil)
|
|
|
|
-- Create CD frame if it doesn't exist
|
|
if not f.debuffs[i].cd then
|
|
if cooldown_anim == 1 then
|
|
-- Animation enabled: Use Model frame with CooldownFrameTemplate
|
|
f.debuffs[i].cd = CreateFrame(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
|
|
f.debuffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1)
|
|
end
|
|
|
|
f.debuffs[i].id = i
|
|
f.debuffs[i]:Hide()
|
|
|
|
CreateBackdrop(f.debuffs[i], default_border)
|
|
|
|
f.debuffs[i]:SetScript("OnEnter", DebuffOnEnter)
|
|
f.debuffs[i]:SetScript("OnLeave", DebuffOnLeave)
|
|
f.debuffs[i]:SetScript("OnClick", DebuffOnClick)
|
|
end
|
|
end
|
|
|
|
if f.config.visible == "1" then
|
|
pfUI.uf:RefreshUnit(f, "all")
|
|
f:EnableScripts()
|
|
f:EnableEvents()
|
|
f:UpdateFrameSize()
|
|
else
|
|
f:UnregisterAllEvents()
|
|
f:Hide()
|
|
end
|
|
end
|
|
|
|
function pfUI.uf.OnShow()
|
|
pfUI.uf:RefreshUnit(this, "portrait")
|
|
pfUI.uf:RefreshUnit(this, "base")
|
|
end
|
|
|
|
-- GUID-keyed aura start-time tracker, fed by Nampower's BUFF_ADDED_OTHER /
|
|
-- BUFF_REMOVED_OTHER. Non-player units have aura.expirationTime == 0 (server
|
|
-- doesn't broadcast time), so we reconstruct the swirl from "when we saw it
|
|
-- applied" + aura.duration (Spell.dbc base). Only auras we actually witnessed
|
|
-- being cast get tracked — pre-existing auras (already on a unit when you
|
|
-- target them) intentionally show no timer rather than a fabricated one.
|
|
pfUI.uf.aura_starts = {} -- {[guid] = {[spellId] = startTime}}
|
|
|
|
local auraTracker = CreateFrame("Frame", "pfUF_AuraTracker", UIParent)
|
|
auraTracker:RegisterEvent("BUFF_ADDED_OTHER")
|
|
auraTracker:RegisterEvent("BUFF_REMOVED_OTHER")
|
|
auraTracker:SetScript("OnEvent", function()
|
|
-- Nampower arg layout: arg1=guid, arg3=spellId. State code 0=added, 1=removed,
|
|
-- 2=modified; we treat any added/modified as "reset start = now" and let the
|
|
-- REMOVED event clear the entry.
|
|
if not arg1 or not arg3 or arg3 == 0 then return end
|
|
if event == "BUFF_REMOVED_OTHER" then
|
|
if pfUI.uf.aura_starts[arg1] then
|
|
pfUI.uf.aura_starts[arg1][arg3] = nil
|
|
end
|
|
else
|
|
pfUI.uf.aura_starts[arg1] = pfUI.uf.aura_starts[arg1] or {}
|
|
pfUI.uf.aura_starts[arg1][arg3] = GetTime()
|
|
end
|
|
end)
|
|
|
|
function pfUI.uf.OnEvent()
|
|
-- Handle shutdown to prevent crash 132
|
|
if event == "PLAYER_LOGOUT" then
|
|
this:UnregisterAllEvents()
|
|
this:SetScript("OnEvent", nil)
|
|
this:SetScript("OnUpdate", nil)
|
|
return
|
|
end
|
|
|
|
-- update indicators
|
|
if event == "PARTY_LEADER_CHANGED" or
|
|
event == "PARTY_LOOT_METHOD_CHANGED" or
|
|
event == "PARTY_MEMBERS_CHANGED" or
|
|
event == "RAID_TARGET_UPDATE" or
|
|
event == "RAID_ROSTER_UPDATE" or
|
|
event == "PLAYER_UPDATE_RESTING"
|
|
then
|
|
this.update_indicators = true
|
|
end
|
|
|
|
-- abort on broken unitframes (e.g focus)
|
|
if not this.label then return end
|
|
|
|
-- update regular frames
|
|
if event == "PLAYER_ENTERING_WORLD" then
|
|
this.update_full = true
|
|
-- Clear GUID tracking on zone change for full rebuild
|
|
if pfUI.uf.ClearGuidTracking then pfUI.uf.ClearGuidTracking() end
|
|
elseif this.label == "target" and event == "PLAYER_TARGET_CHANGED" and not pfScanActive == true then
|
|
this.update_full = true
|
|
-- immediately update raid icon to avoid delay on target swap
|
|
if this.raidIcon and this.config and this.config.raidicon == "1" then
|
|
local raidIcon = UnitName("target") and GetRaidTargetIndex("target")
|
|
if raidIcon then
|
|
SetRaidTargetIconTexture(this.raidIcon.texture, raidIcon)
|
|
this.raidIcon:Show()
|
|
else
|
|
this.raidIcon:Hide()
|
|
end
|
|
end
|
|
elseif ( this.label == "raid" or this.label == "party" or this.label == "player" ) and event == "PARTY_MEMBERS_CHANGED" then
|
|
-- Smart update: check if THIS frame's unit actually changed
|
|
if pfUI.uf.guidTracker and this.id then
|
|
local unit = this.label == "player" and "player" or (this.label .. this.id)
|
|
local newGuid = UnitGUID(unit)
|
|
local oldGuid = pfUI.uf.guidTracker.frameToGuid[this]
|
|
if newGuid ~= oldGuid then
|
|
pfUI.uf.guidTracker.frameToGuid[this] = newGuid
|
|
this.update_full = true
|
|
end
|
|
else
|
|
this.update_full = true
|
|
end
|
|
elseif ( this.label == "raid" or this.label == "party" ) and event == "PARTY_MEMBER_ENABLE" then
|
|
this.update_full = true
|
|
elseif ( this.label == "raid" or this.label == "party" ) and event == "PARTY_MEMBER_DISABLE" then
|
|
this.update_full = true
|
|
elseif ( this.label == "raid" or this.label == "party" ) and event == "RAID_ROSTER_UPDATE" then
|
|
-- Note: Smart GUID-based updates are handled in raid.lua OnUpdate
|
|
-- after frame IDs are reassigned. We don't set update_full here anymore
|
|
-- for raid frames to avoid the freeze.
|
|
if this.label == "party" then
|
|
-- Party frames still need the old logic (no smart tracking yet)
|
|
this.update_full = true
|
|
end
|
|
-- Raid frames: update_full is set by raid.lua GUID tracker
|
|
elseif this.label == "pet" and event == "UNIT_PET" then
|
|
this.update_full = true
|
|
elseif this.label == "player" and (event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_EQUIPMENT_CHANGED") then
|
|
this.update_aura = true
|
|
elseif this.label == "pet" and event == "UNIT_HAPPINESS" then
|
|
this.update_full = true
|
|
-- UNIT_XXX Events
|
|
elseif arg1 and (arg1 == this.label .. this.id or (UnitGUID and arg1 == UnitGUID(this.label .. this.id))) then
|
|
if event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then
|
|
this.update_portrait = true
|
|
elseif event == "UNIT_AURA" then
|
|
this.update_aura = true
|
|
elseif event == "UNIT_FACTION" then
|
|
this.update_pvp = true
|
|
elseif event == "UNIT_COMBAT" then
|
|
CombatFeedback_OnCombatEvent(arg2, arg3, arg4, arg5)
|
|
else
|
|
this.update_base = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Local reference for performance
|
|
local _GetTime = GetTime
|
|
|
|
-- Global cached time for libpredict and other functions
|
|
pfUI.uf.now = 0
|
|
|
|
-- ============================================================================
|
|
-- OnUpdate - eventless per-frame work (range check, aggro glow) and draining
|
|
-- the event-set update flags. Frames refresh on events only; no polling.
|
|
-- ============================================================================
|
|
function pfUI.uf.OnUpdate()
|
|
local now = _GetTime()
|
|
pfUI.uf.now = now
|
|
|
|
-- update combat feedback (no throttle - needs immediate feedback)
|
|
if this.feedbackText then CombatFeedback_OnUpdate(arg1) end
|
|
|
|
-- Throttle raid/party frames for performance
|
|
if this.label == "raid" or this.label == "party" then
|
|
if (this.throttleTick or 0) > now then
|
|
return
|
|
end
|
|
this.throttleTick = now + 0.1 -- Default: 10 FPS
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- EVENTLESS ACTIONS (Range Check, Online/Offline, Aggro) - MUST RUN ALWAYS
|
|
-- These run on their own timer, independent of event-based updates
|
|
-- ============================================================================
|
|
if this.label then
|
|
-- Combat/Aggro Indicators (throttled to 0.2s)
|
|
if not this.lastCombatCheck then this.lastCombatCheck = now + 0.2 end
|
|
if this.lastCombatCheck < now then
|
|
this.lastCombatCheck = now + 0.2
|
|
|
|
if this.config and this.config.squareaggro == "1" and pfUI.api.UnitHasAggro(this.label .. this.id) > 0 then
|
|
this.combat.tex:SetTexture(1,.2,0)
|
|
this.combat:Show()
|
|
elseif this.config and this.config.squarecombat == "1" and UnitAffectingCombat(this.label .. this.id) then
|
|
this.combat.tex:SetTexture(1,1,.2)
|
|
this.combat:Show()
|
|
elseif this.combat then
|
|
this.combat:Hide()
|
|
end
|
|
end
|
|
|
|
-- Range Check / Online-Offline State (throttled)
|
|
-- Validate tick value - it should be an interval (e.g. 0.5), not a timestamp
|
|
local tickInterval = this.tick
|
|
if tickInterval and tickInterval > 10 then
|
|
-- tick is a timestamp, not an interval - ignore it
|
|
tickInterval = nil
|
|
end
|
|
-- Reset lastTick if it's invalid (much larger than now, e.g. from corrupted state)
|
|
if this.lastTick and this.lastTick > now + 10 then
|
|
this.lastTick = nil
|
|
end
|
|
if not this.lastTick then this.lastTick = now + (tickInterval or .5) end
|
|
if this.lastTick < now then
|
|
local unitstr = this.label .. this.id
|
|
this.lastTick = now + (tickInterval or .5)
|
|
|
|
-- target target has a huge delay, make sure to not tick during range checks
|
|
if this.label == "targettarget" or this.label == "targettargettarget" then
|
|
local name = UnitName(this.label)
|
|
if name ~= this.namebuf1 then
|
|
this.namebuf1 = name
|
|
elseif name ~= this.namebuf2 then
|
|
this.namebuf2 = name
|
|
else
|
|
pfUI.uf:RefreshUnitState(this)
|
|
pfUI.uf:RefreshIndicators(this)
|
|
end
|
|
else
|
|
pfUI.uf:RefreshUnitState(this)
|
|
pfUI.uf:RefreshIndicators(this)
|
|
end
|
|
|
|
if this.config and this.config.glowaggro == "1" and pfUI.api.UnitHasAggro(unitstr) > 0 then
|
|
this.glow:SetBackdropBorderColor(1,.2,0)
|
|
this.glow:Show()
|
|
elseif this.config and this.config.glowcombat == "1" and UnitAffectingCombat(unitstr) then
|
|
this.glow:SetBackdropBorderColor(1,1,.2)
|
|
this.glow:Show()
|
|
elseif this.glow then
|
|
this.glow:Hide()
|
|
end
|
|
|
|
-- update everything on eventless frames (targettarget, etc)
|
|
if this.tick then
|
|
pfUI.uf:RefreshUnit(this, "all")
|
|
end
|
|
end
|
|
|
|
-- Heal Prediction (throttled to 0.1s for responsiveness)
|
|
if libpredict and this.incHeal then
|
|
if not this.lastHealTick then this.lastHealTick = now end
|
|
if this.lastHealTick < now then
|
|
this.lastHealTick = now + 0.1
|
|
|
|
local unit = this.label .. this.id
|
|
local heal = libpredict:UnitGetIncomingHeals(unit)
|
|
|
|
local health, maxHealth = UnitHealth(unit), UnitHealthMax(unit)
|
|
|
|
if heal - health - maxHealth ~= this.predictstate then
|
|
local overhealperc = tonumber(this.config.overhealperc)
|
|
this.predictstate = heal - health - maxHealth
|
|
|
|
if heal > 0 and (health < maxHealth or overhealperc > 0 ) then
|
|
local width = this.config.width
|
|
local height = this.config.height
|
|
|
|
if this.config.verticalbar == "0" then
|
|
local healthWidth = width * (health / maxHealth)
|
|
local incWidth = width * heal / maxHealth
|
|
if healthWidth + incWidth > width * (1+(overhealperc/100)) then
|
|
incWidth = width * (1+overhealperc/100) - healthWidth
|
|
end
|
|
|
|
if this.config.invert_healthbar == "1" then
|
|
this.incHeal:SetWidth(incWidth)
|
|
else
|
|
this.incHeal:SetWidth(incWidth + healthWidth)
|
|
end
|
|
else
|
|
local healthHeight = height * (health / maxHealth)
|
|
local incHeight = height * heal / maxHealth
|
|
if healthHeight + incHeight > height * (1+(overhealperc/100)) then
|
|
incHeight = height * (1+overhealperc/100) - healthHeight
|
|
end
|
|
|
|
if this.config.invert_healthbar == "1" then
|
|
this.incHeal:SetHeight(incHeight)
|
|
else
|
|
this.incHeal:SetHeight(incHeight + healthHeight)
|
|
end
|
|
end
|
|
|
|
this.incHeal:Show()
|
|
else
|
|
this.incHeal:Hide()
|
|
end
|
|
end
|
|
|
|
-- update ressurections
|
|
local ress = libpredict:UnitHasIncomingResurrection(unit)
|
|
this.ressIcon:SetShown(ress and UnitIsDeadOrGhost(unit))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- EVENT-BASED UPDATES (Health, Mana, Auras, etc.)
|
|
-- ============================================================================
|
|
|
|
|
|
-- process indicator update events
|
|
if this.update_indicators then
|
|
pfUI.uf:RefreshIndicators(this)
|
|
this.update_indicators = nil
|
|
end
|
|
|
|
-- process all queued unit events
|
|
if this.update_full then
|
|
-- process full updates
|
|
pfUI.uf:RefreshUnit(this, "all")
|
|
|
|
-- clear update caches
|
|
this.update_full = nil
|
|
this.update_base = nil
|
|
this.update_aura = nil
|
|
this.update_portrait = nil
|
|
this.update_pvp = nil
|
|
else
|
|
-- process individual events
|
|
if this.update_aura then
|
|
pfUI.uf:RefreshUnit(this, "aura")
|
|
this.update_aura = nil
|
|
this.update_base = true
|
|
end
|
|
|
|
if this.update_portrait then
|
|
pfUI.uf:RefreshUnit(this, "portrait")
|
|
this.update_portrait = nil
|
|
this.update_base = true
|
|
end
|
|
|
|
if this.update_pvp then
|
|
pfUI.uf:RefreshUnit(this, "pvp")
|
|
this.update_pvp = nil
|
|
end
|
|
|
|
if this.update_base then
|
|
pfUI.uf:RefreshUnit(this, "base")
|
|
this.update_base = nil
|
|
end
|
|
end
|
|
|
|
if not this.label or this.label == "" then return end
|
|
|
|
-- update portrait on first visible frame
|
|
if this.portrait and this.portrait.model and this.portrait.model.update then
|
|
this.portrait.model.lastUnit = UnitName(this.portrait.model.update)
|
|
this.portrait.model:SetUnit(this.portrait.model.update)
|
|
this.portrait.model:SetCamera(0)
|
|
this.portrait.model.update = nil
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:EnableEvents()
|
|
local f = self
|
|
|
|
f:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
f:RegisterEvent("PLAYER_LOGOUT")
|
|
f:RegisterEvent("UNIT_DISPLAYPOWER")
|
|
f:RegisterEvent("UNIT_HEALTH")
|
|
f:RegisterEvent("UNIT_MAXHEALTH")
|
|
f:RegisterEvent("UNIT_MANA")
|
|
f:RegisterEvent("UNIT_MAXMANA")
|
|
f:RegisterEvent("UNIT_RAGE")
|
|
f:RegisterEvent("UNIT_MAXRAGE")
|
|
f:RegisterEvent("UNIT_ENERGY")
|
|
f:RegisterEvent("UNIT_MAXENERGY")
|
|
f:RegisterEvent("UNIT_FOCUS")
|
|
f:RegisterEvent("UNIT_PORTRAIT_UPDATE")
|
|
f:RegisterEvent("UNIT_MODEL_CHANGED")
|
|
f:RegisterEvent("UNIT_FACTION")
|
|
f:RegisterEvent("UNIT_AURA") -- frame=buff, frame=debuff
|
|
f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff
|
|
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs)
|
|
f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon
|
|
f:RegisterEvent("PARTY_LEADER_CHANGED") -- frame=leaderIcon
|
|
f:RegisterEvent("RAID_ROSTER_UPDATE") -- label=raidIcon
|
|
f:RegisterEvent("PLAYER_UPDATE_RESTING") -- label=restIcon
|
|
f:RegisterEvent("PLAYER_TARGET_CHANGED") -- label=target
|
|
f:RegisterEvent("PARTY_LOOT_METHOD_CHANGED") -- frame=lootIcon
|
|
f:RegisterEvent("RAID_TARGET_UPDATE") -- frame=raidIcon
|
|
f:RegisterEvent("UNIT_PET")
|
|
f:RegisterEvent("UNIT_HAPPINESS")
|
|
|
|
f:RegisterForClicks('LeftButtonUp', 'RightButtonUp',
|
|
'MiddleButtonUp', 'Button4Up', 'Button5Up')
|
|
end
|
|
|
|
-- ============================================================
|
|
-- Clique compatibility
|
|
-- Clique's pfUI plugin replaces pfUI.uf.ClickAction and expects an
|
|
-- OnClick script to call it (with `this` = frame, `arg1` = button). The
|
|
-- secure attribute path replaced that dispatch, so when Clique is loaded
|
|
-- EnableScripts routes clicks through this legacy Lua path instead. The
|
|
-- secure path is skipped there because type1="target" switches the target
|
|
-- before Clique can cast.
|
|
-- ============================================================
|
|
|
|
function pfUI.uf.OnClick()
|
|
if not this.label and this.unitname then
|
|
TargetByName(this.unitname, true)
|
|
else
|
|
pfUI.uf:ClickAction(arg1)
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:ClickAction(button)
|
|
local label = this.label or ""
|
|
local id = this.id or ""
|
|
local unitstr = label .. id
|
|
|
|
if SpellIsTargeting() and button == "RightButton" then
|
|
SpellStopTargeting()
|
|
return
|
|
end
|
|
|
|
if SpellIsTargeting() and button == "LeftButton" then
|
|
SpellTargetUnit(unitstr)
|
|
elseif CursorHasItem() then
|
|
DropItemOnUnit(unitstr)
|
|
end
|
|
|
|
-- right-click opens the standard unit menu (ClassicAPI resolves the type)
|
|
if button == "RightButton" then
|
|
ClassicAPI_ToggleUnitMenu(unitstr)
|
|
return
|
|
end
|
|
|
|
-- drop food on petframe
|
|
if label == "pet" and CursorHasItem() then
|
|
if UnitClassBase("player") == "HUNTER" then
|
|
DropItemOnUnit("pet")
|
|
return
|
|
end
|
|
end
|
|
|
|
-- default click
|
|
TargetUnit(unitstr)
|
|
end
|
|
|
|
function pfUI.uf:EnableScripts()
|
|
local f = self
|
|
|
|
if IsAddOnLoaded("Clique") then
|
|
f:SetScript("OnClick", pfUI.uf.OnClick)
|
|
else
|
|
f:SetAttribute("type1", "target")
|
|
f:SetAttribute("type2", "menu")
|
|
f:EnableClickCast()
|
|
end
|
|
|
|
f:SetScript("OnShow", pfUI.uf.OnShow)
|
|
f:SetScript("OnEvent", pfUI.uf.OnEvent)
|
|
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
|
|
|
|
-- add frame to visibility refresh handler
|
|
visibilityscan.frames[f] = true
|
|
end
|
|
|
|
function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
|
|
local fname = (( unit == "Party" ) and "Group" or (unit or "")) .. (id or "")
|
|
local unit = strlower(unit or "")
|
|
local id = strlower(id or "")
|
|
|
|
-- fake party0 units as self
|
|
if unit == "party" and id == "0" then
|
|
unit, id = "player", ""
|
|
end
|
|
|
|
if unit == "partypet" and id == "0" then
|
|
unit, id = "pet", ""
|
|
end
|
|
|
|
if unit == "pettarget" and id == "0" then
|
|
unit, id = "pettarget", ""
|
|
end
|
|
|
|
if unit == "party0target" then
|
|
unit, id = "target", ""
|
|
end
|
|
|
|
local f = CreateFrame("Button", "pf" .. fname, UIParent)
|
|
|
|
-- add unitframe functions
|
|
f.UpdateFrameSize = pfUI.uf.UpdateFrameSize
|
|
f.UpdateVisibility = pfUI.uf.UpdateVisibility
|
|
f.UpdateConfig = pfUI.uf.UpdateConfig
|
|
f.EnableScripts = pfUI.uf.EnableScripts
|
|
f.EnableEvents = pfUI.uf.EnableEvents
|
|
f.EnableClickCast = pfUI.uf.EnableClickCast
|
|
f.GetColor = pfUI.uf.GetColor
|
|
|
|
-- cache values to the frame
|
|
f.label = strlower(unit)
|
|
f.fname = fname
|
|
f.id = id
|
|
f.config = config or pfUI_config.unitframes.fallback
|
|
f.tick = tick
|
|
|
|
-- disable events for unknown unitstrings
|
|
if not pfValidUnits[strlower(unit) .. id] then
|
|
f.unitname = unit
|
|
f.label, f.id = "", ""
|
|
f.RegisterEvent = function() return end
|
|
end
|
|
|
|
CreateBackdropShadow(f)
|
|
|
|
f.hp = CreateFrame("Frame",nil, f)
|
|
f.hp.bar = CreateStatusBar(nil, f.hp)
|
|
|
|
f.power = CreateFrame("Frame",nil, f)
|
|
f.power.bar = CreateStatusBar(nil, f.power)
|
|
|
|
-- Druid secondary mana bar: shows base mana (read via ClassicAPI
|
|
-- UnitPower(unit, Enum.PowerType.Mana), which works regardless of the active
|
|
-- power) while shapeshifted into a form that uses energy/rage. Player +
|
|
-- target only;
|
|
-- styled/positioned in UpdateConfig, driven in UpdateDruidMana.
|
|
if C.unitframes.druidmanabar == "1" and (f.label == "player" or f.label == "target") then
|
|
f.druidmana = CreateFrame("StatusBar", "pfDruidMana_" .. f.label .. f.id, f)
|
|
f.druidmana.text = f.druidmana:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
|
f.druidmana.text:SetPoint("CENTER", f.druidmana, "CENTER", 0, 0)
|
|
f.druidmana.text:SetJustifyH("CENTER")
|
|
f.druidmana:Hide()
|
|
end
|
|
|
|
f.glow = CreateFrame("Frame", nil, f)
|
|
f.combat = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.combat.tex = f.combat:CreateTexture(nil, "OVERLAY")
|
|
f.combat.tex:SetAllPoints()
|
|
|
|
f.texts = CreateFrame("Frame", nil, f)
|
|
f.texts:SetFrameLevel(16)
|
|
f.texts:SetAllPoints()
|
|
|
|
f.hpLeftText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
f.hpRightText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
f.hpCenterText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
f.powerLeftText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
f.powerRightText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
f.powerCenterText = f.texts:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
|
|
f.incHeal = CreateFrame("Frame", nil, f.hp)
|
|
-- Texture lives on hp.bar for correct draw order:
|
|
-- hp.bar.bg (BACKGROUND) < incHeal (BORDER) < hp.bar.bar (NORMAL)
|
|
f.incHeal.texture = f.hp.bar:CreateTexture(nil, "BORDER")
|
|
f.incHeal.texture:SetAllPoints(f.incHeal)
|
|
-- Override Show/Hide since texture lives on hp.bar, not incHeal
|
|
f.incHeal._Show = f.incHeal.Show
|
|
f.incHeal._Hide = f.incHeal.Hide
|
|
f.incHeal.Show = function(self) self:_Show() self.texture:Show() end
|
|
f.incHeal.Hide = function(self) self:_Hide() self.texture:Hide() end
|
|
f.incHeal.texture:Hide()
|
|
|
|
f.ressIcon = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.ressIcon.texture = f.ressIcon:CreateTexture(nil,"BACKGROUND")
|
|
|
|
f.leaderIcon = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.leaderIcon.texture = f.leaderIcon:CreateTexture(nil,"BACKGROUND")
|
|
|
|
f.lootIcon = CreateFrame("Frame",nil, f.hp.bar)
|
|
f.lootIcon.texture = f.lootIcon:CreateTexture(nil,"BACKGROUND")
|
|
|
|
f.pvpIcon = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.pvpIcon.texture = f.pvpIcon:CreateTexture(nil,"BACKGROUND")
|
|
|
|
f.raidIcon = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.raidIcon.texture = f.raidIcon:CreateTexture(nil,"ARTWORK")
|
|
|
|
f.restIcon = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.restIcon.texture = f.restIcon:CreateTexture(nil, "BACKGROUND")
|
|
|
|
f.happinessIcon = CreateFrame("Frame", nil, f.hp.bar)
|
|
f.happinessIcon.texture = f.happinessIcon:CreateTexture(nil, "BACKGROUND")
|
|
|
|
f.portrait = CreateFrame("Frame", "pfPortrait" .. f.label .. f.id, f)
|
|
f.portrait.tex = f.portrait:CreateTexture("pfPortraitTexture" .. f.label .. f.id, "OVERLAY")
|
|
f.portrait.model = CreateFrame("PlayerModel", "pfPortraitModel" .. f.label .. f.id, f.portrait)
|
|
f.portrait.model.next = CreateFrame("PlayerModel", nil, nil)
|
|
f.feedbackText = f:CreateFontString("pfHitIndicator" .. f.label .. f.id, "OVERLAY", "NumberFontNormalHuge")
|
|
|
|
if f.label == "raid" and mod(f.id, 5) == 1 then
|
|
local group = math.ceil(f.id/5)
|
|
f.group = f.group or f.hp.bar:CreateFontString("Status", "OVERLAY", "GameFontNormalSmall")
|
|
f.group:SetFont(pfUI.font_unit, 8, "OUTLINE")
|
|
f.group:SetTextColor(1,1,1,.8)
|
|
f.group:SetHeight(16)
|
|
f.group:SetText(T["Group"] .. " " .. group)
|
|
f.group:Hide()
|
|
end
|
|
|
|
f:Hide()
|
|
f:UpdateConfig()
|
|
f:UpdateFrameSize()
|
|
f:EnableScripts()
|
|
f:EnableEvents()
|
|
|
|
if f.config.visible == "1" then
|
|
pfUI.uf:RefreshUnit(f, "all")
|
|
f:EnableScripts()
|
|
f:EnableEvents()
|
|
f:UpdateFrameSize()
|
|
else
|
|
f:UnregisterAllEvents()
|
|
f:Hide()
|
|
end
|
|
|
|
if f.label ~= "" then
|
|
f:SetAttribute("unit", f.label .. f.id)
|
|
end
|
|
|
|
-- register frame for clique
|
|
_G.ClickCastFrames = ClickCastFrames or {}
|
|
ClickCastFrames[f] = true
|
|
|
|
table.insert(pfUI.uf.frames, f)
|
|
return f
|
|
end
|
|
|
|
function pfUI.uf:RefreshUnitState(unit)
|
|
local alpha = unit.alpha_visible
|
|
local unlock = pfUI.unlock and pfUI.unlock:IsShown() or nil
|
|
|
|
if not UnitIsConnected(unit.label .. unit.id) and not unlock then
|
|
-- offline
|
|
alpha = unit.alpha_offline
|
|
unit.hp.bar:SetMinMaxValues(0, 100, true)
|
|
unit.power.bar:SetMinMaxValues(0, 100, true)
|
|
unit.hp.bar:SetValue(0)
|
|
unit.power.bar:SetValue(0)
|
|
elseif unit.config.faderange == "1" and not pfUI.api.UnitInRange(unit.label .. unit.id, 4) and not unlock then
|
|
alpha = unit.alpha_outrange
|
|
end
|
|
|
|
-- skip if alpha is already correct
|
|
if floor(unit:GetAlpha()*10+.5) == floor(alpha*10+.5) then return end
|
|
|
|
-- set unitframe alpha
|
|
unit:SetAlpha(alpha)
|
|
|
|
-- refresh portrait alpha
|
|
if unit.config.portrait == "bar" then
|
|
unit.portrait:SetAlpha(pfUI_config.unitframes.portraitalpha)
|
|
end
|
|
|
|
-- refresh debuff indicator alpha
|
|
local disptype = unit.config.debuff_indicator
|
|
local indicator = unit.hp.bar.debuffindicators
|
|
if indicator then
|
|
indicator:SetAlpha(0)
|
|
if ( disptype == "4" or disptype == "3" ) then
|
|
indicator:SetAlpha(1)
|
|
elseif disptype == "2" then
|
|
indicator:SetAlpha(.4)
|
|
elseif disptype == "1" then
|
|
indicator:SetAlpha(.2)
|
|
end
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:RefreshIndicators(unit)
|
|
if not unit.label or not unit.id then return end
|
|
local unitstr = unit.label .. unit.id
|
|
|
|
if unit.leaderIcon then -- Leader Icon
|
|
unit.leaderIcon:SetShown(unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup())
|
|
end
|
|
|
|
if unit.lootIcon then -- Loot Icon
|
|
if unit.config.looticon == "0" then
|
|
unit.lootIcon:Hide()
|
|
else
|
|
-- no third return value here.. but leaving this as a hint
|
|
local method, group, raid = GetLootMethod()
|
|
local name = group and UnitName(group == 0 and "player" or "party"..group) or raid and UnitName("raid"..raid) or nil
|
|
unit.lootIcon:SetShown(name and name == UnitName(unitstr))
|
|
end
|
|
end
|
|
|
|
if unit.pvpIcon then -- PvP Icon
|
|
unit.pvpIcon:SetShown(unit.config.showPVP == "1" and UnitIsPVP(unitstr))
|
|
end
|
|
|
|
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
|
|
unit.restIcon:SetShown(C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting())
|
|
end
|
|
|
|
if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon
|
|
local pclass = UnitClassBase("player")
|
|
if unit.config.happinessicon == "0" or pclass ~= "HUNTER" then
|
|
unit.happinessIcon:Hide()
|
|
else
|
|
if UnitIsVisible("pet") then
|
|
local happiness = GetPetHappiness()
|
|
if happiness == 1 then
|
|
unit.happinessIcon.texture:SetTexture(pfUI.media["img:sad"..unit.config.happinessicon])
|
|
unit.happinessIcon.texture:SetVertexColor(1, 0, 0, 1)
|
|
elseif happiness == 2 then
|
|
unit.happinessIcon.texture:SetTexture(pfUI.media["img:neutral"..unit.config.happinessicon])
|
|
unit.happinessIcon.texture:SetVertexColor(1, 1, 0, 1)
|
|
else
|
|
unit.happinessIcon.texture:SetTexture(pfUI.media["img:happy"..unit.config.happinessicon])
|
|
unit.happinessIcon.texture:SetVertexColor(0, 1, 0, 1)
|
|
end
|
|
unit.happinessIcon:Show()
|
|
else
|
|
unit.happinessIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
if unit.raidIcon then -- Raid Icon
|
|
local raidIcon = UnitName(unitstr) and GetRaidTargetIndex(unitstr)
|
|
if unit.config.raidicon == "1" and raidIcon then
|
|
SetRaidTargetIconTexture(unit.raidIcon.texture, raidIcon)
|
|
unit.raidIcon:Show()
|
|
else
|
|
unit.raidIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Druid secondary mana bar update. Reads base mana with ClassicAPI
|
|
-- UnitPower/UnitPowerMax(unit, Enum.PowerType.Mana), which return the mana
|
|
-- slot regardless of the unit's active power -- so it works while shapeshifted,
|
|
-- with no nampower/GetUnitField dependency. Shown only while off mana
|
|
-- (Cat=energy, Bear=rage); non-player frames only for druid units.
|
|
function pfUI.uf:UpdateDruidMana(unit)
|
|
local bar = unit.druidmana
|
|
local unitstr = unit.label .. unit.id
|
|
if not UnitExists(unitstr) then bar:Hide() return end
|
|
if unit.label ~= "player" then
|
|
local cls = UnitClassBase(unitstr)
|
|
if cls ~= "DRUID" then bar:Hide() return end
|
|
end
|
|
if UnitPowerType(unitstr) == Enum.PowerType.Mana then bar:Hide() return end
|
|
local mana, maxmana = UnitPower(unitstr, Enum.PowerType.Mana), UnitPowerMax(unitstr, Enum.PowerType.Mana)
|
|
if not maxmana or maxmana == 0 then bar:Hide() return end
|
|
bar:SetMinMaxValues(0, maxmana)
|
|
bar:SetValue(mana)
|
|
if C.unitframes.druidmanatext == "1" then
|
|
bar.text:SetText(pfUI.api.Abbreviate(mana) .. "/" .. pfUI.api.Abbreviate(maxmana))
|
|
else
|
|
bar.text:SetText("")
|
|
end
|
|
bar:Show()
|
|
end
|
|
|
|
function pfUI.uf:RefreshUnit(unit, component)
|
|
-- break early on misconfigured UF's
|
|
if not unit.label then return end
|
|
if not unit.hp then return end
|
|
if not unit.power then return end
|
|
if not unit.id then unit.id = "" end
|
|
component = component or ""
|
|
|
|
-- don't update scanner activity
|
|
if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then
|
|
if pfScanActive == true then return end
|
|
end
|
|
|
|
-- hide unused and invalid frames
|
|
unit:UpdateVisibility()
|
|
|
|
-- return on invisible unit frames
|
|
if not unit:IsShown() and not unit.visible then return end
|
|
|
|
-- create required fields
|
|
local unitstr = unit.label..unit.id
|
|
local rawborder, default_border = GetBorderSize("unitframes")
|
|
|
|
-- save current values
|
|
unit.namecache = UnitName(unitstr)
|
|
|
|
-- buffs
|
|
if unit.buffs and ( component == "all" or component == "aura" ) then
|
|
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:SetSize(size, size)
|
|
indicator.size = size
|
|
indicator.disp = disptype
|
|
indicator.ipos = indipos
|
|
end
|
|
elseif disptype == "2" or disptype == "1" then
|
|
size = "FULL"
|
|
if size ~= indicator.size or disptype ~= indicator.disp or indipos ~= indicator.ipos then
|
|
indicator:ClearAllPoints()
|
|
indicator:SetAllPoints(unit.hp.bar)
|
|
indicator.size = size
|
|
indicator.disp = disptype
|
|
indicator.ipos = indipos
|
|
end
|
|
end
|
|
|
|
for _, debuff in pairs(unit.dispellable) do
|
|
indicator[debuff] = indicator[debuff] or CreateFrame("Frame", nil, indicator)
|
|
indicator[debuff]:SetParent(indicator)
|
|
indicator[debuff].tex = indicator[debuff].tex or indicator[debuff]:CreateTexture(nil)
|
|
indicator[debuff].tex:SetAllPoints(indicator[debuff])
|
|
|
|
if indicator.size ~= indicator[debuff].size or disptype ~= indicator[debuff].disp then
|
|
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(debuff)
|
|
if disptype == "4" then
|
|
indicator[debuff].tex:SetTexture(pfUI.media["img:"..debuff])
|
|
indicator[debuff].tex:SetVertexColor(dispelColor:GetRGBA())
|
|
indicator[debuff].tex:Show()
|
|
indicator[debuff]:ClearAllPoints()
|
|
indicator[debuff]:SetSize(size, size)
|
|
indicator[debuff]:SetBackdrop(nil)
|
|
elseif disptype == "3" then
|
|
indicator[debuff].tex:SetTexture(dispelColor:GetRGBA())
|
|
indicator[debuff].tex:SetVertexColor(1,1,1,1)
|
|
indicator[debuff].tex:Show()
|
|
indicator[debuff]:ClearAllPoints()
|
|
indicator[debuff]:SetSize(size, size)
|
|
indicator[debuff]:SetBackdrop(nil)
|
|
elseif disptype == "2" then
|
|
indicator[debuff].tex:Hide()
|
|
indicator[debuff]:SetAllPoints(unit.hp.bar)
|
|
indicator[debuff]:SetBackdrop(glow)
|
|
indicator[debuff]:SetBackdropBorderColor(dispelColor:GetRGBA())
|
|
elseif disptype == "1" then
|
|
indicator[debuff].tex:SetTexture(dispelColor:GetRGBA())
|
|
indicator[debuff].tex:SetVertexColor(1,1,1,1)
|
|
indicator[debuff].tex:Show()
|
|
indicator[debuff]:SetAllPoints(unit.hp.bar)
|
|
indicator[debuff]:SetBackdrop(nil)
|
|
end
|
|
|
|
indicator[debuff].size = indicator.size
|
|
indicator[debuff].disp = indicator.disp
|
|
end
|
|
|
|
indicator[debuff].visible = 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)
|
|
|
|
-- Store original values for color calculations (before invert_healthbar modifies hp)
|
|
local hp_orig, hpmax_orig = hp, hpmax
|
|
|
|
if unit.config.invert_healthbar == "1" then
|
|
hp = hpmax - hp
|
|
end
|
|
|
|
unit.hp.bar:SetMinMaxValues(0, hpmax, true)
|
|
unit.hp.bar:SetValue(hp)
|
|
|
|
unit.power.bar:SetMinMaxValues(0, powermax, true)
|
|
unit.power.bar:SetValue(power)
|
|
|
|
-- Hide power bar text for NPCs without real power (power == 0)
|
|
local isNPC = not UnitIsPlayer(unitstr) and not UnitPlayerControlled(unitstr)
|
|
local npcNoPower = isNPC and (not power or power == 0)
|
|
|
|
-- set healthbar color
|
|
local custom_active = nil
|
|
local customfullhp = unit.config.defcolor == "0" and unit.config.customfullhp or C.unitframes.customfullhp
|
|
local customcolor = unit.config.defcolor == "0" and unit.config.customcolor or C.unitframes.customcolor
|
|
local customfade = unit.config.defcolor == "0" and unit.config.customfade or C.unitframes.customfade
|
|
local custom = unit.config.defcolor == "0" and unit.config.custom or C.unitframes.custom
|
|
|
|
local r, g, b, a = .2, .2, .2, 1
|
|
-- O(1) optimization: use cached hp/hpmax instead of UnitHealth()/UnitHealthMax() API calls
|
|
if customfullhp == "1" and hp_orig == hpmax_orig then
|
|
r, g, b, a = GetStringColor(customcolor)
|
|
custom_active = true
|
|
elseif custom == "0" then
|
|
if UnitIsPlayer(unitstr) then
|
|
_, r, g, b = GetUnitColor(unitstr)
|
|
elseif unit.label == "pet" then
|
|
local happiness = GetPetHappiness()
|
|
if happiness == 1 then
|
|
r, g, b = 1, 0, 0
|
|
elseif happiness == 2 then
|
|
r, g, b = 1, 1, 0
|
|
else
|
|
r, g, b = 0, 1, 0
|
|
end
|
|
else
|
|
local color = UnitReactionColor[UnitReaction(unitstr, "player")]
|
|
if color then r, g, b = color.r, color.g, color.b end
|
|
end
|
|
elseif custom == "1" then
|
|
r, g, b, a = GetStringColor(customcolor)
|
|
custom_active = true
|
|
elseif custom == "2" then
|
|
-- O(1) optimization: use cached hp/hpmax instead of UnitHealth()/UnitHealthMax() API calls
|
|
if hpmax_orig > 0 then
|
|
r, g, b = GetColorGradient(hp_orig / hpmax_orig)
|
|
else
|
|
r, g, b = 0, 0, 0
|
|
end
|
|
end
|
|
|
|
if C.unitframes.pastel == "1" and not custom_active then
|
|
r, g, b = (r + .5) * .5, (g + .5) * .5, (b + .5) * .5
|
|
end
|
|
|
|
if customfade == "1" then
|
|
-- fade custom color into default color
|
|
-- O(1) optimization: use cached hp/hpmax instead of UnitHealth()/UnitHealthMax() API calls
|
|
local perc = hpmax_orig > 0 and (hp_orig / hpmax_orig) or 0
|
|
local cr, cg, cb, ca = GetStringColor(customcolor)
|
|
|
|
r = (cr*perc) + (r*(1-perc))
|
|
g = (cg*perc) + (g*(1-perc))
|
|
b = (cb*perc) + (b*(1-perc))
|
|
end
|
|
|
|
unit.hp.bar:SetStatusBarColor(r, g, b, a)
|
|
|
|
-- set powerbar color
|
|
local mana = unit.config.defcolor == "0" and unit.config.manacolor or C.unitframes.manacolor
|
|
local rage = unit.config.defcolor == "0" and unit.config.ragecolor or C.unitframes.ragecolor
|
|
local energy = unit.config.defcolor == "0" and unit.config.energycolor or C.unitframes.energycolor
|
|
local focus = unit.config.defcolor == "0" and unit.config.focuscolor or C.unitframes.focuscolor
|
|
|
|
local r, g, b, a = .5, .5, .5, 1
|
|
local utype = UnitPowerType(unitstr)
|
|
if utype == Enum.PowerType.Mana then
|
|
r, g, b, a = GetStringColor(mana)
|
|
elseif utype == Enum.PowerType.Rage then
|
|
r, g, b, a = GetStringColor(rage)
|
|
elseif utype == Enum.PowerType.Focus then
|
|
r, g, b, a = GetStringColor(focus)
|
|
elseif utype == Enum.PowerType.Energy then
|
|
r, g, b, a = GetStringColor(energy)
|
|
end
|
|
|
|
unit.power.bar:SetStatusBarColor(r, g, b, a)
|
|
|
|
if UnitName(unitstr) then
|
|
unit.hpLeftText:SetText(pfUI.uf:GetStatusValue(unit, "hpleft"))
|
|
unit.hpCenterText:SetText(pfUI.uf:GetStatusValue(unit, "hpcenter"))
|
|
unit.hpRightText:SetText(pfUI.uf:GetStatusValue(unit, "hpright"))
|
|
|
|
-- Hide power text for NPCs without a real power system
|
|
local cfgLeft = unit.config.txtpowerleft
|
|
local cfgCenter = unit.config.txtpowercenter
|
|
local cfgRight = unit.config.txtpowerright
|
|
if npcNoPower and cfgLeft and strfind(cfgLeft, "power") then
|
|
unit.powerLeftText:SetText("")
|
|
else
|
|
unit.powerLeftText:SetText(pfUI.uf:GetStatusValue(unit, "powerleft"))
|
|
end
|
|
if npcNoPower and cfgCenter and strfind(cfgCenter, "power") then
|
|
unit.powerCenterText:SetText("")
|
|
else
|
|
unit.powerCenterText:SetText(pfUI.uf:GetStatusValue(unit, "powercenter"))
|
|
end
|
|
if npcNoPower and cfgRight and strfind(cfgRight, "power") then
|
|
unit.powerRightText:SetText("")
|
|
else
|
|
unit.powerRightText:SetText(pfUI.uf:GetStatusValue(unit, "powerright"))
|
|
end
|
|
|
|
if UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then
|
|
unit.hp.bar:SetStatusBarColor(.5,.5,.5,.5)
|
|
end
|
|
end
|
|
|
|
if unit.druidmana then pfUI.uf:UpdateDruidMana(unit) end
|
|
|
|
pfUI.uf:RefreshUnitState(unit)
|
|
end
|
|
end
|
|
|
|
local modifiers = {
|
|
[""] = "",
|
|
["alt"] = "_alt",
|
|
["ctrl"] = "_ctrl",
|
|
["shift"] = "_shift",
|
|
}
|
|
|
|
function pfUI.uf:EnableClickCast()
|
|
if self.config.clickcast ~= "1" then return end
|
|
for bid = 1, 5 do -- LeftButton, RightButton, MiddleButton, Button4, Button5
|
|
for modifier, mconf in pairs(modifiers) do
|
|
local bconf = bid == 1 and "" or bid
|
|
local action = pfUI_config.unitframes["clickcast"..bconf..mconf]
|
|
if action and action ~= "" then
|
|
local prefix = modifier ~= "" and (modifier .. "-") or ""
|
|
local low = string.lower(action)
|
|
if low == "menu" then
|
|
self:SetAttribute(prefix .. "type" .. bid, "menu")
|
|
elseif low == "target" then
|
|
self:SetAttribute(prefix .. "type" .. bid, "target")
|
|
elseif low == "focus" then
|
|
self:SetAttribute(prefix .. "type" .. bid, "focus")
|
|
elseif string.find(low, "^macro:") then
|
|
self:SetAttribute(prefix .. "type" .. bid, "macro")
|
|
self:SetAttribute(prefix .. "macro" .. bid, string.gsub(string.sub(action, 7), "^%s+", ""))
|
|
elseif string.find(action, "^/") then
|
|
self:SetAttribute(prefix .. "type" .. bid, "macro")
|
|
self:SetAttribute(prefix .. "macrotext" .. bid, action)
|
|
else
|
|
self:SetAttribute(prefix .. "type" .. bid, "spell")
|
|
self:SetAttribute(prefix .. "spell" .. bid, action)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration)
|
|
local showtime = frame.config.indicator_time == "1" and true or nil
|
|
local showstacks = frame.config.indicator_stacks == "1" and true or nil
|
|
local position = frame.config.indicator_pos or "TOPLEFT"
|
|
local iconsize = tonumber(frame.config.indicator_size)
|
|
local spacing = tonumber(frame.config.indicator_spacing)
|
|
|
|
if not frame.hp then return end
|
|
local frame = frame.hp.bar
|
|
if pos > 6 or pos > ceil(frame:GetWidth() / iconsize) then return end
|
|
|
|
frame.icon = frame.icon or CreateFrame("Frame", nil, frame)
|
|
|
|
if not frame.icon[pos] then
|
|
frame.icon[pos] = CreateFrame("Frame", nil, frame.icon)
|
|
frame.icon[pos]:SetParent(frame)
|
|
frame.icon[pos].tex = frame.icon[pos]:CreateTexture("OVERLAY")
|
|
frame.icon[pos].tex:SetAllPoints(frame.icon[pos])
|
|
frame.icon[pos].tex:SetTexCoord(.08, .92, .08, .92)
|
|
frame.icon[pos].stacks = frame.icon[pos]:CreateFontString(nil, "OVERLAY")
|
|
frame.icon[pos].stacks:SetPoint("BOTTOMRIGHT", 0, 0)
|
|
frame.icon[pos].stacks:SetJustifyH("RIGHT")
|
|
frame.icon[pos].stacks:SetJustifyV("BOTTOM")
|
|
|
|
-- Check if parent frame has cooldown animation enabled
|
|
local parent_cooldown_anim = frame.config and tonumber(frame.config.cooldown_anim) or 1
|
|
if parent_cooldown_anim == 1 then
|
|
frame.icon[pos].cd = CreateFrame(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]:SetSize(iconsize, iconsize)
|
|
frame.icon[pos]:SetPoint("TOPLEFT", frame.icon, "TOPLEFT", (pos-1)*(iconsize + spacing), 0)
|
|
frame.icon[pos].stacks:SetFont(pfUI.font_unit, math.max(iconsize/3, 10), "OUTLINE")
|
|
frame.icon[pos].iconsize = iconsize
|
|
frame.icon[pos].spacing = spacing
|
|
end
|
|
|
|
-- update icon
|
|
if frame.icon[pos].icon ~= icon then
|
|
frame.icon[pos].tex:SetTexture(icon)
|
|
frame.icon[pos].icon = icon
|
|
end
|
|
|
|
-- show remaining time if config is set
|
|
if showtime and start and duration and timeleft < 100 and iconsize > 9 then
|
|
CooldownFrame_SetTimer(frame.icon[pos].cd, start, duration, 1)
|
|
elseif showtime and timeleft and timeleft < 100 and iconsize > 9 then
|
|
CooldownFrame_SetTimer(frame.icon[pos].cd, GetTime(), timeleft, 1)
|
|
else
|
|
CooldownFrame_SetTimer(frame.icon[pos].cd, GetTime(), 0, 1)
|
|
end
|
|
|
|
-- show stacks if config is set
|
|
if showstacks and stacks and stacks > 1 and iconsize > 9 then
|
|
frame.icon[pos].stacks:SetText(stacks)
|
|
else
|
|
frame.icon[pos].stacks:SetText("")
|
|
end
|
|
|
|
-- update parent icon size
|
|
if frame.icon.iconsize ~= iconsize then
|
|
frame.icon:SetHeight(iconsize)
|
|
frame.icon.iconsize = iconsize
|
|
end
|
|
|
|
-- update parent position
|
|
if frame.icon.position ~= position then
|
|
frame.icon:ClearAllPoints()
|
|
frame.icon:SetPoint(position, frame, position, 0, 0)
|
|
frame.icon.position = position
|
|
end
|
|
|
|
frame.icon[pos]:Show()
|
|
frame.icon:SetWidth((pos-1)*(iconsize+spacing)+iconsize)
|
|
end
|
|
|
|
function pfUI.uf:HideIcon(frame, pos)
|
|
if not frame or not frame.hp or not frame.hp.bar then return end
|
|
|
|
local frame = frame.hp.bar
|
|
if frame.icon and frame.icon[pos] then
|
|
frame.icon[pos]:Hide()
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:SetupDebuffFilter(allclasses)
|
|
local myclass = UnitClassBase("player")
|
|
local debuffs = {}
|
|
|
|
if myclass == "PALADIN" or myclass == "PRIEST" or myclass == "WARLOCK" or allclasses then
|
|
table.insert(debuffs, "Magic")
|
|
end
|
|
|
|
if myclass == "DRUID" or myclass == "PALADIN" or myclass == "SHAMAN" or allclasses then
|
|
table.insert(debuffs, "Poison")
|
|
end
|
|
|
|
if myclass == "PRIEST" or myclass == "PALADIN" or myclass == "SHAMAN" or allclasses then
|
|
table.insert(debuffs, "Disease")
|
|
end
|
|
|
|
if myclass == "DRUID" or myclass == "MAGE" or allclasses then
|
|
table.insert(debuffs, "Curse")
|
|
end
|
|
|
|
return debuffs
|
|
end
|
|
|
|
function pfUI.uf:SetupBuffIndicators(config)
|
|
local myclass = UnitClassBase("player")
|
|
local indicators = {}
|
|
|
|
if config.show_buffs == "1" then -- buffs
|
|
if myclass == "DRUID" then
|
|
-- 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)
|
|
local rhp, rhpmax = hp, hpmax
|
|
|
|
-- Use libhealth for mob health estimation (overrides Nampower/Standard)
|
|
if pfUI.libhealth and pfUI.libhealth.enabled then
|
|
rhp, rhpmax = pfUI.libhealth:GetUnitHealth(unitstr)
|
|
end
|
|
|
|
if config == "unit" then
|
|
local name = unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr)
|
|
local level = unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr)
|
|
|
|
return level .. " " .. name
|
|
elseif config == "unitrev" then
|
|
local name = unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr)
|
|
local level = unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr)
|
|
|
|
return name .. " " .. level
|
|
elseif config == "name" then
|
|
return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr)
|
|
elseif config == "nameshort" then
|
|
return unit:GetColor("unit") .. strsub(UnitName(unitstr), 0, 3)
|
|
elseif config == "level" then
|
|
return unit:GetColor("level") .. pfUI.uf:GetLevelString(unitstr)
|
|
elseif config == "class" then
|
|
if UnitIsPlayer(unitstr) then
|
|
return unit:GetColor("class") .. (UnitClass(unitstr) or UNKNOWN)
|
|
else
|
|
return ""
|
|
end
|
|
elseif config == "ownername" then
|
|
local owner
|
|
if unit.label == "raidpet" then owner = "raid" .. unit.id
|
|
elseif unit.label == "partypet" then owner = "party" .. unit.id
|
|
elseif unit.label == "pet" then owner = "player" end
|
|
if not (owner and UnitExists(owner)) then return "" end
|
|
local color = ""
|
|
if unit.config["classcolor"] == "1" and UnitIsPlayer(owner) then
|
|
local _, r, g, b = GetUnitColor(owner)
|
|
if C.unitframes.pastel == "1" then r, g, b = (r+.75)*.5, (g+.75)*.5, (b+.75)*.5 end
|
|
color = rgbhex(r, g, b)
|
|
end
|
|
return color .. pfUI.uf:GetNameString(owner)
|
|
|
|
-- health
|
|
elseif config == "health" then
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp)
|
|
elseif config == "healthmax" then
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(rhpmax)
|
|
elseif config == "healthperc" then
|
|
return unit:GetColor("health") .. ceil(hp / hpmax * 100)
|
|
elseif config == "healthmiss" then
|
|
local health = ceil(rhp - rhpmax)
|
|
if UnitIsDead(unitstr) then
|
|
return unit:GetColor("health") .. DEAD
|
|
elseif health == 0 then
|
|
return unit:GetColor("health") .. "0"
|
|
else
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(health)
|
|
end
|
|
elseif config == "healthdyn" then
|
|
if hp ~= hpmax then
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) .. " - " .. ceil(hp / hpmax * 100) .. "%"
|
|
else
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp)
|
|
end
|
|
elseif config == "namehealth" then
|
|
local health = ceil(rhp - rhpmax)
|
|
if UnitIsDead(unitstr) then
|
|
return unit:GetColor("health") .. DEAD
|
|
elseif health == 0 then
|
|
return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr)
|
|
else
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(health)
|
|
end
|
|
elseif config == "namehealthbreak" then
|
|
local health = ceil(rhp - rhpmax)
|
|
if UnitIsDead(unitstr) then
|
|
return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) .. "\n" .. unit:GetColor("health") .. DEAD
|
|
elseif health == 0 then
|
|
return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr)
|
|
else
|
|
return unit:GetColor("unit") .. pfUI.uf:GetNameString(unitstr) .. "\n" .. unit:GetColor("health") .. pfUI.api.Abbreviate(-health)
|
|
end
|
|
elseif config == "shortnamehealth" then
|
|
local health = ceil(rhp - rhpmax)
|
|
if UnitIsDead(unitstr) then
|
|
return unit:GetColor("health") .. DEAD
|
|
elseif health == 0 then
|
|
return unit:GetColor("unit") .. strsub(UnitName(unitstr), 0, 3)
|
|
else
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(health)
|
|
end
|
|
elseif config == "healthminmax" then
|
|
return unit:GetColor("health") .. pfUI.api.Abbreviate(rhp) .. "/" .. pfUI.api.Abbreviate(rhpmax)
|
|
|
|
-- mana/power/focus
|
|
elseif config == "power" then
|
|
return unit:GetColor("power") .. pfUI.api.Abbreviate(mp)
|
|
elseif config == "powermax" then
|
|
return unit:GetColor("power") .. pfUI.api.Abbreviate(mpmax)
|
|
elseif config == "powerperc" then
|
|
local perc = UnitPowerMax(unitstr) > 0 and ceil(mp / mpmax * 100) or 0
|
|
return unit:GetColor("power") .. perc
|
|
elseif config == "powermiss" then
|
|
local power = ceil(mp - mpmax)
|
|
if power == 0 then
|
|
return unit:GetColor("power") .. "0"
|
|
else
|
|
return unit:GetColor("power") .. pfUI.api.Abbreviate(power)
|
|
end
|
|
elseif config == "powerdyn" then
|
|
-- show percentage when only mana is less than 100%
|
|
if mp ~= mpmax and UnitPowerType(unitstr) == Enum.PowerType.Mana then
|
|
return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) .. " - " .. ceil(mp / mpmax * 100) .. "%"
|
|
else
|
|
return unit:GetColor("power") .. pfUI.api.Abbreviate(mp)
|
|
end
|
|
elseif config == "powerminmax" then
|
|
return unit:GetColor("power") .. pfUI.api.Abbreviate(mp) .. "/" .. pfUI.api.Abbreviate(mpmax)
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
|
|
function pfUI.uf.GetColor(self, preset)
|
|
local config = self.config
|
|
|
|
local unitstr = self.label .. self.id
|
|
local r, g, b = 1, 1, 1
|
|
|
|
if preset == "unit" and config["classcolor"] == "1" then
|
|
if UnitIsPlayer(unitstr) then
|
|
_, r, g, b = GetUnitColor(unitstr)
|
|
elseif self.label == "pet" then
|
|
local happiness = GetPetHappiness()
|
|
if happiness == 1 then
|
|
r, g, b = 1, 0, 0
|
|
elseif happiness == 2 then
|
|
r, g, b = 1, 1, 0
|
|
else
|
|
r, g, b = 0, 1, 0
|
|
end
|
|
else
|
|
local color = UnitReactionColor[UnitReaction(unitstr, "player")]
|
|
if color then r, g, b = color.r, color.g, color.b end
|
|
end
|
|
|
|
elseif preset == "class" and config["classcolor"] == "1" then
|
|
_, r, g, b = GetUnitColor(unitstr)
|
|
|
|
elseif preset == "reaction" and config["classcolor"] == "1" then
|
|
local color = UnitReactionColor[UnitReaction(unitstr, "player")]
|
|
r, g, b = color.r, color.g, color.b
|
|
|
|
elseif preset == "health" and config["healthcolor"] == "1" then
|
|
local hp, hpmax = UnitHealth(unitstr), UnitHealthMax(unitstr)
|
|
if hpmax and hpmax > 0 then
|
|
r, g, b = GetColorGradient(hp / hpmax)
|
|
else
|
|
r, g, b = 0, 0, 0
|
|
end
|
|
|
|
elseif preset == "power" and config["powercolor"] == "1" then
|
|
local color = ManaBarColor[UnitPowerType(unitstr)]
|
|
r, g, b = color.r, color.g, color.b
|
|
elseif preset == "level" and config["levelcolor"] == "1" then
|
|
local color = GetDifficultyColor(UnitLevel(unitstr))
|
|
r, g, b = color.r, color.g, color.b
|
|
end
|
|
|
|
if C.unitframes.pastel == "1" then
|
|
r, g, b = (r + .75) * .5, (g + .75) * .5, (b + .75) * .5
|
|
end
|
|
|
|
return rgbhex(r,g,b)
|
|
end
|
|
|