• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[UI] Lua (Wow) Talent system

Status
Not open for further replies.
So, I've been busy last few couple of days and made this little thing that I need for a map and I thought it would be nice if I made it usable by other people.

I'm mostly posting this for people to check out/use or note bugs. (1-2 I already know of)
What I would also appreciate is ideas of how to make the code's structure more efficient or manageable. It does have a lot of code, I think 1300 lines including the example Talent Tree setup. It's been okay until "Backwards dependency" (which is whether player can remove point from talent based on dependencies). It obliterated my code, forced me to think a lot and made a lot of bugs.

A lot of data is scattered around since it's only in development, and I haven't spent enough time rewriting it so I would have the least data locations/references.

It doesn't include a lot of documentation (for functionalities that were added on the fly) also because a lot of functionality is implemented through the API I provided (custom made) so it's not a standard functionality, and because I changed some things after I wrote this part of doc.

-> Maybe I would recommend not to look at the code after all ^^
I'm sorry for the bad formatting, structure and severe lack of comments. I hope the map works :D
Note however, the talents don't do anything. It's just a show of UI.

upload_2019-7-21_1-41-22.png
upload_2019-7-21_1-44-2.png

Changelog
> 0.1: Upload
> 0.2:
  • Reworked Backward dependency, seems to be fully working now (the standard link feature)
  • Fixed the custom wow-like point requirement example
  • Disabled removal requirement for example
  • Exposed "RemoveRequirements" setter for callback that needs to be true in order to remove a (temporary) talent point
  • Replaced the link texture with "Water00.blp" and made it thinner. Looks better imo
  • Added automatic ID to talents (per tree)
  • Credits ._.

It requires Bribe's Global Initialization for Lua:
[Lua] Global Initialization
and Timer Utils for Lua:
[Lua] TimerUtils

Lua:
-------------------------------------------------------------------------------------
-- CreateTalentTree(unit) returns TalentTree[unit]

-- Base function with which you prepare a TalentTree object for a unit.
-- It's not used directly, but rather within a template that also adds
-- specific list of talents and sets their dependencies / values.

-- Make a template function called CreateMageTalentTree(unit) and call this
-- first inside to map a base TalentTree object to the unit and start customizing it.

-------------------------------------------------------------------------------------
-- [talentTreeObject].AddTalent(self, x, y, talentData) returns Talent
-- [talentTreeObject]:AddTalent(x, y, talentData) returns Talent

-- Takes a talent tree object and prepares and adds a new Talent object on specified x/y.
-- "talentData" is an initializer object. If it contains one of the following,
-- they will be passed to their setter on the object before it is returned:

-- Name, Description, Icon, IconDisabled, OnActivate, OnDeactivate, Requirements

-- If talent exists on the specified position, :SetNextTalent will be called on it instead.

-------------------------------------------------------------------------------------
-- [talentTreeObject].SetNextTalent(self, x, y, talentData) returns Talent
-- [talentTreeObject]:SetNextTalent(x, y, talentData) returns Talent

-- Creates a new talent object and sets it as "next talent" to the one it's
-- called upon. When a point is gained in the base talent, it will switch
-- to the talent created by this method.

-- The method returns the NEW talent rather than old, so subsequent setters
-- will affect the NEW returned talent instead of the base one.

-------------------------------------------------------------------------------------
-- Talent Setters/Getters ------------------------------------------------------------------

-----------------------------------------------
-- .Name(self, name) or :Name(name) returns self, if name is nil returns ._name
-- This is text that is shown on top of the tooltip when hovering over the talent.

-----------------------------------------------
-- .Description(self, desc) or :Description(desc) returns self, if desc is nil returns ._desc
-- This is the text that shows under the title and Requirements.

-----------------------------------------------
-- .Icon(self, path) or :Icon(path) returns self, if path is nil returns ._icon
-- The icon that is shown for the talent. Calling this method will also set
-- the ._iconDisabled to the transformed path (added CommandButtonsDisabled/DISBTN)
-- >>> IMPORTANT: For this reason, always pass the forward slash / instead of double back \\

-----------------------------------------------
-- .IconDisabled(self, path) or :IconDisabled(path) returns self, if path is nil returns ._iconDisabled
-- This icon is shown when talent has no points in it.
-- >>> IMPORTANT: If you need icon different than enabled, always call this AFTER :Icon(path)
-- because it will get overwritten.

-----------------------------------------------
-- .OnActivate(self, callback) or :OnActivate(callback)
-- This sets a function that will get called after talent point is confirmed.
-- Use this to apply the bonus effect to the wielder of the talent tree.

-- function(self, caster) returns nothing
-- 1: [self] first parameter is always talent. You can save data to it at start and refer within callback
-- 2: [caster] second parameter will be the unit/wielder of the talent tree.

-----------------------------------------------
-- .OnDeactivate(self, callback) or :OnDeactivate(callback)
-- Currently this function is never called. But when I add ability to reset all talents,
-- This will be used to revert the changes done through onActivate.

-----------------------------------------------
-- .Requirements(self, callback) or :Requirements(callback)
-- This callback will be called when checking dependencies. If it returns false,
-- Putting points in talent will be disabled and specified text will be shown

-- function(self, caster) return isFulfilled, errorText
-- 1. [self] is the talent
-- 2. [caster] is the wielder of the Talent Tree
-- 3. [isFulfilled] is boolean. If it is not true, talent will be disabled
-- 4. [errorText] is string, it will be displayed in red as a reason

-------------------------------------------------------------------------------------

TalentTree = {}

function CreateTalentTree(unit)
    TalentTree[unit] = {
        unit = unit,
        Talents = {},

        TalentState = {},
        TempTalentState = nil,

        TalentLevel = {},
        TempTalentLevel = nil,

        Dependency = {},
        BaseDependency = {},

        Requirements = {},
        RequiredTalents = {},
        AddTalent = AddTalent,

        TalentCount = 0,

        CheckDependencies = function(self, state, level, i, owner)
        
            if (not self.Talents[i].dependency) then return true, "" end
            local ok = true
            local returnString = ""
            local dep = self.Talents[i].dependency

            local tPos = { left = i - 1, up = i + TalentView.COLUMNS, right = i + 1, down = i - TalentView.COLUMNS }
            -- linkFramePos = { left = i - 1, up = i, right = i, down = i - TalentView.COLUMNS }
            local linkPos = { left = i - 1, up = i, right = i, down = i - TalentView.COLUMNS }

            for key, req in pairs(dep) do
                -- print(key, req)

                -- If requirement exists, continue
                if (req) then
                    local pos = tPos[key]

                    -- Set the link visibility to enabled
                    local link
                    if (key == "left" or key == "right") then
                        link = TalentView.horizontalLink[linkPos[key]]
                    else
                        link = TalentView.verticalLink[linkPos[key]]
                    end
                    if (GetLocalPlayer() == owner) then
                        BlzFrameSetTexture(link, TalentView.ACTIVE_LINK_BOX, 0, true)
                        BlzFrameSetVisible(link, true)
                    end

                    -- Check against the state now
                    if (self.Talents[pos] and state[pos] ~= true) then
                        if (level[pos] and level[pos] < req) then
                            ok = false
                            returnString = returnString.." "..self.Talents[pos]._name.." "..req
                            if (GetLocalPlayer() == owner) then
                                BlzFrameSetTexture(link, TalentView.INACTIVE_LINK_BOX, 0, true)
                            end
                        end
                    end
                end
            end
            -- print("ok")
            return ok, returnString
        end,
        CheckBaseDependency = function(self, level, i)
            local tPos = { right = i - 1, down = i + TalentView.COLUMNS, left = i + 1, up = i - TalentView.COLUMNS }

            -- Loop through all sides (left, right, up, down)
            for key, pos in pairs(tPos) do
            
                -- If a talent exists at the position at all, continue
                if (self.Talents[pos]) then

                    -- This is dependency of the talent we are looking for (left right up or down)
                    local dep = {}
                    -- If there is a BaseDependency registered from that talent (i.e. a talent point was added)
                    -- We fetch its data from the dictionary
                    if (self.BaseDependency[pos]) then dep = self.BaseDependency[pos] end

                    -- If dependency has a value/requirement towards our side, check it
                    local depValue = 0
                    if (dep[key]) then depValue = dep[key] end
                
                    -- If the level is equal or lower than the requirement, we cannot allow reduction
                    if (level < depValue) then
                        return false
                    end
                end
            end
            return true
        end,
    }
    return TalentTree[unit]
