Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Classic Talent Trees is a faithful recreation of the Classic World of Warcraft talent tree system. It is similar to Spellweaver's Talent Kitchen, but is for Lua, has a more extensive UI around it, and sacrifices some customization options for much more convenience and code brevity.
In addition to mimicking the World of Warcraft talent trees, these features are available:
Talents with variable talent costs.
Talents with requirements from other talent trees.
Require total talent points spent to unlock talent.
Option to build talent trees bottom-to-top.
New single- or multi-rank talents can be built quickly and easily. You have the option to insert values automatically from code or from ability fields directly into the tooltips. Here is a talent that teaches Starfall to the player's hero:
Lua:
CTT.RegisterTalent({
name = "Starfall",
tooltip = "While channeling, calls down waves of falling stars that damage nearby enemy units. Each wave deals !damage! damage. Lasts for !duration! seconds. !cooldown! seconds cooldown.",
icon = "TalentIcons\\Starfall.blp",
tree = "Lunar",
column = 3,
row = 3,
--Retrieve values from the ability fields of the linked ability.
ability = "AEsf",
values = {
damage = "Esf1",
duration = "adur",
cooldown = "acdn"
},
onLearn = function(whichPlayer, talentName, parentTree, oldRank, newRank)
if newRank > 0 then
UnitAddAbility(HeroOfPlayer[whichPlayer], FourCC "AEsf")
else
UnitRemoveAbility(HeroOfPlayer[whichPlayer], FourCC "AEsf")
end
end,
})
You can conveniently use the talent's values in your code:
Lua:
function OnDamage()
local source = GetEventDamageSource()
if CTT.HasTalent(source, "Increased Damage") then
BlzSetEventDamage(GetEventDamage*(1 + CTT.GetValue(source, "Increased Damage", "amount")))
end
end
This ensures that anytime you change the numbers in your map, all tooltips and constants get updated accordingly.
Other features:
Set different talent trees for different players and add talent trees later in the game.
Customize talent tree menu texts.
Store a player's talent tree to an array. A function to encode that array into a string is not included.
Installation
Copy the ClassicTalentTrees script file into your map. Next, make sure you have all the requirements imported. These are:
These files are required, but you can use your own textures if you wish.
TalentTreeFade1-4.blp
Import these if you want to use the black border around the background artwork.
blackmask.blp
talentTreeBorderYellow.blp
These are referenced in the TalentNavigator.fdf file. You can replace them with different textures if you wish.
Creating a Talent Icon
Creating a Talent
Creating a Values Table
Creating Talent Trees
Customization
Creating a Talent Icon
A talent uses three different frames:
The normal icon (without a suffix) is used when a player has unspent talent points and/or has already spent points into that talent, but only if that talent isn't maxed yet.
If the talent is maxed, the maxed icon ("<TalentIconPath>Maxed.blp") will be used.
The disabled icon ("<TalentIconPath>Disabled.blp") will be used if the talent has no points put into it and the player has no unspent talent points or the requirements for that talent aren't met.
To use the frames from the test map, extract the frame icons that are included in the Assets.zip file and use Button Manager to add the frames to your icons. For the disabled icon, you need to set the saturation of the icon to 0 with an image manipulation tool before adding the frame.
You can use the standard Warcraft 3 enabled and disabled frames instead. To do that, modify the GetTalentIconMaxedPath and GetTalentIconDisabledPath functions in the config to return the correct paths.
Creating a Talent
The CTT.RegisterTalent function creates a talent from the data provided in the data table. The data table has the following fields:
Field
Description
fourCC
The fourCC code of the ability that is used to retrieve the name, icon, and tooltip of the talent.
name
icon
tooltip
Alternatively, you can enter the name, icon, and tooltip manually with these fields.
tree
The name of the parent tree of the talent.
column
row
The column and row of the talent within its parent tree. Columns and rows start at 1 in the bottom-left corner.
maxPoints
The maximum number of points that can be spent on this talent. Defaults to 1 if not provided.
onLearn
An optional function that is executed whenever the number of points in this talent changes (up or down). The function is called with the arguments (whichPlayer, talentName, parentTree, oldRank, newRank).
requirement
An optional name of a talent that is the requirement for this talent or a table containing multiple requirements. Requirement talents will only have an arrow pointing to the successor talent if they are positioned directly next to the successor on the left or right, directly below it, or two rows below it.
prelearnTooltip
An optional tooltip that, when set, overwrites the regular tooltip for as long as a talent has no points put into it.
values
An optional table with string keys that holds talent data such as damage bonus, duration etc. The values can be auto-inserted into the tooltips. Returns 0, "", or false if the talent rank is zero.
ability
Links a talent with the ability with the specified fourCC code. By doing so, you can set the entries in the values table to an ability field of that ability. Requires the EasyAbilityFields library.
affects
An optional fourCC code or table with fourCC codes. Objects added to this table can be displayed in the talent tooltips.
You can run the CTT.RegisterTalent function from anywhere in your code during map initialization before talent trees are initialized.
Creating a Values Table
The values table is a powerful tool to retrieve data from talents as well as insert the values directly into the talent tooltips. The keys of the values table
are strings that should indicate what this value is representing. Example:
Lua:
values = {
damage = 10,
duration = 15
}
You can insert the values automatically into the tooltip by using the referenced word surrounded by two exclamation marks:
"Your Curse of Pain deals an additional !damage! damage over !duration! seconds."
The value provided in the values table will be multiplied by the talent's current rank, so, on a 5-rank talent, the tooltip will be:
"Your Curse of Pain deals an additional 15/30/45/60/75 damage over 15/30/45/60/75 seconds."
Increasing the duration with each rank is probably not intended, so we can turn the value into a constant by adding the $ character:
"Your Curse of Pain deals an additional !damage! damage over !duration,$! seconds."
A value can be expressed as a percentage, by adding the percentage symbol (use \x25 instead if you're adding the tooltip via the tooltip field):
"Increases the damage dealt by your Curse of Pain by !damage,%!."
The two characters can be combined by writing !value,%$!.
You can provide a table instead of a number to specify the value of each rank individually:
values = {
food = {
"Homemade Brownie",
"Creamy Cheesecake",
"Cinnamon Apple Pie",
"Chocolate Fudge Cake",
"Layered Black Forest Cake"
},
health = 100,
cooldown = 180
}
"Creates a !food! that can be consumed to restore !health! health. !cooldown,$! seconds cooldown."
The value can be retrieved for calculations in your code with the CTT.GetValue function:
Lua:
function CreateFood(whichBaker)
local foodType = CTT.GetValue(whichBaker, "Conjure Pastries", "food")
local healingAmount = CTT.GetValue(whichBaker, "Conjure Pastries", "health")
--[...]
end
Values do not have to appear in the tooltips. However, constants can only be declared through a reference in the tooltip with the $ sign.
By linking a talent with an ability, you can retrieve the values directly from the ability fields of that ability:
The values will be looked up from the level of the ability equal to the number of points put into the talent.
Creating Talent Trees
To create a talent tree, you need to add it to the TALENT_TREES table in the config. A texture called "TalentTreeBackground<Name>.blp" must be present for the background art to appear. The name is converted, removing all spaces and special characters. Example: "Archer's Skills" -> "ArchersSkills".
Before a talent tree can be shown to a player, you need to add it to that player's list of talent trees with CTT.SetTrees. For example, when player 1 picks a Mage character and player 2 a Hunter character, you want to do:
To show the talent trees to the respective player, you need to do:
Lua:
CTT.Show(whichPlayer)
Customization
Many characteristics of the talent tree menu can be customized in the config, but, for some, you need to edit the fdf-files. Here is a list of UI elements you can customize this way:
CTT.ForceSelectTalent(who, whichTalent) Selects the specified talent for a player. If the optional subtractCost argument is not set, the player
will not lose unspent talent points and all...
It's pretty cool stuff! However, noticed the next bug:
If you learn talent, exit the menu, save the map, and then load it back - the game will load properly, but opening the talent menu will result in a game crash.
If you do the same while the talent menu is opened - it will disappear on loading, but still pressing "Talent Menu" will crash the game.
IMO, this is a Blizzard issue with frames, I saw that using the FrameLoad custom function could potentially prevent it.
I'm planning to use this for a map where you play multiple heroes sequentially. I will swap out the talent trees registered to the player as you switch to a new hero. But having multiple talent tree systems for different heroes per player at the same time is not supported currently (but it could be with a few tweaks; just replace the player keys and function arguments with units).
It's pretty cool stuff! However, noticed the next bug:
If you learn talent, exit the menu, save the map, and then load it back - the game will load properly, but opening the talent menu will result in a game crash.
If you do the same while the talent menu is opened - it will disappear on loading, but still pressing "Talent Menu" will crash the game.
IMO, this is a Blizzard issue with frames, I saw that using the FrameLoad custom function could potentially prevent it.
Love this system! Super easy to use. I have already implemented it into my project, however something I'd like to request is the ability to make two talents incompatible. That way, you can have a character choose between different Q abilities without allowing the player to choose both. If this is already possible in the system, then I admittedly have missed it!
Love this system! Super easy to use. I have already implemented it into my project, however something I'd like to request is the ability to make two talents incompatible. That way, you can have a character choose between different Q abilities without allowing the player to choose both. If this is already possible in the system, then I admittedly have missed it!
Great to hear! I can make a DisableTalent function and you can call it when the other talent is selected. Then I would make a prelearnTooltip flag for each talent, where you can put the text that informs the player that this talent disables other talents.
CTT.ForceSelectTalent(who, whichTalent) Selects the specified talent for a player. If the optional subtractCost argument is not set, the player
will not lose unspent talent points and all requirements of the talent will be ignored.
I would like to see optional parameters on the function, it can get tricky if there's more than one and the wanted one not right after the required ones.
Lua:
CTT.Show(who, enable) Opens the talent menu for the specified player, or for all players if no player is specified. Set the
optional enable parameter to false to instead force players to close the talent menu.
Here, enable is an optional parameter but it's visible, perhaps show them as optional by using "?" like the sumneko extension?
Lua:
---@param whichPlayer player
---@param whichTalent string
---@return string
local function GetTalentTooltip(whichPlayer, whichTalent)
local currentLevel = playerPointsInTalent[whichPlayer][whichTalent]
local maxPoints = talentMaxPoints[whichTalent]
if maxPoints == 1 then
if currentLevel == 0 then
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. talentTooltip[whichTalent][1] .. GetAffectsText(whichTalent)
else
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. talentTooltip[whichTalent][1] .. GetAffectsText(whichTalent)
end
elseif currentLevel == maxPoints then
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. CURRENT_RANK_TEXT:gsub("!POINTS!", currentLevel):gsub("!MAXPOINTS!", maxPoints) .. "|n" .. talentTooltip[whichTalent][maxPoints] .. GetAffectsText(whichTalent)
elseif currentLevel == 0 then
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. NEXT_RANK_TEXT:gsub("!POINTS!", 1):gsub("!MAXPOINTS!", maxPoints) .. "|n" .. talentTooltip[whichTalent][1] .. GetAffectsText(whichTalent)
else
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. CURRENT_RANK_TEXT:gsub("!POINTS!", currentLevel):gsub("!MAXPOINTS!", maxPoints) .. "|n" .. talentTooltip[whichTalent][currentLevel] .. "|n|n" .. NEXT_RANK_TEXT:gsub("!POINTS!", currentLevel + 1):gsub("!MAXPOINTS!", maxPoints) .. "|n" .. talentTooltip[whichTalent][currentLevel + 1] .. GetAffectsText(whichTalent)
end
end
Lua:
if maxPoints == 1 then
if currentLevel == 0 then
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. talentTooltip[whichTalent][1] .. GetAffectsText(whichTalent)
else
return GetRequirementsText(whichPlayer, whichTalent) .. TALENT_TITLE:gsub("!TREE!", talentParentTree[whichTalent]) .. "|n" .. talentTooltip[whichTalent][1] .. GetAffectsText(whichTalent)
end
Missing oldRank as the parameter before the last parameter while currently this last one is the newRank.
Lua:
OnInit.final("TalentTree", function()
localPlayer = GetLocalPlayer()
GetOwner = ALICE_GetOwner or GetOwningPlayer
What's ALICE doing here? :O
Lua:
---Returns a table containing all talents that affect the specified object. Object can be a fourCC code or id.
---@param object string | integer
---@return table
CTT.AffectedBy = function(object)
local id = type(object) == "string" and FourCC(object) or object
return affectedByTalents[id] or {}
end
Returning an empty table makes users have to change if it's empty by using next. Isn't it more desiredable to return false or something?
Small detail, when the player opens the talent tree the first time, make that talent button at the bottom also bigger, as switching between talent trees does
It's a very flexible system, allowing one to have several talent trees, and even set or add different trees if you happen to engage in an RPG and choose a different character.
Added the prelearnTooltip flag, which can be used to overwrite the regular tooltip for as long as a talent has no points put into it.
Added the CTT.DisableTalent function, which disables a talent until enabled again. You can enter a custom requirements text, notifying the player what he or she has to do to enable the talent.
Added the CTT.EnableTalent function, which enables a previously disabled talent.
Added the CTT.IsOpened function, which returns whether the specified player currently has the talent menu opened.
Fixed an issue with the argument order in the global callback function.
Fixed a bug that caused the navigator button of the default talent tree to not be highlighted when the talent menu is first opened.
Added all optional parameters to the function documentation.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.