• 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!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Classic Talent Trees v1.2

This bundle is marked as high quality. It exceeds standards and is highly desirable.

Overview

Installation

Tutorial

Credits


Overview

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:

RequirementDescription
Total InitializationNeeds no explanation.
Handle TypeAllows ClassicTalentTrees to determine the correct types of the objects passed into it.
Easy Ability Fields
(optional)
Required for linking talents with abilities.

Then, import all the assets and frame definition files needed. These are:

AssetDescription
TalentTreeTemplates.tocThe .toc file loads all the frame definition files. Make sure you keep the empty line at the bottom or it will break!
TalentTree.fdf
TalentTreeBottomBar.fdf
TalentButton.fdf
TalentTooltip.fdf
These are the frade definition files for the various elements of the talent tree menu.
transparentmask.blpImport this one unchanged.
TalentMenuClose.blp
TalentPointsBackdrop.blp
TalentArrow[...].blp
These files are required, but you can use your own textures if you wish.
TalentTreeFade1-4.blpImport 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:

FieldDescription
fourCCThe 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.
treeThe 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.
maxPointsThe maximum number of points that can be spent on this talent. Defaults to 1 if not provided.
onLearnAn 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).
requirementAn 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.
prelearnTooltipAn optional tooltip that, when set, overwrites the regular tooltip for as long as a talent has no points put into it.
valuesAn 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.
abilityLinks 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.
affectsAn 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:

Lua:
values = {
    damage = {10, 15, 20, 25, 30},
    duration = 15
}

The values do not have to be numbers:

Lua:
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:
Lua:
ability = "AHbz"
values = {
    duration = "adur",
    damage = "Hbz2",
    range = "aran"
}

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:
Lua:
CTT.SetTrees(Player 1, "Fire", "Frost", "Arcane")
CTT.SetTrees(Player 2, "Beast Mastery", "Marksmanship", "Survival")

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:

ElementHow to customize
Menu Border TextureTalentTree.fdf -> TalentTreeBorder -> BackdropCornerSize / BackdropEdgeFile
Top/Bottom Bar BackdropTalentTree.fdf -> TalentTree[Top/Bottom]Bar -> BackdropBackground
Top/Bottom Bar BorderTalentTree.fdf -> TalentTree[Top/Bottom]Bar -> BackdropCornerSize / BackdropEdgeFile
Talent Navigator BackdropTalentNavigator.fdf -> BackdropBackground
Talent Navigator BorderTalentNavigator.fdf -> BackdropCornerSize / BackdropEdgeFile
Talent Navigator Mouse-Over HighlightTalentTree.fdf -> TalentNavigatorHighlight -> HighlightAlphaFile
Talent Cost IconTalentTooltip.fdf -> TalentTooltipIcon


Test Map Terrain - Seven Blademasters (2000 B.C. version)
Lunar Background Art - Glenn Rane (Hearthstone)
Survival Tree Background Art - Jorge Jacinto
Contents

ClassicTalentTrees (Binary)

ClassicTalentTrees v1.2 (Map)

TalentTreeAssets (Binary)

Reviews
Wrda
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...
Level 27
Joined
Jun 11, 2017
Messages
837
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.
 
The UI looks good.
I suggest to implement close UI by ESC-Pressing.

Is this for 1 Hero per player?
That is a great idea, thanks!

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.

That sounds awfully tedious! Let's just nuke that problem once and for all!
 
Last edited:
Level 8
Joined
Oct 20, 2010
Messages
213
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.
 

Wrda

Spell Reviewer
Level 28
Joined
Nov 18, 2012
Messages
1,993
Lua:
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
duplicated?

Lua:
GLOBAL_CALLBACK(whichPlayer, whichTalent, whichTree, playerPointsInTalent[whichPlayer][whichTalent])
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.

High Quality
 
Version 1.2
  • 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.
 
Top