end

function CreateTalent(tree, position, data, x, y)
    
    -- print("CreateTalent")
    local talent = {
        Name = function(self, name)
            if (name) then
                self._name = name
                self._activeName = name
                -- print("name set")
                return self
            end
            -- print("printing name")
            return self._name
        end,
        Description = function(self, desc)
            if (desc) then
                self._desc = desc
                self._activeDesc = desc
                -- print("desc set")
                return self
            end
            return self._desc
        end,
        IconDisabled = function(self, path)
            if (path) then
                self._iconDisabled = path
                return self
            end
            return self._iconDisabled
        end,
        Icon = function(self, path)
            if (path) then
                -- print("icon set")
                self._icon = path
                self._activeIcon = path
                self._iconDisabled = string.gsub(path, "CommandButtons/", "CommandButtonsDisabled/DIS")
                return self
            end
            return self._icon
        end,
        OnActivate = function(self, callback)
            if (callback) then
                -- print("onActivate set")
                self.onActivate = callback
                return self
            end
            return self.onActivate
        end,
        OnDeactivate = function(self, callback)
            if (callback) then
                -- print("onDeactivate set")
                self.onDeactivate = callback
                return self
            end
            return self.onDeactivate
        end,
        SetDependency = function(self, direction, level)
            local dep = {}
            if (self.dependency) then dep = self.dependency end
            local x, y = math.floor(math.fmod((position-1), TalentView.COLUMNS)), math.floor((position-1) / TalentView.COLUMNS)
            local var = ({ left = x, right = x, up = y, down = y })[direction]
            local lower = ({ left = 1, right = 0, up = 0, down = 1 })[direction]
            local higher = ({ left = TalentView.COLUMNS, right = TalentView.COLUMNS - 1, up = TalentView.ROWS - 1, down = TalentView.ROWS })[direction]

            if (var >= lower and var < higher) then
                dep[direction] = level
                self.dependency = dep
            end
            return self
        end,
        SetNextTalent = function(self, talentData)
        
            -- Create a new talent and set their references.
            local new = CreateTalent(tree, position, talentData, x, y)
            self.nextTalent = new
            new.previousTalent = self
            table.insert(self.levels, new)
            new.levels = self.levels

            -- Set the onNext method which is called when point is added.
            self.onNext = function(self)
                -- Replace the old talent
                tree.Talents[position] = self.nextTalent
                -- Set its state as false
                tree.TempTalentState[position] = false

                -- If this talent has a dependency, set it as baseDependency to the new one.
                if (self.dependency) then
                    self.nextTalent.baseDependency = self.dependency
                end
                return self.nextTalent
            end
            return new
        end,
        SetFinalTalent = function(self, talentData)
            if (talentData.Name)          then self:ActiveName(talentData.Name) end
            if (talentData.Description)   then self:ActiveDescription(talentData.Description) end
            return self
        end,
        Requirements = function(self, callback)
            if (callback) then
                self.requirements = callback
                return self
            end
            return self.requirements
        end,
        RemoveRequirements = function(self, callback)
            if (callback) then
                self.removeRequirements = callback
                return self
            end
            return self.removeRequirements
        end,
        OnPointAdded = function(self, callback)
            if (callback) then
                self.onPointAdded = callback
                return self
            end
            return self.onPointAdded
        end,
        OnPointRemoved = function(self, callback)
            if (callback) then
                self.onPointRemoved = callback
                return self
            end
            return self.onPointRemoved
        end,
        ActiveName = function(self, name)
            if (name) then
                self._activeName = name
                return self
            end
            return self._activeName
        end,
        ActiveDescription = function(self, desc)
            if (desc) then
                self._activeDesc = desc
                return self
            end
            return self._activeDesc
        end,
        SetCustom = function(self, key, value)
            if (key and value) then
                self[key] = value
            end
        end
    }
    talent.requirements = function(self, tree, caster) return true, "" end
    talent.removeRequirements = function(self, tree, caster) return true, "" end
    talent.onPointAdded = function(self, tree, caster) return end
    talent.onPointRemoved = function(self, tree, caster) print("default removed") return end
    talent.onActivate = function(caster) print("Activated.") end
    talent.onDeactivate = function(caster) print("Deactivated.") end
    talent.x = x
    talent.y = y
    talent._isLink = false
    talent.id = tree.TalentCount
    tree.TalentCount = tree.TalentCount + 1

    if (data) then
        if (data.Name)          then talent:Name(data.Name) end
        if (data.Description)   then talent:Description(data.Description) end
        if (data.Icon)          then talent:Icon(data.Icon) end
        if (data.IconDisabled)  then talent:IconDisabled(data.IconDisabled) end
        if (data.OnActivate)    then talent:OnActivate(data.OnActivate) end
        if (data.OnDeactivate)  then talent:OnDeactivate(data.OnDeactivate) end
        if (data.Requirements)  then talent:Requirements(data.Requirements) end
        if (data.RemoveRequirements)  then talent:RemoveRequirements(data.RemoveRequirements) end
        if (data.OnPointAdded)  then talent:OnPointAdded(data.OnPointAdded) end
        if (data.OnPointRemoved)      then talent:OnPointRemoved(data.OnPointRemoved) end
        if (data.SetCustom)     then talent:SetCustom(data.SetCustom[1], data.SetCustom[2]) end
    end
    return talent
end

