WoWInterface SVN LibResInfo

[/] [trunk/] [LibResInfo-1.0/] [LibResInfo-1.0.lua] - Rev 89

Compare with Previous | Blame | View Log

--[[--------------------------------------------------------------------
LibResInfo-1.0
Library to provide information about resurrections in your group.
Copyright (c) 2012-2014 Phanx. All rights reserved.
See the accompanying README and LICENSE files for more information.
http://www.wowinterface.com/downloads/info21467-LibResInfo-1.0.html
http://wow.curseforge.com/addons/libresinfo/
------------------------------------------------------------------------
TODO:
* Handle Reincarnation with some guesswork?
* Clear data when releasing spirit
----------------------------------------------------------------------]]

local DEBUG_LEVEL = GetAddOnMetadata("LibResInfo-1.0", "Version") and 1 or 0
local DEBUG_FRAME = ChatFrame3

------------------------------------------------------------------------

local MAJOR, MINOR = "LibResInfo-1.0", 22
assert(LibStub, MAJOR.." requires LibStub")
assert(LibStub("CallbackHandler-1.0"), MAJOR.." requires CallbackHandler-1.0")
local lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end

------------------------------------------------------------------------

local callbacks     = lib.callbacks     or LibStub("CallbackHandler-1.0"):New(lib)
local eventFrame    = lib.eventFrame    or CreateFrame("Frame")

local guidFromUnit  = lib.guidFromUnit  or {} -- t[unit] = guid -- table lookup is faster than calling UnitGUID
local nameFromGUID  = lib.nameFromGUID  or {} -- t[guid] = name
local unitFromGUID  = lib.unitFromGUID  or {} -- t[guid] = unit

local castingSingle = lib.castingSingle or {} -- t[casterGUID] = { startTime = <number>, endTime = <number>, target = <guid> }
local castingMass   = lib.castingMass   or {} -- t[casterGUID] = endTime
local hasPending    = lib.hasPending    or {} -- t[targetGUID] = endTime

local hasSoulstone  = lib.hasSoulstone  or {} -- t[targetGUID] = <boolean>
local isDead        = lib.isDead        or {} -- t[targetGUID] = <boolean>
local isGhost       = lib.isGhost       or {} -- t[targetGUID] = <boolean>

------------------------------------------------------------------------

lib.callbacks       = callbacks
lib.eventFrame      = eventFrame

lib.guidFromUnit    = guidFromUnit
lib.nameFromGUID    = nameFromGUID
lib.unitFromGUID    = unitFromGUID

lib.castingSingle   = castingSingle
lib.castingMass     = castingMass
lib.hasPending      = hasPending

lib.hasSoulstone    = hasSoulstone
lib.isDead          = isDead
lib.isGhost         = isGhost

------------------------------------------------------------------------

local RESURRECT_PENDING_TIME = 60
local RELEASE_PENDING_TIME = 360
local RECENTLY_MASS_RESURRECTED = GetSpellInfo(95223)
local SOULSTONE = GetSpellInfo(20707)

local resSpells = {
        [2008]   = GetSpellInfo(2008),   -- Ancestral Spirit (shaman)
        [8342]   = GetSpellInfo(8342),   -- Defibrillate (item: Goblin Jumper Cables)
        [22999]  = GetSpellInfo(22999),  -- Defibrillate (item: Goblin Jumper Cables XL)
        [54732]  = GetSpellInfo(54732),  -- Defibrillate (item: Gnomish Army Knife)
        [54732]  = GetSpellInfo(164729), -- Defibrillate (item: Ultimate Gnomish Army Knife)
        [126393] = GetSpellInfo(126393), -- Eternal Guardian (hunter pet: quilien)
        [61999]  = GetSpellInfo(61999),  -- Raise Ally (death knight)
        [20484]  = GetSpellInfo(20484),  -- Rebirth (druid)
        [7328]   = GetSpellInfo(7328),   -- Redemption (paladin)
        [2006]   = GetSpellInfo(2006),   -- Resurrection (priest)
        [115178] = GetSpellInfo(115178), -- Resuscitate (monk)
        [50769]  = GetSpellInfo(50769),  -- Revive (druid)
        [982]    = GetSpellInfo(982),    -- Revive Pet (hunter)
        [20707]  = GetSpellInfo(20707),  -- Soulstone (warlock)
        [83968]  = GetSpellInfo(83968),  -- Mass Resurrection
}

