Dragonflight Talent System
- For talents in general, see Talent.
The main focus of this guide is the new player talent tree system.[1] Professions and "Generic" trees are sometimes mentioned, but professions in particular have some differences that this guide won't dive into. Currently, the only "Generic" tree, is the dragonriding talent tree. If the system turns out to be a success, we can expect more generic trees in the future.
Talent Build String
With the re-introduction of talent trees, Blizzard has implemented a way to share your talent choices. This is done through a Talent Build String. This string is a base64 encoded binary format, containing the build's spec, a checksum of the tree itself, and selected talent information.
The format for this string, has been described by blizzard in Blizzard_ClassTalentImportExport.lua. This file also contains the code that Blizzard uses to encode and decode talent strings.
Besides manually encoding a string, you can also use C_Traits.GenerateImportString and C_Traits.GenerateInspectImportString to generate a Talent Build String. See also Hyperlinks-talentbuild.
Overview
C_Traits contains all the generic APIs and interacts with C_ClassTalents for talent tree specific APIs, and C_ProfSpecs for profession specific APIs.
The hierarchy in the new talent system is:
[system] - config >< tree < node < nodeEntry - nodeDefinition - spell
In other words (with db2 links added when they line up, however, there are usually multiple tables involved for each entity):
- TraitSystem - specifies Generic trees, e.g. dragonriding talents. This is not part of the hierarchy for player talent trees or professions
- TraitConfig - when you apply your talent choices, you apply it to the config. Switching talent loadouts basically boils down to switching TraitConfig. A config can contain multiple TraitTrees (this is the case for professions, for talent trees there is only 1 TraitTree).
- TraitTree - a tree is basically a list of TraitNodes, and currency cost information. For talent trees, while visually there's a separate class and spec tree, in the API it's all 1 tree. There's 1 TraitTree per class. Professions have a TraitTree for each profession specialization tab.
- TraitNode - a node is roughly equivalent to a button in the tree UI. Nodes can have multiple entries, in particular, choice nodes will have 2, whereas single option nodes have 1.
- TraitNodeEntry - an entry holds information about a specific talent choice itself, including how many ranks it has.
- TraitDefinition - is directly related to a TraitNodeEntry, and will contain the spellID and/or other relevant information, of what a talent will actually do for a player.
Some other entities include
- TraitCurrency - nodes generally cost a specific TraitCurrency to purchase. These TraitCurrencies can be based on regular currencies (such as profession knowlegde, or dragonriding glyphs), or have own tracking and spending (as is the case for player talents).
- TraitSubTree - a The War Within Hero Tree, it's not really part of the hierarchy directly, but instead, has relations to trees, nodes, and nodeEntries.
All of the entities above can be queried with the C_Traits API, which is listed below
Hero Talent Trees
The War Within introduced a new concept into talent trees: Hero Trees. In APIs, these are called subTrees instead. Various APIs have been updated to include subTree information, but generally, the inclusion of subtrees does not affect the overall methodology of working with the C_ClassTalents and C_Traits APIs. Conceptually, SubTrees are implemented in a fashion, that allows the system to be used in applications other than Hero Talents. Time will tell whether that will happen.
Choosing a subTree
Each TraitTree has 1 hidden talent node per specialization, which defines the chosen subTree. This node has the type Enum.TraitNodeType.SubTreeSelection, and has 2 entryIDs. These EntryIDs' entryInfo table, includes a entryInfo.subTreeID value, which corresponds to the SubTreeID.
Choosing a subTree, is as simple as changing the selected node, via C_Traits.SetSelection. This is also how subTree selection is stored in a Talent Build String, encoding the SubTreeSelection type node the same way as a regular Selection type node.
The SubTreeSelection Node has no cost attached to it, only a TraitCondition requirement of level 71.
Purchasing subTree talents
Each subTree in a class has their own TraitCurrency. Note that different classes might share the same TraitCurrencyID. Once a player reaches level 70, they'll stop earning any class or spec TraitCurrency, and will start earning subTree TraitCurrency instead. The player will earn 1 amount of each subTree TraitCurrency every level, up to the level cap. This allows you to purchase talents in both subTrees at once, though only 1 subTree will ever be active. Purchasing subTree talents, functions the exact same way as any other talent, its cost is just a subTree TraitCurrency, rather than a class/spec TraitCurrency.
Checking whether a subTree talent is active
You can use C_SpellBook.IsSpellKnown to find out whether a talent in a subTree is purchased and active. Alternatively, you can check C_Traits.GetNodeInfo nodeInfo.subTreeActive to check whether the node's subTree is active.
SubTree APIs
- C_ClassTalents.GetHeroTalentSpecsForClassSpec returns the available subTreeIDs for a given spec, and the required player level to activate a subTree. Note it requires a configID as an argument, it is not yet confirmed whether this imposes restrictions on the returned value.
- C_Traits.GetSubTreeInfo returns TraitSubTreeInfo for a given subTreeID, this info includes all the info required to display the subTree in the UI, and includes the TraitCurrencyID and SubTreeSelection nodeID. Note it requires a configID as an argument, it is not yet confirmed whether this imposes restrictions on the returned value.
- C_Traits.GetNodeInfo is updated to include
nodeInfo.subTreeIDandnodeInfo.subTreeActiveproperties for subTree talents, for other nodes, these are both nil. - C_Traits.GetEntryInfo is updated to include
entryInfo.subTreeID, which only exists on SubTreeSelection node entries, and specified the subTree that is activated if this entry is chosen. - TRAIT_SUB_TREE_CHANGED is fired when a SubTreeSelection node entry is updated, telling the UI to change its display. This does not mean that the new subTree is actually active, this only happens after the pending change is committed.
Visual positioning implementation details
Each Node has it's own posX and posY information. For subTree nodes, these should not be seen in relation to regular nodes. Instead, subTree nodes' positions should only be viewed in relation to themselves. In fact, the default talent UI will normalize the subTree node positions through TalentFrameUtil.GetNormalizedSubTreeNodePosition.
A given subTree, has SubTreeInfo, which returns the Center X for all its subTree nodes, and the top Y for its top subTree node. This allows the UI to use these numbers to make a general offset for subTree nodes.
Effectively, you take the top-center subTree node, and places it in your desired position, taking the subTree posX and posY as offsets, and position all subTree nodes based on (nodePosX / 10) - subTreePosX and (-nodePosY / 10) + subTreePosY
Other implementation details
SubTreeSelection nodes, have TraitCond applied through TraitNodeXTraitCond, which specifies a SpecSetMember requirement, which maps to the required specializationID. TraitCurrencySource specifies the sources for SubTree TraitCurrency, which also shows there are only 4 currencies total, so all classes use the same currencies for the SubTrees, with most classes using the same 3. Other than that, all the info lives in TraitSubTree, and the rest of the implementation is effectively identical to the spec and class nodes
Common usecases
Determining the currently selected loadout
The unfortunate truth is that there is no API for determining which player created loadout is currently selected. This can and does cause occasional bugs, where addons and even the default UI mistakenly think a different loadout is selected than is actually the case.
The following function can be used as a best effort guess:
local function GetSelectedLoadoutConfigID()
local lastSelected = PlayerUtil.GetCurrentSpecID() and C_ClassTalents.GetLastSelectedSavedConfigID(PlayerUtil.GetCurrentSpecID())
local selectionID = PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame and PlayerSpellsFrame.TalentsFrame.LoadoutDropDown and
PlayerSpellsFrame.TalentsFrame.LoadoutDropDown.GetSelectionID and
PlayerSpellsFrame.TalentsFrame.LoadoutDropDown:GetSelectionID()
-- the priority in authoritativeness is [default UI's dropdown] > [API] > ['ActiveConfigID'] > nil
return selectionID or lastSelected or C_ClassTalents.GetActiveConfigID() or nil -- nil happens when you don't have any spec selected, e.g. on a freshly created character
end
Commands
Several script and slash commands[2] have been added for accessibility and general use in ClassTalentHelper.lua
- Activate loadout by name:
/run ClassTalentHelper.SwitchToLoadoutByName("loadoutName")
/loadoutname loadoutName
/lon loadoutName
- Activate loadout by dropdown list order, starting with 1:
/run ClassTalentHelper.SwitchToLoadoutByIndex(1)
/loadoutindex 2
/loi 3
- Activate specialization by name:
/run ClassTalentHelper.SwitchToSpecializationByName("specName")
/specname specName
/spn specName
- Activate specialization by order within the Specializations tab, starting with 1:
/run ClassTalentHelper.SwitchToSpecializationByIndex(1)
/specindex 2
/spi 3
Examples
- Extracts the list of SpellIDs for the player's current specialization
local function GetSpellIDList()
local list = {}
local configID = C_ClassTalents.GetActiveConfigID()
if configID == nil then return end
local configInfo = C_Traits.GetConfigInfo(configID)
if configInfo == nil then return end
for _, treeID in ipairs(configInfo.treeIDs) do -- in the context of talent trees, there is only 1 treeID
local nodes = C_Traits.GetTreeNodes(treeID)
for i, nodeID in ipairs(nodes) do
local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
for _, entryID in ipairs(nodeInfo.entryIDs) do -- each node can have multiple entries (e.g. choice nodes have 2)
local entryInfo = C_Traits.GetEntryInfo(configID, entryID)
if entryInfo and entryInfo.definitionID then
local definitionInfo = C_Traits.GetDefinitionInfo(entryInfo.definitionID)
if definitionInfo.spellID then
table.insert(list, definitionInfo.spellID)
end
end
end
end
end
return list
end
- Change a selection node choice, and apply the changes.
local function SwitchDragonridingTalentSelection()
local DRAGONRIDING_TRAIT_SYSTEM_ID = 1
local nodeID = 64062 -- vigor regen node
local configID = C_Traits.GetConfigIDBySystemID(DRAGONRIDING_TRAIT_SYSTEM_ID)
-- for class talents, this should be: configID = C_ClassTalents.GetActiveConfigID()
if not configID then return end
local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
if not nodeInfo or not nodeInfo.entryIDs or 2 ~= #nodeInfo.entryIDs then return end
local currentEntryID = nodeInfo.entryIDsWithCommittedRanks[1] or nil
local currentSelection
for index, entryID in ipairs(nodeInfo.entryIDs) do
if entryID == currentEntryID then currentSelection = index end
end
local success = C_Traits.SetSelection(configID, nodeID, nodeInfo.entryIDs[currentSelection == 2 and 1 or 2]) -- defaults to setting the second option, if not currently set
if not success then return end
local commitSuccess = C_Traits.CommitConfig(configID)
-- for class talents, this should be: commitSuccess = C_ClassTalents.CommitConfig(selectedLoadoutID)
end
- Checks if a spellID is a learned talent, note that this is an example to explain the API, and that it's simpler and faster to use C_SpellBook.IsSpellKnown instead.
local function IsSpellTalented(spellID) -- this could be made to be a lot more efficient, if you already know the relevant nodeID and entryID
local configID = C_ClassTalents.GetActiveConfigID()
if configID == nil then return end
local configInfo = C_Traits.GetConfigInfo(configID)
if configInfo == nil then return end
for _, treeID in ipairs(configInfo.treeIDs) do -- in the context of talent trees, there is only 1 treeID
local nodes = C_Traits.GetTreeNodes(treeID)
for i, nodeID in ipairs(nodes) do
local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
for _, entryID in ipairs(nodeInfo.entryIDsWithCommittedRanks) do -- there should be 1 or 0
local entryInfo = C_Traits.GetEntryInfo(configID, entryID)
if entryInfo and entryInfo.definitionID then
local definitionInfo = C_Traits.GetDefinitionInfo(entryInfo.definitionID)
if definitionInfo.spellID == spellID then
return true
end
end
end
end
end
return false
end
Relevant DB2 tables
The overview section already mentions a few DB2 tables, this is a full list with a short explanation of its contents and use. Due to the nature of things, this list is highly subject to changes.
- SkillLineXTraitTree maps TraitTrees to SkillLines, including player classes and professions.
- TraitCond TraitCondition info, defining node visibility, granting, and availability, and the requirements for each condition
- TraitCost the TraitCurrencyID cost for a given TraitCostID, which is often shared by multiple nodes
- TraitCurrency TraitCurrency info, and a relation to CurrencyTypeID in case the TraitCurrency is based on a currency
- TraitCurrencySource defines TraitCurrency sources, for the currencies not tied to a currency
- TraitDefinition the actual effect of a TraitNodeEntry, generally a spell or crafting effect
- TraitDefinitionEffectPoints related to the TraitNodeDefinition and a Curve
- TraitEdge defines the arrows to be drawn between arrows, and whether these arrows impose availability constraints
- TraitNode a node's TraitTreeID, position, type, and TraitSubTreeID
- TraitNodeEntry maps to TraitDefinition, details max ranks, its type, and optionally a TraitSubTreeID
- TraitNodeEntryXTraitCost maps a TraitNodeEntry and TraitCost
- TraitNodeGroup specifies a NodeGroupID, this is used to apply costs and conditions to several nodes at once
- TraitNodeGroupXTraitCond maps groups with conditions
- TraitNodeGroupXTraitCost maps group with costs
- TraitNodeGroupXTraitNode maps nodes with groups
- TraitNodeXTraitCond maps nodes with conditions
- TraitNodeXTraitCost maps nodes with costs
- TraitNodeXTraitNodeEntry maps nodes with their entries
- TraitSubTree currently defines Hero Talent Trees, with their name, description, texture atlas, and parent TraitTreeID
- TraitSystem used for generic talent trees, such as dragonriding
- TraitTree tree, with optional conditions, flags, reference to TraitSystem
- TraitTreeLoadout contains information for starter builds - there are dupplicates in the data, and it's currently unknown how blizzard associates a loadout with a specific spec
- TraitTreeLoadoutEntry contains starter build selections and their order
- TraitTreeXTraitCurrency maps trees and currencies, with information in which order the API should return the TraitCurrencyIDs
References
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||