You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2568 lines
83 KiB

local detailsFramework = _G["DetailsFramework"]
if (not detailsFramework or not DetailsFrameworkCanLoad) then
return
end
local _
--lua locals
local rawset = rawset --lua local
local rawget = rawget --lua local
local setmetatable = setmetatable --lua local
local unpack = table.unpack or unpack --lua local
local type = type --lua local
local floor = math.floor --lua local
local loadstring = loadstring --lua local
local G_CreateFrame = _G.CreateFrame
local CreateFrame = function (frameType , name, parent, template, id)
local frame = G_CreateFrame(frameType , name, parent, template, id)
detailsFramework:Mixin(frame, detailsFramework.FrameFunctions)
frame:SetClipsChildren(false)
return frame
end
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitGetIncomingHeals = UnitGetIncomingHeals
local UnitGetTotalHealAbsorbs = UnitGetTotalHealAbsorbs
local UnitGetTotalAbsorbs = UnitGetTotalAbsorbs
local UnitPowerMax = UnitPowerMax
local UnitPower = UnitPower
local UnitPowerBarID = UnitPowerBarID
local GetUnitPowerBarInfoByID = GetUnitPowerBarInfoByID
local IsInGroup = IsInGroup
local UnitPowerType = UnitPowerType
local UnitIsConnected = UnitIsConnected
local UnitPlayerControlled = UnitPlayerControlled
local UnitIsTapDenied = UnitIsTapDenied
local max = math.max
local min = math.min
local abs = math.abs
local GetSpellInfo = GetSpellInfo or function(spellID) if not spellID then return nil end local si = C_Spell.GetSpellInfo(spellID) if si then return si.name, nil, si.iconID, si.castTime, si.minRange, si.maxRange, si.spellID, si.originalIconID end end
local IS_WOW_PROJECT_MAINLINE = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE
local IS_WOW_PROJECT_NOT_MAINLINE = WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE
local IS_WOW_PROJECT_CLASSIC_ERA = WOW_PROJECT_ID == WOW_PROJECT_CLASSIC
local CastInfo = detailsFramework.CastInfo
local PixelUtil = PixelUtil or DFPixelUtil
local UnitGroupRolesAssigned = detailsFramework.UnitGroupRolesAssigned
local cleanfunction = function() end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--health bar frame
--[=[
DF:CreateHealthBar (parent, name, settingsOverride)
creates a health bar to show an unit health
@parent = frame to pass for the CreateFrame function
@name = absolute name of the frame, if omitted it uses the parent's name .. "HealthBar"
@settingsOverride = table with keys and values to replace the defaults from the framework
methods:
healthbar:SetUnit(unit)
healthBar:GetTexture()
healthBar:SetTexture(texture)
--]=]
---@class df_healthbarsettings : table
---@field CanTick boolean
---@field ShowHealingPrediction boolean
---@field ShowShields boolean
---@field BackgroundColor table
---@field Texture texturepath|textureid|atlasname
---@field ShieldIndicatorTexture texturepath|textureid|atlasname
---@field ShieldGlowTexture texturepath|textureid|atlasname
---@field ShieldGlowWidth number
---@field DontSetStatusBarTexture boolean
---@field Width number
---@field Height number
---@class df_healthbar : statusbar, df_scripthookmixin, df_statusbarmixin
---@field unit unit
---@field displayedUnit unit
---@field oldHealth number
---@field currentHealth number
---@field currentHealthMax number
---@field nextShieldHook number
---@field WidgetType string
---@field Settings df_healthbarsettings
---@field background texture
---@field incomingHealIndicator texture
---@field shieldAbsorbIndicator texture
---@field healAbsorbIndicator texture
---@field shieldAbsorbGlow texture
---@field barTexture texture
---@field SetUnit fun(self:df_healthbar, unit:unit?, displayedUnit:unit)
---@field GetTexture fun(self:df_healthbar) : texture
---@field SetTexture fun(self:df_healthbar, texture:texturepath|textureid|atlasname)
---@field SetColor fun(self:df_healthbar, red:number, green:number, blue:number, alpha:number)
---@field UpdateHealPrediction fun(self:df_healthbar)
---@field UpdateHealth fun(self:df_healthbar)
---@field UpdateMaxHealth fun(self:df_healthbar)
--healthBar meta prototype
local healthBarMetaPrototype = {
WidgetType = "healthBar",
dversion = detailsFramework.dversion,
}
--check if there's a metaPrototype already existing
if (_G[detailsFramework.GlobalWidgetControlNames["healthBar"]]) then
--get the already existing metaPrototype
local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames["healthBar"]]
--check if is older
if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then
--the version is older them the currently loading one
--copy the new values into the old metatable
for funcName, _ in pairs(healthBarMetaPrototype) do
oldMetaPrototype[funcName] = healthBarMetaPrototype[funcName]
end
end
else
--first time loading the framework
_G[detailsFramework.GlobalWidgetControlNames["healthBar"]] = healthBarMetaPrototype
end
local healthBarMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["healthBar"]]
detailsFramework:Mixin(healthBarMetaFunctions, detailsFramework.ScriptHookMixin)
--hook list
local defaultHooksForHealthBar = {
OnHide = {},
OnShow = {},
OnHealthChange = {},
OnHealthMaxChange = {},
OnAbsorbOverflow = {},
}
--use the hook already existing
healthBarMetaFunctions.HookList = healthBarMetaFunctions.HookList or defaultHooksForHealthBar
--copy the non existing values from a new version to the already existing hook table
detailsFramework.table.deploy(healthBarMetaFunctions.HookList, defaultHooksForHealthBar)
--Health Bar Meta Functions
--health bar settings
healthBarMetaFunctions.Settings = {
CanTick = false, --if true calls the method 'OnTick' every tick, the function needs to be overloaded, it receives self and deltaTime as parameters
ShowHealingPrediction = true, --when casting a healing pass, show the amount of health that spell will heal
ShowShields = true, --indicator of the amount of damage absortion the unit has
DontSetStatusBarTexture = false,
--appearance
BackgroundColor = detailsFramework:CreateColorTable (.2, .2, .2, .8),
Texture = [[Interface\RaidFrame\Raid-Bar-Hp-Fill]],
ShieldIndicatorTexture = [[Interface\RaidFrame\Shield-Fill]],
ShieldGlowTexture = [[Interface\RaidFrame\Shield-Overshield]],
ShieldGlowWidth = 16,
--default size
Width = 100,
Height = 20,
}
healthBarMetaFunctions.HealthBarEvents = {
{"PLAYER_ENTERING_WORLD"},
{"UNIT_HEALTH", true},
{"UNIT_MAXHEALTH", true},
{(IS_WOW_PROJECT_NOT_MAINLINE) and "UNIT_HEALTH_FREQUENT", true}, -- this one is classic-only...
{"UNIT_HEAL_PREDICTION", true},
{(UnitGetTotalAbsorbs) and "UNIT_ABSORB_AMOUNT_CHANGED", true},
{(UnitGetTotalHealAbsorbs) and "UNIT_HEAL_ABSORB_AMOUNT_CHANGED", true},
}
--setup the castbar to be used by another unit
healthBarMetaFunctions.SetUnit = function(self, unit, displayedUnit)
if (self.unit ~= unit or self.displayedUnit ~= displayedUnit or unit == nil) then
self.unit = unit
self.displayedUnit = displayedUnit or unit
--register events
if (unit) then
self.currentHealth = UnitHealth(unit) or 0
self.currentHealthMax = UnitHealthMax(unit) or 0
for _, eventTable in ipairs(self.HealthBarEvents) do
local event = eventTable[1]
local isUnitEvent = eventTable[2]
if event then
if (isUnitEvent) then
self:RegisterUnitEvent(event, self.displayedUnit, self.unit)
else
self:RegisterEvent(event)
end
end
end
--check for settings and update some events
if (not self.Settings.ShowHealingPrediction) then
self:UnregisterEvent("UNIT_HEAL_PREDICTION")
if IS_WOW_PROJECT_MAINLINE then
self:UnregisterEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED")
end
self.incomingHealIndicator:Hide()
self.healAbsorbIndicator:Hide()
end
if (not self.Settings.ShowShields) then
if IS_WOW_PROJECT_MAINLINE then
self:UnregisterEvent("UNIT_ABSORB_AMOUNT_CHANGED")
end
self.shieldAbsorbIndicator:Hide()
self.shieldAbsorbGlow:Hide()
end
--set scripts
self:SetScript("OnEvent", self.OnEvent)
if (self.Settings.CanTick) then
self:SetScript("OnUpdate", self.OnTick)
end
self:PLAYER_ENTERING_WORLD(self.unit, self.displayedUnit)
else
--remove all registered events
for _, eventTable in ipairs(self.HealthBarEvents) do
local event = eventTable[1]
if event then
self:UnregisterEvent(event)
end
end
--remove scripts
self:SetScript("OnEvent", nil)
self:SetScript("OnUpdate", nil)
self:Hide()
end
end
end
healthBarMetaFunctions.Initialize = function(self)
PixelUtil.SetWidth(self, self.Settings.Width, 1)
PixelUtil.SetHeight(self, self.Settings.Height, 1)
self:SetTexture(self.Settings.Texture)
self.background:SetAllPoints()
self.background:SetColorTexture(self.Settings.BackgroundColor:GetColor())
--setpoint of these widgets are set inside the function that updates the incoming heal
self.incomingHealIndicator:SetTexture(self:GetTexture())
self.healAbsorbIndicator:SetTexture(self:GetTexture())
self.healAbsorbIndicator:SetVertexColor(.1, .8, .8)
self.shieldAbsorbIndicator:SetTexture(self.Settings.ShieldIndicatorTexture, true, true)
self.shieldAbsorbGlow:SetWidth(self.Settings.ShieldGlowWidth)
self.shieldAbsorbGlow:SetTexture(self.Settings.ShieldGlowTexture)
self.shieldAbsorbGlow:SetBlendMode("ADD")
self.shieldAbsorbGlow:SetPoint("topright", self, "topright", 8, 0)
self.shieldAbsorbGlow:SetPoint("bottomright", self, "bottomright", 8, 0)
self.shieldAbsorbGlow:Hide()
self:SetUnit(nil)
self.currentHealth = 1
self.currentHealthMax = 2
end
--call every tick
healthBarMetaFunctions.OnTick = function(self, deltaTime) end --if overrided, set 'CanTick' to true on the settings table
--when an event happen for this unit, send it to the apropriate function
healthBarMetaFunctions.OnEvent = function(self, event, ...)
local eventFunc = self[event]
if (eventFunc) then
--the function doesn't receive which event was, only 'self' and the parameters
eventFunc(self, ...)
end
end
--when the unit max health is changed
healthBarMetaFunctions.UpdateMaxHealth = function(self)
local maxHealth = UnitHealthMax(self.displayedUnit)
self:SetMinMaxValues(0, maxHealth)
self.currentHealthMax = maxHealth
if (self.OnHealthMaxChange) then --direct call
self.OnHealthMaxChange(self, self.displayedUnit)
else
self:RunHooksForWidget("OnHealthMaxChange", self, self.displayedUnit)
end
end
healthBarMetaFunctions.UpdateHealth = function(self)
-- update max health regardless to avoid weird wrong values on UpdateMaxHealth sometimes
-- local maxHealth = UnitHealthMax(self.displayedUnit)
-- self:SetMinMaxValues(0, maxHealth)
-- self.currentHealthMax = maxHealth
self.oldHealth = self.currentHealth
local health = UnitHealth(self.displayedUnit)
self.currentHealth = health
PixelUtil.SetStatusBarValue(self, health)
if (self.OnHealthChange) then --direct call
self.OnHealthChange(self, self.displayedUnit)
else
self:RunHooksForWidget("OnHealthChange", self, self.displayedUnit)
end
end
--health and absorbs prediction
healthBarMetaFunctions.UpdateHealPrediction = function(self)
local currentHealth = self.currentHealth
local currentHealthMax = self.currentHealthMax
if (not currentHealthMax or currentHealthMax <= 0) then
return
end
local healthPercent = currentHealth / currentHealthMax
--order is: the health of the unit > damage absorb > heal absorb > incoming heal
local width = self:GetWidth()
if (self.Settings.ShowHealingPrediction) then
--incoming heal on the unit from all sources
local unitHealIncoming = UnitGetIncomingHeals and self.displayedUnit and UnitGetIncomingHeals(self.displayedUnit) or 0
--heal absorbs
local unitHealAbsorb = UnitGetTotalHealAbsorbs and self.displayedUnit and UnitGetTotalHealAbsorbs(self.displayedUnit) or 0
if (unitHealIncoming > 0) then
--calculate what is the percent of health incoming based on the max health the player has
local incomingPercent = unitHealIncoming / currentHealthMax
self.incomingHealIndicator:Show()
self.incomingHealIndicator:SetWidth(max(1, min (width * incomingPercent, abs(healthPercent - 1) * width)))
self.incomingHealIndicator:SetPoint("topleft", self, "topleft", width * healthPercent, 0)
self.incomingHealIndicator:SetPoint("bottomleft", self, "bottomleft", width * healthPercent, 0)
else
self.incomingHealIndicator:Hide()
end
if (unitHealAbsorb > 0) then
local healAbsorbPercent = unitHealAbsorb / currentHealthMax
self.healAbsorbIndicator:Show()
self.healAbsorbIndicator:SetWidth(max(1, min (width * healAbsorbPercent, abs(healthPercent - 1) * width)))
self.healAbsorbIndicator:SetPoint("topleft", self, "topleft", width * healthPercent, 0)
self.healAbsorbIndicator:SetPoint("bottomleft", self, "bottomleft", width * healthPercent, 0)
else
self.healAbsorbIndicator:Hide()
end
end
if (self.Settings.ShowShields and UnitGetTotalAbsorbs) then
--damage absorbs
local unitDamageAbsorb = self.displayedUnit and UnitGetTotalAbsorbs (self.displayedUnit) or 0
if (unitDamageAbsorb > 0) then
local damageAbsorbPercent = unitDamageAbsorb / currentHealthMax
self.shieldAbsorbIndicator:Show()
--set the width where the max width size is what is lower: the absorb size or the missing amount of health in the health bar
--/dump NamePlate1PlaterUnitFrameHealthBar.shieldAbsorbIndicator:GetSize()
self.shieldAbsorbIndicator:SetWidth(max(1, min (width * damageAbsorbPercent, abs(healthPercent - 1) * width)))
self.shieldAbsorbIndicator:SetPoint("topleft", self, "topleft", width * healthPercent, 0)
self.shieldAbsorbIndicator:SetPoint("bottomleft", self, "bottomleft", width * healthPercent, 0)
--if the absorb percent pass 100%, show the glow
if ((healthPercent + damageAbsorbPercent) > 1) then
self.nextShieldHook = self.nextShieldHook or 0
if (GetTime() >= self.nextShieldHook) then
self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, healthPercent + damageAbsorbPercent - 1)
self.nextShieldHook = GetTime() + 0.2
end
self.shieldAbsorbGlow:Show()
else
self.shieldAbsorbGlow:Hide()
if (self.nextShieldHook) then
self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, 0)
self.nextShieldHook = nil
end
end
else
self.shieldAbsorbIndicator:Hide()
self.shieldAbsorbGlow:Hide()
if (self.nextShieldHook) then
self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, 0)
self.nextShieldHook = nil
end
end
else
self.shieldAbsorbIndicator:Hide()
self.shieldAbsorbGlow:Hide()
if (self.nextShieldHook) then
self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, 0)
self.nextShieldHook = nil
end
end
end
--Health Events
healthBarMetaFunctions.PLAYER_ENTERING_WORLD = function(self, ...)
self:UpdateMaxHealth()
self:UpdateHealth()
self:UpdateHealPrediction()
end
healthBarMetaFunctions.UNIT_HEALTH = function(self, unitId)
self:UpdateHealth()
self:UpdateHealPrediction()
end
healthBarMetaFunctions.UNIT_MAXHEALTH = function(self, unitId)
self:UpdateMaxHealth()
self:UpdateHealth()
self:UpdateHealPrediction()
end
healthBarMetaFunctions.UNIT_HEALTH_FREQUENT = function(self, ...)
self:UpdateHealth()
self:UpdateHealPrediction()
end
healthBarMetaFunctions.UNIT_HEAL_PREDICTION = function(self, ...)
self:UpdateMaxHealth()
self:UpdateHealth()
self:UpdateHealPrediction()
end
healthBarMetaFunctions.UNIT_ABSORB_AMOUNT_CHANGED = function(self, ...)
self:UpdateMaxHealth()
self:UpdateHealth()
self:UpdateHealPrediction()
end
healthBarMetaFunctions.UNIT_HEAL_ABSORB_AMOUNT_CHANGED = function(self, ...)
self:UpdateMaxHealth()
self:UpdateHealth()
self:UpdateHealPrediction()
end
-- ~healthbar
---comment
---@param parent frame
---@param name string?
---@param settingsOverride table? a table with key/value pairs to override the default settings
---@return df_healthbar
function detailsFramework:CreateHealthBar(parent, name, settingsOverride)
assert(name or parent:GetName(), "DetailsFramework:CreateHealthBar parameter 'name' omitted and parent has no name.")
local healthBar = CreateFrame("StatusBar", name or (parent:GetName() .. "HealthBar"), parent, "BackdropTemplate")
do --layers
--background
healthBar.background = healthBar:CreateTexture(nil, "background")
healthBar.background:SetDrawLayer("background", -6)
--artwork
--healing incoming
healthBar.incomingHealIndicator = healthBar:CreateTexture(nil, "artwork", nil, 5)
healthBar.incomingHealIndicator:SetDrawLayer("artwork", 4)
--current shields on the unit
healthBar.shieldAbsorbIndicator = healthBar:CreateTexture(nil, "artwork", nil, 3)
healthBar.shieldAbsorbIndicator:SetDrawLayer("artwork", 5)
--debuff absorbing heal
healthBar.healAbsorbIndicator = healthBar:CreateTexture(nil, "artwork", nil, 4)
healthBar.healAbsorbIndicator:SetDrawLayer("artwork", 6)
--the shield fills all the bar, show that cool glow
healthBar.shieldAbsorbGlow = healthBar:CreateTexture(nil, "artwork", nil, 6)
healthBar.shieldAbsorbGlow:SetDrawLayer("artwork", 7)
--statusbar texture
healthBar.barTexture = healthBar:CreateTexture(nil, "artwork", nil, 1)
end
--mixins
detailsFramework:Mixin(healthBar, healthBarMetaFunctions)
detailsFramework:Mixin(healthBar, detailsFramework.StatusBarFunctions)
healthBar:CreateTextureMask()
healthBar:SetTexture([[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])
--settings and hooks
local settings = detailsFramework.table.copy({}, healthBarMetaFunctions.Settings)
if (settingsOverride) then
detailsFramework.table.copy(settings, settingsOverride)
end
healthBar.Settings = settings
if (healthBar.Settings.DontSetStatusBarTexture) then
healthBar.barTexture:SetAllPoints()
else
healthBar:SetStatusBarTexture(healthBar.barTexture)
end
--hook list
healthBar.HookList = detailsFramework.table.copy({}, healthBarMetaFunctions.HookList)
--initialize the cast bar
healthBar:Initialize()
return healthBar
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--power bar frame
--[=[
DF:CreatePowerBar (parent, name, settingsOverride)
creates statusbar frame to show the unit power bar
@parent = frame to pass for the CreateFrame function
@name = absolute name of the frame, if omitted it uses the parent's name .. "PPowerBar"
@settingsOverride = table with keys and values to replace the defaults from the framework
--]=]
---@class df_powerbarsettings : table
---@field ShowAlternatePower boolean
---@field ShowPercentText boolean
---@field HideIfNoPower boolean
---@field CanTick boolean
---@field BackgroundColor table
---@field Texture texturepath|textureid|atlasname
---@field Width number
---@field Height number
---@class df_powerbar : statusbar, df_scripthookmixin, df_statusbarmixin
---@field unit string
---@field displayedUnit string
---@field WidgetType string
---@field currentPower number
---@field currentPowerMax number
---@field powerType number
---@field minPower number
---@field Settings df_powerbarsettings
---@field background texture
---@field percentText fontstring
---@field SetUnit fun(self:df_healthbar, unit:unit?, displayedUnit:unit?)
detailsFramework.PowerFrameFunctions = {
WidgetType = "powerBar",
HookList = {
OnHide = {},
OnShow = {},
},
Settings = {
--misc
ShowAlternatePower = true, --if true it'll show alternate power over the regular power the unit uses
ShowPercentText = true, --if true show a text with the current energy percent
HideIfNoPower = true, --if true and the UnitMaxPower returns zero, it'll hide the power bar with self:Hide()
CanTick = false, --if it calls the OnTick function every tick
--appearance
BackgroundColor = detailsFramework:CreateColorTable (.2, .2, .2, .8),
Texture = [[Interface\RaidFrame\Raid-Bar-Resource-Fill]],
--default size
Width = 100,
Height = 20,
},
PowerBarEvents = {
{"PLAYER_ENTERING_WORLD"},
{"UNIT_DISPLAYPOWER", true},
{"UNIT_POWER_BAR_SHOW", true},
{"UNIT_POWER_BAR_HIDE", true},
{"UNIT_MAXPOWER", true},
{"UNIT_POWER_UPDATE", true},
{"UNIT_POWER_FREQUENT", true},
},
--setup the castbar to be used by another unit
SetUnit = function(self, unit, displayedUnit)
if (self.unit ~= unit or self.displayedUnit ~= displayedUnit or unit == nil) then
self.unit = unit
self.displayedUnit = displayedUnit or unit
--register events
if (unit) then
for _, eventTable in ipairs(self.PowerBarEvents) do
local event = eventTable[1]
local isUnitEvent = eventTable[2]
if (isUnitEvent) then
self:RegisterUnitEvent(event, self.displayedUnit)
else
self:RegisterEvent(event)
end
end
--set scripts
self:SetScript("OnEvent", self.OnEvent)
if (self.Settings.CanTick) then
self:SetScript("OnUpdate", self.OnTick)
end
self:Show()
self:UpdatePowerBar()
else
--remove all registered events
for _, eventTable in ipairs(self.PowerBarEvents) do
local event = eventTable[1]
self:UnregisterEvent(event)
end
--remove scripts
self:SetScript("OnEvent", nil)
self:SetScript("OnUpdate", nil)
self:Hide()
end
end
end,
Initialize = function(self)
PixelUtil.SetWidth (self, self.Settings.Width)
PixelUtil.SetHeight(self, self.Settings.Height)
self:SetTexture(self.Settings.Texture)
self.background:SetAllPoints()
self.background:SetColorTexture(self.Settings.BackgroundColor:GetColor())
if (self.Settings.ShowPercentText) then
self.percentText:Show()
PixelUtil.SetPoint(self.percentText, "center", self, "center", 0, 0)
detailsFramework:SetFontSize(self.percentText, 9)
detailsFramework:SetFontColor(self.percentText, "white")
detailsFramework:SetFontOutline(self.percentText, "OUTLINE")
else
self.percentText:Hide()
end
self:SetUnit(nil)
end,
--call every tick
OnTick = function(self, deltaTime) end, --if overrided, set 'CanTick' to true on the settings table
--when an event happen for this unit, send it to the apropriate function
OnEvent = function(self, event, ...)
local eventFunc = self[event]
if (eventFunc) then
--the function doesn't receive which event was, only 'self' and the parameters
eventFunc(self, ...)
end
end,
UpdatePowerBar = function(self)
self:UpdatePowerInfo()
self:UpdateMaxPower()
self:UpdatePower()
self:UpdatePowerColor()
end,
--power update
UpdateMaxPower = function(self)
self.currentPowerMax = UnitPowerMax(self.displayedUnit, self.powerType)
self:SetMinMaxValues(self.minPower, self.currentPowerMax)
if (self.currentPowerMax == 0 and self.Settings.HideIfNoPower) then
self:Hide()
end
end,
UpdatePower = function(self)
self.currentPower = UnitPower(self.displayedUnit, self.powerType)
PixelUtil.SetStatusBarValue(self, self.currentPower)
if (self.Settings.ShowPercentText) then
self.percentText:SetText(floor(self.currentPower / self.currentPowerMax * 100) .. "%")
end
end,
--when a event different from unit_power_update is triggered, update which type of power the unit should show
UpdatePowerInfo = function(self)
if (IS_WOW_PROJECT_MAINLINE and self.Settings.ShowAlternatePower) then -- not available in classic
local barID = UnitPowerBarID(self.displayedUnit)
local barInfo = GetUnitPowerBarInfoByID(barID)
--local name, tooltip, cost = GetUnitPowerBarStringsByID(barID);
--barInfo.barType,barInfo.minPower, barInfo.startInset, barInfo.endInset, barInfo.smooth, barInfo.hideFromOthers, barInfo.showOnRaid, barInfo.opaqueSpark, barInfo.opaqueFlash, barInfo.anchorTop, name, tooltip, cost, barInfo.ID, barInfo.forcePercentage, barInfo.sparkUnderFrame;
if (barInfo and barInfo.showOnRaid and IsInGroup()) then
self.powerType = ALTERNATE_POWER_INDEX
self.minPower = barInfo.minPower
return
end
end
self.powerType = UnitPowerType (self.displayedUnit)
self.minPower = 0
end,
--tint the bar with the color of the power, e.g. blue for a mana bar
UpdatePowerColor = function(self)
if (not UnitIsConnected (self.unit)) then
self:SetStatusBarColor(.5, .5, .5)
return
end
if (self.powerType == ALTERNATE_POWER_INDEX) then
--don't change this, keep the same color as the game tints on CompactUnitFrame.lua
self:SetStatusBarColor(0.7, 0.7, 0.6)
return
end
local powerColor = PowerBarColor[self.powerType] --don't appear to be, but PowerBarColor is a global table with all power colors /run Details:Dump (PowerBarColor)
if (powerColor) then
self:SetStatusBarColor(powerColor.r, powerColor.g, powerColor.b)
return
end
local _, _, r, g, b = UnitPowerType(self.displayedUnit)
if (r) then
self:SetStatusBarColor(r, g, b)
return
end
--if everything else fails, tint as rogue energy
powerColor = PowerBarColor["ENERGY"]
self:SetStatusBarColor(powerColor.r, powerColor.g, powerColor.b)
end,
--events
PLAYER_ENTERING_WORLD = function(self, ...)
self:UpdatePowerBar()
end,
UNIT_DISPLAYPOWER = function(self, ...)
self:UpdatePowerBar()
end,
UNIT_POWER_BAR_SHOW = function(self, ...)
self:UpdatePowerBar()
end,
UNIT_POWER_BAR_HIDE = function(self, ...)
self:UpdatePowerBar()
end,
UNIT_MAXPOWER = function(self, ...)
self:UpdateMaxPower()
self:UpdatePower()
end,
UNIT_POWER_UPDATE = function(self, ...)
self:UpdatePower()
end,
UNIT_POWER_FREQUENT = function(self, ...)
self:UpdatePower()
end,
}
detailsFramework:Mixin(detailsFramework.PowerFrameFunctions, detailsFramework.ScriptHookMixin)
-- ~powerbar
---create a power bar
---@param parent frame
---@param name string?
---@param settingsOverride table? a table with key/value pairs to override the default settings
---@return df_powerbar
function detailsFramework:CreatePowerBar(parent, name, settingsOverride)
assert(name or parent:GetName(), "DetailsFramework:CreatePowerBar parameter 'name' omitted and parent has no name.")
local powerBar = CreateFrame("StatusBar", name or (parent:GetName() .. "PowerBar"), parent, "BackdropTemplate")
do --layers
--background
powerBar.background = powerBar:CreateTexture(nil, "background")
powerBar.background:SetDrawLayer("background", -6)
--artwork
powerBar.barTexture = powerBar:CreateTexture(nil, "artwork")
powerBar:SetStatusBarTexture(powerBar.barTexture)
--overlay
powerBar.percentText = powerBar:CreateFontString(nil, "overlay", "GameFontNormal")
end
--mixins
detailsFramework:Mixin(powerBar, detailsFramework.PowerFrameFunctions)
detailsFramework:Mixin(powerBar, detailsFramework.StatusBarFunctions)
powerBar:CreateTextureMask()
powerBar:SetTexture([[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])
--settings and hooks
local settings = detailsFramework.table.copy({}, detailsFramework.PowerFrameFunctions.Settings)
if (settingsOverride) then
detailsFramework.table.copy(settings, settingsOverride)
end
powerBar.Settings = settings
local hookList = detailsFramework.table.copy({}, detailsFramework.PowerFrameFunctions.HookList)
powerBar.HookList = hookList
--initialize the cast bar
powerBar:Initialize()
return powerBar
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--cast bar frame
--[=[
DF:CreateCastBar (parent, name, settingsOverride)
creates a cast bar to show an unit cast
@parent = frame to pass for the CreateFrame function
@name = absolute name of the frame, if omitted it uses the parent's name .. "CastBar"
@settingsOverride = table with keys and values to replace the defaults from the framework
--]=]
---@class df_castbarsettings : table
---@field NoFadeEffects boolean if true it won't play fade effects when a cast if finished
---@field ShowTradeSkills boolean if true, it shows cast for trade skills, e.g. creating an icon with blacksmith
---@field ShowShield boolean if true, shows the shield above the spell icon for non interruptible casts
---@field CanTick boolean if true it will run its OnTick function every tick.
---@field ShowCastTime boolean if true, show the remaining time to finish the cast, lazy tick must be enabled
---@field FadeInTime number amount of time in seconds to go from zero to 100% alpha when starting to cast
---@field FadeOutTime number amount of time in seconds to go from 100% to zero alpha when the cast finishes
---@field CanLazyTick boolean if true, it'll execute the lazy tick function, it ticks in a much slower pace comparece with the regular tick
---@field LazyUpdateCooldown number amount of time to wait for the next lazy update, this updates non critical things like the cast timer
---@field ShowEmpoweredDuration boolean full hold time for empowered spells
---@field FillOnInterrupt boolean
---@field HideSparkOnInterrupt boolean
---@field Width number
---@field Height number
---@field Colors df_castcolors
---@field BackgroundColor table
---@field Texture texturepath|textureid
---@field BorderShieldWidth number
---@field BorderShieldHeight number
---@field BorderShieldCoords table
---@field BorderShieldTexture number
---@field SpellIconWidth number
---@field SpellIconHeight number
---@field ShieldIndicatorTexture texturepath|textureid
---@field ShieldGlowTexture texturepath|textureid
---@field SparkTexture texturepath|textureid
---@field SparkWidth number
---@field SparkHeight number
---@field SparkOffset number
---@alias caststage_color
---| "Casting"
---| "Channeling"
---| "Interrupted"
---| "Failed"
---| "NotInterruptable"
---| "Finished"
---@class df_castcolors : table
---@field Casting table
---@field Channeling table
---@field Interrupted table
---@field Failed table
---@field NotInterruptable table
---@field Finished table
---@class df_castbar : statusbar, df_scripthookmixin, df_statusbarmixin
---@field unit string
---@field displayedUnit string
---@field WidgetType string
---@field value number
---@field maxValue number
---@field spellStartTime number
---@field spellEndTime number
---@field empowered boolean
---@field curStage number
---@field numStages number
---@field empStages {start:number, finish:number}[]
---@field stagePips texture[]
---@field holdAtMaxTime number
---@field casting boolean
---@field channeling boolean
---@field interrupted boolean
---@field failed boolean
---@field finished boolean
---@field canInterrupt boolean
---@field spellID spellid
---@field castID number
---@field spellName spellname
---@field spellTexture textureid
---@field Colors df_castcolors
---@field Settings df_castbarsettings
---@field background texture
---@field extraBackground texture
---@field Text fontstring
---@field BorderShield texture
---@field Icon texture
---@field Spark texture
---@field percentText fontstring
---@field barTexture texture
---@field flashTexture texture
---@field fadeOutAnimation animationgroup
---@field fadeInAnimation animationgroup
---@field flashAnimation animationgroup
---@field SetUnit fun(self:df_castbar, unit:string?)
---@field SetDefaultColor fun(self:df_castbar, colorType: caststage_color, red:any, green:number?, blue:number?, alpha:number?)
---@field UpdateCastColor fun(self:df_castbar) after setting a new color, call this function to update the bar color (while casting or channeling)
---@field GetCastColor fun(self:df_castbar) return a table with the color values for the current state of the casting process
detailsFramework.CastFrameFunctions = {
WidgetType = "castBar",
HookList = {
OnHide = {},
OnShow = {},
--can be regular cast or channel
OnCastStart = {},
},
CastBarEvents = {
{"UNIT_SPELLCAST_INTERRUPTED"},
{"UNIT_SPELLCAST_DELAYED"},
{"UNIT_SPELLCAST_CHANNEL_START"},
{"UNIT_SPELLCAST_CHANNEL_UPDATE"},
{"UNIT_SPELLCAST_CHANNEL_STOP"},
{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_EMPOWER_START"},
{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_EMPOWER_UPDATE"},
{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_EMPOWER_STOP"},
{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_INTERRUPTIBLE"},
{(IS_WOW_PROJECT_MAINLINE) and "UNIT_SPELLCAST_NOT_INTERRUPTIBLE"},
{"PLAYER_ENTERING_WORLD"},
{"UNIT_SPELLCAST_START", true},
{"UNIT_SPELLCAST_STOP", true},
{"UNIT_SPELLCAST_FAILED", true},
},
Settings = {
NoFadeEffects = false, --if true it won't play fade effects when a cast if finished
ShowTradeSkills = false, --if true, it shows cast for trade skills, e.g. creating an icon with blacksmith
ShowShield = true, --if true, shows the shield above the spell icon for non interruptible casts
CanTick = true, --if true it will run its OnTick function every tick.
ShowCastTime = true, --if true, show the remaining time to finish the cast, lazy tick must be enabled
FadeInTime = 0.1, --amount of time in seconds to go from zero to 100% alpha when starting to cast
FadeOutTime = 0.5, --amount of time in seconds to go from 100% to zero alpha when the cast finishes
CanLazyTick = true, --if true, it'll execute the lazy tick function, it ticks in a much slower pace comparece with the regular tick
LazyUpdateCooldown = 0.2, --amount of time to wait for the next lazy update, this updates non critical things like the cast timer
ShowEmpoweredDuration = true, --full hold time for empowered spells
FillOnInterrupt = true,
HideSparkOnInterrupt = true,
--default size
Width = 100,
Height = 20,
--colour the castbar statusbar by the type of the cast
Colors = {
Casting = detailsFramework:CreateColorTable (1, 0.73, .1, 1),
Channeling = detailsFramework:CreateColorTable (1, 0.73, .1, 1),
Finished = detailsFramework:CreateColorTable (0, 1, 0, 1),
NonInterruptible = detailsFramework:CreateColorTable (.7, .7, .7, 1),
Failed = detailsFramework:CreateColorTable (.4, .4, .4, 1),
Interrupted = detailsFramework:CreateColorTable (.965, .754, .154, 1),
},
--appearance
BackgroundColor = detailsFramework:CreateColorTable (.2, .2, .2, .8),
Texture = [[Interface\TargetingFrame\UI-StatusBar]],
BorderShieldWidth = 10,
BorderShieldHeight = 12,
BorderShieldCoords = {0.26171875, 0.31640625, 0.53125, 0.65625},
BorderShieldTexture = 1300837,
SpellIconWidth = 10,
SpellIconHeight = 10,
ShieldIndicatorTexture = [[Interface\RaidFrame\Shield-Fill]],
ShieldGlowTexture = [[Interface\RaidFrame\Shield-Overshield]],
SparkTexture = [[Interface\CastingBar\UI-CastingBar-Spark]],
SparkWidth = 16,
SparkHeight = 16,
SparkOffset = 0,
},
Initialize = function(self)
self.unit = "unutilized unit"
self.lazyUpdateCooldown = self.Settings.LazyUpdateCooldown
self.Colors = self.Settings.Colors
self:SetUnit(nil)
PixelUtil.SetWidth (self, self.Settings.Width)
PixelUtil.SetHeight(self, self.Settings.Height)
self.background:SetColorTexture(self.Settings.BackgroundColor:GetColor())
self.background:SetAllPoints()
self.extraBackground:SetColorTexture(0, 0, 0, 1)
self.extraBackground:SetVertexColor(self.Settings.BackgroundColor:GetColor())
self.extraBackground:SetAllPoints()
self:SetTexture(self.Settings.Texture)
self.BorderShield:SetPoint("center", self, "left", 0, 0)
self.BorderShield:SetTexture(self.Settings.BorderShieldTexture)
self.BorderShield:SetTexCoord(unpack(self.Settings.BorderShieldCoords))
self.BorderShield:SetSize(self.Settings.BorderShieldWidth, self.Settings.BorderShieldHeight)
self.Icon:SetPoint("center", self, "left", 2, 0)
self.Icon:SetSize(self.Settings.SpellIconWidth, self.Settings.SpellIconHeight)
self.Spark:SetTexture(self.Settings.SparkTexture)
self.Spark:SetSize(self.Settings.SparkWidth, self.Settings.SparkHeight)
self.percentText:SetPoint("right", self, "right", -2, 0)
self.percentText:SetJustifyH("right")
self.fadeOutAnimation.alpha1:SetDuration(self.Settings.FadeOutTime)
self.fadeInAnimation.alpha1:SetDuration(self.Settings.FadeInTime)
end,
SetDefaultColor = function(self, colorType, r, g, b, a)
assert(type(colorType) == "string", "DetailsFramework: CastBar:SetDefaultColor require a string in the first argument.")
self.Colors[colorType]:SetColor(r, g, b, a)
end,
--this get a color suggestion based on the type of cast being shown in the cast bar
GetCastColor = function(self)
if (not self.canInterrupt) then
return self.Colors.NonInterruptible
elseif (self.channeling) then
return self.Colors.Channeling
elseif (self.failed) then
return self.Colors.Failed
elseif (self.interrupted) then
return self.Colors.Interrupted
elseif (self.finished) then
return self.Colors.Finished
else
return self.Colors.Casting
end
end,
--update all colors of the cast bar
UpdateCastColor = function(self)
local castColor = self:GetCastColor()
self:SetColor(castColor) --SetColor handles with ParseColors()
end,
--initial checks to know if this is a valid cast and should show the cast bar, if this fails the cast bar won't show
IsValid = function(self, unit, castName, isTradeSkill, ignoreVisibility)
if (not ignoreVisibility and not self:IsShown()) then
return false
end
if (not self.Settings.ShowTradeSkills) then
if (isTradeSkill) then
return false
end
end
if (not castName) then
return false
end
return true
end,
--handle the interrupt state of the cast
--this does not change the cast bar color because this function is called inside the start cast where is already handles the cast color
UpdateInterruptState = function(self)
if (self.Settings.ShowShield and not self.canInterrupt) then
self.BorderShield:Show()
else
self.BorderShield:Hide()
end
end,
--this check if the cast did reach 100% in the statusbar, mostly called from OnTick
CheckCastIsDone = function(self, event, isFinished)
--check max value
if (not isFinished and not self.finished) then
if (self.casting) then
if (self.value >= self.maxValue) then
isFinished = true
end
elseif (self.channeling) then
if (self.value > self.maxValue or self.value <= 0) then
isFinished = true
end
end
--check if passed an event (not begin used at the moment)
if (event) then
if (event == UNIT_SPELLCAST_STOP or event == UNIT_SPELLCAST_CHANNEL_STOP) then
isFinished = true
end
end
end
--the cast is finished
if (isFinished) then
if (self.casting) then
self.UNIT_SPELLCAST_STOP(self, self.unit, self.unit, self.castID, self.spellID)
elseif (self.channeling) then
self.UNIT_SPELLCAST_CHANNEL_STOP(self, self.unit, self.unit, self.castID, self.spellID)
end
return true
end
end,
--setup the castbar to be used by another unit
SetUnit = function(self, unit, displayedUnit)
if (self.unit ~= unit or self.displayedUnit ~= displayedUnit or unit == nil) then
self.unit = unit
self.displayedUnit = displayedUnit or unit
--reset the cast bar
self.casting = nil
self.channeling = nil
self.caninterrupt = nil
--register events
if (unit) then
for _, eventTable in ipairs(self.CastBarEvents) do
local event = eventTable[1]
local isUnitEvent = eventTable[2]
if event then
if (isUnitEvent) then
self:RegisterUnitEvent(event, unit)
else
self:RegisterEvent(event)
end
end
end
--set scripts
self:SetScript("OnEvent", self.OnEvent)
self:SetScript("OnShow", self.OnShow)
self:SetScript("OnHide", self.OnHide)
if (self.Settings.CanTick) then
self:SetScript("OnUpdate", self.OnTick)
end
--check is can show the cast time text
if (self.Settings.ShowCastTime and self.Settings.CanLazyTick) then
self.percentText:Show()
else
self.percentText:Hide()
end
--setup animtions
self:CancelScheduleToHide()
--self:PLAYER_ENTERING_WORLD (unit, unit)
self:OnEvent("PLAYER_ENTERING_WORLD", unit, unit)
else
for _, eventTable in ipairs(self.CastBarEvents) do
local event = eventTable[1]
if event then
self:UnregisterEvent(event)
end
end
--register main events
self:SetScript("OnUpdate", nil)
self:SetScript("OnEvent", nil)
self:SetScript("OnShow", nil)
self:SetScript("OnHide", nil)
self:Hide()
end
end
end,
--executed after a scheduled to hide timer is done
DoScheduledHide = function(timerObject)
timerObject.castBar.scheduledHideTime = nil
--just to make sure it isn't casting
if (not timerObject.castBar.casting and not timerObject.castBar.channeling) then
if (not timerObject.castBar.Settings.NoFadeEffects) then
timerObject.castBar:Animation_FadeOut()
else
timerObject.castBar:Hide()
end
end
end,
HasScheduledHide = function(self)
return self.scheduledHideTime and not self.scheduledHideTime:IsCancelled()
end,
CancelScheduleToHide = function(self)
if (self:HasScheduledHide()) then
self.scheduledHideTime:Cancel()
end
end,
--after an interrupt, do not immediately hide the cast bar, let it up for short amount of time to give feedback to the player
ScheduleToHide = function(self, delay)
if (not delay) then
if (self.scheduledHideTime and not self.scheduledHideTime:IsCancelled()) then
self.scheduledHideTime:Cancel()
end
self.scheduledHideTime = nil
return
end
--already have a scheduled timer?
if (self.scheduledHideTime and not self.scheduledHideTime:IsCancelled()) then
self.scheduledHideTime:Cancel()
end
self.scheduledHideTime = C_Timer.NewTimer(delay, self.DoScheduledHide)
self.scheduledHideTime.castBar = self
end,
OnHide = function(self)
--just in case some other effects made it have a different alpha since SetUnit won't load if the unit is the same.
self:SetAlpha(1)
--cancel any timer to hide scheduled
self:CancelScheduleToHide()
end,
--just update the current value if a spell is being cast since it wasn't running its tick function during the hide state
--everything else should be in the correct state
OnShow = function(self)
self.flashTexture:Hide()
if (self.unit) then
if (self.casting) then
local name, text, texture, startTime = CastInfo.UnitCastingInfo(self.unit)
if (name) then
--[[if not self.spellStartTime then
self:UpdateCastingInfo(self.unit)
end]]--
self.value = GetTime() - self.spellStartTime
end
self:RunHooksForWidget("OnShow", self, self.unit)
elseif (self.channeling) then
local name, text, texture, endTime = CastInfo.UnitChannelInfo(self.unit)
if (name) then
--[[if not self.spellEndTime then
self:UpdateChannelInfo(self.unit)
end]]--
self.value = self.empowered and (GetTime() - self.spellStartTime) or (self.spellEndTime - GetTime())
end
self:RunHooksForWidget("OnShow", self, self.unit)
end
end
end,
--it's triggering several events since it's not registered for the unit with RegisterUnitEvent
OnEvent = function(self, event, ...)
local arg1 = ...
local unit = self.unit
if (event == "PLAYER_ENTERING_WORLD") then
local newEvent = self.PLAYER_ENTERING_WORLD (self, unit, ...)
if (newEvent) then
self.OnEvent (self, newEvent, unit)
return
end
elseif (arg1 ~= unit) then
return
end
local eventFunc = self [event]
if (eventFunc) then
eventFunc (self, unit, ...)
end
end,
OnTick_LazyTick = function(self)
--run the lazy tick if allowed
if (self.Settings.CanLazyTick) then
--update the cast time
if (self.Settings.ShowCastTime) then
if (self.casting) then
self.percentText:SetText(format("%.1f", abs(self.value - self.maxValue)))
elseif (self.channeling) then
local remainingTime = self.empowered and abs(self.value - self.maxValue) or abs(self.value)
if (remainingTime > 999) then
self.percentText:SetText("")
else
self.percentText:SetText(format("%.1f", remainingTime))
end
else
self.percentText:SetText("")
end
end
return true
else
return false
end
end,
--tick function for regular casts
OnTick_Casting = function(self, deltaTime)
self.value = self.value + deltaTime
if (self:CheckCastIsDone()) then
return
else
self:SetValue(self.value)
end
--update spark position
local sparkPosition = self.value / self.maxValue * self:GetWidth()
self.Spark:SetPoint("center", self, "left", sparkPosition + self.Settings.SparkOffset, 0)
--in order to allow the lazy tick run, it must return true, it tell that the cast didn't finished
return true
end,
--tick function for channeling casts
OnTick_Channeling = function(self, deltaTime)
self.value = self.empowered and self.value + deltaTime or self.value - deltaTime
if (self:CheckCastIsDone()) then
return
else
self:SetValue(self.value)
end
--update spark position
local sparkPosition = self.value / self.maxValue * self:GetWidth()
self.Spark:SetPoint("center", self, "left", sparkPosition + self.Settings.SparkOffset, 0)
self:CreateOrUpdateEmpoweredPips()
return true
end,
OnTick = function(self, deltaTime)
if (self.casting) then
if (not self:OnTick_Casting(deltaTime)) then
return
end
--lazy tick
self.lazyUpdateCooldown = self.lazyUpdateCooldown - deltaTime
if (self.lazyUpdateCooldown < 0) then
self:OnTick_LazyTick()
self.lazyUpdateCooldown = self.Settings.LazyUpdateCooldown
end
elseif (self.channeling) then
if (not self:OnTick_Channeling(deltaTime)) then
return
end
--lazy tick
self.lazyUpdateCooldown = self.lazyUpdateCooldown - deltaTime
if (self.lazyUpdateCooldown < 0) then
self:OnTick_LazyTick()
self.lazyUpdateCooldown = self.Settings.LazyUpdateCooldown
end
end
end,
--animation start script
Animation_FadeOutStarted = function(self)
end,
--animation finished script
Animation_FadeOutFinished = function(self)
local castBar = self:GetParent()
castBar:SetAlpha(1)
castBar:Hide()
end,
--animation start script
Animation_FadeInStarted = function(self)
end,
--animation finished script
Animation_FadeInFinished = function(self)
local castBar = self:GetParent()
castBar:Show()
castBar:SetAlpha(1)
end,
--animation calls
Animation_FadeOut = function(self)
self:ScheduleToHide(false)
if (self.fadeInAnimation:IsPlaying()) then
self.fadeInAnimation:Stop()
end
if (not self.fadeOutAnimation:IsPlaying()) then
self.fadeOutAnimation:Play()
end
end,
Animation_FadeIn = function(self)
self:ScheduleToHide (false)
if (self.fadeOutAnimation:IsPlaying()) then
self.fadeOutAnimation:Stop()
end
if (not self.fadeInAnimation:IsPlaying()) then
self.fadeInAnimation:Play()
end
end,
Animation_Flash = function(self)
if (not self.flashAnimation:IsPlaying()) then
self.flashAnimation:Play()
end
end,
Animation_StopAllAnimations = function(self)
if (self.flashAnimation:IsPlaying()) then
self.flashAnimation:Stop()
end
if (self.fadeOutAnimation:IsPlaying()) then
self.fadeOutAnimation:Stop()
end
if (self.fadeInAnimation:IsPlaying()) then
self.fadeInAnimation:Stop()
end
end,
PLAYER_ENTERING_WORLD = function(self, unit, arg1)
local isChannel = CastInfo.UnitChannelInfo(unit)
local isRegularCast = CastInfo.UnitCastingInfo(unit)
if (isChannel) then
self.channeling = true
self:UpdateChannelInfo(unit)
return self.unit == arg1 and "UNIT_SPELLCAST_CHANNEL_START"
elseif (isRegularCast) then
self.casting = true
self:UpdateCastingInfo(unit)
return self.unit == arg1 and "UNIT_SPELLCAST_START"
else
self.casting = nil
self.channeling = nil
self.failed = nil
self.finished = nil
self.interrupted = nil
self.Spark:Hide()
self:Hide()
end
end,
UpdateCastingInfo = function(self, unit, ...)
local unitID, castID, spellID = ...
local name, text, texture, startTime, endTime, isTradeSkill, uciCastID, notInterruptible, uciSpellID = CastInfo.UnitCastingInfo(unit)
spellID = uciSpellID or spellID
castID = uciCastID or castID
if spellID and (not name or not texture or not text) then
local siName, _, siIcon, siCastTime = GetSpellInfo(spellID)
texture = texture or siIcon
name = name or siName
text = text or siName
if not startTime then
startTime = GetTime()
endTime = startTime + siCastTime
end
end
--is valid?
if (not self:IsValid(unit, name, isTradeSkill, true)) then
return
end
--empowered? no!
self.holdAtMaxTime = nil
self.empowered = false
self.curStage = nil
self.numStages = nil
self.empStages = nil
self:CreateOrUpdateEmpoweredPips()
--setup cast
self.casting = true
self.channeling = nil
self.interrupted = nil
self.failed = nil
self.finished = nil
self.canInterrupt = not notInterruptible
self.spellID = spellID
self.castID = castID
self.spellName = name
self.spellTexture = texture
self.spellStartTime = startTime / 1000
self.spellEndTime = endTime / 1000
self.value = GetTime() - self.spellStartTime
self.maxValue = self.spellEndTime - self.spellStartTime
self:SetMinMaxValues(0, self.maxValue)
self:SetValue(self.value)
self:SetAlpha(1)
self.Icon:SetTexture(texture)
self.Icon:Show()
self.Text:SetText(text or name)
if (self.Settings.ShowCastTime and self.Settings.CanLazyTick) then
self.percentText:Show()
end
self.flashTexture:Hide()
self:Animation_StopAllAnimations()
self:SetAlpha(1)
--set the statusbar color
self:UpdateCastColor()
if (not self:IsShown() and not self.Settings.NoFadeEffects) then
self:Animation_FadeIn()
end
self.Spark:Show()
self:Show()
--update the interrupt cast border
self:UpdateInterruptState()
end,
UNIT_SPELLCAST_START = function(self, unit, ...)
self:UpdateCastingInfo(unit, ...)
self:RunHooksForWidget("OnCastStart", self, self.unit, "UNIT_SPELLCAST_START")
end,
CreateOrUpdateEmpoweredPips = function(self, unit, numStages, startTime, endTime)
unit = unit or self.unit
numStages = numStages or self.numStages
startTime = startTime or ((self.spellStartTime or 0) * 1000)
endTime = endTime or ((self.spellEndTime or 0) * 1000)
if not self.empStages or not numStages or numStages <= 0 then
self.stagePips = self.stagePips or {}
for i, stagePip in pairs(self.stagePips) do
stagePip:Hide()
end
return
end
local width = self:GetWidth()
local height = self:GetHeight()
for i = 1, numStages, 1 do
local curStartTime = self.empStages[i] and self.empStages[i].start
local curEndTime = self.empStages[i] and self.empStages[i].finish
local curDuration = curEndTime - curStartTime
local offset = width * curEndTime / (endTime - startTime) * 1000
if curDuration > -1 then
local stagePip = self.stagePips[i]
if not stagePip then
stagePip = self:CreateTexture(nil, "overlay", nil, 2)
stagePip:SetBlendMode("ADD")
stagePip:SetTexture([[Interface\CastingBar\UI-CastingBar-Spark]])
stagePip:SetTexCoord(11/32,18/32,9/32,23/32)
stagePip:SetSize(2, height)
--stagePip = CreateFrame("FRAME", nil, self, "CastingBarFrameStagePipTemplate")
self.stagePips[i] = stagePip
end
stagePip:ClearAllPoints()
--stagePip:SetPoint("TOP", self, "TOPLEFT", offset, -1)
--stagePip:SetPoint("BOTTOM", self, "BOTTOMLEFT", offset, 1)
--stagePip.BasePip:SetVertexColor(1, 1, 1, 1)
stagePip:SetPoint("CENTER", self, "LEFT", offset, 0)
stagePip:SetVertexColor(1, 1, 1, 1)
stagePip:Show()
end
end
end,
UpdateChannelInfo = function(self, unit, ...)
local unitID, castID, spellID = ...
local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, uciSpellID, _, numStages = CastInfo.UnitChannelInfo (unit)
spellID = uciSpellID or spellID
castID = uciCastID or castID
if spellID and (not name or not texture or not text) then
local siName, _, siIcon, siCastTime = GetSpellInfo(spellID)
texture = texture or siIcon
name = name or siName
text = text or siName
if not startTime then
startTime = GetTime()
endTime = startTime + siCastTime
end
end
--is valid?
if (not self:IsValid (unit, name, isTradeSkill, true)) then
return
end
--empowered?
self.empStages = {}
self.stagePips = self.stagePips or {}
for i, stagePip in pairs(self.stagePips) do
stagePip:Hide()
end
if numStages and numStages > 0 then
self.holdAtMaxTime = GetUnitEmpowerHoldAtMaxTime(self.unit)
self.empowered = true
self.numStages = numStages
local lastStageEndTime = 0
for i = 1, numStages do
self.empStages[i] = {
start = lastStageEndTime,
finish = lastStageEndTime + GetUnitEmpowerStageDuration(unit, i - 1) / 1000,
}
lastStageEndTime = self.empStages[i].finish
if startTime / 1000 + lastStageEndTime <= GetTime() then
self.curStage = i
end
end
if (self.Settings.ShowEmpoweredDuration) then
endTime = endTime + self.holdAtMaxTime
end
--create/update pips
self:CreateOrUpdateEmpoweredPips(unit, numStages, startTime, endTime)
else
self.holdAtMaxTime = nil
self.empowered = false
self.curStage = nil
self.numStages = nil
end
--setup cast
self.casting = nil
self.channeling = true
self.interrupted = nil
self.failed = nil
self.finished = nil
self.canInterrupt = not notInterruptible
self.spellID = spellID
self.castID = castID
self.spellName = name
self.spellTexture = texture
self.spellStartTime = startTime / 1000
self.spellEndTime = endTime / 1000
self.value = self.empowered and (GetTime() - self.spellStartTime) or (self.spellEndTime - GetTime())
self.maxValue = self.spellEndTime - self.spellStartTime
self.reverseChanneling = self.empowered
self:SetMinMaxValues(0, self.maxValue)
self:SetValue(self.value)
self:SetAlpha(1)
self.Icon:SetTexture(texture)
self.Icon:Show()
self.Text:SetText(text)
if (self.Settings.ShowCastTime and self.Settings.CanLazyTick) then
self.percentText:Show()
end
self.flashTexture:Hide()
self:Animation_StopAllAnimations()
self:SetAlpha(1)
--set the statusbar color
self:UpdateCastColor()
if (not self:IsShown() and not self.Settings.NoFadeEffects) then
self:Animation_FadeIn()
end
self.Spark:Show()
self:Show()
--update the interrupt cast border
self:UpdateInterruptState()
end,
UNIT_SPELLCAST_CHANNEL_START = function(self, unit, ...)
self:UpdateChannelInfo(unit, ...)
self:RunHooksForWidget("OnCastStart", self, self.unit, "UNIT_SPELLCAST_CHANNEL_START")
end,
UNIT_SPELLCAST_STOP = function(self, unit, ...)
local unitID, castID, spellID = ...
if (self.castID == castID) then
if (self.interrupted) then
if (self.Settings.HideSparkOnInterrupt) then
self.Spark:Hide()
end
else
self.Spark:Hide()
end
self.percentText:Hide()
local value = self:GetValue()
local _, maxValue = self:GetMinMaxValues()
if (self.interrupted) then
if (self.Settings.FillOnInterrupt) then
self:SetValue(self.maxValue or maxValue or 1)
end
else
self:SetValue(self.maxValue or maxValue or 1)
end
self.casting = nil
self.channeling = nil
self.finished = true
self.castID = nil
if (not self:HasScheduledHide()) then
--check if settings has no fade option or if its parents are not visible
if (not self:IsVisible()) then
self:Hide()
elseif (self.Settings.NoFadeEffects) then
self:ScheduleToHide (0.3)
else
self:Animation_Flash()
self:Animation_FadeOut()
end
end
self:UpdateCastColor()
end
end,
UNIT_SPELLCAST_CHANNEL_STOP = function(self, unit, ...)
local unitID, castID, spellID = ...
if (self.channeling and castID == self.castID) then
self.Spark:Hide()
self.percentText:Hide()
local value = self:GetValue()
local _, maxValue = self:GetMinMaxValues()
self:SetValue(self.maxValue or maxValue or 1)
self.casting = nil
self.channeling = nil
self.finished = true
self.castID = nil
if (not self:HasScheduledHide()) then
--check if settings has no fade option or if its parents are not visible
if (not self:IsVisible()) then
self:Hide()
elseif (self.Settings.NoFadeEffects) then
self:ScheduleToHide (0.3)
else
self:Animation_Flash()
self:Animation_FadeOut()
end
end
self:UpdateCastColor()
end
end,
UNIT_SPELLCAST_EMPOWER_START = function(self, unit, ...)
self:UNIT_SPELLCAST_CHANNEL_START(unit, ...)
end,
UNIT_SPELLCAST_EMPOWER_UPDATE = function(self, unit, ...)
self:UNIT_SPELLCAST_CHANNEL_UPDATE(unit, ...)
end,
UNIT_SPELLCAST_EMPOWER_STOP = function(self, unit, ...)
self:UNIT_SPELLCAST_CHANNEL_STOP(unit, ...)
end,
UNIT_SPELLCAST_FAILED = function(self, unit, ...)
local unitID, castID, spellID = ...
if ((self.casting or self.channeling) and castID == self.castID and not self.fadeOut) then
self.casting = nil
self.channeling = nil
self.failed = true
self.finished = true
self.castID = nil
self:SetValue(self.maxValue or select(2, self:GetMinMaxValues()) or 1)
--set the statusbar color
self:UpdateCastColor()
self.Spark:Hide()
self.percentText:Hide()
self.Text:SetText(FAILED) --auto locale within the global namespace
self:ScheduleToHide (1)
end
end,
UNIT_SPELLCAST_INTERRUPTED = function(self, unit, ...)
local unitID, castID, spellID = ...
if ((self.casting or self.channeling) and castID == self.castID and not self.fadeOut) then
self.casting = nil
self.channeling = nil
self.interrupted = true
self.finished = true
self.castID = nil
if (self.Settings.FillOnInterrupt) then
self:SetValue(self.maxValue or select(2, self:GetMinMaxValues()) or 1)
end
if (self.Settings.HideSparkOnInterrupt) then
self.Spark:Hide()
end
local castColor = self:GetCastColor()
self:SetColor(castColor) --SetColor handles with ParseColors()
self.percentText:Hide()
self.Text:SetText(INTERRUPTED) --auto locale within the global namespace
self:ScheduleToHide(1)
end
end,
UNIT_SPELLCAST_DELAYED = function(self, unit, ...)
local name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = CastInfo.UnitCastingInfo (unit)
if (not self:IsValid (unit, name, isTradeSkill)) then
return
end
--update the cast time
self.spellStartTime = startTime / 1000
self.spellEndTime = endTime / 1000
self.value = GetTime() - self.spellStartTime
self.maxValue = self.spellEndTime - self.spellStartTime
self:SetMinMaxValues(0, self.maxValue)
end,
UNIT_SPELLCAST_CHANNEL_UPDATE = function(self, unit, ...)
local name, text, texture, startTime, endTime, isTradeSkill, notInterruptible, spellID, _, numStages = CastInfo.UnitChannelInfo (unit)
if (not self:IsValid(unit, name, isTradeSkill)) then
return
end
--update the cast time
self.spellStartTime = startTime / 1000
self.spellEndTime = endTime / 1000
self.value = self.empowered and (GetTime() - self.spellStartTime) or (self.spellEndTime - GetTime())
self.maxValue = self.spellEndTime - self.spellStartTime
if (self.value < 0 or self.value > self.maxValue) then
self.value = 0
end
self:SetMinMaxValues(0, self.maxValue)
self:SetValue(self.value)
end,
--cast changed its state to interruptable
UNIT_SPELLCAST_INTERRUPTIBLE = function(self, unit, ...)
self.canInterrupt = true
self:UpdateCastColor()
self:UpdateInterruptState()
end,
--cast changed its state to non interruptable
UNIT_SPELLCAST_NOT_INTERRUPTIBLE = function(self, unit, ...)
self.canInterrupt = false
self:UpdateCastColor()
self:UpdateInterruptState()
end,
}
detailsFramework:Mixin(detailsFramework.CastFrameFunctions, detailsFramework.ScriptHookMixin)
-- ~castbar
---create a castbar widget
---@param parent frame
---@param name string?
---@param settingsOverride table? a table with key/value pairs to override the default settings
---@return df_castbar
function detailsFramework:CreateCastBar(parent, name, settingsOverride)
assert(name or parent:GetName(), "DetailsFramework:CreateCastBar parameter 'name' omitted and parent has no name.")
local castBar = CreateFrame("StatusBar", name or (parent:GetName() .. "CastBar"), parent, "BackdropTemplate")
do --layers
--these widgets was been made with back compatibility in mind
--they are using the same names as the retail game uses on the nameplate castbar
--this should make Plater core and Plater scripts made by users compatible with the new unit frame made on the framework
--background
castBar.background = castBar:CreateTexture(nil, "background", nil, -6)
castBar.extraBackground = castBar:CreateTexture(nil, "background", nil, -5)
--overlay
castBar.Text = castBar:CreateFontString(nil, "overlay", "SystemFont_Shadow_Small")
castBar.Text:SetDrawLayer("overlay", 1)
castBar.Text:SetPoint("center", 0, 0)
castBar.BorderShield = castBar:CreateTexture(nil, "overlay", nil, 5)
castBar.BorderShield:Hide()
castBar.Icon = castBar:CreateTexture(nil, "overlay", nil, 4)
castBar.Icon:Hide()
castBar.Spark = castBar:CreateTexture(nil, "overlay", nil, 3)
castBar.Spark:SetBlendMode("ADD")
--time left on the cast
castBar.percentText = castBar:CreateFontString(nil, "overlay", "SystemFont_Shadow_Small")
castBar.percentText:SetDrawLayer("overlay", 7)
--statusbar texture
castBar.barTexture = castBar:CreateTexture(nil, "artwork", nil, -6)
castBar:SetStatusBarTexture(castBar.barTexture)
--animations fade in and out
local fadeOutAnimationHub = detailsFramework:CreateAnimationHub(castBar, detailsFramework.CastFrameFunctions.Animation_FadeOutStarted, detailsFramework.CastFrameFunctions.Animation_FadeOutFinished)
fadeOutAnimationHub.alpha1 = detailsFramework:CreateAnimation(fadeOutAnimationHub, "ALPHA", 1, 1, 1, 0)
castBar.fadeOutAnimation = fadeOutAnimationHub
local fadeInAnimationHub = detailsFramework:CreateAnimationHub(castBar, detailsFramework.CastFrameFunctions.Animation_FadeInStarted, detailsFramework.CastFrameFunctions.Animation_FadeInFinished)
fadeInAnimationHub.alpha1 = detailsFramework:CreateAnimation(fadeInAnimationHub, "ALPHA", 1, 0.150, 0, 1)
castBar.fadeInAnimation = fadeInAnimationHub
--animatios flash
local flashTexture = castBar:CreateTexture(nil, "overlay", nil, 7)
flashTexture:SetColorTexture(1, 1, 1, 1)
flashTexture:SetAllPoints()
flashTexture:SetAlpha(0)
flashTexture:Hide()
flashTexture:SetBlendMode("ADD")
castBar.flashTexture = flashTexture
local flashAnimationHub = detailsFramework:CreateAnimationHub(flashTexture, function() flashTexture:Show() end, function() flashTexture:Hide() end)
detailsFramework:CreateAnimation(flashAnimationHub, "ALPHA", 1, 0.2, 0, 0.8)
detailsFramework:CreateAnimation(flashAnimationHub, "ALPHA", 2, 0.2, 1, 0)
castBar.flashAnimation = flashAnimationHub
end
--mixins
detailsFramework:Mixin(castBar, detailsFramework.CastFrameFunctions)
detailsFramework:Mixin(castBar, detailsFramework.StatusBarFunctions)
castBar:CreateTextureMask()
castBar:AddMaskTexture(castBar.flashTexture)
castBar:AddMaskTexture(castBar.background)
castBar:AddMaskTexture(castBar.extraBackground)
castBar:SetTexture([[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]])
--settings and hooks
local settings = detailsFramework.table.copy({}, detailsFramework.CastFrameFunctions.Settings)
if (settingsOverride) then
detailsFramework.table.copy(settings, settingsOverride)
end
castBar.Settings = settings
local hookList = detailsFramework.table.copy({}, detailsFramework.CastFrameFunctions.HookList)
castBar.HookList = hookList
--initialize the cast bar
castBar:Initialize()
return castBar
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--unit frame
--[=[
DF:CreateUnitFrame(parent, name, settingsOverride)
creates a very basic unit frame with a healthbar, castbar and power bar
each unit frame has a .Settings table which isn't shared among other unit frames created with this method
all members names are the same as the unit frame from the retail game
@parent = frame to pass for the CreateFrame function
@name = absolute name of the frame, if omitted a random name is created
@settingsOverride = table with keys and values to replace the defaults from the framework
--]=]
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--unit frame
--return true if the unit has been claimed by another player (health bar is gray)
local isUnitTapDenied = function(unit)
return unit and not UnitPlayerControlled(unit) and UnitIsTapDenied(unit)
end
---@class df_unitframesettings : table
---@field ClearUnitOnHide boolean true
---@field ShowCastBar boolean true
---@field ShowPowerBar boolean true
---@field ShowUnitName boolean true
---@field ShowBorder boolean true
---@field CanModifyHealhBarColor boolean true
---@field ColorByAggro boolean false
---@field FixedHealthColor boolean false
---@field UseFriendlyClassColor boolean true
---@field UseEnemyClassColor boolean true
---@field ShowTargetOverlay boolean true
---@field BorderColor table
---@field CanTick boolean
---@field Width number
---@field Height number
---@field PowerBarHeight number
---@field CastBarHeight number
---@class df_unitframemixin
---@field WidgetType string
---@field Settings df_unitframesettings
---@field SetHealthBarColor fun(self:df_unitframe, r:number, g:number?, b:number?, a:number?)
---@field SetUnit fun(self:df_unitframe, unit:string?) sets the unit to be shown in the unit frame
---@field OnTick fun(self:df_unitframe, deltaTime:number?) if CanTick is true, this function will be called every frame
detailsFramework.UnitFrameFunctions = {
WidgetType = "unitFrame",
Settings = {
--unit frames
ClearUnitOnHide = true, --if tue it'll set the unit to nil when the unit frame is set to hide
ShowCastBar = true, --if this is false, the cast bar for the unit won't be shown
ShowPowerBar = true, --if true it'll show the power bar for the unit, e.g. the mana bar
ShowUnitName = true, --if false, the unit name won't show
ShowBorder = true, --if false won't show the border frame
--health bar color
CanModifyHealhBarColor = true, --if false it won't change the color of the health bar
ColorByAggro = false, --if true it'll color the healthbar with red color when the unit has aggro on player
FixedHealthColor = false, --color override with a table {r=1, g=1, b=1}
UseFriendlyClassColor = true, --make the healthbar class color for friendly players
UseEnemyClassColor = true, --make the healthbar class color for enemy players
--misc
ShowTargetOverlay = true, --shows a highlighht for the player current target
BorderColor = detailsFramework:CreateColorTable(0, 0, 0, 1), --border color, set to alpha zero for no border
CanTick = false, --if true it'll run the OnTick event
--size
Width = 100,
Height = 20,
PowerBarHeight = 4,
CastBarHeight = 8,
},
UnitFrameEvents = {
--run for all units
{"PLAYER_ENTERING_WORLD"},
{"PARTY_MEMBER_DISABLE"},
{"PARTY_MEMBER_ENABLE"},
{"PLAYER_TARGET_CHANGED"},
--run for one unit
{"UNIT_NAME_UPDATE", true},
{"UNIT_CONNECTION", true},
{"UNIT_ENTERED_VEHICLE", true},
{"UNIT_EXITED_VEHICLE", true},
{"UNIT_PET", true},
{"UNIT_THREAT_LIST_UPDATE", true},
},
--used when a event is triggered to quickly check if is a unit event
IsUnitEvent = {
["UNIT_NAME_UPDATE"] = true,
["UNIT_CONNECTION"] = true,
["UNIT_ENTERED_VEHICLE"] = true,
["UNIT_EXITED_VEHICLE"] = true,
["UNIT_PET"] = true,
["UNIT_THREAT_LIST_UPDATE"] = true,
},
Initialize = function(self)
self.border:SetBorderColor(self.Settings.BorderColor)
PixelUtil.SetWidth(self, self.Settings.Width, 1)
PixelUtil.SetHeight(self, self.Settings.Height, 1)
PixelUtil.SetPoint(self.powerBar, "bottomleft", self, "bottomleft", 0, 0, 1, 1)
PixelUtil.SetPoint(self.powerBar, "bottomright", self, "bottomright", 0, 0, 1, 1)
PixelUtil.SetHeight(self.powerBar, self.Settings.PowerBarHeight, 1)
--make the castbar overlap the powerbar
PixelUtil.SetPoint(self.castBar, "bottomleft", self, "bottomleft", 0, 0, 1, 1)
PixelUtil.SetPoint(self.castBar, "bottomright", self, "bottomright", 0, 0, 1, 1)
PixelUtil.SetHeight(self.castBar, self.Settings.CastBarHeight, 1)
end,
SetHealthBarColor = function(self, r, g, b, a)
self.healthBar:SetColor(r, g, b, a)
end,
--register all events which will be used by the unit frame
RegisterEvents = function(self)
--register events
for index, eventTable in ipairs(self.UnitFrameEvents) do
local event, isUnitEvent = unpack(eventTable)
if (not isUnitEvent) then
self:RegisterEvent(event)
else
self:RegisterUnitEvent (event, self.unit, self.displayedUnit ~= unit and self.displayedUnit or nil)
end
end
--check settings and unregister events for disabled features
if (not self.Settings.ColorByAggro) then
self:UnregisterEvent ("UNIT_THREAT_LIST_UPDATE")
end
--set scripts
self:SetScript("OnEvent", self.OnEvent)
self:SetScript("OnHide", self.OnHide)
if (self.Settings.CanTick) then
self:SetScript("OnUpdate", self.OnTick)
end
end,
--unregister events, called when this unit frame losses its unit
UnregisterEvents = function(self)
for index, eventTable in ipairs(self.UnitFrameEvents) do
local event, firstUnit, secondUnit = unpack(eventTable)
self:UnregisterEvent(event)
end
self:SetScript("OnEvent", nil)
self:SetScript("OnUpdate", nil)
self:SetScript("OnHide", nil)
end,
--call every tick
OnTick = function(self, deltaTime) end, --if overrided, set 'CanTick' to true on the settings table
--when an event happen for this unit, send it to the apropriate function
OnEvent = function(self, event, ...)
--run the function for this event
local eventFunc = self[event]
if (eventFunc) then
--is this event an unit event?
if (self.IsUnitEvent[event]) then
local unit = ...
--check if is for this unit (even if the event is registered only for the unit)
if (unit == self.unit or unit == self.displayedUnit) then
eventFunc(self, ...)
end
else
eventFunc(self, ...)
end
end
end,
OnHide = function(self)
if (self.Settings.ClearUnitOnHide) then
self:SetUnit(nil)
end
end,
--run if the unit currently shown is different than the new one
SetUnit = function(self, unit)
if (unit ~= self.unit or unit == nil) then
self.unit = unit --absolute unit
self.displayedUnit = unit --~todo rename to 'displayedUnit' for back compatibility with older scripts in Plater
self.unitInVehicle = nil --true when the unit is in a vehicle
if (unit) then
self:RegisterEvents()
self.guid = UnitGUID(unit)
self.class = select(2, UnitClass(unit))
self.name = UnitName(unit)
self.healthBar:SetUnit(unit, self.displayedUnit)
--is using castbars?
if (self.Settings.ShowCastBar) then
self.castBar:SetUnit(unit, self.displayedUnit)
else
self.castBar:SetUnit(nil)
end
--is using powerbars?
if (self.Settings.ShowPowerBar) then
self.powerBar:SetUnit(unit, self.displayedUnit)
else
self.powerBar:SetUnit(nil)
end
--is using the border?
if (self.Settings.ShowBorder) then
self.border:Show()
else
self.border:Hide()
end
if (not self.Settings.ShowUnitName) then
self.unitName:Hide()
end
else
self:UnregisterEvents()
self.healthBar:SetUnit(nil)
self.castBar:SetUnit(nil)
self.powerBar:SetUnit(nil)
end
self:UpdateUnitFrame()
end
end,
--if the unit is controlling a vehicle, need to show the vehicle instead
--.unit and .displayedUnit is always the same execept when the unit is controlling a vehicle, then .displayedUnit is the unitID for the vehicle
--todo: see what 'UnitTargetsVehicleInRaidUI' is, there's a call for this in the CompactUnitFrame.lua but zero documentation
CheckVehiclePossession = function(self)
--this unit is possessing a vehicle?
local unitPossessVehicle = (IS_WOW_PROJECT_MAINLINE) and UnitHasVehicleUI(self.unit) or false
if (unitPossessVehicle) then
if (not self.unitInVehicle) then
if (UnitIsUnit("player", self.unit)) then
self.displayedUnit = "vehicle"
self.unitInVehicle = true
self:RegisterEvents()
self:UpdateAllWidgets()
return true
end
local prefix, id, suffix = string.match(self.unit, "([^%d]+)([%d]*)(.*)") --CompactUnitFrame.lua
local vehicleUnitID = prefix .. "pet" .. id .. suffix
if (UnitExists(vehicleUnitID)) then
self.displayedUnit = vehicleUnitID
self.unitInVehicle = true
self:RegisterEvents()
self:UpdateAllWidgets()
return true
end
end
end
if (self.unitInVehicle) then
self.displayedUnit = self.unit
self.unitInVehicle = nil
self:RegisterEvents()
self:UpdateAllWidgets()
end
end,
--find a color for the health bar, if a color has been passed in the arguments use it instead, 'CanModifyHealhBarColor' must be true for this function run
UpdateHealthColor = function(self, r, g, b)
--check if color changes is disabled
if (not self.Settings.CanModifyHealhBarColor) then
return
end
local unit = self.displayedUnit
--check if a color has been passed within the parameters
if (r) then
--check if passed a special color
if (type(r) ~= "number") then
r, g, b = detailsFramework:ParseColors(r)
end
self:SetHealthBarColor(r, g, b)
return
end
--check if there is a color override in the settings
if (self.Settings.FixedHealthColor) then
local FixedHealthColor = self.Settings.FixedHealthColor
r, g, b = FixedHealthColor.r, FixedHealthColor.g, FixedHealthColor.b
self:SetHealthBarColor(r, g, b)
return
end
--check if the unit is a player
if (UnitIsPlayer(unit)) then
--check if the unit is disconnected (in case it is a player
if (not UnitIsConnected(unit)) then
self:SetHealthBarColor(.5, .5, .5)
return
end
--is a friendly or enemy player?
if (UnitIsFriend ("player", unit)) then
if (self.Settings.UseFriendlyClassColor) then
local _, className = UnitClass(unit)
if (className) then
local classColor = RAID_CLASS_COLORS[className]
if (classColor) then
self:SetHealthBarColor(classColor.r, classColor.g, classColor.b)
return
end
end
else
self:SetHealthBarColor(0, 1, 0)
return
end
else
if (self.Settings.UseEnemyClassColor) then
local _, className = UnitClass(unit)
if (className) then
local classColor = RAID_CLASS_COLORS[className]
if (classColor) then
self:SetHealthBarColor(classColor.r, classColor.g, classColor.b)
return
end
end
else
self:SetHealthBarColor(1, 0, 0)
return
end
end
end
--is tapped?
if (isUnitTapDenied(unit)) then
self:SetHealthBarColor(.6, .6, .6)
return
end
--is this is a npc attacking the player?
if (self.Settings.ColorByAggro) then
local _, threatStatus = UnitDetailedThreatSituation("player", unit)
if (threatStatus) then
self:SetHealthBarColor(1, 0, 0)
return
end
end
-- get the regular color by selection
r, g, b = UnitSelectionColor(unit)
self:SetHealthBarColor (r, g, b)
end,
--misc
UpdateName = function(self)
if (not self.Settings.ShowUnitName) then
return
end
--unit name without realm names by default
local name = UnitName(self.unit)
self.unitName:SetText(name)
self.unitName:Show()
end,
--this runs when the player it self changes its target, need to update the current target overlay
--todo: add focus overlay
UpdateTargetOverlay = function(self)
if (not self.Settings.ShowTargetOverlay) then
self.targetOverlay:Hide()
return
end
if (UnitIsUnit(self.displayedUnit, "target")) then
self.targetOverlay:Show()
else
self.targetOverlay:Hide()
end
end,
UpdateAllWidgets = function(self)
if (UnitExists(self.displayedUnit)) then
local unit = self.unit
local displayedUnit = self.displayedUnit
self:SetUnit(unit, displayedUnit)
--is using castbars?
if (self.Settings.ShowCastBar) then
self.castBar:SetUnit(unit, displayedUnit)
end
--is using powerbars?
if (self.Settings.ShowPowerBar) then
self.powerBar:SetUnit(unit, displayedUnit)
end
self:UpdateName()
self:UpdateTargetOverlay()
self:UpdateHealthColor()
end
end,
--update the unit frame and its widgets
UpdateUnitFrame = function(self)
local unitInVehicle = self:CheckVehiclePossession()
--if the unit is inside a vehicle, the vehicle possession function will call an update on all widgets
if (not unitInVehicle) then
self:UpdateAllWidgets()
end
end,
--event handles
PLAYER_ENTERING_WORLD = function(self, ...)
self:UpdateUnitFrame()
end,
--update overlays when the player changes its target
PLAYER_TARGET_CHANGED = function(self, ...)
self:UpdateTargetOverlay()
end,
--unit received a name update
UNIT_NAME_UPDATE = function(self, ...)
self:UpdateName()
end,
--this is registered only if .settings.ColorByAggro is true
UNIT_THREAT_LIST_UPDATE = function(self, ...)
if (self.Settings.ColorByAggro) then
self:UpdateHealthColor()
end
end,
--vehicle
UNIT_ENTERED_VEHICLE = function(self, ...)
self:UpdateUnitFrame()
end,
UNIT_EXITED_VEHICLE = function(self, ...)
self:UpdateUnitFrame()
end,
--pet
UNIT_PET = function(self, ...)
self:UpdateUnitFrame()
end,
--player connection
UNIT_CONNECTION = function(self, ...)
if (UnitIsConnected (self.unit)) then
self:UpdateUnitFrame()
end
end,
PARTY_MEMBER_ENABLE = function(self, ...)
if (UnitIsConnected(self.unit)) then
self:UpdateName()
end
end,
}
---@class df_unitframe : button, df_unitframemixin
---@field unit string
---@field displayedUnit string
---@field guid guid
---@field class class
---@field name actorname
---@field unitInVehicle boolean
---@field border frame
---@field overlayFrame frame
---@field unitName fontstring
---@field healthBar df_healthbar
---@field castBar df_castbar
---@field powerBar df_powerbar
---@field targetOverlay texture
---@field Settings df_unitframesettings
local globalBaseFrameLevel = 1 -- to be increased + used across each new plate
-- ~unitframe
---create a unit frame with a health bar, cast bar and power bar
---@param parent frame
---@param name string?
---@param unitFrameSettingsOverride table?
---@param healthBarSettingsOverride table?
---@param castBarSettingsOverride table?
---@param powerBarSettingsOverride table?
---@return df_unitframe
function detailsFramework:CreateUnitFrame(parent, name, unitFrameSettingsOverride, healthBarSettingsOverride, castBarSettingsOverride, powerBarSettingsOverride)
local parentName = name or ("DetailsFrameworkUnitFrame" .. tostring(math.random(1, 100000000)))
--create the main unit frame
local mewUnitFrame = CreateFrame("button", parentName, parent, "BackdropTemplate")
--base level
--local baseFrameLevel = f:GetFrameLevel()
local baseFrameLevel = globalBaseFrameLevel
globalBaseFrameLevel = globalBaseFrameLevel + 10
mewUnitFrame:SetFrameLevel(baseFrameLevel)
--create the healthBar
local healthBar = detailsFramework:CreateHealthBar(mewUnitFrame, nil, healthBarSettingsOverride)
healthBar:SetFrameLevel(baseFrameLevel + 1)
mewUnitFrame.healthBar = healthBar
--create the power bar
local powerBar = detailsFramework:CreatePowerBar(mewUnitFrame, nil, powerBarSettingsOverride)
powerBar:SetFrameLevel(baseFrameLevel + 2)
mewUnitFrame.powerBar = powerBar
--create the castBar
local castBar = detailsFramework:CreateCastBar(mewUnitFrame, nil, castBarSettingsOverride)
castBar:SetFrameLevel(baseFrameLevel + 3)
mewUnitFrame.castBar = castBar
--border frame
local borderFrame = detailsFramework:CreateBorderFrame(mewUnitFrame, mewUnitFrame:GetName() .. "Border")
borderFrame:SetFrameLevel(mewUnitFrame:GetFrameLevel() + 5)
mewUnitFrame.border = borderFrame
--overlay frame (widgets that need to stay above the unit frame)
local overlayFrame = CreateFrame("frame", "$parentOverlayFrame", mewUnitFrame, "BackdropTemplate")
overlayFrame:SetFrameLevel(mewUnitFrame:GetFrameLevel() + 6)
mewUnitFrame.overlayFrame = overlayFrame
--unit frame layers
do
--artwork
mewUnitFrame.unitName = mewUnitFrame:CreateFontString(nil, "artwork", "GameFontHighlightSmall")
PixelUtil.SetPoint(mewUnitFrame.unitName, "topleft", healthBar, "topleft", 2, -2, 1, 1)
--target overlay - it's parented in the healthbar so other widgets won't get the overlay
mewUnitFrame.targetOverlay = overlayFrame:CreateTexture(nil, "artwork")
mewUnitFrame.targetOverlay:SetTexture(healthBar:GetTexture())
mewUnitFrame.targetOverlay:SetBlendMode("ADD")
mewUnitFrame.targetOverlay:SetAlpha(.5)
mewUnitFrame.targetOverlay:SetAllPoints(healthBar)
end
--mixins
--inject mixins
detailsFramework:Mixin(mewUnitFrame, detailsFramework.UnitFrameFunctions)
--create the settings table and copy the overrides into it, the table is set into the frame after the mixin
local unitFrameSettings = detailsFramework.table.copy({}, detailsFramework.UnitFrameFunctions.Settings)
if (unitFrameSettingsOverride) then
unitFrameSettings = detailsFramework.table.copy(unitFrameSettings, unitFrameSettingsOverride)
end
mewUnitFrame.Settings = unitFrameSettings
--initialize scripts
--unitframe
mewUnitFrame:Initialize()
return mewUnitFrame
end