------------------------------------------------------------------------

local next, pairs, GetNumGroupMembers, GetTime, IsInGroup, IsInRaid, UnitAura, UnitCastingInfo, UnitGUID, UnitHasIncomingResurrection, UnitHealth, UnitIsConnected, UnitIsDead, UnitIsDeadOrGhost, UnitIsGhost, UnitName
    = next, pairs, GetNumGroupMembers, GetTime, IsInGroup, IsInRaid, UnitAura, UnitCastingInfo, UnitGUID, UnitHasIncomingResurrection, UnitHealth, UnitIsConnected, UnitIsDead, UnitIsDeadOrGhost, UnitIsGhost, UnitName

------------------------------------------------------------------------

local function debug(level, text, ...)
        if level <= DEBUG_LEVEL then
                if ... then
                        if type(text) == "string" and strfind(text, "%%[dfqsx%d%.]") then
                                text = format(text, ...)
                        else
                                text = strjoin(" ", tostringall(text, ...))
                        end
                else
                        text = tostring(text)
                end
                DEBUG_FRAME:AddMessage("|cff00ddba[LRI]|r " .. text)
        end
end

local newTable, remTable
do
        local pool = {}
        function newTable()
                local t = next(pool)
                if t then
                        pool[t] = nil
                        return t
                end
                return {}
        end
        function remTable(t)
                pool[wipe(t)] = true
                return nil
        end
end

------------------------------------------------------------------------

lib.callbacksInUse = lib.callbacksInUse or {}

eventFrame:SetScript("OnEvent", function(self, event, ...)
        return self[event] and self[event](self, event, ...)
end)

function callbacks:OnUsed(lib, callback)
        if not next(lib.callbacksInUse) then
                debug(1, "Callbacks in use! Starting up...")
                eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
                eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
                eventFrame:RegisterEvent("INCOMING_RESURRECT_CHANGED")
                eventFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
                eventFrame:RegisterEvent("UNIT_SPELLCAST_START")
                eventFrame:RegisterEvent("UNIT_SPELLCAST_STOP")
                eventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
                eventFrame:RegisterEvent("UNIT_AURA")
                eventFrame:RegisterEvent("UNIT_CONNECTION")
                eventFrame:RegisterEvent("UNIT_HEALTH")
                eventFrame:GROUP_ROSTER_UPDATE("OnUsed")
        end
        lib.callbacksInUse[callback] = true
end

function callbacks:OnUnused(lib, callback)
        lib.callbacksInUse[callback] = nil
        if not next(lib.callbacksInUse) then
                debug(1, "No callbacks in use. Shutting down...")
                eventFrame:UnregisterAllEvents()
                eventFrame:Hide()
                wipe(guidFromUnit)
                wipe(nameFromGUID)
                wipe(unitFromGUID)
                for caster, data in pairs(castingSingle) do
                        castingSingle[caster] = remTable(data)
                end
                wipe(castingMass)
                wipe(hasPending)
                wipe(hasSoulstone)
                wipe(isDead)
                wipe(isGhost)
        end
end

------------------------------------------------------------------------

