1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[UI] Lua (Wow) Talent system

Discussion in 'The Lab' started by The_Spellweaver, Jul 21, 2019.

Tags:
  1. The_Spellweaver

    The_Spellweaver

    Joined:
    Feb 20, 2013
    Messages:
    95
    Resources:
    2
    Models:
    2
    Resources:
    2
    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

    Code (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
     
    Code (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
     
    Code (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
     
     

    Attached Files:

    Last edited: Jul 22, 2019
  2. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,036
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    You should use "code=Lua" when using wraps around the Lua script.
     
  3. The_Spellweaver

    The_Spellweaver

    Joined:
    Feb 20, 2013
    Messages:
    95
    Resources:
    2
    Models:
    2
    Resources:
    2
    Whoa thanks, updated.
     
  4. Tasyen

    Tasyen

    Joined:
    Jul 18, 2010
    Messages:
    1,300
    Resources:
    16
    Tools:
    2
    Maps:
    2
    Spells:
    7
    Tutorials:
    4
    JASS:
    1
    Resources:
    16
    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.
     
  5. FeelsGoodMan

    FeelsGoodMan

    Joined:
    Dec 13, 2018
    Messages:
    463
    Resources:
    1
    Maps:
    1
    Resources:
    1
  6. The_Spellweaver

    The_Spellweaver

    Joined:
    Feb 20, 2013
    Messages:
    95
    Resources:
    2
    Models:
    2
    Resources:
    2
    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.

    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: Jul 22, 2019