1650 lines
61 KiB
Lua
1650 lines
61 KiB
Lua
SFrames.Target = {}
|
|
local _A = SFrames.ActiveTheme
|
|
|
|
local targetCLCast = nil
|
|
local spellIconCache = {}
|
|
|
|
local SPELL_ICONS = {
|
|
-- Mage
|
|
["Fireball"] = "Spell_Fire_FlameBolt",
|
|
["火球术"] = "Spell_Fire_FlameBolt",
|
|
["Frostbolt"] = "Spell_Frost_FrostBolt02",
|
|
["寒冰箭"] = "Spell_Frost_FrostBolt02",
|
|
["Polymorph"] = "Spell_Nature_Polymorph",
|
|
["变形术"] = "Spell_Nature_Polymorph",
|
|
["Arcane Missiles"] = "Spell_Nature_StarFall",
|
|
["奥术飞弹"] = "Spell_Nature_StarFall",
|
|
["Pyroblast"] = "Spell_Fire_Fireball02",
|
|
["炎爆术"] = "Spell_Fire_Fireball02",
|
|
["Scorch"] = "Spell_Fire_SoulBurn",
|
|
["灼烧"] = "Spell_Fire_SoulBurn",
|
|
["Flamestrike"] = "Spell_Fire_SelfDestruct",
|
|
["烈焰风暴"] = "Spell_Fire_SelfDestruct",
|
|
["Blizzard"] = "Spell_Frost_IceStorm",
|
|
["暴风雪"] = "Spell_Frost_IceStorm",
|
|
-- Warlock
|
|
["Shadow Bolt"] = "Spell_Shadow_ShadowBolt",
|
|
["暗影箭"] = "Spell_Shadow_ShadowBolt",
|
|
["Fear"] = "Spell_Shadow_Possession",
|
|
["恐惧术"] = "Spell_Shadow_Possession",
|
|
["Immolate"] = "Spell_Fire_Immolation",
|
|
["献祭"] = "Spell_Fire_Immolation",
|
|
["Soul Fire"] = "Spell_Fire_Fireball02",
|
|
["灵魂之火"] = "Spell_Fire_Fireball02",
|
|
["Drain Life"] = "Spell_Shadow_LifeDrain02",
|
|
["吸取生命"] = "Spell_Shadow_LifeDrain02",
|
|
["Drain Mana"] = "Spell_Shadow_SiphonMana",
|
|
["吸取法力"] = "Spell_Shadow_SiphonMana",
|
|
["Rain of Fire"] = "Spell_Shadow_RainOfFire",
|
|
["火焰之雨"] = "Spell_Shadow_RainOfFire",
|
|
["Hellfire"] = "Spell_Fire_Incinerate",
|
|
["地狱烈焰"] = "Spell_Fire_Incinerate",
|
|
-- Priest
|
|
["Greater Heal"] = "Spell_Holy_GreaterHeal",
|
|
["强效治疗术"] = "Spell_Holy_GreaterHeal",
|
|
["Flash Heal"] = "Spell_Holy_FlashHeal",
|
|
["快速治疗"] = "Spell_Holy_FlashHeal",
|
|
["Heal"] = "Spell_Holy_Heal",
|
|
["治疗术"] = "Spell_Holy_Heal",
|
|
["Smite"] = "Spell_Holy_HolySmite",
|
|
["惩击"] = "Spell_Holy_HolySmite",
|
|
["Mind Blast"] = "Spell_Shadow_UnholyFrenzy",
|
|
["心灵震爆"] = "Spell_Shadow_UnholyFrenzy",
|
|
["Mind Flay"] = "Spell_Shadow_SiphonMana",
|
|
["精神鞭笞"] = "Spell_Shadow_SiphonMana",
|
|
["Mind Control"] = "Spell_Shadow_ShadowWordDominate",
|
|
["精神控制"] = "Spell_Shadow_ShadowWordDominate",
|
|
["Holy Fire"] = "Spell_Holy_SearingLight",
|
|
["神圣之火"] = "Spell_Holy_SearingLight",
|
|
["Resurrection"] = "Spell_Holy_Resurrection",
|
|
["复活术"] = "Spell_Holy_Resurrection",
|
|
-- Shaman
|
|
["Lightning Bolt"] = "Spell_Nature_Lightning",
|
|
["闪电箭"] = "Spell_Nature_Lightning",
|
|
["Chain Lightning"] = "Spell_Nature_ChainLightning",
|
|
["闪电链"] = "Spell_Nature_ChainLightning",
|
|
["Healing Wave"] = "Spell_Nature_MagicImmunity",
|
|
["治疗波"] = "Spell_Nature_MagicImmunity",
|
|
["Lesser Healing Wave"] = "Spell_Nature_HealingWaveLesser",
|
|
["次级治疗波"] = "Spell_Nature_HealingWaveLesser",
|
|
["Chain Heal"] = "Spell_Nature_HealingWaveGreater",
|
|
["治疗链"] = "Spell_Nature_HealingWaveGreater",
|
|
["Ancestral Spirit"] = "Spell_Nature_Regenerate",
|
|
["先祖之魂"] = "Spell_Nature_Regenerate",
|
|
-- Druid
|
|
["Wrath"] = "Spell_Nature_AbolishMagic",
|
|
["愤怒"] = "Spell_Nature_AbolishMagic",
|
|
["Starfire"] = "Spell_Arcane_StarFire",
|
|
["星火术"] = "Spell_Arcane_StarFire",
|
|
["Regrowth"] = "Spell_Nature_ResistNature",
|
|
["愈合"] = "Spell_Nature_ResistNature",
|
|
["Healing Touch"] = "Spell_Nature_HealingTouch",
|
|
["治疗之触"] = "Spell_Nature_HealingTouch",
|
|
["Entangling Roots"] = "Spell_Nature_StrangleVines",
|
|
["纠缠根须"] = "Spell_Nature_StrangleVines",
|
|
["Hibernate"] = "Spell_Nature_Sleep",
|
|
["休眠"] = "Spell_Nature_Sleep",
|
|
["Rebirth"] = "Spell_Nature_Reincarnation",
|
|
["复生"] = "Spell_Nature_Reincarnation",
|
|
["Tranquility"] = "Spell_Nature_Tranquility",
|
|
["宁静"] = "Spell_Nature_Tranquility",
|
|
["Moonfire"] = "Spell_Nature_StarFall",
|
|
["月火术"] = "Spell_Nature_StarFall",
|
|
-- Paladin
|
|
["Holy Light"] = "Spell_Holy_HolyBolt",
|
|
["圣光术"] = "Spell_Holy_HolyBolt",
|
|
["Flash of Light"] = "Spell_Holy_FlashHeal",
|
|
["圣光闪现"] = "Spell_Holy_FlashHeal",
|
|
["Hammer of Wrath"] = "Spell_Holy_SealOfMight",
|
|
["愤怒之锤"] = "Spell_Holy_SealOfMight",
|
|
["Exorcism"] = "Spell_Holy_Excorcism_02",
|
|
["驱邪术"] = "Spell_Holy_Excorcism_02",
|
|
["Redemption"] = "Spell_Holy_Resurrection",
|
|
["救赎"] = "Spell_Holy_Resurrection",
|
|
-- Hunter
|
|
["Aimed Shot"] = "INV_Spear_07",
|
|
["瞄准射击"] = "INV_Spear_07",
|
|
["Multi-Shot"] = "Ability_UpgradeMoonGlaive",
|
|
["多重射击"] = "Ability_UpgradeMoonGlaive",
|
|
["Volley"] = "Ability_Marksmanship",
|
|
["乱射"] = "Ability_Marksmanship",
|
|
["Revive Pet"] = "Ability_Hunter_BeastSoothe",
|
|
["复活宠物"] = "Ability_Hunter_BeastSoothe",
|
|
-- Common NPC / generic
|
|
["Shoot"] = "Ability_Marksmanship",
|
|
["射击"] = "Ability_Marksmanship",
|
|
["Mend"] = "Spell_Holy_Heal",
|
|
["修补"] = "Spell_Holy_Heal",
|
|
["Rejuvenation"] = "Spell_Nature_Rejuvenation",
|
|
["回春术"] = "Spell_Nature_Rejuvenation",
|
|
}
|
|
for k, v in pairs(SPELL_ICONS) do
|
|
SPELL_ICONS[k] = "Interface\\Icons\\" .. v
|
|
end
|
|
|
|
local function BuildSpellIconCache()
|
|
if not GetSpellName or not GetSpellTexture then return end
|
|
local i = 1
|
|
while true do
|
|
local name = GetSpellName(i, "spell")
|
|
if not name then break end
|
|
local tex = GetSpellTexture(i, "spell")
|
|
if tex then spellIconCache[name] = tex end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
local function GetSpellIcon(spellName)
|
|
if not spellName then return nil end
|
|
local tex = spellIconCache[spellName]
|
|
or SPELL_ICONS[spellName]
|
|
or (NanamiPlates_CombatLog and NanamiPlates_CombatLog.castIcons
|
|
and NanamiPlates_CombatLog.castIcons[spellName])
|
|
if tex then return tex end
|
|
if GetSpellName and GetSpellTexture then
|
|
local i = 1
|
|
while true do
|
|
local name = GetSpellName(i, "spell")
|
|
if not name then break end
|
|
if name == spellName then
|
|
tex = GetSpellTexture(i, "spell")
|
|
if tex then
|
|
spellIconCache[spellName] = tex
|
|
return tex
|
|
end
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
SFrames.GetSpellIcon = GetSpellIcon
|
|
SFrames.BuildSpellIconCache = BuildSpellIconCache
|
|
|
|
local function CLMatch(str, pattern)
|
|
if not str or not pattern then return nil end
|
|
local pat = string.gsub(pattern, "%%%d?%$?s", "(.+)")
|
|
pat = string.gsub(pat, "%%%d?%$?d", "(%%d+)")
|
|
for a, b, c, d in string.gfind(str, pat) do
|
|
return a, b, c, d
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function Clamp(value, minValue, maxValue)
|
|
if value < minValue then
|
|
return minValue
|
|
end
|
|
if value > maxValue then
|
|
return maxValue
|
|
end
|
|
return value
|
|
end
|
|
|
|
local DIST_BASE_WIDTH = 80
|
|
local DIST_BASE_HEIGHT = 24
|
|
local DIST_BASE_FONTSIZE = 14
|
|
|
|
function SFrames.Target:GetDistance(unit)
|
|
if not UnitExists(unit) then return nil end
|
|
if UnitIsUnit(unit, "player") then return "0 码" end
|
|
-- Using multiple "scale rulers" (rungs) for better precision in 1.12
|
|
if CheckInteractDistance(unit, 2) then return "< 8 码" -- Trade
|
|
elseif CheckInteractDistance(unit, 3) then return "8-10 码" -- Duel
|
|
elseif CheckInteractDistance(unit, 4) then return "10-28 码" -- Follow
|
|
elseif UnitIsVisible(unit) then return "28-100 码"
|
|
else return "> 100 码" end
|
|
end
|
|
|
|
function SFrames.Target:GetConfig()
|
|
local db = SFramesDB or {}
|
|
|
|
local width = tonumber(db.targetFrameWidth) or SFrames.Config.width or 220
|
|
width = Clamp(math.floor(width + 0.5), 170, 420)
|
|
|
|
local portraitWidth = tonumber(db.targetPortraitWidth) or SFrames.Config.portraitWidth or 50
|
|
portraitWidth = Clamp(math.floor(portraitWidth + 0.5), 32, 95)
|
|
if portraitWidth > width - 90 then
|
|
portraitWidth = width - 90
|
|
end
|
|
|
|
local healthHeight = tonumber(db.targetHealthHeight) or 38
|
|
healthHeight = Clamp(math.floor(healthHeight + 0.5), 14, 80)
|
|
|
|
local powerHeight = tonumber(db.targetPowerHeight) or 9
|
|
powerHeight = Clamp(math.floor(powerHeight + 0.5), 6, 40)
|
|
|
|
local height = healthHeight + powerHeight + 4
|
|
height = Clamp(height, 30, 140)
|
|
|
|
local nameFont = tonumber(db.targetNameFontSize) or 10
|
|
nameFont = Clamp(math.floor(nameFont + 0.5), 8, 18)
|
|
|
|
local valueFont = tonumber(db.targetValueFontSize) or 10
|
|
valueFont = Clamp(math.floor(valueFont + 0.5), 8, 18)
|
|
|
|
local frameScale = tonumber(db.targetFrameScale) or 1
|
|
frameScale = Clamp(frameScale, 0.7, 1.8)
|
|
|
|
return {
|
|
width = width,
|
|
height = height,
|
|
portraitWidth = portraitWidth,
|
|
healthHeight = healthHeight,
|
|
powerHeight = powerHeight,
|
|
nameFont = nameFont,
|
|
valueFont = valueFont,
|
|
scale = frameScale,
|
|
}
|
|
end
|
|
|
|
function SFrames.Target:ApplyConfig()
|
|
if not self.frame then return end
|
|
|
|
local cfg = self:GetConfig()
|
|
local f = self.frame
|
|
local db = SFramesDB or {}
|
|
|
|
local showPortrait = db.targetShowPortrait ~= false
|
|
local frameAlpha = tonumber(db.targetFrameAlpha) or 1
|
|
frameAlpha = Clamp(frameAlpha, 0.1, 1.0)
|
|
|
|
f:SetScale(cfg.scale)
|
|
f:SetWidth(cfg.width)
|
|
f:SetHeight(cfg.height)
|
|
f:SetAlpha(frameAlpha)
|
|
|
|
if showPortrait then
|
|
if f.portrait then
|
|
f.portrait:SetWidth(cfg.portraitWidth)
|
|
f.portrait:SetHeight(cfg.height - 2)
|
|
f.portrait:Show()
|
|
end
|
|
if f.portraitBG then
|
|
f.portraitBG:ClearAllPoints()
|
|
f.portraitBG:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -1, 0)
|
|
f.portraitBG:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
|
f.portraitBG:Show()
|
|
end
|
|
if f.health then
|
|
f.health:ClearAllPoints()
|
|
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
|
f.health:SetPoint("TOPRIGHT", f.portrait, "TOPLEFT", -1, 0)
|
|
f.health:SetHeight(cfg.healthHeight)
|
|
end
|
|
if f.classIcon and f.classIcon.overlay then
|
|
f.classIcon.overlay:ClearAllPoints()
|
|
f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
|
|
end
|
|
if f.comboText then
|
|
f.comboText:ClearAllPoints()
|
|
f.comboText:SetPoint("CENTER", f.portrait, "CENTER", 0, 0)
|
|
end
|
|
else
|
|
if f.portrait then f.portrait:Hide() end
|
|
if f.portraitBG then f.portraitBG:Hide() end
|
|
if f.health then
|
|
f.health:ClearAllPoints()
|
|
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
|
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
|
f.health:SetHeight(cfg.healthHeight)
|
|
end
|
|
if f.classIcon and f.classIcon.overlay then
|
|
f.classIcon.overlay:ClearAllPoints()
|
|
f.classIcon.overlay:SetPoint("CENTER", f, "TOPRIGHT", -8, 0)
|
|
end
|
|
if f.comboText then
|
|
f.comboText:ClearAllPoints()
|
|
f.comboText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0)
|
|
end
|
|
end
|
|
|
|
if f.healthBGFrame then
|
|
f.healthBGFrame:ClearAllPoints()
|
|
f.healthBGFrame:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
|
|
f.healthBGFrame:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
|
|
end
|
|
|
|
if f.power then
|
|
f.power:ClearAllPoints()
|
|
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
|
|
f.power:SetPoint("TOPRIGHT", f.health, "BOTTOMRIGHT", 0, 0)
|
|
f.power:SetHeight(cfg.powerHeight)
|
|
end
|
|
|
|
if f.powerBGFrame then
|
|
f.powerBGFrame:ClearAllPoints()
|
|
f.powerBGFrame:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
|
|
f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
|
|
end
|
|
|
|
local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
|
local fontPath = SFrames:GetFont()
|
|
|
|
if f.nameText then
|
|
f.nameText:SetFont(fontPath, cfg.nameFont, outline)
|
|
end
|
|
if f.healthText then
|
|
f.healthText:SetFont(fontPath, cfg.valueFont, outline)
|
|
end
|
|
if f.powerText then
|
|
f.powerText:SetFont(fontPath, cfg.valueFont, outline)
|
|
end
|
|
|
|
if f.castbar then
|
|
f.castbar:ClearAllPoints()
|
|
if showPortrait then
|
|
f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 6)
|
|
f.castbar:SetPoint("BOTTOMRIGHT", f.portrait, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
|
|
else
|
|
f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", 0, 6)
|
|
f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
|
|
end
|
|
end
|
|
|
|
if self.distanceFrame then
|
|
local dScale = tonumber(SFramesDB and SFramesDB.targetDistanceScale) or 1
|
|
self:ApplyDistanceScale(dScale)
|
|
end
|
|
|
|
if UnitExists("target") then
|
|
self:UpdateAll()
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:ApplyDistanceScale(scale)
|
|
local f = self.distanceFrame
|
|
if not f then return end
|
|
scale = Clamp(tonumber(scale) or 1, 0.7, 1.8)
|
|
f:SetWidth(DIST_BASE_WIDTH * scale)
|
|
f:SetHeight(DIST_BASE_HEIGHT * scale)
|
|
if f.text then
|
|
local fontPath = SFrames:GetFont()
|
|
local outline = (SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
|
local fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * scale + 0.5))
|
|
f.text:SetFont(fontPath, fontSize, outline)
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:InitializeDistanceFrame()
|
|
local f = CreateFrame("Button", "SFramesTargetDistanceFrame", UIParent)
|
|
f:SetFrameStrata("HIGH")
|
|
|
|
local dScale = (SFramesDB and type(SFramesDB.targetDistanceScale) == "number") and SFramesDB.targetDistanceScale or 1
|
|
dScale = Clamp(dScale, 0.7, 1.8)
|
|
f:SetWidth(DIST_BASE_WIDTH * dScale)
|
|
f:SetHeight(DIST_BASE_HEIGHT * dScale)
|
|
|
|
if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["TargetDistanceFrame"] then
|
|
local pos = SFramesDB.Positions["TargetDistanceFrame"]
|
|
f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
|
|
else
|
|
f:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
|
|
end
|
|
|
|
f:SetMovable(true)
|
|
f:EnableMouse(true)
|
|
f:RegisterForDrag("LeftButton")
|
|
f:SetScript("OnDragStart", function() if IsAltKeyDown() or SFrames.isUnlocked then this:StartMoving() end end)
|
|
f:SetScript("OnDragStop", function()
|
|
this:StopMovingOrSizing()
|
|
if not SFramesDB then SFramesDB = {} end
|
|
if not SFramesDB.Positions then SFramesDB.Positions = {} end
|
|
local point, relativeTo, relativePoint, xOfs, yOfs = this:GetPoint()
|
|
SFramesDB.Positions["TargetDistanceFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs }
|
|
end)
|
|
|
|
SFrames:CreateUnitBackdrop(f)
|
|
f:SetBackdrop(nil)
|
|
|
|
local fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * dScale + 0.5))
|
|
f.text = SFrames:CreateFontString(f, fontSize, "CENTER")
|
|
f.text:SetPoint("CENTER", f, "CENTER", 0, 0)
|
|
f.text:SetTextColor(1, 0.8, 0.2)
|
|
f.text:SetShadowColor(0, 0, 0, 1)
|
|
f.text:SetShadowOffset(1, -1)
|
|
|
|
SFrames.Target.distanceFrame = f
|
|
f:Hide()
|
|
|
|
f.timer = 0
|
|
f:SetScript("OnUpdate", function()
|
|
if SFramesDB and SFramesDB.targetDistanceEnabled == false then
|
|
if this:IsShown() then this:Hide() end
|
|
return
|
|
end
|
|
if not UnitExists("target") then
|
|
if this:IsShown() then this:Hide() end
|
|
return
|
|
end
|
|
this.timer = this.timer + (arg1 or 0)
|
|
if this.timer >= 0.4 then
|
|
this.timer = 0
|
|
local dist = SFrames.Target:GetDistance("target")
|
|
this.text:SetText(dist or "---")
|
|
if not this:IsShown() then this:Show() end
|
|
end
|
|
end)
|
|
end
|
|
|
|
local AURA_SIZE = 24
|
|
local AURA_SPACING = 2
|
|
local AURA_ROW_SPACING = 1
|
|
|
|
local function GetIncomingHeals(unit)
|
|
return SFrames:GetIncomingHeals(unit)
|
|
end
|
|
|
|
local function TryDropCursorOnUnit(unit)
|
|
if not unit or not UnitExists(unit) then return false end
|
|
if not CursorHasItem or not CursorHasItem() then return false end
|
|
if not DropItemOnUnit then return false end
|
|
|
|
local ok = pcall(DropItemOnUnit, unit)
|
|
if not ok then
|
|
return false
|
|
end
|
|
|
|
return not CursorHasItem()
|
|
end
|
|
|
|
function SFrames.Target:Initialize()
|
|
local f = CreateFrame("Button", "SFramesTargetFrame", UIParent)
|
|
f:SetWidth(SFrames.Config.width)
|
|
f:SetHeight(SFrames.Config.height)
|
|
if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["TargetFrame"] then
|
|
local pos = SFramesDB.Positions["TargetFrame"]
|
|
f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
|
|
else
|
|
f:SetPoint("CENTER", UIParent, "CENTER", 200, -100) -- Mirrored from player
|
|
end
|
|
local frameScale = (SFramesDB and type(SFramesDB.targetFrameScale) == "number") and SFramesDB.targetFrameScale or 1
|
|
f:SetScale(frameScale)
|
|
|
|
f:SetMovable(true)
|
|
f:EnableMouse(true)
|
|
f:RegisterForDrag("LeftButton")
|
|
f:SetScript("OnDragStart", function() if IsAltKeyDown() or SFrames.isUnlocked then f:StartMoving() end end)
|
|
f:SetScript("OnDragStop", function()
|
|
f:StopMovingOrSizing()
|
|
if not SFramesDB then SFramesDB = {} end
|
|
if not SFramesDB.Positions then SFramesDB.Positions = {} end
|
|
local point, relativeTo, relativePoint, xOfs, yOfs = f:GetPoint()
|
|
SFramesDB.Positions["TargetFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs }
|
|
end)
|
|
|
|
f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
|
f:SetScript("OnClick", function()
|
|
if arg1 == "LeftButton" then
|
|
if TryDropCursorOnUnit(this.unit) then
|
|
return
|
|
end
|
|
if SpellIsTargeting and SpellIsTargeting() then
|
|
SpellTargetUnit(this.unit)
|
|
end
|
|
elseif arg1 == "RightButton" then
|
|
if SpellIsTargeting() then
|
|
SpellStopTargeting()
|
|
return
|
|
end
|
|
HideDropDownMenu(1)
|
|
TargetFrameDropDown.unit = "target"
|
|
TargetFrameDropDown.name = UnitName("target")
|
|
TargetFrameDropDown.initialize = TargetFrameDropDown_Initialize
|
|
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "SFramesTargetFrame", 120, 10)
|
|
end
|
|
end)
|
|
f:SetScript("OnReceiveDrag", function()
|
|
if TryDropCursorOnUnit(this.unit) then
|
|
return
|
|
end
|
|
if SpellIsTargeting and SpellIsTargeting() then
|
|
SpellTargetUnit(this.unit)
|
|
end
|
|
end)
|
|
|
|
SFrames:CreateUnitBackdrop(f)
|
|
|
|
-- 3D Portrait (Right side for target)
|
|
local pWidth = SFrames.Config.portraitWidth
|
|
f.portrait = CreateFrame("PlayerModel", nil, f)
|
|
f.portrait:SetWidth(pWidth)
|
|
f.portrait:SetHeight(SFrames.Config.height - 2)
|
|
f.portrait:SetPoint("RIGHT", f, "RIGHT", -1, 0)
|
|
|
|
local pbg = CreateFrame("Frame", nil, f)
|
|
pbg:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -1, 0)
|
|
pbg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
|
|
pbg:SetFrameLevel(f:GetFrameLevel())
|
|
SFrames:CreateUnitBackdrop(pbg)
|
|
f.portraitBG = pbg
|
|
|
|
-- Health Bar (Left side)
|
|
f.health = SFrames:CreateStatusBar(f, "SFramesTargetHealth")
|
|
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
|
f.health:SetPoint("TOPRIGHT", f.portrait, "TOPLEFT", -1, 0)
|
|
f.health:SetHeight((SFrames.Config.height - 2) * 0.82 - 1)
|
|
|
|
local hbg = CreateFrame("Frame", nil, f)
|
|
hbg:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
|
|
hbg:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
|
|
hbg:SetFrameLevel(f:GetFrameLevel() - 1)
|
|
SFrames:CreateUnitBackdrop(hbg)
|
|
f.healthBGFrame = hbg
|
|
|
|
-- Add a dark backdrop behind the health texture
|
|
f.health.bg = f.health:CreateTexture(nil, "BACKGROUND")
|
|
f.health.bg:SetAllPoints()
|
|
f.health.bg:SetTexture(SFrames:GetTexture())
|
|
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
|
|
|
|
-- Heal prediction overlay (incoming heals)
|
|
f.health.healPredMine = f.health:CreateTexture(nil, "OVERLAY")
|
|
f.health.healPredMine:SetTexture(SFrames:GetTexture())
|
|
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
|
|
f.health.healPredMine:Hide()
|
|
|
|
f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
|
|
f.health.healPredOther:SetTexture(SFrames:GetTexture())
|
|
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
|
|
f.health.healPredOther:Hide()
|
|
|
|
-- Power Bar
|
|
f.power = SFrames:CreateStatusBar(f, "SFramesTargetPower")
|
|
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
|
|
f.power:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMLEFT", -1, 0)
|
|
|
|
local powerbg = CreateFrame("Frame", nil, f)
|
|
powerbg:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
|
|
powerbg:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
|
|
powerbg:SetFrameLevel(f:GetFrameLevel() - 1)
|
|
SFrames:CreateUnitBackdrop(powerbg)
|
|
f.powerBGFrame = powerbg
|
|
|
|
-- Add a dark backdrop behind the power texture
|
|
f.power.bg = f.power:CreateTexture(nil, "BACKGROUND")
|
|
f.power.bg:SetAllPoints()
|
|
f.power.bg:SetTexture(SFrames:GetTexture())
|
|
f.power.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
|
|
|
|
-- Class Icon Badge (overlaid on portrait, top-right corner with 1/3 outside)
|
|
f.classIcon = SFrames:CreateClassIcon(f, 16)
|
|
f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
|
|
|
|
-- Texts
|
|
f.nameText = SFrames:CreateFontString(f.health, 10, "LEFT")
|
|
f.nameText:SetPoint("LEFT", f.health, "LEFT", 4, 0)
|
|
|
|
f.healthText = SFrames:CreateFontString(f.health, 10, "RIGHT")
|
|
f.healthText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0)
|
|
|
|
f.powerText = SFrames:CreateFontString(f.power, 10, "RIGHT")
|
|
f.powerText:SetPoint("RIGHT", f.power, "RIGHT", -4, 0)
|
|
|
|
-- Outline/shadow setup for text to make it pop
|
|
f.nameText:SetShadowColor(0, 0, 0, 1)
|
|
f.nameText:SetShadowOffset(1, -1)
|
|
f.healthText:SetShadowColor(0, 0, 0, 1)
|
|
f.healthText:SetShadowOffset(1, -1)
|
|
f.powerText:SetShadowColor(0, 0, 0, 1)
|
|
f.powerText:SetShadowOffset(1, -1)
|
|
|
|
-- Combo Points
|
|
f.comboText = SFrames:CreateFontString(f, 20, "CENTER")
|
|
f.comboText:SetPoint("CENTER", f.portrait, "CENTER", 0, 0)
|
|
f.comboText:SetTextColor(1, 0.8, 0)
|
|
f.comboText:SetText("")
|
|
|
|
-- Raid Target Icon (top center of health bar, half outside frame)
|
|
local raidIconSize = 22
|
|
local raidIconOvr = CreateFrame("Frame", nil, f)
|
|
raidIconOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 5)
|
|
raidIconOvr:SetWidth(raidIconSize)
|
|
raidIconOvr:SetHeight(raidIconSize)
|
|
raidIconOvr:SetPoint("CENTER", f.health, "TOP", 0, 0)
|
|
f.raidIcon = raidIconOvr:CreateTexture(nil, "OVERLAY")
|
|
f.raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
|
|
f.raidIcon:SetAllPoints(raidIconOvr)
|
|
f.raidIcon:Hide()
|
|
f.raidIconOverlay = raidIconOvr
|
|
|
|
self.frame = f
|
|
self:ApplyConfig()
|
|
f:Hide()
|
|
|
|
SFrames:RegisterEvent("PLAYER_TARGET_CHANGED", function() self:OnTargetChanged() end)
|
|
SFrames:RegisterEvent("UNIT_HEALTH", function() if arg1 == "target" then self:UpdateHealth() end end)
|
|
SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if arg1 == "target" then self:UpdateHealth() end end)
|
|
SFrames:RegisterEvent("UNIT_MANA", function() if arg1 == "target" then self:UpdatePower() end end)
|
|
SFrames:RegisterEvent("UNIT_MAXMANA", function() if arg1 == "target" then self:UpdatePower() end end)
|
|
SFrames:RegisterEvent("UNIT_ENERGY", function() if arg1 == "target" then self:UpdatePower() end end)
|
|
SFrames:RegisterEvent("UNIT_MAXENERGY", function() if arg1 == "target" then self:UpdatePower() end end)
|
|
SFrames:RegisterEvent("UNIT_RAGE", function() if arg1 == "target" then self:UpdatePower() end end)
|
|
SFrames:RegisterEvent("UNIT_MAXRAGE", function() if arg1 == "target" then self:UpdatePower() end end)
|
|
SFrames:RegisterEvent("PLAYER_COMBO_POINTS", function() self:UpdateComboPoints() end)
|
|
SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "target" then self:UpdatePowerType() end end)
|
|
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function()
|
|
if arg1 == "target" and self.frame.portrait and not (SFramesDB and SFramesDB.targetShowPortrait == false) then
|
|
self.frame.portrait:SetUnit("target")
|
|
self.frame.portrait:SetCamera(0)
|
|
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
|
end
|
|
end)
|
|
SFrames:RegisterEvent("UNIT_DYNAMIC_FLAGS", function() if arg1 == "target" then self:UpdateAll() end end)
|
|
SFrames:RegisterEvent("UNIT_FACTION", function() if arg1 == "target" then self:UpdateAll() end end)
|
|
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end)
|
|
|
|
self:CreateAuras()
|
|
self:CreateCastbar()
|
|
self:InitializeDistanceFrame()
|
|
self:InitCastDetection()
|
|
|
|
BuildSpellIconCache()
|
|
SFrames:RegisterEvent("SPELLS_CHANGED", BuildSpellIconCache)
|
|
|
|
f.unit = "target"
|
|
f:SetScript("OnEnter", function()
|
|
GameTooltip_SetDefaultAnchor(GameTooltip, this)
|
|
GameTooltip:SetUnit(this.unit)
|
|
GameTooltip:Show()
|
|
end)
|
|
f:SetScript("OnLeave", function()
|
|
GameTooltip:Hide()
|
|
end)
|
|
|
|
-- If target already exists on load (e.g. after /reload), show and update it immediately
|
|
self:OnTargetChanged()
|
|
|
|
-- Register movers
|
|
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
|
SFrames.Movers:RegisterMover("TargetFrame", f, "目标",
|
|
"CENTER", "UIParent", "CENTER", 200, -100)
|
|
if SFrames.Target.distanceFrame then
|
|
SFrames.Movers:RegisterMover("TargetDistanceFrame", SFrames.Target.distanceFrame, "目标距离",
|
|
"CENTER", "UIParent", "CENTER", 0, 100)
|
|
end
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:InitCastDetection()
|
|
local castFrame = CreateFrame("Frame", nil, UIParent)
|
|
|
|
castFrame:RegisterEvent("UNIT_CASTEVENT")
|
|
castFrame:RegisterEvent("SPELLCAST_START")
|
|
castFrame:RegisterEvent("SPELLCAST_STOP")
|
|
castFrame:RegisterEvent("SPELLCAST_FAILED")
|
|
castFrame:RegisterEvent("SPELLCAST_INTERRUPTED")
|
|
castFrame:RegisterEvent("SPELLCAST_CHANNEL_START")
|
|
castFrame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
|
|
|
|
local CL_EVENTS = {
|
|
"CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE",
|
|
"CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE",
|
|
"CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE",
|
|
"CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF",
|
|
"CHAT_MSG_SPELL_CREATURE_VS_PARTY_BUFF",
|
|
"CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF",
|
|
"CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE",
|
|
"CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF",
|
|
"CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE",
|
|
"CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF",
|
|
"CHAT_MSG_SPELL_PARTY_DAMAGE",
|
|
"CHAT_MSG_SPELL_PARTY_BUFF",
|
|
"CHAT_MSG_SPELL_SELF_DAMAGE",
|
|
"CHAT_MSG_SPELL_SELF_BUFF",
|
|
}
|
|
for _, ev in ipairs(CL_EVENTS) do
|
|
castFrame:RegisterEvent(ev)
|
|
end
|
|
|
|
local function ResolveSelfIcon(spellName)
|
|
local texture
|
|
local _UCI = UnitCastingInfo or (ShaguTweaks and ShaguTweaks.UnitCastingInfo)
|
|
if _UCI then
|
|
local _, _, _, tex = _UCI("player")
|
|
texture = tex
|
|
end
|
|
if not texture then
|
|
local _UCH = UnitChannelInfo or (ShaguTweaks and ShaguTweaks.UnitChannelInfo)
|
|
if _UCH then
|
|
local _, _, _, tex = _UCH("player")
|
|
texture = tex
|
|
end
|
|
end
|
|
if not texture and SFrames.castdb and UnitGUID then
|
|
local guid = UnitGUID("player")
|
|
if guid and SFrames.castdb[guid] and SFrames.castdb[guid].icon then
|
|
texture = SFrames.castdb[guid].icon
|
|
end
|
|
end
|
|
if not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark" then
|
|
texture = GetSpellIcon(spellName) or texture
|
|
end
|
|
return texture or "Interface\\Icons\\INV_Misc_QuestionMark"
|
|
end
|
|
|
|
castFrame:SetScript("OnEvent", function()
|
|
-- Player's own cast events (for self-target and friendly-target-is-self)
|
|
if event == "SPELLCAST_START" then
|
|
if UnitExists("target") and UnitIsUnit("target", "player") then
|
|
local spellName = arg1
|
|
local duration = arg2
|
|
targetCLCast = {
|
|
spell = spellName,
|
|
startTime = GetTime(),
|
|
duration = (duration or 2000) / 1000,
|
|
icon = ResolveSelfIcon(spellName),
|
|
channel = false,
|
|
}
|
|
end
|
|
return
|
|
end
|
|
if event == "SPELLCAST_CHANNEL_START" then
|
|
if UnitExists("target") and UnitIsUnit("target", "player") then
|
|
local duration = arg1
|
|
local spellName = arg2
|
|
targetCLCast = {
|
|
spell = spellName,
|
|
startTime = GetTime(),
|
|
duration = (duration or 2000) / 1000,
|
|
icon = ResolveSelfIcon(spellName),
|
|
channel = true,
|
|
}
|
|
end
|
|
return
|
|
end
|
|
if event == "SPELLCAST_STOP" or event == "SPELLCAST_FAILED"
|
|
or event == "SPELLCAST_INTERRUPTED" or event == "SPELLCAST_CHANNEL_STOP" then
|
|
if UnitExists("target") and UnitIsUnit("target", "player") then
|
|
targetCLCast = nil
|
|
end
|
|
return
|
|
end
|
|
|
|
-- UNIT_CASTEVENT (SuperWoW / TurtleWoW): works for all units
|
|
if event == "UNIT_CASTEVENT" then
|
|
if not UnitGUID or not UnitExists("target") then return end
|
|
local targetGUID = UnitGUID("target")
|
|
if not targetGUID or arg1 ~= targetGUID then return end
|
|
|
|
if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
|
|
local spell, icon
|
|
if SpellInfo and arg4 then
|
|
spell, _, icon = SpellInfo(arg4)
|
|
end
|
|
spell = spell or "Casting"
|
|
if not icon or icon == "" or icon == "Interface\\Icons\\INV_Misc_QuestionMark" then
|
|
icon = GetSpellIcon(spell) or icon
|
|
end
|
|
icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark"
|
|
targetCLCast = {
|
|
spell = spell,
|
|
startTime = GetTime(),
|
|
duration = (arg5 or 2000) / 1000,
|
|
icon = icon,
|
|
channel = (arg3 == "CHANNEL"),
|
|
}
|
|
elseif arg3 == "FAIL" then
|
|
targetCLCast = nil
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Combat log parsing: "X begins to cast Y" (third-person, all other units)
|
|
if not arg1 or not UnitExists("target") then return end
|
|
local targetName = UnitName("target")
|
|
if not targetName then return end
|
|
|
|
local msg = arg1
|
|
local caster, spell
|
|
|
|
local castStart = SPELLCASTOTHERSTART or "%s begins to cast %s."
|
|
caster, spell = CLMatch(msg, castStart)
|
|
if not caster then
|
|
local perfStart = SPELLPERFORMOTHERSTART or "%s begins to perform %s."
|
|
caster, spell = CLMatch(msg, perfStart)
|
|
end
|
|
|
|
if caster and caster == targetName and spell then
|
|
local icon = GetSpellIcon(spell) or "Interface\\Icons\\INV_Misc_QuestionMark"
|
|
targetCLCast = {
|
|
spell = spell,
|
|
startTime = GetTime(),
|
|
duration = 2.0,
|
|
icon = icon,
|
|
channel = false,
|
|
}
|
|
return
|
|
end
|
|
|
|
if targetCLCast then
|
|
local isFail = false
|
|
for u in string.gfind(msg, "(.+)'s .+ is interrupted%.") do
|
|
if u == targetName then isFail = true end
|
|
end
|
|
if not isFail then
|
|
for u in string.gfind(msg, "(.+)'s .+ fails%.") do
|
|
if u == targetName then isFail = true end
|
|
end
|
|
end
|
|
if not isFail and SPELLINTERRUPTOTHEROTHER then
|
|
local a = CLMatch(msg, SPELLINTERRUPTOTHEROTHER)
|
|
if a == targetName then isFail = true end
|
|
end
|
|
if isFail then targetCLCast = nil end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function SFrames.Target:OnTargetChanged()
|
|
targetCLCast = nil
|
|
if UnitExists("target") then
|
|
self.frame:Show()
|
|
self:UpdateAll()
|
|
if SFrames.Target.distanceFrame then
|
|
local dist = self:GetDistance("target")
|
|
SFrames.Target.distanceFrame.text:SetText(dist or "---")
|
|
if not (SFramesDB and SFramesDB.targetDistanceEnabled == false) then
|
|
SFrames.Target.distanceFrame:Show()
|
|
else
|
|
SFrames.Target.distanceFrame:Hide()
|
|
end
|
|
end
|
|
else
|
|
self.frame:Hide()
|
|
if SFrames.Target.distanceFrame then SFrames.Target.distanceFrame:Hide() end
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdateAll()
|
|
self:UpdateHealth()
|
|
self:UpdatePowerType()
|
|
self:UpdatePower()
|
|
self:UpdateComboPoints()
|
|
self:UpdateRaidIcon()
|
|
self:UpdateAuras()
|
|
|
|
local showPortrait = not (SFramesDB and SFramesDB.targetShowPortrait == false)
|
|
if showPortrait and self.frame.portrait then
|
|
self.frame.portrait:SetUnit("target")
|
|
self.frame.portrait:SetCamera(0)
|
|
self.frame.portrait:Hide()
|
|
self.frame.portrait:Show()
|
|
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
|
end
|
|
|
|
local name = UnitName("target") or ""
|
|
local level = UnitLevel("target")
|
|
local levelText = level
|
|
|
|
-- Difficulty Color logic
|
|
local function RGBToHex(r, g, b)
|
|
return string.format("|cff%02x%02x%02x", r*255, g*255, b*255)
|
|
end
|
|
|
|
local function GetLevelDiffColor(targetLevel)
|
|
local playerLevel = UnitLevel("player")
|
|
if targetLevel == -1 then return 1, 0, 0 end -- Skull
|
|
|
|
local diff = targetLevel - playerLevel
|
|
if diff >= 5 then
|
|
return 1, 0.1, 0.1 -- Red
|
|
elseif diff >= 3 then
|
|
return 1, 0.5, 0.25 -- Orange
|
|
elseif diff >= -2 then
|
|
return 1, 1, 0 -- Yellow
|
|
elseif -diff <= GetQuestGreenRange() then
|
|
return 0.25, 0.75, 0.25 -- Green
|
|
else
|
|
return 0.5, 0.5, 0.5 -- Grey
|
|
end
|
|
end
|
|
|
|
local levelColor = RGBToHex(1, 1, 1) -- default white
|
|
|
|
if level == -1 then
|
|
levelText = "??"
|
|
levelColor = RGBToHex(1, 0, 0) -- skull is always red
|
|
else
|
|
local r, g, b = GetLevelDiffColor(level)
|
|
levelColor = RGBToHex(r, g, b)
|
|
end
|
|
|
|
local classif = UnitClassification("target")
|
|
if classif == "elite" or classif == "rareelite" then
|
|
levelText = levelText .. "+"
|
|
elseif classif == "rare" then
|
|
levelText = levelText .. "R"
|
|
elseif classif == "worldboss" then
|
|
levelText = "??"
|
|
levelColor = RGBToHex(1, 0, 0)
|
|
end
|
|
|
|
local formattedLevel = levelColor .. levelText .. "|r"
|
|
|
|
-- Toggle level display from config DB
|
|
if SFramesDB and SFramesDB.showLevel == false then
|
|
formattedLevel = ""
|
|
else
|
|
formattedLevel = formattedLevel .. " "
|
|
end
|
|
local showClassText = not (SFramesDB and SFramesDB.targetShowClass == false)
|
|
if showClassText and UnitIsPlayer("target") then
|
|
local localizedClass = UnitClass("target")
|
|
if localizedClass and localizedClass ~= "" then
|
|
name = name .. " " .. localizedClass
|
|
end
|
|
end
|
|
|
|
if UnitIsPlayer("target") and not (SFramesDB and SFramesDB.targetShowClassIcon == false) then
|
|
local _, tClass = UnitClass("target")
|
|
SFrames:SetClassIcon(self.frame.classIcon, tClass)
|
|
else
|
|
if self.frame.classIcon then
|
|
self.frame.classIcon:Hide()
|
|
if self.frame.classIcon.overlay then self.frame.classIcon.overlay:Hide() end
|
|
end
|
|
end
|
|
|
|
local useClassColor = not (SFramesDB and SFramesDB.classColorHealth == false)
|
|
|
|
if UnitIsPlayer("target") and useClassColor then
|
|
local _, class = UnitClass("target")
|
|
if class and SFrames.Config.colors.class[class] then
|
|
local color = SFrames.Config.colors.class[class]
|
|
-- Set Health Color
|
|
self.frame.health:SetStatusBarColor(color.r, color.g, color.b)
|
|
-- Apply Class Color to Name
|
|
self.frame.nameText:SetText(formattedLevel .. name)
|
|
self.frame.nameText:SetTextColor(color.r, color.g, color.b)
|
|
else
|
|
self.frame.health:SetStatusBarColor(0, 1, 0)
|
|
self.frame.nameText:SetText(formattedLevel .. name)
|
|
self.frame.nameText:SetTextColor(1, 1, 1)
|
|
end
|
|
else
|
|
local r, g, b = 0.85, 0.77, 0.36 -- Neutral (Softer Yellow)
|
|
local isTapped = UnitIsTapped("target") and not UnitIsTappedByPlayer("target")
|
|
if isTapped then
|
|
r, g, b = 0.53, 0.53, 0.53 -- Tapped by others (Grey)
|
|
name = name .. " (无拾取)" -- For colorblind
|
|
elseif UnitIsEnemy("player", "target") then
|
|
r, g, b = 0.78, 0.25, 0.25 -- Enemy (Softer Red)
|
|
elseif UnitIsFriend("player", "target") then
|
|
r, g, b = 0.33, 0.59, 0.33 -- Friend (Softer Green)
|
|
end
|
|
self.frame.health:SetStatusBarColor(r, g, b)
|
|
-- Color Name same as reaction
|
|
self.frame.nameText:SetText(formattedLevel .. name)
|
|
self.frame.nameText:SetTextColor(r, g, b)
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdateHealth()
|
|
local hp = UnitHealth("target")
|
|
local maxHp = UnitHealthMax("target")
|
|
self.frame.health:SetMinMaxValues(0, maxHp)
|
|
self.frame.health:SetValue(hp)
|
|
|
|
local displayHp, displayMax = hp, maxHp
|
|
if (not SFramesDB or SFramesDB.mobRealHealth ~= false) and maxHp == 100
|
|
and not UnitIsPlayer("target") and not UnitPlayerControlled("target") then
|
|
local name = UnitName("target")
|
|
local level = UnitLevel("target")
|
|
if name and level and LibMobHealth_Cache then
|
|
local key = name .. ":" .. level
|
|
local realMax = LibMobHealth_Cache[key]
|
|
if realMax and realMax > 0 then
|
|
displayMax = realMax
|
|
displayHp = math.floor(realMax * hp / 100)
|
|
end
|
|
end
|
|
end
|
|
|
|
if displayMax > 0 then
|
|
self.frame.healthText:SetText(displayHp .. " / " .. displayMax)
|
|
else
|
|
self.frame.healthText:SetText(displayHp)
|
|
end
|
|
|
|
self:UpdateHealPrediction()
|
|
end
|
|
|
|
function SFrames.Target:UpdateHealPrediction()
|
|
if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther) then return end
|
|
local predMine = self.frame.health.healPredMine
|
|
local predOther = self.frame.health.healPredOther
|
|
|
|
local function HidePredictions()
|
|
predMine:Hide()
|
|
predOther:Hide()
|
|
end
|
|
|
|
if not UnitExists("target") then
|
|
HidePredictions()
|
|
return
|
|
end
|
|
|
|
local hp = UnitHealth("target") or 0
|
|
local maxHp = UnitHealthMax("target") or 0
|
|
if maxHp <= 0 or hp >= maxHp then
|
|
HidePredictions()
|
|
return
|
|
end
|
|
|
|
local _, mineIncoming, othersIncoming = GetIncomingHeals("target")
|
|
local missing = maxHp - hp
|
|
if missing <= 0 then
|
|
HidePredictions()
|
|
return
|
|
end
|
|
|
|
local mineShown = math.min(math.max(0, mineIncoming), missing)
|
|
local remaining = missing - mineShown
|
|
local otherShown = math.min(math.max(0, othersIncoming), remaining)
|
|
if mineShown <= 0 and otherShown <= 0 then
|
|
HidePredictions()
|
|
return
|
|
end
|
|
|
|
local barWidth = self.frame.health:GetWidth() or 0
|
|
if barWidth <= 0 then
|
|
HidePredictions()
|
|
return
|
|
end
|
|
|
|
local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5)
|
|
if currentWidth < 0 then currentWidth = 0 end
|
|
if currentWidth > barWidth then currentWidth = barWidth end
|
|
|
|
local availableWidth = barWidth - currentWidth
|
|
if availableWidth <= 0 then
|
|
HidePredictions()
|
|
return
|
|
end
|
|
|
|
local mineWidth = math.floor((mineShown / maxHp) * barWidth + 0.5)
|
|
local otherWidth = math.floor((otherShown / maxHp) * barWidth + 0.5)
|
|
if mineWidth < 0 then mineWidth = 0 end
|
|
if otherWidth < 0 then otherWidth = 0 end
|
|
if mineWidth > availableWidth then mineWidth = availableWidth end
|
|
if otherWidth > (availableWidth - mineWidth) then
|
|
otherWidth = availableWidth - mineWidth
|
|
end
|
|
|
|
if mineWidth > 0 then
|
|
predMine:ClearAllPoints()
|
|
predMine:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth, 0)
|
|
predMine:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth, 0)
|
|
predMine:SetWidth(mineWidth)
|
|
predMine:Show()
|
|
else
|
|
predMine:Hide()
|
|
end
|
|
|
|
if otherWidth > 0 then
|
|
predOther:ClearAllPoints()
|
|
predOther:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth + mineWidth, 0)
|
|
predOther:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
|
|
predOther:SetWidth(otherWidth)
|
|
predOther:Show()
|
|
else
|
|
predOther:Hide()
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdatePowerType()
|
|
local powerType = UnitPowerType("target")
|
|
local color = SFrames.Config.colors.power[powerType]
|
|
if color then
|
|
self.frame.power:SetStatusBarColor(color.r, color.g, color.b)
|
|
else
|
|
self.frame.power:SetStatusBarColor(0, 0, 1)
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdatePower()
|
|
local power = UnitMana("target")
|
|
local maxPower = UnitManaMax("target")
|
|
self.frame.power:SetMinMaxValues(0, maxPower)
|
|
self.frame.power:SetValue(power)
|
|
if maxPower > 0 then
|
|
self.frame.powerText:SetText(power .. " / " .. maxPower)
|
|
else
|
|
self.frame.powerText:SetText("")
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdateComboPoints()
|
|
local points = GetComboPoints()
|
|
if points > 0 then
|
|
self.frame.comboText:SetText(points)
|
|
else
|
|
self.frame.comboText:SetText("")
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdateRaidIcon()
|
|
if not (self.frame and self.frame.raidIcon) then return end
|
|
if not GetRaidTargetIndex then
|
|
self.frame.raidIcon:Hide()
|
|
return
|
|
end
|
|
if not UnitExists("target") then
|
|
self.frame.raidIcon:Hide()
|
|
return
|
|
end
|
|
local index = GetRaidTargetIndex("target")
|
|
if index and index > 0 and index <= 8 then
|
|
local col = math.mod(index - 1, 4)
|
|
local row = math.floor((index - 1) / 4)
|
|
self.frame.raidIcon:SetTexCoord(col * 0.25, (col + 1) * 0.25, row * 0.25, (row + 1) * 0.25)
|
|
self.frame.raidIcon:Show()
|
|
else
|
|
self.frame.raidIcon:Hide()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Target Auras (Buffs / Debuffs)
|
|
--------------------------------------------------------------------------------
|
|
|
|
function SFrames.Target:CreateAuras()
|
|
self.frame.buffs = {}
|
|
self.frame.debuffs = {}
|
|
|
|
-- Target Buffs (Top left to right)
|
|
for i = 1, 16 do
|
|
local b = CreateFrame("Button", "SFramesTargetBuff"..i, self.frame)
|
|
b:SetWidth(AURA_SIZE)
|
|
b:SetHeight(AURA_SIZE)
|
|
SFrames:CreateUnitBackdrop(b)
|
|
|
|
b.icon = b:CreateTexture(nil, "ARTWORK")
|
|
b.icon:SetAllPoints()
|
|
b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
|
|
|
b.cdText = SFrames:CreateFontString(b, 9, "CENTER")
|
|
b.cdText:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
|
|
b.cdText:SetTextColor(1, 0.82, 0)
|
|
b.cdText:SetShadowColor(0, 0, 0, 1)
|
|
b.cdText:SetShadowOffset(1, -1)
|
|
|
|
-- Tooltip support
|
|
b:SetScript("OnEnter", function()
|
|
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
|
|
GameTooltip:SetUnitBuff("target", this:GetID())
|
|
end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
|
|
-- Default row anchor (Starting bottom left for buffs)
|
|
if i == 1 then
|
|
b:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
|
|
elseif math.mod(i - 1, 8) == 0 then
|
|
b:SetPoint("TOP", self.frame.buffs[i-8], "BOTTOM", 0, -AURA_ROW_SPACING)
|
|
else
|
|
b:SetPoint("LEFT", self.frame.buffs[i-1], "RIGHT", AURA_SPACING, 0)
|
|
end
|
|
b:Hide()
|
|
self.frame.buffs[i] = b
|
|
end
|
|
|
|
-- Target Debuffs (Bottom left to right)
|
|
for i = 1, 16 do
|
|
local b = CreateFrame("Button", "SFramesTargetDebuff"..i, self.frame)
|
|
b:SetWidth(AURA_SIZE)
|
|
b:SetHeight(AURA_SIZE)
|
|
SFrames:CreateUnitBackdrop(b)
|
|
|
|
b.icon = b:CreateTexture(nil, "ARTWORK")
|
|
b.icon:SetAllPoints()
|
|
b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
|
|
|
b.cdText = SFrames:CreateFontString(b, 9, "CENTER")
|
|
b.cdText:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
|
|
b.cdText:SetTextColor(1, 0.82, 0)
|
|
b.cdText:SetShadowColor(0, 0, 0, 1)
|
|
b.cdText:SetShadowOffset(1, -1)
|
|
|
|
-- Tooltip support
|
|
b:SetScript("OnEnter", function()
|
|
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
|
|
GameTooltip:SetUnitDebuff("target", this:GetID())
|
|
end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
|
|
-- Debuff anchors are recalulated dynamically in UpdateAuras
|
|
if i == 1 then
|
|
b:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
|
|
elseif math.mod(i - 1, 8) == 0 then
|
|
b:SetPoint("TOP", self.frame.debuffs[i-8], "BOTTOM", 0, -AURA_ROW_SPACING)
|
|
else
|
|
b:SetPoint("LEFT", self.frame.debuffs[i-1], "RIGHT", AURA_SPACING, 0)
|
|
end
|
|
b:Hide()
|
|
self.frame.debuffs[i] = b
|
|
end
|
|
|
|
SFrames:RegisterEvent("UNIT_AURA", function() if arg1 == "target" then self:UpdateAuras() end end)
|
|
|
|
self.auraUpdater = CreateFrame("Frame", nil, self.frame)
|
|
self.auraUpdater.timer = 0
|
|
self.auraUpdater:SetScript("OnUpdate", function()
|
|
this.timer = this.timer + arg1
|
|
if this.timer >= 0.25 then
|
|
SFrames.Target:TickAuras()
|
|
SFrames.Target:UpdateHealPrediction()
|
|
this.timer = 0
|
|
end
|
|
end)
|
|
end
|
|
|
|
function SFrames.Target:TickAuras()
|
|
if not UnitExists("target") then return end
|
|
|
|
local timeNow = GetTime()
|
|
local npFormat = NanamiPlates_Auras and NanamiPlates_Auras.FormatTime
|
|
local hasNP = NanamiPlates_SpellDB and NanamiPlates_SpellDB.FindEffectData
|
|
|
|
local targetName, targetLevel, targetGUID
|
|
if hasNP then
|
|
targetName = UnitName("target")
|
|
targetLevel = UnitLevel("target") or 0
|
|
targetGUID = UnitGUID and UnitGUID("target")
|
|
end
|
|
|
|
-- Buffs
|
|
for i = 1, 16 do
|
|
local b = self.frame.buffs[i]
|
|
if b:IsShown() and b.expirationTime then
|
|
local timeLeft = b.expirationTime - timeNow
|
|
if timeLeft > 0 and timeLeft < 3600 then
|
|
if npFormat then
|
|
local text, r, g, bc, a = npFormat(timeLeft)
|
|
b.cdText:SetText(text)
|
|
if r then b.cdText:SetTextColor(r, g, bc, a or 1) end
|
|
else
|
|
b.cdText:SetText(SFrames:FormatTime(timeLeft))
|
|
end
|
|
else
|
|
b.cdText:SetText("")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Debuffs: re-query SpellDB for live-accurate timers
|
|
for i = 1, 16 do
|
|
local b = self.frame.debuffs[i]
|
|
if b:IsShown() then
|
|
local timeLeft = nil
|
|
|
|
if hasNP and b.effectName then
|
|
local data = targetGUID and NanamiPlates_SpellDB:FindEffectData(targetGUID, targetLevel, b.effectName)
|
|
if not data and targetName then
|
|
data = NanamiPlates_SpellDB:FindEffectData(targetName, targetLevel, b.effectName)
|
|
end
|
|
if data and data.start and data.duration then
|
|
local remaining = data.duration + data.start - timeNow
|
|
if remaining > 0 then
|
|
timeLeft = remaining
|
|
b.expirationTime = timeNow + remaining
|
|
end
|
|
end
|
|
end
|
|
|
|
if not timeLeft and b.expirationTime then
|
|
timeLeft = b.expirationTime - timeNow
|
|
end
|
|
|
|
if timeLeft and timeLeft > 0 and timeLeft < 3600 then
|
|
if npFormat then
|
|
local text, r, g, bc, a = npFormat(timeLeft)
|
|
b.cdText:SetText(text)
|
|
if r then b.cdText:SetTextColor(r, g, bc, a or 1) end
|
|
else
|
|
b.cdText:SetText(SFrames:FormatTime(timeLeft))
|
|
end
|
|
else
|
|
b.cdText:SetText("")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function SFrames.Target:UpdateAuras()
|
|
if not UnitExists("target") then return end
|
|
|
|
local numBuffs = 0
|
|
-- Buffs
|
|
for i = 1, 16 do
|
|
local texture = UnitBuff("target", i)
|
|
local b = self.frame.buffs[i]
|
|
b:SetID(i) -- Ensure ID is set for tooltips
|
|
if texture then
|
|
b.icon:SetTexture(texture)
|
|
|
|
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
|
SFrames.Tooltip:ClearLines()
|
|
SFrames.Tooltip:SetUnitBuff("target", i)
|
|
local timeLeft = SFrames:GetAuraTimeLeft("target", i, true)
|
|
SFrames.Tooltip:Hide()
|
|
if timeLeft and timeLeft > 0 then
|
|
b.expirationTime = GetTime() + timeLeft
|
|
b.cdText:SetText(SFrames:FormatTime(timeLeft))
|
|
else
|
|
b.expirationTime = nil
|
|
b.cdText:SetText("")
|
|
end
|
|
|
|
b:Show()
|
|
numBuffs = numBuffs + 1
|
|
else
|
|
b.expirationTime = nil
|
|
b.cdText:SetText("")
|
|
b:Hide()
|
|
end
|
|
end
|
|
|
|
-- Dynamically re-anchor the first Debuff based on visible Buffs
|
|
local firstDebuff = self.frame.debuffs[1]
|
|
if firstDebuff then
|
|
firstDebuff:ClearAllPoints()
|
|
if numBuffs > 0 then
|
|
-- Find the start of the LAST row of buffs
|
|
local lastRowStart = math.floor((numBuffs - 1) / 8) * 8 + 1
|
|
firstDebuff:SetPoint("TOP", self.frame.buffs[lastRowStart], "BOTTOM", 0, -AURA_ROW_SPACING)
|
|
else
|
|
firstDebuff:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
|
|
end
|
|
end
|
|
|
|
-- Debuffs
|
|
local hasNP = NanamiPlates_SpellDB and NanamiPlates_SpellDB.UnitDebuff
|
|
local npFormat = NanamiPlates_Auras and NanamiPlates_Auras.FormatTime
|
|
|
|
for i = 1, 16 do
|
|
local texture = UnitDebuff("target", i)
|
|
local b = self.frame.debuffs[i]
|
|
b:SetID(i)
|
|
if texture then
|
|
b.icon:SetTexture(texture)
|
|
|
|
local timeLeft = 0
|
|
local effectName = nil
|
|
|
|
if hasNP then
|
|
local effect, rank, _, stacks, dtype, duration, npTimeLeft, isOwn = NanamiPlates_SpellDB:UnitDebuff("target", i)
|
|
effectName = effect
|
|
if npTimeLeft and npTimeLeft > 0 then
|
|
timeLeft = npTimeLeft
|
|
elseif effect and effect ~= "" and duration and duration > 0
|
|
and NanamiPlates_Auras and NanamiPlates_Auras.timers then
|
|
local unitKey = (UnitGUID and UnitGUID("target")) or UnitName("target") or ""
|
|
local cached = NanamiPlates_Auras.timers[unitKey .. "_" .. effect]
|
|
if not cached and UnitName("target") then
|
|
cached = NanamiPlates_Auras.timers[UnitName("target") .. "_" .. effect]
|
|
end
|
|
if cached and cached.startTime and cached.duration then
|
|
local remaining = cached.duration - (GetTime() - cached.startTime)
|
|
if remaining > 0 then timeLeft = remaining end
|
|
end
|
|
end
|
|
end
|
|
|
|
if timeLeft <= 0 then
|
|
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
|
|
SFrames.Tooltip:ClearLines()
|
|
SFrames.Tooltip:SetUnitDebuff("target", i)
|
|
timeLeft = SFrames:GetAuraTimeLeft("target", i, false)
|
|
SFrames.Tooltip:Hide()
|
|
end
|
|
|
|
if timeLeft and timeLeft > 0 then
|
|
b.expirationTime = GetTime() + timeLeft
|
|
b.effectName = effectName
|
|
if npFormat then
|
|
local text, r, g, bc, a = npFormat(timeLeft)
|
|
b.cdText:SetText(text)
|
|
if r then b.cdText:SetTextColor(r, g, bc, a or 1) end
|
|
else
|
|
b.cdText:SetText(SFrames:FormatTime(timeLeft))
|
|
end
|
|
else
|
|
b.expirationTime = nil
|
|
b.effectName = nil
|
|
b.cdText:SetText("")
|
|
end
|
|
|
|
b:Show()
|
|
else
|
|
b.expirationTime = nil
|
|
b.effectName = nil
|
|
b.cdText:SetText("")
|
|
b:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Target Castbar
|
|
--------------------------------------------------------------------------------
|
|
|
|
function SFrames.Target:CreateCastbar()
|
|
local cb = SFrames:CreateStatusBar(self.frame, "SFramesTargetCastbar")
|
|
cb:SetHeight(SFrames.Config.castbarHeight)
|
|
cb:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", 0, 6)
|
|
cb:SetPoint("BOTTOMRIGHT", self.frame.portrait, "TOPRIGHT", -(SFrames.Config.castbarHeight + 6), 6)
|
|
|
|
local cbbg = CreateFrame("Frame", nil, self.frame)
|
|
cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
|
|
cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
|
|
cbbg:SetFrameLevel(cb:GetFrameLevel() - 1)
|
|
SFrames:CreateUnitBackdrop(cbbg)
|
|
|
|
cb.bg = cb:CreateTexture(nil, "BACKGROUND")
|
|
cb.bg:SetAllPoints()
|
|
cb.bg:SetTexture(SFrames:GetTexture())
|
|
cb.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
|
|
cb:SetStatusBarColor(1, 0.7, 0)
|
|
|
|
cb.text = SFrames:CreateFontString(cb, 10, "LEFT")
|
|
cb.text:SetPoint("LEFT", cb, "LEFT", 4, 0)
|
|
|
|
cb.icon = cb:CreateTexture(nil, "ARTWORK")
|
|
cb.icon:SetWidth(SFrames.Config.castbarHeight + 2)
|
|
cb.icon:SetHeight(SFrames.Config.castbarHeight + 2)
|
|
cb.icon:SetPoint("LEFT", cb, "RIGHT", 4, 0)
|
|
cb.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
|
|
|
local ibg = CreateFrame("Frame", nil, self.frame)
|
|
ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
|
|
ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
|
|
ibg:SetFrameLevel(cb:GetFrameLevel() - 1)
|
|
SFrames:CreateUnitBackdrop(ibg)
|
|
|
|
cb:Hide()
|
|
cbbg:Hide()
|
|
cb.icon:Hide()
|
|
ibg:Hide()
|
|
|
|
self.frame.castbar = cb
|
|
self.frame.castbar.cbbg = cbbg
|
|
self.frame.castbar.ibg = ibg
|
|
|
|
self.frame.castbarUpdater = CreateFrame("Frame", nil, self.frame)
|
|
self.frame.castbarUpdater:SetScript("OnUpdate", function() SFrames.Target:CastbarOnUpdate() end)
|
|
end
|
|
|
|
function SFrames.Target:CastbarOnUpdate()
|
|
local cb = self.frame.castbar
|
|
if not UnitExists("target") then
|
|
cb:Hide()
|
|
cb.cbbg:Hide()
|
|
cb.icon:Hide()
|
|
cb.ibg:Hide()
|
|
return
|
|
end
|
|
|
|
local cast, texture, startTime, endTime, channel
|
|
|
|
-- 1) UnitCastingInfo / UnitChannelInfo (TurtleWoW / ShaguTweaks)
|
|
local _UnitCastingInfo = UnitCastingInfo or (ShaguTweaks and ShaguTweaks.UnitCastingInfo)
|
|
if _UnitCastingInfo then
|
|
local c, _, _, tex, st, et = _UnitCastingInfo("target")
|
|
if c then
|
|
cast, texture, startTime, endTime = c, tex, st, et
|
|
end
|
|
end
|
|
|
|
if not cast then
|
|
local _UnitChannelInfo = UnitChannelInfo or (ShaguTweaks and ShaguTweaks.UnitChannelInfo)
|
|
if _UnitChannelInfo then
|
|
local c, _, _, tex, st, et = _UnitChannelInfo("target")
|
|
if c then
|
|
cast, texture, startTime, endTime = c, tex, st, et
|
|
channel = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 2) SFrames.castdb (UNIT_CASTEVENT via Tweaks.lua, has SpellInfo icon)
|
|
if SFrames.castdb and UnitGUID then
|
|
local guid = UnitGUID("target")
|
|
if guid then
|
|
local entry = SFrames.castdb[guid]
|
|
if entry and entry.cast and entry.start and entry.casttime then
|
|
local elapsed = GetTime() - entry.start
|
|
local duration = entry.casttime / 1000
|
|
if elapsed < duration + 0.5 then
|
|
if not cast then
|
|
cast = entry.cast
|
|
startTime = entry.start * 1000
|
|
endTime = (entry.start + duration) * 1000
|
|
channel = entry.channel
|
|
end
|
|
if not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark" then
|
|
texture = entry.icon
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 3) NanamiPlates castDB (GUID-based, UNIT_CASTEVENT with SpellInfo icon)
|
|
if NanamiPlates and NanamiPlates.castDB and UnitGUID then
|
|
local guid = UnitGUID("target")
|
|
if guid then
|
|
local entry = NanamiPlates.castDB[guid]
|
|
if entry and entry.spell and entry.startTime and entry.duration then
|
|
local elapsed = GetTime() - entry.startTime
|
|
local duration = entry.duration / 1000
|
|
if elapsed < duration + 0.5 then
|
|
if not cast then
|
|
cast = entry.spell
|
|
startTime = entry.startTime * 1000
|
|
endTime = (entry.startTime + duration) * 1000
|
|
channel = entry.channel
|
|
end
|
|
if not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark" then
|
|
texture = entry.icon
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 4) NanamiPlates castTracker (name-based, combat log)
|
|
if not cast and NanamiPlates and NanamiPlates.castTracker then
|
|
local targetName = UnitName("target")
|
|
if targetName and NanamiPlates.castTracker[targetName] then
|
|
local entries = NanamiPlates.castTracker[targetName]
|
|
if entries and entries[1] then
|
|
local entry = entries[1]
|
|
if entry.spell and entry.startTime then
|
|
local duration = (entry.duration or 2000) / 1000
|
|
local elapsed = GetTime() - entry.startTime
|
|
if elapsed < duration + 0.5 then
|
|
cast = entry.spell
|
|
texture = entry.icon
|
|
startTime = entry.startTime * 1000
|
|
endTime = (entry.startTime + duration) * 1000
|
|
channel = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 5) Local UNIT_CASTEVENT / SPELLCAST_* tracker
|
|
if targetCLCast then
|
|
local elapsed = GetTime() - targetCLCast.startTime
|
|
if elapsed < targetCLCast.duration + 0.5 then
|
|
if not cast then
|
|
cast = targetCLCast.spell
|
|
startTime = targetCLCast.startTime * 1000
|
|
endTime = (targetCLCast.startTime + targetCLCast.duration) * 1000
|
|
channel = targetCLCast.channel
|
|
end
|
|
if not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark" then
|
|
texture = targetCLCast.icon
|
|
end
|
|
else
|
|
targetCLCast = nil
|
|
end
|
|
end
|
|
|
|
if cast and startTime and endTime then
|
|
local duration = (endTime - startTime) / 1000
|
|
local cur = GetTime() - (startTime / 1000)
|
|
|
|
if channel then
|
|
cur = duration + (startTime / 1000) - GetTime()
|
|
end
|
|
|
|
if cur > duration then cur = duration end
|
|
if cur < 0 then cur = 0 end
|
|
|
|
cb:SetMinMaxValues(0, duration)
|
|
cb:SetValue(cur)
|
|
cb.text:SetText(cast)
|
|
|
|
if not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark" then
|
|
texture = GetSpellIcon(cast) or texture
|
|
end
|
|
|
|
cb:SetAlpha(1)
|
|
cb.cbbg:SetAlpha(1)
|
|
cb:Show()
|
|
cb.cbbg:Show()
|
|
|
|
if texture then
|
|
cb.icon:SetTexture(texture)
|
|
cb.icon:SetAlpha(1)
|
|
cb.ibg:SetAlpha(1)
|
|
cb.icon:Show()
|
|
cb.ibg:Show()
|
|
else
|
|
cb.icon:Hide()
|
|
cb.ibg:Hide()
|
|
end
|
|
else
|
|
cb:Hide()
|
|
cb.cbbg:Hide()
|
|
cb.icon:Hide()
|
|
cb.ibg:Hide()
|
|
end
|
|
end
|
|
|
|
-- Diagnostic Slash Command (Position Recovery)
|
|
SLASH_NANAMIDIST1 = "/nanamidist"
|
|
SlashCmdList["NANAMIDIST"] = function()
|
|
if SFrames.Target.distanceFrame then
|
|
SFrames.Target.distanceFrame:ClearAllPoints()
|
|
SFrames.Target.distanceFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
|
|
SFrames.Target.distanceFrame:Show()
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffffd100Nanami-UI:|r 距离显示已重置到屏幕中央。")
|
|
end
|
|
end
|