function AddTalent(self, x, y, talent)

    if (x >= TalentView.COLUMNS or y >= TalentView.ROWS) then
        -- print("Error, talent position incorrect: ", x, y, "expected", TalentView.COLUMNS, TalentView.ROWS)
        return nil
    end

    local position = x + 1 + y * TalentView.COLUMNS

    -- If talent on this position already exists, call SetNextTalent instead

    if (self.Talents[position]) then
        local talents = self.Talents[position].levels
        return talents[#talents]:SetNextTalent(talent)
    end

    local t = CreateTalent(self, position, talent, x, y)
    t.levels = { t }
    self.TalentState[position] = false
    if (self.TempTalentState) then
        self.TempTalentState[position] = false
    end
    self.TalentLevel[position] = 0
    self.Dependency[position] = {}
    self.Talents[position] = t

    -- print(t.levels[1]._name)
    return t
end
Lua:
function CreateArcaneMageTalentTree(unit)
    local tt = CreateTalentTree(unit)
    tt.points = { }
    tt.highestTier = 0

    local tier = {
        "tier",
        0
    }

    -- Arcane Concentration
    local data = {
        Name = "Arcane Concentration",
        Icon = "ReplaceableTextures/CommandButtons/BTNManaBurn.tga",
        SetCustom = tier,
        OnPointAdded = function(self, tree, caster)
            if (not tree.points[self.tier]) then tree.points[self.tier] = 0 end
            tree.points[self.tier] = tree.points[self.tier] + 1
        end,
        OnPointRemoved = function(self, tree, caster)
            if (not tree.points[self.tier]) then tree.points[self.tier] = 0 end
            tree.points[self.tier] = tree.points[self.tier] - 1
        end,
        Requirements = function(self, tree, caster, debug)
            local sum = 0

            for i = 0, self.tier-1 do
                if (tree.points[i]) then
                    sum = sum + tree.points[i]
                end
            end
            -- if (debug == true) then print(sum.."sum vs point"..self.tier*5) end
            if (sum >= self.tier * 5) then
                return true, ""
            end        
            return false, " "..(self.tier*5).." points"
        end
        -- RemoveRequirements = function(self, tree, caster)

        --     local sum = 0
        --     for i = 1, self.tier + 1 do
        --         if (tree.points[i]) then
        --             sum = sum + tree.points[i]
        --         end
        --     end
        --     sum = sum - 1
        --     print("sum "..sum.." tier "..self.tier)
        
        --     -- local i = self.tier + 1
        --     for i = self.tier + 1, 6 do
        --         if (tree.points[i+1]) then
        --             if (tree.points[i+1] and tree.points[i+1] > 0 and sum < i * 5) then
        --                 print("false")
        --                 return false
        --             end
        --             sum = sum + tree.points[i]
        --         end
        --         print("next")
        --     end
        --     return true
        -- end,
    }
    tt:AddTalent(0, 6, data)
        :Description("Rank 0/3\nGives you a 3%% chance of entering a Clearcasting state after any damage spell hits a target. The Clearcasting state reduces the mana cost of your next damage spell by 100%%")
    :SetNextTalent(data)
        :Description("Rank 1/3\nGives you a 3%% chance of entering a Clearcasting state after any damage spell hits a target. The Clearcasting state reduces the mana cost of your next damage spell by 100%%\n\nNext Rank\nGives you a 6%% chance of entering a Clearcasting state after any damage spell hits a target. The Clearcasting state reduces the mana cost of your next damage spell by 100%%")
    :SetNextTalent(data)
        :Description("Rank 2/3\nGives you a 6%% chance of entering a Clearcasting state after any damage spell hits a target. The Clearcasting state reduces the mana cost of your next damage spell by 100%%\n\nNext Rank\nGives you a 10%% chance of entering a Clearcasting state after any damage spell hits a target. The Clearcasting state reduces the mana cost of your next damage spell by 100%%")
    :SetFinalTalent({
        Description = "Rank 3/3\nGives you a 10%% chance of entering a Clearcasting state after any damage spell hits a target. The Clearcasting state reduces the mana cost of your next damage spell by 100%%"
    })

 
    data.Name = "Improved Counterspell"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNSilence.tga"
    tt:AddTalent(1, 6, data)
        :Description("Rank 0/2\nYour Counterspell also silences the target for 2 sec.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nYour Counterspell also silences the target for 2 sec.\n\nNext Rank\nYour Counterspell also silences the target for 4 sec.")
    :SetFinalTalent({
        Description = "Rank 2/2\nYour Counterspell also silences the target for 4 sec."
    })


    data.Name = "Netherwind Presence"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNEnchantedCrows.blp"
    tt:AddTalent(2, 6, data)
        :Description("Rank 0/3\nIncreases your spell haste by 1%%")
    :SetNextTalent(data)
        :Description("Rank 1/3\nIncreases your spell haste by 1%%\n\nNext Rank\nIncreases your spell haste by 2%%")
        :SetNextTalent(data)
        :Description("Rank 2/3\nIncreases your spell haste by 2%%\n\nNext Rank\nIncreases your spell haste by 3%%")
    :SetFinalTalent({
        Description = "Rank 3/3\nIncreases your spell haste by 3%%"
    })

    tier[2] = 1

    data.Name = "Torment the Weak"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNUnholyFrenzy.tga"
    tt:AddTalent(0, 5, data)
        :Description("Rank 0/3\nYour Arcane damage spells deal 2%% more damage to snared or slowed targets.")
    :SetNextTalent(data)
        :Description("Rank 1/3\nYour Arcane damage spells deal 2%% more damage to snared or slowed targets.\n\nNext Rank\nYour Arcane damage spells deal 4%% more damage to snared or slowed targets.")
        :SetNextTalent(data)
        :Description("Rank 2/3\nYour Arcane damage spells deal 4%% more damage to snared or slowed targets.\n\nNext Rank\nYour Arcane damage spells deal 6%% more damage to snared or slowed targets.")
    :SetFinalTalent({
        Description = "Rank 3/3\nYour Arcane damage spells deal 6%% more damage to snared or slowed targets."
    })
 
    data.Name = "Invocation"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNDispelMagic.blp"
    tt:AddTalent(1, 5, data)
        :Description("Rank 0/2\nYou gain a 5%% damage bonus for 8 sec. after successfully interrupting a spell.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nYou gain a 5%% damage bonus for 8 sec. after successfully interrupting a spell.\n\nNext Rank\nYou gain a 10%% damage bonus for 8 sec. after successfully interrupting a spell.")
    :SetFinalTalent({
        Description = "Rank 2/2\nYou gain a 10%% damage bonus for 8 sec. after successfully interrupting a spell."
    })

    data.Name = "Improved Arcane Missiles"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNStarfall.blp"
    tt:AddTalent(2, 5, data)
        :Description("Rank 0/2\n")
    :SetNextTalent(data)
        :Description("Rank 1/2\nIncreases the number of missiles fired by your Arcane Missiles by 1.\n\nNext Rank\nIncreases the number of missiles fired by your Arcane Missiles by 2.")
    :SetFinalTalent({
        Description = "Rank 2/2\nIncreases the number of missiles fired by your Arcane Missiles by 2."
    })

    data.Name = "Improved Blink"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNBlink.blp"
    tt:AddTalent(3, 5, data)
        :Description("Rank 0/2\nIncreases your speed by 35%% for 3 sec. after casting the Blink spell.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nIncreases your speed by 35%% for 3 sec. after casting the Blink spell.\n\nNext Rank\nIncreases your speed by 70%% for 3 sec. after casting the Blink spell.")
    :SetFinalTalent({
        Description = "Rank 2/2\nIncreases your speed by 70%% for 3 sec. after casting the Blink spell."
    })

    tier[2] = 2

    data.Name = "Prismatic Cloak"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNAdvancedMoonArmor.blp"
    tt:AddTalent(3, 4, data)
        :Description("Rank 0/3\nReduces all damage taken by 2%% and reduces the fade time of your Invisibility spell by 1 sec.")
    :SetNextTalent(data)
        :Description("Rank 1/3\nReduces all damage taken by 2%% and reduces the fade time of your Invisibility spell by 1 sec.\n\nNext Rank\nReduces all damage taken by 4%% and reduces the fade time of your Invisibility spell by 2 sec.")
    :SetNextTalent(data)
        :Description("Rank 2/3\nReduces all damage taken by 4%% and reduces the fade time of your Invisibility spell by 2 sec.\n\nNext Rank\nReduces all damage taken by 6%% and reduces the fade time of your Invisibility spell by 3 sec.")
    :SetFinalTalent({
        Description = "Rank 3/3\nReduces all damage taken by 6%% and reduces the fade time of your Invisibility spell by 3 sec."
    })

    data.Name = "Missile Barrage"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNScatterRockets.blp"
    tt:AddTalent(2, 4, data)
        :Description("Rank 0/2\nYour Arcane Missile spell will fire its missiles every 0.6 sec.")
        :SetDependency("up", 2)
    :SetNextTalent(data)
        :Description("Rank 1/2\nYour Arcane Missile spell will fire its missiles every 0.6 sec.\n\nNext Rank\nYour Arcane Missile spell will fire its missiles every 0.5 sec.")
        :SetDependency("up", 2)
    :SetFinalTalent({
        Description = "Rank 2/2\nYour Arcane Missile spell will fire its missiles every 0.5 sec."
    })

    data.Name = "Presence of Mind"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNHelmutPurple.blp"
    tt:AddTalent(1, 4, data)
        :Description("Rank 0/1\nWhen activated, your next Mage spell with a casting time less than 10 sec becomes an instant cast spell.")
    :SetFinalTalent({ Description = "Rank 1/1\nWhen activated, your next Mage spell with a casting time less than 10 sec becomes an instant cast spell." })

    data.Name = "Arcane Flows"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNPossession.tga"
    tt:AddTalent(0, 4, data)
        :Description("Rank 0/2\nReduces the Cooldown of your Presence of Mind, Arcane Power and Invisibility spells by 12%% and the cooldown of your Evocation spell by 1 min.")
        :SetDependency("right", 1)
    :SetNextTalent(data)
        :Description("Rank 1/2\nReduces the Cooldown of your Presence of Mind, Arcane Power and Invisibility spells by 12%% and the cooldown of your Evocation spell by 1 min.\n\nNext Rank\nReduces the Cooldown of your Presence of Mind, Arcane Power and Invisibility spells by 25%% and the cooldown of your Evocation spell by 2 min.")
        :SetDependency("right", 1)
    :SetFinalTalent({
        Description = "Rank 2/2\nReduces the Cooldown of your Presence of Mind, Arcane Power and Invisibility spells by 25%% and the cooldown of your Evocation spell by 2 min."
    })

    tier[2] = 3

    data.Name = "Improved Polymorph"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNPolymorph.blp"
    tt:AddTalent(0, 3, data)
        :Description("Rank 0/2\nWhen a target you've polymorphed is damaged, that target is stunned for 1.5 sec. This effect cannot occur more often than once every 10 sec.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nWhen a target you've polymorphed is damaged, that arget is stunned for 1.5 sec. This effect cannot occur more often than once every 10 sec.\n\nNext Rank\nWhen a target you've polymorphed is damaged, that arget is stunned for 3 sec. This effect cannot occur more often than once every 10 sec.")
    :SetFinalTalent({
        Description = "Rank 2/2\nWhen a target you've polymorphed is damaged, that arget is stunned for 3 sec. This effect cannot occur more often than once every 10 sec."
    })

    data.Name = "Arcane Tactics"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNMassTeleport.tga"
    tt:AddTalent(1, 3, data)
        :Description("Rank 0/1\nIncreases the damage of all party and raid members within 100 yards by 3%%.")
        :SetDependency("up", 1)
    :SetFinalTalent({ Description = "Rank 1/1\nIncreases the damage of all party and raid members within 100 yards by 3%%." })

    data.Name = "Incanter's Absorption"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNAbsorbMagic.tga"
    tt:AddTalent(2, 3, data)
        :Description("Rank 0/2\nWhen your Mana Shield or Mage Ward absorbs damage your spell damage is increased by 10%% of the amount absorbed for 10 sec. In addition, when your Mana Shield destroyed, all enemies within 6 yards are knocked back 12 yards.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nWhen your Mana Shield or Mage Ward absorbs damage your spell damage is increased by 10%% of the amount absorbed for 10 sec. In addition, when your Mana Shield destroyed, all enemies within 6 yards are knocked back 12 yards.\n\nNext Rank\nWhen your Mana Shield or Mage Ward absorbs damage your spell damage is increased by 20%% of the amount absorbed for 10 sec. In addition, when your Mana Shield destroyed, all enemies within 6 yards are knocked back 12 yards.")
    :SetFinalTalent({
        Description = "Rank 2/2\nWhen your Mana Shield or Mage Ward absorbs damage your spell damage is increased by 20%% of the amount absorbed for 10 sec. In addition, when your Mana Shield destroyed, all enemies within 6 yards are knocked back 12 yards."
    })

    data.Name = "Improved Arcane Explosion"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNWispSplode.blp"
    tt:AddTalent(3, 3, data)
        :Description("Rank 0/2\nReduces the global cooldown of your Arcane Explosion spell by 0.3 sec. Reduces the threat generated by 40%%, and reduces the mana cost by 25%%.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nReduces the global cooldown of your Arcane Explosion spell by 0.3 sec. Reduces the threat generated by 40%%, and reduces the mana cost by 25%%.\n\nNext Rank\nReduces the global cooldown of your Arcane Explosion spell by 0.5 sec. Reduces the threat generated by 80%%, and reduces the mana cost by 50%%.")
    :SetFinalTalent({
        Description = "Rank 2/2\nReduces the global cooldown of your Arcane Explosion spell by 0.5 sec. Reduces the threat generated by 80%%, and reduces the mana cost by 50%%."
    })

    tier[2] = 4

    data.Name = "Arcane Potency"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNPurge.blp"
    tt:AddTalent(0, 2, data)
        :Description("Rank 0/2\nIncreases the critical strike chance of your next two damaging spells by 7%% after gaining Clearcasting or Presence of Mind.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nIncreases the critical strike chance of your next two damaging spells by 7%% after gaining Clearcasting or Presence of Mind.\n\nNext Rank\nIncreases the critical strike chance of your next two damaging spells by 15%% after gaining Clearcasting or Presence of Mind.")
    :SetFinalTalent({
        Description = "Rank 2/2\nIncreases the critical strike chance of your next two damaging spells by 15%% after gaining Clearcasting or Presence of Mind."
    })

    data.Name = "Slow"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNSlow.blp"
    tt:AddTalent(1, 2, data)
        :Description("Rank 0/1\nReduces target's movement speed by 60%%, increases the time between ranged attacks by 60%% and increases casting time by 30%%. Lasts 15 sec. Slow can only affect one target at a time.")
        :OnPointAdded(function(self, tree, caster)
            -- print("point added")
            tree.TempTalentState[6] = true
        end)
        :OnPointRemoved(function(self, tree, caster)
            -- print("point removed")
            tree.TempTalentState[6] = false
        end)
    :SetFinalTalent({ Description = "Rank 1/1\nReduces target's movement speed by 60%%, increases the time between ranged attacks by 60%% and increases casting time by 30%%. Lasts 15 sec. Slow can only affect one target at a time." })

    data.Name = "Nether Vortex"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNStun.blp"
    tt:AddTalent(2, 2, data)
        :Description("Rank 0/2\nGives your Arcane Blast spell a 50%% chance to apply the Slow spell to any target it damages if no target is currently affected by Slow.")
        :SetDependency("left", 1)
    :SetNextTalent(data)
        :Description("Rank 1/2\nGives your Arcane Blast spell a 50%% chance to apply the Slow spell to any target it damages if no target is currently affected by Slow.\n\nNext Rank\nGives your Arcane Blast spell a 100%% chance to apply the Slow spell to any target it damages if no target is currently affected by Slow.")
        :SetDependency("left", 1)
    :SetFinalTalent({
        Description = "Rank 2/2\nGives your Arcane Blast spell a 100%% chance to apply the Slow spell to any target it damages if no target is currently affected by Slow."
    })

    tier[2] = 5

    data.Name = "Focus Magic"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNCharm.blp"
    tt:AddTalent(0, 1, data)
        :Description("Rank 0/1\nIncreases the target's chance to critcally hit with spells by 3%% for 30 min. When the target critically hits, your chance to critically hit with spells is increased by 3%% for 10 sec. Cannot be cast on self. Limit 1 Target.")
    :SetFinalTalent({ Description = "Rank 1/1\nIncreases the target's chance to critcally hit with spells by 3%% for 30 min. When the target critically hits, your chance to critically hit with spells is increased by 3%% for 10 sec. Cannot be cast on self. Limit 1 Target." })
 
    data.Name = "Improved Mana Gem"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNManaStone.tga"
    tt:AddTalent(2, 1, data)
        :Description("Rank 0/2\nMana gained from your Mana Gem also increases your spell power by 1%% of you maximum mana for 15 sec.")
    :SetNextTalent(data)
        :Description("Rank 1/2\nMana gained from your Mana Gem also increases your spell power by 1%% of you maximum mana for 15 sec.\n\nNext Rank\nMana gained from your Mana Gem also increases your spell power by 2%% of you maximum mana for 15 sec.")
    :SetFinalTalent({
        Description = "Rank 2/2\nMana gained from your Mana Gem also increases your spell power by 2%% of you maximum mana for 15 sec."
    })

    tier[2] = 6

    tt:AddTalent(1, 1)
        :Name("")
        :Description("")
        :Icon("UI/Minimap/MinimapIconCreepLoc.blp")
        :SetDependency("up", 1)
        :Requirements(function() return false, "" end)
        ._isLink = true

    data.Name = "Arcane Power"
    data.Icon = "ReplaceableTextures/CommandButtons/BTNControlMagic.blp"
    tt:AddTalent(1, 0, data)
        :Description("Rank 0/1\nWhen activated, you deal 20%% more spell damage and damaging spells cost 10%% more mana to cast. This Effect lasts 15 sec.")
        :SetDependency("up", 1)
    :SetFinalTalent({ Description = "Rank 1/1\nWhen activated, you deal 20%% more spell damage and damaging spells cost 10%% more mana to cast. This Effect lasts 15 sec." })

    -- data.Name = "Improved Arcane Missiles"
    -- data.Icon = "ReplaceableTextures/CommandButtons/BTNStarfall.blp"
    -- tt:AddTalent(1, 6, data)
    --     :Description("Rank 0/2\n")
    -- :SetNextTalent(data)
    --     :Description("Rank 1/2\n\n\nNext Rank\n")
    -- :SetFinalTalent({
    --     Description = "Rank 2/2\n"
    -- })
 
    -- data.Name = "Torment the Weak"
    -- data.Icon = ""
    -- tt:AddTalent(0, 5, data)
    --     :Description("Rank 0/3\n")
    -- :SetNextTalent(data)
    --     :Description("Rank 1/3\n\n\nNext Rank\n")
    --     :SetNextTalent(data)
    --     :Description("Rank 2/3\n\n\nNext Rank\n")
    -- :SetFinalTalent({
    --     Description = "Rank 3/3\n"
    -- })

end
Lua:
TalentView = {

    -- Global number of column slots on the Talent tree
    COLUMNS = 4,
    -- Global number of row slots on the Talent tree
    ROWS = 7,
    -- Width of the Talent View box
    WIDTH = 0.3,
    -- Height of the Talent View box
    HEIGHT = 0.44,
    -- Amount of empty space between Talents and the side borders
    SIDE_MARGIN = 0.1,
    -- Amount of empty space between Talents and top/bottom border
    VERTICAL_MARGIN = 0.15,

    -- Position of the Show Talents Button
    --                        Above inventory, above command card default
    SHOW_TALENTS_X = 0.14, -- 0.53             0.74               0.14
    SHOW_TALENTS_Y = 0.182, -- 0.16             0.17               0.182

    -- Global width of the Talent icons
    TALENT_WIDTH = 0.04,
    -- Global height of the Talent icons
    TALENT_HEIGHT = 0.04,

    TOOLTIP_BOX_WIDTH = 0.28,
    TOOLTIP_BOX_HEIGHT = 0.16,

    -- Selectable talent outline scale against talent width/height
    OUTLINE_BORDER_RATIO = 0.95,

    -- Whether it's possible to remove talent points by Right Clicking
    RIGHT_CLICK_TALENT_REMOVAL = true,

    -- Default icon to display while testing, not important
    DEFAULT_TALENT_ICON = "ReplaceableTextures/CommandButtons/BTNPeasant.blp",
    -- Icon for the level counter box background on bottom left of the talent
    -- UI\Console\Human\human-transport-slot.blp
    -- UI\Widgets\Console/Human/human-transport-slot.blp
    -- UI/Widgets/Console/Human/CommandButton/human-button-lvls-overlay.blp
    COUNTER_ICON_ACTIVE = "UI/Widgets/Console/Human/human-transport-slot.blp",
    COUNTER_ICON_DISABLED = "UI/WidgetsBattleNet/chaticons/iconselection-border-disabled.blp",
 
    -- ACTIVE_LINK = 'UI/Glues/SinglePlayer/Undead3D_Exp/sunwaves.blp',
    -- UI/Widgets/Console/Human/human-inventory-slotfiller.blp
    INACTIVE_LINK = 'UI/Widgets/Console/Human/human-inventory-slotfiller.blp',

    GREEN_BORDER = 'UI/Widgets/Console/Human/CommandButton/human-activebutton.blp',

    -- Texture for the Talent Dependency link
    -- Textures\star6.blp
    INACTIVE_LINK_BOX = 'UI/Widgets/Console/Human/human-inventory-slotfiller.blp',
    -- ReplaceableTextures/WorldEditUI/Doodad-Water.blp
    -- UI/Widgets/Console/Human/CommandButton/human-subgroup-background.blp
    -- Textures/star6.blp
    -- ReplaceableTextures\TeamGlow\TeamGlow00.blp
    -- ReplaceableTextures/WorldEditUI/Doodad-Water.blp'
    -- Textures\Water00.blp
    -- UI\Glues\SinglePlayer\UndeadCampaign3D\gem.blp
    -- UI/Glues/SinglePlayer/UndeadCampaign3D/gem.blp
    ACTIVE_LINK_BOX = 'Textures/Water00.blp',

    -- ====================== Internal, do not touch =================================
    ActiveUnit = {},
    ViewedUnit = {},
    verticalLink = {},
    horizontalLink = {},
    playerRightClick = {},
    playerClickedFrame = {}
    --
}

function TalentView:OnView(owner)
    local self = TalentView
    -- local owner = GetTriggerPlayer()

    local viewedUnit = {}
    -- If there is no active or viewed unit, we break
    if (self.ViewedUnit[owner] == nil and self.ActiveUnit[owner] == nil) then
        return
    elseif (self.ViewedUnit[owner]) then
        viewedUnit = self.ViewedUnit[owner]
    else
        -- Now we are viewing the Active unit, so it becomes ViewedUnit instead.
        viewedUnit = self.ActiveUnit[owner]
        self.ViewedUnit[owner] = viewedUnit
    end
 
    if (GetLocalPlayer() == owner) then
        -- Now that we have a viewed unit, we can render/re-render the TalentView
        -- First we disable the Show Talents button
        BlzFrameSetEnable(self.Frames.viewTalentsButton, false)
        BlzFrameSetEnable(self.Frames.viewTalentsButton, true)
        BlzFrameSetVisible(self.Frames.viewTalentsButton, false)
    end

    -- Get the TalentTree from the unit
    local tt = TalentTree[viewedUnit]
    local tempState = {}
    local tempLevel = {}

    -- Check if TempTalentState exists, if not it needs to be copied
    if (tt.TempTalentState) then
        tempState = tt.TempTalentState
        tempLevel = tt.TempTalentLevel
    else
        -- Since it does not exist, it needs to be copied over
        for i = 1, self.MAX_TALENTS do
            if (tt.TalentState[i]) then
                tempState[i] = true
            else
                tempState[i] = false
            end
            if (tt.TalentLevel[i]) then tempLevel[i] = tt.TalentLevel[i] end
        end
    end

    if (GetLocalPlayer() == owner) then
        -- Hide all the links first and reset inverse Dependencies
        for i = 1, self.MAX_TALENTS do
            BlzFrameSetVisible(self.horizontalLink[i], false)
            BlzFrameSetVisible(self.verticalLink[i], false)
        end
    end

    -- Now that we have our Temporary state, either fresh or from earlier re-render,
    -- Loop through the talents and display them as necessary
    for i = 1, self.MAX_TALENTS do
        -- Fetch the Talent Frame for this index, will need it.
        local frame = TalentView.Frames.Talent[i]
        -- If talent exists, process it. If not, hide it.
        if (tt.Talents[i]) then
            local talent = tt.Talents[i]

            -- Calculate the Requirements and Dependencies
            local dep, depString, link = tt:CheckDependencies(tempState, tempLevel, i, owner)
            local req, reqString = talent:requirements(tt, viewedUnit)
            local requirements = "\n|cffff6450Requires:"..reqString.." "..depString.."|r\n"
            -- if (talent._name..talent._desc == "") then
            --     print("hide tooltip")
            --     BlzFrameSetEnable(frame.tooltipBox, false)
            -- end

            if (GetLocalPlayer() == owner) then
                -- If both dependencies and requirements passed, the button is enabled
                if (dep and req) then
                    -- print("a")
                    BlzFrameSetEnable(frame.mainButton, true)
                    BlzFrameSetText(frame.tooltipText, talent._name.."\n\n"..talent._desc)
                    BlzFrameSetVisible(frame.availableImage, true)
                else
                    -- print("b")
                    -- If either of those failed, update the tooltip with the requirements
                    BlzFrameSetEnable(frame.mainButton, false)
                    BlzFrameSetText(frame.tooltipText, talent._name.."\n"..requirements..talent._desc)
                    BlzFrameSetVisible(frame.availableImage, false)
                end

                -- If the Talent is Active and has no levels, disable it
                if (tempState[i]) then
                    -- print("c")
                    BlzFrameSetTexture(frame.mainImage, talent._icon, 0, true)
                    BlzFrameSetEnable(frame.mainButton, false)
                    BlzFrameSetVisible(frame.availableImage, false)
                    BlzFrameSetText(frame.tooltipText, talent._activeName.."\n\n"..talent._activeDesc)
                elseif (tempLevel[i] and tempLevel[i] > 0) then
                    -- print("d")
                    -- If the talent under it is Active, but there's a higher level of it
                    -- which is inactive, display it enabled, but Active
                    BlzFrameSetTexture(frame.mainImage, talent._icon, 0, true)
                    -- BlzFrameSetEnable(frame.mainButton, true)
                else
                    -- If the Talent is Inactive, set it to grey icon
                    BlzFrameSetTexture(frame.mainImage, talent._iconDisabled, 0, true)
                    -- print("e")
                end

                -- Need to update the level change in the counter as well
                if (tempLevel[i]) then
                    BlzFrameSetText(frame.counterText, tempLevel[i])
                    BlzFrameSetVisible(frame.counterText, true)
                    BlzFrameSetVisible(frame.counterImage, true)
                else
                    BlzFrameSetVisible(frame.counterText, false)
                    BlzFrameSetVisible(frame.counterImage, false)
                end

                -- Set the frame to visible
                if (not talent._isLink) then
                    BlzFrameSetVisible(frame.mainButton, true)
                else
                    BlzFrameSetVisible(frame.mainButton, false)
                end
            end
        elseif (GetLocalPlayer() == owner) then
            BlzFrameSetVisible(frame.mainButton, false)
            BlzFrameSetVisible(frame.availableImage, false)
        end
    end
 
    -- Update the global TempTalentState
    -- print("onView over "..type(tempState))
    tt.TempTalentState = tempState
    tt.TempTalentLevel = tempLevel
    if (GetLocalPlayer() == owner) then
        BlzFrameSetVisible(self.Frames.box, true)
    end
end

function TalentView:OnConfirm()
    local owner = GetTriggerPlayer()

    if (not TalentView.ViewedUnit[owner]) then return end

    local viewedUnit = TalentView.ViewedUnit[owner]
    local tt = TalentTree[viewedUnit]
    local tempState = tt.TempTalentState
    local tempLevel = tt.TempTalentLevel

    for i, v in ipairs(tempState) do
        if (tt.Talents[i]) then

            if (tempLevel[i] and tt.TalentLevel[i] ~= tempLevel[i]) then
                local talents = tt.Talents[i].levels

                for j = tt.TalentLevel[i]+1, tempLevel[i] do

                    if (tempLevel[i] > tt.TalentLevel[i]) then
                        talents[j]:onActivate(viewedUnit)
                    else
                        talents[j]:onDeactivate(viewedUnit)
                    end
                end
            elseif (tt.TalentState[i] ~= nil and (tt.TalentState[i] ~= v)) then
                if (v == true) then tt.Talents[i].onActivate(viewedUnit)
                else tt.Talents[i].onDeactivate(viewedUnit) end
            end
            tt.TalentLevel[i] = tempLevel[i]
            tt.TalentState[i] = v
        end
    end
    -- print("b")

    tt.TempTalentState = nil

    if (GetLocalPlayer() == owner) then
        BlzFrameSetVisible(TalentView.Frames.box, false)
        BlzFrameSetVisible(TalentView.Frames.viewTalentsButton, true)
    end
end

function TalentView:OnCancel()
    local owner = GetTriggerPlayer()

    if (not TalentView.ViewedUnit[owner]) then return end
 
    local viewedUnit = TalentView.ViewedUnit[owner]
    local tt = TalentTree[viewedUnit]

    -- Loop through talent choices and reduce their level to previous
    for i = 1, TalentView.MAX_TALENTS do
        if (tt.Talents[i]) then
            if (tt.TalentLevel[i] ~= tt.TempTalentLevel[i]) then
                for j = tt.TempTalentLevel[i], tt.TalentLevel[i] +1, -1 do
                    local t = tt.Talents[i].levels[j]
                    tt.Talents[i] = t
                    if (tt.Dependency[i]) then
                        tt.Dependency[i] = t.dependency
                    end
                    if (t.onPointRemoved) then t:onPointRemoved(tt, viewedUnit) end
                end
            end
        end
    end

    -- Reset the talent state
    tt.TempTalentState = nil

    if (GetLocalPlayer() == owner) then
        BlzFrameSetVisible(TalentView.Frames.box, false)
        BlzFrameSetVisible(TalentView.Frames.viewTalentsButton, true)

        BlzFrameSetEnable(self.Frames.cancelButton, false)
        BlzFrameSetEnable(self.Frames.cancelButton, true)
    end
end

function TalentView:OnClicked(i, owner)

    local self = TalentView
 
    local viewedUnit = {}
    -- If there is no active or viewed unit, we break
    if (self.ViewedUnit[owner] == nil) then return end
 
    viewedUnit = self.ViewedUnit[owner]
 
    local tt = TalentTree[viewedUnit]
    local state = {}

    -- If we clicked on the Talent whose state was false, change it into true
    if (tt.TempTalentState[i] == false) then
        -- print("start")
        local ttl = tt.TempTalentLevel
        local talent = tt.Talents[i]
        -- print("before setinverse dep: "..tt.Talents[i]._name)

        tt.TempTalentState[i] = true
        if (ttl[i]) then ttl[i] = ttl[i] + 1 end

        tt.BaseDependency[i] = talent.dependency
        -- if (talent.requirements) then
        --     table.insert(tt.RequiredTalents, talent)
        --     print("insert", #tt.RequiredTalents)
        -- end

        -- On event added point
        if (talent.onPointAdded) then talent:onPointAdded(tt, viewedUnit) end

        if (talent.nextTalent) then
            talent = talent:onNext()
        end

        TalentView:OnView(owner)
    end
end

function TalentView:OnRightClick(i, owner)
 
    local self = TalentView
 
    local viewedUnit = {}
    -- If there is no active or viewed unit, we break
    if (self.ViewedUnit[owner] == nil) then return end
    viewedUnit = self.ViewedUnit[owner]
 
    local tt = TalentTree[viewedUnit]

    -- If the temporary level is higher than the actual level, we can continue
    if (tt.TalentLevel[i] < tt.TempTalentLevel[i]) then
    
        local talent = tt.Talents[i]
        local state = tt.TempTalentState[i]

        -- Check if some other talent requires this one. If it exists and
        -- if level is higher than its required level, it's okay to remove it.
        if (tt:CheckBaseDependency(tt.TempTalentLevel[i]-1, i)) then

            if (talent:removeRequirements(tt, viewedUnit)) then
                -- If this talent is the last in the stack, just set its state to false
                if (state == true) then
                    state = false
                    tt.TempTalentState[i] = false
                else
                    -- If it wasn't last but its temporary level is higher than its actual,
                    -- That means that it's not the First talent. We can safely replace it with lower
                    local nextLvl = tt.TempTalentLevel[i]
                    if (nextLvl > 2) then nextLvl = nextLvl - 1 end

                    talent = tt.Talents[i].levels[nextLvl]
                    -- We need to apply the new base dependency to the surroundings.
                end
                tt.Talents[i] = talent
                tt.BaseDependency[i] = talent.baseDependency
                if (tt.TempTalentLevel[i]) then tt.TempTalentLevel[i] = tt.TempTalentLevel[i] - 1 end
                if (tt.Talents[i].onPointRemoved) then tt.Talents[i]:onPointRemoved(tt, viewedUnit) end

                -- ========= Experimental feature, reverse Requirement checking =========
                -- local talentReqId = nil
                -- -- Test if removing this talent would result in violation of Requirements
                -- for j, t in ipairs(tt.RequiredTalents) do
                
                --     -- If the talent we decreased is one of the Required ones, save its reference
                --     print("j"..j, "tid"..t.id, "currId"..oldTalent.id, t._name)
                --     if (t.id and t.id == oldTalent.id) then
                --         print("REQ ID")
                --         talentReqId = j
                --     end

                --     -- If it violates the requirements, return the state as it was before.
                --     if (t:requirements(tt, viewedUnit, true) == false) then
                --         print("violated")
                --         TalentView:OnClicked(i, owner)
                --         return;
                --     end
                -- end
                -- print("removed")
                -- -- If it didn't violate the requirement,
                -- -- we remove the talent from the Requirements
                -- if (talentReqId) then
                --     print("req rem", talentReqId)
                --     table.remove(tt.RequiredTalents, talentReqId)
                -- end
            end
        end
    end
    self:OnView(owner)
end

function TalentFrameInitialize(i)
 
    local self = TalentView
    local tf = { Frames = {} }

    tf.clickTrigger = CreateTrigger()

    -- print("i: "..(i+1))
    if (math.fmod(i, self.COLUMNS) < 3) then
        tf.horizontalLink = BlzCreateFrameByType("BACKDROP", "HorizontalLink", self.Frames.box, "", 0)
        self.horizontalLink[i+1] = tf.horizontalLink
    end
    if (i < (self.MAX_TALENTS - self.COLUMNS)) then
        tf.verticalLink = BlzCreateFrameByType("BACKDROP", "VerticalLink", self.Frames.box, "", 0)
        self.verticalLink[i+1] = tf.verticalLink
    end

    tf.availableImage = BlzCreateFrameByType("BACKDROP", "AvailableImg", self.Frames.box, "", 0)
    tf.mainButton = BlzCreateFrame("ScoreScreenBottomButtonTemplate", self.Frames.box, 0, 0)
    tf.mainImage = BlzGetFrameByName("ScoreScreenButtonBackdrop", 0)
    tf.tooltipBox = BlzCreateFrame("ListBoxWar3", tf.mainButton, 0, 0)
    tf.tooltipText = BlzCreateFrameByType("TEXT", "StandardInfoTextTemplate", tf.tooltipBox, "StandardInfoTextTemplate", 0)
    tf.counterImage = BlzCreateFrameByType("BACKDROP", "Counter", tf.mainButton, "", 0)
    tf.counterText = BlzCreateFrameByType("TEXT", "FaceFrameTooltip", tf.mainButton, "", 0)

    -- BlzFrameSetParent(tf.availableImage, tf.mainButton)
    -- BlzFrameSetLevel(tf.mainImage, 1)
    -- BlzFrameSetLevel(tf.counterImage, 2)
    -- BlzFrameSetLevel(tf.availableImage, 0)
    BlzFrameSetTooltip(tf.mainButton, tf.tooltipBox)
    BlzFrameSetTextAlignment(tf.counterText, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_MIDDLE)

    local xPos = math.floor(math.fmod(i, self.COLUMNS))
    local yPos = math.floor((i) / self.COLUMNS)

    local xIncrem = (self.WIDTH*(1-self.SIDE_MARGIN)) / (self.COLUMNS + 1)
    local yIncrem = (self.HEIGHT*(1-self.VERTICAL_MARGIN)) / (self.ROWS + 1)
    local xOffset = xPos * xIncrem - ((self.COLUMNS-1) * 0.5) * xIncrem
    --local yOffset = self.HEIGHT*(0.1+self.VERTICAL_MARGIN) + yPos * ((self.HEIGHT*(1-self.VERTICAL_MARGIN)) / self.ROWS)
    local yOffset = yPos * yIncrem - ((self.ROWS-1) * 0.5) * yIncrem

    local Config = {
        --mainButton = { point = true, pos = { fp = FRAMEPOINT_CENTER, frame = self.Frames.box, fp2 = FRAMEPOINT_BOTTOM, x = xOffset, y = yOffset }, size = { x = self.TALENT_WIDTH, y = self.TALENT_HEIGHT }},
        mainButton = { point = true, pos = { fp = FRAMEPOINT_CENTER, frame = self.Frames.box, fp2 = FRAMEPOINT_CENTER, x = xOffset, y = yOffset }, size = { x = self.TALENT_WIDTH, y = self.TALENT_HEIGHT }},
        tooltipBox = { point = true, pos = { fp = FRAMEPOINT_TOPLEFT, frame = self.Frames.box, fp2 = FRAMEPOINT_TOPRIGHT, x = 0.0, y = 0.0 }, size = { x = self.TOOLTIP_BOX_WIDTH, y = self.TOOLTIP_BOX_HEIGHT }},
        tooltipText = { clear = true, point = true, pos = { fp = FRAMEPOINT_CENTER, frame = tf.tooltipBox, fp2 = FRAMEPOINT_CENTER, x = 0.0, y = 0.0 }, size = { x = self.TOOLTIP_BOX_WIDTH-0.03, y = self.TOOLTIP_BOX_HEIGHT-0.03 }, text = "Default talent name \n\nDefault talent description"},
        counterImage = { point = true, pos = { fp = FRAMEPOINT_BOTTOMRIGHT, frame = tf.mainButton, fp2 = FRAMEPOINT_BOTTOMRIGHT, x = -0.0006, y = 0.0015 }, size = { x = 0.014, y = 0.014 }, texture = self.COUNTER_ICON_ACTIVE },
        counterText = { clear = true, point = true, pos = { fp = FRAMEPOINT_CENTER, frame = tf.counterImage, fp2 = FRAMEPOINT_CENTER, x = 0, y = 0}, size = { x = 0.01, y = 0.012 }, text = "0"},
        verticalLink = { point = true, pos = { fp = FRAMEPOINT_BOTTOM, frame = self.Frames.box, fp2 = FRAMEPOINT_CENTER, x = xOffset, y = yOffset }, size = { x = self.TALENT_WIDTH*0.10, y = yIncrem }, texture = self.INACTIVE_LINK_BOX },
        horizontalLink = { point = true, pos = { fp = FRAMEPOINT_LEFT, frame = self.Frames.box, fp2 = FRAMEPOINT_CENTER, x = xOffset, y = yOffset }, size = { x = xIncrem, y = self.TALENT_HEIGHT*0.10 }, texture = self.INACTIVE_LINK_BOX },
        availableImage = { point = true, pos = { fp = FRAMEPOINT_CENTER, frame = tf.mainButton, fp2 = FRAMEPOINT_CENTER, x = 0, y = 0 }, size = { x = self.TALENT_WIDTH*self.OUTLINE_BORDER_RATIO, y = self.TALENT_HEIGHT*self.OUTLINE_BORDER_RATIO }, texture = self.GREEN_BORDER },
        mainImage = { texture = self.DEFAULT_TALENT_ICON }
    }

    ConfigFrames(Config, tf)

    tf.onClick = function()
        local frame = BlzGetTriggerFrame()
        BlzFrameSetEnable(frame, false)
        BlzFrameSetEnable(frame, true)
        TalentView:OnClicked(i+1, GetTriggerPlayer())
    end

    TriggerAddAction(tf.clickTrigger, tf.onClick)
    BlzTriggerRegisterFrameEvent(tf.clickTrigger, tf.mainButton, FRAMEEVENT_CONTROL_CLICK)

    if (TalentView.RIGHT_CLICK_TALENT_REMOVAL) then
        tf.mouseUpTrigger = CreateTrigger()
        TriggerAddAction(tf.mouseUpTrigger, function()
            local owner = GetTriggerPlayer()
            if (TalentView.playerRightClick[owner] == true) then
                TalentView:OnRightClick(i+1, owner)
            end
        end)
        BlzTriggerRegisterFrameEvent(tf.mouseUpTrigger, tf.mainButton, FRAMEEVENT_MOUSE_UP)
    end

    return tf
end

function TalentView:Initialize()

    self.Frames = {}
    self.Config = {}
    self.MAX_TALENTS = self.COLUMNS * self.ROWS
    -- Create and setup the Talent View dialog box
    self.Frames.box  = BlzCreateFrame("SuspendDialog", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0), 0,0)
    self.Config.box = { clear = true, abs = true, pos = { x = 0.35, y = 0.34, fp = FRAMEPOINT_CENTER }, size = { x = self.WIDTH, y = self.HEIGHT }}
    self.Frames.boxTitle = BlzGetFrameByName("SuspendTitleText",0)
    self.Config.boxTitle = { text = "Talent Tree" }

    -- Create and set up the Talent Save/Confirmation Button Frame
    self.Frames.confirmButton  = BlzGetFrameByName("SuspendDropPlayersButton",0)
    self.Config.confirmButton = { clear = true, point = true, pos = { x = 0.0, y = 0.02, frame = self.Frames.box, fp = FRAMEPOINT_BOTTOMRIGHT, fp2 = FRAMEPOINT_BOTTOM }, size = { x = 0.12, y = 0.03 }}
    self.Frames.confirmText = BlzGetFrameByName("SuspendDropPlayersButtonText",0)
    self.Config.confirmText = { text = "Confirm" }

    -- Create and setup the Talent Cancel/Close view Button Frame
    self.Frames.cancelButton = BlzCreateFrame("ScriptDialogButton", self.Frames.box, 0,0)
    self.Config.cancelButton = { clear = true, point = true, pos = { x = 0.0, y = 0.02, frame = self.Frames.box, fp = FRAMEPOINT_BOTTOMLEFT, fp2 = FRAMEPOINT_BOTTOM }, size = { x = 0.12, y = 0.03 }}
    self.Frames.cancelText = BlzGetFrameByName("ScriptDialogButtonText",0)
    self.Config.cancelText = { text = "Cancel" }

    -- Create and setup the Show Talents Button Frame
    self.Frames.viewTalentsButton = BlzCreateFrame("ScriptDialogButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0), 0, 0)
    self.Config.viewTalentsButton = { clear = true, abs = true, pos = { x = self.SHOW_TALENTS_X, y = self.SHOW_TALENTS_Y, fp = FRAMEPOINT_CENTER }, size = { x = 0.11, y = 0.035 }}
    self.Frames.viewTalentsText = BlzGetFrameByName("ScriptDialogButtonText",0)
    self.Config.viewTalentsText = { text = "Show Talents" }

    -- Apply the config to all created frames.
    ConfigFrames(self.Config, self.Frames)

    -- Create the talent frames
    self.Frames.Talent = {}
    for i = 0, self.COLUMNS * self.ROWS - 1 do
        table.insert(self.Frames.Talent, TalentFrameInitialize(i))
    end

    -- Create the needed triggers for the buttons
    self.confirmTrigger = CreateTrigger()
    self.cancelTrigger = CreateTrigger()
    self.viewTrigger = CreateTrigger()

    -- Add the trigger Actions
    TriggerAddAction(self.confirmTrigger, self.OnConfirm)
    TriggerAddAction(self.cancelTrigger, self.OnCancel)
    TriggerAddAction(self.viewTrigger, function() TalentView:OnView(GetTriggerPlayer()) end)

    -- Register the event on the frames
    BlzTriggerRegisterFrameEvent(self.confirmTrigger, self.Frames.confirmButton, FRAMEEVENT_CONTROL_CLICK)
    BlzTriggerRegisterFrameEvent(self.cancelTrigger, self.Frames.cancelButton, FRAMEEVENT_CONTROL_CLICK)
    BlzTriggerRegisterFrameEvent(self.viewTrigger, self.Frames.viewTalentsButton, FRAMEEVENT_CONTROL_CLICK)

    if (self.RIGHT_CLICK_TALENT_REMOVAL) then
        self.setRightClickDownTrigger = CreateTrigger()
        self.unsetRightClickUpTrigger = CreateTrigger()
        TriggerRegisterPlayerMouseEventBJ(self.setRightClickDownTrigger, Player(0), bj_MOUSEEVENTTYPE_DOWN)
        TriggerRegisterPlayerMouseEventBJ(self.unsetRightClickUpTrigger, Player(0), bj_MOUSEEVENTTYPE_UP)
        TriggerAddAction(self.setRightClickDownTrigger, function()
            if (BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_RIGHT) then
                TalentView.playerRightClick[GetTriggerPlayer()] = true
            end
        end)
        TriggerAddAction(self.unsetRightClickUpTrigger, function()
            local player = GetTriggerPlayer()
            TimerStart(NewTimer(), 0.1, false, function() ReleaseTimer(GetExpiredTimer()) TalentView.playerRightClick[player] = false end)
        end)
    end

    BlzFrameSetVisible(self.Frames.box, false)
end

function ConfigFrames(configs, frames)
    for k,v in pairs(configs) do
        if (v.clear) then BlzFrameClearAllPoints(frames[k]) end
        if (v.size) then BlzFrameSetSize(frames[k], v.size.x, v.size.y) end
        if (v.abs) then BlzFrameSetAbsPoint(frames[k], v.pos.fp, v.pos.x, v.pos.y)
        elseif (v.point) then BlzFrameSetPoint(frames[k], v.pos.fp, v.pos.frame, v.pos.fp2, v.pos.x, v.pos.y) end
        if (v.text) then BlzFrameSetText(frames[k], v.text) end
        if (v.texture) then BlzFrameSetTexture(frames[k], v.texture, 0, true) end
    end
end

onGlobalInit(function()
    TimerStart(NewTimer(), 0.5, false, function() Test() end)
end)

-- THIS IS FOR TEST ONLY
function Test()
    TalentView:Initialize()
    TalentView.ActiveUnit[Player(0)] = gg_unit_Hblm_0003
    -- TalentView.ActiveUnit[Player(1)] = gg_unit_Hblm_0008

    local tt = CreateArcaneMageTalentTree(gg_unit_Hblm_0003)
    -- local tt2 = CreateTestTalentTree(gg_unit_Hblm_0008)
end
 

Attachments

  • upload_2019-7-21_1-40-47.png
    upload_2019-7-21_1-40-47.png
    1.3 MB · Views: 526
  • TalentViewSystem_0.2.w3x
    49.7 KB · Views: 225
Last edited:
Thanks so much for taking time to check it out!

Oh yeah there's tons of bugs, I just wanted to make it look better.
In any case, I think the removal dependency is now fully working.

Cool System, but there is currently a bug. When one selets many Talents and then uses the cancel button, one can select higher Tier Talents without spending Points in lower ones. Maybe that is only the demo tree, didnt looked so much into it.
Yeah, that was experimental, but it appears wow talent algorithm isn't so simple. In any case, the problem there was cancel not calling "onPointRemoved" for each level of the talent that gets dropped from temporary. For example, level 3 Torment the Weak and you cancel, level of Torment the Weak basically gets set to the original state one (0) and "onPointRemoved" only gets called once. Thus not decreasing the total points by 3, but by 1.
Also, you are majority of reason that I managed to do this so big thanks! I hope I can publish this.

There was a mess with Dependency (the links) so I kind of got a better structure down. I think it works now, if it doesn't please tell.

I tried to replicate the removal algorithm in past day or two but I gave up for now. If someone can nail it down, I'd appreciate if you share and I'd stick it in the map example.
 
Last edited:
Status
Not open for further replies.
Top