完成焦点等开发

This commit is contained in:
rucky
2026-03-31 18:03:23 +08:00
parent c7dd0f4848
commit 6e18269bfd
34 changed files with 6803 additions and 542 deletions

View File

@@ -129,6 +129,17 @@ function SFrames.Party:ApplyFrameStyle(frame, metrics)
frame.healthBGFrame:SetPoint("BOTTOMRIGHT", frame.health, "BOTTOMRIGHT", 1, -1)
end
local bgA = (SFramesDB and type(SFramesDB.partyBgAlpha) == "number") and SFramesDB.partyBgAlpha or 0.9
local A = SFrames.ActiveTheme
if A and A.panelBg and bgA < 0.89 then
if frame.pbg and frame.pbg.SetBackdropColor then
frame.pbg:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
if frame.healthBGFrame and frame.healthBGFrame.SetBackdropColor then
frame.healthBGFrame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
end
if frame.power then
frame.power:ClearAllPoints()
frame.power:SetPoint("TOPLEFT", frame.health, "BOTTOMLEFT", 0, -1)
@@ -302,6 +313,10 @@ function SFrames.Party:Initialize()
end)
f:SetScript("OnClick", function()
if arg1 == "LeftButton" then
if IsShiftKeyDown() and SFrames.Focus and SFrames.Focus.SetFromUnit then
pcall(SFrames.Focus.SetFromUnit, SFrames.Focus, this.unit)
return
end
if TryDropCursorOnUnit(this.unit) then
return
end
@@ -324,10 +339,12 @@ function SFrames.Party:Initialize()
end
end)
f:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
end)
f:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
f.unit = unit
@@ -376,13 +393,21 @@ function SFrames.Party:Initialize()
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:SetDrawLayer("OVERLAY", 7)
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:SetDrawLayer("OVERLAY", 7)
f.health.healPredOther:Hide()
f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOver:SetTexture(SFrames:GetTexture())
f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6)
f.health.healPredOver:SetDrawLayer("OVERLAY", 7)
f.health.healPredOver:Hide()
-- Power Bar
f.power = SFrames:CreateStatusBar(f, "SFramesPartyFrame"..i.."Power")
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
@@ -470,10 +495,14 @@ function SFrames.Party:Initialize()
pf:RegisterForClicks("LeftButtonUp", "RightButtonUp")
pf:SetScript("OnClick", function() TargetUnit(this.unit) end)
pf:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
end)
pf:SetScript("OnLeave", function() GameTooltip:Hide() end)
pf:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
pf:Hide()
f.petFrame = pf
@@ -882,14 +911,16 @@ function SFrames.Party:UpdateHealPrediction(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
if not (f.health and f.health.healPredMine and f.health.healPredOther) then return end
if not (f.health and f.health.healPredMine and f.health.healPredOther and f.health.healPredOver) then return end
local predMine = f.health.healPredMine
local predOther = f.health.healPredOther
local predOver = f.health.healPredOver
local function HidePredictions()
predMine:Hide()
predOther:Hide()
predOver:Hide()
end
if not UnitExists(unit) or not UnitIsConnected(unit) then
@@ -899,14 +930,39 @@ function SFrames.Party:UpdateHealPrediction(unit)
local hp = UnitHealth(unit) or 0
local maxHp = UnitHealthMax(unit) or 0
if maxHp <= 0 or hp >= maxHp then
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, unit)
if ok2 then
hp = realHp or hp
end
local ok3, realMaxHp = pcall(UnitHealthMax, unit)
if ok3 then
maxHp = realMaxHp or maxHp
end
end
end
if maxHp <= 0 then
HidePredictions()
return
end
local _, mineIncoming, othersIncoming = GetIncomingHeals(unit)
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, _, realMine, realOther = pcall(GetIncomingHeals, unit)
if ok2 then
mineIncoming = realMine or mineIncoming
othersIncoming = realOther or othersIncoming
end
end
end
local missing = maxHp - hp
if missing <= 0 then
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
@@ -914,29 +970,30 @@ function SFrames.Party:UpdateHealPrediction(unit)
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
if mineIncoming <= 0 and othersIncoming <= 0 then
HidePredictions()
return
end
local barWidth = f.health:GetWidth() or 0
local barWidth = f:GetWidth() - (f.portrait:GetWidth() + 4)
if barWidth <= 0 then
HidePredictions()
return
end
local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5)
local currentWidth = (hp / maxHp) * barWidth
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
if availableWidth < 0 then availableWidth = 0 end
local mineWidth = math.floor((mineShown / maxHp) * barWidth + 0.5)
local otherWidth = math.floor((otherShown / maxHp) * barWidth + 0.5)
local mineWidth = 0
local otherWidth = 0
if missing > 0 then
mineWidth = (mineShown / missing) * availableWidth
otherWidth = (otherShown / missing) * availableWidth
end
if mineWidth < 0 then mineWidth = 0 end
if otherWidth < 0 then otherWidth = 0 end
if mineWidth > availableWidth then mineWidth = availableWidth end
@@ -949,6 +1006,7 @@ function SFrames.Party:UpdateHealPrediction(unit)
predMine:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth, 0)
predMine:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth, 0)
predMine:SetWidth(mineWidth)
predMine:SetHeight(f.health:GetHeight())
predMine:Show()
else
predMine:Hide()
@@ -959,10 +1017,29 @@ function SFrames.Party:UpdateHealPrediction(unit)
predOther:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
predOther:SetWidth(otherWidth)
predOther:SetHeight(f.health:GetHeight())
predOther:Show()
else
predOther:Hide()
end
local totalIncoming = mineIncoming + othersIncoming
local overHeal = totalIncoming - missing
if overHeal > 0 then
local overWidth = (overHeal / maxHp) * barWidth
if overWidth > 0 then
predOver:ClearAllPoints()
predOver:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth + mineWidth + otherWidth, 0)
predOver:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth + mineWidth + otherWidth, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(f.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Party:UpdatePowerType(unit)

View File

@@ -270,11 +270,13 @@ function SFrames.Pet:Initialize()
end)
f:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit("pet") end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit("pet")
GameTooltip:Show()
end)
f:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
@@ -297,6 +299,25 @@ function SFrames.Pet:Initialize()
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, "ARTWORK")
f.health.healPredMine:SetTexture(SFrames:GetTexture())
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
f.health.healPredMine:SetDrawLayer("ARTWORK", 2)
f.health.healPredMine:Hide()
f.health.healPredOther = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredOther:SetTexture(SFrames:GetTexture())
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
f.health.healPredOther:SetDrawLayer("ARTWORK", 2)
f.health.healPredOther:Hide()
f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOver:SetTexture(SFrames:GetTexture())
f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6)
f.health.healPredOver:SetDrawLayer("OVERLAY", 7)
f.health.healPredOver:Hide()
-- Power Bar
f.power = SFrames:CreateStatusBar(f, "SFramesPetPower")
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
@@ -434,6 +455,128 @@ function SFrames.Pet:UpdateHealth()
else
self.frame.healthText:SetText("")
end
self:UpdateHealPrediction()
end
function SFrames.Pet:UpdateHealPrediction()
if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther and self.frame.health.healPredOver) then return end
local predMine = self.frame.health.healPredMine
local predOther = self.frame.health.healPredOther
local predOver = self.frame.health.healPredOver
local function HidePredictions()
predMine:Hide()
predOther:Hide()
predOver:Hide()
end
local hp = UnitHealth("pet") or 0
local maxHp = UnitHealthMax("pet") or 0
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, "pet")
if ok2 then hp = realHp or hp end
local ok3, realMaxHp = pcall(UnitHealthMax, "pet")
if ok3 then maxHp = realMaxHp or maxHp end
end
end
if maxHp <= 0 or UnitIsDeadOrGhost("pet") then
HidePredictions()
return
end
local totalIncoming, mineIncoming, othersIncoming = 0, 0, 0
local ok, t, m, o = pcall(function() return SFrames:GetIncomingHeals("pet") end)
if ok then
totalIncoming, mineIncoming, othersIncoming = t or 0, m or 0, o or 0
end
local missing = maxHp - hp
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 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 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
local barWidth = self.frame.health:GetWidth()
if barWidth <= 0 then
HidePredictions()
return
end
local currentWidth = (hp / maxHp) * barWidth
if currentWidth < 0 then currentWidth = 0 end
if currentWidth > barWidth then currentWidth = barWidth end
local availableWidth = barWidth - currentWidth
if availableWidth <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
local mineWidth = 0
local otherWidth = 0
if missing > 0 then
mineWidth = (mineShown / missing) * availableWidth
otherWidth = (otherShown / missing) * availableWidth
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
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:SetHeight(self.frame.health:GetHeight())
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:SetHeight(self.frame.health:GetHeight())
predOther:Show()
else
predOther:Hide()
end
local totalIncomingValue = mineIncoming + othersIncoming
local overHeal = totalIncomingValue - missing
if overHeal > 0 then
local overWidth = math.floor((overHeal / maxHp) * barWidth + 0.5)
if overWidth > 0 then
predOver:ClearAllPoints()
predOver:SetPoint("TOPLEFT", self.frame.health, "TOPRIGHT", 0, 0)
predOver:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMRIGHT", 0, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(self.frame.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Pet:UpdatePowerType()
@@ -1118,6 +1261,7 @@ function SFrames.Pet:CreateAuras()
this.timer = this.timer + arg1
if this.timer >= 0.25 then
SFrames.Pet:TickAuras()
SFrames.Pet:UpdateHealPrediction()
this.timer = 0
end
end)

