local PA = _G.ProjectAzilroka local AR = PA:NewModule('AuraReminder', 'AceEvent-3.0', 'AceTimer-3.0') PA.AR = AR AR.Title = PA.ACL['|cFF16C3F2Aura|r |cFFFFFFFFReminder|r'] AR.Description = PA.ACL['Reminder for Buffs / Debuffs'] AR.Authors = 'Azilroka' AR.isEnabled = false local _G = _G local pairs = pairs local ipairs = ipairs local tonumber = tonumber local tostring = tostring local select = select local format = format local strmatch = strmatch local unpack = unpack local tinsert = tinsert local wipe = wipe local GetSpellCooldown = GetSpellCooldown local GetSpellInfo = GetSpellInfo local IsSpellKnownOrOverridesKnown = IsSpellKnownOrOverridesKnown local IsUsableSpell = IsUsableSpell local IsInInstance = IsInInstance local UnitAffectingCombat = UnitAffectingCombat local UnitIsDeadOrGhost = UnitIsDeadOrGhost local UnitLevel = UnitLevel local UnitInVehicle = UnitInVehicle local GetInventoryItemID = GetInventoryItemID local GetWeaponEnchantInfo = GetWeaponEnchantInfo local GetInventoryItemTexture = GetInventoryItemTexture local PlaySoundFile = PlaySoundFile local GetSpecialization = GetSpecialization local GetSpecializationRole = GetSpecializationRole local GetSpecializationInfo = GetSpecializationInfo local GetItemInfoInstant = GetItemInfoInstant local MAX_PLAYER_LEVEL = MAX_PLAYER_LEVEL local CreateFrame = CreateFrame local UIParent = UIParent _G.AuraReminder = AR AR.CreatedReminders = {} local selectedFilter, selectedGroup, filters, spellList, filterTypeList, DefaultFilters = nil, PA.MyClass, {}, {}, {} do local function SpellIDPredicate(spellIDToFind, casterToFind, _, _, _, _, _, _, _, caster, _, _, spellID) return (casterToFind and caster == casterToFind and spellIDToFind == spellID) or (spellIDToFind == spellID) end function AR:FindAuraBySpellID(spellID, unit, filter, caster) return _G.AuraUtil.FindAura(SpellIDPredicate, unit, filter, spellID, caster); end end function AR:FindPlayerAura(db, checkPersonal, filter) if db then for spellID, value in pairs(db) do if value == true then return AR:FindAuraBySpellID(spellID, 'player', filter, checkPersonal and 'player') end end end end function AR:IsSpellOnCooldown(id) local start, duration = GetSpellCooldown(id) if start > 0 and duration > 1.5 then return true end end function AR:IsSpellUsable(id) local isUsable = IsUsableSpell(id) if isUsable then return true end end function AR:UpdateColors(frame, id) local isUsable, notEnoughMana = IsUsableSpell(id) if isUsable then frame.icon:SetVertexColor(1, 1, 1) elseif notEnoughMana then frame.icon:SetVertexColor(.5, .5, 1) else frame.icon:SetVertexColor(.4, .4, .4) end end function AR:HasOffHandWeapon(itemID) if not itemID then return end local itemEquipLoc = select(4, GetItemInfoInstant(itemID)) return (itemEquipLoc == "INVTYPE_WEAPON" or itemEquipLoc == "INVTYPE_2HWEAPON" or itemEquipLoc == "INVTYPE_WEAPONOFFHAND") end function AR:FilterCheck(db, isReverse) local _, instanceType = IsInInstance() local roleCheck, treeCheck, combatCheck, instanceCheck, PVPCheck = true, true, true, true, true if PA.Retail then local spec = GetSpecialization() if db.role and (db.role ~= 'ANY' and db.role ~= GetSpecializationRole(spec)) then roleCheck = false end if not isReverse and db.tree and (db.tree ~= 'ANY' and db.tree ~= spec) then treeCheck = false end if isReverse and db.talentTreeException and db.talentTreeException == spec then treeCheck = false end end if db.combat and not UnitAffectingCombat('player') then combatCheck = false end if db.instance and (instanceType ~= 'party' and instanceType ~= 'raid') then instanceCheck = false end if db.pvp and (instanceType ~= 'arena' and instanceType ~= 'pvp') then PVPCheck = false end if (isReverse or roleCheck and treeCheck) and combatCheck and instanceCheck and PVPCheck then return true else return false end end function AR:Reminder_Update() if UnitIsDeadOrGhost('player') or UnitInVehicle('player') then return end for _, button in ipairs(AR.CreatedReminders) do button:Hide() button:SetAlpha(1) end local Position = 1 for _, filter in pairs({PA.MyClass, 'Global'}) do if AR.db.Filters then for _, db in pairs(AR.db.Filters[filter]) do if db.enable and (not db.level or db.level and UnitLevel('player') > db.level) then local Button = AR.CreatedReminders[Position] if (not Button) or (Button:IsVisible()) then Button = AR:CreateReminder(Position) Position = Position + 1 end AR:SetIconPosition(Button, db) local filterCheck, reverseCheck = AR:FilterCheck(db), AR:FilterCheck(db, true) if db.filterType == 'COOLDOWN' and db.cooldownSpellID and filterCheck then local start, duration = GetSpellCooldown(db.cooldownSpellID) if (duration and duration > 1.5) then Button.cooldown:SetCooldown(start, duration) end Button.cooldown:SetShown((duration and duration > 0)) Button.icon:SetTexture(select(3, GetSpellInfo(db.cooldownSpellID))) Button:SetShown((duration and duration == 0) or db.onCooldown) AR:UpdateColors(Button, db.cooldownSpellID) if (duration and duration > 1.5) and filterCheck and db.onCooldown then Button:SetAlpha(db.cooldownAlpha or .5) end elseif (db.filterType == 'WEAPON' or (db.filterType == 'SPELL' and db.spellGroup and PA:CountTable(db.spellGroup) > 0)) and filterCheck then if db.filterType == 'SPELL' then local hasBuff, hasDebuff = AR:FindPlayerAura(db.spellGroup, db.personal), AR:FindPlayerAura(db.spellGroup, nil, 'HARMFUL') local negate = AR:FindPlayerAura(db.negateGroup, db.personal) local skip = false if not (negate or hasBuff or hasDebuff) then for buff, value in pairs(db.spellGroup) do if value then local usable = IsUsableSpell(buff); if db.strictFilter then usable = usable and IsSpellKnownOrOverridesKnown(buff); skip = not usable; end if usable and AR:IsSpellOnCooldown(buff) then break end if usable or not db.strictFilter then Button.icon:SetTexture(select(3, GetSpellInfo(buff))) AR:UpdateColors(Button, buff) break end end end Button:SetShown(not skip and (((not hasBuff) and (not hasDebuff)) and not db.reverseCheck) or (reverseCheck and db.reverseCheck and ((hasBuff or hasDebuff) or ((not hasBuff) and (not hasDebuff))))) end end if db.filterType == 'WEAPON' then local hasOffhandWeapon = AR:HasOffHandWeapon(GetInventoryItemID('player', 17)) local hasMainHandEnchant, _, _, hasOffHandEnchant = GetWeaponEnchantInfo() if (not hasMainHandEnchant) then Button.icon:SetTexture(GetInventoryItemTexture('player', 16)) elseif (hasOffhandWeapon and not hasOffHandEnchant) then Button.icon:SetTexture(GetInventoryItemTexture('player', 17)) end Button:SetShown(not (hasMainHandEnchant or hasOffHandEnchant)) Button.icon:SetVertexColor(1, 1, 1) end if Button:IsShown() and not db.disableSound then AR:PlaySoundFile() end end end end end end end function AR:ResetSoundPlayback() AR.IsPlaying = false end function AR:PlaySoundFile() if AR.IsPlaying then return end AR.IsPlaying = true PlaySoundFile(PA.LSM:Fetch('sound', AR.db.Sound)) AR:ScheduleTimer('ResetSoundPlayback', 10) end function AR:SetIconPosition(button, db) if not (button or db) then return end local xOffset = db.xOffset or 0 local yOffset = db.yOffset or 200 local size = db.size or 40 button:ClearAllPoints() button:SetPoint('CENTER', UIParent, 'CENTER', xOffset, yOffset); button:SetSize(size, size) end function AR:CreateReminder(index) local frame = CreateFrame('Frame', 'AuraReminder'..index, UIParent) frame:Hide() frame:SetClampedToScreen(true) frame.icon = frame:CreateTexture(nil, 'OVERLAY') frame.icon:SetTexCoord(unpack(PA.TexCoords)) frame.cooldown = CreateFrame('Cooldown', nil, frame, 'CooldownFrameTemplate') frame.cooldown:SetAllPoints(frame.icon) PA:SetTemplate(frame) PA:SetInside(frame.icon) tinsert(AR.CreatedReminders, frame) return frame end function AR:UpdateFilterGroup(group) for option, optionTable in pairs(PA.Options.args.AuraReminder.args.filterGroup.args[group].args) do if strmatch(option, "^AR") then optionTable.hidden = true end end if AR.db.Filters[selectedGroup][selectedFilter] and AR.db.Filters[selectedGroup][selectedFilter][group] then local i = 1 for spell in pairs(AR.db.Filters[selectedGroup][selectedFilter][group]) do if spell and GetSpellInfo(spell) then local name = format('AR%s', i + 2) local optionName = PA.Options.args.AuraReminder.args.filterGroup.args[group].args[name] if not optionName then PA.Options.args.AuraReminder.args.filterGroup.args[group].args[name] = { type = 'toggle', width = 'double' } optionName = PA.Options.args.AuraReminder.args.filterGroup.args[group].args[name] end optionName.name = function() return format('%s (%s)', GetSpellInfo(spell), spell) end optionName.get = function() return spell and AR.db.Filters[selectedGroup][selectedFilter][group][spell] end optionName.set = function(_, value) AR.db.Filters[selectedGroup][selectedFilter][group][spell] = value end optionName.hidden = false i = i + 1 end end end end function AR:CleanDB() -- Cleanup DB for _, filter in pairs({PA.MyClass, 'Global'}) do if AR.db.Filters and AR.db.Filters[filter] then for _, db in pairs(AR.db.Filters[filter]) do if db.role == 'ANY' then db.role = nil end if db.tree == 'ANY' then db.tree = nil end if db.talentTreeException == 'NONE' then db.talentTreeException = nil end if (db.level and db.level == 0) then db.level = nil end if not db.combat then db.combat = nil end if not db.instance then db.instance = nil end if not db.pvp then db.pvp = nil end if not db.onCooldown then db.onCooldown = nil end if not db.disableSound then db.disableSound = nil end end end end end local addGroupTemplate = { name = '', template = ''} function AR:GetOptions() local AuraReminder = PA.ACH:Group(AR.Title, nil, nil, 'tree', function(info) return AR.db[info[#info]] end, function(info, value) AR.db[info[#info]] = value end) PA.Options.args.AuraReminder = AuraReminder AuraReminder.args = { Description = { order = 0, type = 'description', name = AR.Description, }, Enable = { order = 1, type = 'toggle', name = PA.ACL['Enable'], set = function(info, value) AR.db[info[#info]] = value if (not AR.isEnabled) then AR:Initialize() else _G.StaticPopup_Show('PROJECTAZILROKA_RL') end end, }, Sound = { order = 2, type = 'select', dialogControl = 'LSM30_Sound', name = PA.ACL['Sound'], desc = PA.ACL['Sound that will play when you have a warning icon displayed.'], values = PA.LSM:HashTable('sound'), }, selectGroup = { order = 3, type = 'select', name = PA.ACL['Select Group'], get = function() if selectedGroup == 'Global' then return selectedGroup else return 'Class' end end, set = function(_, value) selectedFilter = nil if value == 'Class' then selectedGroup = PA.MyClass else selectedGroup = value end end, values = { Class = 'Class', Global = 'Global'}, }, selectFilter = { order = 4, type = 'select', name = PA.ACL['Select Filter'], get = function() return selectedFilter ~= '' and selectedFilter or '' end, set = function(_, value) selectedFilter = value ~= '' and value or nil AR:UpdateFilterGroup('spellGroup') AR:UpdateFilterGroup('negateGroup') end, values = function() wipe(filters) for filter in pairs(AR.db.Filters[selectedGroup]) do filters[filter] = filter end if not next(filters) then filters[''] = PA.ACL['None'] end return filters end, }, filterControl = { order = 5, type = 'group', name = PA.ACL['Filter Control'], inline = true, args = { addFilter = { order = 1, type = 'input', name = PA.ACL['New Filter Name'], get = function() return addGroupTemplate.name end, set = function(_, value) if AR.db.Filters[selectedGroup][value] then return end addGroupTemplate.name = value end, }, addFilterTemplate = { order = 2, type = 'select', name = PA.ACL['New Filter Type'], get = function() return addGroupTemplate.template end, set = function(_, value) if AR.db.Filters[selectedGroup][value] then return end addGroupTemplate.template = value end, values = function() wipe(filterTypeList) filterTypeList.SPELL = PA.ACL['Spell'] if selectedGroup ~= 'Global' then filterTypeList.WEAPON = PA.ACL['Weapon'] filterTypeList.COOLDOWN = PA.ACL['Cooldown'] end return filterTypeList end, }, addFilterButton = { type = 'execute', order = 3, name = PA.ACL['Add Filter'], hidden = function() return addGroupTemplate.name == '' or addGroupTemplate.template == '' end, func = function() AR.db.Filters[selectedGroup][addGroupTemplate.name] = { enable = true, size = 50, filterType = addGroupTemplate.template } if addGroupTemplate.template == 'COOLDOWN' then AR.db.Filters[selectedGroup][addGroupTemplate.name].cooldownAlpha = .5 --elseif addGroupTemplate.template == 'WEAPON' then else AR.db.Filters[selectedGroup][addGroupTemplate.name].spellGroup = {} AR.db.Filters[selectedGroup][addGroupTemplate.name].negateGroup = {} end selectedFilter = addGroupTemplate.name addGroupTemplate.name = '' addGroupTemplate.template = '' end, }, deleteGroup = { order = 5, type = 'select', name = PA.ACL['Remove Filter'], get = function() return '' end, confirm = function(_, value) return PA.ACL['Remove Filter']..' - '..value end, set = function(_, value) selectedFilter = nil if DefaultFilters[selectedGroup][value] then AR.db.Filters[selectedGroup][value].enable = false; else AR.db.Filters[selectedGroup][value] = nil; end end, values = function() wipe(filters) for filter in pairs(AR.db.Filters[selectedGroup]) do filters[filter] = filter end if not next(filters) then filters[''] = PA.ACL['None'] end return filters end, }, }, }, filterGroup = { order = 8, type = 'group', name = function() return selectedFilter end, inline = true, get = function(info) return AR.db.Filters[selectedGroup][selectedFilter][info[#info]] end, set = function(info, value) AR.db.Filters[selectedGroup][selectedFilter][info[#info]] = value end, hidden = function() return not selectedFilter end, args = { enable = { order = 1, type = 'toggle', name = PA.ACL['Enable'], }, filterType = { order = 2, type = 'select', name = PA.ACL['Filter Type'], desc = PA.ACL['Change this if you want the Reminder module to check for weapon enchants, setting this will cause it to ignore any spells listed.'], values = function() wipe(filterTypeList) filterTypeList.SPELL = PA.ACL['Spell'] if selectedGroup ~= 'Global' then filterTypeList.WEAPON = PA.ACL['Weapon'] filterTypeList.COOLDOWN = PA.ACL['Cooldown'] end return filterTypeList end, }, xOffset = { order = 3, type = 'range', name = PA.ACL['X Offset'], min = -(PA.ScreenWidth / 2), max = (PA.ScreenWidth / 2), step = 1, }, yOffset = { order = 4, type = 'range', name = PA.ACL['Y Offset'], min = -(PA.ScreenHeight / 2), max = (PA.ScreenHeight / 2), step = 1, }, size = { order = 5, type = 'range', name = PA.ACL['Size'], min = 0, max = 128, step = 1, }, conditions = { order = 10, type = 'multiselect', name = PA.ACL['Conditions'], get = function(_, key) return AR.db.Filters[selectedGroup][selectedFilter][key] end, set = function(_, key, value) AR.db.Filters[selectedGroup][selectedFilter][key] = value end, values = { instance = PA.ACL['Inside Raid/Party'], pvp = PA.ACL['Inside BG/Arena'], combat = PA.ACL['Combat'], }, }, filterConditions = { order = 11, type = 'group', name = PA.ACL['Filter Conditions'], inline = true, args = { level = { order = 4, type = 'range', name = PA.ACL['Level Requirement'], desc = PA.ACL['Level requirement for the icon to be able to display. 0 for disabled.'], min = 0, max = MAX_PLAYER_LEVEL, step = 1, }, personal = { order = 5, type = 'toggle', name = PA.ACL['Personal Buffs'], desc = PA.ACL['Only check if the buff is coming from you.'], hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType ~= 'SPELL' end, }, reverseCheck = { order = 6, type = 'toggle', name = PA.ACL['Reverse Check'], desc = PA.ACL['Instead of hiding the frame when you have the buff, show the frame when you have the buff.'], hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType ~= 'SPELL' end, }, strictFilter = { order = 7, type = 'toggle', name = PA.ACL['Strict Filter'], desc = PA.ACL['This ensures you can only see spells that you actually know. You may want to uncheck this option if you are trying to monitor a spell that is not directly clickable out of your spellbook.'], hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType == 'COOLDOWN' end, }, disableSound = { order = 8, type = 'toggle', name = PA.ACL['Disable Sound'], hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType == 'COOLDOWN' end, }, }, }, cooldownConditions = { order = 12, type = 'group', name = PA.ACL['Cooldown Conditions'], inline = true, hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType ~= 'COOLDOWN' or selectedGroup == 'Global' end, args = { dscription = { order = 0, type = 'description', fontSize = 'medium', name = function() local spellID = AR.db.Filters[selectedGroup][selectedFilter].cooldownSpellID if not spellID or spellID == '' then return end return format('%s (%s)', GetSpellInfo(spellID), spellID) end, hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].cooldownSpellID == '' end, }, cooldownSpellID = { order = 1, type = 'input', name = PA.ACL['Spell ID'], get = function(info) return tostring(AR.db.Filters[selectedGroup][selectedFilter][info[#info]] or '') end, set = function(info, value) value = tonumber(value) if not value then return end AR.db.Filters[selectedGroup][selectedFilter][info[#info]] = value end, }, onCooldown = { order = 2, type = 'toggle', name = PA.ACL['Show On Cooldown'], }, cooldownAlpha = { order = 3, type = 'range', name = PA.ACL['Cooldown Alpha'], min = 0, max = 1, step = 0.1, hidden = function() return not AR.db.Filters[selectedGroup][selectedFilter].onCooldown end, }, }, }, spellGroup = { order = 13, type = 'group', name = PA.ACL['Spells'], inline = true, get = function() AR:UpdateFilterGroup('spellGroup') return '' end, hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType ~= 'SPELL' end, args = { AddSpell = { order = 0, type = 'input', name = PA.ACL['New ID'], set = function(_, value) value = tonumber(value) if not value then return end AR.db.Filters[selectedGroup][selectedFilter].spellGroup = AR.db.Filters[selectedGroup][selectedFilter].spellGroup or {} AR.db.Filters[selectedGroup][selectedFilter].spellGroup[value] = true AR:UpdateFilterGroup('spellGroup') end, }, RemoveSpell = { order = 1, type = 'select', name = PA.ACL['Remove ID'], get = function() return '' end, set = function(_, value) AR.db.Filters[selectedGroup][selectedFilter].spellGroup[value] = nil; AR:UpdateFilterGroup('spellGroup') end, values = function() wipe(spellList) if AR.db.Filters[selectedGroup][selectedFilter].spellGroup then for spellID in pairs(AR.db.Filters[selectedGroup][selectedFilter].spellGroup) do local name = GetSpellInfo(spellID) spellList[spellID] = name and format('%s (%s)', name, spellID) or spellID end end return spellList end, }, spacer = PA.ACH:Spacer(2) }, }, negateGroup = { order = 14, type = 'group', name = PA.ACL['Negate Spells'], inline = true, get = function() AR:UpdateFilterGroup('negateGroup') return '' end, hidden = function() return AR.db.Filters[selectedGroup][selectedFilter].filterType ~= 'SPELL' end, args = { AddSpell = { order = 0, type = 'input', name = PA.ACL['New ID'], set = function(_, value) value = tonumber(value) if not value then return end AR.db.Filters[selectedGroup][selectedFilter].negateGroup = AR.db.Filters[selectedGroup][selectedFilter].negateGroup or {} AR.db.Filters[selectedGroup][selectedFilter].negateGroup[value] = true AR:UpdateFilterGroup('negateGroup') end, }, RemoveSpell = { order = 1, type = 'select', name = PA.ACL['Remove ID'], get = function() return '' end, set = function(_, value) AR.db.Filters[selectedGroup][selectedFilter].negateGroup[value] = nil; AR:UpdateFilterGroup('negateGroup') end, values = function() wipe(spellList) if AR.db.Filters[selectedGroup][selectedFilter].negateGroup then for spellID in pairs(AR.db.Filters[selectedGroup][selectedFilter].negateGroup) do local name = GetSpellInfo(spellID) spellList[spellID] = name and format('%s (%s)', name, spellID) or spellID end end if not next(spellList) then spellList[''] = PA.ACL['None'] end return spellList end, }, spacer = PA.ACH:Spacer(2) }, }, }, }, } PA.Options.args.AuraReminder.args.AuthorHeader = PA.ACH:Header(PA.ACL['Authors:'], -2) PA.Options.args.AuraReminder.args.Authors = PA.ACH:Description(AR.Authors, -1, 'large') if PA.Retail then local optionGroup = PA.Options.args.AuraReminder.args.filterGroup.args.filterConditions.args local Specializations = { ['ANY'] = PA.ACL['Any'] } for i = 1, 4 do Specializations[tostring(i)] = select(2, GetSpecializationInfo(i)) end optionGroup.role = PA.ACH:Select(PA.ACL['Role'], PA.ACL['You must be a certain role for the icon to appear.'], 1, { TANK = PA.ACL['Tank'], DAMAGER = PA.ACL['Damage'], HEALER = PA.ACL['Healer'], ANY = PA.ACL['Any'] }, nil, nil, function(info) return AR.db.Filters[selectedGroup][selectedFilter][info[#info]] or 'ANY' end, nil, nil, function() return selectedGroup == 'Global' end) optionGroup.tree = PA.ACH:Select(PA.ACL['Talent Tree'], PA.ACL['You must be using a certain talent tree for the icon to show.'], 2, Specializations, nil, nil, function() return tostring(AR.db.Filters[PA.MyClass][selectedFilter].tree or 'ANY') end, function(_, value) if value == 'ANY' then AR.db.Filters[PA.MyClass][selectedFilter].tree = 'ANY' else AR.db.Filters[PA.MyClass][selectedFilter].tree = tonumber(value) end end, nil, function() return selectedGroup == 'Global' or AR.db.Filters[PA.MyClass][selectedFilter].reverseCheck end) optionGroup.talentTreeException = PA.ACH:Select(PA.ACL['Tree Exception'], PA.ACL['Set a talent tree to not follow the reverse check.'], 2, CopyTable(Specializations), nil, nil, function() return tostring(AR.db.Filters[PA.MyClass][selectedFilter]['talentTreeException'] or 'NONE') end, function(_, value) if value == 'NONE' then AR.db.Filters[PA.MyClass][selectedFilter].talentTreeException = nil else AR.db.Filters[PA.MyClass][selectedFilter]['talentTreeException'] = tonumber(value) end; end, nil, function() return selectedGroup == 'Global' or not AR.db.Filters[PA.MyClass][selectedFilter].reverseCheck end) optionGroup.talentTreeException.values.NONE = PA.ACL['None'] end end function AR:BuildProfile() PA.Defaults.profile.AuraReminder = { Enable = true, Sound = 'Warning', Filters = { Global = {} }, } for k in pairs(LOCALIZED_CLASS_NAMES_MALE) do PA.Defaults.profile.AuraReminder.Filters[k] = {} end DefaultFilters = CopyTable(PA.Defaults.profile.AuraReminder.Filters) end function AR:Initialize() AR.db = PA.db.AuraReminder if AR.db.Enable ~= true then return end AR.isEnabled = true AR:ScheduleRepeatingTimer('Reminder_Update', .5) AR:CleanDB() AR:RegisterEvent("PLAYER_LOGOUT", 'CleanDB') end