Files

268 lines
8.8 KiB
Lua

local _G = ShaguTweaks.GetGlobalEnv()
local L = ShaguTweaks.L
local API = ShaguTweaks.API
local libspell = ShaguTweaks.libspell
local libtipscan = ShaguTweaks.libtipscan
local hooksecurefunc = ShaguTweaks.hooksecurefunc
if ShaguTweaks.GetExpansion() ~= "vanilla" then return end
local valid_units = {}
valid_units["pet"] = true
valid_units["player"] = true
valid_units["target"] = true
valid_units["mouseover"] = true
valid_units["pettarget"] = true
valid_units["playertarget"] = true
valid_units["targettarget"] = true
valid_units["mouseovertarget"] = true
valid_units["targettargettarget"] = true
for i=1,4 do valid_units["party" .. i] = true end
for i=1,4 do valid_units["partypet" .. i] = true end
for i=1,40 do valid_units["raid" .. i] = true end
for i=1,40 do valid_units["raidpet" .. i] = true end
for i=1,4 do valid_units["party" .. i .. "target"] = true end
for i=1,4 do valid_units["partypet" .. i .. "target"] = true end
for i=1,40 do valid_units["raid" .. i .. "target"] = true end
for i=1,40 do valid_units["raidpet" .. i .. "target"] = true end
valid_units.focus = true
valid_units.focustarget = true
local libcast = CreateFrame("Frame", "ShaguTweaksEnemyCast")
ShaguTweaks.libcast = libcast
local player = UnitName("player")
local _, playerClass = UnitClass("player")
local useClassicCasts = API and API.casts
local needsCustomHunter = useClassicCasts and playerClass == "HUNTER"
local lastrank
local scanner
local function GetScanner()
if not scanner then scanner = libtipscan:GetScanner("libcast") end
return scanner
end
libcast.db = {}
if needsCustomHunter then libcast.db[player] = {} end
function libcast:Prune(now)
for key, cast in pairs(self.db) do
if key ~= player and (not cast.start or not cast.casttime or cast.start + cast.casttime/1000 <= now) then self.db[key] = nil end
end
end
local function ReadCached(key, channel)
local cast = key and libcast.db[key]
if not cast or not cast.cast or not cast.start or not cast.casttime then return end
if cast.start + cast.casttime/1000 <= GetTime() then
if key == player then cast.cast = nil else libcast.db[key] = nil end
return
end
if (cast.channel and true or false) ~= channel then return end
return cast.cast, cast.rank or "", "", cast.icon, cast.start*1000,
cast.start*1000 + cast.casttime, nil
end
ShaguTweaks.UnitCastingInfo = function(unit)
if valid_units[unit] then
local name, rank, text, icon, startTime, endTime, trade = API.GetCastInfo(unit)
if name then return name, rank, text, icon, startTime, endTime, trade end
-- A missing native cast is authoritative. Only the local Hunter's custom
-- emulation may fill a native gap; never consult a name-keyed remote cache.
if unit ~= "player" or not needsCustomHunter then return end
unit = player
end
return ReadCached(unit, false)
end
ShaguTweaks.UnitChannelInfo = function(unit)
if valid_units[unit] then
return API.GetChannelInfo(unit)
end
return ReadCached(unit, true)
end
-- No ClassicAPI remote event mirror or combat-text parser. Official castbars
-- query ClassicAPI directly; optional SuperWoW owns timed GUID records only.
if needsCustomHunter then
libcast:RegisterEvent("SPELLCAST_STOP")
libcast:RegisterEvent("SPELLCAST_FAILED")
libcast:RegisterEvent("SPELLCAST_INTERRUPTED")
libcast:SetScript("OnEvent", function()
local cast = libcast.db[player]
if cast then cast.cast = nil end
lastrank = nil
end)
--[[ Custom Casts
Enable Castbars for spells that don't have a castbar by default
(e.g Multi-Shot and Aimed Shot)
]]--
local aimedshot = L["customcast"]["AIMEDSHOT"]
local multishot = L["customcast"]["MULTISHOT"]
libcast.customcast = {}
libcast.customcast[strlower(aimedshot)] = function(begin, duration)
if begin then
local duration = duration or 3000
for i=1,32 do
local buff = UnitBuff("player", i)
if buff == "Interface\\Icons\\Racial_Troll_Berserk" then
local berserk = 0.3
if((UnitHealth("player")/UnitHealthMax("player")) >= 0.40) then
berserk = (1.30 - (UnitHealth("player") / UnitHealthMax("player"))) / 3
end
duration = duration / (1 + berserk)
elseif buff == "Interface\\Icons\\Ability_Hunter_RunningShot" then
duration = duration / 1.4
elseif buff == "Interface\\Icons\\Ability_Warrior_InnerRage" then
duration = duration / 1.3
elseif buff == "Interface\\Icons\\Inv_Trinket_Naxxramas04" then
duration = duration / 1.2
end
end
local _,_, lag = GetNetStats()
local start = GetTime() + lag/1000
-- add cast action to the database
libcast.db[player].cast = aimedshot
libcast.db[player].rank = lastrank
libcast.db[player].start = start
libcast.db[player].casttime = duration
libcast.db[player].icon = "Interface\\Icons\\Inv_spear_07"
libcast.db[player].channel = nil
else
-- remove cast action to the database
libcast.db[player].cast = nil
libcast.db[player].rank = nil
libcast.db[player].start = nil
libcast.db[player].casttime = nil
libcast.db[player].icon = nil
libcast.db[player].channel = nil
end
end
libcast.customcast[strlower(multishot)] = function(begin, duration)
if begin then
local duration = duration or 500
for i=1,32 do
local buff = UnitBuff("player", i)
if buff == "Interface\\Icons\\Racial_Troll_Berserk" then
local berserk = 0.3
if((UnitHealth("player")/UnitHealthMax("player")) >= 0.40) then
berserk = (1.30 - (UnitHealth("player") / UnitHealthMax("player"))) / 3
end
duration = duration / (1 + berserk)
elseif buff == "Interface\\Icons\\Ability_Hunter_RunningShot" then
duration = duration / 1.4
elseif buff == "Interface\\Icons\\Ability_Warrior_InnerRage" then
duration = duration / 1.3
elseif buff == "Interface\\Icons\\Inv_Trinket_Naxxramas04" then
duration = duration / 1.2
end
end
local _,_, lag = GetNetStats()
local start = GetTime() + lag/1000
-- add cast action to the database
libcast.db[player].cast = multishot
libcast.db[player].rank = lastrank
libcast.db[player].start = start
libcast.db[player].casttime = duration
libcast.db[player].icon = "Interface\\Icons\\Ability_upgrademoonglaive"
libcast.db[player].channel = nil
else
-- remove cast action to the database
libcast.db[player].cast = nil
libcast.db[player].rank = nil
libcast.db[player].start = nil
libcast.db[player].casttime = nil
libcast.db[player].icon = nil
libcast.db[player].channel = nil
end
end
local function GetCustomCast(spell)
if not spell then return end
local lowerSpell = strlower(spell)
for custom, func in pairs(libcast.customcast) do
if strfind(lowerSpell, custom) then
return func
end
end
end
local function CastCustom(spell)
local func = GetCustomCast(spell)
if func and not ShaguTweaks.UnitCastingInfo("player") then
func(true)
end
end
hooksecurefunc("CastSpell", function(id, bookType)
local spellName, rank = GetSpellName(id, bookType)
if not GetCustomCast(spellName) then return end
lastrank = rank
if GetSpellCooldown(id, bookType) ~= 0 then
CastCustom(spellName)
end
end)
hooksecurefunc("CastSpellByName", function(spell, target)
-- Only Aimed Shot and Multi-Shot need the legacy custom-cast emulation.
-- Ordinary macro casts can return immediately instead of resolving their
-- spellbook rank on every invocation.
local custom = GetCustomCast(spell)
if not custom then return end
_, lastrank = libspell.GetSpellInfo(spell)
for i=1,120 do
-- detect if any cast is ongoing
if IsCurrentAction(i) then
if not ShaguTweaks.UnitCastingInfo(player) then
custom(true)
end
return
end
end
end)
hooksecurefunc("UseAction", function(slot, target, button)
-- ClassicAPI identifies action slots in native code. This avoids building a
-- hidden tooltip for every button press, while keeping the old scanner as a
-- cold compatibility path for an older ClassicAPI action surface.
if API and API.actioninfo and API.GetActionInfo then
local actionType, actionID = API.GetActionInfo(slot)
if actionType == "macro" then
-- The real spell selected by a conditional macro is handled later by
-- CastSpellByName. Do not guess it from the macro's first cast line.
return
elseif actionType == "item" then
lastrank = nil
return
elseif actionType == "spell" and actionID then
local spellName, rank = API.GetSpellInfo(actionID)
if not GetCustomCast(spellName) then return end
lastrank = rank
if not IsCurrentAction(slot) then return end
CastCustom(spellName)
return
elseif actionType then
return
end
end
if GetActionText(slot) or not IsCurrentAction(slot) then return end
local tip = GetScanner()
tip:SetAction(slot)
local spellName, rank = tip:Line(1)
lastrank = rank
CastCustom(spellName)
end)
end