function lib.RegisterAllCallbacks(handler, method, includeMassRes)
        lib.RegisterCallback(handler, "LibResInfo_ResCastStarted", method)
        lib.RegisterCallback(handler, "LibResInfo_ResCastCancelled", method)
        lib.RegisterCallback(handler, "LibResInfo_ResCastFinished", method)

        if includeMassRes then
                lib.RegisterCallback(handler, "LibResInfo_MassResStarted", method)
                lib.RegisterCallback(handler, "LibResInfo_MassResCancelled", method)
                lib.RegisterCallback(handler, "LibResInfo_MassResFinished", method)
                lib.RegisterCallback(handler, "LibResInfo_UnitUpdate", method)
        end

        lib.RegisterCallback(handler, "LibResInfo_ResPending", method)
        lib.RegisterCallback(handler, "LibResInfo_ResUsed", method)
        lib.RegisterCallback(handler, "LibResInfo_ResExpired", method)
end

------------------------------------------------------------------------
--      Returns information about the res being cast on the specified unit.
--      Arguments: unit (unitID or GUID)
--      Returns: resType (string), endTime (number), caster (unitID), casterGUID
--      * All returns are nil if no res is being cast on the unit.
--      * resType is one of:
--   - SELFRES if the unit has a Soulstone or other self-res ability available,
--   - PENDING if the unit already has a res available to accept, or
--        - CASTING if a res is being cast on the unit.
--      * caster and casterGUID are nil if the unit is being Mass Ressed.
------------------------------------------------------------------------

function lib:UnitHasIncomingRes(unit)
        if type(unit) ~= "string" then return end
        local guid
        if strmatch(unit, "^Player%-") then
                guid = unit
                unit = unitFromGUID[guid]
        else
                guid = UnitGUID(unit)
                unit = unitFromGUID[guid]
        end
        if not guid or not unit or not UnitIsDeadOrGhost(unit) or not UnitIsConnected(unit) then
                return
        end
        if hasPending[guid] then
                local state = hasSoulstone[guid] and "SELFRES" or "PENDING"
                debug(2, "UnitHasIncomingRes", nameFromGUID[guid], state)
                return state, hasPending[guid]
        end

        local state, firstCaster, firstEnd
        for caster, data in pairs(castingSingle) do
                if data.target == guid then
                        if not firstEnd or data.endTime < firstEnd then
                                state, firstCaster, firstEnd = "CASTING", caster, data.endTime
                        end
                end
        end
        if not UnitDebuff(unit, RECENTLY_MASS_RESURRECTED) then
                for caster, endTime in pairs(castingMass) do
                        if not firstEnd or endTime < firstEnd then
                                state, firstCaster, firstEnd = "MASSRES", caster, endTime
                        end
                end
        end
        if state and firstCaster and firstEnd then
                debug(2, "UnitHasIncomingRes", nameFromGUID[guid], state, nameFromGUID[firstCaster])
                return state, firstEnd, unitFromGUID[firstCaster], firstCaster
        end
        --debug(3, "UnitHasIncomingRes", nameFromGUID[guid], "nil")
end

------------------------------------------------------------------------
--      Return information about the res being cast by the specified unit.
--      Arguments: unit (unitID or GUID)
--      Returns: endTime (number), target (unitID), targetGUID (guid), isFirst (boolean)
--      * all returns are nil if the unit is not casting a res
--      * target and targetGUID are nil if the unit is casting Mass Res
------------------------------------------------------------------------

function lib:UnitIsCastingRes(unit)
        if type(unit) ~= "string" then return end
        local guid
        if strmatch(unit, "^Player%-") then
                guid = unit
                unit = unitFromGUID[guid]
        else
                guid = UnitGUID(unit)
                unit = unitFromGUID[guid]
        end
        if not guid or not unit then
                return
        end

        local casting = castingSingle[guid]
        if casting then
                local endTime, target, isFirst = casting.endTime, casting.target, true
                -- TODO: Handle edge case where this function is called in between the cast start and the target identification?
                for caster, data in pairs(castingSingle) do
                        if data.target == target and data.endTime < endTime then
                                isFirst = false
                                break
                        end
                end
                debug(2, "UnitIsCastingRes", nameFromGUID[guid], "casting on", nameFromGUID[casting.target], isFirst and "(first)" or "(duplicate)")
                return endTime, unitFromGUID[casting.target], casting.target, isFirst
        end

        casting = castingMass[guid]
        if casting then
                local endTime, isFirst = casting, true
                for caster, endTime2 in pairs(castingMass) do
                        if endTime2 < endTime then
                                isFirst = false
                                break
                        end
                end
                debug(2, "UnitIsCastingRes", nameFromGUID[guid], "casting Mass Res", isFirst and "(first)" or "(duplicate)")
                return endTime, nil, nil, isFirst
        end

        --debug(3, "UnitIsCastingRes", nameFromGUID[guid], "nil")
