-- $Id$ --[[ LICENSE Copyright (c) 2006-2007, Kyle Smith All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --]] --[[ DOCUMENTATION Description: Healcomm is an embedded library for communicating heal casts and their targets using the AddonMessage channel. Addons can register with Healcomm to receive notification of these messages. API: Healcomm:RegisterCallback(key, func) -- Adds a callback funciton to be invoked when a spell event is received -- -- If key is a table, then func must be a string and the callback will be -- invoked as: -- key[func](key, sender, action, spell, rank, target) -- otherwise func must be a function and will be invoked as: -- func(sender, action, spell, rank, target) -- -- spell, rank, and target are only sent when the action is "CAST" -- string sender: -- the name of the character that sent the event -- string action: -- one of "CAST", "DELAYED", "INTERRUPTED", or "FAILED" -- string spell: -- the name of the spell cast, translated into your locale -- number rank: -- the rank number of the spell -- string target: -- the name of the target for the spell -- -- Usage: -- Healcomm:RegisterCallback(MyAddon, "Healcomm") -- or -- Healcomm:RegisterCallback("MyAddon", OnHealcommReceived) Healcomm:UnregisterCallback(key) -- Removes the callback function specified by key -- -- Usage: -- Healcomm:UnregisterCallback(MyAddon) -- or -- Healcomm:UnregisterCallback("MyAddon") Healcomm:Enable() -- Enables the library. You probably shouldn't be calling this. Healcomm:Disable() -- Disables the library. You probably shouldn't be calling this. Healcomm:ToggleDebugging(on) -- Enables, disables, or toggles the debugging state of Healcomm -- -- If on is nil, the debugging state will be toggled, otherwise it will be set -- to the value of on. --]] local name = "Healcomm" local version = tonumber(("$Revision$"):match("(%d+)")) or 1 local oldLib = getglobal(name) if oldLib and oldLib.version >= version then return end local lib = {} lib.name = name lib.version = version --[[ LOCALIZATION ]]---------------------------------------------------------- local L local currentLocale = GetLocale() if currentLocale == "enUS" then L = { -- error messages ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "Lesser Heal", SPELL_HEAL = "Heal", SPELL_FLASH_HEAL = "Flash Heal", SPELL_GREATER_HEAL = "Greater Heal", SPELL_PRAYER_OF_HEALING = "Prayer of Healing", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "Holy Light", SPELL_FLASH_OF_LIGHT = "Flash of Light", -- druid heal spells with a casting time SPELL_REGROWTH = "Regrowth", SPELL_HEALING_TOUCH = "Healing Touch", -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = "Lesser Healing Wave", SPELL_HEALING_WAVE = "Healing Wave", } elseif currentLocale == "deDE" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "Geringes Heilen", SPELL_HEAL = "Heilen", SPELL_FLASH_HEAL = "Blitzheilung", SPELL_GREATER_HEAL = "Gro\195\159e Heilung", SPELL_PRAYER_OF_HEALING = "Gebet der Heilung", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "Heiliges Licht", SPELL_FLASH_OF_LIGHT = "Lichtblitz", -- druid heal spells with a casting time SPELL_REGROWTH = "Nachwachsen", SPELL_HEALING_TOUCH = "Heilende Ber\195\188hrung", -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = "Geringe Welle der Heilung", SPELL_HEALING_WAVE = "Welle der Heilung", } elseif currentLocale == "frFR" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "Soins inf\195\169rieurs", SPELL_HEAL = "Soins", SPELL_FLASH_HEAL = "Soins rapides", SPELL_GREATER_HEAL = "Soins sup\195\169rieurs", SPELL_PRAYER_OF_HEALING = "Pri\195\168re de soins", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "Lumi\195\168re sacr\195\169e", SPELL_FLASH_OF_LIGHT = "Eclair lumineux", -- druid heal spells with a casting time SPELL_REGROWTH = "R\195\169tablissement", SPELL_HEALING_TOUCH = "Toucher gu\195\169risseur", -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = "Vague de soins inf\195\169rieurs", SPELL_HEALING_WAVE = "Vague de soins", } elseif currentLocale == "zhCN" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "次级治疗术", SPELL_HEAL = "治疗术", SPELL_FLASH_HEAL = "快速治疗", SPELL_GREATER_HEAL = "强效治疗术", SPELL_PRAYER_OF_HEALING = "治疗祷言", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "圣光术", SPELL_FLASH_OF_LIGHT = "圣光闪现", -- druid heal spells with a casting time SPELL_REGROWTH = "愈合", SPELL_HEALING_TOUCH = "治疗之触", -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = "次级治疗波", SPELL_HEALING_WAVE = "治疗波", } elseif currentLocale == "zhTW" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "次級治療術", SPELL_HEAL = "治療術", SPELL_FLASH_HEAL = "快速治療", SPELL_GREATER_HEAL = "強效治療術", SPELL_PRAYER_OF_HEALING = "治療禱言", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "聖光術", SPELL_FLASH_OF_LIGHT = "聖光閃現", -- druid heal spells with a casting time SPELL_REGROWTH = "癒合", SPELL_HEALING_TOUCH = "治療之觸", -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = "次級治療波", SPELL_HEALING_WAVE = "治療波", } elseif currentLocale == "koKR" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "하급 치유", SPELL_HEAL = "치유", SPELL_FLASH_HEAL = "순간 치유", SPELL_GREATER_HEAL = "상급 치유", SPELL_PRAYER_OF_HEALING = "치유의 기원", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "성스러운 빛", SPELL_FLASH_OF_LIGHT = "빛의 섬광", -- druid heal spells with a casting time SPELL_REGROWTH = "재생", SPELL_HEALING_TOUCH = "치유의 손길", -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = "하급 치유의 물결", SPELL_HEALING_WAVE = "치유의 물결", } elseif currentLocale == "esES" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = "Sanación inferior", SPELL_HEAL = "Sanación", SPELL_FLASH_HEAL = "Sanación instantánea", SPELL_GREATER_HEAL = "Sanación superior", SPELL_PRAYER_OF_HEALING = "Rezo de sanación", -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = "Luz Sagrada", SPELL_FLASH_OF_LIGHT = "Luz Cegadora", -- druid heal spells with a casting time SPELL_REGROWTH = "Recrecimiento", SPELL_HEALING_TOUCH = "Toque de sanación", -- shaman heal spells with a casting time -- FIXME: confirm lesser healing wave SPELL_LESSER_HEALING_WAVE = "Ola de sanación inferior", SPELL_HEALING_WAVE = "Ola de sanación", } --[[ localization template elseif currentLocale == "unknown" then L = { -- error messages -- FIXME: translate these ERROR_CALLBACK_REGISTERED = "a callback has already been registered with that key", ERROR_CALLBACK_USAGE = "usage: Healcomm:RegisterCallback(table, string or Healcomm:RegisterCallback(key, function)", -- priest heal spells with a casting time SPELL_LESSER_HEAL = SPELL_HEAL = SPELL_FLASH_HEAL = SPELL_GREATER_HEAL = SPELL_PRAYER_OF_HEALING = -- paladin heal spells with a casting time SPELL_HOLY_LIGHT = SPELL_FLASH_OF_LIGHT = -- druid heal spells with a casting time SPELL_REGROWTH = SPELL_HEALING_TOUCH = -- shaman heal spells with a casting time SPELL_LESSER_HEALING_WAVE = SPELL_HEALING_WAVE = } --]] else -- TODO: steal translations from BabbleSpell error(("Locale %s not supported."):format(currentLocale)) end --[[ DATA TABLES ]]----------------------------------------------------------- -- spells to watch for local watchSpells = { -- priest [L["SPELL_LESSER_HEAL"]] = true, [L["SPELL_HEAL"]] = true, [L["SPELL_FLASH_HEAL"]] = true, [L["SPELL_GREATER_HEAL"]] = true, [L["SPELL_PRAYER_OF_HEALING"]] = true, -- paladin [L["SPELL_HOLY_LIGHT"]] = true, [L["SPELL_FLASH_OF_LIGHT"]] = true, -- druid [L["SPELL_REGROWTH"]] = true, [L["SPELL_HEALING_TOUCH"]] = true, -- shaman [L["SPELL_LESSER_HEALING_WAVE"]] = true, [L["SPELL_HEALING_WAVE"]] = true, } -- spell abbreviations for the addon message -- all abbreviations are 2 letters so we can use string.sub instead of -- string.match -- lookup spell -> abbreviation local spellAbbr = {} -- lookup abbreviation ->spell local abbrSpell = {} -- keep the forward and backup lookups consistent -- also, sanity-check the input local function spellAbbr__newindex (t, k, v) error("don't modify this table!") end setmetatable(spellAbbr, { __newindex = spellAbbr__newindex }) local function abbrSpell__newindex (t, k, v) -- sanity-check our arguments if type(k) ~= "string" then error("key must be string") end if type(v) ~= "string" then error("value must be string") end if k:len() ~= 2 then error("key must be 2 characters long") end rawset(t, k, v) rawset(spellAbbr, v, k) end setmetatable(abbrSpell, { __newindex = abbrSpell__newindex }) -- priest abbrSpell["LH"] = L["SPELL_LESSER_HEAL"] abbrSpell["HE"] = L["SPELL_HEAL"] abbrSpell["FH"] = L["SPELL_FLASH_HEAL"] abbrSpell["GH"] = L["SPELL_GREATER_HEAL"] abbrSpell["PH"] = L["SPELL_PRAYER_OF_HEALING"] -- paladin abbrSpell["HL"] = L["SPELL_HOLY_LIGHT"] abbrSpell["FL"] = L["SPELL_FLASH_OF_LIGHT"] -- druid abbrSpell["RE"] = L["SPELL_REGROWTH"] abbrSpell["HT"] = L["SPELL_HEALING_TOUCH"] -- shaman abbrSpell["LW"] = L["SPELL_LESSER_HEALING_WAVE"] abbrSpell["HW"] = L["SPELL_HEALING_WAVE"] --[[ HERE'S THE REAL CODE ]]-------------------------------------------------- function lib:Initialize (old) self:Debug("Initialize()") self.callbacks = old and old.callbacks or {} self:RegisterEvent("ADDON_LOADED") end function lib:Enable () self:Debug("Enable()") self:RegisterEvent("UNIT_SPELLCAST_SENT") self:RegisterEvent("UNIT_SPELLCAST_DELAYED") self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") self:RegisterEvent("UNIT_SPELLCAST_FAILED") self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE") self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") self:RegisterEvent("CHAT_MSG_ADDON") end function lib:Disable () self:Debug("Disable()") self:UnregisterAllEvents() end -- callback management function lib:RegisterCallback (k, f) if self.callbacks[k] then error(L["ERROR_CALLBACK_REGISTERED"]) end if type(k) == "table" then if type(f) ~= "string" then error(L["ERROR_CALLBACK_USAGE"]) end elseif type(f) ~= "function" then error(L["ERROR_CALLBACK_USAGE"]) end self.callbacks[k] = f end function lib:UnregisterCallback (k) self.callbacks[k] = nil end --[[ Notes on event orders The following are the order that these events have been known to arrive in: Attempting to cast on an in-range target with obstructed line-of-sight: UNIT_SPELLCAST_SENT UNIT_SPELLCAST_FAILED Attempting to cast a non-instant spell while moving: UNIT_SPELLCAST_FAILED Casting a non-instant spell and then moving: UNIT_SPELLCAST_SENT UNIT_SPELLCAST_INTERRUPTED Casting a non-instant spell successfully: UNIT_SPELLCAST_SENT UNIT_SPELLCAST_SUCCEEDED UNIT_SPELLCAST_SUCCEEDED Casting a non-instant spell while being attacked: UNIT_SPELLCAST_SENT UNIT_SPELLCAST_DELAYED UNIT_SPELLCAST_SUCCEEDED UNIT_SPELLCAST_SUCCEEDED Casting an instant spell successfully: UNIT_SPELLCAST_SENT UNIT_SPELLCAST_SUCCEEDED Casting a channeled spell and then moving: ??? Casting a channeled spell successfully: ??? Casting a channeled spell while being attacked: ??? --]] -- event handlers local lastSpell -- only fired for player function lib:UNIT_SPELLCAST_SENT (unit, spell, rank, target) if unit ~= "player" then return end lastSpell = spell if not watchSpells[spell] then return end self:SendMessage("CAST", spell, rank, target) end function lib:UNIT_SPELLCAST_DELAYED (unit) if unit ~= "player" then return end if not (lastSpell and watchSpells[lastSpell]) then return end self:SendMessage("DELAYED") end function lib:UNIT_SPELLCAST_INTERRUPTED (unit) if unit ~= "player" then return end if not (lastSpell and watchSpells[lastSpell]) then return end self:SendMessage("INTERRUPTED") lastSpell = nil end -- only fired for player function lib:UNIT_SPELLCAST_SUCCEEDED (unit, spell, rank) if unit ~= "player" then return end lastSpell = nil end function lib:UNIT_SPELLCAST_FAILED (unit) if unit ~= "player" then return end if not (lastSpell and watchSpells[lastSpell]) then return end self:SendMessage("FAILED") lastSpell = nil end function lib:UNIT_SPELLCAST_CHANNEL_START (unit) if unit ~= "player" then return end if not (lastSpell and watchSpells[lastSpell]) then return end -- self:SendMessage("CHANNEL_START") end function lib:UNIT_SPELLCAST_CHANNEL_UPDATE (unit) if unit ~= "player" then return end if not (lastSpell and watchSpells[lastSpell]) then return end -- self:SendMessage("CHANNEL_UPDATE") end function lib:UNIT_SPELLCAST_CHANNEL_STOP (unit) if unit ~= "player" then return end if not (lastSpell and watchSpells[lastSpell]) then return end -- self:SendMessage("CHANNEL_STOP") end function lib:CHAT_MSG_ADDON (prefix, message, distribution, sender) if prefix ~= self.name then return end self:ReceiveMessage(sender, message) end -- ChatThrottleLib support local SendAddonMessage = SendAddonMessage local CTL function lib:ADDON_LOADED (name) if ChatThrottleLib == CTL then return end CTL = ChatThrottleLib self:Debug("ChatThrottleLib", CTL.version, "detected") local function CTL_SendAddonMessage (prefix, message, distribution) return CTL:SendAddonMessage("ALERT", prefix, message, distribution) end SendAddonMessage = CTL_SendAddonMessage end -- action abbreviations for the addon message -- all abbreviations are 2 letters so we can use string.sub instead of -- string.match -- lookup action -> abbreviation local actionAbbr = {} -- lookup abbreviation -> action local abbrAction = {} -- keep the forward and backup lookups consistent -- also, sanity-check the input local function actionAbbr__newindex (t, k, v) error("don't modify this table!") end setmetatable(actionAbbr, { __newindex = actionAbbr__newindex }) local function abbrAction__newindex (t, k, v) -- sanity-check our arguments if type(k) ~= "string" then error("key must be string") end if type(v) ~= "string" then error("value must be string") end if k:len() ~= 2 then error("key must be 2 characters long") end rawset(t, k, v) rawset(actionAbbr, v, k) end setmetatable(abbrAction, { __newindex = abbrAction__newindex }) abbrAction["C "] = "CAST" abbrAction["D "] = "DELAYED" abbrAction["I "] = "INTERRUPTED" abbrAction["F "] = "FAILED" abbrAction["S "] = "SUCCESS" -- message functions function lib:SendMessage(action, spell, rank, target) local message if action == "CAST" then rank = rank:match("(%d+)") message = ("%-2s%-2s%-2d%s"):format(actionAbbr[action], spellAbbr[spell], rank, target) else message = ("%-2s"):format(actionAbbr[action]) end local distribution if select(2, IsInInstance()) == "pvp" then distribution = "BATTLEGROUND" elseif GetNumRaidMembers() > 0 then distribution = "RAID" elseif GetNumPartyMembers() > 0 then distribution = "PARTY" end self:Debug("SendAddonMessage", self.name, message, distribution) if not distribution then return end SendAddonMessage(self.name, message, distribution) end function lib:ReceiveMessage(sender, message) local action, spell, rank, target action = message:sub(1, 2) spell = message:sub(3, 4) rank = message:sub(5, 6) target = message:sub(7, -1) action = abbrAction[action] spell = abbrSpell[spell] rank = tonumber(rank) if action == "CAST" then self:Debug("ReceiveMessage", sender, action, spell, rank, "on", target) else self:Debug("ReceiveMessage", sender, action) end for k, f in pairs(self.callbacks) do if type(k) == "table" then k[f](k, sender, action, spell, rank, target) else f(sender, action, spell, rank, target) end end end --[[ LIBRARY CORE ]]---------------------------------------------------------- -- everything below here is just basic library stuff -- inspired by Dongle and Ace2 --[[ DEBUGGING ]]------------------------------------------------------------- local debugArgs = {} function lib:DebugPrint (...) for i = #debugArgs, 1, -1 do debugArgs[i] = nil end table.insert(debugArgs, ("|cfffcc000||%s ||%d|||r"):format(self.name, self.version)) for i = 1, select("#", ...) do table.insert(debugArgs, tostring(select(i, ...))) end local message = table.concat(debugArgs, " ") self.debugFrame:AddMessage(message) end function lib:ToggleDebugging (on) if on == nil then self.debug = not self.debug else self.debug = on end self:DebugPrint("Debugging", self.debug and "|cff00cf00enabled|r" or "|cffcf0000disabled|r") return self.debug end function lib:Debug (...) if not self.debug then return end return self:DebugPrint(...) end --[[ EVENT HANDLING ]]-------------------------------------------------------- -- event dispatch table local events = {} -- event dispatch function function lib.OnEvent (frame, event, ...) local handler = events[event] lib.event = event if handler and type(lib[handler]) == "function" then lib:Debug("|cffcccc00Event:|r", event, ...) return lib[handler](lib, ...) end end -- I'm sure we'll use this for a timer eventually local time_since_last_schedule = 0 local time_until_next_schedule local next_scheduled_function function lib.OnUpdate (frame, elapsed) -- nothing to do if not time_until_next_schedule then return end time_since_last_schedule = time_since_last_schedule + elapsed -- nothing to do yet if time_since_last_schedule < time_until_next_schedule then return end next_scheduled_function() -- reset time_since_last_schedule = 0 time_until_next_schedule = nil next_scheduled_function = nil end -- setup our frame to catch events -- reuse an existing frame if we're replacing an older copy of the library -- function lib:RegisterEvent (event, handler) events[event] = handler or event self.eventFrame:RegisterEvent(event) end function lib:UnregisterEvent(event) self.eventFrame:UnregisterEvent(event) events[event] = nil end function lib:UnregisterAllEvents () for event in pairs(events) do self:UnregisterEvent(event) end end function lib:TriggerEvent (event, ...) return self.OnEvent(nil, event, ...) end --[[ LIBRARY MANAGEMENT ]]---------------------------------------------------- function lib:Install () local old = getglobal(self.name) -- copy or initialize library data if old then -- debugging self.debug = old.debug self.debugFrame = old.debugFrame -- event handling old:UnregisterAllEvents() self.eventFrame = old.eventFrame self.eventFrame:SetScript("OnEvent", nil) self.eventFrame:SetScript("OnUpdate", nil) else -- debugging self.debug = false self.debugFrame = ChatFrame1 -- event handling self.eventFrame = CreateFrame("Frame", self.name .. "_EVENT_FRAME") end -- more event handler setup self.eventFrame:SetScript("OnEvent", self.OnEvent) self.eventFrame:SetScript("OnUpdate", self.OnUpdate) -- install the library in the global namespace setglobal(self.name, self) -- register important event handlers self:RegisterEvent("LIB_INSTALLED", "Initialize") self:RegisterEvent("PLAYER_LOGIN", "Enable") self:RegisterEvent("PLAYER_LOGOUT", "Disable") -- tell the library to do it's thing self:TriggerEvent("LIB_INSTALLED", old) end lib:Install()