mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-24 13:00:50 +00:00
librange: rely on SuperWoW UnitPosition
Drops the three other range-check paths and the target-juggling machinery they required: - Nampower IsSpellInRange branch + spellbook-scan to find the spell name - UnitXP_SP3 distance check (api.lua's UnitInRange wrapper still owns the UnitXP precise mode independently) - Vanilla IsActionInRange fallback that briefly retargeted via TargetUnit / TargetLastTarget With target-juggling gone, this also deletes the support scaffolding it required: PlaySound override, TargetFrame_OnEvent swap, ReAttack / lastattack restoration, the wand and combo-points detection frames (both were suspension guards for target-juggling), the loot/inspect/ trade/combat skip-checks, librange's pfScanActive set/clear (focus.lua still uses its own), and the librange_isLoggingOut crash workaround the target-flip path needed. Target case still uses IsActionInRange (vanilla-native, works for hostile targets too); friendly party/raid/pet scan uses UnitPosition. The friendly-only restriction on UnitPosition isn't an issue because the scan list contains only friendlies anyway. DPS classes (no 40y healing spell in `spells[class]`) now also get party/raid range coloring; they only miss the target case, which is unchanged from before. 328 → 166 lines.
This commit is contained in:
+26
-189
@@ -5,16 +5,19 @@ setfenv(1, pfUI:GetEnvironment())
|
|||||||
-- A pfUI library that detects and caches distance to units.
|
-- A pfUI library that detects and caches distance to units.
|
||||||
--
|
--
|
||||||
-- librange:UnitInSpellRange(unit)
|
-- librange:UnitInSpellRange(unit)
|
||||||
-- Returns `1` if the unit is within a 40y range
|
-- Returns `1` if the unit is within range, `nil` otherwise.
|
||||||
--
|
--
|
||||||
|
-- Requires SuperWoW's UnitPosition for the friendly scan path. Target
|
||||||
|
-- range still works via IsActionInRange (vanilla-native) for any class
|
||||||
|
-- with a known 40y healing spell on the action bar.
|
||||||
|
|
||||||
-- return instantly when another librange is already active
|
|
||||||
if pfUI.api.librange then return end
|
if pfUI.api.librange then return end
|
||||||
|
|
||||||
local _, class = UnitClass("player")
|
local _, class = UnitClass("player")
|
||||||
local librange = CreateFrame("Frame", "pfRangecheck", UIParent)
|
local librange = CreateFrame("Frame", "pfRangecheck", UIParent)
|
||||||
|
|
||||||
-- table of 40y spells per class
|
-- 40y spells per class. Only consulted to find an action-bar slot for the
|
||||||
|
-- IsActionInRange target-range path; the party/raid scan uses UnitPosition.
|
||||||
local spells = {
|
local spells = {
|
||||||
["PALADIN"] = {
|
["PALADIN"] = {
|
||||||
"Interface\\Icons\\Spell_Holy_FlashHeal",
|
"Interface\\Icons\\Spell_Holy_FlashHeal",
|
||||||
@@ -39,50 +42,7 @@ local spells = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Use Nampower's IsSpellInRange if available (vanilla only)
|
-- friendly units the scan loop iterates
|
||||||
-- This provides more accurate range checking without needing to find spell slots
|
|
||||||
local nampower_spell
|
|
||||||
if GetNampowerVersion then
|
|
||||||
librange:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
|
||||||
librange:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
||||||
librange:SetScript("OnEvent", function()
|
|
||||||
-- abort on non healing classes
|
|
||||||
if not spells[class] then return end
|
|
||||||
|
|
||||||
nampower_spell = nil
|
|
||||||
|
|
||||||
for i = 1, GetNumSpellTabs() do
|
|
||||||
local _, _, offset, num = GetSpellTabInfo(i)
|
|
||||||
for id = offset + 1, offset + num do
|
|
||||||
local name, rank = GetSpellName(id, BOOKTYPE_SPELL)
|
|
||||||
local texture = GetSpellTexture(id, BOOKTYPE_SPELL)
|
|
||||||
|
|
||||||
if texture then
|
|
||||||
for _, tex in pairs(spells[class]) do
|
|
||||||
if tex == texture then
|
|
||||||
nampower_spell = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
function librange:UnitInSpellRange(unit)
|
|
||||||
if not nampower_spell then return nil end
|
|
||||||
-- Nampower's IsSpellInRange returns 1 if in range, 0 if not, -1 if invalid
|
|
||||||
local result = IsSpellInRange(nampower_spell, unit)
|
|
||||||
if result == 1 then return 1
|
|
||||||
elseif result == 0 then return nil
|
|
||||||
else return nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- add librange to pfUI API
|
|
||||||
pfUI.api.librange = librange
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- units that should be scanned
|
|
||||||
local units = {}
|
local units = {}
|
||||||
table.insert(units, "pet")
|
table.insert(units, "pet")
|
||||||
for i=1,4 do table.insert(units, "party" .. i) end
|
for i=1,4 do table.insert(units, "party" .. i) end
|
||||||
@@ -91,67 +51,25 @@ for i=1,40 do table.insert(units, "raid" .. i) end
|
|||||||
for i=1,40 do table.insert(units, "raidpet" .. i) end
|
for i=1,40 do table.insert(units, "raidpet" .. i) end
|
||||||
local numunits = table.getn(units)
|
local numunits = table.getn(units)
|
||||||
|
|
||||||
-- cache for unit relations
|
|
||||||
local unitcache = {}
|
local unitcache = {}
|
||||||
|
local unitdata = {}
|
||||||
-- actual unit-range table
|
|
||||||
local unitdata = { }
|
|
||||||
|
|
||||||
-- setup sound function switches
|
|
||||||
local SoundOn = PlaySound
|
|
||||||
local SoundOff = function() return end
|
|
||||||
|
|
||||||
librange.id = 1
|
|
||||||
|
|
||||||
-- Shooting with wands does not make the PlayerFrame inCombat attribute change.
|
|
||||||
-- This frame makes wand attacks accesible via wandCombat on the PlayerFrame.
|
|
||||||
local wand = CreateFrame("Frame", "pfWandShootDetect")
|
|
||||||
wand:RegisterEvent("START_AUTOREPEAT_SPELL")
|
|
||||||
wand:RegisterEvent("STOP_AUTOREPEAT_SPELL")
|
|
||||||
wand:SetScript("OnEvent", function()
|
|
||||||
PlayerFrame.wandCombat = event == "START_AUTOREPEAT_SPELL" and true or nil
|
|
||||||
end)
|
|
||||||
|
|
||||||
--Players with combo points aren't necessarily auto attacking, meaning we can't use inCombat.
|
|
||||||
--This allows us to avoid rangechecking when the player has combo points to avoid losing them.
|
|
||||||
local hascombopoints
|
|
||||||
local combo = CreateFrame("Frame", "pfComboPointsDetect")
|
|
||||||
combo:RegisterEvent("PLAYER_COMBO_POINTS")
|
|
||||||
combo:SetScript("OnEvent", function()
|
|
||||||
hascombopoints = GetComboPoints() > 0
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Flag to prevent UnitXP calls during logout (crash prevention)
|
|
||||||
local librange_isLoggingOut = false
|
local librange_isLoggingOut = false
|
||||||
|
librange.id = 1
|
||||||
-- Detect UnitXP_SP3 once at load time to avoid pcall+closure overhead per tick.
|
|
||||||
-- UnitXP_SP3 exposes UnitXP("distanceBetween", ...) — test it once and cache the result.
|
|
||||||
local hasUnitXP_SP3 = false
|
|
||||||
if UnitXP then
|
|
||||||
local ok, val = pcall(UnitXP, "distanceBetween", "player", "player")
|
|
||||||
if ok and val then
|
|
||||||
hasUnitXP_SP3 = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
librange:Hide()
|
librange:Hide()
|
||||||
librange:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
librange:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
||||||
librange:RegisterEvent("PLAYER_ENTERING_WORLD")
|
librange:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||||
librange:RegisterEvent("PLAYER_ENTER_COMBAT")
|
|
||||||
librange:RegisterEvent("PLAYER_LEAVE_COMBAT")
|
|
||||||
librange:RegisterEvent("PLAYER_LOGOUT")
|
librange:RegisterEvent("PLAYER_LOGOUT")
|
||||||
librange:RegisterEvent("PLAYER_LEAVING_WORLD")
|
librange:RegisterEvent("PLAYER_LEAVING_WORLD")
|
||||||
librange:SetScript("OnEvent", function()
|
librange:SetScript("OnEvent", function()
|
||||||
-- Handle logout to prevent UnitXP crashes during shutdown
|
|
||||||
if event == "PLAYER_LOGOUT" or event == "PLAYER_LEAVING_WORLD" then
|
if event == "PLAYER_LOGOUT" or event == "PLAYER_LEAVING_WORLD" then
|
||||||
librange_isLoggingOut = true
|
librange_isLoggingOut = true
|
||||||
this:SetScript("OnUpdate", nil) -- Stop OnUpdate completely
|
this:SetScript("OnUpdate", nil)
|
||||||
this:Hide()
|
this:Hide()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- disable range checking activities
|
if pfUI_config.unitframes.rangecheck == "0" then
|
||||||
if pfUI_config.unitframes.rangecheck == "0" or not spells[class] then
|
|
||||||
this:Hide()
|
this:Hide()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -160,110 +78,36 @@ librange:SetScript("OnEvent", function()
|
|||||||
|
|
||||||
if event == "ACTIONBAR_SLOT_CHANGED" or event == "PLAYER_ENTERING_WORLD" then
|
if event == "ACTIONBAR_SLOT_CHANGED" or event == "PLAYER_ENTERING_WORLD" then
|
||||||
librange.slot = this:GetRangeSlot()
|
librange.slot = this:GetRangeSlot()
|
||||||
this:Show()
|
if UnitPosition then this:Show() end
|
||||||
elseif event == "PLAYER_ENTER_COMBAT" then
|
|
||||||
this.lastattack = GetTime()
|
|
||||||
this:Hide()
|
|
||||||
elseif event == "PLAYER_LEAVE_COMBAT" then
|
|
||||||
if not this:ReAttack() then
|
|
||||||
this:Show()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local _, class = UnitClass("player")
|
|
||||||
local druid = class == "DRUID"
|
|
||||||
local target_event = TargetFrame_OnEvent
|
|
||||||
local target_nop = function() return end
|
|
||||||
|
|
||||||
librange:SetScript("OnUpdate", function()
|
librange:SetScript("OnUpdate", function()
|
||||||
-- Prevent UnitXP calls during logout (crash prevention)
|
|
||||||
if librange_isLoggingOut then return end
|
if librange_isLoggingOut then return end
|
||||||
|
|
||||||
if ( this.tick or 1) > GetTime() then
|
if (this.tick or 1) > GetTime() then return end
|
||||||
return
|
this.tick = GetTime() + this.interval
|
||||||
else
|
|
||||||
this.tick = GetTime() + this.interval
|
|
||||||
end
|
|
||||||
|
|
||||||
-- skip invalid units
|
|
||||||
while not this:NeedRangeScan(units[this.id]) and this.id <= numunits do
|
while not this:NeedRangeScan(units[this.id]) and this.id <= numunits do
|
||||||
this.id = this.id + 1
|
this.id = this.id + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if this.id <= numunits and librange.slot then
|
if this.id <= numunits then
|
||||||
local unit = units[this.id]
|
local unit = units[this.id]
|
||||||
if not UnitIsUnit("target", unit) then
|
if not UnitIsUnit("target", unit) then
|
||||||
-- Try UnitXP_SP3 first (most accurate distance measurement)
|
local x1, y1, z1 = UnitPosition("player")
|
||||||
if hasUnitXP_SP3 then
|
local x2, y2, z2 = UnitPosition(unit)
|
||||||
local unitxp_distance = UnitXP("distanceBetween", "player", unit)
|
if x1 and x2 then
|
||||||
if unitxp_distance then
|
local distance = ((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2)^.5
|
||||||
local threshold = (tonumber(C.unitframes.rangecheck_distance) or 40) + 5
|
unitdata[unit] = distance < 45 and 1 or 0
|
||||||
unitdata[unit] = unitxp_distance < threshold and 1 or 0
|
|
||||||
this.id = this.id + 1
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- try to read distance via superwow second
|
|
||||||
if HasSuperWoW() and UnitPosition then
|
|
||||||
local x1, y1, z1 = UnitPosition("player")
|
|
||||||
local x2, y2, z2 = UnitPosition(unit)
|
|
||||||
-- only continue if we got position values
|
|
||||||
if x1 and y1 and z1 and x2 and y2 and z2 then
|
|
||||||
local distance = ((x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2)^.5
|
|
||||||
unitdata[unit] = distance < 45 and 1 or 0
|
|
||||||
this.id = this.id + 1
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- suspend for various conditions
|
|
||||||
if pfUI.loot and pfUI.loot:IsShown() then return nil end
|
|
||||||
if LootFrame and LootFrame:IsShown() then return nil end
|
|
||||||
if InspectFrame and InspectFrame:IsShown() then return nil end
|
|
||||||
if TradeFrame and TradeFrame:IsShown() then return nil end
|
|
||||||
if PlayerFrame and PlayerFrame.inCombat then return nil end
|
|
||||||
if PlayerFrame and PlayerFrame.wandCombat then return nil end
|
|
||||||
if druid and UnitPowerType("player") == 3 then return nil end
|
|
||||||
if hascombopoints then return nil end
|
|
||||||
|
|
||||||
_G.PlaySound = SoundOff
|
|
||||||
pfScanActive = true
|
|
||||||
|
|
||||||
-- save and disable target frame events
|
|
||||||
target_event = TargetFrame_OnEvent
|
|
||||||
_G.TargetFrame_OnEvent = target_nop
|
|
||||||
|
|
||||||
TargetUnit(unit)
|
|
||||||
unitdata[unit] = IsActionInRange(librange.slot)
|
|
||||||
TargetLastTarget()
|
|
||||||
|
|
||||||
-- restore target events
|
|
||||||
_G.TargetFrame_OnEvent = target_event
|
|
||||||
|
|
||||||
_G.PlaySound = SoundOn
|
|
||||||
pfScanActive = false
|
|
||||||
|
|
||||||
this:ReAttack()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
this.id = this.id + 1
|
this.id = this.id + 1
|
||||||
else
|
else
|
||||||
this.id = 1
|
this.id = 1
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
function librange:ReAttack()
|
|
||||||
-- we accidentally broke the autoattack... restoring the old state
|
|
||||||
if this.lastattack and this.lastattack + this.interval > GetTime() and UnitCanAttack("player", "target") then
|
|
||||||
AttackTarget()
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function librange:NeedRangeScan(unit)
|
function librange:NeedRangeScan(unit)
|
||||||
if not UnitExists(unit) then return nil end
|
if not UnitExists(unit) then return nil end
|
||||||
if not UnitIsVisible(unit) then return nil end
|
if not UnitIsVisible(unit) then return nil end
|
||||||
@@ -274,10 +118,8 @@ end
|
|||||||
function librange:GetRealUnit(unit)
|
function librange:GetRealUnit(unit)
|
||||||
if unitdata[unit] then return unit end
|
if unitdata[unit] then return unit end
|
||||||
|
|
||||||
if unitcache[unit] then
|
if unitcache[unit] and UnitIsUnit(unitcache[unit], unit) then
|
||||||
if UnitIsUnit(unitcache[unit], unit) then
|
return unitcache[unit]
|
||||||
return unitcache[unit]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for id, realunit in pairs(units) do
|
for id, realunit in pairs(units) do
|
||||||
@@ -291,26 +133,21 @@ function librange:GetRealUnit(unit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function librange:GetRangeSlot()
|
function librange:GetRangeSlot()
|
||||||
local texture
|
if not spells[class] then return nil end
|
||||||
|
|
||||||
for i=1,120 do
|
for i=1,120 do
|
||||||
texture = GetActionTexture(i)
|
local texture = GetActionTexture(i)
|
||||||
if texture and not GetActionText(i) then
|
if texture and not GetActionText(i) then
|
||||||
for _, check in pairs(spells[class]) do
|
for _, check in pairs(spells[class]) do
|
||||||
if check == texture then
|
if check == texture then return i end
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function librange:UnitInSpellRange(unit)
|
function librange:UnitInSpellRange(unit)
|
||||||
if not librange.slot then return nil end
|
|
||||||
|
|
||||||
if UnitIsUnit("target", unit) then
|
if UnitIsUnit("target", unit) then
|
||||||
|
if not librange.slot then return nil end
|
||||||
return IsActionInRange(librange.slot) == 1 and 1 or nil
|
return IsActionInRange(librange.slot) == 1 and 1 or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user