View File

@@ -21,31 +21,7 @@ local function GetChineseClassName(classToken, localizedClass)
end
local function GetIncomingHeals(unit)
if not (ShaguTweaks and ShaguTweaks.libpredict and ShaguTweaks.libpredict.UnitGetIncomingHeals) then
return 0, 0, 0
end
local libpredict = ShaguTweaks.libpredict
if libpredict.UnitGetIncomingHealsBreakdown then
local ok, total, mine, others = pcall(function()
return libpredict:UnitGetIncomingHealsBreakdown(unit, UnitName("player"))
end)
if ok then
total = math.max(0, tonumber(total) or 0)
mine = math.max(0, tonumber(mine) or 0)
others = math.max(0, tonumber(others) or 0)
return total, mine, others
end
end
local ok, amount = pcall(function()
return libpredict:UnitGetIncomingHeals(unit)
end)
if not ok then return 0, 0, 0 end
amount = tonumber(amount) or 0
if amount < 0 then amount = 0 end
return amount, 0, amount
return SFrames:GetIncomingHeals(unit)
end
local function Clamp(value, minValue, maxValue)
@@ -116,6 +92,15 @@ function SFrames.Player:ApplyConfig()
f:SetHeight(cfg.height)
f:SetAlpha(frameAlpha)
local bgA = tonumber(db.playerBgAlpha) or 0.9
local _A = SFrames.ActiveTheme
if _A and _A.panelBg and bgA < 0.89 then
if f.SetBackdropColor then f:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
if f.healthBGFrame and f.healthBGFrame.SetBackdropColor then f.healthBGFrame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
if f.powerBGFrame and f.powerBGFrame.SetBackdropColor then f.powerBGFrame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
if f.portraitBG and f.portraitBG.SetBackdropColor then f.portraitBG:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
end
if showPortrait then
if f.portrait then
f.portrait:SetWidth(cfg.portraitWidth)
@@ -295,15 +280,23 @@ function SFrames.Player:Initialize()
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 = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredMine:SetTexture(SFrames:GetTexture())
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
f.health.healPredMine:SetDrawLayer("ARTWORK", 2)
f.health.healPredMine:Hide()
f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOther = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredOther:SetTexture(SFrames:GetTexture())
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
f.health.healPredOther:SetDrawLayer("ARTWORK", 2)
f.health.healPredOther:Hide()
f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOver:SetTexture(SFrames:GetTexture())
f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6)
f.health.healPredOver:SetDrawLayer("OVERLAY", 7)
f.health.healPredOver:Hide()
-- Power Bar
f.power = SFrames:CreateStatusBar(f, "SFramesPlayerPower")
@@ -524,11 +517,13 @@ function SFrames.Player:Initialize()
f.unit = "player"
f:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
GameTooltip:Show()
end)
f:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
end
@@ -1094,6 +1089,21 @@ end
function SFrames.Player:UpdateHealth()
local hp = UnitHealth("player")
local maxHp = UnitHealthMax("player")
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, "player")
if ok2 then
hp = realHp or hp
end
local ok3, realMaxHp = pcall(UnitHealthMax, "player")
if ok3 then
maxHp = realMaxHp or maxHp
end
end
end
self.frame.health:SetMinMaxValues(0, maxHp)
self.frame.health:SetValue(hp)
@@ -1107,25 +1117,47 @@ function SFrames.Player:UpdateHealth()
end
function SFrames.Player:UpdateHealPrediction()
if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther) then return end
if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther and self.frame.health.healPredOver) then return end
local predMine = self.frame.health.healPredMine
local predOther = self.frame.health.healPredOther
local predOver = self.frame.health.healPredOver
local function HidePredictions()
predMine:Hide()
predOther:Hide()
predOver:Hide()
end
local hp = UnitHealth("player") or 0
local maxHp = UnitHealthMax("player") or 0
if maxHp <= 0 or hp >= maxHp or UnitIsDeadOrGhost("player") then
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, "player")
if ok2 then
hp = realHp or hp
end
local ok3, realMaxHp = pcall(UnitHealthMax, "player")
if ok3 then
maxHp = realMaxHp or maxHp
end
end
end
if maxHp <= 0 or UnitIsDeadOrGhost("player") then
HidePredictions()
return
end
local _, mineIncoming, othersIncoming = GetIncomingHeals("player")
local totalIncoming, mineIncoming, othersIncoming = 0, 0, 0
local ok, t, m, o = pcall(function() return GetIncomingHeals("player") end)
if ok then
totalIncoming, mineIncoming, othersIncoming = t or 0, m or 0, o or 0
end
local missing = maxHp - hp
if missing <= 0 then
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
@@ -1133,34 +1165,39 @@ function SFrames.Player:UpdateHealPrediction()
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
if mineShown <= 0 and otherShown <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
local barWidth = self.frame.health:GetWidth() or 0
local showPortrait = SFramesDB and SFramesDB.playerShowPortrait ~= false
local barWidth = self.frame:GetWidth() - (showPortrait and (self.frame.portrait:GetWidth() + 2) or 2)
if barWidth <= 0 then
HidePredictions()
return
end
local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5)
local currentWidth = (hp / maxHp) * barWidth
if currentWidth < 0 then currentWidth = 0 end
if currentWidth > barWidth then currentWidth = barWidth end
local availableWidth = barWidth - currentWidth
if availableWidth <= 0 then
if availableWidth <= 0 and (mineIncoming <= 0 and othersIncoming <= 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
local mineWidth = 0
local otherWidth = 0
if missing > 0 then
mineWidth = (mineShown / missing) * availableWidth
otherWidth = (otherShown / missing) * availableWidth
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
end
if mineWidth > 0 then
@@ -1168,6 +1205,7 @@ function SFrames.Player:UpdateHealPrediction()
predMine:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth, 0)
predMine:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth, 0)
predMine:SetWidth(mineWidth)
predMine:SetHeight(self.frame.health:GetHeight())
predMine:Show()
else
predMine:Hide()
@@ -1178,10 +1216,29 @@ function SFrames.Player:UpdateHealPrediction()
predOther:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
predOther:SetWidth(otherWidth)
predOther:SetHeight(self.frame.health:GetHeight())
predOther:Show()
else
predOther:Hide()
end
local totalIncomingValue = mineIncoming + othersIncoming
local overHeal = totalIncomingValue - missing
if overHeal > 0 then
local overWidth = math.floor((overHeal / maxHp) * barWidth + 0.5)
if overWidth > 0 then
predOver:ClearAllPoints()
predOver:SetPoint("TOPLEFT", self.frame.health, "TOPRIGHT", 0, 0)
predOver:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMRIGHT", 0, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(self.frame.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Player:UpdatePowerType()
@@ -1426,7 +1483,7 @@ end
--------------------------------------------------------------------------------
function SFrames.Player:CreateAuras()
-- Create 16 Buff Slots
-- Create 32 Buff Slots
self.frame.buffs = {}
self.frame.debuffs = {}
local size = 24
@@ -1434,7 +1491,7 @@ function SFrames.Player:CreateAuras()
local rowSpacing = 1
local buffsPerRow = 9
for i = 1, 16 do
for i = 1, 32 do
local b = CreateFrame("Button", "SFramesPlayerBuff"..i, self.frame)
b:SetWidth(size)
b:SetHeight(size)
@@ -1474,17 +1531,25 @@ end
function SFrames.Player:UpdateAuras()
local slotIdx = 0
local hasGetPlayerBuffID = SFrames.superwow_active and type(GetPlayerBuffID) == "function"
for i = 0, 31 do
local buffIndex, untilCancelled = GetPlayerBuff(i, "HELPFUL")
if buffIndex and buffIndex >= 0 then
if not SFrames:IsBuffHidden(buffIndex) then
slotIdx = slotIdx + 1
if slotIdx > 16 then break end
if slotIdx > 32 then break end
local b = self.frame.buffs[slotIdx]
local texture = GetPlayerBuffTexture(buffIndex)
if texture then
b.icon:SetTexture(texture)
b.buffIndex = buffIndex
-- Store aura ID when SuperWoW is available
if hasGetPlayerBuffID then
local ok, auraID = pcall(GetPlayerBuffID, buffIndex)
b.auraID = ok and auraID or nil
else
b.auraID = nil
end
b:Show()
local timeLeft = GetPlayerBuffTimeLeft(buffIndex)
@@ -1499,8 +1564,9 @@ function SFrames.Player:UpdateAuras()
end
end
end
for j = slotIdx + 1, 16 do
for j = slotIdx + 1, 32 do
self.frame.buffs[j]:Hide()
self.frame.buffs[j].auraID = nil
end
end
@@ -1544,6 +1610,63 @@ end
-- Player Castbar
--------------------------------------------------------------------------------
local function GetLatencySeconds()
if GetNetStats then
local _, _, latency = GetNetStats()
if latency and latency > 0 then return latency / 1000 end
end
return 0
end
local function HSVtoRGB(h, s, v)
local i = math.floor(h * 6)
local f = h * 6 - i
local p = v * (1 - s)
local q = v * (1 - f * s)
local t = v * (1 - (1 - f) * s)
local m = math.mod(i, 6)
if m == 0 then return v, t, p
elseif m == 1 then return q, v, p
elseif m == 2 then return p, v, t
elseif m == 3 then return p, q, v
elseif m == 4 then return t, p, v
else return v, p, q end
end
local RAINBOW_TEX_PATH = "Interface\\AddOns\\Nanami-UI\\img\\progress"
local RAINBOW_SEG_COORDS = {
{40/512, 473/512, 45/512, 169/512},
{40/512, 473/512, 194/512, 318/512},
{40/512, 473/512, 343/512, 467/512},
}
local function UpdateRainbowProgress(cb, progress)
if not cb.rainbowSegs then return end
local barWidth = cb:GetWidth()
if barWidth <= 0 then return end
local fillW = progress * barWidth
for i = 1, 3 do
local seg = cb.rainbowSegs[i]
local segL = barWidth * (i - 1) / 3
local segR = barWidth * i / 3
if fillW <= segL then
seg:Hide()
else
local visR = math.min(fillW, segR)
local frac = (visR - segL) / (segR - segL)
if visR >= segR and i < 3 then
visR = segR + 2
end
seg:ClearAllPoints()
seg:SetPoint("TOPLEFT", cb, "TOPLEFT", segL, 0)
seg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMLEFT", visR, 0)
local c = seg.fullCoords
seg:SetTexCoord(c[1], c[1] + (c[2] - c[1]) * frac, c[3], c[4])
seg:Show()
end
end
end
function SFrames.Player:CreateCastbar()
local cb = SFrames:CreateStatusBar(self.frame, "SFramesPlayerCastbar")
cb:SetHeight(SFrames.Config.castbarHeight)
@@ -1581,6 +1704,23 @@ function SFrames.Player:CreateCastbar()
ibg:SetFrameLevel(cb:GetFrameLevel() - 1)
SFrames:CreateUnitBackdrop(ibg)
local lagTex = cb:CreateTexture(nil, "OVERLAY")
lagTex:SetTexture(SFrames:GetTexture())
lagTex:SetVertexColor(1, 0.2, 0.2, 0.5)
lagTex:Hide()
cb.lagTex = lagTex
cb.rainbowSegs = {}
for i = 1, 3 do
local tex = cb:CreateTexture(nil, "ARTWORK")
tex:SetTexture(RAINBOW_TEX_PATH)
local c = RAINBOW_SEG_COORDS[i]
tex:SetTexCoord(c[1], c[2], c[3], c[4])
tex.fullCoords = c
tex:Hide()
cb.rainbowSegs[i] = tex
end
cb:Hide()
cbbg:Hide()
cb.icon:Hide()
@@ -1591,7 +1731,9 @@ function SFrames.Player:CreateCastbar()
self.frame.castbar.ibg = ibg
cb:SetScript("OnUpdate", function() SFrames.Player:CastbarOnUpdate() end)
self:ApplyCastbarPosition()
-- Hook events
SFrames:RegisterEvent("SPELLCAST_START", function() self:CastbarStart(arg1, arg2) end)
SFrames:RegisterEvent("SPELLCAST_STOP", function() self:CastbarStop() end)
@@ -1603,6 +1745,60 @@ function SFrames.Player:CreateCastbar()
SFrames:RegisterEvent("SPELLCAST_CHANNEL_STOP", function() self:CastbarStop() end)
end
function SFrames.Player:ApplyCastbarPosition()
local cb = self.frame.castbar
if not cb then return end
local db = SFramesDB or {}
cb:ClearAllPoints()
cb.cbbg:ClearAllPoints()
cb.icon:ClearAllPoints()
cb.ibg:ClearAllPoints()
if db.castbarStandalone then
cb:SetParent(UIParent)
cb.cbbg:SetParent(UIParent)
cb.ibg:SetParent(UIParent)
cb:SetWidth(280)
cb:SetHeight(20)
cb:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 120)
cb:SetFrameStrata("HIGH")
cb.cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
cb.cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
cb.cbbg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
cb.icon:SetWidth(22)
cb.icon:SetHeight(22)
cb.icon:SetPoint("RIGHT", cb, "LEFT", -4, 0)
cb.ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
cb.ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
cb.ibg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
if SFrames.Movers and SFrames.Movers.RegisterMover then
SFrames.Movers:RegisterMover("PlayerCastbar", cb, "施法条",
"BOTTOM", "UIParent", "BOTTOM", 0, 120)
end
else
cb:SetParent(self.frame)
cb.cbbg:SetParent(self.frame)
cb.ibg:SetParent(self.frame)
local cbH = SFrames.Config.castbarHeight
cb:SetWidth(0) -- 清除独立模式的显式宽度,让双锚点自动计算
cb:SetHeight(cbH)
cb:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", 0, 6)
cb:SetPoint("BOTTOMLEFT", self.frame.portrait, "TOPLEFT", cbH + 6, 6)
cb:SetFrameStrata("MEDIUM")
cb.cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
cb.cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
cb.cbbg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
cb.icon:SetWidth(cbH + 2)
cb.icon:SetHeight(cbH + 2)
cb.icon:SetPoint("RIGHT", cb, "LEFT", -4, 0)
cb.ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
cb.ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
cb.ibg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
end
end
function SFrames.Player:CastbarStart(spellName, duration)
local cb = self.frame.castbar
cb.casting = true
@@ -1648,6 +1844,23 @@ function SFrames.Player:CastbarStart(spellName, duration)
end
cb:Show()
cb.cbbg:Show()
local lag = GetLatencySeconds()
if lag > 0 and cb.maxValue > 0 then
local barW = cb:GetWidth()
local lagW = math.min((lag / cb.maxValue) * barW, barW * 0.5)
if lagW >= 1 then
cb.lagTex:ClearAllPoints()
cb.lagTex:SetPoint("TOPRIGHT", cb, "TOPRIGHT", 0, 0)
cb.lagTex:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 0, 0)
cb.lagTex:SetWidth(lagW)
cb.lagTex:Show()
else
cb.lagTex:Hide()
end
else
cb.lagTex:Hide()
end
end
function SFrames.Player:CastbarChannelStart(duration, spellName)
@@ -1696,6 +1909,23 @@ function SFrames.Player:CastbarChannelStart(duration, spellName)
end
cb:Show()
cb.cbbg:Show()
local lag = GetLatencySeconds()
if lag > 0 and cb.maxValue > 0 then
local barW = cb:GetWidth()
local lagW = math.min((lag / cb.maxValue) * barW, barW * 0.5)
if lagW >= 1 then
cb.lagTex:ClearAllPoints()
cb.lagTex:SetPoint("TOPLEFT", cb, "TOPLEFT", 0, 0)
cb.lagTex:SetPoint("BOTTOMLEFT", cb, "BOTTOMLEFT", 0, 0)
cb.lagTex:SetWidth(lagW)
cb.lagTex:Show()
else
cb.lagTex:Hide()
end
else
cb.lagTex:Hide()
end
end
function SFrames.Player:CastbarStop()
@@ -1703,7 +1933,7 @@ function SFrames.Player:CastbarStop()
cb.casting = nil
cb.channeling = nil
cb.fadeOut = true
-- keep showing for a short fade out
if cb.lagTex then cb.lagTex:Hide() end
end
function SFrames.Player:CastbarDelayed(delay)
@@ -1714,18 +1944,17 @@ function SFrames.Player:CastbarDelayed(delay)
end
end
function SFrames.Player:CastbarChannelUpdate(delay)
function SFrames.Player:CastbarChannelUpdate(remainingMs)
local cb = self.frame.castbar
if cb.channeling then
local add = delay / 1000
cb.maxValue = cb.maxValue + add
cb.endTime = cb.endTime + add
cb:SetMinMaxValues(0, cb.maxValue)
cb.endTime = GetTime() + remainingMs / 1000
end
end
function SFrames.Player:CastbarOnUpdate()
local cb = self.frame.castbar
local db = SFramesDB or {}
if cb.casting then
local elapsed = GetTime() - cb.startTime
if elapsed >= cb.maxValue then
@@ -1736,6 +1965,19 @@ function SFrames.Player:CastbarOnUpdate()
end
cb:SetValue(elapsed)
cb.time:SetText(string.format("%.1f", math.max(cb.maxValue - elapsed, 0)))
if db.castbarRainbow then
if not cb.rainbowActive then
cb:SetStatusBarColor(0, 0, 0, 0)
cb.rainbowActive = true
end
UpdateRainbowProgress(cb, elapsed / cb.maxValue)
elseif cb.rainbowActive then
cb:SetStatusBarColor(1, 0.7, 0)
if cb.rainbowSegs then
for i = 1, 3 do cb.rainbowSegs[i]:Hide() end
end
cb.rainbowActive = nil
end
if not cb.icon:IsShown() then
self:CastbarTryResolveIcon()
end
@@ -1749,10 +1991,28 @@ function SFrames.Player:CastbarOnUpdate()
end
cb:SetValue(timeRemaining)
cb.time:SetText(string.format("%.1f", timeRemaining))
if db.castbarRainbow then
if not cb.rainbowActive then
cb:SetStatusBarColor(0, 0, 0, 0)
cb.rainbowActive = true
end
UpdateRainbowProgress(cb, timeRemaining / cb.maxValue)
elseif cb.rainbowActive then
cb:SetStatusBarColor(1, 0.7, 0)
if cb.rainbowSegs then
for i = 1, 3 do cb.rainbowSegs[i]:Hide() end
end
cb.rainbowActive = nil
end
if not cb.icon:IsShown() then
self:CastbarTryResolveIcon()
end
elseif cb.fadeOut then
if cb.rainbowActive then
for i = 1, 3 do cb.rainbowSegs[i]:Hide() end
cb:SetStatusBarColor(1, 0.7, 0)
cb.rainbowActive = nil
end
local alpha = cb:GetAlpha() - 0.05
if alpha > 0 then
cb:SetAlpha(alpha)