end

------------------------------------------------------------------------
--      Handle group changes:

local function AddUnit(unit)
        local guid = UnitGUID(unit)
        if not guid then return end
        guidFromUnit[unit] = guid
        nameFromGUID[guid] = UnitName(unit)
        unitFromGUID[guid] = unit
        -- Check for soulstones:
        eventFrame:UNIT_AURA("AddUnit", unit)
end

function eventFrame:GROUP_ROSTER_UPDATE(event)
        debug(3, event)

        -- Update guid <==> unit mappings:
        wipe(guidFromUnit)
        wipe(unitFromGUID)
        if IsInRaid() then
                for i = 1, GetNumGroupMembers() do
                        AddUnit("raid"..i)
                        AddUnit("raidpet"..i)
                end
        else
                AddUnit("player")
                AddUnit("pet")
                if IsInGroup() then
                        for i = 1, GetNumGroupMembers() - 1 do
                                AddUnit("party"..i)
                                AddUnit("partypet"..i)
                        end
                end
        end

        -- Remove data for single casters no longer in the group:
        for caster, data in pairs(castingSingle) do
                if not unitFromGUID[caster] then
                        local target = data.target
                        castingSingle[caster] = remTable(data)
                        debug(1, ">> ResCastCancelled on", nameFromGUID[target], "by", nameFromGUID[caster], "(caster left group)")
                        callbacks:Fire("LibResInfo_ResCastCancelled", unitFromGUID[target], target, nil, caster)
                end
        end

        -- Remove data for mass casters no longer in the group:
        for caster in pairs(castingMass) do
                if not unitFromGUID[caster] then
                        castingMass[caster] = nil
                        debug(1, ">> MassResCancelled by", nameFromGUID[caster], "(left group)")
                        callbacks:Fire("LibResInfo_MassResCancelled", nil, caster)
                end
        end

        -- Remove data for targets no longer in the group:
        for caster, data in pairs(castingSingle) do
                local target = data.target
                if not unitFromGUID[target] then
                        castingSingle[caster] = remTable(data)
                        -- TODO: Is this callback needed, or will the cast cancel on its own?
                        debug(1, ">> ResCastCancelled on", nameFromGUID[target], "by", nameFromGUID[caster], "(target left group)")
                        callbacks:Fire("LibResInfo_ResCastCancelled", nil, target, unitFromGUID[caster], caster)
                end
        end

        -- Remove data for waiters no longer in the group:
        for target in pairs(hasPending) do
                if not unitFromGUID[target] then
                        hasPending[target] = nil
                        debug(1, ">> ResExpired on", nameFromGUID[target], "(left group)")
                        callbacks:Fire("LibResInfo_ResExpired", nil, target)
                end
        end

        -- Unregister unit events and stop the timer if there are no waiters:
        if not next(hasPending) then
                debug(3, "Nobody pending, stop timer")
                self:Hide()
        end

        -- Unregister CLEU if there are no casts:
        if not next(castingSingle) and not next(castingMass) then
                debug(3, "Nobody casting, unregistering CLEU")
                self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
        end

        -- Remove names no longer in the group:
        for guid, name in pairs(nameFromGUID) do
                if not unitFromGUID[guid] then
                        debug(4, name, "is no longer in the group")
                        nameFromGUID[guid] = nil
                end
        end
