mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-22 07:36:56 +00:00
00292ca3b4
commit70b1e66c2dAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sun Jul 26 18:30:18 2026 -0500 Use SetSize/SetShown and simplify minimap/map commit7b6bfe9975Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 11:27:14 2026 -0500 Include raidpet frames in /pftest test mode The unit-frame test toggle (showall) only previews frames that exist and are positioned. Raidpet frames are created when raidpet.visible == "1", but LayoutPets' collapse mode only positions pets whose raidpet<N> unit actually exists, so solo/in test mode they stayed hidden. Add a showall branch to LayoutPets that mirror-lays every pet cell and shows it (bypassing collapse and the roster gate), still guarded by the existing showpets check so nothing happens when raidpet is disabled. Call LayoutPets from the /pftest handler so the grid updates immediately on toggle-on and restores to the normal layout on toggle-off. commit4c65c38647Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:34:33 2026 -0500 Remove unused RangeCache local in UnitInRange commit6c96bbbe6cAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:34:33 2026 -0500 Use Clamp() for the two-sided clamps in ui-widgets Replace the hand-rolled math.min/math.max and if/elseif clamp idioms in the status bar and scroll frame with Clamp(). Equivalent for every value seen (min <= max always holds), just clearer. commit830e0a0be1Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:34:33 2026 -0500 Drop vanilla GetItemInfo shim for C_Item.GetItemInfo The compat/vanilla.lua GetItemInfo override reshaped vanilla's tuple into retail order by inserting nil at slot 4 (itemLevel), but truncated at 8 fields, dropping itemEquipLoc/itemTexture. ClassicAPI's global GetItemInfo keeps the vanilla shape (its hook only warms the item cache), so the shim stayed necessary but incomplete. Remove it and point the three callers at ClassicAPI's C_Item.GetItemInfo, which returns the full 18-field retail tuple. Positions 1-8 are identical to the old shim output, so bags/roll are unchanged; character.lua's itemSlot (equipLoc, field 9) was always nil under the truncating shim and now resolves correctly for ShaguScore. commitc0da63657dAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 10:09:34 2026 -0500 Various cleanup Removes version compatibility checks for TBC/Vanilla, consolidating code to target a single WoW version. Refactors repetitive SetWidth/SetHeight calls to SetSize for cleaner code. Removes duplicate function definitions from unitxp module and simplifies conditional show/hide operations using SetShown. commit11a6302c2dAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 09:53:37 2026 -0500 Delegate pfUI.api.strsplit to ClassicAPI's strsplit Replace the Lua pattern-based implementation with a thin wrapper around ClassicAPI's C-level strsplit. Keeps the pfUI.api.strsplit entry point for backwards compatibility with addons that call it, while dropping the redundant reimplementation. Behavioral note: the old version used ([^delim]+) which silently collapsed empty fields; delegating to real strsplit now preserves them ("a,,b" -> "a", "", "b"). All in-repo callers split numeric color tuples, version strings, or build name-keyed lookup tables, none of which are affected by empty-field preservation. commit67c126eac8Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 01:01:02 2026 -0500 Bump ClassicAPI minimum version to 1.8.0 commita6cf157518Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 00:49:50 2026 -0500 read spell rank via C_Spell.GetSpellSubtext The rank string comes from the spell subtext ("Rank N"), which ClassicAPI's C_Spell.GetSpellSubtext returns directly -- so drop nampower's GetSpellRecField(spellId, "rank") here. The existing "Rank " gsub parse is unchanged. commit4b1ba99b4fAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 00:33:20 2026 -0500 Move player info overlay onto ClassicAPI (drop Nampower) The haste/spell-power overlay read both values through Nampower and disabled itself entirely without it. Move both to ClassicAPI so it works on any ClassicAPI client: - Spell power: GetSpellPower("net") (nampower) -> GetSpellBonusDamage(i) (ClassicAPI, same per-school field). Merged the build+scan into one loop; the default school seeds the tiebreak so equal schools still favor it. - Haste: GetUnitField("player", "modCastSpeed") -> UnitSpellHaste("player"), which returns the haste percentage directly (the (1/modCastSpeed-1)*100 conversion is now baked in, off the same UNIT_MOD_CAST_SPEED field, signed). - Dropped the "if not GetUnitField then return" gate -- UpdateInfoText no longer touches Nampower, so the overlay runs everywhere. commit7df4aa6d50Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Sat Jul 25 00:32:44 2026 -0500 Hook the real global _G.UnitHealth for feign death Inside a RegisterModule body, `function UnitHealth(...)` defines UnitHealth on the pfUI environment, not the real global -- so the feign-death real-HP fix only reached callers that resolve UnitHealth through pfUI's env, and missed _G consumers (Blizzard frames, other addons). Hook _G.UnitHealth explicitly (and capture oldUnitHealth from _G) so the un-gate applies everywhere. commitb0bf2fd869Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 21:10:58 2026 -0500 Refactor nampower module Simplify reactive spell storage by using spell IDs instead of texture/name tables. Update to use modern C_Spell APIs (GetSpellTexture, GetSpellName) instead of manual texture paths. Consolidate SetWidth/SetHeight calls to SetSize and use print() instead of DEFAULT_CHAT_FRAME:AddMessage(). commit0b06961333Author: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 20:54:23 2026 -0500 Use GetNamePlateForUnit for target lookups; drop dead ScanGuid block - Replace the three GetNamePlateForGUID(UnitGUID("target")) round-trips with GetNamePlateForUnit("target"), which resolves the token to a GUID internally -- no UnitGUID string detour, and it returns nil for no-target/no-plate so the UnitExists guards collapse. GetNamePlateForGUID is left for raw-GUID cases. - Remove the dead libunitscan.ScanGuid nameplate block (ScanGuid was deleted from libunitscan long ago, so the guard was never true) -- it carried the last GetUnitField("npcFlags") read. commit3e6210086eAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 20:54:23 2026 -0500 Drop Nampower stats system and polling from unit frames GetUnitStats now reads health/power straight from UnitHealth/UnitPower (the descriptor fields the server broadcasts) -- for a real unit token GetUnitField read nothing different, so the whole Nampower-vs-fallback apparatus was measuring a distinction that no longer exists. Removed: - The stats system: pfUI.uf.stats, pfUIStatsFrame + UpdateStatsDisplay, the lastUnitStats change-cache, the /pfuistats slash command, and every counter increment (event/heartbeat/earlyReturn/throttledSkip/nampower/fallback). - All GetUnitField health reads (GetUnitStats, heal-prediction, health-gradient color) -- collapsed to the UnitHealth/UnitHealthMax they already fell back to. - The heartbeat-polling backstop and its fallbackThrottle/lastEventUpdate deps. Frames now refresh on events only; eventless frames still use their own tick, and range/aggro still run in the eventless-actions block. commit63001b7b0cAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 20:09:42 2026 -0500 Move nameplates onto ClassicAPI stable nameplate tokens ClassicAPI now assigns retail-exact, per-plate-stable "nameplateN" tokens and fires vanilla UNIT_* events for them, so the nameplate module can key off the token instead of Nampower/SuperWoW GUID primitives: - Events: UNIT_AURA_GUID/UNIT_FLAGS_GUID -> UNIT_AURA/UNIT_FLAGS, matched by the "nameplateN" token (guarded on the token prefix, since these also fire for target/party/raid). Registered unconditionally -- no GetUnitField gate. - Health: GetUnitField(guid, "health"/"maxHealth") -> UnitHealth/UnitHealthMax on the plate's cached token. Same UNIT_FIELD_HEALTH the server broadcasts (real HP on Turtle; the ~= 100 scaled-vs-real guard is unchanged). - Combat/target in GetCombatStateColor: GetUnitField("flags") + HasFlag -> UnitAffectingCombat(token); GetUnitField("target") + SuperWoW "<guid>target" -> the "nameplateNtarget" chain. Dropped the now-dead HasFlag, UNIT_FLAG_IN_COMBAT, and guidTargetTokenCache. - GetCastInfo now takes the token directly; every caller already holds it, so the UnitTokenFromGUID(guid) round-trip is gone (unused module-wide now). NAME_PLATE_UNIT_ADDED caches the token as plate.nameplate.unit alongside the GUID; REMOVED clears both. GUID is retained only as the stable cache key (debuffCache/threatMemory/combatColorCache). Only GetUnitField("npcFlags") remains -- it has no token/vanilla equivalent. commitce1c49fcbbAuthor: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri Jul 24 18:20:06 2026 -0500 Make focus/focustarget event-driven via ClassicAPI unit events ClassicAPI now fires UNIT_* (health/mana/aura/...) with arg1 == "focus" and arg1 == "focustarget", observed per-unit like target/party/raid. Both frames already registered those events (focus/focustarget are in pfValidUnits) and their OnEvent already matches arg1 == label, so the 0.2s polling ticks were pure workarounds for the missing events. Drop both ticks; the frames now refresh on-event like target, with range/glow still on the shared 0.5s state pass and PLAYER_FOCUS_CHANGED still driving assign/clear.
2764 lines
99 KiB
Lua
2764 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 = { }
|
|
function pfUI.api.UnitHasAggro(unit)
|
|
-- Only cache positive results to allow instant detection when aggro changes
|
|
if aggrodata[unit] and aggrodata[unit].state > 0 and GetTime() < aggrodata[unit].check + 1 then
|
|
return aggrodata[unit].state
|
|
end
|
|
|
|
aggrodata[unit] = aggrodata[unit] or { }
|
|
aggrodata[unit].check = GetTime()
|
|
aggrodata[unit].state = 0
|
|
|
|
if UnitExists(unit) and UnitIsFriend(unit, "player") then
|
|
for u in pairs(pfValidUnits) do
|
|
local t = u .. "target"
|
|
local tt = t .. "target"
|
|
|
|
if UnitExists(t) and UnitIsUnit(t, unit) and UnitCanAttack(u, unit) then
|
|
aggrodata[unit].state = aggrodata[unit].state + 1
|
|
end
|
|
|
|
if UnitExists(tt) and UnitIsUnit(tt, unit) and UnitCanAttack(t, unit) then
|
|
aggrodata[unit].state = aggrodata[unit].state + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return aggrodata[unit].state
|
|
end
|
|
|
|
pfUI.uf.glow = CreateFrame("Frame", nil, UIParent)
|
|
pfUI.uf.glow:SetScript("OnUpdate", function()
|
|
local fpsmod = GetFramerate() / 30
|
|
if not this.val or this.val >= .8 then
|
|
this.mod = -0.01 / fpsmod
|
|
elseif this.val <= .4 then
|
|
this.mod = 0.01 / fpsmod
|
|
end
|
|
this.val = this.val + this.mod
|
|
end)
|
|
|
|
pfUI.uf.glow.mod = 0
|
|
pfUI.uf.glow.val = 0.6
|
|
|
|
function pfUI.uf.glow.UpdateGlowAnimation()
|
|
local val = pfUI.uf.glow.val or 0.6
|
|
if val < 0.4 then val = 0.4 end
|
|
if val > 0.8 then val = 0.8 end
|
|
this:SetAlpha(val)
|
|
end
|
|
|
|
function pfUI.uf:UpdateVisibility()
|
|
local self = self or this
|
|
|
|
-- cache result of strsub to avoid repeating calls
|
|
if not self.cache_raid then
|
|
if strsub(self:GetName(),0,6) == "pfRaid" then
|
|
self.cache_raid = tonumber(strsub(self:GetName(),7,8)) or 0
|
|
else
|
|
self.cache_raid = 0
|
|
end
|
|
end
|
|
|
|
-- show groupframes as raid
|
|
if self.cache_raid > 0 then
|
|
local id = self.cache_raid
|
|
|
|
-- always show self in raidframes
|
|
if not IsInGroup() and C.unitframes.selfinraid == "1" and id == 1 then
|
|
self.id = ""
|
|
self.label = "player"
|
|
|
|
-- use raidframes for groups
|
|
elseif not IsInRaid() and IsInGroup() and C.unitframes.raidforgroup == "1" then
|
|
if id == 1 then
|
|
self.id = ""
|
|
self.label = "player"
|
|
elseif id <= 5 then
|
|
self.id = id - 1
|
|
self.label = "party"
|
|
end
|
|
|
|
-- reset to regular raid unitstrings
|
|
elseif self.label == "party" or self.label == "player" then
|
|
self.id = id
|
|
self.label = "raid"
|
|
end
|
|
end
|
|
|
|
-- display every unit as player while pfUI.uf.showall is set
|
|
if pfUI.uf.showall then
|
|
self._label = self._label or self.label
|
|
self._id = self._id or self.id
|
|
self.label, self.id = "player", ""
|
|
elseif not pfUI.uf.showall and self._label and self._id then
|
|
self.label, self.id = self._label, self._id
|
|
self._label, self._id = nil, nil
|
|
end
|
|
|
|
local unitstr = string.format("%s%s", self.label or "", self.id or "")
|
|
local visibility = string.format("[target=%s,exists] show; hide", unitstr)
|
|
|
|
-- 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 self.label and strsub(self.label,0,5) == "party" then
|
|
-- hide group while shown as a raid grid and option is set (raid frames
|
|
-- carry label "party" under raidforgroup, so exclude them by cache_raid)
|
|
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:SetWidth((f.config.pwidth ~= "-1" and f.config.pwidth or f.config.width))
|
|
f.power:SetHeight(f.config.pheight)
|
|
if tonumber(f.config.pheight) < 0 then f.power:Hide() end
|
|
|
|
pfUI.api.CreateBackdrop(f.power, default_border)
|
|
f.power.bar:SetFrameLevel(f.power:GetFrameLevel() + 1)
|
|
f.power.bar:SetStatusBarTexture(pfUI.media[f.config.pbartexture])
|
|
f.power.bar:SetAllPoints(f.power)
|
|
|
|
local custompbg = f.config.defcolor == "0" and f.config.custompbg or C.unitframes.custompbg
|
|
local custompbgcolor = f.config.defcolor == "0" and f.config.custompbgcolor or C.unitframes.custompbgcolor
|
|
|
|
if custompbg == "1" then
|
|
local cr, cg, cb, ca = GetStringColor(custompbgcolor)
|
|
cr, cg, cb, ca = tonumber(cr), tonumber(cg), tonumber(cb), tonumber(ca)
|
|
f.power.bar:SetStatusBarBackgroundTexture(cr,cg,cb,ca)
|
|
end
|
|
|
|
local fontname, fontsize, fontstyle
|
|
if f.config.customfont == "1" then
|
|
fontname = pfUI.media[f.config.customfont_name]
|
|
fontsize = tonumber(f.config.customfont_size)
|
|
fontstyle = f.config.customfont_style
|
|
else
|
|
fontname = pfUI.font_unit
|
|
fontsize = tonumber(C.global.font_unit_size)
|
|
fontstyle = C.global.font_unit_style
|
|
end
|
|
|
|
-- 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]:SetWidth(f.config.debuffsize)
|
|
f.debuffs[i]:SetHeight(f.config.debuffsize)
|
|
f.debuffs[i]:SetNormalTexture(nil)
|
|
|
|
-- Create CD frame if it doesn't exist
|
|
if not f.debuffs[i].cd then
|
|
if cooldown_anim == 1 then
|
|
-- Animation enabled: Use Model frame with CooldownFrameTemplate
|
|
f.debuffs[i].cd = CreateFrame(COOLDOWN_FRAME_TYPE, f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i], "CooldownFrameTemplate")
|
|
else
|
|
-- Animation disabled: Use regular Frame with dummy functions
|
|
f.debuffs[i].cd = CreateFrame("Frame", f.debuffs[i]:GetName() .. "Cooldown", f.debuffs[i])
|
|
f.debuffs[i].cd.AdvanceTime = DoNothing
|
|
f.debuffs[i].cd.SetSequence = DoNothing
|
|
f.debuffs[i].cd.SetSequenceTime = DoNothing
|
|
end
|
|
end
|
|
|
|
-- Always update CD properties (in case size changed)
|
|
local cdScale = f.config.debuffsize / 32
|
|
f.debuffs[i].cd:ClearAllPoints()
|
|
f.debuffs[i].cd:SetScale(cdScale)
|
|
f.debuffs[i].cd:SetAllPoints(f.debuffs[i])
|
|
f.debuffs[i].cd:SetFrameLevel(14)
|
|
f.debuffs[i].cd.pfCooldownType = "ALL"
|
|
f.debuffs[i].cd.pfCooldownStyleText = cooldown_text
|
|
f.debuffs[i].cd.pfCooldownStyleAnimation = cooldown_anim
|
|
f.debuffs[i].cd:SetAlpha(cooldown_anim == 1 and 1 or 0)
|
|
|
|
-- immediately show/hide existing cooldown text
|
|
if f.debuffs[i].cd.pfCooldownText then
|
|
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)
|
|
if ress and UnitIsDeadOrGhost(unit) then
|
|
this.ressIcon:Show()
|
|
else
|
|
this.ressIcon:Hide()
|
|
end
|
|
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.OnEnter()
|
|
if not this.label then return end
|
|
|
|
-- Nampower: Set native mouseover unit for macro/addon compatibility
|
|
if SetMouseoverUnit then
|
|
local unitstr = this.label .. this.id
|
|
-- For GUID-based frames (focus), use the GUID directly
|
|
if this.label and string.find(this.label, "^0x") then
|
|
SetMouseoverUnit(this.label)
|
|
elseif UnitExists(unitstr) then
|
|
SetMouseoverUnit(unitstr)
|
|
end
|
|
end
|
|
|
|
if this.config.showtooltip == "0" then return end
|
|
GameTooltip_SetDefaultAnchor(GameTooltip, this)
|
|
GameTooltip:SetUnit(this.label .. this.id)
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
function pfUI.uf.OnLeave()
|
|
-- Nampower: Clear native mouseover unit
|
|
if SetMouseoverUnit then
|
|
SetMouseoverUnit()
|
|
end
|
|
|
|
GameTooltip:FadeOut()
|
|
end
|
|
|
|
function pfUI.uf.OnClick()
|
|
if not this.label and this.unitname then
|
|
TargetByName(this.unitname, true)
|
|
else
|
|
pfUI.uf:ClickAction(arg1)
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:RightClickAction(unit)
|
|
if unit == "player" then
|
|
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor")
|
|
elseif unit == "target" then
|
|
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor")
|
|
elseif unit == "pet" then
|
|
ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor")
|
|
elseif unit == "party" or strfind(unit, "party%d") then
|
|
ToggleDropDownMenu(1, nil, getglobal("PartyMemberFrame" .. this.id .. "DropDown"), "cursor")
|
|
elseif unit == "raid" or strfind(unit, "raid%d") then
|
|
local name = this.lastUnit
|
|
local unitstr = this.label .. this.id
|
|
FriendsDropDown.displayMode = "MENU"
|
|
FriendsDropDown.initialize = function() UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PARTY", unitstr, name, id) end
|
|
ToggleDropDownMenu(1, nil, FriendsDropDown, "cursor")
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:EnableEvents()
|
|
local f = self
|
|
|
|
f:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
f:RegisterEvent("PLAYER_LOGOUT")
|
|
f:RegisterEvent("UNIT_DISPLAYPOWER")
|
|
f:RegisterEvent("UNIT_HEALTH")
|
|
f:RegisterEvent("UNIT_MAXHEALTH")
|
|
f:RegisterEvent("UNIT_MANA")
|
|
f:RegisterEvent("UNIT_MAXMANA")
|
|
f:RegisterEvent("UNIT_RAGE")
|
|
f:RegisterEvent("UNIT_MAXRAGE")
|
|
f:RegisterEvent("UNIT_ENERGY")
|
|
f:RegisterEvent("UNIT_MAXENERGY")
|
|
f:RegisterEvent("UNIT_FOCUS")
|
|
f:RegisterEvent("UNIT_PORTRAIT_UPDATE")
|
|
f:RegisterEvent("UNIT_MODEL_CHANGED")
|
|
f:RegisterEvent("UNIT_FACTION")
|
|
f:RegisterEvent("UNIT_AURA") -- frame=buff, frame=debuff
|
|
f:RegisterEvent("PLAYER_AURAS_CHANGED") -- label=player && frame=buff
|
|
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") -- label=player && frame=buff (ClassicAPI: weapon-enchant buffs)
|
|
f:RegisterEvent("PARTY_MEMBERS_CHANGED") -- label=party, frame=leaderIcon
|
|
f:RegisterEvent("PARTY_LEADER_CHANGED") -- frame=leaderIcon
|
|
f:RegisterEvent("RAID_ROSTER_UPDATE") -- label=raidIcon
|
|
f:RegisterEvent("PLAYER_UPDATE_RESTING") -- label=restIcon
|
|
f:RegisterEvent("PLAYER_TARGET_CHANGED") -- label=target
|
|
f:RegisterEvent("PARTY_LOOT_METHOD_CHANGED") -- frame=lootIcon
|
|
f:RegisterEvent("RAID_TARGET_UPDATE") -- frame=raidIcon
|
|
f:RegisterEvent("UNIT_PET")
|
|
f:RegisterEvent("UNIT_HAPPINESS")
|
|
|
|
f:RegisterForClicks('LeftButtonUp', 'RightButtonUp',
|
|
'MiddleButtonUp', 'Button4Up', 'Button5Up')
|
|
end
|
|
|
|
function pfUI.uf:EnableScripts()
|
|
local f = self
|
|
|
|
f:SetScript("OnClick", pfUI.uf.OnClick)
|
|
|
|
f:SetScript("OnShow", pfUI.uf.OnShow)
|
|
f:SetScript("OnEvent", pfUI.uf.OnEvent)
|
|
f:SetScript("OnUpdate", pfUI.uf.OnUpdate)
|
|
f:SetScript("OnEnter", pfUI.uf.OnEnter)
|
|
f:SetScript("OnLeave", pfUI.uf.OnLeave)
|
|
f:EnableClickCast()
|
|
|
|
-- add frame to visibility refresh handler
|
|
visibilityscan.frames[f] = true
|
|
end
|
|
|
|
function pfUI.uf:CreateUnitFrame(unit, id, config, tick)
|
|
local fname = (( unit == "Party" ) and "Group" or (unit or "")) .. (id or "")
|
|
local unit = strlower(unit or "")
|
|
local id = strlower(id or "")
|
|
|
|
-- fake party0 units as self
|
|
if unit == "party" and id == "0" then
|
|
unit, id = "player", ""
|
|
end
|
|
|
|
if unit == "partypet" and id == "0" then
|
|
unit, id = "pet", ""
|
|
end
|
|
|
|
if unit == "pettarget" and id == "0" then
|
|
unit, id = "pettarget", ""
|
|
end
|
|
|
|
if unit == "party0target" then
|
|
unit, id = "target", ""
|
|
end
|
|
|
|
local f = CreateFrame("Button", "pf" .. fname, UIParent, UNITFRAME_SECURE_TEMPLATE)
|
|
|
|
-- add unitframe functions
|
|
f.UpdateFrameSize = pfUI.uf.UpdateFrameSize
|
|
f.UpdateVisibility = pfUI.uf.UpdateVisibility
|
|
f.UpdateConfig = pfUI.uf.UpdateConfig
|
|
f.EnableScripts = pfUI.uf.EnableScripts
|
|
f.EnableEvents = pfUI.uf.EnableEvents
|
|
f.EnableClickCast = pfUI.uf.EnableClickCast
|
|
f.GetColor = pfUI.uf.GetColor
|
|
|
|
-- cache values to the frame
|
|
f.label = strlower(unit)
|
|
f.fname = fname
|
|
f.id = id
|
|
f.config = config or pfUI_config.unitframes.fallback
|
|
f.tick = tick
|
|
|
|
-- disable events for unknown unitstrings
|
|
if not pfValidUnits[strlower(unit) .. id] then
|
|
f.unitname = unit
|
|
f.label, f.id = "", ""
|
|
f.RegisterEvent = function() return end
|
|
end
|
|
|
|
CreateBackdropShadow(f)
|
|
|
|
f.hp = CreateFrame("Frame",nil, f)
|
|
f.hp.bar = CreateStatusBar(nil, f.hp)
|
|
|
|
f.power = CreateFrame("Frame",nil, f)
|
|
f.power.bar = CreateStatusBar(nil, f.power)
|
|
|
|
-- 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("Group " .. group)
|
|
f.group:Hide()
|
|
end
|
|
|
|
f:Hide()
|
|
f:UpdateConfig()
|
|
f:UpdateFrameSize()
|
|
f:EnableScripts()
|
|
f:EnableEvents()
|
|
|
|
if f.config.visible == "1" then
|
|
pfUI.uf:RefreshUnit(f, "all")
|
|
f:EnableScripts()
|
|
f:EnableEvents()
|
|
f:UpdateFrameSize()
|
|
else
|
|
f:UnregisterAllEvents()
|
|
f:Hide()
|
|
end
|
|
|
|
-- register frame for clique
|
|
_G.ClickCastFrames = ClickCastFrames or {}
|
|
ClickCastFrames[f] = true
|
|
|
|
table.insert(pfUI.uf.frames, f)
|
|
return f
|
|
end
|
|
|
|
function pfUI.uf:RefreshUnitState(unit)
|
|
local alpha = unit.alpha_visible
|
|
local unlock = pfUI.unlock and pfUI.unlock:IsShown() or nil
|
|
|
|
if not UnitIsConnected(unit.label .. unit.id) and not unlock then
|
|
-- offline
|
|
alpha = unit.alpha_offline
|
|
unit.hp.bar:SetMinMaxValues(0, 100, true)
|
|
unit.power.bar:SetMinMaxValues(0, 100, true)
|
|
unit.hp.bar:SetValue(0)
|
|
unit.power.bar:SetValue(0)
|
|
elseif unit.config.faderange == "1" and not pfUI.api.UnitInRange(unit.label .. unit.id, 4) and not unlock then
|
|
alpha = unit.alpha_outrange
|
|
end
|
|
|
|
-- skip if alpha is already correct
|
|
if floor(unit:GetAlpha()*10+.5) == floor(alpha*10+.5) then return end
|
|
|
|
-- set unitframe alpha
|
|
unit:SetAlpha(alpha)
|
|
|
|
-- refresh portrait alpha
|
|
if unit.config.portrait == "bar" then
|
|
unit.portrait:SetAlpha(pfUI_config.unitframes.portraitalpha)
|
|
end
|
|
|
|
-- refresh debuff indicator alpha
|
|
local disptype = unit.config.debuff_indicator
|
|
local indicator = unit.hp.bar.debuffindicators
|
|
if indicator then
|
|
indicator:SetAlpha(0)
|
|
if ( disptype == "4" or disptype == "3" ) then
|
|
indicator:SetAlpha(1)
|
|
elseif disptype == "2" then
|
|
indicator:SetAlpha(.4)
|
|
elseif disptype == "1" then
|
|
indicator:SetAlpha(.2)
|
|
end
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:RefreshIndicators(unit)
|
|
if not unit.label or not unit.id then return end
|
|
local unitstr = unit.label .. unit.id
|
|
|
|
if unit.leaderIcon then -- Leader Icon
|
|
if unit.config.leadericon == "1" and UnitIsPartyLeader(unitstr) and IsInGroup() then
|
|
unit.leaderIcon:Show()
|
|
else
|
|
unit.leaderIcon:Hide()
|
|
end
|
|
end
|
|
|
|
if unit.lootIcon then -- Loot Icon
|
|
if unit.config.looticon == "0" then
|
|
unit.lootIcon:Hide()
|
|
else
|
|
-- no third return value here.. but leaving this as a hint
|
|
local method, group, raid = GetLootMethod()
|
|
local name = group and UnitName(group == 0 and "player" or "party"..group) or raid and UnitName("raid"..raid) or nil
|
|
|
|
if name and name == UnitName(unitstr) then
|
|
unit.lootIcon:Show()
|
|
else
|
|
unit.lootIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
if unit.pvpIcon then -- PvP Icon
|
|
if unit.config.showPVP == "1" and UnitIsPVP(unitstr) then
|
|
unit.pvpIcon:Show()
|
|
else
|
|
unit.pvpIcon:Hide()
|
|
end
|
|
end
|
|
|
|
if unit.restIcon and unit:GetName() == "pfPlayer" then -- Rest Icon
|
|
if C.unitframes.player.showRest == "1" and UnitIsUnit(unitstr, "player") and IsResting() then
|
|
unit.restIcon:Show()
|
|
else
|
|
unit.restIcon:Hide()
|
|
end
|
|
end
|
|
|
|
if unit.happinessIcon and unit:GetName() == "pfPet" then -- Happiness Icon
|
|
local _, pclass = UnitClass("player")
|
|
if unit.config.happinessicon == "0" or pclass ~= "HUNTER" then
|
|
unit.happinessIcon:Hide()
|
|
else
|
|
if UnitIsVisible("pet") then
|
|
local happiness = GetPetHappiness()
|
|
if happiness == 1 then
|
|
unit.happinessIcon.texture:SetTexture(pfUI.media["img:sad"..unit.config.happinessicon])
|
|
unit.happinessIcon.texture:SetVertexColor(1, 0, 0, 1)
|
|
elseif happiness == 2 then
|
|
unit.happinessIcon.texture:SetTexture(pfUI.media["img:neutral"..unit.config.happinessicon])
|
|
unit.happinessIcon.texture:SetVertexColor(1, 1, 0, 1)
|
|
else
|
|
unit.happinessIcon.texture:SetTexture(pfUI.media["img:happy"..unit.config.happinessicon])
|
|
unit.happinessIcon.texture:SetVertexColor(0, 1, 0, 1)
|
|
end
|
|
unit.happinessIcon:Show()
|
|
else
|
|
unit.happinessIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
if unit.raidIcon then -- Raid Icon
|
|
local raidIcon = UnitName(unitstr) and GetRaidTargetIndex(unitstr)
|
|
if unit.config.raidicon == "1" and raidIcon then
|
|
SetRaidTargetIconTexture(unit.raidIcon.texture, raidIcon)
|
|
unit.raidIcon:Show()
|
|
else
|
|
unit.raidIcon:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 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 = UnitClass(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
|
|
local component = component or ""
|
|
|
|
-- don't update scanner activity
|
|
if unit.label == "target" or unit.label == "targettarget" or unit.label == "targettargettarget" then
|
|
if pfScanActive == true then return end
|
|
end
|
|
|
|
-- hide unused and invalid frames
|
|
unit:UpdateVisibility()
|
|
|
|
-- return on invisible unit frames
|
|
if not unit:IsShown() and not unit.visible then return end
|
|
|
|
-- create required fields
|
|
local unitstr = unit.label..unit.id
|
|
local rawborder, default_border = GetBorderSize("unitframes")
|
|
|
|
-- save current values
|
|
unit.namecache = UnitName(unitstr)
|
|
|
|
-- buffs
|
|
if unit.buffs and ( component == "all" or component == "aura" ) then
|
|
for i=1, unit.config.bufflimit do
|
|
if not unit.buffs[i] then break end
|
|
|
|
local aura = C_UnitAuras.GetBuffDataByIndex(unitstr, i)
|
|
|
|
if aura then
|
|
unit.buffs[i].texture:SetTexture(aura.icon)
|
|
unit.buffs[i]:Show()
|
|
|
|
if aura.applications > 1 then
|
|
unit.buffs[i].stacks:SetText(aura.applications)
|
|
else
|
|
unit.buffs[i].stacks:SetText("")
|
|
end
|
|
|
|
if aura.expirationTime > 0 then
|
|
-- pfUI's cooldown text falls into a 2^32 wraparound branch when start > GetTime(),
|
|
-- which happens for talent-extended buffs where the real duration exceeds aura.duration
|
|
-- (the Spell.dbc base). Anchor start to now in that case to keep the remaining math sane.
|
|
local now = GetTime()
|
|
local start = aura.expirationTime - aura.duration
|
|
local duration = aura.duration
|
|
if start > now or duration <= 0 then
|
|
start, duration = now, aura.expirationTime - now
|
|
end
|
|
if duration > 0 then
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, start, duration, 1)
|
|
else
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
|
end
|
|
elseif aura.duration > 0 then
|
|
local guid = UnitGUID(unitstr)
|
|
local guidStarts = guid and pfUI.uf.aura_starts[guid]
|
|
local start = guidStarts and guidStarts[aura.spellId]
|
|
if start then
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, start, aura.duration, 1)
|
|
else
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
|
end
|
|
else
|
|
CooldownFrame_SetTimer(unit.buffs[i].cd, 0, 0, 0)
|
|
end
|
|
else
|
|
unit.buffs[i]:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- debuffs
|
|
if unit.debuffs and ( component == "all" or component == "aura" ) then
|
|
local texture, stacks, dtype
|
|
local perrow = unit.config.debuffperrow
|
|
local bperrow = unit.config.buffperrow
|
|
local selfdebuff = unit.config.selfdebuff
|
|
|
|
local invert_h, invert_v, af
|
|
if unit.config.debuffs == "TOPLEFT" then
|
|
invert_h = 1
|
|
invert_v = 1
|
|
af = "BOTTOMLEFT"
|
|
elseif unit.config.debuffs == "BOTTOMLEFT" then
|
|
invert_h = -1
|
|
invert_v = 1
|
|
af = "TOPLEFT"
|
|
elseif unit.config.debuffs == "TOPRIGHT" then
|
|
invert_h = 1
|
|
invert_v = -1
|
|
af = "BOTTOMRIGHT"
|
|
elseif unit.config.debuffs == "BOTTOMRIGHT" then
|
|
invert_h = -1
|
|
invert_v = -1
|
|
af = "TOPRIGHT"
|
|
end
|
|
|
|
local buffrow, reposition = 0, ( component == "all" and true or nil )
|
|
if unit.config.buffs == unit.config.debuffs then
|
|
if unit.buffs[0*bperrow+1] and unit.buffs[0*bperrow+1]:IsShown() then buffrow = buffrow + 1 end
|
|
if unit.buffs[1*bperrow+1] and unit.buffs[1*bperrow+1]:IsShown() then buffrow = buffrow + 1 end
|
|
if unit.buffs[2*bperrow+1] and unit.buffs[2*bperrow+1]:IsShown() then buffrow = buffrow + 1 end
|
|
if unit.buffs[3*bperrow+1] and unit.buffs[3*bperrow+1]:IsShown() then buffrow = buffrow + 1 end
|
|
end
|
|
|
|
if buffrow ~= unit.lastbuffrow then
|
|
unit.lastbuffrow = buffrow
|
|
reposition = true
|
|
end
|
|
|
|
for i=1, unit.config.debufflimit do
|
|
if not unit.debuffs[i] then break end
|
|
|
|
local row = floor((i-1) / unit.config.debuffperrow)
|
|
|
|
if reposition then
|
|
local anchor = unit.config.portraitheight ~= "-1" and unit.hp or unit
|
|
if anchor == unit.hp and (unit.config.debuffs == "BOTTOMLEFT" or unit.config.debuffs == "BOTTOMRIGHT") then
|
|
anchor = unit.power
|
|
end
|
|
local multiply = C.appearance.border.force_blizz == "1" and 1 or 2
|
|
unit.debuffs[i]:SetPoint(af, anchor, unit.config.debuffs,
|
|
invert_v * (i-1-row*perrow)*(multiply*default_border + unit.config.debuffsize + 1),
|
|
invert_h * ((row+buffrow)*(multiply*default_border + unit.config.debuffsize + 1) + (multiply*default_border + 1)))
|
|
end
|
|
|
|
-- selfdebuff narrows to player-cast harmful auras via the PLAYER filter.
|
|
-- Player-frame debuffs aren't gated on it (it'd hide most party-applied
|
|
-- effects on you).
|
|
local filter = (unit.label ~= "player" and selfdebuff == "1") and "HARMFUL|PLAYER" or "HARMFUL"
|
|
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, filter)
|
|
if aura then
|
|
texture, stacks, dtype = aura.icon, aura.applications, aura.dispelName
|
|
else
|
|
texture, stacks, dtype = nil, 0, nil
|
|
end
|
|
|
|
unit.debuffs[i].texture:SetTexture(texture)
|
|
|
|
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(dtype or "")
|
|
unit.debuffs[i].backdrop:SetBackdropBorderColor(dispelColor:GetRGBA())
|
|
|
|
if texture then
|
|
unit.debuffs[i]:Show()
|
|
|
|
if aura and aura.expirationTime > 0 then
|
|
-- Cap start to now so talent-extended debuffs (expirationTime past
|
|
-- the dbc base duration) don't push start into the future and trip
|
|
-- CooldownFrame_SetTimer's 2^32-wraparound branch.
|
|
local now = GetTime()
|
|
local start = aura.expirationTime - aura.duration
|
|
local duration = aura.duration
|
|
if start > now or duration <= 0 then
|
|
start, duration = now, aura.expirationTime - now
|
|
end
|
|
if duration > 0 then
|
|
CooldownFrame_SetTimer(unit.debuffs[i].cd, start, duration, 1)
|
|
else
|
|
CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0)
|
|
end
|
|
else
|
|
CooldownFrame_SetTimer(unit.debuffs[i].cd, 0, 0, 0)
|
|
end
|
|
|
|
if stacks > 1 then
|
|
unit.debuffs[i].stacks:SetText(stacks)
|
|
else
|
|
unit.debuffs[i].stacks:SetText("")
|
|
end
|
|
else
|
|
unit.debuffs[i]:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- indicators
|
|
if component == "all" or component == "aura" then
|
|
if not unit.dispellable and unit.config.debuff_indicator ~= "0" then
|
|
unit.dispellable = pfUI.uf:SetupDebuffFilter((unit.config.debuff_ind_class == "0" and true or nil))
|
|
elseif not unit.dispellable then
|
|
unit.dispellable = {}
|
|
end
|
|
|
|
if table.getn(unit.dispellable) > 0 then
|
|
unit.hp.bar.debuffindicators = unit.hp.bar.debuffindicators or CreateFrame("Frame", nil, unit.hp.bar)
|
|
|
|
-- 0 = OFF, 1 = Legacy, 2 = Glow, 3 = Square, 4 = Icons
|
|
local disptype = unit.config.debuff_indicator
|
|
local indicator = unit.hp.bar.debuffindicators
|
|
local indipos = unit.config.debuff_ind_pos
|
|
local count = 0
|
|
local size
|
|
|
|
if disptype == "4" or disptype == "3" then
|
|
size = unit.hp.bar:GetHeight() * tonumber(unit.config.debuff_ind_size)
|
|
if size ~= indicator.size or disptype ~= indicator.disp or indipos ~= indicator.ipos then
|
|
indicator:ClearAllPoints()
|
|
indicator:SetPoint(indipos, 0, 0)
|
|
indicator:SetHeight(size)
|
|
indicator:SetWidth(size)
|
|
indicator.size = size
|
|
indicator.disp = disptype
|
|
indicator.ipos = indipos
|
|
end
|
|
elseif disptype == "2" or disptype == "1" then
|
|
size = "FULL"
|
|
if size ~= indicator.size or disptype ~= indicator.disp or indipos ~= indicator.ipos then
|
|
indicator:ClearAllPoints()
|
|
indicator:SetAllPoints(unit.hp.bar)
|
|
indicator.size = size
|
|
indicator.disp = disptype
|
|
indicator.ipos = indipos
|
|
end
|
|
end
|
|
|
|
for _, debuff in pairs(unit.dispellable) do
|
|
indicator[debuff] = indicator[debuff] or CreateFrame("Frame", nil, indicator)
|
|
indicator[debuff]:SetParent(indicator)
|
|
indicator[debuff].tex = indicator[debuff].tex or indicator[debuff]:CreateTexture(nil)
|
|
indicator[debuff].tex:SetAllPoints(indicator[debuff])
|
|
|
|
if indicator.size ~= indicator[debuff].size or disptype ~= indicator[debuff].disp then
|
|
local dispelColor = C_UnitAuras.GetAuraDispelTypeColor(debuff)
|
|
if disptype == "4" then
|
|
indicator[debuff].tex:SetTexture(pfUI.media["img:"..debuff])
|
|
indicator[debuff].tex:SetVertexColor(dispelColor:GetRGBA())
|
|
indicator[debuff].tex:Show()
|
|
indicator[debuff]:ClearAllPoints()
|
|
indicator[debuff]:SetHeight(size)
|
|
indicator[debuff]:SetWidth(size)
|
|
indicator[debuff]:SetBackdrop(nil)
|
|
elseif disptype == "3" then
|
|
indicator[debuff].tex:SetTexture(dispelColor:GetRGBA())
|
|
indicator[debuff].tex:SetVertexColor(1,1,1,1)
|
|
indicator[debuff].tex:Show()
|
|
indicator[debuff]:ClearAllPoints()
|
|
indicator[debuff]:SetHeight(size)
|
|
indicator[debuff]:SetWidth(size)
|
|
indicator[debuff]:SetBackdrop(nil)
|
|
elseif disptype == "2" then
|
|
indicator[debuff].tex:Hide()
|
|
indicator[debuff]:SetAllPoints(unit.hp.bar)
|
|
indicator[debuff]:SetBackdrop(glow)
|
|
indicator[debuff]:SetBackdropBorderColor(dispelColor:GetRGBA())
|
|
elseif disptype == "1" then
|
|
indicator[debuff].tex:SetTexture(dispelColor:GetRGBA())
|
|
indicator[debuff].tex:SetVertexColor(1,1,1,1)
|
|
indicator[debuff].tex:Show()
|
|
indicator[debuff]:SetAllPoints(unit.hp.bar)
|
|
indicator[debuff]:SetBackdrop(nil)
|
|
end
|
|
|
|
indicator[debuff].size = indicator.size
|
|
indicator[debuff].disp = indicator.disp
|
|
end
|
|
|
|
indicator[debuff].visible = nil
|
|
|
|
for i=1,16 do
|
|
local a = C_UnitAuras.GetDebuffDataByIndex(unitstr, i)
|
|
local dtype = a and a.dispelName
|
|
if dtype == debuff then
|
|
indicator[debuff].visible = true
|
|
end
|
|
end
|
|
|
|
if indicator[debuff].visible then
|
|
indicator[debuff]:Show()
|
|
indicator:Show()
|
|
indicator:SetAlpha(0)
|
|
if disptype == "4" or disptype == "3" then
|
|
indicator:SetAlpha(1)
|
|
elseif disptype == "2" then
|
|
indicator:SetAlpha(.4)
|
|
elseif disptype == "1" then
|
|
indicator:SetAlpha(.2)
|
|
end
|
|
|
|
if disptype == "4" or disptype == "3" then
|
|
indicator[debuff]:SetPoint("LEFT", indicator, "LEFT", count*(size+1), 0)
|
|
count = count + 1
|
|
end
|
|
else
|
|
indicator[debuff]:Hide()
|
|
end
|
|
end
|
|
|
|
if disptype == "4" or disptype == "3" then
|
|
indicator:SetWidth(count*(size+1))
|
|
end
|
|
elseif unit.hp.bar.debuffindicators then
|
|
unit.hp.bar.debuffindicators:Hide()
|
|
end
|
|
|
|
if not unit.indicators and unit.config.buff_indicator == "1" then
|
|
unit.indicators = pfUI.uf:SetupBuffIndicators(unit.config)
|
|
elseif not unit.indicators then
|
|
unit.indicators = {}
|
|
end
|
|
|
|
if not unit.indicator_custom and unit.config.buff_indicator == "1" then
|
|
unit.indicator_custom = {}
|
|
for k, v in pairs({strsplit("#", unit.config.custom_indicator)}) do
|
|
unit.indicator_custom[k] = string.lower(v)
|
|
end
|
|
elseif not unit.indicator_custom then
|
|
unit.indicator_custom = {}
|
|
end
|
|
|
|
local pos = 1
|
|
if table.getn(unit.indicators) > 0 then
|
|
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
|
|
local texLower = string.lower(aura.icon)
|
|
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
|
|
|
for _, filter in pairs(unit.indicators) do
|
|
if filter == texLower then
|
|
local hot = HOT_INDICATORS[texLower]
|
|
if hot and string.lower(aura.name) ~= hot.name then
|
|
break -- texture matches but name disambiguates (e.g. shared icon)
|
|
end
|
|
if hot then
|
|
local start, duration, prediction = libpredict:GetHotDuration(unitstr, hot.predict)
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft or prediction, aura.applications, tonumber(start), tonumber(duration))
|
|
else
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
|
end
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if table.getn(unit.indicator_custom) > 0 then
|
|
for _, aura in ipairs(C_UnitAuras.GetUnitAuras(unitstr, "HELPFUL")) do
|
|
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
|
local lowerName = string.lower(aura.name)
|
|
for _, filter in pairs(unit.indicator_custom) do
|
|
if filter == lowerName then
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local debuffFilter = unit.config.selfdebuff == "1" and "HARMFUL|PLAYER" or "HARMFUL"
|
|
for i=1,16 do -- scan for custom debuffs
|
|
local aura = C_UnitAuras.GetAuraDataByIndex(unitstr, i, debuffFilter)
|
|
if aura then
|
|
local timeleft = aura.expirationTime > 0 and (aura.expirationTime - GetTime()) or nil
|
|
for _, filter in pairs(unit.indicator_custom) do
|
|
if filter == string.lower(aura.name) then
|
|
pfUI.uf:AddIcon(unit, pos, aura.icon, timeleft, aura.applications)
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- hide unused icon slots
|
|
for pos=pos, 6 do pfUI.uf:HideIcon(unit, pos) end
|
|
end
|
|
|
|
-- portrait
|
|
if unit.portrait and ( component == "all" or component == "portrait" ) then
|
|
if C.unitframes.always2dportrait == "1" then
|
|
unit.portrait.tex:Show()
|
|
unit.portrait.model:Hide()
|
|
SetPortraitTexture(unit.portrait.tex, unitstr)
|
|
else
|
|
if not UnitIsVisible(unitstr) or not UnitIsConnected(unitstr) then
|
|
if unit.config.portrait == "bar" then
|
|
unit.portrait.tex:Hide()
|
|
unit.portrait.model:Hide()
|
|
elseif C.unitframes.portraittexture == "1" then
|
|
unit.portrait.tex:Show()
|
|
unit.portrait.model:Hide()
|
|
SetPortraitTexture(unit.portrait.tex, unitstr)
|
|
else
|
|
unit.portrait.tex:Hide()
|
|
unit.portrait.model:Show()
|
|
unit.portrait.model:SetModelScale(4.25)
|
|
unit.portrait.model:SetPosition(0, 0, -1)
|
|
unit.portrait.model:SetModel("Interface\\Buttons\\talktomequestionmark.mdx")
|
|
end
|
|
else
|
|
if unit.config.portrait == "bar" then
|
|
unit.portrait:SetAlpha(C.unitframes.portraitalpha)
|
|
else
|
|
unit.portrait:SetAlpha(1)
|
|
end
|
|
unit.portrait.tex:Hide()
|
|
unit.portrait.model:Show()
|
|
|
|
if component == "portrait" then
|
|
-- regular portrait update after event
|
|
unit.portrait.model.update = unitstr
|
|
else
|
|
-- detect portrait change without events
|
|
unit.portrait.model.next:SetUnit(unitstr)
|
|
if unit.portrait.model.lastUnit ~= UnitName(unitstr) or unit.portrait.model:GetModel() ~= unit.portrait.model.next:GetModel() then
|
|
unit.portrait.model.update = unitstr
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- base frame
|
|
if component == "all" or component == "base" then
|
|
-- Unit HP/MP with Nampower Integration
|
|
local hp, hpmax, power, powermax, powerType = pfUI.api.GetUnitStats(unitstr)
|
|
|
|
-- 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 buttons = {
|
|
[1] = "LeftButton",
|
|
[2] = "RightButton",
|
|
[3] = "MiddleButton",
|
|
[4] = "Button4",
|
|
[5] = "Button5",
|
|
}
|
|
|
|
local modifiers = {
|
|
[""] = "",
|
|
["alt"] = "_alt",
|
|
["ctrl"] = "_ctrl",
|
|
["shift"] = "_shift",
|
|
}
|
|
|
|
function pfUI.uf:EnableClickCast()
|
|
if self.config.clickcast ~= "1" then return end
|
|
for bid, button in pairs(buttons) do
|
|
for modifier, mconf in pairs(modifiers) do
|
|
local bconf = bid == 1 and "" or bid
|
|
if pfUI_config.unitframes["clickcast"..bconf..mconf] ~= "" then
|
|
-- fill clickaction table
|
|
self.clickactions = self.clickactions or {}
|
|
self.clickactions[modifier..button] = pfUI_config.unitframes["clickcast"..bconf..mconf]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:ClickAction(button)
|
|
local label = this.label or ""
|
|
local id = this.id or ""
|
|
local unitstr = label .. id
|
|
local showmenu = button == "RightButton" and true or nil
|
|
if SpellIsTargeting() and button == "RightButton" then
|
|
SpellStopTargeting()
|
|
return
|
|
end
|
|
|
|
if SpellIsTargeting() and button == "LeftButton" then
|
|
SpellTargetUnit(unitstr)
|
|
elseif CursorHasItem() then
|
|
DropItemOnUnit(unitstr)
|
|
end
|
|
|
|
-- run click casting if enabled
|
|
local modstring = ""
|
|
modstring = IsAltKeyDown() and modstring.."alt" or modstring
|
|
modstring = IsControlKeyDown() and modstring.."ctrl" or modstring
|
|
modstring = IsShiftKeyDown() and modstring.."shift" or modstring
|
|
modstring = modstring..button
|
|
if this.clickactions and this.clickactions[modstring] then
|
|
local action_lower = string.lower(this.clickactions[modstring])
|
|
if string.find(action_lower, "^menu") then
|
|
-- show menu
|
|
showmenu = true
|
|
elseif string.find(action_lower, "^target") then
|
|
-- target unit
|
|
showmenu = nil
|
|
else
|
|
-- run click cast action
|
|
local is_macro = string.find(this.clickactions[modstring], "^%/(.+)")
|
|
|
|
local tswitch = UnitIsUnit(unitstr, "target")
|
|
TargetUnit(unitstr)
|
|
|
|
if is_macro then
|
|
RunMacroText(this.clickactions[modstring])
|
|
else
|
|
CastSpellByName(this.clickactions[modstring])
|
|
end
|
|
|
|
if not tswitch then TargetLastTarget() end
|
|
|
|
return
|
|
end
|
|
end
|
|
|
|
-- dropdown menus
|
|
if showmenu then
|
|
pfUI.uf:RightClickAction(label)
|
|
return
|
|
end
|
|
|
|
-- drop food on petframe
|
|
if label == "pet" and CursorHasItem() then
|
|
local _, playerClass = UnitClass("player")
|
|
if playerClass == "HUNTER" then
|
|
DropItemOnUnit("pet")
|
|
return
|
|
end
|
|
end
|
|
|
|
-- default click
|
|
TargetUnit(unitstr)
|
|
end
|
|
|
|
function pfUI.uf:AddIcon(frame, pos, icon, timeleft, stacks, start, duration)
|
|
local showtime = frame.config.indicator_time == "1" and true or nil
|
|
local showstacks = frame.config.indicator_stacks == "1" and true or nil
|
|
local position = frame.config.indicator_pos or "TOPLEFT"
|
|
local iconsize = tonumber(frame.config.indicator_size)
|
|
local spacing = tonumber(frame.config.indicator_spacing)
|
|
|
|
if not frame.hp then return end
|
|
local frame = frame.hp.bar
|
|
if pos > 6 or pos > ceil(frame:GetWidth() / iconsize) then return end
|
|
|
|
frame.icon = frame.icon or CreateFrame("Frame", nil, frame)
|
|
|
|
if not frame.icon[pos] then
|
|
frame.icon[pos] = CreateFrame("Frame", nil, frame.icon)
|
|
frame.icon[pos]:SetParent(frame)
|
|
frame.icon[pos].tex = frame.icon[pos]:CreateTexture("OVERLAY")
|
|
frame.icon[pos].tex:SetAllPoints(frame.icon[pos])
|
|
frame.icon[pos].tex:SetTexCoord(.08, .92, .08, .92)
|
|
frame.icon[pos].stacks = frame.icon[pos]:CreateFontString(nil, "OVERLAY")
|
|
frame.icon[pos].stacks:SetPoint("BOTTOMRIGHT", 0, 0)
|
|
frame.icon[pos].stacks:SetJustifyH("RIGHT")
|
|
frame.icon[pos].stacks:SetJustifyV("BOTTOM")
|
|
|
|
-- Check if parent frame has cooldown animation enabled
|
|
local parent_cooldown_anim = frame.config and tonumber(frame.config.cooldown_anim) or 1
|
|
if parent_cooldown_anim == 1 then
|
|
frame.icon[pos].cd = CreateFrame(COOLDOWN_FRAME_TYPE, nil, frame.icon[pos])
|
|
else
|
|
frame.icon[pos].cd = CreateFrame("Frame", nil, frame.icon[pos])
|
|
frame.icon[pos].cd.AdvanceTime = DoNothing
|
|
frame.icon[pos].cd.SetSequence = DoNothing
|
|
frame.icon[pos].cd.SetSequenceTime = DoNothing
|
|
end
|
|
|
|
frame.icon[pos].cd.pfCooldownStyleAnimation = 0
|
|
frame.icon[pos].cd.pfCooldownType = "ALL"
|
|
frame.icon[pos].cd:SetFrameLevel(48)
|
|
end
|
|
|
|
-- update icon configuration
|
|
if frame.icon[pos].iconsize ~= iconsize or frame.icon[pos].spacing ~= spacing then
|
|
frame.icon[pos]:SetWidth(iconsize)
|
|
frame.icon[pos]:SetHeight(iconsize)
|
|
frame.icon[pos]:SetPoint("TOPLEFT", frame.icon, "TOPLEFT", (pos-1)*(iconsize + spacing), 0)
|
|
frame.icon[pos].stacks:SetFont(pfUI.font_unit, math.max(iconsize/3, 10), "OUTLINE")
|
|
frame.icon[pos].iconsize = iconsize
|
|
frame.icon[pos].spacing = spacing
|
|
end
|
|
|
|
-- update icon
|
|
if frame.icon[pos].icon ~= icon then
|
|
frame.icon[pos].tex:SetTexture(icon)
|
|
frame.icon[pos].icon = icon
|
|
end
|
|
|
|
-- show remaining time if config is set
|
|
if showtime and start and duration and timeleft < 100 and iconsize > 9 then
|
|
CooldownFrame_SetTimer(frame.icon[pos].cd, start, duration, 1)
|
|
elseif showtime and timeleft and timeleft < 100 and iconsize > 9 then
|
|
CooldownFrame_SetTimer(frame.icon[pos].cd, GetTime(), timeleft, 1)
|
|
else
|
|
CooldownFrame_SetTimer(frame.icon[pos].cd, GetTime(), 0, 1)
|
|
end
|
|
|
|
-- show stacks if config is set
|
|
if showstacks and stacks and stacks > 1 and iconsize > 9 then
|
|
frame.icon[pos].stacks:SetText(stacks)
|
|
else
|
|
frame.icon[pos].stacks:SetText("")
|
|
end
|
|
|
|
-- update parent icon size
|
|
if frame.icon.iconsize ~= iconsize then
|
|
frame.icon:SetHeight(iconsize)
|
|
frame.icon.iconsize = iconsize
|
|
end
|
|
|
|
-- update parent position
|
|
if frame.icon.position ~= position then
|
|
frame.icon:ClearAllPoints()
|
|
frame.icon:SetPoint(position, frame, position, 0, 0)
|
|
frame.icon.position = position
|
|
end
|
|
|
|
frame.icon[pos]:Show()
|
|
frame.icon:SetWidth((pos-1)*(iconsize+spacing)+iconsize)
|
|
end
|
|
|
|
function pfUI.uf:HideIcon(frame, pos)
|
|
if not frame or not frame.hp or not frame.hp.bar then return end
|
|
|
|
local frame = frame.hp.bar
|
|
if frame.icon and frame.icon[pos] then
|
|
frame.icon[pos]:Hide()
|
|
end
|
|
end
|
|
|
|
function pfUI.uf:SetupDebuffFilter(allclasses)
|
|
local _, myclass = UnitClass("player")
|
|
local debuffs = {}
|
|
|
|
if myclass == "PALADIN" or myclass == "PRIEST" or myclass == "WARLOCK" or allclasses then
|
|
table.insert(debuffs, "Magic")
|
|
end
|
|
|
|
if myclass == "DRUID" or myclass == "PALADIN" or myclass == "SHAMAN" or allclasses then
|
|
table.insert(debuffs, "Poison")
|
|
end
|
|
|
|
if myclass == "PRIEST" or myclass == "PALADIN" or myclass == "SHAMAN" or allclasses then
|
|
table.insert(debuffs, "Disease")
|
|
end
|
|
|
|
if myclass == "DRUID" or myclass == "MAGE" or allclasses then
|
|
table.insert(debuffs, "Curse")
|
|
end
|
|
|
|
return debuffs
|
|
end
|
|
|
|
function pfUI.uf:SetupBuffIndicators(config)
|
|
local _, myclass = UnitClass("player")
|
|
local indicators = {}
|
|
|
|
if config.show_buffs == "1" then -- buffs
|
|
if myclass == "DRUID" then
|
|
-- Mark of the Wild
|
|
table.insert(indicators, "interface\\icons\\spell_nature_regeneration")
|
|
-- Gift of the Wild
|
|
table.insert(indicators, "interface\\icons\\spell_nature_giftofthewild")
|
|
-- Thorns
|
|
table.insert(indicators, "interface\\icons\\spell_nature_thorns")
|
|
end
|
|
|
|
if myclass == "PRIEST" then
|
|
-- Prayer Of Fortitude"
|
|
table.insert(indicators, "interface\\icons\\spell_holy_wordfortitude")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayeroffortitude")
|
|
-- Prayer of Spirit
|
|
table.insert(indicators, "interface\\icons\\spell_holy_divinespirit")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofspirit")
|
|
-- Shadow Protection
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_antishadow")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofshadowprotection")
|
|
-- Fear Ward
|
|
table.insert(indicators, "interface\\icons\\spell_holy_excorcism")
|
|
end
|
|
|
|
if myclass == "PALADIN" then
|
|
-- Blessing of Salvation
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsalvation")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofsalvation")
|
|
-- Blessing of Wisdom
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofwisdom")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofwisdom")
|
|
-- Blessing of Sanctuary
|
|
table.insert(indicators, "interface\\icons\\spell_nature_lightningshield")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofsanctuary")
|
|
-- Blessing of Kings
|
|
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
|
|
table.insert(indicators, "interface\\icons\\spell_magic_greaterblessingofkings")
|
|
-- Blessing of Might
|
|
table.insert(indicators, "interface\\icons\\spell_holy_fistofjustice")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingofkings")
|
|
-- Blessing of Light
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofhealing02")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_greaterblessingoflight")
|
|
-- Blessing of Sacrifice
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofsacrifice")
|
|
-- Blessing of Freedom
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofvalor")
|
|
-- Blessing of Protection
|
|
table.insert(indicators, "interface\\icons\\spell_holy_sealofprotection")
|
|
end
|
|
|
|
if myclass == "WARLOCK" then
|
|
-- Fire Shield
|
|
table.insert(indicators, "interface\\icons\\spell_fire_firearmor")
|
|
-- Blood Pact
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_bloodboil")
|
|
-- Soulstone
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_soulgem")
|
|
-- Unending Breath
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_demonbreath")
|
|
-- Detect Greater Invisibility or Detect Invisibility
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_detectinvisibility")
|
|
-- Detect Lesser Invisibility
|
|
table.insert(indicators, "interface\\icons\\spell_shadow_detectlesserinvisibility")
|
|
-- Paranoia
|
|
table.insert(indicators, "interface\\icons\\Spell_Shadow_AuraOfDarkness")
|
|
end
|
|
|
|
if myclass == "WARRIOR" then
|
|
-- Battle Shout
|
|
table.insert(indicators, "interface\\icons\\ability_warrior_battleshout")
|
|
-- Commanding Shout (TBC)
|
|
table.insert(indicators, "interface\\icons\\ability_warrior_rallyingcry")
|
|
end
|
|
|
|
if myclass == "MAGE" then
|
|
-- Arcane Intellect
|
|
table.insert(indicators, "interface\\icons\\spell_holy_magicalsentry")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_arcaneintellect")
|
|
-- Dampen Magic
|
|
table.insert(indicators, "interface\\icons\\spell_nature_abolishmagic")
|
|
-- Amplify Magic
|
|
table.insert(indicators, "interface\\icons\\spell_holy_flashheal")
|
|
end
|
|
|
|
if myclass == "HUNTER" then
|
|
-- Aspect of the Wild
|
|
table.insert(indicators, "interface\\icons\\spell_nature_protectionformnature")
|
|
|
|
-- Aspect of the Pack
|
|
table.insert(indicators, "interface\\icons\\ability_mount_whitetiger")
|
|
|
|
-- Misdirection (TBC)
|
|
table.insert(indicators, "interface\\icons\\ability_hunter_misdirection")
|
|
end
|
|
|
|
if myclass == "SHAMAN" then
|
|
-- Earth Shield (TBC)
|
|
table.insert(indicators, "interface\\icons\\spell_nature_skinofearth")
|
|
end
|
|
end
|
|
|
|
if config.show_procs == "1" then -- procs
|
|
if myclass == "SHAMAN" or config.all_procs == "1" then
|
|
-- Ancestral Fortitude
|
|
table.insert(indicators, "interface\\icons\\spell_nature_undyingstrength")
|
|
-- Healing Way
|
|
table.insert(indicators, "interface\\icons\\spell_nature_healingway")
|
|
-- Totemic Power (known issue: one conflicts with Blessed Sunfruit buff)
|
|
table.insert(indicators, "interface\\icons\\spell_holy_spiritualguidence")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_devotion")
|
|
table.insert(indicators, "interface\\icons\\spell_holy_holynova")
|
|
table.insert(indicators, "interface\\icons\\spell_magic_magearmor")
|
|
end
|
|
|
|
if myclass == "PRIEST" or config.all_procs == "1" then
|
|
-- Inspiration
|
|
table.insert(indicators, "interface\\icons\\inv_shield_06")
|
|
end
|
|
end
|
|
|
|
if config.show_hots == "1" then -- hots
|
|
if myclass == "PRIEST" or config.all_hots == "1" then
|
|
-- Renew
|
|
table.insert(indicators, "interface\\icons\\spell_holy_renew")
|
|
-- Power Word: Shield
|
|
table.insert(indicators, "interface\\icons\\spell_holy_powerwordshield")
|
|
-- Prayer of Mending (TBC)
|
|
table.insert(indicators, "interface\\icons\\spell_holy_prayerofmendingtga")
|
|
end
|
|
|
|
if myclass == "DRUID" or config.all_hots == "1" then
|
|
-- Regrowth
|
|
table.insert(indicators, "interface\\icons\\spell_nature_resistnature")
|
|
-- Rejuvenation
|
|
table.insert(indicators, "interface\\icons\\spell_nature_rejuvenation")
|
|
-- Lifebloom
|
|
table.insert(indicators, "interface\\icons\\inv_misc_herb_felblossom")
|
|
end
|
|
end
|
|
|
|
if config.show_totems == "1" and myclass == "SHAMAN" then -- totems
|
|
-- Strength of Earth Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_earthbindtotem")
|
|
-- Stoneskin Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_stoneskintotem")
|
|
-- Mana Spring Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_manaregentotem")
|
|
-- Mana Tide Totem
|
|
table.insert(indicators, "interface\\icons\\spell_frost_summonwaterelemental")
|
|
-- Healing Spring Totem
|
|
table.insert(indicators, "interface\\icons\\inv_spear_04")
|
|
-- Tranquil Air Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_brilliance")
|
|
-- Grace of Air Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_invisibilitytotem")
|
|
-- Grounding Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_groundingtotem")
|
|
-- Nature Resistance Totem
|
|
table.insert(indicators, "interface\\icons\\spell_nature_natureresistancetotem")
|
|
-- Fire Resistance Totem
|
|
table.insert(indicators, "interface\\icons\\spell_fireresistancetotem_01")
|
|
-- Frost Resistance Totem
|
|
table.insert(indicators, "interface\\icons\\spell_frostresistancetotem_01")
|
|
end
|
|
|
|
return indicators
|
|
end
|
|
|
|
local function abbrevname(t)
|
|
return string.sub(t,1,1)..". "
|
|
end
|
|
|
|
function pfUI.uf:GetNameString(unitstr)
|
|
local name = UnitName(unitstr)
|
|
local abbrev = pfUI_config.unitframes.abbrevname == "1" or nil
|
|
local size = 20
|
|
|
|
-- first try to only abbreviate the first word
|
|
if abbrev and name and strlen(name) > size then
|
|
name = string.gsub(name, "^(%S+) ", abbrevname)
|
|
end
|
|
|
|
-- abbreviate all if it still doesn't fit
|
|
if abbrev and name and strlen(name) > size then
|
|
name = string.gsub(name, "(%S+) ", abbrevname)
|
|
end
|
|
|
|
return name
|
|
end
|
|
|
|
function pfUI.uf:GetLevelString(unitstr)
|
|
local level = UnitLevel(unitstr)
|
|
if level == -1 then level = "??" end
|
|
|
|
local elite = UnitClassification(unitstr)
|
|
if elite == "worldboss" then
|
|
level = level .. "B"
|
|
elseif elite == "rareelite" then
|
|
level = level .. "R+"
|
|
elseif elite == "elite" then
|
|
level = level .. "+"
|
|
elseif elite == "rare" then
|
|
level = level .. "R"
|
|
end
|
|
|
|
return level
|
|
end
|
|
|
|
function pfUI.uf:GetStatusValue(unit, pos)
|
|
if not pos or not unit then return end
|
|
local config = unit.config["txt"..pos]
|
|
local unitstr = unit.label .. unit.id
|
|
local frame = unit[pos .. "Text"]
|
|
|
|
-- as a fallback, draw the name
|
|
if pos == "center" and not config then
|
|
config = "unit"
|
|
end
|
|
|
|
-- Get stats with Nampower Integration
|
|
local hp, hpmax, mp, mpmax, powerType = pfUI.api.GetUnitStats(unitstr)
|
|
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
|
|
|