View File

@@ -71,6 +71,17 @@ function SFrames.Raid:ApplyFrameStyle(frame, metrics)
frame.healthBGFrame:SetPoint("BOTTOMRIGHT", frame.health, "BOTTOMRIGHT", 1, -1)
end
local bgA = (SFramesDB and type(SFramesDB.raidBgAlpha) == "number") and SFramesDB.raidBgAlpha or 0.9
local A = SFrames.ActiveTheme
if A and A.panelBg and bgA < 0.89 then
if frame.healthBGFrame and frame.healthBGFrame.SetBackdropColor then
frame.healthBGFrame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
if frame.powerBGFrame and frame.powerBGFrame.SetBackdropColor then
frame.powerBGFrame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
end
if frame.power then
if metrics.showPower then
frame.power:Show()
@@ -290,6 +301,10 @@ function SFrames.Raid:EnsureFrames()
end)
f:SetScript("OnClick", function()
if arg1 == "LeftButton" then
if IsShiftKeyDown() and SFrames.Focus and SFrames.Focus.SetFromUnit then
pcall(SFrames.Focus.SetFromUnit, SFrames.Focus, this.unit)
return
end
if TryDropCursorOnUnit(this.unit) then return end
if SpellIsTargeting and SpellIsTargeting() then
SpellTargetUnit(this.unit)
@@ -375,10 +390,14 @@ function SFrames.Raid:EnsureFrames()
end
end)
f:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
end)
f:SetScript("OnLeave", function() GameTooltip:Hide() end)
f:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
f.unit = unit
-- Health Bar
@@ -395,16 +414,24 @@ function SFrames.Raid:EnsureFrames()
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 = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredMine:SetTexture(SFrames:GetTexture())
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
f.health.healPredMine:SetDrawLayer("ARTWORK", 2)
f.health.healPredMine:Hide()
f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOther = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredOther:SetTexture(SFrames:GetTexture())
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
f.health.healPredOther:SetDrawLayer("ARTWORK", 2)
f.health.healPredOther:Hide()
f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOver:SetTexture(SFrames:GetTexture())
f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6)
f.health.healPredOver:SetDrawLayer("OVERLAY", 7)
f.health.healPredOver:Hide()
-- Power Bar
f.power = SFrames:CreateStatusBar(f, "SFramesRaidFrame"..i.."Power")
f.power:SetMinMaxValues(0, 100)
@@ -793,14 +820,16 @@ function SFrames.Raid:UpdateHealPrediction(unit)
if not frameData then return end
local f = frameData.frame
if not (f.health and f.health.healPredMine and f.health.healPredOther) then return end
if not (f.health and f.health.healPredMine and f.health.healPredOther and f.health.healPredOver) then return end
local predMine = f.health.healPredMine
local predOther = f.health.healPredOther
local predOver = f.health.healPredOver
local function HidePredictions()
predMine:Hide()
predOther:Hide()
predOver:Hide()
end
if not UnitExists(unit) or not UnitIsConnected(unit) then
@@ -810,14 +839,39 @@ function SFrames.Raid:UpdateHealPrediction(unit)
local hp = UnitHealth(unit) or 0
local maxHp = UnitHealthMax(unit) or 0
if maxHp <= 0 or hp >= maxHp then
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, unit)
if ok2 then
hp = realHp or hp
end
local ok3, realMaxHp = pcall(UnitHealthMax, unit)
if ok3 then
maxHp = realMaxHp or maxHp
end
end
end
if maxHp <= 0 then
HidePredictions()
return
end
local _, mineIncoming, othersIncoming = GetIncomingHeals(unit)
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, _, realMine, realOther = pcall(GetIncomingHeals, unit)
if ok2 then
mineIncoming = realMine or mineIncoming
othersIncoming = realOther or othersIncoming
end
end
end
local missing = maxHp - hp
if missing <= 0 then
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
@@ -825,41 +879,46 @@ function SFrames.Raid:UpdateHealPrediction(unit)
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
if mineIncoming <= 0 and othersIncoming <= 0 then
HidePredictions()
return
end
local barWidth = f.health:GetWidth() or 0
local barWidth = f:GetWidth() - 2
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 currentPosition = (hp / maxHp) * barWidth
if currentPosition < 0 then currentPosition = 0 end
if currentPosition > barWidth then currentPosition = barWidth end
local availableWidth = barWidth - currentWidth
if availableWidth <= 0 then
local availableWidth = barWidth - currentPosition
if availableWidth <= 0 and (mineIncoming <= 0 and othersIncoming <= 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
local mineWidth = 0
local otherWidth = 0
if missing > 0 then
mineWidth = (mineShown / missing) * availableWidth
otherWidth = (otherShown / missing) * availableWidth
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
end
if mineWidth > 0 then
predMine:ClearAllPoints()
predMine:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth, 0)
predMine:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth, 0)
predMine:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentPosition, 0)
predMine:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentPosition, 0)
predMine:SetWidth(mineWidth)
predMine:SetHeight(f.health:GetHeight())
predMine:Show()
else
predMine:Hide()
@@ -867,13 +926,32 @@ function SFrames.Raid:UpdateHealPrediction(unit)
if otherWidth > 0 then
predOther:ClearAllPoints()
predOther:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
predOther:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentPosition + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentPosition + mineWidth, 0)
predOther:SetWidth(otherWidth)
predOther:SetHeight(f.health:GetHeight())
predOther:Show()
else
predOther:Hide()
end
local totalIncoming = mineIncoming + othersIncoming
local overHeal = totalIncoming - missing
if overHeal > 0 then
local overWidth = (overHeal / maxHp) * barWidth
if overWidth > 0 then
predOver:ClearAllPoints()
predOver:SetPoint("TOPLEFT", f.health, "TOPRIGHT", 0, 0)
predOver:SetPoint("BOTTOMLEFT", f.health, "BOTTOMRIGHT", 0, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(f.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Raid:UpdatePower(unit)
@@ -1016,15 +1094,51 @@ function SFrames.Raid:UpdateAuras(unit)
return false
end
-- Helper: get buff name via SuperWoW aura ID (fast) or tooltip scan (fallback)
local hasSuperWoW = SFrames.superwow_active and SpellInfo
local function GetBuffName(unit, index)
if hasSuperWoW then
local texture, auraID = UnitBuff(unit, index)
if auraID and SpellInfo then
local spellName = SpellInfo(auraID)
if spellName and spellName ~= "" then
return spellName, texture
end
end
end
-- Fallback: tooltip scan
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetUnitBuff(unit, index)
local buffName = SFramesScanTooltipTextLeft1:GetText()
SFrames.Tooltip:Hide()
return buffName, UnitBuff(unit, index)
end
local function GetDebuffName(unit, index)
if hasSuperWoW then
local texture, count, dtype, auraID = UnitDebuff(unit, index)
if auraID and SpellInfo then
local spellName = SpellInfo(auraID)
if spellName and spellName ~= "" then
return spellName, texture, count, dtype
end
end
end
-- Fallback: tooltip scan
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetUnitDebuff(unit, index)
local debuffName = SFramesScanTooltipTextLeft1:GetText()
SFrames.Tooltip:Hide()
local texture, count, dtype = UnitDebuff(unit, index)
return debuffName, texture, count, dtype
end
-- Check Buffs
for i = 1, 32 do
local texture, applications = UnitBuff(unit, i)
if not texture then break end
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetUnitBuff(unit, i)
local buffName = SFramesScanTooltipTextLeft1:GetText()
SFrames.Tooltip:Hide()
local buffName = GetBuffName(unit, i)
if buffName then
for pos, listData in pairs(buffsNeeded) do
@@ -1058,10 +1172,7 @@ function SFrames.Raid:UpdateAuras(unit)
end
end
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetUnitDebuff(unit, i)
local debuffName = SFramesScanTooltipTextLeft1:GetText()
SFrames.Tooltip:Hide()
local debuffName = GetDebuffName(unit, i)
if debuffName then
for pos, listData in pairs(buffsNeeded) do

View File

@@ -186,13 +186,30 @@ local DIST_BASE_WIDTH = 80
local DIST_BASE_HEIGHT = 24
local DIST_BASE_FONTSIZE = 14
-- Check if UnitXP SP3 distance API is available
local hasUnitXP = nil -- nil = not checked yet, true/false = cached result
local function IsUnitXPAvailable()
if hasUnitXP ~= nil then return hasUnitXP end
hasUnitXP = (type(UnitXP) == "function") and (pcall(UnitXP, "nop", "nop"))
return hasUnitXP
end
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
-- Prefer UnitXP precise distance when available
if IsUnitXPAvailable() then
local ok, dist = pcall(UnitXP, "distanceBetween", "player", unit)
if ok and type(dist) == "number" and dist >= 0 then
return string.format("%.1f 码", dist)
end
end
-- Fallback: CheckInteractDistance rough ranges
if CheckInteractDistance(unit, 2) then return "< 8 码"
elseif CheckInteractDistance(unit, 3) then return "8-10 码"
elseif CheckInteractDistance(unit, 4) then return "10-28 码"
elseif UnitIsVisible(unit) then return "28-100 码"
else return "> 100 码" end
end
@@ -255,6 +272,15 @@ function SFrames.Target:ApplyConfig()
f:SetHeight(cfg.height)
f:SetAlpha(frameAlpha)
local bgA = tonumber(db.targetBgAlpha) or 0.9
local _A = SFrames.ActiveTheme
if _A and _A.panelBg and bgA < 0.89 then
if f.SetBackdropColor then f:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
if f.healthBGFrame and f.healthBGFrame.SetBackdropColor then f.healthBGFrame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
if f.powerBGFrame and f.powerBGFrame.SetBackdropColor then f.powerBGFrame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
if f.portraitBG and f.portraitBG.SetBackdropColor then f.portraitBG:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
end
if showPortrait then
if f.portrait then
f.portrait:SetWidth(cfg.portraitWidth)
@@ -362,7 +388,13 @@ function SFrames.Target:ApplyDistanceScale(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))
local customSize = SFramesDB and tonumber(SFramesDB.targetDistanceFontSize)
local fontSize
if customSize and customSize >= 8 and customSize <= 24 then
fontSize = math.floor(customSize + 0.5)
else
fontSize = math.max(8, math.floor(DIST_BASE_FONTSIZE * scale + 0.5))
end
f.text:SetFont(fontPath, fontSize, outline)
end
end
@@ -405,6 +437,13 @@ function SFrames.Target:InitializeDistanceFrame()
f.text:SetShadowColor(0, 0, 0, 1)
f.text:SetShadowOffset(1, -1)
-- Behind indicator text (shown next to distance)
f.behindText = SFrames:CreateFontString(f, fontSize, "LEFT")
f.behindText:SetPoint("LEFT", f.text, "RIGHT", 4, 0)
f.behindText:SetShadowColor(0, 0, 0, 1)
f.behindText:SetShadowOffset(1, -1)
f.behindText:Hide()
SFrames.Target.distanceFrame = f
f:Hide()
@@ -416,6 +455,7 @@ function SFrames.Target:InitializeDistanceFrame()
end
if not UnitExists("target") then
if this:IsShown() then this:Hide() end
if this.behindText then this.behindText:Hide() end
return
end
this.timer = this.timer + (arg1 or 0)
@@ -424,6 +464,28 @@ function SFrames.Target:InitializeDistanceFrame()
local dist = SFrames.Target:GetDistance("target")
this.text:SetText(dist or "---")
if not this:IsShown() then this:Show() end
-- Behind indicator
if this.behindText then
local showBehind = not SFramesDB or SFramesDB.Tweaks == nil
or SFramesDB.Tweaks.behindIndicator ~= false
if showBehind and IsUnitXPAvailable() then
local ok, isBehind = pcall(UnitXP, "behind", "player", "target")
if ok and isBehind then
this.behindText:SetText("背后")
this.behindText:SetTextColor(0.2, 1.0, 0.3)
this.behindText:Show()
elseif ok then
this.behindText:SetText("正面")
this.behindText:SetTextColor(1.0, 0.35, 0.3)
this.behindText:Show()
else
this.behindText:Hide()
end
else
this.behindText:Hide()
end
end
end
end)
end
@@ -476,7 +538,23 @@ function SFrames.Target:Initialize()
f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
f:SetScript("OnClick", function()
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] OnClick fired: " .. tostring(arg1) .. "|r")
if arg1 == "LeftButton" then
-- Shift+左键 = 设为焦点
if IsShiftKeyDown() then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] Shift+LeftButton -> SetFocus|r")
if SFrames.Focus and SFrames.Focus.SetFromTarget then
local ok, err = pcall(SFrames.Focus.SetFromTarget, SFrames.Focus)
if ok then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] Focus set OK|r")
else
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] Focus error: " .. tostring(err) .. "|r")
end
else
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] SFrames.Focus missing!|r")
end
return
end
if TryDropCursorOnUnit(this.unit) then
return
end
@@ -484,15 +562,132 @@ function SFrames.Target:Initialize()
SpellTargetUnit(this.unit)
end
elseif arg1 == "RightButton" then
if SpellIsTargeting() then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] RightButton hit|r")
if SpellIsTargeting and 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)
if not UnitExists("target") then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] No target, abort|r")
return
end
if not SFrames.Target.dropDown then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] Creating dropdown...|r")
local ok1, err1 = pcall(function()
SFrames.Target.dropDown = CreateFrame("Frame", "SFramesTargetDropDown", UIParent, "UIDropDownMenuTemplate")
end)
if not ok1 then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] CreateFrame failed: " .. tostring(err1) .. "|r")
return
end
SFrames.Target.dropDown.displayMode = "MENU"
SFrames.Target.dropDown.initialize = function()
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] initialize() called|r")
local dd = SFrames.Target.dropDown
local name = dd.targetName
if not name then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] initialize: no targetName|r")
return
end
local info = {}
info.text = name
info.isTitle = 1
info.notCheckable = 1
UIDropDownMenu_AddButton(info)
if UnitIsPlayer("target") and UnitIsFriend("player", "target") and not UnitIsUnit("target", "player") then
-- 悄悄话
info = {}
info.text = "悄悄话"
info.notCheckable = 1
info.func = function() ChatFrame_SendTell(name) end
UIDropDownMenu_AddButton(info)
-- 组队相关(动态)
local inParty = UnitInParty("target")
local isLeader = IsPartyLeader()
if inParty and isLeader then
info = {}
info.text = "提升为队长"
info.notCheckable = 1
info.func = function() PromoteToLeader(name) end
UIDropDownMenu_AddButton(info)
info = {}
info.text = "取消邀请"
info.notCheckable = 1
info.func = function() UninviteByName(name) end
UIDropDownMenu_AddButton(info)
end
-- 观察
info = {}
info.text = "观察"
info.notCheckable = 1
info.func = function() InspectUnit("target") end
UIDropDownMenu_AddButton(info)
-- 交易
info = {}
info.text = "交易"
info.notCheckable = 1
info.func = function() InitiateTrade("target") end
UIDropDownMenu_AddButton(info)
-- 邀请组队(不在队伍中时显示)
if not inParty then
info = {}
info.text = "邀请组队"
info.notCheckable = 1
info.func = function() InviteByName(name) end
UIDropDownMenu_AddButton(info)
end
-- 跟随
info = {}
info.text = "跟随"
info.notCheckable = 1
info.func = function() FollowUnit("target") end
UIDropDownMenu_AddButton(info)
-- 决斗(不在队伍中时显示,节省按钮数)
if not inParty then
info = {}
info.text = "决斗"
info.notCheckable = 1
info.func = function() StartDuel("target") end
UIDropDownMenu_AddButton(info)
end
end
if SFrames.Focus and SFrames.Focus.GetFocusName then
info = {}
local ok2, curFocus = pcall(SFrames.Focus.GetFocusName, SFrames.Focus)
local isSameTarget = ok2 and curFocus and curFocus == name
if isSameTarget then
info.text = "取消焦点"
info.func = function() pcall(SFrames.Focus.Clear, SFrames.Focus) end
else
info.text = "设为焦点"
info.func = function() pcall(SFrames.Focus.SetFromTarget, SFrames.Focus) end
end
info.notCheckable = 1
UIDropDownMenu_AddButton(info)
end
-- 取消按钮不添加,点击菜单外部即可关闭(节省按钮位)
end
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] Dropdown created OK|r")
end
SFrames.Target.dropDown.targetName = UnitName("target")
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] Calling ToggleDropDownMenu...|r")
local ok3, err3 = pcall(ToggleDropDownMenu, 1, nil, SFrames.Target.dropDown, "cursor")
if not ok3 then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami] ToggleDropDownMenu failed: " .. tostring(err3) .. "|r")
else
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[Nanami] ToggleDropDownMenu OK|r")
end
end
end)
f:SetScript("OnReceiveDrag", function()
@@ -540,15 +735,23 @@ function SFrames.Target:Initialize()
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 = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredMine:SetTexture(SFrames:GetTexture())
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
f.health.healPredMine:SetDrawLayer("ARTWORK", 2)
f.health.healPredMine:Hide()
f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOther = f.health:CreateTexture(nil, "ARTWORK")
f.health.healPredOther:SetTexture(SFrames:GetTexture())
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
f.health.healPredOther:SetDrawLayer("ARTWORK", 2)
f.health.healPredOther:Hide()
f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOver:SetTexture(SFrames:GetTexture())
f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6)
f.health.healPredOver:SetDrawLayer("OVERLAY", 7)
f.health.healPredOver:Hide()
-- Power Bar
f.power = SFrames:CreateStatusBar(f, "SFramesTargetPower")
@@ -645,11 +848,13 @@ function SFrames.Target:Initialize()
f.unit = "target"
f:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
GameTooltip:Show()
end)
f:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
@@ -1012,13 +1217,15 @@ function SFrames.Target:UpdateHealth()
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
if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther and self.frame.health.healPredOver) then return end
local predMine = self.frame.health.healPredMine
local predOther = self.frame.health.healPredOther
local predOver = self.frame.health.healPredOver
local function HidePredictions()
predMine:Hide()
predOther:Hide()
predOver:Hide()
end
if not UnitExists("target") then
@@ -1028,14 +1235,39 @@ function SFrames.Target:UpdateHealPrediction()
local hp = UnitHealth("target") or 0
local maxHp = UnitHealthMax("target") or 0
if maxHp <= 0 or hp >= maxHp then
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, "target")
if ok2 then
hp = realHp or hp
end
local ok3, realMaxHp = pcall(UnitHealthMax, "target")
if ok3 then
maxHp = realMaxHp or maxHp
end
end
end
if maxHp <= 0 then
HidePredictions()
return
end
local _, mineIncoming, othersIncoming = GetIncomingHeals("target")
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, _, realMine, realOther = pcall(GetIncomingHeals, "target")
if ok2 then
mineIncoming = realMine or mineIncoming
othersIncoming = realOther or othersIncoming
end
end
end
local missing = maxHp - hp
if missing <= 0 then
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
@@ -1043,34 +1275,39 @@ function SFrames.Target:UpdateHealPrediction()
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
if mineShown <= 0 and otherShown <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
HidePredictions()
return
end
local barWidth = self.frame.health:GetWidth() or 0
local showPortrait = SFramesDB and SFramesDB.targetShowPortrait ~= false
local barWidth = self.frame:GetWidth() - (showPortrait and (self.frame.portrait:GetWidth() + 2) or 2)
if barWidth <= 0 then
HidePredictions()
return
end
local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5)
local currentWidth = (hp / maxHp) * barWidth
if currentWidth < 0 then currentWidth = 0 end
if currentWidth > barWidth then currentWidth = barWidth end
local availableWidth = barWidth - currentWidth
if availableWidth <= 0 then
if availableWidth <= 0 and (mineIncoming <= 0 and othersIncoming <= 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
local mineWidth = 0
local otherWidth = 0
if missing > 0 then
mineWidth = (mineShown / missing) * availableWidth
otherWidth = (otherShown / missing) * availableWidth
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
end
if mineWidth > 0 then
@@ -1078,6 +1315,7 @@ function SFrames.Target:UpdateHealPrediction()
predMine:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth, 0)
predMine:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth, 0)
predMine:SetWidth(mineWidth)
predMine:SetHeight(self.frame.health:GetHeight())
predMine:Show()
else
predMine:Hide()
@@ -1088,10 +1326,29 @@ function SFrames.Target:UpdateHealPrediction()
predOther:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
predOther:SetWidth(otherWidth)
predOther:SetHeight(self.frame.health:GetHeight())
predOther:Show()
else
predOther:Hide()
end
local totalIncoming = mineIncoming + othersIncoming
local overHeal = totalIncoming - missing
if overHeal > 0 then
local overWidth = math.floor((overHeal / maxHp) * barWidth + 0.5)
if overWidth > 0 then
predOver:ClearAllPoints()
predOver:SetPoint("TOPLEFT", self.frame.health, "TOPRIGHT", 0, 0)
predOver:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMRIGHT", 0, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(self.frame.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Target:UpdatePowerType()
@@ -1155,7 +1412,7 @@ function SFrames.Target:CreateAuras()
self.frame.debuffs = {}
-- Target Buffs (Top left to right)
for i = 1, 16 do
for i = 1, 32 do
local b = CreateFrame("Button", "SFramesTargetBuff"..i, self.frame)
b:SetWidth(AURA_SIZE)
b:SetHeight(AURA_SIZE)
@@ -1191,7 +1448,7 @@ function SFrames.Target:CreateAuras()
end
-- Target Debuffs (Bottom left to right)
for i = 1, 16 do
for i = 1, 32 do
local b = CreateFrame("Button", "SFramesTargetDebuff"..i, self.frame)
b:SetWidth(AURA_SIZE)
b:SetHeight(AURA_SIZE)
@@ -1256,7 +1513,7 @@ function SFrames.Target:TickAuras()
end
-- Buffs
for i = 1, 16 do
for i = 1, 32 do
local b = self.frame.buffs[i]
if b:IsShown() and b.expirationTime then
local timeLeft = b.expirationTime - timeNow
@@ -1275,7 +1532,7 @@ function SFrames.Target:TickAuras()
end
-- Debuffs: re-query SpellDB for live-accurate timers
for i = 1, 16 do
for i = 1, 32 do
local b = self.frame.debuffs[i]
if b:IsShown() then
local timeLeft = nil
@@ -1316,14 +1573,17 @@ end
function SFrames.Target:UpdateAuras()
if not UnitExists("target") then return end
local hasSuperWoW = SFrames.superwow_active and SpellInfo
local numBuffs = 0
-- Buffs
for i = 1, 16 do
local texture = UnitBuff("target", i)
for i = 1, 32 do
local texture, swAuraID = 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)
-- Store aura ID when SuperWoW is available
b.auraID = hasSuperWoW and swAuraID or nil
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:ClearLines()
@@ -1364,12 +1624,14 @@ function SFrames.Target:UpdateAuras()
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)
for i = 1, 32 do
local texture, dbCount, dbType, swDebuffAuraID = UnitDebuff("target", i)
local b = self.frame.debuffs[i]
b:SetID(i)
if texture then
b.icon:SetTexture(texture)
-- Store aura ID when SuperWoW is available
b.auraID = hasSuperWoW and swDebuffAuraID or nil
local timeLeft = 0
local effectName = nil