Add files via upload
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user