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:
brues-code
2026-05-27 20:35:15 -05:00
committed by GitHub
parent f8b4b5b5a3
commit f0d9dca63f
4 changed files with 89 additions and 266 deletions
+64 -183
View File
@@ -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