end

eventFrame.PLAYER_ENTERING_WORLD = eventFrame.GROUP_ROSTER_UPDATE

------------------------------------------------------------------------

function eventFrame:INCOMING_RESURRECT_CHANGED(event, unit)
        local guid = guidFromUnit[unit]
        if not guid then return end

        local hasRes = UnitHasIncomingResurrection(unit)
        debug(3, event, nameFromGUID[guid], hasRes)

        if hasRes then
                -- Unit has a res incoming. Match it to a spell.
                local now = GetTime()
                for caster, data in pairs(castingSingle) do
                        if not data.target and data.startTime - now < 10 then
                                -- Found it!
                                data.target = guid
                                debug(1, ">> ResCastStarted on", nameFromGUID[guid], "by", nameFromGUID[caster], "in", event)
                                callbacks:Fire("LibResInfo_ResCastStarted", unit, guid, unitFromGUID[caster], caster, data.endTime)
                                break
                        end
                end
                -- TODO: Why was I searching for finished casts here???
        else
                -- Check if unit previously had any resses.
                for caster, data in pairs(castingSingle) do
                        if data.target == guid then
                                debug(4, nameFromGUID[caster], "was casting...")
                                if data.startTime then
                                        debug(4, "...and stopped.")
                                        castingSingle[caster] = remTable(data)
                                        debug(1, ">> ResCastCancelled", "on", nameFromGUID[guid], "by", nameFromGUID[casterGUID], "in", event)
                                        callbacks:Fire("LibResInfo_ResCastCancelled", unit, guid, unitFromGUID[casterGUID], casterGUID)
                                else
                                        debug(4, "...and finished.")
                                        castingSingle[caster] = remTable(data)
                                        hasPending[guid] = nil
                                        debug(1, ">> ResCastFinished", "on", nameFromGUID[guid], "by", nameFromGUID[casterGUID], "in", event)
                                        callbacks:Fire("LibResInfo_ResCastFinished", unit, guid, unitFromGUID[casterGUID], casterGUID)
                                        self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
                                end
                        end
                end
        end
end

------------------------------------------------------------------------

function eventFrame:UNIT_SPELLCAST_START(event, unit, spellName, _, _, spellID)
        if not resSpells[spellID] then return end
        local guid = guidFromUnit[unit]
        if not guid then return end
        debug(3, event, nameFromGUID[guid], "casting", spellName)

        local _, _, _, _, startTime, endTime = UnitCastingInfo(unit)

        if spellID == 83968 then -- Mass Resurrection
                castingMass[guid] = endTime / 1000
                debug(1, ">> MassResStarted", nameFromGUID[guid])
                callbacks:Fire("LibResInfo_MassResStarted", unit, guid, endTime / 1000)
                return
        end

        local data = newTable()
        data.startTime = startTime / 1000
        data.endTime = endTime / 1000
        castingSingle[guid] = data
end

function eventFrame:UNIT_SPELLCAST_SUCCEEDED(event, unit, spellName, _, _, spellID)
        if not resSpells[spellID] then return end
        local guid = guidFromUnit[unit]
        if not guid then return end

        debug(3, event, nameFromGUID[guid], "finished", spellName)

        if spellID == 83968 then -- Mass Resurrection
                castingMass[guid] = nil
                debug(1, ">> MassResFinished", nameFromGUID[guid])
                callbacks:Fire("LibResInfo_MassResFinished", unit, guid)
                self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
                return
        end

        local data = castingSingle[guid]
        if data then -- No START event for instant cast spells.
                local target = data.target
                if not target then
                        -- Probably Soulstone precast on a live target.
                        return
                end
                data.finished = true -- Flag so STOP can ignore this.
                debug(1, ">> ResCastFinished", "on", nameFromGUID[target], "by", nameFromGUID[guid], "in", event)
                callbacks:Fire("LibResInfo_ResCastFinished", unitFromGUID[target], target, unit, guid)
        end

        debug(3, "Registering CLEU")
        self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
