--[[ *** DataStore_Talents *** Written by : Thaoky, EU-Marécages de Zangar June 23rd, 2009 --]] if not DataStore then return end local addonName = "DataStore_Talents" _G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0") local addon = _G[addonName] local THIS_ACCOUNT = "Default" -- TODO: -- add support for hunter pets' talent trees local NUM_GLYPH_SLOTS = 6 local AddonDB_Defaults = { global = { Characters = { ['*'] = { -- ["Account.Realm.Name"] lastUpdate = nil, ActiveTalents = nil, -- 1 for primary, 2 for secondary Class = nil, -- englishClass PointsSpent = nil, -- "51,5,15 ... " 3 numbers for primary spec, 3 for secondary, comma separated TalentTrees = { ['*'] = { -- "Fire|2" = Mage Fire tree, secondary ['*'] = 0 } }, Glyphs = {}, } } } } -- This table saved reference data required to rebuild a talent tree for a class when logged in under another class. -- The API does not provide that ability, but saving and reusing is fine local ReferenceDB_Defaults = { global = { ['*'] = { -- "englishClass" like "MAGE", "DRUID" etc.. Order = nil, Version = nil, -- build number under which this class ref was saved Locale = nil, -- locale under which this class ref was saved Trees = { ['*'] = { -- tree name icon = nil, background = nil, talents = {}, -- name, icon, max rank etc..for talent x in this tree prereqs = {} -- prerequisites }, } }, } } local TALENT_ICON_PATH = "Interface\\Icons\\" local BACKGROUND_PATH = "Interface\\TalentFrame\\" -- *** Utility functions *** local function GetVersion() local _, version = GetBuildInfo() return tonumber(version) end -- *** Scanning functions *** local LocaleExceptions = {} --- see ScanTalentReference() for an explanation on the purpose of this table if GetLocale() == "enUS" then LocaleExceptions["Elemental Combat"] = "Elemental" LocaleExceptions["Shadow Magic"] = "Shadow" elseif GetLocale() == "frFR" then LocaleExceptions["Combat élémentaire"] = "Elémentaire" LocaleExceptions["Arcanes"] = "Arcane" LocaleExceptions["Magie de l'ombre"] = "Ombre" elseif GetLocale() == "deDE" then LocaleExceptions["Wiederherstellung"] = "Wiederherst." LocaleExceptions["Elementarkampf"] = "Elementar" LocaleExceptions["Verstärkung"] = "Verstärk." LocaleExceptions["Schattenmagie"] = "Schatten" elseif GetLocale() == "zhTW" then LocaleExceptions["生存技能"] = "生存" LocaleExceptions["暗影魔法"] = "暗影" LocaleExceptions["元素戰鬥"] = "元素" end local function ScanTalents() local level = UnitLevel("player") if not level or level < 10 then return end -- don't scan anything for low level characters local char = addon.ThisCharacter local _, englishClass = UnitClass("player") char.ActiveTalents = GetActiveTalentGroup() -- returns 1 or 2 char.Class = englishClass wipe(char.TalentTrees) local points = {} for specNum = 1, 2 do -- primary and secondary specs for tabNum = 1, GetNumTalentTabs() do -- all tabs local name, _, pointsSpent = GetTalentTabInfo( tabNum, nil, nil, specNum ); table.insert(points, pointsSpent) for talentNum = 1, GetNumTalents(tabNum) do -- all talents local _, _, _, _, currentRank = GetTalentInfo( tabNum, talentNum, nil, nil, specNum ) char.TalentTrees[name .."|" .. specNum][talentNum] = currentRank end end end char.PointsSpent = table.concat(points, ",") char.lastUpdate = time() end local function ScanTalentReference() local level = UnitLevel("player") if not level or level < 10 then return end -- don't scan anything for low level characters local _, class = UnitClass("player") -- we need the englishClass local ref = addon.ref.global[class] -- see if we already have data for this version if ref.Version then -- if we already have a version .. if ref.Version == GetVersion() then -- .. and it's the current build .. return -- ..then exit end end ref.Version = GetVersion() ref.Locale = GetLocale() local order = {} -- order of the talent tabs -- first talent tree, gather reference + user specific for tabNum = 1, GetNumTalentTabs() do local talentTabName, _, _, fileName = GetTalentTabInfo( tabNum, nil, nil, 1 ); order[tabNum] = talentTabName local ti = ref.Trees[talentTabName] -- ti for talent info ti.background = fileName for talentNum = 1, GetNumTalents(tabNum) do local nameTalent, iconPath, tier, column, _, maximumRank = GetTalentInfo(tabNum, talentNum, nil, nil, 1 ) -- all paths start with this prefix, let's hope blue does not change this :) -- saves a lot of memory not to keep the full path for each talent (about 16k in total for all classes) iconPath = string.gsub(iconPath, TALENT_ICON_PATH, "") local link = GetTalentLink(tabNum, talentNum) local id = tonumber(link:match("talent:(%d+)")) ti.talents[talentNum] = id .. "|" .. nameTalent .. "|" .. iconPath .. "|" .. tier .. "|" .. column .. "|" .. maximumRank prereqTier, prereqColumn = GetTalentPrereqs(tabNum, talentNum) -- talent prerequisites if prereqTier and prereqColumn then ti.prereqs[talentNum] = prereqTier .. "|" .. prereqColumn end end end -- save the order of talent tabs, this is necessary because the order of talent tabs is not the same as that of spell tabs in all languages/classes -- it is fine in enUS, but not in frFR (druid at least did not match) ref["Order"] = table.concat(order, ",") for i = 2, 4 do local name, icon = GetSpellTabInfo(i) -- skip spell tab 1, it's the general tab -- the icon may be nil on a low level char. -- Example : rogue lv 2 -- GetSpellTabInfo(1) returns the General tab -- GetSpellTabInfo(2) returns the Assassination tab -- GetSpellTabInfo(3) returns the Combat tab -- GetSpellTabInfo(4) returns nil, instead of Subtelty if name and icon then -- in addition to having different order between spell tabs & talent tabs, the names may not match. Most of the time they do, but in case they don't, use the excpetion table name = LocaleExceptions[name] or name local ti = ref.Trees[name] -- ti for talent info ti.icon = string.gsub(icon, TALENT_ICON_PATH, "") end end end local function ScanGlyphs() -- GLYPHTYPE_MAJOR = 1; -- GLYPHTYPE_MINOR = 2; -- 1 -- 3 5 -- 6 4 -- 2 local glyphs = addon.ThisCharacter.Glyphs wipe(glyphs) local enabled, glyphType, spell, icon, glyphID local link, index for specNum = 1, 2 do for i = 1, NUM_GLYPH_SLOTS do index = ((specNum - 1) * NUM_GLYPH_SLOTS) + i enabled, glyphType, spell, icon = GetGlyphSocketInfo(i, specNum) link = GetGlyphLink(i, specNum) if link then _, glyphID = link:match("glyph:(%d+):(%d+)") end glyphID = glyphID or 0 glyphType = glyphType or 0 enabled = enabled or 0 spell = spell or "" icon = icon or "" glyphs[index] = enabled .."|" .. glyphType .. "|" .. spell .. "|" .. icon .. "|" .. glyphID end end addon.ThisCharacter.lastUpdate = time() end -- ** Mixins ** local function _GetReferenceTable() return addon.ref.global end local function _GetClassReference(class) assert(type(class) == "string") return addon.ref.global[class] end local function _GetTreeReference(class, tree) assert(type(class) == "string") assert(type(tree) == "string") return addon.ref.global[class].Trees[tree] end local function _IsClassKnown(class) class = class or "" -- if by any chance nil is passed, trap it to make sure the function does not fail, but returns nil anyway local ref = _GetClassReference(class) if ref.Order then -- if the Order field is not nil, we have data for this class return true end end local function _ImportClassReference(class, data) assert(type(class) == "string") assert(type(data) == "table") addon.ref.global[class] = data end local function _GetClassTrees(class) assert(type(class) == "string") local ref = _GetClassReference(class) local order = ref.Order if order then return order:gmatch("([^,]+)") end -- to do, add a return value that does not require validity testing by the caller end local function _GetTreeInfo(class, tree) local t = _GetTreeReference(class, tree) if t then return TALENT_ICON_PATH..t.icon, BACKGROUND_PATH .. t.background end end local function _GetTreeNameByID(class, id) -- returns the name of tree "id" for a given class assert(type(class) == "string") local index = 1 for name in _GetClassTrees(class) do if index == id then return name end index = index + 1 end end local function _GetTalentLink(id, rank, name) return format("|cff4e96f7|Htalent:%s:%s|h[%s]|h|r", id, (rank-1), name) end local function _GetNumTalents(class, tree) -- returns the number of talents in a given tree local t = _GetTreeReference(class, tree) if t then return #t.talents end end local function _GetTalentInfo(class, tree, index) local t = _GetTreeReference(class, tree) local talentInfo = t.talents[index] if not talentInfo then return end local id, name, icon, tier, column, maximumRank = strsplit("|", talentInfo) return tonumber(id), name, TALENT_ICON_PATH..icon, tonumber(tier), tonumber(column), tonumber(maximumRank) end local function _GetTalentRank(character, tree, specNum, index) return character.TalentTrees[tree .. "|" .. specNum][index] end local function _GetActiveTalents(character) return character.ActiveTalents end local function _GetNumPointsSpent(character, tree, specNum) local index = 1 for treeName in _GetClassTrees(character.Class) do if treeName == tree then break end index = index + 1 end if index == 4 then return end -- = 4 means tree was not found index = index + ((specNum-1) * 3) return select(index, strsplit(",", character.PointsSpent)) or 0 end local function _GetTalentPrereqs(class, tree, index) local t = _GetTreeReference(class, tree) local prereq = t.prereqs[index] if prereq then local prereqTier, prereqColumn = strsplit("|", prereq) return tonumber(prereqTier), tonumber(prereqColumn) end end local function _GetGlyphInfo(character, specNum, index) index = ((specNum - 1) * NUM_GLYPH_SLOTS) + index -- 4th glyph = 4 for spec 1, 10 for spec 2 local glyph = character.Glyphs[index] if glyph then local enabled, glyphType, spell, icon, glyphID = strsplit("|", glyph) return tonumber(enabled), tonumber(glyphType), tonumber(spell), icon, tonumber(glyphID) end end local function _GetGlyphLink(id, spell, glyphID) local name = GetSpellInfo(spell) return format("|cff66bbff|Hglyph:2%s:%s|h[%s]|h|r", id, glyphID, name) end local PublicMethods = { GetReferenceTable = _GetReferenceTable, GetClassReference = _GetClassReference, GetTreeReference = _GetTreeReference, IsClassKnown = _IsClassKnown, ImportClassReference = _ImportClassReference, GetClassTrees = _GetClassTrees, GetTreeInfo = _GetTreeInfo, GetTreeNameByID = _GetTreeNameByID, GetTalentLink = _GetTalentLink, GetNumTalents = _GetNumTalents, GetTalentInfo = _GetTalentInfo, GetTalentRank = _GetTalentRank, GetActiveTalents = _GetActiveTalents, GetNumPointsSpent = _GetNumPointsSpent, GetTalentPrereqs = _GetTalentPrereqs, GetGlyphInfo = _GetGlyphInfo, GetGlyphLink = _GetGlyphLink, } function addon:OnInitialize() addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults) addon.ref = LibStub("AceDB-3.0"):New(addonName .. "RefDB", ReferenceDB_Defaults) DataStore:RegisterModule(addonName, addon, PublicMethods) DataStore:SetCharacterBasedMethod("GetTalentRank") DataStore:SetCharacterBasedMethod("GetActiveTalents") DataStore:SetCharacterBasedMethod("GetNumPointsSpent") DataStore:SetCharacterBasedMethod("GetGlyphInfo") end function addon:OnEnable() addon:RegisterEvent("PLAYER_ALIVE") addon:RegisterEvent("PLAYER_TALENT_UPDATE", ScanTalents) addon:RegisterEvent("GLYPH_ADDED", ScanGlyphs) addon:RegisterEvent("GLYPH_REMOVED", ScanGlyphs) addon:RegisterEvent("GLYPH_UPDATED", ScanGlyphs) end function addon:OnDisable() addon:UnregisterEvent("PLAYER_ALIVE") addon:UnregisterEvent("PLAYER_TALENT_UPDATE") addon:UnregisterEvent("GLYPH_ADDED") addon:UnregisterEvent("GLYPH_REMOVED") addon:UnregisterEvent("GLYPH_UPDATED") end -- *** EVENT HANDLERS *** function addon:PLAYER_ALIVE() -- print("DataStore_Talents.lua") -- DEBUG 2025 07 21 if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard ScanTalents() ScanTalentReference() ScanGlyphs() end