diff --git a/WeakAuras/LibGroupTalentsWrapper.lua b/WeakAuras/LibGroupTalentsWrapper.lua
new file mode 100644
index 0000000..7b045fe
--- /dev/null
+++ b/WeakAuras/LibGroupTalentsWrapper.lua
@@ -0,0 +1,166 @@
+if not WeakAuras.IsLibsOK() then return end
+
+local AddonName, Private = ...
+
+local timer = WeakAuras.timer;
+local eventLock = false
+
+local nameToGlyphs = {}
+local nameToSpecMap = {}
+local nameToUnitMap = {
+ [UnitName("player")] = "player"
+}
+
+local subscribers = {}
+
+Private.LibGroupTalentsWrapper = {
+ Register = function(callback) end,
+ SpecForUnit = function(unit) end,
+ SpecRolePositionForUnit = function(unit) end,
+ CheckTalentForUnit = function(unit, talentId) end,
+ CheckGlyphForUnit = function(unit, glyphId) end,
+}
+
+local LibGroupTalents = LibStub("LibGroupTalents-1.0")
+if LibGroupTalents then
+ local frame = CreateFrame("Frame")
+ frame:RegisterEvent("PLAYER_LOGIN")
+ frame:RegisterEvent("RAID_ROSTER_UPDATE")
+ frame:RegisterEvent("PARTY_MEMBERS_CHANGED")
+
+ local function ProcessEvent()
+ local ownName = UnitName("player")
+
+ nameToUnitMap = {}
+ nameToUnitMap[ownName] = "player"
+
+ local numMembers
+ local units
+ if IsInRaid() then
+ numMembers = GetNumGroupMembers()
+ units = WeakAuras.raidUnits
+ else
+ numMembers = GetNumPartyMembers()
+ units = WeakAuras.partyUnits
+ end
+
+ for i = 1, numMembers do
+ local unit = units[i]
+ local name = UnitName(unit)
+ nameToUnitMap[name] = unit
+ end
+
+ for name in pairs(nameToSpecMap) do
+ if not nameToUnitMap[name] then
+ nameToSpecMap[name] = nil
+ nameToGlyphs[name] = nil
+ end
+ end
+
+ eventLock = false
+ end
+
+ frame:SetScript("OnEvent", function()
+ if not eventLock then
+ eventLock = true
+ timer:ScheduleTimer(ProcessEvent, 1)
+ end
+ end)
+
+ --- LibGroupTalents callback for talents and glyphs
+ local function LibGroupTalentsCallback(guid, unit)
+ local unitName = UnitName(unit)
+
+ -- Update specialization data
+ local specInfo = { LibGroupTalents:GetUnitTalentSpec(unit) }
+ if specInfo and #specInfo > 0 then
+ nameToSpecMap[unitName] = specInfo
+ end
+
+ -- Update glyphs
+ local glyphs = { LibGroupTalents:GetUnitGlyphs(unit) }
+ if glyphs and #glyphs > 0 then
+ nameToGlyphs[unitName] = {}
+ for _, glyphId in ipairs(glyphs) do
+ nameToGlyphs[unitName][glyphId] = true
+ end
+ end
+
+ -- Notify subscribers
+ for _, f in ipairs(subscribers) do
+ f(nameToUnitMap[unitName])
+ end
+ end
+
+ LibGroupTalents:RegisterCallback("LibGroupTalents_Update", LibGroupTalentsCallback)
+
+ function Private.LibGroupTalentsWrapper.Register(f)
+ table.insert(subscribers, f)
+ end
+
+ function Private.LibGroupTalentsWrapper.SpecForUnit(unit)
+ if UnitIsUnit(unit, "player") then
+ return LibGroupTalents:GetUnitTalentSpec(unit)
+ end
+
+ local unitName = UnitName(unit)
+ return nameToSpecMap[unitName] and nameToSpecMap[unitName][1] or nil
+ end
+
+ function Private.LibGroupTalentsWrapper.SpecRolePositionForUnit(unit)
+ if UnitIsUnit(unit, "player") then
+ return LibGroupTalents:GetUnitTalentSpec(unit)
+ end
+
+ local data = nameToSpecMap[UnitName(unit)]
+ if data then
+ return unpack(data)
+ else
+ return nil
+ end
+ end
+
+ function Private.LibGroupTalentsWrapper.CheckTalentForUnit(unit, talentId)
+ if UnitIsUnit(unit, "player") then
+ return select(4, WeakAuras.GetTalentById(talentId))
+ end
+
+ local unitName = UnitName(unit)
+ if not nameToSpecMap[unitName] then
+ return nil
+ end
+
+ return LibGroupTalents:UnitHasTalent(unit, talentId) and true or false
+ end
+
+ function Private.LibGroupTalentsWrapper.CheckGlyphForUnit(unit, glyphId)
+ if UnitIsUnit(unit, "player") then
+ local glyphs = { LibGroupTalents:GetUnitGlyphs(unit) }
+ for _, id in ipairs(glyphs) do
+ if id == glyphId then
+ return true
+ end
+ end
+ return false
+ end
+
+ local unitName = UnitName(unit)
+ if nameToGlyphs[unitName] then
+ return nameToGlyphs[unitName][glyphId] or false
+ end
+
+ return false
+ end
+else
+ function Private.LibGroupTalentsWrapper.Register(f) end
+ function Private.LibGroupTalentsWrapper.SpecForUnit(unit) return nil end
+ function Private.LibGroupTalentsWrapper.SpecRolePositionForUnit(unit) return nil end
+ function Private.LibGroupTalentsWrapper.CheckTalentForUnit(unit) return nil end
+ function Private.LibGroupTalentsWrapper.CheckGlyphForUnit(unit) return nil end
+end
+
+-- Export for GenericTrigger
+WeakAuras.SpecForUnit = Private.LibGroupTalentsWrapper.SpecForUnit
+WeakAuras.SpecRolePositionForUnit = Private.LibGroupTalentsWrapper.SpecRolePositionForUnit
+WeakAuras.CheckTalentForUnit = Private.LibGroupTalentsWrapper.CheckTalentForUnit
+WeakAuras.CheckGlyphForUnit = Private.LibGroupTalentsWrapper.CheckGlyphForUnit
diff --git a/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-3.0.lua b/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-3.0.lua
new file mode 100644
index 0000000..fc4a012
--- /dev/null
+++ b/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-3.0.lua
@@ -0,0 +1,292 @@
+-- LibBabble-3.0 is hereby placed in the Public Domain
+-- Credits: ckknight
+local LIBBABBLE_MAJOR, LIBBABBLE_MINOR = "LibBabble-3.0", 2
+
+local LibBabble = LibStub:NewLibrary(LIBBABBLE_MAJOR, LIBBABBLE_MINOR)
+if not LibBabble then
+ return
+end
+
+local data = LibBabble.data or {}
+for k,v in pairs(LibBabble) do
+ LibBabble[k] = nil
+end
+LibBabble.data = data
+
+local tablesToDB = {}
+for namespace, db in pairs(data) do
+ for k,v in pairs(db) do
+ tablesToDB[v] = db
+ end
+end
+
+local function warn(message)
+ local _, ret = pcall(error, message, 3)
+ geterrorhandler()(ret)
+end
+
+local lookup_mt = { __index = function(self, key)
+ local db = tablesToDB[self]
+ local current_key = db.current[key]
+ if current_key then
+ self[key] = current_key
+ return current_key
+ end
+ local base_key = db.base[key]
+ local real_MAJOR_VERSION
+ for k,v in pairs(data) do
+ if v == db then
+ real_MAJOR_VERSION = k
+ break
+ end
+ end
+ if not real_MAJOR_VERSION then
+ real_MAJOR_VERSION = LIBBABBLE_MAJOR
+ end
+ if base_key then
+ warn(("%s: Translation %q not found for locale %q"):format(real_MAJOR_VERSION, key, GetLocale()))
+ rawset(self, key, base_key)
+ return base_key
+ end
+ warn(("%s: Translation %q not found."):format(real_MAJOR_VERSION, key))
+ rawset(self, key, key)
+ return key
+end }
+
+local function initLookup(module, lookup)
+ local db = tablesToDB[module]
+ for k in pairs(lookup) do
+ lookup[k] = nil
+ end
+ setmetatable(lookup, lookup_mt)
+ tablesToDB[lookup] = db
+ db.lookup = lookup
+ return lookup
+end
+
+local function initReverse(module, reverse)
+ local db = tablesToDB[module]
+ for k in pairs(reverse) do
+ reverse[k] = nil
+ end
+ for k,v in pairs(db.current) do
+ reverse[v] = k
+ end
+ tablesToDB[reverse] = db
+ db.reverse = reverse
+ db.reverseIterators = nil
+ return reverse
+end
+
+local prototype = {}
+local prototype_mt = {__index = prototype}
+
+--[[---------------------------------------------------------------------------
+Notes:
+ * If you try to access a nonexistent key, it will warn but allow the code to pass through.
+Returns:
+ A lookup table for english to localized words.
+Example:
+ local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
+ local BL = B:GetLookupTable()
+ assert(BL["Some english word"] == "Some localized word")
+ DoSomething(BL["Some english word that doesn't exist"]) -- warning!
+-----------------------------------------------------------------------------]]
+function prototype:GetLookupTable()
+ local db = tablesToDB[self]
+
+ local lookup = db.lookup
+ if lookup then
+ return lookup
+ end
+ return initLookup(self, {})
+end
+--[[---------------------------------------------------------------------------
+Notes:
+ * If you try to access a nonexistent key, it will return nil.
+Returns:
+ A lookup table for english to localized words.
+Example:
+ local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
+ local B_has = B:GetUnstrictLookupTable()
+ assert(B_has["Some english word"] == "Some localized word")
+ assert(B_has["Some english word that doesn't exist"] == nil)
+-----------------------------------------------------------------------------]]
+function prototype:GetUnstrictLookupTable()
+ local db = tablesToDB[self]
+
+ return db.current
+end
+--[[---------------------------------------------------------------------------
+Notes:
+ * If you try to access a nonexistent key, it will return nil.
+ * This is useful for checking if the base (English) table has a key, even if the localized one does not have it registered.
+Returns:
+ A lookup table for english to localized words.
+Example:
+ local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
+ local B_hasBase = B:GetBaseLookupTable()
+ assert(B_hasBase["Some english word"] == "Some english word")
+ assert(B_hasBase["Some english word that doesn't exist"] == nil)
+-----------------------------------------------------------------------------]]
+function prototype:GetBaseLookupTable()
+ local db = tablesToDB[self]
+
+ return db.base
+end
+--[[---------------------------------------------------------------------------
+Notes:
+ * If you try to access a nonexistent key, it will return nil.
+ * This will return only one English word that it maps to, if there are more than one to check, see :GetReverseIterator("word")
+Returns:
+ A lookup table for localized to english words.
+Example:
+ local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
+ local BR = B:GetReverseLookupTable()
+ assert(BR["Some localized word"] == "Some english word")
+ assert(BR["Some localized word that doesn't exist"] == nil)
+-----------------------------------------------------------------------------]]
+function prototype:GetReverseLookupTable()
+ local db = tablesToDB[self]
+
+ local reverse = db.reverse
+ if reverse then
+ return reverse
+ end
+ return initReverse(self, {})
+end
+local blank = {}
+local weakVal = {__mode='v'}
+--[[---------------------------------------------------------------------------
+Arguments:
+ string - the localized word to chek for.
+Returns:
+ An iterator to traverse all English words that map to the given key
+Example:
+ local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
+ for word in B:GetReverseIterator("Some localized word") do
+ DoSomething(word)
+ end
+-----------------------------------------------------------------------------]]
+function prototype:GetReverseIterator(key)
+ local db = tablesToDB[self]
+ local reverseIterators = db.reverseIterators
+ if not reverseIterators then
+ reverseIterators = setmetatable({}, weakVal)
+ db.reverseIterators = reverseIterators
+ elseif reverseIterators[key] then
+ return pairs(reverseIterators[key])
+ end
+ local t
+ for k,v in pairs(db.current) do
+ if v == key then
+ if not t then
+ t = {}
+ end
+ t[k] = true
+ end
+ end
+ reverseIterators[key] = t or blank
+ return pairs(reverseIterators[key])
+end
+--[[---------------------------------------------------------------------------
+Returns:
+ An iterator to traverse all translations English to localized.
+Example:
+ local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
+ for english, localized in B:Iterate() do
+ DoSomething(english, localized)
+ end
+-----------------------------------------------------------------------------]]
+function prototype:Iterate()
+ local db = tablesToDB[self]
+
+ return pairs(db.current)
+end
+
+-- #NODOC
+-- modules need to call this to set the base table
+function prototype:SetBaseTranslations(base)
+ local db = tablesToDB[self]
+ local oldBase = db.base
+ if oldBase then
+ for k in pairs(oldBase) do
+ oldBase[k] = nil
+ end
+ for k, v in pairs(base) do
+ oldBase[k] = v
+ end
+ base = oldBase
+ else
+ db.base = base
+ end
+ for k,v in pairs(base) do
+ if v == true then
+ base[k] = k
+ end
+ end
+end
+
+local function init(module)
+ local db = tablesToDB[module]
+ if db.lookup then
+ initLookup(module, db.lookup)
+ end
+ if db.reverse then
+ initReverse(module, db.reverse)
+ end
+ db.reverseIterators = nil
+end
+
+-- #NODOC
+-- modules need to call this to set the current table. if current is true, use the base table.
+function prototype:SetCurrentTranslations(current)
+ local db = tablesToDB[self]
+ if current == true then
+ db.current = db.base
+ else
+ local oldCurrent = db.current
+ if oldCurrent then
+ for k in pairs(oldCurrent) do
+ oldCurrent[k] = nil
+ end
+ for k, v in pairs(current) do
+ oldCurrent[k] = v
+ end
+ current = oldCurrent
+ else
+ db.current = current
+ end
+ end
+ init(self)
+end
+
+for namespace, db in pairs(data) do
+ setmetatable(db.module, prototype_mt)
+ init(db.module)
+end
+
+-- #NODOC
+-- modules need to call this to create a new namespace.
+function LibBabble:New(namespace, minor)
+ local module, oldminor = LibStub:NewLibrary(namespace, minor)
+ if not module then
+ return
+ end
+
+ if not oldminor then
+ local db = {
+ module = module,
+ }
+ data[namespace] = db
+ tablesToDB[module] = db
+ else
+ for k,v in pairs(module) do
+ module[k] = nil
+ end
+ end
+
+ setmetatable(module, prototype_mt)
+
+ return module
+end
diff --git a/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-TalentTree-3.0.lua b/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-TalentTree-3.0.lua
new file mode 100644
index 0000000..a7fbab7
--- /dev/null
+++ b/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-TalentTree-3.0.lua
@@ -0,0 +1,295 @@
+--[[
+Name: LibBabble-TalentTree-3.0
+Revision: $Rev: 26 $
+Maintainers: ckknight, nevcairiel, Ackis
+Website: http://www.wowace.com/projects/libbabble-talenttree-3-0/
+Dependencies: None
+License: MIT
+]]
+
+local MAJOR_VERSION = "LibBabble-TalentTree-3.0"
+local MINOR_VERSION = 90000 + tonumber(("$Rev: 26 $"):match("%d+"))
+
+if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end
+local lib = LibStub("LibBabble-3.0"):New(MAJOR_VERSION, MINOR_VERSION)
+if not lib then return end
+
+local GAME_LOCALE = GetLocale()
+
+lib:SetBaseTranslations {
+ Affliction = "Affliction",
+ Arcane = "Arcane",
+ Arms = "Arms",
+ Assassination = "Assassination",
+ Balance = "Balance",
+ ["Beast Mastery"] = "Beast Mastery",
+ Blood = "Blood",
+ Combat = "Combat",
+ Demonology = "Demonology",
+ Destruction = "Destruction",
+ Discipline = "Discipline",
+ Elemental = "Elemental",
+ Enhancement = "Enhancement",
+ ["Feral Combat"] = "Feral Combat",
+ Fire = "Fire",
+ Frost = "Frost",
+ Fury = "Fury",
+ Holy = "Holy",
+ Hybrid = "Hybrid",
+ Marksmanship = "Marksmanship",
+ Protection = "Protection",
+ Restoration = "Restoration",
+ Retribution = "Retribution",
+ Shadow = "Shadow",
+ Subtlety = "Subtlety",
+ Survival = "Survival",
+ Unholy = "Unholy",
+}
+
+
+if GAME_LOCALE == "enUS" then
+ lib:SetCurrentTranslations(true)
+elseif GAME_LOCALE == "deDE" then
+ lib:SetCurrentTranslations {
+ Affliction = "Gebrechen",
+ Arcane = "Arkan",
+ Arms = "Waffen",
+ Assassination = "Meucheln",
+ Balance = "Gleichgewicht",
+ ["Beast Mastery"] = "Tierherrschaft",
+ Blood = "Blut",
+ Combat = "Kampf",
+ Demonology = "Dämonologie",
+ Destruction = "Zerstörung",
+ Discipline = "Disziplin",
+ Elemental = "Elementar",
+ Enhancement = "Verstärkung",
+ ["Feral Combat"] = "Wilder Kampf",
+ Fire = "Feuer",
+ Frost = "Frost",
+ Fury = "Furor",
+ Holy = "Heilig",
+ Hybrid = "Hybride",
+ Marksmanship = "Treffsicherheit",
+ Protection = "Schutz",
+ Restoration = "Wiederherstellung",
+ Retribution = "Vergeltung",
+ Shadow = "Schatten",
+ Subtlety = "Täuschung",
+ Survival = "Überleben",
+ Unholy = "Unheilig",
+}
+elseif GAME_LOCALE == "frFR" then
+ lib:SetCurrentTranslations {
+ Affliction = "Affliction",
+ Arcane = "Arcane",
+ Arms = "Armes",
+ Assassination = "Assassinat",
+ Balance = "Equilibre",
+ ["Beast Mastery"] = "Maîtrise des bêtes",
+ Blood = "Sang",
+ Combat = "Combat",
+ Demonology = "Démonologie",
+ Destruction = "Destruction",
+ Discipline = "Discipline",
+ Elemental = "Elémentaire",
+ Enhancement = "Amélioration",
+ ["Feral Combat"] = "Combat farouche",
+ Fire = "Feu",
+ Frost = "Givre",
+ Fury = "Fureur",
+ Holy = "Sacré",
+ Hybrid = "Hybride",
+ Marksmanship = "Précision",
+ Protection = "Protection",
+ Restoration = "Restauration",
+ Retribution = "Vindicte",
+ Shadow = "Ombre",
+ Subtlety = "Finesse",
+ Survival = "Survie",
+ Unholy = "Impie",
+}
+elseif GAME_LOCALE == "koKR" then
+ lib:SetCurrentTranslations {
+ Affliction = "고통",
+ Arcane = "비전",
+ Arms = "무기",
+ Assassination = "암살",
+ Balance = "조화",
+ ["Beast Mastery"] = "야수",
+ Blood = "혈기",
+ Combat = "전투",
+ Demonology = "악마",
+ Destruction = "파괴",
+ Discipline = "수양",
+ Elemental = "정기",
+ Enhancement = "고양",
+ ["Feral Combat"] = "야성",
+ Fire = "화염",
+ Frost = "냉기",
+ Fury = "분노",
+ Holy = "신성",
+ Hybrid = "하이브리드",
+ Marksmanship = "사격",
+ Protection = "방어",
+ Restoration = "복원",
+ Retribution = "징벌",
+ Shadow = "암흑",
+ Subtlety = "잠행",
+ Survival = "생존",
+ Unholy = "부정",
+}
+elseif GAME_LOCALE == "esES" then
+ lib:SetCurrentTranslations {
+ Affliction = "Aflicción",
+ Arcane = "Arcano",
+ Arms = "Armas",
+ Assassination = "Asesinato",
+ Balance = "Equilibrio",
+ ["Beast Mastery"] = "Dominio de bestias",
+ Blood = "Sangre",
+ Combat = "Combate",
+ Demonology = "Demonología",
+ Destruction = "Destrucción",
+ Discipline = "Disciplina",
+ Elemental = "Elemental",
+ Enhancement = "Mejora",
+ ["Feral Combat"] = "Combate Feral",
+ Fire = "Fuego",
+ Frost = "Escarcha",
+ Fury = "Furia",
+ Holy = "Sagrado",
+ Hybrid = "Híbrido",
+ Marksmanship = "Puntería",
+ Protection = "Protección",
+ Restoration = "Restauración",
+ Retribution = "Reprensión",
+ Shadow = "Sombras",
+ Subtlety = "Sutileza",
+ Survival = "Supervivencia",
+ Unholy = "Profano",
+}
+elseif GAME_LOCALE == "esMX" then
+ lib:SetCurrentTranslations {
+ Affliction = "Aflicción",
+ Arcane = "Arcano",
+ Arms = "Armas",
+ Assassination = "Asesinato",
+ Balance = "Balance",
+ ["Beast Mastery"] = "Bestias",
+ Blood = "Sangre",
+ Combat = "Combate",
+ Demonology = "Demonología",
+ Destruction = "Destrucción",
+ Discipline = "Disciplina",
+ Elemental = "Elemental",
+ Enhancement = "Mejora",
+ ["Feral Combat"] = "Combate feral",
+ Fire = "Fuego",
+ Frost = "Escarcha",
+ Fury = "Furia",
+ Holy = "Sagrado",
+ Hybrid = "Híbrido",
+ Marksmanship = "Puntería",
+ Protection = "Protección",
+ Restoration = "Restauración",
+ Retribution = "Reprensión",
+ Shadow = "Sombra",
+ Subtlety = "Sutileza",
+ Survival = "Supervivencia",
+ Unholy = "Profano",
+}
+elseif GAME_LOCALE == "ruRU" then
+ lib:SetCurrentTranslations {
+ Affliction = "Колдовство",
+ Arcane = "Тайная магия",
+ Arms = "Оружие",
+ Assassination = "Убийство",
+ Balance = "Баланс",
+ ["Beast Mastery"] = "Чувство зверя",
+ Blood = "Кровь",
+ Combat = "Бой",
+ Demonology = "Демонология",
+ Destruction = "Разрушение",
+ Discipline = "Послушание",
+ Elemental = "Укрощение стихии",
+ Enhancement = "Совершенствование",
+ ["Feral Combat"] = "Сила зверя",
+ Fire = "Огонь",
+ Frost = "Лед",
+ Fury = "Неистовство",
+ Holy = "Свет",
+ Hybrid = "Гибрид",
+ Marksmanship = "Стрельба",
+ Protection = "Защита",
+ Restoration = "Исцеление",
+ Retribution = "Возмездие",
+ Shadow = "Темная магия",
+ Subtlety = "Скрытность",
+ Survival = "Выживание",
+ Unholy = "Нечестивость",
+}
+elseif GAME_LOCALE == "zhCN" then
+ lib:SetCurrentTranslations {
+ Affliction = "痛苦",
+ Arcane = "奥术",
+ Arms = "武器",
+ Assassination = "刺杀",
+ Balance = "平衡",
+ ["Beast Mastery"] = "野兽控制",
+ Blood = "鲜血",
+ Combat = "战斗",
+ Demonology = "恶魔学识",
+ Destruction = "毁灭",
+ Discipline = "戒律",
+ Elemental = "元素战斗",
+ Enhancement = "增强",
+ ["Feral Combat"] = "野性战斗",
+ Fire = "火焰",
+ Frost = "冰霜",
+ Fury = "狂怒",
+ Holy = "神圣",
+ Hybrid = "混合",
+ Marksmanship = "射击",
+ Protection = "防护",
+ Restoration = "恢复",
+ Retribution = "惩戒",
+ Shadow = "暗影魔法",
+ Subtlety = "敏锐",
+ Survival = "生存技能",
+ Unholy = "邪恶",
+}
+elseif GAME_LOCALE == "zhTW" then
+ lib:SetCurrentTranslations {
+ Affliction = "痛苦",
+ Arcane = "秘法",
+ Arms = "武器",
+ Assassination = "刺殺",
+ Balance = "平衡",
+ ["Beast Mastery"] = "野獸控制",
+ Blood = "血魄",
+ Combat = "戰鬥",
+ Demonology = "惡魔學識",
+ Destruction = "毀滅",
+ Discipline = "戒律",
+ Elemental = "元素",
+ Enhancement = "增強",
+ ["Feral Combat"] = "野性戰鬥",
+ Fire = "火焰",
+ Frost = "冰霜",
+ Fury = "狂怒",
+ Holy = "神聖",
+ Hybrid = "混合",
+ Marksmanship = "射擊",
+ Protection = "防護",
+ Restoration = "恢復",
+ Retribution = "懲戒",
+ Shadow = "暗影",
+ Subtlety = "敏銳",
+ Survival = "生存",
+ Unholy = "穢邪",
+}
+
+else
+ error(("%s: Locale %q not supported"):format(MAJOR_VERSION, GAME_LOCALE))
+end
diff --git a/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-TalentTree-3.0.toc b/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-TalentTree-3.0.toc
new file mode 100644
index 0000000..ab7936b
--- /dev/null
+++ b/WeakAuras/Libs/LibBabble-TalentTree-3.0/LibBabble-TalentTree-3.0.toc
@@ -0,0 +1,21 @@
+## Interface: 30300
+## LoadOnDemand: 1
+## Title: Lib: Babble-TalentTree-3.0
+## Notes: A library to help with localization of talent trees.
+## Notes-deDE: BabbleLib ist eine Bibliothek, die bei der Lokalisierung helfen soll.
+## Notes-esES: Una biblioteca para ayudar con las localizaciones.
+## Notes-frFR: Une bibliothèque d'aide à la localisation.
+## Notes-ruRU: Библиотека для локализации аддонов.
+## Notes-zhCN: 为本地化服务的支持库[声望阵营]
+## Notes-zhTW: 為本地化服務的函式庫[聲望陣營]
+## Author: Pneumatus
+## X-Category: Library
+## X-License: MIT
+## X-Curse-Packaged-Version: 3.3-release39
+## X-Curse-Project-Name: LibBabble-TalentTree-3.0
+## X-Curse-Project-ID: libbabble-talenttree-3-0
+## X-Curse-Repository-ID: wow/libbabble-talenttree-3-0/mainline
+
+LibStub\LibStub.lua
+lib.xml
+
diff --git a/WeakAuras/Libs/LibBabble-TalentTree-3.0/lib.xml b/WeakAuras/Libs/LibBabble-TalentTree-3.0/lib.xml
new file mode 100644
index 0000000..d1ad4c2
--- /dev/null
+++ b/WeakAuras/Libs/LibBabble-TalentTree-3.0/lib.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/WeakAuras/Libs/LibGroupTalents-1.0/LibGroupTalents-1.0.lua b/WeakAuras/Libs/LibGroupTalents-1.0/LibGroupTalents-1.0.lua
new file mode 100644
index 0000000..e3a0cc2
--- /dev/null
+++ b/WeakAuras/Libs/LibGroupTalents-1.0/LibGroupTalents-1.0.lua
@@ -0,0 +1,1727 @@
+--[[
+Name: LibGroupTalents-1.0
+Revision: $Rev: 55 $
+Author: Zek
+Documentation: http://wowace.com/wiki/LibGroupTalents-1.0
+SVN: svn://svn.wowace.com/wow/libgrouptalents-1-0/mainline/trunk
+Description: Talent Library abstraction layer to provide easy interface to the lower level system
+Dependancies: LibStub, CallbackHandler-1.0, LibTalentQuery-1.0
+License: GPL v3
+
+Purpose:
+ LibGroupTalents-1.0 is intended to do the following basic functions usually handled at the mod level.
+
+ - Maintain a raid wide table of talents, automatically updated on roster changes, notifying you on talent receipts.
+ - Provide easy access to talent queries (spec weight, spec name, specific talent presence)
+ - Monitor talent changes in players, and notify of changes (respec, talent swap, update after out of sight, level up)
+ - Monitor player roles, and notify of changes (melee, tank, healer, caster)
+ - Communicate directly with itself to other users to update talents via addon channel when possible
+
+Notes:
+ The LibTalentQuery-1.0 dependancy must be included before LibGroupTalents-1.0 in any lib.xml or mod side TOC declarations.
+
+Functions:
+ UnitHasTalent(unit, talentName[, group])-- Returns: Points spent in talent or nil
+ GUIDHasTalent(guid, talentName[, group])-- As UnitHasTalent
+ GetUnitTalentSpec(unitid[, group]) -- Returns: Dominant Tree, spent1, spent2, spent3
+ GetGUIDTalentSpec(guid[, group]) -- As GetUnitTalentSpec
+ GetUnitTalents(unit, refresh) -- Returns: Raw talent information in form of table of 3 strings of points spent. The refresh arg will force a re-query of the unit's talents
+ GetGUIDTalents(guid, refresh) -- As GetUnitTalents
+ GetUnitRole(unit) -- Returns one of: "melee", "caster", "healer", "tank"
+ GetGUIDRole(guid) -- As GetUnitRole
+ RefreshTalentsByUnit(unit) -- Force a refresh of talents for the specific unit
+ RefreshTalentsByGUID(guid) -- Force a refresh of talents for the specific player GUID
+ GetTreeNames(class) -- Returns: The three talent tree names for that class (Note: These return values are only valid after a player of that class has been inspected)
+ GetTreeIcons(class) -- Returns: The three talent tree icons for that class (Note: As above)
+ GetTalentCount() -- Returns: Talent info got, Talent info missing
+ GetTalentMissingNames() -- Returns: Comma delimited list of player names we're missing talents for
+ GetClassTalentInfo(class, talentName) -- Returns: Max Rank, Icon, Tab, Tier, Column, Tree Index
+ GetUnitStorageString(unit) -- Returns: An encoded data string containing talent information for the player which can be stored by mods to set in later sessions using SetStorageString()
+ GetGUIDStorageString(guid) -- As GetUnitStorageString
+ SetStorageString(talentString) -- Returns: true on success (applicable). Any second return value indicates the data was invalid and should not be kept
+ GetUnitGlyphs(unit[, group]) -- Returns: Up to 6 spell IDs for the currently assigned Glyphs (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
+ GetGUIDGlyphs(guid[, group]) -- As GetUnitGlyphs
+ UnitHasGlyph(unit, glyph [, group]) -- Returns: true if the player has the glyph associated with spellID or spellName (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
+ GUIDHasGlyph(unit, glyph [, group]) -- As UnitHasGlyph
+ PurgeAndRescanTalents() -- Wipe current roster of all talents and rescan from start
+
+Convenience Functions (Similar to Blizzard API functions, but callable with a unit ID):
+ GetActiveTalentGroup(unit) -- Returns: Active talent group for unit
+ GetNumTalentGroups(unit) -- Returns: Number of talent groups for unit
+ GetNumTalentTabs(unit) -- Returns: Number of talent tabs. Here's a clue; it's going to be 3...
+ GetTalentTabInfo(unit, tab[, group]) -- Returns: Tree Name, Tree Icon, Points Spent, Tree Background
+ GetNumTalents(unit, tab) -- Returns: Number of talents for specified tree
+ GetTalentInfo(unit, tab, index[, group])-- Returns: Talent Name, Icon, Tier, Column, Points Spent, Max Rank (Note that preview return values are not given unless called with "player")
+ GetUnspentTalentPoints(unit[, group]) -- Returns: Number of un-spent talent points for the unit
+
+Events:
+ LibGroupTalents_Update(guid, unit, newSpec, n1, n2, n3 [, oldSpec, o1, o2, o3]) -- Received updated talents. If it's a respec, or old set is know, it passes the old info also (this is not sent if new talent scan is same as previous)
+ LibGroupTalents_UpdateComplete(guid1, guid2[, ...]) -- Sent when there are no more pending talent reads due (passes all GUIDs that were updated since last time this event was fired)
+ LibGroupTalents_Add(guid, unit, name, realm) -- Unit added to talent roster (Talents not necessarily available yet, but this is the mod's chance to feed talents using SetStorageString)
+ LibGroupTalents_Remove(guid, name, realm) -- Unit removed from talent roster (This is your last chance to store talents if required using GetUnitStorageString)
+ LibGroupTalents_RoleChange(guid, unit, newrole, oldrole) -- Roles are: "melee", "caster", "healer", "tank"
+ LibGroupTalents_GlyphUpdate(guid, unit) -- Fired when a player's glyphs change (Note: For the moment, we can only see the glyphs of players running LibGroupTalents-1.0)
+
+]]
+
+local TalentQuery = LibStub("LibTalentQuery-1.0")
+
+local MAJOR, MINOR = "LibGroupTalents-1.0", tonumber(("$Rev: 55 $"):match("(%d+)"))
+local lib = LibStub:NewLibrary(MAJOR, MINOR)
+if not lib then return end
+
+local ChatThrottleLib = _G.ChatThrottleLib
+
+lib.roster = lib.roster or {}
+lib.classTalentData = lib.classTalentData or {}
+lib.batch = lib.batch or {}
+lib.pendingStorageStrings = lib.pendingStorageStrings or {}
+
+local function UnitFullName(unit)
+ local name, realm = UnitName(unit)
+ local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
+ return namerealm
+end
+
+local function RosterInfoFullName(info)
+ local name, realm = info.name, info.realm
+ local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
+ return namerealm
+end
+
+local specChangers = {}
+for index,spellid in ipairs(_G.TALENT_ACTIVATION_SPELLS) do
+ specChangers[GetSpellInfo(spellid)] = index
+end
+
+local frame = lib.frame
+if (not frame) then
+ frame = CreateFrame("Frame", "LibGroupTalents_Frame")
+ lib.frame = frame
+end
+frame:UnregisterAllEvents()
+frame:RegisterEvent("RAID_ROSTER_UPDATE")
+frame:RegisterEvent("PARTY_MEMBERS_CHANGED")
+frame:RegisterEvent("UNIT_NAME_UPDATE")
+frame:RegisterEvent("PLAYER_TALENT_UPDATE")
+frame:RegisterEvent("UNIT_LEVEL")
+frame:RegisterEvent("UNIT_AURA") -- Always get a UNIT_AURA when a unit's UnitIsVisible() changes
+frame:RegisterEvent("CHAT_MSG_ADDON")
+frame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
+frame:RegisterEvent("PLAYER_LOGIN")
+frame:RegisterEvent("GLYPH_ADDED")
+frame:RegisterEvent("GLYPH_REMOVED")
+frame:RegisterEvent("GLYPH_UPDATED")
+
+frame:SetScript("OnEvent", function(self, event, ...)
+ return lib[event](lib, ...)
+end)
+
+if not lib.events then
+ lib.events = LibStub("CallbackHandler-1.0"):New(lib)
+end
+
+local next, select, pairs, type = next, select, pairs, type
+local new, del, deepDel
+do
+ local list = setmetatable({},{__mode='k'})
+ function new(...)
+ local t = next(list)
+ if t then
+ list[t] = nil
+ for i = 1, select('#', ...) do
+ t[i] = select(i, ...)
+ end
+ return t
+ else
+ return {...}
+ end
+ end
+ function del(t)
+ if (t) then
+ wipe(t)
+ t[''] = true
+ t[''] = nil
+ list[t] = true
+ end
+ end
+ function deepDel(t)
+ if (t) then
+ for k,v in pairs(t) do
+ if type(v) == "table" then
+ deepDel(v)
+ end
+ t[k] = nil
+ end
+ t[''] = true
+ t[''] = nil
+ list[t] = true
+ end
+ end
+end
+
+do
+ local delay = 0
+ frame:SetScript("OnUpdate", function(self, elapsed)
+ if (lib.raidRosterUpdate) then
+ lib.raidRosterUpdate = nil
+ lib:OnRaidRosterUpdate()
+ end
+
+ if (lib.refreshCheckTimer) then
+ lib.refreshCheckTimer = lib.refreshCheckTimer - elapsed
+ if (lib.refreshCheckTimer < 0) then
+ lib.refreshCheckTimer = nil
+ lib:CheckForMissingTalents()
+ end
+ end
+
+ if (lib.talentTimers) then
+ delay = delay + elapsed
+ if (delay > 1) then
+ delay = 0
+ local now = GetTime()
+ local triggers
+ for guid,when in pairs(lib.talentTimers) do
+ if (now > when) then
+ -- Pass to second table to process, because RefreshTimers can affect this talentTimers table
+ -- So it's important we're not still iterating it at the time
+ if (not triggers) then
+ triggers = new()
+ end
+ triggers[guid] = true
+ lib.talentTimers[guid] = nil
+ if (not next(lib.talentTimers)) then
+ lib.talentTimers = del(lib.talentTimers)
+ break
+ end
+ end
+ end
+
+ if (triggers) then
+ for guid in pairs(triggers) do
+ lib:RefreshTalentsByGUID(guid)
+ end
+ del(triggers)
+ end
+ end
+ end
+
+ if (not lib.talentTimers and not lib.refreshCheckTimer) then
+ self:Hide()
+ end
+ end)
+end
+frame:Show()
+lib.raidRosterUpdate = true
+
+-- GetGUIDTalentsRaw
+local function GetGUIDTalentsRaw(guid, group)
+ local r = guid and lib.roster[guid]
+ return r and r.talents and r.talents[group or r.active], r
+end
+
+-- PLAYER_LOGIN
+function lib:PLAYER_LOGIN()
+ ChatThrottleLib = _G.ChatThrottleLib
+ lib.PLAYER_LOGIN = nil
+end
+
+-- RAID_ROSTER_UPDATE
+function lib:RAID_ROSTER_UPDATE()
+ self.raidRosterUpdate = true
+ frame:Show()
+end
+lib.PARTY_MEMBERS_CHANGED = lib.RAID_ROSTER_UPDATE
+
+-- UNIT_NAME_UPDATE
+function lib:UNIT_NAME_UPDATE(unit)
+ local guid = unit and UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (r) then
+ local needsAdd = r.name == UNKNOWN
+ r.name, r.realm = UnitName(unit)
+ if (r.realm == "") then
+ r.realm = nil
+ end
+ r.class = select(2, UnitClass(unit))
+ r.level = UnitLevel(unit)
+ if (not r.talents) then
+ if (needsAdd) then
+ self.events:Fire("LibGroupTalents_Add", guid, unit, r.name, r.realm)
+ end
+
+ self:CheckForMissingTalents()
+ end
+ end
+end
+
+-- AnyPending
+local function AnyPending()
+ local checkUpdate
+ for guid,info in pairs(lib.roster) do
+ local namerealm = RosterInfoFullName(info)
+ if (UnitIsConnected(namerealm)) then
+ if (lib.wasOffline) then
+ lib.wasOffline[guid] = nil
+ end
+ if (not info.talents or info.refresh) then
+ return true
+ end
+ else
+ if (not lib.wasOffline) then
+ lib.wasOffline = new()
+ end
+ lib.wasOffline[guid] = true
+ end
+ end
+ if (lib.wasOffline and not next(lib.wasOffline)) then
+ lib.wasOffline = del(lib.wasOffline)
+ end
+end
+
+-- CheckForUpdateComplete
+local function CheckForUpdateComplete()
+ -- When all pending updates are complete, send an event to notify nothing else is due
+ if (next(lib.batch)) then
+ if (not AnyPending()) then
+ lib.events:Fire("LibGroupTalents_UpdateComplete", unpack(lib.batch))
+ wipe(lib.batch)
+ end
+ end
+end
+
+-- UNIT_LEVEL
+function lib:UNIT_LEVEL(unit)
+ if (UnitInParty(unit) or UnitInRaid(unit)) then
+ local guid = UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (r) then
+ r.level = UnitLevel(unit)
+ self:RefreshTalentsByUnit(unit)
+ end
+ end
+end
+
+-- UNIT_AURA
+function lib:UNIT_AURA(unit)
+ local guid = UnitGUID(unit)
+ if (not UnitIsVisible(unit) or (self.wasOffline and self.wasOffline[guid])) then
+ if (not self.outOfSight) then
+ self.outOfSight = {}
+ end
+ self.outOfSight[guid] = true
+ self:RefreshTalentsByGUID(guid)
+ end
+end
+
+-- OnRaidRosterUpdate
+function lib:OnRaidRosterUpdate()
+ local instanceType = select(2, IsInInstance())
+ if (instanceType == "pvp" or instanceType == "arena") then
+ self.distribution = "BATTLEGROUND"
+ else
+ if (GetNumRaidMembers() > 0) then
+ self.distribution = "RAID"
+ elseif (GetNumPartyMembers() > 0) then
+ self.distribution = "PARTY"
+ else
+ self.distribution = nil
+ end
+ end
+ if (self.distribution) then
+ if (self.sentHello ~= self.distribution) then
+ self.sentHello = self.distribution
+ self:SendCommMessage("HELLO "..MINOR, nil, self.distribution)
+ end
+ else
+ self.sentHello = nil
+ self.talentThrottle = del(self.talentThrottle)
+ self.wasOffline = del(self.wasOffline)
+ self.outOfSight = del(self.outOfSight)
+ wipe(self.pendingStorageStrings)
+ end
+
+ -- Now check for roster changes
+ local subtractions = new()
+ local additions = new()
+ local changes = new()
+
+ if (self.roster) then
+ for guid,info in pairs(self.roster) do
+ subtractions[guid] = info.level or 0
+ end
+ end
+
+ for unit in self:IterateRoster() do
+ local guid = UnitGUID(unit)
+ if (guid) then
+ local n = self.roster[guid]
+ if (not n) then
+ n = new()
+ self.roster[guid] = n
+ end
+
+ n.name, n.realm = UnitName(unit)
+ if (n.realm == "") then
+ n.realm = nil -- Fix this already..
+ end
+ n.level = UnitLevel(unit)
+ n.class = select(2, UnitClass(unit))
+ n.unit = unit
+
+ if (subtractions[guid]) then
+ if (subtractions[guid] ~= n.level) then
+ changes[guid] = unit -- Level changed, needs a rescan
+ end
+
+ subtractions[guid] = nil
+ else
+ if (n.name ~= UNKNOWN) then
+ self.events:Fire("LibGroupTalents_Add", guid, unit, n.name, n.realm)
+ end
+ additions[guid] = unit
+ end
+ end
+ end
+
+ if (next(additions)) then
+ for guid,unit in pairs(additions) do
+ self:GetUnitTalents(unit)
+ end
+ end
+
+ if (next(changes)) then
+ for guid,unit in pairs(changes) do
+ self:GetUnitTalents(unit)
+ end
+ end
+
+ if (next(subtractions)) then
+ for guid in pairs(subtractions) do
+ local r = self.roster[guid]
+ if (r) then
+ self.events:Fire("LibGroupTalents_Remove", guid, r.name, r.realm)
+ self.roster[guid] = deepDel(r)
+
+ local classStorageStrings = self.pendingStorageStrings[r.class]
+ if (classStorageStrings) then
+ classStorageStrings[guid] = del(classStorageStrings[guid])
+ if (not next(classStorageStrings)) then
+ self.pendingStorageStrings[r.class] = del(self.pendingStorageStrings[r.class])
+ end
+ end
+ end
+ end
+
+ CheckForUpdateComplete()
+ end
+
+ del(additions)
+ del(subtractions)
+ del(changes)
+
+ self:CheckForMissingTalents()
+end
+
+-- ValidateUnit
+local function ValidateUnit(r, guid)
+ local unit = r.unit
+ if (UnitGUID(unit) ~= guid) then
+ local name = r.name .. (r.realm and "-" or "") .. (r.realm or "")
+ local index = UnitInRaid(name)
+ if (index) then
+ r.unit = "raid"..index
+ return true
+ else
+ if (UnitGUID("player") == guid) then
+ r.unit = "player"
+ return true
+
+ elseif (UnitInParty(name)) then
+ for i = 1,4 do
+ if (UnitGUID("party"..i) == guid) then
+ r.unit = "party"..i
+ return true
+ end
+ end
+ end
+ end
+ return
+ end
+
+ return true
+end
+
+-- CountTree
+local function CountTree(branch)
+ local count = 0
+ for i = 1,#branch do
+ count = count + branch:byte(i) - 48
+ end
+ return count
+end
+
+-- TalentWeight
+local function TalentWeight(talents, class)
+ if (talents and #talents == 3 and class) then
+ local c1, c2, c3 = CountTree(talents[1]), CountTree(talents[2]), CountTree(talents[3])
+
+ local weight = 1
+ if (c2 > c1 and c2 > c3) then
+ weight = 2
+ elseif (c3 > c1 and c3 > c2) then
+ weight = 3
+ end
+
+ local data = lib.classTalentData[class]
+ if (data and data[weight]) then
+ return data[weight].name, c1, c2, c3
+ end
+
+ return weight, c1, c2, c3
+ end
+ return nil, 0, 0, 0
+end
+
+do
+-- First segment: Player ID (from GUID), Name, level, class, activePage, TalentString
+-- Subsequent: spec number, talentString()
+
+ -- crc
+ local function crc32(str)
+ local val = tonumber((select(2, GetBuildInfo()))) -- Use WoW build as CRC base
+ for i = 1,#str do
+ val = bit.band(val * 2 + str:byte(i), 0xFFFF)
+ end
+ return val
+ end
+
+ -- GetUnitStorageString
+ function lib:GetUnitStorageString(unit)
+ return self:GetGUIDStorageString(UnitGUID(unit))
+ end
+
+ -- GetGUIDStorageString
+ -- Make a storage string for mods to store talents.
+ -- Rules: 1) Your own realm only 2) Their talents are complete (nothing unspent)
+ function lib:GetGUIDStorageString(guid)
+ local r = self.roster[guid]
+ if (r) then
+ local id
+ local playerGUID = UnitGUID("player")
+ if (playerGUID:sub(1, 6) == guid:sub(1, 6)) then
+ -- Same realm code, so just trim it off. This is likely always true from what I've seen
+ id = format("%X", tonumber(guid:sub(7), 16))
+ else
+ id = guid:sub(4)
+ end
+
+ if (r.talents and r.active and not r.realm and (not r.unspent or not r.unspent[r.active])) then
+ if (r.level < 1) then
+ r.level = UnitLevel(r.name) or 0
+ end
+ local str = format("%s,%d,%s,%d,%d", id, r.level, r.class, r.active, r.numActive)
+ for i = 1,r.numActive do
+ local t = r.talents[i]
+ if (t) then
+ str = format("%s;%d,%s", str, i, table.concat(t, "-"))
+ end
+ end
+ return format("%s;%d", str, crc32(str))
+ end
+ end
+ end
+
+ -- SetStorageString
+ function lib:SetStorageString(str, comms)
+ local ret, retInfo
+ if (str) then
+ local parts = new(strsplit(";", str))
+ if (#parts >= 2) then
+ local strCRC = tonumber(parts[#parts])
+ local temp = table.concat(parts, ";", 1, #parts - 1)
+ if (crc32(temp) == strCRC) then
+ local part1 = new(strsplit(",", parts[1]))
+
+ while true do
+ local guid
+ local id = part1[1]
+ if (id:len() < 12) then
+ -- Trimmed GUID, we'll prefix it with our own GUID's realm code
+ guid = format("%s%012X", UnitGUID("player"):sub(1, 6), tonumber(id, 16))
+ else
+ guid = format("0x0%015s", id)
+ end
+
+ local r = self.roster[guid]
+ if (not r) then
+ retInfo = format("Unexpected SetStorageString for ID %s", guid)
+ ret = true -- Still return true, we just didn't want this string yet
+ break
+ elseif (r.name == UNKNOWN) then
+ retInfo = format("Premature SetStorageString for ID %s", guid)
+ ret = true -- Still return true, we just didn't want this string yet
+ break
+ end
+ if (r.talents) then
+ -- We've already received talents for this player
+ ret = true -- Still return true, we just didn't want this string because we have their talents
+ break
+ end
+
+ if (not self.classTalentData[r.class]) then
+ -- Received a storage string for a class that we've not yet been able to scan
+ -- the talent trees for. We store this until we have that data
+ local classStorageStrings = self.pendingStorageStrings[r.class]
+ if (not classStorageStrings) then
+ classStorageStrings = new()
+ self.pendingStorageStrings[r.class] = classStorageStrings
+ end
+ classStorageStrings[guid] = str
+ ret = true
+ break
+ end
+
+ local level = tonumber(part1[2])
+ local class = part1[3]
+ local active = tonumber(part1[4])
+ local numActive = tonumber(part1[5])
+
+ if (r.level < 1) then
+ r.level = UnitLevel(r.name) or 0
+ end
+ if (level ~= r.level and r.level > 1) then
+ -- Won't accept talents for mismatched levels (but ignore errors reading the UnitLevel early)
+ retInfo = "Wrong level"
+ break
+ end
+ if (not r.class and class) then
+ -- If we don't have the class, but the storage string does, we'll take it
+ r.class = class
+ end
+ if (class ~= r.class) then
+ -- Class doesn't match, probably a char delete/remake or xrealm
+ retInfo = format("Wrong class: expected %q, got %q", tostring(r.class), tostring(class))
+ break
+ end
+
+ -- Now the talent trees
+ local talents = new()
+ for i = 2,#parts - 1 do
+ local partN = new(strsplit(",", parts[i]))
+ if (#partN == 2) then
+ local specNumber = tonumber(partN[1])
+ local specTalents = new(strsplit("-", partN[2]))
+
+ if (specNumber and #specTalents >= 3) then
+ talents[specNumber] = specTalents
+ else
+ del(specTalents)
+ talents = del(talents)
+ retInfo = "Invalid talent specs in tree "..i
+ break
+ end
+ end
+ end
+
+ if (talents) then
+ r.talents = talents
+ r.active = active
+ r.numActive = numActive
+ if (comms ~= r.name) then
+ -- If comms part sends player name along with packet, then we'll skip the refresh later
+ -- which we'd normally do when Storage is set via app startup
+ r.refresh = true
+ else
+ r.refresh = nil
+ end
+
+ ValidateUnit(r, guid)
+ local newSpec, n1, n2, n3 = TalentWeight(r.talents[r.active], r.class)
+ self.events:Fire("LibGroupTalents_Update", guid, r.unit, newSpec, n1, n2, n3)
+ self:GetGUIDRole(guid, true)
+ ret = true
+ end
+ break
+ end
+
+ del(part1)
+ else
+ retInfo = "Invalid string"
+ end
+ end
+ del(parts)
+ end
+
+ return ret, retInfo
+ end
+end
+
+-- GetClassTalentData
+-- Builds an internal table for talent name -> tree/index lookups.
+function GetClassTalentData(unit)
+ local _, class = UnitClass(unit)
+ if (class) then
+ local data = lib.classTalentData[class]
+ if (not data) then
+ local isnotplayer = not UnitIsUnit("player", unit)
+ if (GetNumTalentTabs(isnotplayer) > 0) then
+ data = new()
+
+ for tab = 1, GetNumTalentTabs(isnotplayer) do
+ local tree = new()
+ local _
+ tree.name, tree.icon, _, tree.background = GetTalentTabInfo(tab, isnotplayer)
+ tinsert(data, tree)
+
+ tree.list = new()
+ for i = 1,GetNumTalents(tab, isnotplayer) do
+ local name, icon, tier, column, currentRank, maxRank = GetTalentInfo(tab, i, isnotplayer)
+ if (name) then
+ local entry = new()
+ entry.name = name
+ entry.icon = icon
+ entry.tier = tier
+ entry.column = column
+ entry.maxRank = maxRank
+ entry.index = i
+ entry.treeIndex = tab
+ tinsert(tree.list, entry)
+ if (not data.list) then
+ data.list = new()
+ end
+ data.list[name] = entry
+ end
+ end
+ end
+
+ if (next(data)) then
+ lib.classTalentData[class] = data
+
+ --for guid,r in pairs(lib.roster) do
+ -- if (r.class == class and r.talents) then
+ -- -- We picked up class talent data for a class after receiving talents for them via comms
+ -- -- So, we fire an Update event for any members of the class we already have so that
+ -- -- talents can now be interpreted correctly.
+ -- local spec, n1, n2, n3 = TalentWeight(r.talents[r.active], class)
+ -- lib.events:Fire("LibGroupTalents_Update", guid, unit, spec, n1, n2, n3)
+ -- end
+ --end
+
+ local classStorageStrings = lib.pendingStorageStrings[class]
+ if (classStorageStrings) then
+ local unitGUID = UnitGUID(unit)
+ for guid, str in pairs(classStorageStrings) do
+ if (guid ~= unitGUID) then
+ lib:SetStorageString(str)
+ end
+ end
+ lib.pendingStorageStrings[class] = del(lib.pendingStorageStrings[class])
+ end
+ else
+ deepDel(data)
+ end
+ end
+ end
+ end
+end
+
+-- GetTreeNames
+function lib:GetTreeNames(class)
+ local info = self.classTalentData[class]
+ if (info) then
+ return info[1].name, info[2].name, info[3].name
+ end
+end
+
+-- GetTreeIcons
+function lib:GetTreeIcons(class)
+ local info = self.classTalentData[class]
+ if (info) then
+ return info[1].icon, info[2].icon, info[3].icon
+ end
+end
+
+-- ReadTalentGroup
+local function ReadTalentGroup(isnotplayer, group, class)
+ local numTabs = GetNumTalentTabs(isnotplayer)
+ if (numTabs and numTabs >= 3 and GetNumTalents(1, isnotplayer) > 0) then
+ local ctd = lib.classTalentData[class]
+--[===[@debug@
+assert(ctd and ctd[1] and ctd[2] and ctd[3])
+assert(ctd[1].list and ctd[2].list and ctd[3].list)
+--@end-debug@]===]
+
+ local n = new()
+ for tab = 1, numTabs do
+ local branchLength = GetNumTalents(tab, isnotplayer, nil, group)
+ if (branchLength ~= #ctd[tab].list) then
+ -- Tab tree size is not what we expected for this class
+ del(n)
+ return
+ end
+
+ local t = new()
+ local trim
+ for i = 1,branchLength do
+ local name, icon, tier, column, currentRank, maxRank = GetTalentInfo(tab, i, isnotplayer, nil, group)
+ tinsert(t, currentRank)
+ if (currentRank > 0) then
+ trim = i -- We strip off trailing zeros from talent strings to save storage space
+ end
+ end
+
+ tinsert(n, table.concat(t, nil, 1, trim or 0))
+ del(t)
+ end
+
+ return n
+ end
+end
+
+-- TalentQuery_Ready
+function lib:TalentQuery_Ready_Outsider(e, name, realm, unit)
+ self:TalentQuery_Ready(e, name, realm, unit)
+end
+
+-- TalentQuery_Ready
+function lib:TalentQuery_Ready(e, name, realm, unit)
+ GetClassTalentData(unit)
+
+ local guid = unit and UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (r) then
+ local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
+ local isnotplayer = not UnitIsUnit(unit, "player")
+
+ if (GetTalentTabInfo(1, isnotplayer)) then
+ local active = GetActiveTalentGroup(isnotplayer)
+ local numActive = GetNumTalentGroups(isnotplayer)
+ local listUnspent, invalid
+ local talents = new()
+
+ for group = 1,numActive do
+ local n = ReadTalentGroup(isnotplayer, group, r.class)
+ if (n and #n >= 3) then
+ talents[group] = n
+ else
+ invalid = true
+ break
+ end
+
+ local unspent = GetUnspentTalentPoints(isnotplayer, nil, group)
+ if (unspent and unspent > 0) then
+ if (not listUnspent) then
+ listUnspent = new()
+ end
+ listUnspent[group] = unspent
+ end
+ end
+
+ if (isnotplayer and (invalid or (listUnspent and listUnspent[active] or 0) > 0)) then
+ -- Unit didn't have all their points spent in active group, so we'll have another look in 10 seconds
+ -- Don't need to check when it's "player" because we get PLAYER_TALENT_UPDATE event on changes
+ self:TriggerRefreshTalents(guid, 10)
+ end
+
+ if (not invalid) then
+ if (active > numActive) then
+ -- May be better to discard instead? We'll see
+ active = 1
+ end
+ self:OnReceiveTalents(guid, unit, talents, active, numActive, listUnspent)
+ end
+ end
+ end
+end
+TalentQuery.RegisterCallback(lib, "TalentQuery_Ready")
+TalentQuery.RegisterCallback(lib, "TalentQuery_Ready_Outsider")
+
+-- GetUnitTalentSpec
+function lib:GetUnitTalentSpec(unit, group)
+ return self:GetGUIDTalentSpec(UnitGUID(unit), group)
+end
+
+-- GetGUIDTalentSpec
+function lib:GetGUIDTalentSpec(guid, group)
+ local talents, r = GetGUIDTalentsRaw(guid, group)
+ if (talents) then
+ return TalentWeight(talents, r.class)
+ end
+end
+
+-- CompareTalents
+local function CompareTalents(tree1, tree2)
+ if ((tree1 ~= nil) ~= (tree2 ~= nil)) then
+ return
+ end
+ if (tree1 and tree2 and #tree1 == #tree2) then
+ for i = 1,#tree1 do
+ if (tree1[i] ~= tree2[i]) then
+ return
+ end
+ end
+ return true
+ end
+end
+
+-- OnReceiveTalents
+function lib:OnReceiveTalents(guid, unit, talents, active, numActive, listUnspent)
+ local r = self.roster[guid]
+ if (r) then
+ if (active ~= r.active or numActive ~= r.numActive or not CompareTalents(talents and talents[active], r.talents and r.talents[r.active])) then
+ local oldTalents
+ if (r.talents) then
+ oldTalents = r.talents[r.active]
+ end
+ del(r.unspent)
+
+ r.talents = talents
+ r.active = active
+ r.numActive = numActive
+ r.unspent = listUnspent
+
+ local newSpec, n1, n2, n3 = TalentWeight(r.talents[active], r.class)
+
+ local fired
+ if (oldTalents) then
+ -- For those cases when we didn't have the alternate talents for a player for whatever reason.
+ -- Maybe they just picked up dual talent spec, or gated to trainer to respec.
+ local oldSpec, o1, o2, o3 = TalentWeight(oldTalents, r.class)
+
+ if (o1 ~= n1 or o2 ~= n2 or o3 ~= n3) then
+ self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3, oldSpec, o1, o2, o3)
+ fired = true
+ end
+ end
+
+ if (not fired) then
+ self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3)
+ end
+ self:GetGUIDRole(guid, true)
+
+ tinsert(self.batch, guid)
+ CheckForUpdateComplete()
+
+ oldTalents = del(oldTalents)
+ return
+ end
+ end
+ del(talents)
+end
+
+-- OnReceiveGlyphs
+function lib:OnReceiveGlyphs(guid, sender, glyphs)
+ local r = self.roster[guid]
+ if (r) then
+ if (ValidateUnit(r, guid)) then
+ local oldGlyphs
+ if (r.glyphs) then
+ oldGlyphs = r.glyphs[r.active]
+ r.glyphs = del(r.glyphs)
+ end
+
+ r.glyphs = glyphs
+
+ local newGlyphs = r.glyphs and r.glyphs[r.active]
+ if (newGlyphs ~= oldGlyphs) then
+ self.events:Fire("LibGroupTalents_GlyphUpdate", guid, r.unit)
+ end
+ return
+ end
+ end
+
+ del(glyphs)
+end
+
+-- GetUnitGlyphs
+function lib:GetUnitGlyphs(unit, group)
+ return self:GetGUIDGlyphs(UnitGUID(unit), group)
+end
+
+-- GetGUIDGlyphs
+function lib:GetGUIDGlyphs(guid, group)
+ local r = self.roster[guid]
+ if (r) then
+ local g = r.glyphs and r.glyphs[group or r.active]
+ if (g) then
+ local temp = new(strsplit(",", g))
+ for i,str in ipairs(temp) do
+ temp[i] = tonumber(str)
+ end
+ local a, b, c, d, e, f = unpack(temp)
+ del(temp)
+ return a, b, c, d, e, f
+ end
+ end
+end
+
+-- UnitHasGlyph
+function lib:UnitHasGlyph(unit, glyphID, group)
+ return lib:GUIDHasGlyph(UnitGUID(unit), glyphID, group)
+end
+
+-- GUIDHasGlyph
+function lib:GUIDHasGlyph(guid, glyphID, group)
+ local ret
+ local r = self.roster[guid]
+ if (r) then
+ local g = r.glyphs and r.glyphs[group or r.active]
+ if (g) then
+ local temp = new(strsplit(",", g))
+ for i,str in ipairs(temp) do
+ local id = tonumber(str)
+ if (type(glyphID) == "number") then
+ if (glyphID == id) then
+ ret = true
+ break
+ end
+ else
+ if (glyphID == GetSpellInfo(id)) then
+ ret = true
+ break
+ end
+ end
+ end
+ del(temp)
+ end
+ end
+ return ret
+end
+
+-- GLYPH_ADDED
+function lib:GLYPH_ADDED(index, a, b, c)
+ self:RefreshPlayerGlyphs()
+end
+
+-- GLYPH_REMOVED
+function lib:GLYPH_REMOVED(index, a, b, c)
+ self:RefreshPlayerGlyphs()
+end
+
+-- GLYPH_UPDATED
+function lib:GLYPH_UPDATED(index, a, b, c)
+ self:RefreshPlayerGlyphs()
+end
+
+-- RefreshPlayerGlyphs
+function lib:RefreshPlayerGlyphs()
+ local guid = UnitGUID("player")
+ local r = self.roster[guid]
+ if (not r) then
+ return
+ end
+
+ local glyphs = new()
+ local any
+ for talentGroup = 1,GetNumTalentGroups() do
+ local list = new()
+ for i = 1,GetNumGlyphSockets() do
+ local enabled, glyphType, glyphSpell, icon = GetGlyphSocketInfo(i, talentGroup)
+ if (enabled and glyphType and glyphSpell) then
+ tinsert(list, glyphSpell)
+ any = true
+ end
+ end
+ glyphs[talentGroup] = table.concat(list, ",")
+ del(list)
+ end
+
+ local oldGlyphs = r.glyphs
+ if (any) then
+ r.glyphs = glyphs
+ else
+ del(glyphs)
+ end
+
+ local change = (oldGlyphs and oldGlyphs[r.active]) ~= (r.glyphs and r.glyphs[r.active])
+ if (change) then
+ self:SendMyGlyphs()
+ self.events:Fire("LibGroupTalents_GlyphUpdate", guid, "player")
+ end
+
+ del(oldGlyphs)
+end
+
+-- PLAYER_TALENT_UPDATE
+function lib:PLAYER_TALENT_UPDATE()
+ self:TriggerRefreshTalents(UnitGUID("player"), 2)
+end
+
+-- UNIT_SPELLCAST_SUCCEEDED
+function lib:UNIT_SPELLCAST_SUCCEEDED(unit, spell)
+ local newActiveGroup = specChangers[spell]
+ if (newActiveGroup) then
+ local guid = UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (r) then
+ if (newActiveGroup == r.active) then
+ -- We obviously didn't see them switch from this set
+ self:GetGUIDRole(guid, true)
+ return
+ end
+
+ if (r.talents) then
+ local oldSet = r.talents[r.active]
+ local newSet = r.talents[newActiveGroup]
+ if (oldSet and newSet) then
+ -- We have the other talent set, so no need to refresh anything. Just compare and notify
+ r.active = newActiveGroup
+
+ local oldSpec, o1, o2, o3 = TalentWeight(oldSet, r.class)
+ local newSpec, n1, n2, n3 = TalentWeight(newSet, r.class)
+
+ if (o1 ~= n1 or o2 ~= n2 or o3 ~= n3) then
+ self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3, oldSpec, o1, o2, o3)
+ else
+ self.events:Fire("LibGroupTalents_Update", guid, unit, newSpec, n1, n2, n3)
+ end
+ self:GetGUIDRole(guid, true)
+ return
+ end
+ end
+
+ -- If we get this far, then someone probably gated to respec
+ self:RefreshTalentsByGUID(guid)
+ end
+ end
+end
+
+-- TriggerRefreshTalents
+function lib:TriggerRefreshTalents(guid, delay)
+ if (not self.talentTimers) then
+ self.talentTimers = new()
+ end
+ if (guid) then
+ self.talentTimers[guid] = GetTime() + delay
+ frame:Show()
+ end
+end
+
+-- RefreshTalentsByUnit
+function lib:RefreshTalentsByUnit(unit)
+ local guid = UnitGUID(unit)
+ if (guid) then
+ self:RefreshTalentsByGUID(guid)
+ end
+end
+
+-- RefreshTalentsByGUID
+function lib:RefreshTalentsByGUID(guid)
+ local r = self.roster[guid]
+ if (not r) then
+ return
+ end
+ if (not ValidateUnit(r, guid)) then
+ return
+ end
+
+ if (self.talentTimers) then
+ self.talentTimers[guid] = nil
+ end
+
+ if (not self.talentThrottle) then
+ self.talentThrottle = {}
+ end
+ for guidThrottle,when in pairs(self.talentThrottle) do
+ if (when < GetTime() - 5) then
+ self.talentThrottle[guidThrottle] = nil
+ elseif (guid == guidThrottle) then
+ return
+ end
+ end
+ self.talentThrottle[guid] = GetTime()
+
+ if (self.commQueried) then
+ self.commQueried[guid] = nil
+ if (not next(self.commQueried)) then
+ self.commQueried = del(self.commQueried)
+ end
+ end
+
+ r.refresh = true
+ self:CheckForMissingTalents()
+
+ if (UnitGUID("player") == guid) then
+ self:SendMyTalents()
+ end
+end
+
+-- CheckForMissingTalents
+function lib:CheckForMissingTalents()
+ local any
+ for guid,info in pairs(self.roster) do
+ local namerealm = RosterInfoFullName(info)
+ if (not info.talents or (not UnitIsVisible(namerealm) and UnitExists(namerealm)) or info.refresh) then
+ any = true
+ info.refresh = nil
+ self:GetUnitTalents(info.unit, true)
+ end
+ end
+
+ if (any) then
+ lib.refreshCheckTimer = 15
+ frame:Show()
+ end
+end
+
+do
+ local survivalOfTheFittest = GetSpellInfo(33853) -- Survival of the Fittest
+ local protectorOfThePack = GetSpellInfo(57873) -- Protector of the Pack
+ local dkBladeBarrier = GetSpellInfo(49182) -- Blade Barrier
+ local dkToughness = GetSpellInfo(49042) -- Toughness
+ local dkAnticipation = GetSpellInfo(55129) -- Anticipation
+
+ -- GetUnitRole
+ function lib:GetUnitRole(unit, reset)
+ local guid = UnitGUID(unit)
+ if (guid) then
+ return self:GetGUIDRole(guid, reset)
+ end
+ end
+
+ -- GetGUIDRole
+ function lib:GetGUIDRole(guid, reset)
+ local r = guid and self.roster[guid]
+ if (not r) then
+ return
+ end
+ if (r.role and not reset) then
+ return r.role
+ end
+ if (not ValidateUnit(r, guid)) then
+ return
+ end
+
+ local class = r.class
+ local role
+
+ local unit = r.unit
+ if (class == "ROGUE" or class == "HUNTER") then
+ role = "melee"
+ elseif (class == "MAGE" or class == "WARLOCK") then
+ role = "caster"
+ elseif (r.talents and r.talents[r.active]) then
+ if (class == "DEATHKNIGHT") then
+ local score = self:GUIDHasTalent(guid, dkBladeBarrier) and 1 or 0
+ score = score + (self:GUIDHasTalent(guid, dkToughness) and 1 or 0)
+ score = score + (self:GUIDHasTalent(guid, dkAnticipation) and 1 or 0)
+ role = score >= 2 and "tank" or "melee" -- Has 2 of the 3 tanking talents at least
+
+ else
+ local specName, t1, t2, t3 = TalentWeight(r.talents[r.active], class)
+
+ if (class == "PRIEST") then
+ role = ((t1 + t2) > t3) and "healer" or "caster"
+ elseif (class == "WARRIOR") then
+ role = ((t1 + t2) > t3) and "melee" or "tank"
+ else
+ local heavy = (t1 > t2 and t1 > t3 and 1) or (t2 > t1 and t2 > t3 and 2) or (t3 > t1 and t3 > t2 and 3) or 0
+ if (class == "PALADIN") then
+ role = heavy == 1 and "healer" or heavy == 2 and "tank" or heavy == 3 and "melee"
+
+ elseif (class == "DRUID") then
+ if (heavy == 2) then
+ if (self:GUIDHasTalent(guid, survivalOfTheFittest) and self:GUIDHasTalent(guid, protectorOfThePack)) then
+ role = "tank"
+ else
+ role = "melee"
+ end
+ else
+ role = heavy == 1 and "caster" or "healer"
+ end
+
+ elseif (class == "SHAMAN") then
+ role = heavy == 1 and "caster" or heavy == 2 and "melee" or heavy == 3 and "healer"
+ end
+ end
+ end
+ end
+
+ local oldrole = r.role
+ r.role = role
+
+ if (role and role ~= oldrole) then
+ self.events:Fire("LibGroupTalents_RoleChange", guid, unit, role, oldrole)
+ end
+ return role
+ end
+end
+
+-- GetUnitTalents
+function lib:GetUnitTalents(unit, refetch)
+ local guid = UnitGUID(unit)
+ if (not guid) then
+ return
+ end
+ return self:GetGUIDTalents(guid, refetch)
+end
+
+-- CanCommQuery
+local function CanCommQuery(guid)
+ if (not lib.commQueried or not lib.commQueried[guid]) then
+ if (not lib.commQueried) then
+ lib.commQueried = new()
+ end
+ lib.commQueried[guid] = true
+ return true
+ end
+end
+
+-- GetGUIDTalents
+function lib:GetGUIDTalents(guid, refetch)
+ local r = self.roster[guid]
+ if (not r) then
+ return
+ end
+ if (not ValidateUnit(r, guid)) then
+ return
+ end
+
+ local unit = r.unit
+ local name, realm = UnitName(unit)
+ local activeTalents = r.talents and r.talents[r.active]
+
+ if (activeTalents) then
+ -- If someone is out of sight, we won't catch their talent swap spell cast, so we'll invalidate them here and recheck talents
+ if ((not UnitIsVisible(unit) and UnitIsConnected(unit)) or (self.outOfSight and self.outOfSight[guid])) then
+ if (not r.version) then
+ refetch = true
+ end
+ end
+ end
+
+ if (not activeTalents or refetch) then
+ if (UnitIsUnit("player", unit)) then
+ self:RefreshPlayerGlyphs()
+ self:TalentQuery_Ready(nil, name, nil, unit)
+
+ elseif ((UnitInRaid(unit) or UnitInParty(unit)) and UnitIsConnected(unit)) then
+ TalentQuery:Query(unit)
+
+ local namerealm = UnitFullName(unit)
+ if (not r.talents and not r.requested) then
+ -- Don't need to query on a 'refetch' because they'll send changes anyway via comms
+ local skipGlyphs
+ if (not UnitIsVisible(unit) or not CanInspect(unit)) then
+ if (r.version) then
+ if (CanCommQuery(guid)) then
+ -- We request talents via comms for anyone that may be out of inspect range
+ self:SendCommMessage("REQUESTTALENTS", namerealm)
+ r.requested = true
+ skipGlyphs = true
+ end
+ end
+ end
+ end
+
+ if (not r.glyphs and not skipGlyphs) then
+ if (r.version and r.version >= 15) then
+ if (CanCommQuery(guid)) then
+ -- They're in range to inspect, but we'll still want to ask for their glyphs
+ self:SendCommMessage("REQUESTGLYPHS", namerealm)
+ end
+ end
+ end
+ end
+
+ if (self.outOfSight) then
+ self.outOfSight[guid] = nil
+ end
+ end
+
+ return activeTalents
+end
+
+-- SendCommMessage
+function lib:SendCommMessage(msg, target, channel)
+ if (msg) then
+ if (ChatThrottleLib) then
+ ChatThrottleLib:SendAddonMessage("NORMAL", MAJOR, msg, channel or "WHISPER", target)
+ else
+ SendAddonMessage(MAJOR, msg, channel or "WHISPER", target)
+ end
+ end
+end
+
+-- Throttle - Purposely local to here
+-- Abuse prevention. Yes, who would abuse addon comms? Noone would make a macro to crash a mod user would they. Right?
+-- Well, this one time, at band camp. Someone thought it was super funny to make a macro that DCd PallyPower users
+local throttle
+local function Throttle(sender, key)
+ if (not throttle) then
+ throttle = {}
+ end
+ local s = throttle[sender]
+ if (not s) then
+ s = {}
+ throttle[sender] = s
+ end
+
+ if ((s[key] or 0) < GetTime() - 4.5) then
+ -- Same message key only allowable once every 4.5 secs from 1 person (Respec cast time is 5 seconds)
+ s[key] = GetTime()
+ return true
+ end
+end
+
+-- CHAT_MSG_ADDON
+function lib:CHAT_MSG_ADDON(prefix, msg, channel, sender)
+ if (prefix == MAJOR) then
+ if (sender == UnitName("player")) then
+ return
+ elseif (not UnitInRaid(sender) and not UnitInParty(sender)) then
+ return
+ end
+
+ local guid = UnitGUID(sender)
+ if (not guid) then
+ return
+ end
+ local r = self.roster[guid]
+ if (not r) then
+ return
+ end
+
+ local cmd, str = msg:match("^(%a+) *(.*)$")
+ if (not cmd) then
+ return
+ end
+
+ if (cmd == "TALENTS") then
+ -- Talents come in form of:
+ local t = r.talents
+ r.talents = nil -- SetStorageString won't overwrite talents usually, but we want it to here, without providing a means to do it easily with an arg from a mod
+ if (not self:SetStorageString(str, sender)) then
+ r.talents = t
+ else
+ deepDel(t)
+ end
+
+ elseif (cmd == "GLYPHS") then
+ local invalid
+ local pages = new(strsplit(";", str))
+ local glyphs = new()
+ for page,info in ipairs(pages) do
+ local list = new(strsplit(",", info))
+ local tab = tonumber(tremove(list, 1))
+ if (tab) then
+ glyphs[tab] = table.concat(list, ",")
+ del(list)
+ else
+ invalid = true
+ del(glyphs)
+ del(list)
+ break
+ end
+ end
+ if (not invalid) then
+ self:OnReceiveGlyphs(guid, sender, glyphs)
+ end
+ del(pages)
+
+ elseif (cmd == "REQUESTTALENTS") then
+ if (Throttle(sender, "REQUESTTALENTS")) then
+ if ((r.version or 0) < 39) then
+ if (lib.sentToOld and lib.sentToOld[guid]) then
+ return
+ end
+ if (not lib.sentToOld) then
+ lib.sentToOld = new()
+ end
+ lib.sentToOld[guid] = time()
+ end
+
+ self:SendMyTalents(sender)
+ self:SendMyGlyphs(sender)
+ end
+
+ elseif (cmd == "REQUESTGLYPHS") then
+ if (Throttle(sender, "REQUESTGLYPHS")) then
+ self:SendMyGlyphs(sender)
+ end
+
+ elseif (cmd == "HELLO") then
+ r.version = tonumber(str)
+ if (channel ~= "WHISPER") then
+ if (lib.sentToOld) then
+ lib.sentToOld[guid] = nil
+ end
+ if (UnitIsConnected(sender) and Throttle(sender, "HELLO")) then
+ self:SendCommMessage("HELLO "..MINOR, sender)
+ self:SendMyGlyphs(sender)
+ end
+ end
+ end
+ end
+end
+
+-- SendMy
+local function SendMy(sender, str)
+ if (sender) then
+ if (UnitIsConnected(sender)) then
+ lib:SendCommMessage(str, sender)
+ end
+ else
+ for guid,info in pairs(lib.roster) do
+ if (info.version) then
+ local namerealm = RosterInfoFullName(info)
+ if (UnitIsConnected(namerealm)) then
+ lib:SendCommMessage(str, namerealm)
+ end
+ end
+ end
+ end
+end
+
+-- SendMyTalents
+function lib:SendMyTalents(sender)
+ if (sender or self:UserCount() > 0) then
+ local str = self:GetGUIDStorageString(UnitGUID("player"))
+ if (str) then
+ SendMy(sender, "TALENTS "..str)
+ end
+ end
+end
+
+-- SendMyGlyphs
+function lib:SendMyGlyphs(sender)
+ if (sender or self:UserCount() > 0) then
+ local r = self.roster[UnitGUID("player")]
+ if (r and r.glyphs) then
+ local str = "GLYPHS "
+ local i = 1
+ for tab,g in pairs(r.glyphs) do
+ local temp = format("%d,%s", tab, g)
+ str = str .. (i > 1 and ";" or "") .. temp
+ i = i + 1
+ end
+ SendMy(sender, str)
+ end
+ end
+end
+
+-- UserCount
+function lib:UserCount()
+ local count = 0
+ for guid,info in pairs(self.roster) do
+ if (info.version and not UnitIsUnit("player", RosterInfoFullName(info))) then
+ count = count + 1
+ end
+ end
+ return count
+end
+
+-- UnitHasTalent
+-- eg: lib:UnitHasTalent("player", GetSpellInfo(talentSpellID))
+-- Returns: nil, or number of points spent into talent
+-- If the talent group is not specified, then the active talent group is used
+function lib:UnitHasTalent(unit, talentName, group)
+ return unit and self:GUIDHasTalent(UnitGUID(unit), talentName, group)
+end
+
+-- GUIDHasTalent
+-- Returns: nil, or number of points spent into talent
+function lib:GUIDHasTalent(guid, talentName, group)
+ local talents, r = GetGUIDTalentsRaw(guid, group)
+ if (talents and r.class) then
+ local data = self.classTalentData[r.class]
+ if (data) then
+ local info = data.list and data.list[talentName]
+ if (info) then
+ local str = talents[info.treeIndex]
+ if (str) then
+ local amount = (str:byte(info.index) or 48) - 48
+ return (amount or 0) > 0 and amount or nil
+ end
+ end
+ end
+ end
+end
+
+-- GetClassTalentInfo
+function lib:GetClassTalentInfo(class, talentName)
+-- Returns: Max Rank, Icon, Tab, Tier, Column, Tree Index
+ local data = self.classTalentData[class]
+ if (data) then
+ local info = data.list and data.list[talentName]
+ if (info) then
+ return info.maxRank, info.icon, info.treeIndex, info.column, info.tier, info.index
+ end
+ end
+end
+
+-- GetActiveTalentGroup
+function lib:GetActiveTalentGroup(unit)
+ if (UnitIsUnit(unit, "player")) then
+ return GetActiveTalentGroup()
+ else
+ local guid = unit and UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ return r and r.active or nil
+ end
+end
+
+-- GetNumTalentGroups
+function lib:GetNumTalentGroups(unit)
+ if (UnitIsUnit(unit, "player")) then
+ return GetNumTalentGroups()
+ else
+ local guid = unit and UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ return r and r.numActive or nil
+ end
+end
+
+-- GetTalentTabInfo
+function lib:GetTalentTabInfo(unit, tab, group)
+ if (UnitIsUnit(unit, "player")) then
+ return GetTalentTabInfo(tab, nil, nil, group or GetActiveTalentGroup())
+ else
+ local guid = unit and UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (r and r.class) then
+ local ctd = self.classTalentData[r.class]
+ if (ctd and tab >= 1 and tab <= #ctd) then
+ local spec, c1, c2, c3 = self:GetGUIDTalentSpec(guid, group)
+ return ctd[tab].name, ctd[tab].icon, tab == 1 and c1 or tab == 2 and c2 or c3, ctd[tab].background, 0
+ end
+ end
+ end
+end
+
+-- GetNumTalents
+function lib:GetNumTalents(unit, tab)
+ if (UnitIsUnit(unit, "player")) then
+ return GetNumTalents(tab)
+ else
+ local _, class = UnitClass(unit)
+ if (class) then
+ local ctd = self.classTalentData[class]
+ if (ctd and tab >= 1 and tab <= #ctd) then
+ return #ctd[tab].list
+ end
+ end
+ end
+end
+
+-- GetTalentInfo
+function lib:GetTalentInfo(unit, tab, index, group)
+ if (UnitIsUnit(unit, "player")) then
+ return GetTalentInfo(tab, index, nil, nil, group or GetActiveTalentGroup())
+ else
+ local _, class = UnitClass(unit)
+ if (class) then
+ local ctd = self.classTalentData[class]
+ if (ctd and tab >= 1 and tab <= #ctd) then
+ local info = ctd[tab].list[index]
+ if (info) then
+ local spent = self:UnitHasTalent(unit, info.name, group)
+ return info.name, info.icon, info.tier, info.column, spent or 0, info.maxRank
+ end
+ end
+ end
+ end
+end
+
+-- GetNumTalentTabs
+function lib:GetNumTalentTabs()
+ return GetNumTalentTabs()
+end
+
+-- GetNumTalentTabs
+function lib:GetUnspentTalentPoints(unit, group)
+ if (UnitIsUnit(unit, "player")) then
+ return GetUnspentTalentPoints(nil, nil, group)
+ else
+ local guid = unit and UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (r) then
+ return r.unspent and r.unspent[group or r.active or 1]
+ end
+ end
+end
+
+-- GetTalentCount
+function lib:GetTalentCount()
+ local count, missing = 0, 0
+ for guid,info in pairs(self.roster) do
+ if (info.talents) then
+ count = count + 1
+ else
+ missing = missing + 1
+ end
+ end
+ return count, missing
+end
+
+-- GetTalentMissingNames
+function lib:GetTalentMissingNames()
+ local list = new()
+ for unit in self:IterateRoster() do
+ local guid = UnitGUID(unit)
+ local r = guid and self.roster[guid]
+ if (not r or not r.talents) then
+ tinsert(list, UnitFullName(unit))
+ end
+ end
+ local ret
+ if (next(list)) then
+ ret = table.concat(list, ",")
+ end
+ del(list)
+ return ret
+end
+
+-- PurgeAndRescanTalents
+function lib:PurgeAndRescanTalents()
+ if (self.roster) then
+ wipe(self.pendingStorageStrings)
+ for guid,info in pairs(self.roster) do
+ info.talents = del(info.talents)
+ info.active = nil
+ info.numActive = nil
+ info.requested = nil
+ end
+ end
+ self:CheckForMissingTalents()
+end
+
+-- Roster iterator
+do
+ local function iter(t)
+ local key = t.id
+ local ret
+ if (t.mode == "raid") then
+ if (key > t.r) then
+ del(t)
+ return nil
+ end
+ ret = "raid"..key
+ else
+ if (key > t.p) then
+ del(t)
+ return nil
+ end
+ ret = key == 0 and "player" or "party"..key
+ end
+ t.id = key + 1
+ return ret
+ end
+
+ -- IterateRoster
+ function lib:IterateRoster()
+ local t = new()
+ if (GetNumRaidMembers() > 0) then
+ t.mode = "raid"
+ t.id = 1
+ t.r = GetNumRaidMembers()
+ else
+ t.mode = "party"
+ t.id = 0
+ t.p = GetNumPartyMembers()
+ end
+ return iter, t
+ end
+end
\ No newline at end of file
diff --git a/WeakAuras/Libs/LibGroupTalents-1.0/LibTalentQuery-1.0.lua b/WeakAuras/Libs/LibGroupTalents-1.0/LibTalentQuery-1.0.lua
new file mode 100644
index 0000000..c82d4f3
--- /dev/null
+++ b/WeakAuras/Libs/LibGroupTalents-1.0/LibTalentQuery-1.0.lua
@@ -0,0 +1,358 @@
+--[[
+Name: LibTalentQuery-1.0
+Revision: $Rev: 84 $
+Author: Rich Martel (richmartel@gmail.com)
+Documentation: http://wowace.com/wiki/LibTalentQuery-1.0
+SVN: svn://svn.wowace.com/wow/libtalentquery-1-0/mainline/trunk
+Description: Library to help with querying unit talents
+Dependancies: LibStub, CallbackHandler-1.0
+License: LGPL v2.1
+
+Example Usage:
+ local TalentQuery = LibStub:GetLibrary("LibTalentQuery-1.0")
+ TalentQuery.RegisterCallback(self, "TalentQuery_Ready")
+
+ local raidTalents = {}
+ ...
+ TalentQuery:Query(unit)
+ ...
+ function MyAddon:TalentQuery_Ready(e, name, realm, unitid)
+ local isnotplayer = not UnitIsUnit(unitid, "player")
+ local spec = {}
+ for tab = 1, GetNumTalentTabs(isnotplayer) do
+ local treename, _, pointsspent = GetTalentTabInfo(tab, isnotplayer)
+ tinsert(spec, pointsspent)
+ end
+ raidTalents[UnitGUID(unitid)] = spec
+ end
+]]
+
+local MAJOR, MINOR = "LibTalentQuery-1.0", 90000 + tonumber(("$Rev: 84 $"):match("(%d+)"))
+
+local lib = LibStub:NewLibrary(MAJOR, MINOR)
+if not lib then return end
+
+local INSPECTDELAY = 1
+local INSPECTTIMEOUT = 5
+if not lib.events then
+ lib.events = LibStub("CallbackHandler-1.0"):New(lib)
+end
+
+local validateTrees
+local enteredWorld = IsLoggedIn()
+local frame = lib.frame
+if not frame then
+ frame = CreateFrame("Frame", MAJOR .. "_Frame")
+ lib.frame = frame
+end
+frame:UnregisterAllEvents()
+frame:RegisterEvent("INSPECT_TALENT_READY")
+frame:RegisterEvent("PLAYER_ENTERING_WORLD")
+frame:RegisterEvent("PLAYER_LEAVING_WORLD")
+frame:RegisterEvent("PLAYER_LOGIN")
+frame:SetScript("OnEvent", function(this, event, ...)
+ return lib[event](lib, ...)
+end)
+
+do
+ local lastUpdateTime = 0
+ frame:SetScript("OnUpdate", function(this, elapsed)
+ lastUpdateTime = lastUpdateTime + elapsed
+ if lastUpdateTime > INSPECTDELAY then
+ lib:CheckInspectQueue()
+ lastUpdateTime = 0
+ end
+ end)
+ frame:Hide()
+end
+
+local inspectQueue = lib.inspectQueue or {}
+lib.inspectQueue = inspectQueue
+local garbageQueue = lib.garbageQueue or {} -- Added a second queue to things. Inspects that initially fail are now
+lib.garbageQueue = garbageQueue -- thrown into second queue will will be processed once main queue is empty
+
+if next(inspectQueue) then
+ frame:Show()
+end
+
+local UnitIsPlayer = _G.UnitIsPlayer
+local UnitName = _G.UnitName
+local UnitExists = _G.UnitExists
+local UnitGUID = _G.UnitGUID
+local GetNumRaidMembers = _G.GetNumRaidMembers
+local GetNumPartyMembers = _G.GetNumPartyMembers
+local UnitIsVisible = _G.UnitIsVisible
+local UnitIsConnected = _G.UnitIsConnected
+local UnitCanAttack = _G.UnitCanAttack
+local CanInspect = _G.CanInspect
+
+local function UnitFullName(unit)
+ local name, realm = UnitName(unit)
+ local namerealm = realm and realm ~= "" and name .. "-" .. realm or name
+ return namerealm
+end
+
+-- GuidToUnitID
+local function GuidToUnitID(guid)
+ local prefix, min, max = "raid", 1, GetNumRaidMembers()
+ if max == 0 then
+ prefix, min, max = "party", 0, GetNumPartyMembers()
+ end
+
+ -- Prioritise getting direct units first because other players targets
+ -- can change between notify and event which can bugger things up
+ for i = min, max do
+ local unit = i == 0 and "player" or prefix .. i
+ if (UnitGUID(unit) == guid) then
+ return unit
+ end
+ end
+
+ -- This properly detects target units
+ if (UnitGUID("target") == guid) then
+ return "target"
+ elseif (UnitGUID("focus") == guid) then
+ return "focus"
+ elseif (UnitGUID("mouseover") == guid) then
+ return "mouseover"
+ end
+
+ for i = min, max + 3 do
+ local unit
+ if i == 0 then
+ unit = "player"
+ elseif i == max + 1 then
+ unit = "target"
+ elseif i == max + 2 then
+ unit = "focus"
+ elseif i == max + 3 then
+ unit = "mouseover"
+ else
+ unit = prefix .. i
+ end
+ if (UnitGUID(unit .. "target") == guid) then
+ return unit .. "target"
+ elseif (i <= max and UnitGUID(unit.."pettarget") == guid) then
+ return unit .. "pettarget"
+ end
+ end
+ return nil
+end
+
+-- Query
+function lib:Query(unit)
+ if (UnitLevel(unit) < 10 or UnitName(unit) == UNKNOWN) then
+ return
+ end
+
+ self.lastQueuedInspectReceived = nil
+ if UnitIsUnit(unit, "player") then
+ self.events:Fire("TalentQuery_Ready", UnitName("player"), nil, "player")
+ else
+ if type(unit) ~= "string" then
+ error(("Bad argument #2 to 'Query'. Expected %q, received %q (%s)"):format("string", type(unit), tostring(unit)), 2)
+ elseif not UnitExists(unit) or not UnitIsPlayer(unit) then
+ error(("Bad argument #2 to 'Query'. %q is not a valid player unit"):format(tostring(unit)), 2)
+ elseif not UnitExists(unit) or not UnitIsPlayer(unit) then
+ error(("Bad argument #2 to 'Query'. %q does not require a server query before reading talents"):format("player"), 2)
+ else
+ local name = UnitFullName(unit)
+ if (not inspectQueue[name]) then
+ inspectQueue[name] = UnitGUID(unit)
+ garbageQueue[name] = nil
+ end
+ frame:Show()
+ end
+ end
+end
+
+-- CheckInspectQueue
+-- Originally, it would wait until no pending NotifyInspect() were expected, and then do it's own.
+-- It was also only bother looking at ready results if it had triggered the Notify for that occasion.
+-- For the changes I've done, no assumption is made about which mod is performing NotifyInspect().
+-- We note the name, unit, time of any inspects done whether from this queue or any other source,
+-- we remove from our queue any we were expecting, and use a seperate event in case extra talent
+-- info is any time wanted (opportunistic refreshes etc) - Zeksie, 20th May 2009
+function lib:CheckInspectQueue()
+ if (_G.InspectFrame and _G.InspectFrame:IsShown()) then
+ return
+ end
+
+ if (not self.lastInspectTime or self.lastInspectTime < GetTime() - INSPECTTIMEOUT) then
+ self.lastInspectPending = 0
+ end
+
+ if (self.lastInspectPending > 0 or not enteredWorld) then
+ return
+ end
+
+ if (self.lastQueuedInspectReceived and self.lastQueuedInspectReceived < GetTime() - 60) then
+ -- No queued results received for a minute, so purge the queue as invalid and move on with our lives
+ self.lastQueuedInspectReceived = nil
+ inspectQueue = {}
+ lib.inspectQueue = inspectQueue
+ garbageQueue = {}
+ lib.garbageQueue = garbageQueue
+ frame:Hide()
+ return
+ end
+
+ for name,guid in pairs(inspectQueue) do
+ local unit = GuidToUnitID(guid)
+ if (not unit) then
+ inspectQueue[name] = nil
+ else
+ if (UnitIsVisible(unit) and UnitIsConnected(unit) and not UnitCanAttack("player", unit) and not UnitCanAttack(unit, "player") and CanInspect(unit) and UnitClass(unit)) then
+ NotifyInspect(unit)
+ break
+ else
+ garbageQueue[name] = guid -- Not available, throw into secondary queue and continue
+ inspectQueue[name] = nil
+ end
+ end
+ end
+
+ if (not next(inspectQueue)) then
+ if (next(garbageQueue)) then
+ -- Retry initially failed inspects
+ lib.inspectQueue = garbageQueue
+ inspectQueue = lib.inspectQueue
+ lib.garbageQueue = {}
+ garbageQueue = lib.garbageQueue
+ else
+ frame:Hide()
+ end
+ end
+end
+
+-- NotifyInspect
+if not lib.NotifyInspect then -- don't hook twice
+ hooksecurefunc("NotifyInspect", function(...) return lib:NotifyInspect(...) end)
+end
+function lib:NotifyInspect(unit)
+ if (not (UnitExists(unit) and UnitIsVisible(unit) and UnitIsConnected(unit) and CheckInteractDistance(unit, 4))) then
+ return
+ end
+ self.lastInspectUnit = unit
+ self.lastInspectGUID = UnitGUID(unit)
+ self.lastInspectTime = GetTime()
+ self.lastInspectName = UnitFullName(unit)
+ self.lastInspectPending = self.lastInspectPending + 1
+ local isnotplayer = not UnitIsUnit("player", unit)
+ self.lastInspectTree = GetTalentTabInfo(1, isnotplayer) -- Talent tree names are available immediately
+end
+
+-- Reset
+function lib:Reset()
+ self.lastInspectPending = 0
+ self.lastInspectUnit = nil
+ self.lastInspectTime = nil
+ self.lastInspectName = nil
+ self.lastInspectGUID = nil
+ self.lastInspectTree = nil
+end
+
+-- INSPECT_TALENT_READY
+function lib:INSPECT_TALENT_READY()
+ self.lastInspectPending = self.lastInspectPending - 1
+
+ -- Results are valid only when we have received as many events as we have posted notifies
+ if (self.lastInspectName and self.lastInspectPending == 0) then
+ -- Check unit ID is still pointing to same actual unit
+ if (UnitGUID(self.lastInspectUnit) == self.lastInspectGUID) then
+ local guid = inspectQueue[self.lastInspectName]
+ inspectQueue[self.lastInspectName] = nil
+
+ local name, realm = strsplit("-", self.lastInspectName)
+
+ self.lastQueuedInspectReceived = GetTime()
+
+ -- Notify of expected talent results
+ local isnotplayer = not UnitIsUnit("player", self.lastInspectName)
+ local group = GetActiveTalentGroup(isnotplayer)
+ local tree1, _, spent1 = GetTalentTabInfo(1, isnotplayer, nil, group)
+ if (tree1 ~= self.lastInspectTree) then
+ -- Expected talent tree name to be the same as it was when we triggered the NotifyInspect()
+ garbageQueue[self.lastInspectName] = self.lastInspectGUID
+ self:Reset()
+ self:CheckInspectQueue()
+ return
+
+ elseif (validateTrees) then
+ -- Double checking here. Check the tree name matches what we expect for this class
+ local _, class = UnitClass(self.lastInspectUnit)
+ if (tree1 ~= validateTrees[class]) then
+ garbageQueue[self.lastInspectName] = self.lastInspectGUID
+ self:Reset()
+ self:CheckInspectQueue()
+ return
+ end
+ end
+
+ local tree2, _, spent2 = GetTalentTabInfo(2, isnotplayer, nil, group)
+ local tree3, _, spent3 = GetTalentTabInfo(3, isnotplayer, nil, group)
+ if ((spent1 or 0) + (spent2 or 0) + (spent3 or 0) > 0) then
+ if (guid) then
+ -- It was in our queue
+ self.events:Fire("TalentQuery_Ready", name, realm, self.lastInspectUnit)
+ else
+ -- Also notify of non-expected ones, as it's entirely useful to refresh them if they're there
+ -- It is up to the receiving applicating to determine whether they want to receive the information
+ self.events:Fire("TalentQuery_Ready_Outsider", name, realm, self.lastInspectUnit)
+ end
+ else
+ -- Tree came back with zero points spent, probably an issue while logging in
+ garbageQueue[self.lastInspectName] = guid
+ end
+ end
+
+ self:Reset()
+ self:CheckInspectQueue()
+ end
+end
+
+function lib:PLAYER_ENTERING_WORLD()
+ -- We can't inspect other's talents until now
+ -- We just get 0/0/0 back even though we get an INSPECT_TALENT_READY event
+ enteredWorld = true
+end
+
+function lib:PLAYER_LEAVING_WORLD()
+ enteredWorld = nil
+end
+
+function lib:PLAYER_LOGIN()
+ validateTrees = {
+ DRUID = "Balance",
+ PRIEST = "Discipline",
+ ROGUE = "Assassination",
+ HUNTER = "Beast Mastery",
+ WARLOCK = "Affliction",
+ WARRIOR = "Arms",
+ DEATHKNIGHT = "Blood",
+ PALADIN = "Holy",
+ SHAMAN = "Elemental",
+ MAGE = "Arcane",
+ }
+
+ if (GetLocale() ~= "enUS" and GetLocale() ~= "enGB") then
+ -- LibBabble-TalentTree-3.0 only loaded if present and not enUS
+ local LBT = LibStub("LibBabble-TalentTree-3.0", true)
+ if (not LBT) then
+ LoadAddOn("LibBabble-TalentTree-3.0")
+ LBT = LibStub("LibBabble-TalentTree-3.0", true)
+ end
+ LBT = LBT and LBT:GetLookupTable()
+ if (LBT) then
+ for class,tree1 in pairs(validateTrees) do
+ validateTrees[class] = LBT[tree1]
+ end
+ else
+ validateTrees = nil
+ end
+ end
+
+ self.PLAYER_LOGIN = nil
+end
+
+lib:Reset()
diff --git a/WeakAuras/Libs/LibGroupTalents-1.0/lib.xml b/WeakAuras/Libs/LibGroupTalents-1.0/lib.xml
new file mode 100644
index 0000000..c930898
--- /dev/null
+++ b/WeakAuras/Libs/LibGroupTalents-1.0/lib.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/WeakAuras/WeakAuras.lua b/WeakAuras/WeakAuras.lua
index 0f97e81..2a84715 100644
--- a/WeakAuras/WeakAuras.lua
+++ b/WeakAuras/WeakAuras.lua
@@ -2438,14 +2438,27 @@ local function validateUserConfig(data, options, config)
end
end
-local function removeNameplateUnits(data)
- for _, triggerData in ipairs(data.triggers) do
- local trigger = triggerData.trigger
- if trigger and trigger.type == "unit" then
- if trigger.unit == "nameplate" then
- trigger.unit = "target"
- end
- end
+local function removeNameplateUnitsAndAnchors(data)
+ -- Dynamic Group Anchor
+ if data.useAnchorPerUnit == true and data.anchorPerUnit == "NAMEPLATE" then
+ data.useAnchorPerUnit = false
+ data.anchorPerUnit = "CUSTOM"
+ end
+ -- Aura Anchor
+ if data.anchorFrameType == "NAMEPLATE" then
+ data.anchorFrameType = "SCREEN"
+ end
+ -- Action Glow Anchor
+ if data.actions and data.actions.start and data.actions.start.glow_frame_type == "NAMEPLATE" then
+ data.actions.start.glow_frame_type = "FRAMESELECTOR"
+ end
+ -- Trigger units
+ for _, triggerData in ipairs(data.triggers) do
+ local trigger = triggerData.trigger
+ if trigger and trigger.type == "unit" then
+ if trigger.unit == "nameplate" then
+ trigger.unit = "target"
+ end
end
end
end
diff --git a/WeakAuras/WeakAuras.toc b/WeakAuras/WeakAuras.toc
index 3ecc97f..8a0a8f1 100644
--- a/WeakAuras/WeakAuras.toc
+++ b/WeakAuras/WeakAuras.toc
@@ -42,6 +42,7 @@ Conditions.lua
AnchorToWeakAuras.lua
# Trigger systems
+LibGroupTalentsWrapper.lua
BuffTrigger2.lua
GenericTrigger.lua
BossMods.lua
diff --git a/WeakAuras/embeds.xml b/WeakAuras/embeds.xml
index fa7a525..5a27d01 100644
--- a/WeakAuras/embeds.xml
+++ b/WeakAuras/embeds.xml
@@ -16,4 +16,6 @@
+
+