end

function eventFrame:UNIT_SPELLCAST_STOP(event, unit, spellName, _, _, spellID)
        if not resSpells[spellID] then return end
        local guid = guidFromUnit[unit]
        if not guid then return end

        debug(3, event, nameFromGUID[guid], "stopped", spellName)

        if spellID == 83968 then -- Mass Resurrection
                if not castingMass[guid] then return end -- already SUCCEEDED
                castingMass[guid] = nil
                debug(1, ">> MassResCancelled", nameFromGUID[guid])
                callbacks:Fire("LibResInfo_MassResCancelled", unit, guid)
        else
                local data = castingSingle[guid]
                if data then
                        local target = data.target
                        local finished = data.finished
                        castingSingle[guid] = remTable(data)
                        if finished or not target then
                                -- no target = Probably Soulstone precast on a live target.
                                -- finished = Cast finished. Don't fire a callback or unregister CLEU.
                                return
                        end
                        debug(1, ">> ResCastCancelled", "on", nameFromGUID[target], "by", nameFromGUID[guid])
                        callbacks:Fire("LibResInfo_ResCastCancelled", unitFromGUID[target], target, unit, guid)
                end
        end

        -- Unregister CLEU if there are no casts:
        if not next(castingSingle) and not next(castingMass) then
                debug(3, "Nobody casting, unregistering CLEU")
                self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
        end
end

eventFrame.UNIT_SPELLCAST_INTERRUPTED = eventFrame.UNIT_SPELLCAST_STOP

------------------------------------------------------------------------

function eventFrame:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, combatEvent, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags, spellID, spellName, spellSchool)
        if combatEvent ~= "SPELL_RESURRECT" then return end

        local destUnit = unitFromGUID[destGUID]
        if not destUnit then return end
        debug(3, combatEvent, "on", destName, "by", sourceName)

        local now = GetTime()
        local endTime = now + RESURRECT_PENDING_TIME

        hasPending[destGUID] = endTime

        self:Show()

        debug(1, ">> ResPending", "on", strmatch(destName, "[^%-]+"), "by", strmatch(sourceName, "[^%-]+"))
        callbacks:Fire("LibResInfo_ResPending", destUnit, destGUID, endTime)

        -- Unregister CLEU if there are no casts:
        if not next(castingSingle) and not next(castingMass) then
                -- TODO: Keep track of number of instant casts?
                -- Seems unlikely that multiple casts would end so close together that this would be an issue.
                debug(3, "Nobody casting, unregistering CLEU")
                self:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
        end
end

------------------------------------------------------------------------

function eventFrame:UNIT_AURA(event, unit)
        local guid = guidFromUnit[unit]
        if not guid then return end
        debug(5, event, unit)

        if not isDead[guid] then
                local stoned = UnitAura(unit, SOULSTONE)
                if stoned ~= hasSoulstone[guid] then
                        if not stoned and UnitHealth(unit) <= 1 then
                                return
                        end
                        hasSoulstone[guid] = stoned
                        debug(2, nameFromGUID[guid], stoned and "gained" or "lost", SOULSTONE)
                end
                return
        end

        if UnitIsGhost(unit) and not isGhost[guid] then
                isGhost[guid] = true
                if hasPending[guid] then
                        hasPending[guid] = nil
                        debug(1, ">> ResExpired", nameFromGUID[guid], "(released)")
                        callbacks:Fire("LibResInfo_ResExpired", unit, guid)
                end
                -- No need to check next(castingMass) and fire a UnitUpdate here
                -- since Mass Resurrection will still hit units who released.
        end
end

