mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-29 10:46:03 +00:00
Classic API Focus (#2)
* focus: migrate to FocusUnit / "focus" token / PLAYER_FOCUS_CHANGED
ClassicAPI now polyfills modern WoW's focus system: FocusUnit / ClearFocus
+ "focus" / "focustarget" unit tokens accepted by every UnitX function +
PLAYER_FOCUS_CHANGED event. Drop pfUI's GUID-juggling pseudo-frame.
- env/tables.lua: add "focus" and "focustarget" to pfValidUnits — the
focus frames now follow the standard event-driven CreateUnitFrame path
with f.label = "focus"
- modules/focus.lua: rewrite. Slash commands use FocusUnit / ClearFocus
directly; no more manual frame.label/unitname/id manipulation. /focus
<name> still does a target-swap to resolve names → units, then
FocusUnit("target") captures the GUID before the target is restored.
PLAYER_FOCUS_CHANGED triggers immediate frame refresh on assign/clear.
- api/unitframes.lua: delete the pseudo-focus block (per-tick "scan all
units for matching name" loop) and the "focus"/"focustarget" special-
case in the visibility branch — "focus" is now a real token.
- modules/castbar.lua: bind the focus castbar to unitstr = "focus" once;
drop the OnUpdate that synced pfUI.castbar.focus from
pfUI.uf.focus.label/unitname. UnitGUID("focus") resolves at read time.
- Drop C_Minimap.SetFocusByGUID / SetFocusByName / ClearFocus calls —
C_Minimap subscribes to PLAYER_FOCUS_CHANGED on its own.
Net -183 lines. Focus stops being a pfUI-special pseudo-frame and
becomes "just another unit token" — same treatment as target/player.
* add nameplate to valid units
---------
Co-authored-by: Brues <5278969+brues-code@users.noreply.github.com>
This commit is contained in:
+2
-47
@@ -356,7 +356,7 @@ function pfUI.uf:UpdateVisibility()
|
||||
end
|
||||
|
||||
-- vanilla visibility
|
||||
if self.unitname and self.unitname ~= "focus" and self.unitname ~= "focustarget" then
|
||||
if self.unitname then
|
||||
self:Show()
|
||||
elseif visibility == "hide" then
|
||||
self:Hide()
|
||||
@@ -1462,52 +1462,7 @@ function pfUI.uf.OnUpdate()
|
||||
end
|
||||
end
|
||||
|
||||
-- handle pseudo focus frames
|
||||
if this.unitname and this == pfFocus then
|
||||
local unitname = ( this.label and UnitName(this.label) ) or ""
|
||||
|
||||
if pfFocusTarget then -- update focus target
|
||||
pfFocusTarget.label = this.label and this.label .. "target" or nil
|
||||
local focustargetname = pfFocusTarget.label and UnitName(pfFocusTarget.label) or nil
|
||||
|
||||
if pfFocusTarget.lastUnit ~= focustargetname then
|
||||
pfFocusTarget.lastUnit = focustargetname
|
||||
pfFocusTarget.instantRefresh = true
|
||||
pfUI.uf:RefreshUnit(pfFocusTarget, "all")
|
||||
end
|
||||
end
|
||||
|
||||
-- break here on unset focus frames
|
||||
if not this.unitname or this.unitname == "focus" then return end
|
||||
|
||||
-- focus unit detection
|
||||
if this.unitname ~= strlower(unitname) then
|
||||
-- invalid focus frame
|
||||
for unit, bool in pairs(pfValidUnits) do
|
||||
local scan = UnitName(unit) or ""
|
||||
if this.unitname == strlower(scan) then
|
||||
this.label = unit
|
||||
if this.portrait then this.portrait.model.lastUnit = nil end
|
||||
this.instantRefresh = true
|
||||
pfUI.uf:RefreshUnit(this, "all")
|
||||
return
|
||||
end
|
||||
this.label = nil
|
||||
this.instantRefresh = true
|
||||
this.hp.bar:SetStatusBarColor(.2,.2,.2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- handle pseudo focus target visibility
|
||||
if this.unitname and this == pfFocusTarget then
|
||||
if pfFocus and not pfFocus.label or pfFocus.label == "" then
|
||||
this.label = nil
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not this.label then return end
|
||||
if not this.label or this.label == "" then return end
|
||||
|
||||
-- update portrait on first visible frame
|
||||
if this.portrait and this.portrait.model and this.portrait.model.update then
|
||||
|
||||
Vendored
+21
-8
@@ -52,20 +52,33 @@ pfValidUnits["player"] = true
|
||||
pfValidUnits["target"] = true
|
||||
pfValidUnits["mouseover"] = true
|
||||
|
||||
pfValidUnits["focus"] = true
|
||||
pfValidUnits["focustarget"] = true
|
||||
pfValidUnits["focustargettarget"] = true
|
||||
|
||||
pfValidUnits["pettarget"] = true
|
||||
pfValidUnits["playertarget"] = true
|
||||
pfValidUnits["targettarget"] = true
|
||||
pfValidUnits["mouseovertarget"] = true
|
||||
pfValidUnits["targettargettarget"] = true
|
||||
for i=1,4 do pfValidUnits["party" .. i] = true end
|
||||
for i=1,4 do pfValidUnits["partypet" .. i] = true end
|
||||
for i=1,40 do pfValidUnits["raid" .. i] = true end
|
||||
for i=1,40 do pfValidUnits["raidpet" .. i] = true end
|
||||
|
||||
for i=1,4 do pfValidUnits["party" .. i .. "target"] = true end
|
||||
for i=1,4 do pfValidUnits["partypet" .. i .. "target"] = true end
|
||||
for i=1,40 do pfValidUnits["raid" .. i .. "target"] = true end
|
||||
for i=1,40 do pfValidUnits["raidpet" .. i .. "target"] = true end
|
||||
for i=1,4 do
|
||||
pfValidUnits["party" .. i] = true
|
||||
pfValidUnits["party" .. i .. "target"] = true
|
||||
pfValidUnits["partypet" .. i] = true
|
||||
pfValidUnits["partypet" .. i .. "target"] = true
|
||||
end
|
||||
|
||||
for i=1,40 do
|
||||
pfValidUnits["raid" .. i] = true
|
||||
pfValidUnits["raid" .. i .. "target"] = true
|
||||
|
||||
pfValidUnits["raidpet" .. i] = true
|
||||
pfValidUnits["raidpet" .. i .. "target"] = true
|
||||
|
||||
pfValidUnits["nameplate" .. i] = true
|
||||
pfValidUnits["nameplate" .. i .. "target"] = true
|
||||
end
|
||||
|
||||
-- itemid = buyPrice (copper). Sell price comes from C_Item.GetItemSellPriceByID;
|
||||
-- this table only carries the curated vendor-buy price (which isn't a static
|
||||
|
||||
+2
-28
@@ -390,9 +390,6 @@ pfUI:RegisterModule("castbar", function ()
|
||||
pfUI.castbar.focus.showrank = C.castbar.focus.showrank == "1" and true or nil
|
||||
pfUI.castbar.focus.spacing = default_border * 2 + tonumber(C.unitframes.focus.pspace) * GetPerfectPixel()
|
||||
|
||||
-- reset unitstr for vanilla focus frame emulation
|
||||
pfUI.castbar.focus.unitstr = nil
|
||||
|
||||
local anchor = pfUI.uf.focus.portrait:GetHeight() > pfUI.uf.focus:GetHeight() and pfUI.uf.focus.power or pfUI.uf.focus
|
||||
local width = C.castbar.focus.width ~= "-1" and C.castbar.focus.width or anchor:GetWidth()
|
||||
pfUI.castbar.focus:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -pfUI.castbar.focus.spacing)
|
||||
@@ -402,31 +399,8 @@ pfUI:RegisterModule("castbar", function ()
|
||||
pfUI.castbar.focus:SetHeight(C.castbar.focus.height)
|
||||
end
|
||||
|
||||
-- keep unit values in sync with focus unitframe
|
||||
HookScript(pfUI.castbar.focus, "OnUpdate", function()
|
||||
-- clear on empty focus frames
|
||||
if pfUI.uf.focus.unitname == nil and pfUI.uf.focus.label == nil then
|
||||
pfUI.castbar.focus.unitstr = nil
|
||||
pfUI.castbar.focus.unitname = nil
|
||||
return
|
||||
end
|
||||
|
||||
-- skip on initial values
|
||||
if pfUI.uf.focus.unitname == "focus" then return end
|
||||
if pfUI.uf.focus.unitname == "" then return end
|
||||
|
||||
-- try to obtain a unitstr
|
||||
pfUI.castbar.focus.unitstr = string.format("%s%s", (pfUI.uf.focus.label or ""), (pfUI.uf.focus.id or ""))
|
||||
pfUI.castbar.focus.unitstr = pfUI.castbar.focus.unitstr == "" and nil or pfUI.castbar.focus.unitstr
|
||||
|
||||
if pfUI.castbar.focus.unitstr then
|
||||
-- read non-lowercase unitname when possible
|
||||
pfUI.castbar.focus.unitname = UnitName(pfUI.castbar.focus.unitstr) or pfUI.castbar.focus.unitname
|
||||
elseif strlower(pfUI.castbar.focus.unitname) ~= strlower(pfUI.uf.focus.unitname) then
|
||||
-- sync unitname with focus frame's lowercase value
|
||||
pfUI.castbar.focus.unitname = pfUI.uf.focus.unitname
|
||||
end
|
||||
end)
|
||||
-- bind castbar to the "focus" unit token; the GUID resolves at read time
|
||||
pfUI.castbar.focus.unitstr = "focus"
|
||||
|
||||
UpdateMovable(pfUI.castbar.focus)
|
||||
end
|
||||
|
||||
+64
-183
@@ -13,236 +13,117 @@ pfUI:RegisterModule("focus", function ()
|
||||
pfUI.uf.focustarget:SetPoint("BOTTOMLEFT", pfUI.uf.focus, "TOP", 0, 10)
|
||||
UpdateMovable(pfUI.uf.focustarget)
|
||||
pfUI.uf.focustarget:Hide()
|
||||
|
||||
-- PLAYER_FOCUS_CHANGED drives immediate refresh on focus assign / clear.
|
||||
-- The frame's 0.2s tick keeps health/power/aura data fresh between events.
|
||||
local refresher = CreateFrame("Frame")
|
||||
refresher:RegisterEvent("PLAYER_FOCUS_CHANGED")
|
||||
refresher:SetScript("OnEvent", function()
|
||||
pfUI.uf.focus.instantRefresh = true
|
||||
pfUI.uf:RefreshUnit(pfUI.uf.focus, "all")
|
||||
pfUI.uf.focustarget.instantRefresh = true
|
||||
pfUI.uf:RefreshUnit(pfUI.uf.focustarget, "all")
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Helper: set focus frame to a GUID
|
||||
local function SetFocusByGUID(guid)
|
||||
pfUI.uf.focus.unitname = nil
|
||||
pfUI.uf.focus.label = guid
|
||||
pfUI.uf.focus.id = ""
|
||||
|
||||
if pfUI.uf.focustarget then
|
||||
pfUI.uf.focustarget.unitname = nil
|
||||
pfUI.uf.focustarget.label = guid .. "target"
|
||||
pfUI.uf.focustarget.id = ""
|
||||
end
|
||||
|
||||
C_Minimap.SetFocusByGUID(guid)
|
||||
end
|
||||
|
||||
-- Helper: set focus frame by name (fallback, no Nampower)
|
||||
local function SetFocusByName(name)
|
||||
pfUI.uf.focus.unitname = strlower(name)
|
||||
pfUI.uf.focus.label = nil
|
||||
pfUI.uf.focus.id = nil
|
||||
|
||||
if pfUI.uf.focustarget then
|
||||
pfUI.uf.focustarget.unitname = strlower(name) .. "target"
|
||||
pfUI.uf.focustarget.label = nil
|
||||
pfUI.uf.focustarget.id = nil
|
||||
end
|
||||
|
||||
C_Minimap.SetFocusByName(name)
|
||||
end
|
||||
|
||||
SLASH_PFFOCUS1, SLASH_PFFOCUS2 = '/focus', '/pffocus'
|
||||
function SlashCmdList.PFFOCUS(msg)
|
||||
if not pfUI.uf or not pfUI.uf.focus then return end
|
||||
if msg == "" then
|
||||
FocusUnit("target")
|
||||
return
|
||||
end
|
||||
|
||||
if msg ~= "" then
|
||||
-- Try to resolve GUID via short target swap
|
||||
if UnitExists then
|
||||
local prevGUID = UnitGUID("target")
|
||||
local prevPlayer = UnitIsUnit("target", "player")
|
||||
-- Resolve name → unit via short target-swap, capturing focus during the swap.
|
||||
local prevGUID = UnitGUID("target")
|
||||
local prevPlayer = UnitIsUnit("target", "player")
|
||||
|
||||
-- Suppress "Unknown unit" errors during targeting attempts (fired async)
|
||||
UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
|
||||
-- Suppress "Unknown unit" errors during targeting attempts (fired async)
|
||||
UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
|
||||
|
||||
-- Try exact match first, then prefix match via /tar
|
||||
TargetByName(msg, true)
|
||||
local guid = UnitGUID("target")
|
||||
-- Try exact match first, then prefix match via /tar
|
||||
TargetByName(msg, true)
|
||||
if not UnitExists("target") then
|
||||
SlashCmdList.TARGET(msg)
|
||||
end
|
||||
|
||||
if not guid or guid == "0x0000000000000000" then
|
||||
-- Fallback: prefix match (like /tar storm -> Stormwind Guard)
|
||||
SlashCmdList.TARGET(msg)
|
||||
_, guid = UnitExists("target")
|
||||
end
|
||||
if UnitExists("target") then
|
||||
FocusUnit("target")
|
||||
end
|
||||
|
||||
-- Re-enable errors next frame (errors are fired async)
|
||||
local restore = CreateFrame("Frame")
|
||||
restore:SetScript("OnUpdate", function()
|
||||
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
||||
restore:SetScript("OnUpdate", nil)
|
||||
end)
|
||||
-- Re-enable errors next frame
|
||||
local restore = CreateFrame("Frame")
|
||||
restore:SetScript("OnUpdate", function()
|
||||
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
||||
restore:SetScript("OnUpdate", nil)
|
||||
end)
|
||||
|
||||
-- Restore previous target
|
||||
if prevGUID and prevGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(prevGUID)
|
||||
elseif prevPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
ClearTarget()
|
||||
end
|
||||
|
||||
if guid and guid ~= "0x0000000000000000" then
|
||||
SetFocusByGUID(guid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: name-based (non-Nampower clients)
|
||||
SetFocusByName(msg)
|
||||
if prevGUID and prevGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(prevGUID)
|
||||
elseif prevPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
-- No msg: use current target
|
||||
if UnitExists then
|
||||
local guid = UnitGUID("target")
|
||||
if guid and guid ~= "0x0000000000000000" then
|
||||
SetFocusByGUID(guid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: name-based
|
||||
local name = UnitName("target")
|
||||
if name then
|
||||
SetFocusByName(name)
|
||||
end
|
||||
ClearTarget()
|
||||
end
|
||||
end
|
||||
|
||||
SLASH_PFCLEARFOCUS1, SLASH_PFCLEARFOCUS2 = '/clearfocus', '/pfclearfocus'
|
||||
function SlashCmdList.PFCLEARFOCUS(msg)
|
||||
if pfUI.uf and pfUI.uf.focus then
|
||||
pfUI.uf.focus.unitname = nil
|
||||
pfUI.uf.focus.label = nil
|
||||
pfUI.uf.focus.id = nil
|
||||
end
|
||||
|
||||
if pfUI.uf and pfUI.uf.focustarget then
|
||||
pfUI.uf.focustarget.unitname = nil
|
||||
pfUI.uf.focustarget.label = nil
|
||||
pfUI.uf.focustarget.id = nil
|
||||
end
|
||||
|
||||
C_Minimap.ClearFocus()
|
||||
ClearFocus()
|
||||
end
|
||||
|
||||
SLASH_PFCASTFOCUS1, SLASH_PFCASTFOCUS2 = '/castfocus', '/pfcastfocus'
|
||||
function SlashCmdList.PFCASTFOCUS(msg)
|
||||
if not pfUI.uf.focus or not pfUI.uf.focus:IsShown() then
|
||||
local focusGUID = UnitGUID("focus")
|
||||
if not focusGUID or focusGUID == "0x0000000000000000" then
|
||||
UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
local func = pfUI.api.TryMemoizedFuncLoadstringForSpellCasts(msg)
|
||||
local focusGUID = pfUI.uf.focus.label
|
||||
local hasGUID = focusGUID and focusGUID ~= "" and focusGUID ~= "0x0000000000000000"
|
||||
|
||||
-- GUID-based cast (Nampower) - no target toggle needed
|
||||
if hasGUID and CastSpellByName and not func then
|
||||
if not func then
|
||||
CastSpellByName(msg, focusGUID)
|
||||
return
|
||||
end
|
||||
|
||||
-- For lua functions with GUID: short target swap via GUID
|
||||
if hasGUID and func then
|
||||
local currentGUID = UnitGUID("target")
|
||||
local isPlayer = UnitIsUnit("target", "player")
|
||||
-- Lua-function cast: short target swap via GUID
|
||||
local prevGUID = UnitGUID("target")
|
||||
local prevPlayer = UnitIsUnit("target", "player")
|
||||
|
||||
TargetUnit(focusGUID)
|
||||
local newGUID = UnitGUID("target")
|
||||
|
||||
if newGUID ~= focusGUID then
|
||||
-- Could not target focus, restore and fail
|
||||
if currentGUID and currentGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(currentGUID)
|
||||
elseif isPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
TargetLastTarget()
|
||||
end
|
||||
UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
func()
|
||||
|
||||
if currentGUID and currentGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(currentGUID)
|
||||
elseif isPlayer then
|
||||
TargetUnit(focusGUID)
|
||||
if UnitGUID("target") ~= focusGUID then
|
||||
if prevGUID and prevGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(prevGUID)
|
||||
elseif prevPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
TargetLastTarget()
|
||||
end
|
||||
UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
-- Fallback: name-based target swap (no Nampower / no GUID)
|
||||
local skiptarget = false
|
||||
local player = UnitIsUnit("target", "player")
|
||||
local unitname = ""
|
||||
func()
|
||||
|
||||
if pfUI.uf.focus.label and pfUI.uf.focus.id and
|
||||
UnitIsUnit("target", pfUI.uf.focus.label .. pfUI.uf.focus.id) then
|
||||
skiptarget = true
|
||||
if prevGUID and prevGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(prevGUID)
|
||||
elseif prevPlayer then
|
||||
TargetUnit("player")
|
||||
else
|
||||
pfScanActive = true
|
||||
if pfUI.uf.focus.label and pfUI.uf.focus.id then
|
||||
unitname = UnitName(pfUI.uf.focus.label .. pfUI.uf.focus.id)
|
||||
TargetUnit(pfUI.uf.focus.label .. pfUI.uf.focus.id)
|
||||
else
|
||||
unitname = pfUI.uf.focus.unitname
|
||||
TargetByName(pfUI.uf.focus.unitname, true)
|
||||
end
|
||||
|
||||
if strlower(UnitName("target") or "") ~= strlower(unitname or "") then
|
||||
pfScanActive = nil
|
||||
TargetLastTarget()
|
||||
UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1, 0, 0)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if func then
|
||||
func()
|
||||
else
|
||||
CastSpellByName(msg)
|
||||
end
|
||||
|
||||
if skiptarget == false then
|
||||
pfScanActive = nil
|
||||
if player then
|
||||
TargetUnit("player")
|
||||
else
|
||||
TargetLastTarget()
|
||||
end
|
||||
TargetLastTarget()
|
||||
end
|
||||
end
|
||||
|
||||
SLASH_PFSWAPFOCUS1, SLASH_PFSWAPFOCUS2 = '/swapfocus', '/pfswapfocus'
|
||||
function SlashCmdList.PFSWAPFOCUS(msg)
|
||||
if not pfUI.uf or not pfUI.uf.focus then return end
|
||||
local targetGUID = UnitGUID("target")
|
||||
local oldFocusGUID = UnitGUID("focus")
|
||||
|
||||
local _, guid = nil, nil
|
||||
if UnitExists then
|
||||
_, guid = UnitExists("target")
|
||||
end
|
||||
|
||||
if guid and guid ~= "0x0000000000000000" then
|
||||
local oldGUID = pfUI.uf.focus.label
|
||||
|
||||
SetFocusByGUID(guid)
|
||||
|
||||
-- Target old focus if we had one
|
||||
if oldGUID and oldGUID ~= "" and oldGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(oldGUID)
|
||||
end
|
||||
else
|
||||
-- Fallback: name-based swap
|
||||
local oldunit = UnitExists("target") and strlower(UnitName("target") or "")
|
||||
if oldunit and pfUI.uf.focus.unitname then
|
||||
TargetByName(pfUI.uf.focus.unitname, true)
|
||||
pfUI.uf.focus.unitname = oldunit
|
||||
if targetGUID and targetGUID ~= "0x0000000000000000" then
|
||||
FocusUnit("target")
|
||||
if oldFocusGUID and oldFocusGUID ~= "0x0000000000000000" then
|
||||
TargetUnit(oldFocusGUID)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user