forked from Dusk/ShaguTweaks-ClassicAPI
185 lines
8.0 KiB
Lua
185 lines
8.0 KiB
Lua
local _G = ShaguTweaks.GetGlobalEnv()
|
|
local T = ShaguTweaks.T
|
|
local hooksecurefunc = ShaguTweaks.hooksecurefunc
|
|
local API = ShaguTweaks.API
|
|
|
|
local module = ShaguTweaks:register({
|
|
title = T["Range Color"],
|
|
description = T["Action buttons will be colored red when out of range."],
|
|
expansions = { ["vanilla"] = true },
|
|
category = T["Action Bar"],
|
|
enabled = nil,
|
|
})
|
|
|
|
-- Modern APIs return booleans; legacy range APIs return 1/0/nil.
|
|
-- In Lua, numeric zero is truthy, so normalize both contracts explicitly.
|
|
local function NormalizeRange(value)
|
|
if value == true or value == 1 then return 1 end
|
|
if value == false or value == 0 then return 0 end
|
|
end
|
|
|
|
local function GetActionRange(action)
|
|
if not UnitExists("target") then return nil end
|
|
|
|
if API and API.GetActionInfo then
|
|
local actionType, id = API.GetActionInfo(action)
|
|
if actionType == "spell" and id and API.spellrange
|
|
and API.IsSpellInRange then
|
|
-- ClassicAPI's check is geometric: it does not reject the wrong
|
|
-- faction. A friendly buff should not turn red against an enemy.
|
|
local spells = _G.C_Spell
|
|
if spells then
|
|
if type(spells.IsSpellHelpful) == "function"
|
|
and spells.IsSpellHelpful(id) and type(UnitCanAssist) == "function"
|
|
and not UnitCanAssist("player", "target") then return nil end
|
|
if type(spells.IsSpellHarmful) == "function"
|
|
and spells.IsSpellHarmful(id) and type(UnitCanAttack) == "function"
|
|
and not UnitCanAttack("player", "target") then return nil end
|
|
end
|
|
-- nil is authoritative: self/rangeless spells have no range to
|
|
-- fail. Never turn it into an old action-slot false result.
|
|
return NormalizeRange(API.IsSpellInRange(id, "target"))
|
|
elseif actionType == "item" and id and API.itemrange
|
|
and API.IsItemInRange then
|
|
return NormalizeRange(API.IsItemInRange(id, "target"))
|
|
end
|
|
end
|
|
|
|
-- Macros, unresolved actions and clients without the native capability
|
|
-- retain the action-slot compatibility fallback.
|
|
return NormalizeRange(IsActionInRange(action))
|
|
end
|
|
|
|
local RANGE_INTERVAL = TOOLTIP_UPDATE_TIME or 0.2
|
|
|
|
-- Newer ClassicAPI owns the range sampling; Lua reacts only to slot changes.
|
|
local function EnableNativeRange()
|
|
local actionbar = _G.C_ActionBar
|
|
if not actionbar or type(actionbar.EnableActionRangeCheck) ~= "function"
|
|
or not API.eventutils or not _G.C_EventUtils.IsEventValid("ACTION_RANGE_CHECK_UPDATE") then return false end
|
|
local states, byButton, ranges = {}, {}, {}
|
|
local function Paint(state)
|
|
local button = state.button
|
|
local slot = state.slot
|
|
if not slot then return end
|
|
local icon = state.icon
|
|
if not icon then return end
|
|
local result = ranges[slot]
|
|
-- Keep invalid-faction spells neutral, as the old module did.
|
|
local kind, id = API.GetActionInfo(slot)
|
|
local spells = _G.C_Spell
|
|
if result == 0 and kind == "spell" and spells then
|
|
if (spells.IsSpellHelpful and spells.IsSpellHelpful(id) and not UnitCanAssist("player", "target"))
|
|
or (spells.IsSpellHarmful and spells.IsSpellHarmful(id) and not UnitCanAttack("player", "target")) then result = nil end
|
|
end
|
|
if result == 0 then
|
|
icon:SetVertexColor(button.r or .85, button.g or .18, button.b or .18, button.a or 1)
|
|
else
|
|
local usable, noMana = IsUsableAction(slot)
|
|
if usable and usable ~= 0 then icon:SetVertexColor(1, 1, 1, 1)
|
|
elseif noMana and noMana ~= 0 then icon:SetVertexColor(.5, .5, 1, 1)
|
|
else icon:SetVertexColor(.4, .4, .4, 1) end
|
|
end
|
|
end
|
|
local function Sync(state)
|
|
local slot = state.button:IsVisible() and ActionButton_GetPagedID(state.button) or nil
|
|
if slot and (slot < 1 or slot > 120) then slot = nil end
|
|
if slot ~= state.slot then
|
|
local old = state.slot
|
|
state.slot = slot
|
|
if old then actionbar.EnableActionRangeCheck(old, false) end
|
|
if slot then
|
|
ranges[slot] = nil
|
|
actionbar.EnableActionRangeCheck(slot, true)
|
|
end
|
|
end
|
|
Paint(state)
|
|
end
|
|
local function ButtonChanged()
|
|
local state = byButton[this]
|
|
if state then Sync(state) end
|
|
end
|
|
local function AllChanged()
|
|
for _, state in ipairs(states) do Sync(state) end
|
|
end
|
|
local listener = CreateFrame("Frame")
|
|
listener:RegisterEvent("ACTION_RANGE_CHECK_UPDATE")
|
|
listener:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
listener:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
listener:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
|
|
listener:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
|
|
listener:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
|
|
listener:SetScript("OnEvent", function()
|
|
if event == "ACTION_RANGE_CHECK_UPDATE" then
|
|
ranges[arg1] = arg3 and NormalizeRange(arg2) or nil
|
|
for _, state in ipairs(states) do if state.slot == arg1 then Paint(state) end end
|
|
else
|
|
-- Unchanged native results do not fire again: retain their cached state.
|
|
AllChanged()
|
|
end
|
|
end)
|
|
for _, prefix in ipairs({"ActionButton", "BonusActionButton", "MultiBarBottomLeftButton",
|
|
"MultiBarBottomRightButton", "MultiBarLeftButton", "MultiBarRightButton"}) do
|
|
for i = 1, 12 do
|
|
local button = _G[prefix .. i]
|
|
if button then
|
|
local state = {button = button, icon = _G[prefix .. i .. "Icon"]}
|
|
byButton[button] = state
|
|
table.insert(states, state)
|
|
ShaguTweaks.HookScript(button, "OnShow", ButtonChanged)
|
|
ShaguTweaks.HookScript(button, "OnHide", ButtonChanged)
|
|
end
|
|
end
|
|
end
|
|
hooksecurefunc("ActionButton_Update", ButtonChanged)
|
|
hooksecurefunc("ActionButton_UpdateUsable", ButtonChanged)
|
|
AllChanged()
|
|
return true
|
|
end
|
|
|
|
module.enable = function(self)
|
|
if EnableNativeRange() then return end
|
|
hooksecurefunc("ActionButton_OnUpdate", function(elapsed)
|
|
-- Blizzard sets rangeTimer only for actions that have a range. Use our
|
|
-- own countdown instead of sharing it: decrementing rangeTimer here and
|
|
-- testing "<= 0.2" ran this check on every frame for every ranged
|
|
-- button, and kept Blizzard's own range check from ever firing.
|
|
if this.rangeTimer then
|
|
local timer = (this.shaguRangeTimer or 0) - (elapsed or 0)
|
|
if timer > 0 then
|
|
this.shaguRangeTimer = timer
|
|
else
|
|
this.shaguRangeTimer = RANGE_INTERVAL
|
|
local action = ActionButton_GetPagedID(this)
|
|
local inRange = GetActionRange(action)
|
|
-- Look the icon up once instead of building its name each time.
|
|
local icon = this.shaguRangeIcon
|
|
if not icon then
|
|
icon = _G[this:GetName() .. "Icon"]
|
|
this.shaguRangeIcon = icon
|
|
end
|
|
|
|
if icon then
|
|
if inRange == 0 then
|
|
if not this.a then
|
|
this.r, this.g, this.b, this.a = 0.85, 0.18, 0.18, 1
|
|
end
|
|
icon:SetVertexColor(this.r, this.g, this.b, this.a)
|
|
else
|
|
-- Restore the normal usability tint even if the old
|
|
-- red action is now unusable or short of mana.
|
|
local usable, noMana = IsUsableAction(action)
|
|
if usable and usable ~= 0 then
|
|
icon:SetVertexColor(1, 1, 1, 1)
|
|
elseif noMana and noMana ~= 0 then
|
|
icon:SetVertexColor(0.5, 0.5, 1, 1)
|
|
else
|
|
icon:SetVertexColor(0.4, 0.4, 0.4, 1)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end, true)
|
|
end
|