Add files via upload

This commit is contained in:
Dusk-92
2026-07-28 14:26:42 +02:00
committed by GitHub
commit d2ae117132
87 changed files with 36309 additions and 0 deletions
+409
View File
@@ -0,0 +1,409 @@
local _G = ShaguTweaks.GetGlobalEnv()
local L = ShaguTweaks.L
local GetExpansion = ShaguTweaks.GetExpansion
local libtipscan = ShaguTweaks.libtipscan
local libspell = ShaguTweaks.libspell
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local cmatch = ShaguTweaks.cmatch
-- return instantly if we're not on a vanilla client
if 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
local lastcasttex, lastrank, _
local scanner = libtipscan:GetScanner("libcast")
local libcast = CreateFrame("Frame", "ShaguTweaksEnemyCast")
local player = UnitName("player")
ShaguTweaks.UnitChannelInfo = _G.UnitChannelInfo or function(unit)
-- convert to name if unitstring was given
unit = valid_units[unit] and UnitName(unit) or unit
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
local db = libcast.db[unit]
-- clean legacy values
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
if not db.channel then return end
cast = db.cast
nameSubtext = db.rank
text = ""
texture = db.icon
startTime = db.start * 1000
endTime = startTime + db.casttime
isTradeSkill = nil
elseif db then
-- remove cast action to the database
db.cast = nil
db.rank = nil
db.start = nil
db.casttime = nil
db.icon = nil
db.channel = nil
end
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
end
ShaguTweaks.UnitCastingInfo = _G.UnitCastingInfo or function(unit)
-- convert to name if unitstring was given
unit = valid_units[unit] and UnitName(unit) or unit
local cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
local db = libcast.db[unit]
-- clean legacy values
if db and db.cast and db.start + db.casttime / 1000 > GetTime() then
if db.channel then return end
cast = db.cast
nameSubtext = db.rank or ""
text = ""
texture = db.icon
startTime = db.start * 1000
endTime = startTime + db.casttime
isTradeSkill = nil
elseif db then
-- remove cast action to the database
db.cast = nil
db.rank = nil
db.start = nil
db.casttime = nil
db.icon = nil
db.channel = nil
end
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
end
function libcast:AddAction(mob, spell, channel)
if not mob or not spell then return nil end
if L["spells"][spell] ~= nil then
local casttime = L["spells"][spell].t
local icon = L["spells"][spell].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][spell].icon) or nil
-- add cast action to the database
if not self.db[mob] then self.db[mob] = {} end
self.db[mob].cast = spell
self.db[mob].rank = nil
self.db[mob].start = GetTime()
self.db[mob].casttime = casttime
self.db[mob].icon = icon
self.db[mob].channel = channel
return true
end
return nil
end
function libcast:RemoveAction(mob, spell)
if self.db[mob] and ( L["interrupts"][spell] ~= nil or spell == "INTERRUPT" ) then
-- remove cast action to the database
self.db[mob].cast = nil
self.db[mob].rank = nil
self.db[mob].start = nil
self.db[mob].casttime = nil
self.db[mob].icon = nil
self.db[mob].channel = nil
end
end
-- main data
libcast.db = { [player] = {} }
-- environmental casts
libcast:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF")
libcast:RegisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_PARTY_BUFF")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS")
libcast:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE")
libcast:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF")
-- player spells
libcast:RegisterEvent("SPELLCAST_START")
libcast:RegisterEvent("SPELLCAST_STOP")
libcast:RegisterEvent("SPELLCAST_FAILED")
libcast:RegisterEvent("SPELLCAST_INTERRUPTED")
libcast:RegisterEvent("SPELLCAST_DELAYED")
libcast:RegisterEvent("SPELLCAST_CHANNEL_START")
libcast:RegisterEvent("SPELLCAST_CHANNEL_STOP")
libcast:RegisterEvent("SPELLCAST_CHANNEL_UPDATE")
libcast:SetScript("OnEvent", function()
-- Fill database with player casts
if event == "SPELLCAST_START" then
local icon = L["spells"][arg1] and L["spells"][arg1].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][arg1].icon) or lastcasttex
-- add cast action to the database
this.db[player].cast = arg1
this.db[player].rank = lastrank
this.db[player].start = GetTime()
this.db[player].casttime = arg2
this.db[player].icon = icon
this.db[player].channel = nil
if not L["spells"][arg1] or not L["spells"][arg1].icon or not L["spells"][arg1].t then
L["spells"][arg1] = L["spells"][arg1] or { }
L["spells"][arg1].icon = L["spells"][arg1].icon or icon
L["spells"][arg1].t = L["spells"][arg1].t or arg2
end
lastcasttex, lastrank = nil, nil
elseif event == "SPELLCAST_STOP" or event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
if this.db[player] and not this.db[player].channel then
-- remove cast action to the database
this.db[player].cast = nil
this.db[player].rank = nil
this.db[player].rank = nil
this.db[player].start = nil
this.db[player].casttime = nil
this.db[player].icon = nil
this.db[player].channel = nil
else
lastcasttex, lastrank = nil, nil
end
elseif event == "SPELLCAST_DELAYED" then
if this.db[player].cast then
this.db[player].start = this.db[player].start + arg1/1000
end
elseif event == "SPELLCAST_CHANNEL_START" then
-- add cast action to the database
this.db[player].cast = arg2
this.db[player].rank = lastrank
this.db[player].start = GetTime()
this.db[player].casttime = arg1
this.db[player].icon = L["spells"][arg2] and L["spells"][arg2].icon and string.format("%s%s", "Interface\\Icons\\", L["spells"][arg2].icon) or lastcasttex
this.db[player].channel = true
lastcasttex, lastrank = nil, nil
elseif event == "SPELLCAST_CHANNEL_STOP" then
if this.db[player] and this.db[player].channel then
-- remove cast action to the database
this.db[player].cast = nil
this.db[player].rank = nil
this.db[player].start = nil
this.db[player].casttime = nil
this.db[player].icon = nil
this.db[player].channel = nil
end
elseif event == "SPELLCAST_CHANNEL_UPDATE" then
if this.db[player].cast then
this.db[player].start = -this.db[player].casttime/1000 + GetTime() + arg1/1000
end
-- Fill database with environmental casts
elseif arg1 then
local mob, spell, _
-- (.+) begins to cast (.+).
mob, spell = cmatch(arg1, SPELLCASTOTHERSTART)
if libcast:AddAction(mob, spell) then return end
-- (.+) begins to perform (.+).
mob, spell = cmatch(arg1, SPELLPERFORMOTHERSTART)
if libcast:AddAction(mob, spell) then return end
-- (.+) gains (.+).
mob, spell = cmatch(arg1, AURAADDEDOTHERHELPFUL)
if libcast:RemoveAction(mob, spell) then return end
-- (.+) is afflicted by (.+).
mob, spell = cmatch(arg1, AURAADDEDOTHERHARMFUL)
if libcast:RemoveAction(mob, spell) then return end
-- Your (.+) hits (.+) for (%d+).
spell, mob = cmatch(arg1, SPELLLOGSELFOTHER)
if libcast:RemoveAction(mob, spell) then return end
-- Your (.+) crits (.+) for (%d+).
spell, mob = cmatch(arg1, SPELLLOGCRITSELFOTHER)
if libcast:RemoveAction(mob, spell) then return end
-- (.+)'s (.+) %a hits (.+) for (%d+).
_, spell, mob = cmatch(arg1, SPELLLOGOTHEROTHER)
if libcast:RemoveAction(mob, spell) then return end
-- (.+)'s (.+) %a crits (.+) for (%d+).
_, spell, mob = cmatch(arg1, SPELLLOGCRITOTHEROTHER)
if libcast:RemoveAction(mob, spell) then return end
-- You interrupt (.+)'s (.+).
mob, spell = cmatch(arg1, SPELLINTERRUPTSELFOTHER)
if libcast:RemoveAction(mob, spell) then return end
-- (.+) interrupts (.+)'s (.+).
_, mob, spell = cmatch(arg1, SPELLINTERRUPTOTHEROTHER)
if libcast:RemoveAction(mob, spell) then return end
end
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
if UnitBuff("player", i) == "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 UnitBuff("player", i) == "Interface\\Icons\\Ability_Hunter_RunningShot" then
duration = duration / 1.4
elseif UnitBuff("player", i) == "Interface\\Icons\\Ability_Warrior_InnerRage" then
duration = duration / 1.3
elseif UnitBuff("player", i) == "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
if UnitBuff("player", i) == "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 UnitBuff("player", i) == "Interface\\Icons\\Ability_Hunter_RunningShot" then
duration = duration / 1.4
elseif UnitBuff("player", i) == "Interface\\Icons\\Ability_Warrior_InnerRage" then
duration = duration / 1.3
elseif UnitBuff("player", i) == "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 CastCustom(spell)
if not spell then return end
if not ShaguTweaks.UnitCastingInfo(UnitName("player")) then
for custom, func in pairs(libcast.customcast) do
if strfind(strlower(spell), custom) or strlower(spell) == custom then
func(true)
end
end
end
end
hooksecurefunc("UseContainerItem", function(id, index)
lastcasttex = GetContainerItemInfo(id, index)
end)
hooksecurefunc("CastSpell", function(id, bookType)
_, lastrank = libspell.GetSpellInfo(id, bookType)
lastcasttex = GetSpellTexture(id, bookType)
if GetSpellCooldown(id, bookType) ~= 0 then
local spellName = GetSpellName(id, bookType)
CastCustom(spellName)
end
end)
hooksecurefunc("CastSpellByName", function(spell, target)
_, lastrank = libspell.GetSpellInfo(spell)
for i=1,120 do
-- detect if any cast is ongoing
if IsCurrentAction(i) then
CastCustom(spell)
return
end
end
end)
hooksecurefunc("UseAction", function(slot, target, button)
scanner:SetAction(slot)
local spellName, rank = scanner:Line(1)
lastcasttex = GetActionTexture(slot)
lastrank = rank
if GetActionText(slot) or not IsCurrentAction(slot) then return end
CastCustom(spellName)
end)
ShaguTweaks.libcast = libcast
+279
View File
@@ -0,0 +1,279 @@
local _G = ShaguTweaks.GetGlobalEnv()
local L = ShaguTweaks.L
local GetExpansion = ShaguTweaks.GetExpansion
local libtipscan = ShaguTweaks.libtipscan
local libspell = ShaguTweaks.libspell
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local QueueFunction = ShaguTweaks.QueueFunction
local cmatch = ShaguTweaks.cmatch
-- return instantly if we're not on a vanilla client
if GetExpansion() ~= "vanilla" then return end
-- fix a typo (missing $) in ruRU capture index
if GetLocale() == "ruRU" then
SPELLREFLECTSELFOTHER = gsub(SPELLREFLECTSELFOTHER, "%%2s", "%%2%$s")
end
local libdebuff = CreateFrame("Frame", "ShaguTweaksDebuffsScanner", UIParent)
local scanner = libtipscan:GetScanner("libdebuff")
local _, class = UnitClass("player")
local lastspell
function libdebuff:GetDuration(effect, rank)
if L["debuffs"][effect] then
local rank = rank and tonumber((string.gsub(rank, RANK .. " ", ""))) or 0
local rank = L["debuffs"][effect][rank] and rank or libdebuff:GetMaxRank(effect)
local duration = L["debuffs"][effect][rank]
if effect == L["dyndebuffs"]["Rupture"] then
-- Rupture: +2 sec per combo point
duration = duration + GetComboPoints()*2
elseif effect == L["dyndebuffs"]["Kidney Shot"] then
-- Kidney Shot: +1 sec per combo point
duration = duration + GetComboPoints()*1
elseif effect == L["dyndebuffs"]["Demoralizing Shout"] then
-- Booming Voice: 10% per talent
local _,_,_,_,count = GetTalentInfo(2,1)
if count and count > 0 then duration = duration + ( duration / 100 * (count*10)) end
elseif effect == L["dyndebuffs"]["Shadow Word: Pain"] then
-- Improved Shadow Word: Pain: +3s per talent
local _,_,_,_,count = GetTalentInfo(3,4)
if count and count > 0 then duration = duration + count * 3 end
elseif effect == L["dyndebuffs"]["Frostbolt"] then
-- Permafrost: +1s per talent
local _,_,_,_,count = GetTalentInfo(3,7)
if count and count > 0 then duration = duration + count end
elseif effect == L["dyndebuffs"]["Gouge"] then
-- Improved Gouge: +.5s per talent
local _,_,_,_,count = GetTalentInfo(2,1)
if count and count > 0 then duration = duration + (count*.5) end
end
return duration
else
return 0
end
end
function libdebuff:UpdateDuration(unit, unitlevel, effect, duration)
if not unit or not effect or not duration then return end
unitlevel = unitlevel or 0
if libdebuff.objects[unit] and libdebuff.objects[unit][unitlevel] and libdebuff.objects[unit][unitlevel][effect] then
libdebuff.objects[unit][unitlevel][effect].duration = duration
end
end
function libdebuff:GetMaxRank(effect)
local max = 0
for id in pairs(L["debuffs"][effect]) do
if id > max then max = id end
end
return max
end
function libdebuff:UpdateUnits()
TargetDebuffButton_Update()
end
function libdebuff:AddPending(unit, unitlevel, effect, duration)
if not unit then return end
if not L["debuffs"][effect] then return end
if duration > 0 and libdebuff.pending[3] ~= effect then
libdebuff.pending[1] = unit
libdebuff.pending[2] = unitlevel or 0
libdebuff.pending[3] = effect
libdebuff.pending[4] = duration or libdebuff:GetDuration(effect)
end
end
function libdebuff:RemovePending()
libdebuff.pending[1] = nil
libdebuff.pending[2] = nil
libdebuff.pending[3] = nil
libdebuff.pending[4] = nil
end
function libdebuff:PersistPending(effect)
if not libdebuff.pending[3] then return end
if libdebuff.pending[3] == effect or ( effect == nil and libdebuff.pending[3] ) then
libdebuff:AddEffect(libdebuff.pending[1], libdebuff.pending[2], libdebuff.pending[3], libdebuff.pending[4])
libdebuff:RemovePending()
end
end
function libdebuff:RevertLastAction()
lastspell.start = lastspell.start_old
lastspell.start_old = nil
libdebuff:UpdateUnits()
end
function libdebuff:AddEffect(unit, unitlevel, effect, duration)
if not unit or not effect then return end
unitlevel = unitlevel or 0
if not libdebuff.objects[unit] then libdebuff.objects[unit] = {} end
if not libdebuff.objects[unit][unitlevel] then libdebuff.objects[unit][unitlevel] = {} end
if not libdebuff.objects[unit][unitlevel][effect] then libdebuff.objects[unit][unitlevel][effect] = {} end
-- save current effect as lastspell
lastspell = libdebuff.objects[unit][unitlevel][effect]
libdebuff.objects[unit][unitlevel][effect].effect = effect
libdebuff.objects[unit][unitlevel][effect].start_old = libdebuff.objects[unit][unitlevel][effect].start
libdebuff.objects[unit][unitlevel][effect].start = GetTime()
libdebuff.objects[unit][unitlevel][effect].duration = duration or libdebuff:GetDuration(effect)
libdebuff:UpdateUnits()
end
-- scan for debuff application
libdebuff:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE")
libdebuff:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE")
libdebuff:RegisterEvent("CHAT_MSG_SPELL_FAILED_LOCALPLAYER")
libdebuff:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
libdebuff:RegisterEvent("PLAYER_TARGET_CHANGED")
libdebuff:RegisterEvent("SPELLCAST_STOP")
libdebuff:RegisterEvent("UNIT_AURA")
-- register seal handler
if class == "PALADIN" then
libdebuff:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
end
-- Remove Pending
libdebuff.rp = { SPELLIMMUNESELFOTHER, IMMUNEDAMAGECLASSSELFOTHER,
SPELLMISSSELFOTHER, SPELLRESISTSELFOTHER, SPELLEVADEDSELFOTHER,
SPELLDODGEDSELFOTHER, SPELLDEFLECTEDSELFOTHER, SPELLREFLECTSELFOTHER,
SPELLPARRIEDSELFOTHER, SPELLLOGABSORBSELFOTHER }
libdebuff.objects = {}
libdebuff.pending = {}
-- Gather Data by Events
libdebuff:SetScript("OnEvent", function()
-- paladin seal refresh
if event == "CHAT_MSG_COMBAT_SELF_HITS" then
local hit = cmatch(arg1, COMBATHITSELFOTHER)
local crit = cmatch(arg1, COMBATHITCRITSELFOTHER)
if hit or crit then
for seal in L["judgements"] do
local name = UnitName("target")
local level = UnitLevel("target")
if name and libdebuff.objects[name] then
if level and libdebuff.objects[name][level] and libdebuff.objects[name][level][seal] then
libdebuff:AddEffect(name, level, seal)
elseif libdebuff.objects[name][0] and libdebuff.objects[name][0][seal] then
libdebuff:AddEffect(name, 0, seal)
end
end
end
end
-- Add Combat Log
elseif event == "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE" or event == "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE" then
local unit, effect = cmatch(arg1, AURAADDEDOTHERHARMFUL)
if unit and effect then
local unitlevel = UnitName("target") == unit and UnitLevel("target") or 0
if not libdebuff.objects[unit] or not libdebuff.objects[unit][unitlevel] or not libdebuff.objects[unit][unitlevel][effect] then
libdebuff:AddEffect(unit, unitlevel, effect)
end
end
-- Add Missing Buffs by Iteration
elseif ( event == "UNIT_AURA" and arg1 == "target" ) or event == "PLAYER_TARGET_CHANGED" then
for i=1, 16 do
local effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff("target", i)
-- abort when no further debuff was found
if not texture then return end
if texture and effect and effect ~= "" then
-- don't overwrite existing timers
local unitlevel = UnitLevel("target") or 0
local unit = UnitName("target")
if not libdebuff.objects[unit] or not libdebuff.objects[unit][unitlevel] or not libdebuff.objects[unit][unitlevel][effect] then
libdebuff:AddEffect(unit, unitlevel, effect)
end
end
end
-- Update Pending Spells
elseif event == "CHAT_MSG_SPELL_FAILED_LOCALPLAYER" or event == "CHAT_MSG_SPELL_SELF_DAMAGE" then
-- Remove pending spell
for _, msg in pairs(libdebuff.rp) do
local effect = cmatch(arg1, msg)
if effect and libdebuff.pending[3] == effect then
-- instant removal of the pending spell
libdebuff:RemovePending()
return
elseif effect and lastspell and lastspell.start_old and lastspell.effect == effect then
-- late removal of debuffs (e.g hunter arrows as they hit late)
libdebuff:RevertLastAction()
return
end
end
elseif event == "SPELLCAST_STOP" then
QueueFunction(libdebuff.PersistPending)
end
end)
-- Gather Data by User Actions
hooksecurefunc("CastSpell", function(id, bookType)
local effect = GetSpellName(id, bookType)
local _, rank = libspell.GetSpellInfo(id, bookType)
local duration = libdebuff:GetDuration(effect, rank)
libdebuff:AddPending(UnitName("target"), UnitLevel("target"), effect, duration)
end)
hooksecurefunc("CastSpellByName", function(effect, target)
local _, rank = libspell.GetSpellInfo(effect)
local duration = libdebuff:GetDuration(effect, rank)
libdebuff:AddPending(UnitName("target"), UnitLevel("target"), effect, duration)
end)
hooksecurefunc("UseAction", function(slot, target, button)
if GetActionText(slot) or not IsCurrentAction(slot) then return end
scanner:SetAction(slot)
local effect, rank = scanner:Line(1)
local duration = libdebuff:GetDuration(effect, rank)
libdebuff:AddPending(UnitName("target"), UnitLevel("target"), effect, duration)
end)
function libdebuff:UnitDebuff(unit, id)
local unitname = UnitName(unit)
local unitlevel = UnitLevel(unit)
local texture, stacks, dtype = UnitDebuff(unit, id)
local duration, timeleft = nil, -1
local rank = nil -- no backport
local effect
if texture then
scanner:SetUnitDebuff(unit, id)
effect = scanner:Line(1) or ""
end
if libdebuff.objects[unitname] and libdebuff.objects[unitname][unitlevel] and libdebuff.objects[unitname][unitlevel][effect] then
-- clean up cache
if libdebuff.objects[unitname][unitlevel][effect].duration and libdebuff.objects[unitname][unitlevel][effect].duration + libdebuff.objects[unitname][unitlevel][effect].start < GetTime() then
libdebuff.objects[unitname][unitlevel][effect] = nil
else
duration = libdebuff.objects[unitname][unitlevel][effect].duration
timeleft = duration + libdebuff.objects[unitname][unitlevel][effect].start - GetTime()
end
-- no level data
elseif libdebuff.objects[unitname] and libdebuff.objects[unitname][0] and libdebuff.objects[unitname][0][effect] then
-- clean up cache
if libdebuff.objects[unitname][0][effect].duration and libdebuff.objects[unitname][0][effect].duration + libdebuff.objects[unitname][0][effect].start < GetTime() then
libdebuff.objects[unitname][0][effect] = nil
else
duration = libdebuff.objects[unitname][0][effect].duration
timeleft = duration + libdebuff.objects[unitname][0][effect].start - GetTime()
end
end
return effect, rank, texture, stacks, dtype, duration, timeleft
end
ShaguTweaks.libdebuff = libdebuff
+108
View File
@@ -0,0 +1,108 @@
local _G = ShaguTweaks.GetGlobalEnv()
local L = ShaguTweaks.L
local GetExpansion = ShaguTweaks.GetExpansion
local mobdb = {}
local target, dmg, perc, diff = nil, 0, 0, 0, 0
local UnitHealth, UnitHealthMax
local libhealth = CreateFrame("Frame")
libhealth.enabled = true
libhealth.reqhit = 4
libhealth.reqdmg = 5
libhealth:RegisterEvent("UNIT_HEALTH")
libhealth:RegisterEvent("UNIT_COMBAT")
libhealth:RegisterEvent("PLAYER_TARGET_CHANGED")
libhealth:RegisterEvent("PLAYER_ENTERING_WORLD")
libhealth:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- create initial health cache tables
ShaguTweaks_cache = ShaguTweaks_cache or {}
ShaguTweaks_cache["libhealth"] = ShaguTweaks_cache["libhealth"] or {}
mobdb = ShaguTweaks_cache["libhealth"]
-- load enable state and set functions
libhealth.enabled = true
libhealth.reqhit = 2
libhealth.reqdmg = 10
end
-- return as we're not supposed to be here
if not libhealth.enabled then this:UnregisterAllEvents() return end
-- try to estimate mob health based on combat and health events
if event == "PLAYER_TARGET_CHANGED" then
dmg, perc = 0, _G.UnitHealth("target")
if UnitName("target") and UnitLevel("target") and _G.UnitHealthMax("target") == 100 then
target = string.format("%s:%d",UnitName("target"), UnitLevel("target"))
else
target = nil
end
elseif target and event == "UNIT_COMBAT" and arg1 == "target" then
if arg2 and arg2 == "HEAL" then return end
dmg = dmg + arg4
elseif target and event == "UNIT_HEALTH" and arg1 == "target" then
diff = perc-_G.UnitHealth("target")
if dmg > 0 and diff > 0 then
mobdb[target] = mobdb[target] or {}
if not mobdb[target][2] or diff > mobdb[target][2] then
mobdb[target][1] = ceil(dmg / diff * 100)
mobdb[target][2] = diff
mobdb[target][3] = mobdb[target][3] and mobdb[target][3] + 1 or 1
end
end
end
end)
-- core function to query the generated database
local unit, level, cur, max, dbstring
local function GetUnitHealth(self, unitstr)
unit = UnitName(unitstr)
level = UnitLevel(unitstr)
cur = _G.UnitHealth(unitstr)
max = _G.UnitHealthMax(unitstr)
-- return raw values if another addon is replacing global API calls
if cur > 100 or max > 100 or max < 100 then
return cur, max, true
end
-- return raw values if no unit data could be found (unlikely but happened...)
if not unit or not level then
return cur, max
end
-- query the database for known values
dbstring = string.format("%s:%s", unit, level)
if mobdb[dbstring] and mobdb[dbstring][1] and mobdb[dbstring][2] > libhealth.reqdmg and (not mobdb[dbstring][3] or mobdb[dbstring][3] > libhealth.reqhit) then
return ceil(mobdb[dbstring][1]/100*cur), mobdb[dbstring][1], true
end
-- return raw values as fallback
return cur, max
end
local function GetUnitHealthByName(self, unit, level, cur, max)
-- return raw values if another addon is replacing global API calls
if cur > 100 or max > 100 or max < 100 then
return cur, max, true
end
-- return raw values if no unit data could be found (unlikely but happened...)
if not unit or not level then
return cur, max
end
-- query the database for known values
dbstring = string.format("%s:%s", unit, level)
if mobdb[dbstring] and mobdb[dbstring][1] and mobdb[dbstring][2] > libhealth.reqdmg and (not mobdb[dbstring][3] or mobdb[dbstring][3] > libhealth.reqhit) then
return ceil(mobdb[dbstring][1]/100*cur), mobdb[dbstring][1], true
end
-- return raw values as fallback
return cur, max
end
-- add api calls to global tree
ShaguTweaks.libhealth = libhealth
ShaguTweaks.libhealth.GetUnitHealth = GetUnitHealth
ShaguTweaks.libhealth.GetUnitHealthByName = GetUnitHealthByName
+75
View File
@@ -0,0 +1,75 @@
local _G = ShaguTweaks.GetGlobalEnv()
local GetExpansion = ShaguTweaks.GetExpansion
local GetUnitData = ShaguTweaks.GetUnitData
local NAMEPLATE_OBJECTORDER = { "border", "glow", "name", "level", "levelicon", "raidicon" }
local NAMEPLATE_TYPE = "Button"
if GetExpansion() == "tbc" then
NAMEPLATE_OBJECTORDER = { "border", "castborder", "casticon", "glow", "name", "level", "levelicon", "raidicon" }
NAMEPLATE_TYPE = "Frame"
end
local function IsNamePlate(frame)
if frame:GetObjectType() ~= NAMEPLATE_TYPE then return nil end
regions = frame:GetRegions()
if not regions then return nil end
if not regions.GetObjectType then return nil end
if not regions.GetTexture then return nil end
if regions:GetObjectType() ~= "Texture" then return nil end
return regions:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" or nil
end
local registry = {}
local initialized = 0
local parentcount, childs, plate
ShaguTweaks.libnameplate = CreateFrame("Frame", nil, UIParent)
ShaguTweaks.libnameplate.OnInit = {}
ShaguTweaks.libnameplate.OnShow = {}
ShaguTweaks.libnameplate.OnUpdate = {}
ShaguTweaks.libnameplate:SetScript("OnUpdate", function()
parentcount = WorldFrame:GetNumChildren()
if initialized < parentcount then
childs = { WorldFrame:GetChildren() }
for i = initialized + 1, parentcount do
plate = childs[i]
if IsNamePlate(plate) and not registry[plate] then
plate.healthbar = plate:GetChildren()
for i, object in pairs({plate:GetRegions()}) do
if plate and NAMEPLATE_OBJECTORDER[i] then
plate[NAMEPLATE_OBJECTORDER[i]] = object
end
end
-- run OnInit functions
for id, func in pairs(ShaguTweaks.libnameplate.OnInit) do
func(plate)
end
-- register OnUpdate functions
local oldUpdate = plate:GetScript("OnUpdate")
plate:SetScript("OnUpdate", function(self, elapsed)
if oldUpdate then oldUpdate(self, elapsed) end
for id, func in pairs(ShaguTweaks.libnameplate.OnUpdate) do
func(self, elapsed)
end
end)
-- register OnShow functions
local oldShow = plate:GetScript("OnShow")
plate:SetScript("OnShow", function(self)
if oldShow then oldShow(self) end
for id, func in pairs(ShaguTweaks.libnameplate.OnShow) do
func(self)
end
end)
registry[plate] = plate
end
end
initialized = parentcount
end
end)
+469
View File
@@ -0,0 +1,469 @@
local _G = ShaguTweaks.GetGlobalEnv()
local L = ShaguTweaks.L
local GetExpansion = ShaguTweaks.GetExpansion
local libtipscan = ShaguTweaks.libtipscan
local libspell = ShaguTweaks.libspell
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local strsplit = ShaguTweaks.strsplit
local cmatch = ShaguTweaks.cmatch
local senttarget
local heals, ress, events = {}, {}, {}
local PRAYER_OF_HEALING
do -- Prayer of Healing
local locales = {
["deDE"] = "Gebet der Heilung",
["enUS"] = "Prayer of Healing",
["esES"] = "Rezo de curación",
["frFR"] = "Prière de soins",
["koKR"] = "치유의 기원",
["ruRU"] = "Молитва исцеления",
["zhCN"] = "治疗祷言",
}
PRAYER_OF_HEALING = locales[GetLocale()] or locales["enUS"]
end
local libpredict = CreateFrame("Frame")
libpredict:RegisterEvent("UNIT_HEALTH")
libpredict:RegisterEvent("CHAT_MSG_ADDON")
libpredict:RegisterEvent("PLAYER_TARGET_CHANGED")
libpredict:SetScript("OnEvent", function()
if event == "CHAT_MSG_ADDON" and (arg1 == "HealComm" or arg1 == "CTRA") then
this:ParseChatMessage(arg4, arg2, arg1)
elseif event == "UNIT_HEALTH" then
local name = UnitName(arg1)
if ress[name] and not UnitIsDeadOrGhost(arg1) then
ress[UnitName(arg1)] = nil
end
end
end)
libpredict:SetScript("OnUpdate", function()
-- update on timeout events
for timestamp, targets in pairs(events) do
if GetTime() >= timestamp then
events[timestamp] = nil
end
end
end)
function libpredict:ParseComm(sender, msg)
local msgtype, target, heal, time
if msg == "Healstop" or msg == "GrpHealstop" then
msgtype = "Stop"
elseif msg == "Resurrection/stop/" then
msgtype = "RessStop"
elseif msg then
local msgobj = {strsplit("/", msg)}
if msgobj and msgobj[1] and msgobj[2] then
-- legacy healcomm object
if msgobj[1] == "GrpHealdelay" or msgobj[1] == "Healdelay" then
msgtype, time = "Delay", msgobj[2]
end
if msgobj[1] and msgobj[1] == "Resurrection" and msgobj[2] then
msgtype, target = "Ress", msgobj[2]
end
if msgobj[1] == "Heal" and msgobj[2] then
msgtype, target, heal, time = "Heal", msgobj[2], msgobj[3], msgobj[4]
end
if msgobj[1] == "GrpHeal" and msgobj[2] then
msgtype, target, heal, time = "Heal", {}, msgobj[2], msgobj[3]
for i=4,8 do
if msgobj[i] then table.insert(target, msgobj[i]) end
end
end
elseif select and UnitCastingInfo then
-- latest healcomm
msgtype = tonumber(string.sub(msg, 1, 3))
if not msgtype then return end
if msgtype == 0 then
msgtype = "Heal"
heal = tonumber(string.sub(msg, 4, 8))
target = string.sub(msg,9, -1)
local starttime = select(5, UnitCastingInfo(sender))
local endtime = select(6, UnitCastingInfo(sender))
if not starttime or not endtime then return end
time = endtime - starttime
elseif msgtype == 1 then
msgtype = "Stop"
elseif msgtype == 2 then
msgtype = "Heal"
heal = tonumber(string.sub(msg,4, 8))
target = {strsplit(":", string.sub(msg,9, -1))}
local starttime = select(5, UnitCastingInfo(sender))
local endtime = select(6, UnitCastingInfo(sender))
if not starttime or not endtime then return end
time = endtime - starttime
end
end
end
return msgtype, target, heal, time
end
function libpredict:ParseChatMessage(sender, msg, comm)
local msgtype, target, heal, time
if comm == "HealComm" then
msgtype, target, heal, time = libpredict:ParseComm(sender, msg)
elseif comm == "CTRA" then
local _, _, cmd, ctratarget = string.find(msg, "(%a+)%s?([^#]*)")
if cmd and ctratarget and cmd == "RES" and ctratarget ~= "" and ctratarget ~= UNKNOWN then
msgtype = "Ress"
target = ctratarget
end
end
if msgtype == "Stop" and sender then
libpredict:HealStop(sender)
return
elseif ( msg == "RessStop" or msg == "RESNO" ) and sender then
libpredict:RessStop(sender)
return
elseif msgtype == "Delay" and time then
libpredict:HealDelay(sender, time)
elseif msgtype == "Heal" and target and heal and time then
if type(target) == "table" then
for _, name in pairs(target) do
libpredict:Heal(sender, name, heal, time)
end
else
libpredict:Heal(sender, target, heal, time)
end
elseif msgtype == "Ress" then
libpredict:Ress(sender, target)
end
end
function libpredict:AddEvent(time, target)
events[time] = events[time] or {}
table.insert(events[time], target)
end
function libpredict:Heal(sender, target, amount, duration)
if not sender or not target or not amount or not duration then
return
end
local timeout = duration/1000 + GetTime()
heals[target] = heals[target] or {}
heals[target][sender] = { amount, timeout }
libpredict:AddEvent(timeout, target)
end
function libpredict:HealStop(sender)
for ttarget, t in pairs(heals) do
for tsender in pairs(heals[ttarget]) do
if sender == tsender then
heals[ttarget][tsender] = nil
end
end
end
end
function libpredict:HealDelay(sender, delay)
local delay = delay/1000
for target, t in pairs(heals) do
for tsender, amount in pairs(heals[target]) do
if sender == tsender then
amount[2] = amount[2] + delay
libpredict:AddEvent(amount[2], target)
end
end
end
end
function libpredict:Ress(sender, target)
ress[target] = ress[target] or {}
ress[target][sender] = true
end
function libpredict:RessStop(sender)
for ttarget, t in pairs(ress) do
for tsender in pairs(ress[ttarget]) do
if sender == tsender then
ress[ttarget][tsender] = nil
end
end
end
end
function libpredict:UnitGetIncomingHeals(unit)
if not unit or not UnitName(unit) then return 0 end
if UnitIsDeadOrGhost(unit) then return 0 end
local name = UnitName(unit)
local sumheal = 0
if not heals[name] then
return sumheal
else
for sender, amount in pairs(heals[name]) do
if amount[2] <= GetTime() then
heals[name][sender] = nil
else
sumheal = sumheal + amount[1]
end
end
end
return sumheal
end
function libpredict:UnitHasIncomingResurrection(unit)
if not unit or not UnitName(unit) then return nil end
local name = UnitName(unit)
if not ress[name] then
return nil
else
for sender, val in pairs(ress[name]) do
if val == true then
return val
end
end
end
return nil
end
local spell_queue = { "DUMMY", "DUMMYRank 9", "TARGET" }
local realm = GetRealmName()
local player = UnitName("player")
local cache, gear_string = {}, ""
local resetcache = CreateFrame("Frame")
resetcache:RegisterEvent("PLAYER_ENTERING_WORLD")
resetcache:RegisterEvent("SKILL_LINES_CHANGED")
resetcache:RegisterEvent("UNIT_INVENTORY_CHANGED")
resetcache:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- load and initialize previous caches of spell amounts
ShaguTweaks_cache = ShaguTweaks_cache or {}
ShaguTweaks_cache["prediction"] = ShaguTweaks_cache["prediction"] or {}
ShaguTweaks_cache["prediction"][realm] = ShaguTweaks_cache["prediction"][realm] or {}
ShaguTweaks_cache["prediction"][realm][player] = ShaguTweaks_cache["prediction"][realm][player] or {}
ShaguTweaks_cache["prediction"][realm][player]["heals"] = ShaguTweaks_cache["prediction"][realm][player]["heals"] or {}
cache = ShaguTweaks_cache["prediction"][realm][player]["heals"]
return
end
if event == "UNIT_INVENTORY_CHANGED" then
-- skip non-player events
if arg1 and arg1 ~= "player" then return end
local gear = ""
for id = 1, 18 do
gear = gear .. (GetInventoryItemLink("player",id) or "")
end
-- abort when inventory didn't change
if gear == gear_string then return end
gear_string = gear
end
-- flag all cached heals for renewal
for k in pairs(cache) do
if type(cache[k]) == "number" or type(cache[k]) == "string" then
-- migrate old data
local oldval = cache[k]
cache[k] = { [1] = oldval }
end
-- flag for reset
cache[k][2] = true
end
end)
local function UpdateCache(spell, heal, crit)
if not spell or not heal then return end
local heal = tonumber(heal)
if not cache[spell] or cache[spell][2] then
-- skills or equipment changed, save whatever is detected
cache[spell] = cache[spell] or {}
cache[spell][1] = crit and heal*2/3 or heal
cache[spell][2] = crit
elseif not crit and cache[spell][1] < heal then
-- safe the best heal we can get
cache[spell][1] = heal
cache[spell][2] = nil
end
end
-- Gather Data by User Actions
hooksecurefunc("CastSpell", function(id, bookType)
if not libpredict.sender.enabled then return end
local effect, rank = libspell.GetSpellInfo(id, bookType)
if not effect then return end
spell_queue[1] = effect
spell_queue[2] = effect.. ( rank or "" )
spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
end)
hooksecurefunc("CastSpellByName", function(effect, target)
if not libpredict.sender.enabled then return end
local effect, rank = libspell.GetSpellInfo(effect)
if not effect then return end
spell_queue[1] = effect
spell_queue[2] = effect.. ( rank or "" )
spell_queue[3] = UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
end)
local scanner = libtipscan:GetScanner("prediction")
hooksecurefunc("UseAction", function(slot, target, selfcast)
if not libpredict.sender.enabled then return end
if GetActionText(slot) or not IsCurrentAction(slot) then return end
scanner:SetAction(slot)
local effect, rank = scanner:Line(1)
if not effect then return end
spell_queue[1] = effect
spell_queue[2] = effect.. ( rank or "" )
spell_queue[3] = selfcast and UnitName("player") or UnitName("target") and UnitCanAssist("player", "target") and UnitName("target") or UnitName("player")
end)
libpredict.sender = CreateFrame("Frame", "ShaguTweaksPredictionSender", UIParent)
libpredict.sender.enabled = true
libpredict.sender.SendHealCommMsg = function(self, msg)
SendAddonMessage("HealComm", msg, "RAID")
SendAddonMessage("HealComm", msg, "BATTLEGROUND")
end
libpredict.sender.SendResCommMsg = function(self, msg)
SendAddonMessage("CTRA", msg, "RAID")
SendAddonMessage("CTRA", msg, "BATTLEGROUND")
end
-- tbc
libpredict.sender:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_START")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_STOP")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_FAILED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
libpredict.sender:RegisterEvent("UNIT_SPELLCAST_SENT")
-- vanilla
libpredict.sender:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF")
libpredict.sender:RegisterEvent("SPELLCAST_START")
libpredict.sender:RegisterEvent("SPELLCAST_STOP")
libpredict.sender:RegisterEvent("SPELLCAST_FAILED")
libpredict.sender:RegisterEvent("SPELLCAST_INTERRUPTED")
libpredict.sender:RegisterEvent("SPELLCAST_DELAYED")
-- force cache updates
libpredict.sender:RegisterEvent("UNIT_INVENTORY_CHANGED")
libpredict.sender:RegisterEvent("SKILL_LINES_CHANGED")
libpredict.sender:SetScript("OnEvent", function()
if event == "CHAT_MSG_SPELL_SELF_BUFF" then -- vanilla
local spell, _, heal = cmatch(arg1, HEALEDSELFOTHER) -- "Your %s heals %s for %d."
if spell and heal then
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
return
end
local spell, heal = cmatch(arg1, HEALEDSELFSELF) -- "Your %s heals you for %d."
if spell and heal then
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
return
end
local spell, heal = cmatch(arg1, HEALEDCRITSELFOTHER) -- "Your %s critically heals %s for %d."
if spell and heal then
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
return
end
local spell, _, heal = cmatch(arg1, HEALEDCRITSELFSELF) -- "Your %s critically heals you for %d."
if spell and heal then
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
return
end
elseif event == "COMBAT_LOG_EVENT_UNFILTERED" and arg2 == "SPELL_HEAL" and arg4 == player then -- tbc
local spell, heal, crit = arg10, arg12, arg13
if spell and heal and crit then
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal, true) end
elseif spell and heal then
if spell == spell_queue[1] then UpdateCache(spell_queue[2], heal) end
end
elseif event == "UNIT_SPELLCAST_SENT" and arg4 then -- fix tbc mouseover macros
senttarget = arg4
elseif strfind(event, "SPELLCAST_START", 1) then
local spell, time = arg1, arg2
if strfind(event, "UNIT_", 1) then -- tbc
if arg1 ~= "player" then return end
local spellname, _, _, _, starttime, endtime = UnitCastingInfo("player")
spell, time = spellname, endtime - starttime
end
if spell_queue[1] == spell and cache[spell_queue[2]] then
local sender = player
local target = senttarget or spell_queue[3]
local amount = cache[spell_queue[2]][1]
local casttime = time
if spell == PRAYER_OF_HEALING then
target = sender
for i=1,4 do
if CheckInteractDistance("party"..i, 4) then
libpredict:Heal(player, UnitName("party"..i), amount, casttime)
if GetExpansion() == "vanilla" then -- vanilla
libpredict.sender:SendHealCommMsg("Heal/" .. UnitName("party"..i) .. "/" .. amount .. "/" .. casttime .. "/")
else -- tbc
libpredict.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), UnitName("party"..i)))
end
libpredict.sender.healing = true
end
end
end
libpredict:Heal(player, target, amount, casttime)
if GetExpansion() == "vanilla" then -- vanilla
libpredict.sender:SendHealCommMsg("Heal/" .. target .. "/" .. amount .. "/" .. casttime .. "/")
else -- tbc
libpredict.sender:SendHealCommMsg(string.format("002%05d%s", math.min(amount, 99999), target))
end
libpredict.sender.healing = true
elseif spell_queue[1] == spell and L["resurrections"][spell] then
local target = senttarget or spell_queue[3]
libpredict:Ress(player, target)
libpredict.sender:SendHealCommMsg("Resurrection/" .. target .. "/start/")
libpredict.sender:SendResCommMsg("RES " .. target)
libpredict.sender.resurrecting = true
end
elseif strfind(event, "SPELLCAST_FAILED", 1) or strfind(event, "SPELLCAST_INTERRUPTED", 1) then
if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
if libpredict.sender.healing then
libpredict:HealStop(player)
if GetExpansion() == "vanilla" then -- vanilla
libpredict.sender:SendHealCommMsg("HealStop")
else -- tbc
libpredict.sender:SendHealCommMsg("001F")
end
libpredict.sender.healing = nil
elseif libpredict.sender.resurrecting then
local target = senttarget or spell_queue[3]
libpredict:RessStop(player)
libpredict.sender:SendHealCommMsg("Resurrection/stop/")
libpredict.sender:SendResCommMsg("RESNO " .. target)
libpredict.sender.resurrecting = nil
end
elseif event == "SPELLCAST_DELAYED" then
if libpredict.sender.healing then
libpredict:HealDelay(player, arg1)
libpredict.sender:SendHealCommMsg("Healdelay/" .. arg1 .. "/")
end
elseif strfind(event, "SPELLCAST_STOP", 1) then
if strfind(event, "UNIT_", 1) and arg1 ~= "player" then return end
libpredict:HealStop(player)
end
end)
ShaguTweaks.libpredict = libpredict
+129
View File
@@ -0,0 +1,129 @@
local _G = ShaguTweaks.GetGlobalEnv()
local GetExpansion = ShaguTweaks.GetExpansion
local libtipscan = ShaguTweaks.libtipscan
local scanner = libtipscan:GetScanner("libspell")
local libspell = {}
-- [ GetSpellMaxRank ]
-- Returns the maximum rank of a players spell.
-- 'name' [string] spellname to query
-- return: [string],[number] maximum rank in characters and the number
-- e.g "Rank 1" and "1"
local spellmaxrank = {}
function libspell.GetSpellMaxRank(name)
local cache = spellmaxrank[name]
if cache then return cache[1], cache[2] end
local rank = { 0, nil}
for i = 1, GetNumSpellTabs() do
local _, _, offset, num = GetSpellTabInfo(i)
local bookType = BOOKTYPE_SPELL
for id = offset + 1, offset + num do
local spellName, spellRank = GetSpellName(id, bookType)
if spellName == name then
if not rank[2] then rank[2] = spellRank end
local _, _, numRank = string.find(spellRank, " (%d+)$")
if numRank and tonumber(numRank) > rank[1] then
rank = { tonumber(numRank), spellRank}
end
end
end
end
spellmaxrank[name] = { rank[2], rank[1] }
return rank[2], rank[1]
end
-- [ GetSpellIndex ]
-- Returns the spellbook index and bookid of the given spell.
-- 'name' [string] spellname to query
-- 'rank' [string] rank to query (optional)
-- return: [number],[string] spell index and spellbook id
local spellindex = {}
function libspell.GetSpellIndex(name, rank)
if not name then return end
local cache = spellindex[name..(rank or "")]
if cache then return cache[1], cache[2] end
if not rank then rank = libspell.GetSpellMaxRank(name) end
for i = 1, GetNumSpellTabs() do
local _, _, offset, num = GetSpellTabInfo(i)
local bookType = BOOKTYPE_SPELL
for id = offset + 1, offset + num do
local spellName, spellRank = GetSpellName(id, bookType)
if rank and rank == spellRank and name == spellName then
spellindex[name..rank] = { id, bookType }
return id, bookType
elseif not rank and name == spellName then
spellindex[name] = { id, bookType }
return id, bookType
end
end
end
spellindex[name..(rank or "")] = { nil }
return nil
end
-- [ GetSpellInfo ]
-- Returns several information about a spell.
-- 'index' [string/number] Spellname or Index of a spell in the spellbook
-- 'bookType' [string] Type of spellbook (optional)
-- return:
-- [string] Name of the spell
-- [string] Secondary text associated with the spell
-- (e.g."Rank 5", "Racial", etc.)
-- [string] Path to an icon texture for the spell
-- [number] Casting time of the spell in milliseconds
-- [number] Minimum range from the target required to cast the spell
-- [number] Maximum range from the target at which you can cast the spell
local spellinfo = {}
function libspell.GetSpellInfo(index, bookType)
local cache = spellinfo[index]
if cache then return cache[1], cache[2], cache[3], cache[4], cache[5], cache[6] end
local name, rank, id
local icon = ""
local castingTime = 0
local minRange = 0
local maxRange = 0
if type(index) == "string" then
local _, _, sname, srank = string.find(index, '(.+)%((.+)%)')
name = sname or index
rank = srank or libspell.GetSpellMaxRank(name)
id, bookType = libspell.GetSpellIndex(name, rank)
else
name, rank = GetSpellName(index, bookType)
id, bookType = libspell.GetSpellIndex(name, rank)
end
if name and id then
icon = GetSpellTexture(id, bookType)
end
if id then
scanner:SetSpell(id, bookType)
local _, sec = scanner:Find(gsub(SPELL_CAST_TIME_SEC, "%%.3g", "%(.+%)"))
local _, min = scanner:Find(gsub(SPELL_CAST_TIME_MIN, "%%.3g", "%(.+%)"))
local _, range = scanner:Find(gsub(SPELL_RANGE, "%%s", "%(.+%)"))
castingTime = (tonumber(sec) or tonumber(min) or 0) * 1000
if range then
local _, _, min, max = string.find(range, "(.+)-(.+)")
if min and max then
minRange = tonumber(min)
maxRange = tonumber(max)
else
minRange = 0
maxRange = tonumber(range)
end
end
end
spellinfo[index] = { name, rank, icon, castingTime, minRange, maxRange }
return name, rank, icon, castingTime, minRange, maxRange
end
ShaguTweaks.libspell = libspell
+169
View File
@@ -0,0 +1,169 @@
local _G = ShaguTweaks.GetGlobalEnv()
local libtipscan = {}
local baseName = "ShaguTweaksTooltip"
local methods = {
"SetBagItem", "SetAction", "SetAuctionItem", "SetAuctionSellItem", "SetBuybackItem",
"SetCraftItem", "SetCraftSpell", "SetHyperlink", "SetInboxItem", "SetInventoryItem",
"SetLootItem", "SetLootRollItem", "SetMerchantItem", "SetPetAction", "SetPlayerBuff",
"SetQuestItem", "SetQuestLogItem", "SetQuestRewardSpell", "SetSendMailItem", "SetShapeshift",
"SetSpell", "SetTalent", "SetTrackingSpell", "SetTradePlayerItem", "SetTradeSkillItem", "SetTradeTargetItem",
"SetTrainerService", "SetUnit", "SetUnitBuff", "SetUnitDebuff",
}
local extra_methods = {
"Find", "Line", "Text", "List",
}
local getFontString = function(obj)
local name = obj:GetName()
local r, g, b, color, a
local text, segment
for i=1, obj:NumLines() do
local left = _G[string.format("%sTextLeft%d",name,i)]
segment = left and left:IsVisible() and left:GetText()
segment = segment and segment ~= "" and segment or nil
if segment then
r, g, b, a = left:GetTextColor()
segment = rgbhex(r,g,b) .. segment .. "|r"
text = text and text .. "\n" .. segment or segment
end
end
return text
end
local getText = function(obj)
local name = obj:GetName()
local text = {}
for i=1, obj:NumLines() do
local left, right = _G[string.format("%sTextLeft%d",name,i)], _G[string.format("%sTextRight%d",name,i)]
left = left and left:IsVisible() and left:GetText()
right = right and right:IsVisible() and right:GetText()
left = left and left ~= "" and left or nil
right = right and right ~= "" and right or nil
if left or right then
text[i] = {left, right}
end
end
return text
end
local findText = function(obj, text, exact)
local name = obj:GetName()
for i=1, obj:NumLines() do
local left, right = _G[string.format("%sTextLeft%d",name,i)], _G[string.format("%sTextRight%d",name,i)]
left = left and left:IsVisible() and left:GetText()
right = right and right:IsVisible() and right:GetText()
if exact then
if (left and left == text) or (right and right == text) then
return i, text
end
else
if left then
local found,_,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10 = string.find(left, text)
if found then
return i, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
end
end
if right then
local found,_,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10 = string.find(right, text)
if found then
return i, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
end
end
end
end
end
local lineText = function(obj, line)
local name = obj:GetName()
if line <= obj:NumLines() then
local left, right = _G[string.format("%sTextLeft%d",name,line)], _G[string.format("%sTextRight%d",name,line)]
left = left and left:IsVisible() and left:GetText()
right = right and right:IsVisible() and right:GetText()
if left or right then
return left, right
end
end
end
local findColor = function(obj, r,g,b)
local name = obj:GetName()
if type(r) == "table" then
r,g,b = r.r or r[1], r.g or r[2], r.b or r[3]
end
for i=1, obj:NumLines() do
local tr, tg, tb
local left, right = _G[string.format("%sTextLeft%d",name,i)], _G[string.format("%sTextRight%d",name,i)]
if left and left:IsVisible() then
tr, tg, tb = left:GetTextColor()
tr, tg, tb = round(tr,1), round(tg,1), round(tb,1)
end
if tr and (tr == r and tg == g and tb == b) then
return i
end
if right and right:IsVisible() then
tr, tg, tb = right:GetTextColor()
tr, tg, tb = round(tr,1), round(tg,1), round(tb,1)
end
if tr and (tr == r and tg == g and tb == b) then
return i
end
end
end
libtipscan._registry = setmetatable({},{__index = function(t,k)
local v = CreateFrame("GameTooltip", string.format("%s%s",baseName,k), nil, "GameTooltipTemplate")
v:SetOwner(WorldFrame,"ANCHOR_NONE")
v:SetScript("OnHide", function ()
this:SetOwner(WorldFrame,"ANCHOR_NONE")
end)
function v:Text()
return getText(self)
end
function v:FontString()
return getFontString(self)
end
function v:Find(text, exact)
return findText(self, text, exact)
end
function v:Color(r,g,b)
return findColor(self,r,g,b)
end
function v:Line(line)
return lineText(self, line)
end
for _,method in ipairs(methods) do
local method = method
local old = v[method]
v[method] = function(v, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
v:ClearLines()
return old(v, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
end
end
function v:List()
table.sort(methods)
for _,method in ipairs(methods) do
print(method)
end
for _,method in ipairs(extra_methods) do
print(method)
end
end
rawset(t,k,v)
return v
end})
function libtipscan:GetScanner(type)
local scanner = self._registry[type]
scanner:ClearLines()
return scanner
end
function libtipscan:List()
for name, scanner in pairs(self._registry) do
print(name)
end
end
ShaguTweaks.libtipscan = libtipscan
+144
View File
@@ -0,0 +1,144 @@
local _G = ShaguTweaks.GetGlobalEnv()
local GetExpansion = ShaguTweaks.GetExpansion
local L = ShaguTweaks.L
local units = { players = {}, mobs = {} }
local queue = { }
local libunitscan = CreateFrame("Frame", "ShaguTweaksUnitScan", UIParent)
function ShaguTweaks.GetUnitData(name, active)
if units["players"][name] then
local ret = units["players"][name]
return ret.class, ret.level, ret.elite, true
elseif units["mobs"][name] then
local ret = units["mobs"][name]
return ret.class, ret.level, ret.elite, nil
elseif active then
queue[name] = true
libunitscan:Show()
end
end
local function AddData(db, name, class, level, elite)
if not name then return end
units[db][name] = units[db][name] or {}
units[db][name].class = class or units[db][name].class
units[db][name].level = level or units[db][name].level
units[db][name].elite = elite or units[db][name].elite
queue[name] = nil
end
libunitscan:RegisterEvent("PLAYER_ENTERING_WORLD")
libunitscan:RegisterEvent("FRIENDLIST_UPDATE")
libunitscan:RegisterEvent("GUILD_ROSTER_UPDATE")
libunitscan:RegisterEvent("RAID_ROSTER_UPDATE")
libunitscan:RegisterEvent("PARTY_MEMBERS_CHANGED")
libunitscan:RegisterEvent("PLAYER_TARGET_CHANGED")
libunitscan:RegisterEvent("WHO_LIST_UPDATE")
libunitscan:RegisterEvent("CHAT_MSG_SYSTEM")
libunitscan:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
libunitscan:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- load database
ShaguTweaks_cache = ShaguTweaks_cache or {}
ShaguTweaks_cache["players"] = ShaguTweaks_cache["players"] or {}
units.players = ShaguTweaks_cache["players"]
-- update own character details
local name = UnitName("player")
local _, class = UnitClass("player")
local level = UnitLevel("player")
AddData("players", name, class, level)
elseif event == "FRIENDLIST_UPDATE" then
local name, class, level
for i = 1, GetNumFriends() do
name, level, class = GetFriendInfo(i)
class = L["class"][class] or nil
-- friendlist updates due to friend going off-line return level 0, let's not overwrite good older values
level = level > 0 and level or nil
AddData("players", name, class, level)
end
elseif event == "GUILD_ROSTER_UPDATE" then
local name, class, level, _
for i = 1, GetNumGuildMembers() do
name, _, _, level, class = GetGuildRosterInfo(i)
class = L["class"][class] or nil
AddData("players", name, class, level)
end
elseif event == "RAID_ROSTER_UPDATE" then
local name, class, SubGroup, level, _
for i = 1, GetNumRaidMembers() do
name, _, SubGroup, level, class = GetRaidRosterInfo(i)
class = L["class"][class] or nil
AddData("players", name, class, level)
end
elseif event == "PARTY_MEMBERS_CHANGED" then
local name, class, level, unit, _
for i = 1, GetNumPartyMembers() do
unit = "party" .. i
_, class = UnitClass(unit)
name = UnitName(unit)
level = UnitLevel(unit)
AddData("players", name, class, level)
end
elseif event == "WHO_LIST_UPDATE" or event == "CHAT_MSG_SYSTEM" then
local name, class, level, _
for i = 1, GetNumWhoResults() do
name, _, level, _, class, _ = GetWhoInfo(i)
class = L["class"][class] or nil
AddData("players", name, class, level)
end
elseif event == "UPDATE_MOUSEOVER_UNIT" or event == "PLAYER_TARGET_CHANGED" then
local scan = event == "PLAYER_TARGET_CHANGED" and "target" or "mouseover"
local name, class, level, elite, _
if UnitIsPlayer(scan) then
_, class = UnitClass(scan)
level = UnitLevel(scan)
name = UnitName(scan)
AddData("players", name, class, level)
else
_, class = UnitClass(scan)
elite = UnitClassification(scan)
level = UnitLevel(scan)
name = UnitName(scan)
AddData("mobs", name, class, level, elite)
end
end
end)
-- since TargetByName can only be triggered within vanilla,
-- we can't auto-scan targets on further expansions.
if GetExpansion() == "vanilla" then
-- setup sound function switches
local SoundOn = PlaySound
local SoundOff = function() return end
libunitscan:SetScript("OnUpdate", function()
-- don't scan when another unit is in target
if UnitExists("target") or UnitName("target") then return end
local name = next(queue)
if name then
-- disable sound
_G.PlaySound = SoundOff
-- try to target the unknown unit
TargetByName(name, true)
ClearTarget()
-- enable sound again
_G.PlaySound = SoundOn
queue[name] = nil
end
this:Hide()
end)
end