function eventFrame:UNIT_CONNECTION(event, unit)
        local guid = guidFromUnit[unit]
        if not guid then return end
        debug(4, event, unit)

        if hasPending[unit] and not UnitIsConnected(unit) then
                hasPending[guid] = nil
                debug(1, ">> ResExpired", nameFromGUID[guid], "(offline)")
                callbacks:Fire("LibResInfo_ResExpired", unit, guid)
        elseif next(castingMass) then
                for caster, data in pairs(castingSingle) do
                        if data.target == guid then
                                return
                        end
                end
                debug(1, ">> UnitUpdate", nameFromGUID[guid], "(offline)")
                callbacks:Fire("LibResInfo_UnitUpdate", unit, guid)
        end
end

function eventFrame:UNIT_HEALTH(event, unit)
        local guid = guidFromUnit[unit]
        if not guid then return end
        debug(5, event, unit)

        local dead = UnitIsDead(unit)

        if dead and not isDead[guid] then
                debug(2, nameFromGUID[guid], "is now dead")
                isDead[guid] = true
                if hasSoulstone[guid] then
                        local endTime = GetTime() + RELEASE_PENDING_TIME
                        hasPending[guid] = endTime
                        debug(1, ">> ResPending", nameFromGUID[guid], SOULSTONE)
                        callbacks:Fire("LibResInfo_ResPending", unit, guid, endTime, true)
                elseif next(castingMass) then
                        debug(1, ">> UnitUpdate", nameFromGUID[guid], "(dead)")
                        callbacks:Fire("LibResInfo_UnitUpdate", unit, guid)
                end

        elseif isDead[guid] and not dead then
                debug(2, nameFromGUID[guid], "is now alive")
                isDead[guid] = nil
                if hasPending[guid] then
                        isGhost[guid] = nil
                        hasPending[guid] = nil
                        debug(1, ">> ResUsed", nameFromGUID[guid])
                        callbacks:Fire("LibResInfo_ResUsed", unit, guid)
                elseif next(castingMass) then
                        for caster, data in pairs(castingSingle) do
                                if data.target == guid then
                                        return
                                end
                        end
                        debug(1, ">> UnitUpdate", nameFromGUID[guid], "(alive)")
                        callbacks:Fire("LibResInfo_UnitUpdate", unit, guid)
                end
        end
end

------------------------------------------------------------------------

eventFrame:Hide()

local timer, INTERVAL = 0, 0.5
eventFrame:SetScript("OnUpdate", function(self, elapsed)
        timer = timer + elapsed
        if timer >= INTERVAL then
                debug(6, "Timer update")
                if not next(hasPending) then
                        debug(4, "Nobody pending, stop timer")
                        return self:Hide()
                end
                local now = GetTime()
                for guid, endTime in pairs(hasPending) do
                        if endTime - now < INTERVAL then -- It will expire before the next update.
                                local unit = unitFromGUID[guid]
                                hasPending[guid] = nil
                                debug(1, ">> ResExpired", nameFromGUID[guid])
                                callbacks:Fire("LibResInfo_ResExpired", unit, guid, true)
                        end
                end
                timer = 0
        end
end)

eventFrame:SetScript("OnShow", function()
        debug(4, "Timer start")
end)

eventFrame:SetScript("OnHide", function()
        debug(4, "Timer stop")
        timer = 0
end)

------------------------------------------------------------------------

SLASH_LIBRESINFO1 = "/lri"
SlashCmdList.LIBRESINFO = function(input)
        input = gsub(input, "[^A-Za-z0-9]", "")
        if strlen(input) < 1 then return end
        if strmatch(input, "%D") then
                local f = _G[input]
                if type(f) == "table" and type(f.AddMessage) == "function" then
                        DEBUG_FRAME = f
                        debug(0, "Debug frame set to", input)
                else
                        debug(0, input, "is not a valid debug output frame!")
                end
        else
                local v = tonumber(input)
                if v and v >= 0 then
                        DEBUG_LEVEL = v
                        debug(0, "Debug level set to", input)
                else
                        debug(0, input, "is not a valid debug level!")
                end
        end
end

Compare with Previous | Blame