1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Head to the 33rd Modeling Contest Poll and drink to your heart's desire.
    Dismiss Notice
  3. Choose your means of doom in the 17th Mini Mapping Contest Poll.
    Dismiss Notice
  4. A slave to two rhythms, the 22nd Terraining Contest is here.
    Dismiss Notice
  5. The heavens smile on the old faithful. The 16th Techtree Contest has begun.
    Dismiss Notice
  6. The die is cast - the 6th Melee Mapping Contest results have been announced. Onward to the Hive Cup!
    Dismiss Notice
  7. The glory of the 20th Icon Contest is yours for the taking!
    Dismiss Notice
  8. 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.

Cooldown Reduction v1.6

Submitted by chopinski
This bundle is marked as approved. It works and satisfies the submission rules.
Cooldown Reduction v1.6
Intro
This library intension in to introduce to Warcraft an easy way to manipulate abilities cooldowns based on a cooldown reduction value that is unique for each unit.
How it Works?
When casting an ability, its "new" cooldown is calculated based on the amount of cooldown reduction of the casting unit. the formula for calculation is:
Cooldown = (Default Cooldown - Cooldown Offset) * [(1 - source1)*(1 - source2)*...] * (1 - Cooldown Reduction Flat)

The system also allow negative values for CDR, resulting in increased ability cooldown.

It does not accumulate because the abilities are registered automatically on the first cast, saving its base cooldown (Object Editor values) and always using this base value for calculation, so you can still edit the ability via the editor and the system takes care of the rest.


How to Import?
Simply copy the Cooldown Reduction folder over to your map, and start using the API functions.
Requirements
Cooldown Reduction requires RegisterPlayerUnitEvent and Unit Indexer . Credits to Magtheridon96 for RegisterPlayerUnitEvent and to Bribe for the Unit Indexer. It also requires patch 1.31+.
JASS API
Code (vJASS):

            /* --------------------------------- Getters -------------------------------- */
            function GetUnitCooldownReduction takes unit u returns real
                -> Returns the amount of cdr a unit has (this bonuses stacks multiplicatively)
                -> 0.1 = 10%
 
            function GetUnitCooldownReductionFlat takes unit u returns real
                -> Returns the amount of cdr flat a unit has (this bonuses stacks additively)
                -> 0.1 = 10%
 
            function GetUnitCooldownOffset takes unit u returns real
                -> Returns the amount of cdr offset a unit has (this bonuses stacks additively)
                -> 3.0 = 3 seconds is taken from default cooldown before cdr calculation
 
            /* --------------------------------- Setters -------------------------------- */
            function SetUnitCooldownReduction takes unit u, real value returns nothing
                -> Set the amount of cdr for a unit (stacks multiplicatively)
 
            function SetUnitCooldownReductionFlat takes unit u, real value returns nothing
                -> Set the amount of cdr flat for a unit (stacks additively)
 
            function SetUnitCooldownOffset takes unit u, real value returns nothing
                -> Set the amount of cdr offset for a unit (stacks additively)

            function CalculateAbilityCooldown takes unit u, integer id, integer lvl, real cd returns nothing
                -> Main funciton used to calculate ability coodown. It is used internaly by default and can
                -> be called by itself to set custom cooldowns that take advantage of cooldown reduction

            /* -------------------------------- Aritmetic ------------------------------- */
            function UnitAddCooldownReduction takes unit u, real value returns nothing
                -> Add to the amount of cdr of a unit. Accepts positive and negative values
                -> Adding a negative with the intent of removing a positive bonus will not work
                -> for that use/read UnitRemoveCooldownReduction().
 
            function UnitAddCooldownReductionFlat takes unit u, real value returns nothing
                -> Add to the amount of cdr flat of a unit. Accepts positive and negative values
 
            function UnitAddCooldownOffset takes unit u, real value returns nothing
                -> Add to the amount of cdr offset of a unit. Accepts positive and negative values
 
            function UnitRemoveCooldownReduction takes unit u, real value returns nothing
                -> Use this function to remove a default cdr bonus from a unit. Additions of default cdr are paired,
                -> meaning that if you added 0.5 and are trying to remove 0.4, it will not work.
 
            /* -------------------------------- Utilities ------------------------------- */
            function RegisterAbility takes ability abil returns nothing
                -> Manually register an ability into the system. This is very usefull if you have abilities with
                -> varying cooldown that are triggered. Right After changing its cooldown, call this function to
                -> re-register the default cooldowns of the ability.
   
            function GetUnitAbilityCooldown takes unit u, integer abilityId, integer level returns real
                -> Returns the calculated cooldown for an ability
 

Code (vJASS):

            /* ------------------------------- Timed Bonus ------------------------------ */
            function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
                -> Add to the amount of cdr of a unit for a given duration. Accepts positive and negative values.
                -> It handles removing the bonus automatically
 
            function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
                -> Add to the amount of cdr flat of a unit for a given period. Accepts positive and negative values.
                -> It handles removing the bonus automatically
 
            function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
                -> Add to the amount of cdr offset of a unit for a given period. Accepts positive and negative values.
                -> It handles removing the bonus automatically
   
            /* ----------------------------- String Previews ---------------------------- */
            function GetUnitAbilityCooldownEx takes unit u, integer abilityId, integer level returns string
                -> Returns the calculated cooldown for an ability as a string
 
            function GetUnitCooldownReductionEx takes unit u returns string
                -> Returns the amount of cdr a unit has as a string factored by 100
                -> example of return: 10.50 -> 0.105 internally.
 
            function GetUnitCooldownReductionFlatEx takes unit u returns string
                -> Returns the amount of cdr flat a unit has as a string factored by 100
                -> example of return: 10.50 -> 0.105 internally.
 
            function GetUnitCooldownOffsetEx takes unit u returns string
                -> Returns the amount of cdr offset a unit has as a string
 
            /* ------------------------------ Maybe Usefull ----------------------------- */
            function GetAbilityTable takes nothing returns hashtable
                -> Returns the hashtable that holds the units default cooldown reduction values.
                -> Use with caution! you might break stuff
 

Code
Code (vJASS):

library CooldownReduction requires RegisterPlayerUnitEvent
    /* ------------------ Cooldown Reduction v1.4 by Chopinski ------------------ */
    //! novjass
        Intro
            This library intension in to introduce to warcraft an easy way to
            manipulate abilities cooldowns based on a cooldown reduction value that
            is unique for each unit.
 
        How it Works?
            When casting an ability, its "new" cooldown is calculated based on the
            amount of cooldown reduction of the casting unit. the formula for
            calculation is:
                Cooldown = (Default Cooldown - Cooldown Offset) * [(1 - source1)*(1 - source2)*...] * (1 - Cooldown Reduction Flat)
 
            The system also allow negative values for CDR, resulting in increased
            ability cooldown.
 
            It does not acumulate because the abilities are registered automatically
            on the first cast, saving its base cooldown (Object Editor values) and
            always using this base value for calculation, so you can still edit
            the ability via the editor and the system takes care of the rest.
 
            Of course you can manually register the abilities, but why do the work
            yourself?
 
        How to Import
            simply copy the CooldownReduction folder over to your map, and start
            use the API functions
 
        Requirements
            CooldownReduction requires RegisterPlayerUnitEvent and  a Unit Indexer.
            Credits to Magtheridon96 for RegisterPlayerUnitEvent and to Bribe for
            the UnitIndexer that i chose to use in the test map, but any Unit Indexer
            will do the job. It also requires patch 1.30+.
 
        JASS API
            /* --------------------------------- Getters -------------------------------- */
            function GetUnitCooldownReduction takes unit u returns real
                -> Returns the amount of cdr a unit has (this bonuses stacks multiplicatively)
                -> 0.1 = 10%
     
            function GetUnitCooldownReductionFlat takes unit u returns real
                -> Returns the amount of cdr flat a unit has (this bonuses stacks additively)
                -> 0.1 = 10%
     
            function GetUnitCooldownOffset takes unit u returns real
                -> Returns the amount of cdr offset a unit has (this bonuses stacks additively)
                -> 3.0 = 3 seconds is taken from default cooldown before cdr calculation
     
            /* --------------------------------- Setters -------------------------------- */
            function SetUnitCooldownReduction takes unit u, real value returns nothing
                -> Set the amount of cdr for a unit (stacks multiplicatively)
     
            function SetUnitCooldownReductionFlat takes unit u, real value returns nothing
                -> Set the amount of cdr flat for a unit (stacks additively)
     
            function SetUnitCooldownOffset takes unit u, real value returns nothing
                -> Set the amount of cdr offset for a unit (stacks additively)

            function CalculateAbilityCooldown takes unit u, integer id, integer lvl, real cd returns nothing
                -> Main funciton used to calculate ability coodown. It is used internaly by default and can
                -> be called by itself to set custom cooldowns that take advantage of cooldown reduction

            /* -------------------------------- Aritmetic ------------------------------- */
            function UnitAddCooldownReduction takes unit u, real value returns nothing
                -> Add to the amount of cdr of a unit. Accepts positive and negative values
                -> Adding a negative with the intent of removing a positive bonus will not work
                -> for that use/read UnitRemoveCooldownReduction().
     
            function UnitAddCooldownReductionFlat takes unit u, real value returns nothing
                -> Add to the amount of cdr flat of a unit. Accepts positive and negative values
     
            function UnitAddCooldownOffset takes unit u, real value returns nothing
                -> Add to the amount of cdr offset of a unit. Accepts positive and negative values
     
            function UnitRemoveCooldownReduction takes unit u, real value returns nothing
                -> Use this function to remove a default cdr bonus from a unit. Additions of default cdr are paired,
                -> meaning that if you added 0.5 and are trying to remove 0.4, it will not work.
     
            /* -------------------------------- Utilities ------------------------------- */
            function RegisterAbility takes ability abil returns nothing
                -> Manually register an ability into the system. This is very usefull if you have abilities with
                -> varying cooldown that are triggered. Right After changing its cooldown, call this function to
                -> re-register the default cooldowns of the ability.
         
            function GetUnitAbilityCooldown takes unit u, integer abilityId, integer level returns real
                -> Returns the calculated cooldown for an ability
    //! endnovjass
    /* ----------------------------------- END ---------------------------------- */
        struct CDR
            readonly static hashtable Ability_Table = InitHashtable()
            //----------------------------------------------
            readonly static real    array CooldownReduction
            readonly static real    array CooldownReductionFlat
            readonly static real    array CooldownOffset
            readonly static integer array BonusCount
            private  static integer array LastCount
            private  static real    array LastFlat
            private  static real    array LastOffset
            //----------------------------------------------
 
            // Getters and Setters
            static method Get takes unit u returns real
                return CooldownReduction[GetUnitUserData(u)]
            endmethod
 
            static method GetFlat takes unit u returns real
                return CooldownReductionFlat[GetUnitUserData(u)]
            endmethod
 
            static method GetOffset takes unit u returns real
                return CooldownOffset[GetUnitUserData(u)]
            endmethod
 
            static method Set takes unit u, real value returns nothing
                set CooldownReduction[GetUnitUserData(u)] = value
            endmethod
 
            static method SetFlat takes unit u, real value returns nothing
                local integer idx = GetUnitUserData(u)

                set LastFlat[idx] = CooldownReductionFlat[idx]
                set CooldownReductionFlat[idx] = value
            endmethod
 
            static method SetOffset takes unit u, real value returns nothing
                local integer idx = GetUnitUserData(u)

                set LastOffset[idx] = CooldownOffset[idx]
                set CooldownOffset[idx] = value
            endmethod
 
            //Calculates an unit cooldown reduction for the default type (stacks multiplicatively)
            private static method Calculate takes unit u returns real
                local integer idx = GetUnitUserData(u)
                local integer id  = GetHandleId(u)
                local integer i   = 0
                //-------------------------------------------------------------------
                local real    cdr = 0
                local real    aux
                //-------------------------------------------------------------------
 
                loop
                    exitwhen i > BonusCount[idx]
                        set aux = LoadReal(Ability_Table, id, i)
                     
                        if i > 0 then
                            set cdr = cdr * (1 - aux)
                        else
                            set cdr = 1 - aux
                        endif
                    set i = i + 1
                endloop
 
                return cdr
            endmethod
 
            // Added in version 1.1: Changed the default formula to stack multiplicatively instead of additively.
            // Add inserts in the unit list of bonuses and Remove, well, removes it.
            static method Add takes unit u, real amount returns nothing
                local integer idx = GetUnitUserData(u)
                local integer id  = GetHandleId(u)
                //-------------------------------------------------------------------
 
                if amount != 0 then
                    call SaveReal(Ability_Table, id, BonusCount[idx], amount)
                    set CooldownReduction[idx] = Calculate(u)
                    set BonusCount[idx] = BonusCount[idx] + 1
                    set LastCount[idx] = BonusCount[idx]
                endif
            endmethod
 
            //Removes are paired with additions. If trying to remove a value that was not
            //added first, than it should fail.
            //Since its a multiplication, the order of operations do not alter the result,
            //so to save performance, when removing a bonus, the last bonus registered is
            // moved to the position of the bonus that was just removed and the loop breks.
            static method Remove takes unit u, real amount returns nothing
                local integer idx = GetUnitUserData(u)
                local integer id  = GetHandleId(u)
                local integer i   = 0
                //--------------------------------------------------------
                local real    aux
                //--------------------------------------------------------
 
                if amount == 0 then
                    return
                endif

                loop
                    exitwhen i > BonusCount[idx] - 1
                        set aux = LoadReal(Ability_Table, id, i)
                     
                        if aux == amount then
                            call RemoveSavedReal(Ability_Table, id, i)
                            if i != BonusCount[idx] - 1 then
                                set aux = LoadReal(Ability_Table, id, BonusCount[idx] - 1)
                                call SaveReal(Ability_Table, id, i, aux)
                                call RemoveSavedReal(Ability_Table, id, BonusCount[idx] - 1)
                            endif
                            set BonusCount[idx] = BonusCount[idx] - 1
                            set CooldownReduction[idx] = Calculate(u)
                            set i = BonusCount[idx] + 1
                        else
                            set i = i + 1
                        endif
                endloop
            endmethod
 
            private static method GetDefaultCooldown takes ability abil, integer level returns real
                return LoadReal(Ability_Table, GetHandleId(abil), level)
            endmethod
 
            static method RegisterDefaultCooldown takes ability abil returns nothing
                local integer i          = 0
                local integer levels     = BlzGetAbilityIntegerField(abil, ABILITY_IF_LEVELS)
                //--------------------------------------------
 
                call SaveBoolean(Ability_Table, GetHandleId(abil), 0, true)
                loop
                    exitwhen i >= levels
                        call SaveReal(Ability_Table, GetHandleId(abil), (i + 1), BlzGetAbilityRealLevelField(abil, ABILITY_RLF_COOLDOWN, i))
                    set i = i + 1
                endloop
            endmethod
 
            static method GetNewCooldown takes unit u, integer id, integer level returns real
                local integer idx        = GetUnitUserData(u)
                //---------------------------------------------------------------------------
                local ability abil       = BlzGetUnitAbility(u, id)
                //---------------------------------------------------------------------------
                local boolean registered = LoadBoolean(Ability_Table, GetHandleId(abil), 0)
                //---------------------------------------------------------------------------
                local real    cooldown
                //---------------------------------------------------------------------------
 
                if not registered then
                    call RegisterDefaultCooldown(abil)
                endif
 
                if BonusCount[idx] > 0 then
                    set cooldown = ((GetDefaultCooldown(abil, level) - CooldownOffset[idx]) * CooldownReduction[idx] * (1 - CooldownReductionFlat[idx]))
                else
                    set cooldown = ((GetDefaultCooldown(abil, level) - CooldownOffset[idx]) * (1 - CooldownReductionFlat[idx]))
                endif
             
                set abil = null
                return cooldown
            endmethod

            static method CalculateCooldown takes unit u, integer id, integer lvl, real cd, boolean manual returns nothing
                local ability abi = BlzGetUnitAbility(u, id)
                //----------------------------------------
                local integer idx = GetUnitUserData(u)
                //----------------------------------------
                local real    cdr = cd
                //----------------------------------------

                if manual then
                    if BonusCount[idx] > 0 or GetFlat(u) != 0 or GetOffset(u) != 0 then
                        if BonusCount[idx] > 0 then
                            set cdr = ((cd - CooldownOffset[idx]) * CooldownReduction[idx] * (1 - CooldownReductionFlat[idx]))
                        else
                            set cdr = ((cd - CooldownOffset[idx]) * (1 - CooldownReductionFlat[idx]))
                        endif
                    endif
                endif

                call BlzSetAbilityRealLevelField(abi, ABILITY_RLF_COOLDOWN, lvl - 1, cdr)
                call IncUnitAbilityLevel(u, id)
                call DecUnitAbilityLevel(u, id)

                set abi = null
            endmethod
 
            private static method OnCast takes nothing returns nothing
                local unit    src = GetTriggerUnit()
                //--------------------------------------------
                local integer cod = GetSpellAbilityId()
                local integer lvl = GetUnitAbilityLevel(src, cod)
                local integer idx = GetUnitUserData(src)
                //--------------------------------------------
     
                // This condition is for preventing the system to run for units with no cooldown reduction of any kind
                if LastCount[idx] > 0 or LastFlat[idx] != 0 or LastOffset[idx] != 0 or GetFlat(src) != 0 or GetOffset(src) != 0 then
                    set LastCount[idx]  = BonusCount[idx]
                    set LastFlat[idx]   = GetFlat(src)
                    set LastOffset[idx] = GetOffset(src)
                    call CalculateCooldown(src, cod, lvl, GetNewCooldown(src, cod, lvl), false)
                endif
 
                set src = null
            endmethod
 
            static method onInit takes nothing returns nothing
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.OnCast)
            endmethod
        endstruct
 
        /* -------------------------------------------------------------------------- */
        /*                               Public JASS API                              */
        /* -------------------------------------------------------------------------- */
 
        /* --------------------------------- Getters -------------------------------- */
        function GetUnitCooldownReduction takes unit u returns real
            return CDR.Get(u)
        endfunction
 
        function GetUnitCooldownReductionFlat takes unit u returns real
            return CDR.GetFlat(u)
        endfunction
 
        function GetUnitCooldownOffset takes unit u returns real
            return CDR.GetOffset(u)
        endfunction
 
        /* --------------------------------- Setters -------------------------------- */
        function SetUnitCooldownReduction takes unit u, real value returns nothing
            call CDR.Set(u, value)
        endfunction
 
        function SetUnitCooldownReductionFlat takes unit u, real value returns nothing
            call CDR.SetFlat(u, value)
        endfunction
 
        function SetUnitCooldownOffset takes unit u, real value returns nothing
            call CDR.SetOffset(u, value)
        endfunction
 
        /* -------------------------------- Aritmetic ------------------------------- */
        function UnitAddCooldownReduction takes unit u, real value returns nothing
            call CDR.Add(u, value)
        endfunction
 
        function UnitAddCooldownReductionFlat takes unit u, real value returns nothing
            call CDR.SetFlat(u, CDR.GetFlat(u) + value)
        endfunction
 
        function UnitAddCooldownOffset takes unit u, real value returns nothing
            call CDR.SetOffset(u, CDR.GetOffset(u) + value)
        endfunction
 
        function UnitRemoveCooldownReduction takes unit u, real value returns nothing
            call CDR.Remove(u, value)
        endfunction
 
        /* -------------------------------- Utilities ------------------------------- */
        function RegisterAbility takes ability abil returns nothing
            call CDR.RegisterDefaultCooldown(abil)
        endfunction
     
        function GetUnitAbilityCooldown takes unit u, integer abilityId, integer level returns real
            local real cd = CDR.GetNewCooldown(u, abilityId, level)
 
            if cd < 0 then
                set cd = 0.00
            endif
 
            return cd
        endfunction

        function CalculateAbilityCooldown takes unit u, integer id, integer lvl, real cd returns nothing
            call CDR.CalculateCooldown(u, id, lvl, cd, true)
        endfunction    
endlibrary
 

Code (vJASS):

library CooldownReductionUtils requires CooldownReduction
/* --------------- Cooldown Reduction Utils v1.4 by Chopinski --------------- */
    //! novjass
        Intro
            Utility Library that include a few extra functions to deal with
            Cooldown Reduction
 
        JASS API
            /* ------------------------------- Timed Bonus ------------------------------ */
            function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
                -> Add to the amount of cdr of a unit for a given duration. Accepts positive and negative values.
                -> It handles removing the bonus automatically
     
            function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
                -> Add to the amount of cdr flat of a unit for a given period. Accepts positive and negative values.
                -> It handles removing the bonus automatically
     
            function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
                -> Add to the amount of cdr offset of a unit for a given period. Accepts positive and negative values.
                -> It handles removing the bonus automatically
         
            /* ----------------------------- String Previews ---------------------------- */
            function GetUnitAbilityCooldownEx takes unit u, integer abilityId, integer level returns string
                -> Returns the calculated cooldown for an ability as a string
     
            function GetUnitCooldownReductionEx takes unit u returns string
                -> Returns the amount of cdr a unit has as a string factored by 100
                -> example of return: 10.50 -> 0.105 internally.
     
            function GetUnitCooldownReductionFlatEx takes unit u returns string
                -> Returns the amount of cdr flat a unit has as a string factored by 100
                -> example of return: 10.50 -> 0.105 internally.
     
            function GetUnitCooldownOffsetEx takes unit u returns string
                -> Returns the amount of cdr offset a unit has as a string
     
            /* ------------------------------ Maybe Usefull ----------------------------- */
            function GetAbilityTable takes nothing returns hashtable
                -> Returns the hashtable that holds the units default cooldown reduction values.
                -> Use with caution! you might break stuff
    //! endnovjass
/* ----------------------------------- END ---------------------------------- */
    private struct CDRUtils extends CDR
        static timer t = CreateTimer()
        //----------------------------------------------
        static integer didx = -1
        static thistype array data
        //----------------------------------------------

        unit    u
        real    ticks
        real    amount
        integer tipo

        method destroy takes nothing returns nothing
            if didx == -1 then
                call PauseTimer(t)
            endif

            set .u     = null
            set .ticks = 0
            call .deallocate()
        endmethod

        private static method onPeriod takes nothing returns nothing
            local integer  i = 0
            local thistype this
         
            loop
                exitwhen i > didx
                    set this   = data[i]
                    set .ticks = .ticks - 1

                    if .ticks <= 0 then
                        if .tipo == 0 then
                            call Remove(.u, .amount)
                        elseif .tipo == 1 then
                            call SetFlat(.u, GetFlat(.u) - .amount)
                        else
                            call SetOffset(.u, GetOffset(.u) - .amount)
                        endif

                        set  data[i] = data[didx]
                        set  didx    = didx - 1
                        set  i         = i - 1
                        call .destroy()
                    endif
                set i = i + 1
            endloop
        endmethod

        static method AddTimed takes unit u, real amount, real duration, integer tipo returns nothing
            local thistype this = thistype.allocate()

            set .u          = u
            set .amount     = amount
            set .tipo       = tipo
            set .ticks      = duration/0.03125000
            set didx        = didx + 1
            set data[didx]  = this

            if tipo == 0 then
                call Add(u, amount)
            elseif tipo == 1 then
                call SetFlat(u, GetFlat(u) + amount)
            else
                call SetOffset(u, GetOffset(u) + amount)
            endif
         
            if didx == 0 then
                call TimerStart(t, 0.03125000, true, function thistype.onPeriod)
            endif
        endmethod
    endstruct

    /* -------------------------------------------------------------------------- */
    /*                               Public JASS API                              */
    /* -------------------------------------------------------------------------- */

    /* ------------------------------- Timed Bonus ------------------------------ */
    function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
        call CDRUtils.AddTimed(u, value, duration, 0)
    endfunction

    function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
        call CDRUtils.AddTimed(u, value, duration, 1)
    endfunction

    function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
        call CDRUtils.AddTimed(u, value, duration, 2)
    endfunction

    /* ----------------------------- String Previews ---------------------------- */
    function GetUnitAbilityCooldownEx takes unit u, integer abilityId, integer level returns string
        return R2SW(GetUnitAbilityCooldown(u, abilityId, level) , 1, 2)
    endfunction

    function GetUnitCooldownReductionEx takes unit u returns string
        return R2SW(CDRUtils.Get(u)*100, 1, 2)
    endfunction

    function GetUnitCooldownReductionFlatEx takes unit u returns string
        return R2SW(CDRUtils.GetFlat(u)*100, 1, 2)
    endfunction

    function GetUnitCooldownOffsetEx takes unit u returns string
        return R2SW(CDRUtils.GetOffset(u), 1, 2)
    endfunction

    /* ------------------------------ Maybe Usefull ----------------------------- */
    function GetAbilityTable takes nothing returns hashtable
        return CDRUtils.Ability_Table
    endfunction
endlibrary
 

Code (vJASS):

library CooldownReduction requires RegisterPlayerUnitEvent, Table
    /* ------------------ Cooldown Reduction v1.6 by Chopinski ------------------ */
    //! novjass
        Intro
            This library intension in to introduce to warcraft an easy way to
            manipulate abilities cooldowns based on a cooldown reduction value that
            is unique for each unit.
 
        How it Works?
            When casting an ability, its "new" cooldown is calculated based on the
            amount of cooldown reduction of the casting unit. the formula for
            calculation is:
                Cooldown = (Default Cooldown - Cooldown Offset) * [(1 - source1)*(1 - source2)*...] * (1 - Cooldown Reduction Flat)
 
            The system also allow negative values for CDR, resulting in increased
            ability cooldown.
 
            It does not acumulate because the abilities are registered automatically
            on the first cast, saving its base cooldown (Object Editor values) and
            always using this base value for calculation, so you can still edit
            the ability via the editor and the system takes care of the rest.
 
        How to Import
            simply copy the CooldownReduction folder over to your map, and start
            use the API functions
 
        Requirements
            CooldownReduction requires RegisterPlayerUnitEvent and Unit Indexer by Bribe.
            Credits to Magtheridon96 for RegisterPlayerUnitEvent and to Bribe for
            the UnitIndexer. It also requires patch 1.31+.

            RegisterPlayerUnitEvent: www.hiveworkshop.com/threads/snippet-registerplayerunitevent.203338/
            UnitIndexer: www.hiveworkshop.com/threads/gui-unit-indexer-1-4-0-0.197329/#resource-45899
 
        JASS API
            /* --------------------------------- Getters -------------------------------- */
            function GetUnitCooldownReduction takes unit u returns real
                -> Returns the amount of cdr a unit has (this bonuses stacks multiplicatively)
                -> 0.1 = 10%
     
            function GetUnitCooldownReductionFlat takes unit u returns real
                -> Returns the amount of cdr flat a unit has (this bonuses stacks additively)
                -> 0.1 = 10%
     
            function GetUnitCooldownOffset takes unit u returns real
                -> Returns the amount of cdr offset a unit has (this bonuses stacks additively)
                -> 3.0 = 3 seconds is taken from default cooldown before cdr calculation
     
            /* --------------------------------- Setters -------------------------------- */
            function SetUnitCooldownReduction takes unit u, real value returns nothing
                -> Set the amount of cdr for a unit (stacks multiplicatively)
     
            function SetUnitCooldownReductionFlat takes unit u, real value returns nothing
                -> Set the amount of cdr flat for a unit (stacks additively)
     
            function SetUnitCooldownOffset takes unit u, real value returns nothing
                -> Set the amount of cdr offset for a unit (stacks additively)

            function CalculateAbilityCooldown takes unit u, integer id, integer lvl, real cd returns nothing
                -> use this function to pass a custom cooldown as parameter taht will
                -> advantage of this system

            /* -------------------------------- Aritmetic ------------------------------- */
            function UnitAddCooldownReduction takes unit u, real value returns nothing
                -> Add to the amount of cdr of a unit. Accepts positive and negative values
                -> Adding a negative with the intent of removing a positive bonus will not work
                -> for that use/read UnitRemoveCooldownReduction().
     
            function UnitAddCooldownReductionFlat takes unit u, real value returns nothing
                -> Add to the amount of cdr flat of a unit. Accepts positive and negative values
     
            function UnitAddCooldownOffset takes unit u, real value returns nothing
                -> Add to the amount of cdr offset of a unit. Accepts positive and negative values
     
            function UnitRemoveCooldownReduction takes unit u, real value returns nothing
                -> Use this function to remove a default cdr bonus from a unit. Additions of default cdr are paired,
                -> meaning that if you added 0.5 and are trying to remove 0.4, it will not work.
    //! endnovjass
    /* ----------------------------------- END ---------------------------------- */

    globals
        // Hashtable used by the system to save stuff
        public hashtable Ability_Table = InitHashtable()
    endglobals

    // This struct represents a unit list of abilities, and is used
    // to register and change a unit ability cooldown whenever that
    // unit cooldown reductions value are manipulated
    private struct Abilities
        unit    unit
        integer count
        Table   table

        /* ------------------------------- Destructor ------------------------------- */
        method destroy takes nothing returns nothing
            call FlushChildHashtable(Ability_Table, this)
            call FlushChildHashtable(Ability_Table, GetHandleId(unit))
            call table.destroy()
            call deallocate()

            set count = 0
            set unit  = null
        endmethod

        // the abilities default cooldowns
        method default takes ability abil, integer level returns real
            return LoadReal(Ability_Table, GetHandleId(abil), level)
        endmethod

        // register the default cooldowns for a new unit ability being inserted
        method register takes ability abil returns nothing
            local integer i      = 0
            local integer levels = BlzGetAbilityIntegerField(abil, ABILITY_IF_LEVELS)

            loop
                exitwhen i >= levels
                    call SaveReal(Ability_Table, GetHandleId(abil), (i + 1), BlzGetAbilityRealLevelField(abil, ABILITY_RLF_COOLDOWN, i))
                set i = i + 1
            endloop
        endmethod

        // update all abilities registered for the unit
        method update takes integer bonus_count, real cdr, real flat, real offset returns nothing
            local integer i = 0
            local integer j = 0
            local integer id
            local integer lvl
            local ability abi
            local real    cd

            loop
                exitwhen i > count - 1
                    set id  = table[i]
                    set abi = BlzGetUnitAbility(unit, id)
                    set lvl = BlzGetAbilityIntegerField(abi, ABILITY_IF_LEVELS)

                    loop
                        exitwhen j >= lvl
                            if bonus_count > 0 then
                                set cd = ((default(abi, j+1) - offset) * cdr * (1 - flat))
                            else
                                set cd = ((default(abi, j+1) - offset) * (1 - flat))
                            endif
                            call BlzSetAbilityRealLevelField(abi, ABILITY_RLF_COOLDOWN, j, cd)
                            call IncUnitAbilityLevel(unit, id)
                            call DecUnitAbilityLevel(unit, id)
                        set j = j + 1
                    endloop
                set i = i + 1
            endloop

            set abi = null
        endmethod

        // returns true if the ability is already registered for the unit
        method has takes integer id returns boolean
            return LoadBoolean(Ability_Table, this, id)
        endmethod

        // Insert a new ability into the unit ability list
        method insert takes integer id returns nothing
            set table[count] = id
            set count = count + 1

            call register(BlzGetUnitAbility(unit, id))
            call SaveBoolean(Ability_Table, this, id, true)
        endmethod

        // Calculates the resultant cooldown for a custom value
        // usefull for cases where the ability cooldown is calculated
        // on its cast and you want the new cooldown to take advantage
        // of the system
        method calculate takes integer id, integer lvl, real newCD, integer bonus_count, real cdr, real flat, real offset returns nothing
            local real    cd
            local ability abi = BlzGetUnitAbility(unit, id)

            if bonus_count > 0 then
                set cd = ((newCD - offset) * cdr * (1 - flat))
            else
                set cd = ((newCD - offset) * (1 - flat))
            endif

            call BlzSetAbilityRealLevelField(abi, ABILITY_RLF_COOLDOWN, lvl, cd)
            call IncUnitAbilityLevel(unit, id)
            call DecUnitAbilityLevel(unit, id)
        endmethod

        /* ------------------------------- Construtor ------------------------------- */
        static method create takes unit u returns thistype
            local thistype this = thistype.allocate()

            set count = 0
            set unit  = u
            set table = Table.create()

            return this
        endmethod
    endstruct

    /* -------------------------------------------------------------------------- */
    /*                             Cooldown Reduction                             */
    /* -------------------------------------------------------------------------- */

    struct CDR
        readonly static real    array CooldownReduction // stacks multiplicatively
        readonly static real    array CooldownReductionFlat // stacks additively
        readonly static real    array CooldownOffset // stacks additively
        private  static integer array BonusCount // the number of normal cdr a unit has
        private  static integer array n // each unit registered has it's own instance

        // Retrieve or create the Abilities list from a unit
        private static method instance takes unit u returns Abilities
            local integer idx = GetUnitUserData(u)

            if n[idx] == 0 then
                set n[idx] = Abilities.create(u)
            endif

            return n[idx]
        endmethod

        // Update the unit abilities cooldowns
        private static method update takes unit u returns nothing
            local Abilities list = instance(u)
            local integer   idx  = GetUnitUserData(u)

            call list.update(BonusCount[idx], CooldownReduction[idx], CooldownReductionFlat[idx], CooldownOffset[idx])
        endmethod

        // Getter and Setter
        // 0 -> default type, 1 -> flat, 2 -> offset
        static method Get takes unit u, integer types returns real
            if types == 0 then
                return CooldownReduction[GetUnitUserData(u)]
            elseif types == 1 then
                return CooldownReductionFlat[GetUnitUserData(u)]
            else
                return CooldownOffset[GetUnitUserData(u)]
            endif
        endmethod

        static method Set takes unit u, real value, integer types returns nothing
            if types == 0 then
                set CooldownReduction[GetUnitUserData(u)] = value
            elseif types == 1 then
                set CooldownReductionFlat[GetUnitUserData(u)] = value
            else
                set CooldownOffset[GetUnitUserData(u)] = value
            endif

            call update(u)
        endmethod

        //Calculates an unit cooldown reduction for the default type (stacks multiplicatively)
        private static method calculate takes unit u returns real
            local integer idx = GetUnitUserData(u)
            local integer id  = GetHandleId(u)
            local integer i   = 0
            local real    cdr = 0
            local real    aux

            loop
                exitwhen i > BonusCount[idx]
                    set aux = LoadReal(Ability_Table, id, i)
                 
                    if i > 0 then
                        set cdr = cdr * (1 - aux)
                    else
                        set cdr = 1 - aux
                    endif
                set i = i + 1
            endloop

            return cdr
        endmethod

        // Added in version 1.1: Changed the default formula to stack multiplicatively instead of additively.
        // Add inserts in the unit list of bonuses and Remove, well, removes it.
        static method add takes unit u, real amount returns nothing
            local integer   idx  = GetUnitUserData(u)
            local integer   id   = GetHandleId(u)
            //-----------------------------------------------------

            if amount != 0 then
                call SaveReal(Ability_Table, id, BonusCount[idx], amount)
                set CooldownReduction[idx] = calculate(u)
                set BonusCount[idx] = BonusCount[idx] + 1
                call update(u)
            endif
        endmethod

        //Removes are paired with additions. If trying to remove a value that was not
        //added first, than it should fail.
        //Since its a multiplication, the order of operations do not alter the result,
        //so to save performance, when removing a bonus, the last bonus registered is
        // moved to the position of the bonus that was just removed and the loop breks.
        static method remove takes unit u, real amount returns nothing
            local integer   idx  = GetUnitUserData(u)
            local integer   id   = GetHandleId(u)
            local integer   i    = 0
            local real    aux

            if amount == 0 then
                return
            endif

            loop
                exitwhen i > BonusCount[idx] - 1
                    set aux = LoadReal(Ability_Table, id, i)
                 
                    if aux == amount then
                        call RemoveSavedReal(Ability_Table, id, i)
                        if i != BonusCount[idx] - 1 then
                            set aux = LoadReal(Ability_Table, id, BonusCount[idx] - 1)
                            call SaveReal(Ability_Table, id, i, aux)
                            call RemoveSavedReal(Ability_Table, id, BonusCount[idx] - 1)
                        endif
                        set BonusCount[idx] = BonusCount[idx] - 1
                        set CooldownReduction[idx] = calculate(u)
                        set i = BonusCount[idx] + 1
                        call update(u)
                    else
                        set i = i + 1
                    endif
            endloop
        endmethod

        // Sets the cooldown of a specific unit ability in a specific level given a custom cooldown
        static method CalculateCooldown takes unit u, integer id, integer lvl, real newCD returns nothing
            local integer   idx  = GetUnitUserData(u)
            local Abilities list = instance(u)

            call list.calculate(id, lvl - 1, newCD, BonusCount[idx], CooldownReduction[idx], CooldownReductionFlat[idx], CooldownOffset[idx])
        endmethod

        private static method onCast takes nothing returns nothing
            local unit      src  = GetTriggerUnit()
            local integer   abi  = GetSpellAbilityId()
            local integer   idx  = GetUnitUserData(src)
            local Abilities list
         
            // this condition is here to stop the system spamming registration
            // on every ability cast. The registration will only happen when a
            // cooldown reduction amount is manipulated to a unit
            if n[idx] != 0 then
                set list = n[idx]
                if not list.has(abi) then
                    call list.insert(abi)
                    call update(src)
                endif
            endif

            set src = null
        endmethod

        // clean up on deindex event
        private static method onDeIndex takes nothing returns nothing
            local Abilities list = n[udg_UDex]

            call list.destroy()
            set n[udg_UDex] = 0
            set CooldownReduction[udg_UDex] = 0
            set CooldownReductionFlat[udg_UDex] = 0
            set CooldownOffset[udg_UDex] = 0
            set BonusCount[udg_UDex] = 0
        endmethod

        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
            call TriggerAddCondition(t, Condition(function thistype.onDeIndex))

            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
        endmethod
    endstruct
 
    /* -------------------------------------------------------------------------- */
    /*                               Public JASS API                              */
    /* -------------------------------------------------------------------------- */

    /* --------------------------------- Getters -------------------------------- */
    function GetUnitCooldownReduction takes unit u returns real
        return CDR.Get(u, 0)
    endfunction

    function GetUnitCooldownReductionFlat takes unit u returns real
        return CDR.Get(u, 1)
    endfunction

    function GetUnitCooldownOffset takes unit u returns real
        return CDR.Get(u, 2)
    endfunction

    /* --------------------------------- Setters -------------------------------- */
    function SetUnitCooldownReduction takes unit u, real value returns nothing
        call CDR.Set(u, value, 0)
    endfunction

    function SetUnitCooldownReductionFlat takes unit u, real value returns nothing
        call CDR.Set(u, value, 1)
    endfunction

    function SetUnitCooldownOffset takes unit u, real value returns nothing
        call CDR.Set(u, value, 2)
    endfunction

    /* -------------------------------- Aritmetic ------------------------------- */
    function UnitAddCooldownReduction takes unit u, real value returns nothing
        call CDR.add(u, value)
    endfunction

    function UnitAddCooldownReductionFlat takes unit u, real value returns nothing
        call CDR.Set(u, CDR.Get(u, 1) + value, 1)
    endfunction

    function UnitAddCooldownOffset takes unit u, real value returns nothing
        call CDR.Set(u, CDR.Get(u, 2) + value, 2)
    endfunction

    function UnitRemoveCooldownReduction takes unit u, real value returns nothing
        call CDR.remove(u, value)
    endfunction

    /* -------------------------------- Utilities ------------------------------- */
    function CalculateAbilityCooldown takes unit u, integer id, integer lvl, real cd returns nothing
        call CDR.CalculateCooldown(u, id, lvl, cd)
    endfunction      
endlibrary
 

Code (vJASS):

library CooldownReductionUtils requires CooldownReduction
    /* --------------- Cooldown Reduction Utils v1.6 by Chopinski --------------- */
        //! novjass
            Intro
                Utility Library that include a few extra functions to deal with
                Cooldown Reduction
     
            JASS API
                /* ------------------------------- Timed Bonus ------------------------------ */
                function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
                    -> Add to the amount of cdr of a unit for a given duration. Accepts positive and negative values.
                    -> It handles removing the bonus automatically
         
                function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
                    -> Add to the amount of cdr flat of a unit for a given period. Accepts positive and negative values.
                    -> It handles removing the bonus automatically
         
                function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
                    -> Add to the amount of cdr offset of a unit for a given period. Accepts positive and negative values.
                    -> It handles removing the bonus automatically
             
                /* ----------------------------- String Previews ---------------------------- */
                function GetUnitCooldownReductionEx takes unit u returns string
                    -> Returns the amount of cdr a unit has as a string factored by 100
                    -> example of return: 10.50 -> 0.105 internally.
         
                function GetUnitCooldownReductionFlatEx takes unit u returns string
                    -> Returns the amount of cdr flat a unit has as a string factored by 100
                    -> example of return: 10.50 -> 0.105 internally.
         
                function GetUnitCooldownOffsetEx takes unit u returns string
                    -> Returns the amount of cdr offset a unit has as a string
         
                /* ------------------------------ Maybe Usefull ----------------------------- */
                function GetAbilityTable takes nothing returns hashtable
                    -> Returns the hashtable that holds the units default cooldown reduction values.
                    -> Use with caution! you might break stuff
        //! endnovjass
    /* ----------------------------------- END ---------------------------------- */
        private struct CDRUtils extends CDR
            static timer t = CreateTimer()
            //----------------------------------------------
            static integer didx = -1
            static thistype array data
            //----------------------------------------------
 
            unit    u
            real    ticks
            real    amount
            integer tipo
 
            method destroy takes nothing returns nothing
                if didx == -1 then
                    call PauseTimer(t)
                endif
 
                set .u     = null
                set .ticks = 0
                call .deallocate()
            endmethod
 
            private static method onPeriod takes nothing returns nothing
                local integer  i = 0
                local thistype this
             
                loop
                    exitwhen i > didx
                        set this   = data[i]
                        set .ticks = .ticks - 1
 
                        if .ticks <= 0 then
                            if .tipo == 0 then
                                call remove(.u, .amount)
                            elseif .tipo == 1 then
                                call Set(.u, Get(.u, 1) - .amount, 1)
                            else
                                call Set(.u, Get(.u, 2) - .amount, 2)
                            endif
 
                            set  data[i] = data[didx]
                            set  didx    = didx - 1
                            set  i       = i - 1
                            call .destroy()
                        endif
                    set i = i + 1
                endloop
            endmethod
 
            static method AddTimed takes unit u, real amount, real duration, integer tipo returns nothing
                local thistype this = thistype.allocate()
 
                set .u          = u
                set .amount     = amount
                set .tipo       = tipo
                set .ticks      = duration/0.03125000
                set didx        = didx + 1
                set data[didx]  = this
 
                if tipo == 0 then
                    call add(u, amount)
                elseif tipo == 1 then
                    call Set(u, Get(u, 1) + amount, 1)
                else
                    call Set(u, Get(u, 2) + amount, 2)
                endif
             
                if didx == 0 then
                    call TimerStart(t, 0.03125000, true, function thistype.onPeriod)
                endif
            endmethod
        endstruct
 
        /* -------------------------------------------------------------------------- */
        /*                               Public JASS API                              */
        /* -------------------------------------------------------------------------- */
 
        /* ------------------------------- Timed Bonus ------------------------------ */
        function UnitAddCooldownReductionTimed takes unit u, real value, real duration returns nothing
            call CDRUtils.AddTimed(u, value, duration, 0)
        endfunction
 
        function UnitAddCooldownReductionFlatTimed takes unit u, real value, real duration returns nothing
            call CDRUtils.AddTimed(u, value, duration, 1)
        endfunction
 
        function UnitAddCooldownOffsetTimed takes unit u, real value, real duration returns nothing
            call CDRUtils.AddTimed(u, value, duration, 2)
        endfunction
 
        /* ----------------------------- String Previews ---------------------------- */
        function GetUnitCooldownReductionEx takes unit u returns string
            return R2SW(CDRUtils.Get(u, 0)*100, 1, 2)
        endfunction
 
        function GetUnitCooldownReductionFlatEx takes unit u returns string
            return R2SW(CDRUtils.Get(u, 1)*100, 1, 2)
        endfunction
 
        function GetUnitCooldownOffsetEx takes unit u returns string
            return R2SW(CDRUtils.Get(u, 2), 1, 2)
        endfunction
 
        /* ------------------------------ Maybe Usefull ----------------------------- */
        function GetAbilityTable takes nothing returns hashtable
            return CooldownReduction_Ability_Table
        endfunction
    endlibrary
 




Changelog
(v1.0)
  • Submission
(v1.1)
  • Redesign the system.
  • 3 types of cooldown manipulation
    • Default cooldown reduction now stacks multiplicatively
    • Flat cooldown reduction stacks additively
    • Offset cooldown stacks additively
(v1.2)
  • Added a new functionality CalculateAbilityCooldown() (See API) used by the default method of calculating a new cooldown and also used to manualy set an ability cooldown that will take advantage of the system.
(v1.3)
  • Splitted CooldownReduction into 2 Libraries: CooldownReduction and CooldownReductionUtils
  • Fixed a bug when removing the last default buff of a unit.
  • Fixed a minor leak
  • Code Cleaning
(v1.4)
  • Fixed a bug for the Flat and Offset types
(v1.5)
  • BIG UPDATE: System redesigned to be more efficient, not calculating the cooldowns on every cast for every ability but only when a unit has its cooldown reduction parameters altered.
  • function GetUnitAbilityCooldown() removed in v1.5. Users can now use the native BlzGetUnitAbilityCooldown() normally.
  • function RegisterAbility() removed, since it's pointless 1.5
  • Added a Legacy section in the code for versions 1.4 and lower
(v1.6)
  • CooldownReduction now uses Table Library instead of a fixed size array to list the registered abilities. This allows for a more dynamic and less limited system.
Contents

Cooldown Reduction v1.6 (Map)

  1. pick-a-chew

    pick-a-chew

    Joined:
    Jul 15, 2007
    Messages:
    732
    Resources:
    4
    Icons:
    2
    Maps:
    2
    Resources:
    4
    There are a few issues with this:
    • Doesn't seem to support abilities with their own dynamic cooldowns, e.g. if an ability's cooldown is dependent on the caster's mana at cast for example, the CDR from this system is not applied. There's no advice given by this resource on how to handle these situations (if it can handle it at all).
    • It's a lot of code for something that can be made in GUI with a hashtable. What advantages does this have?
    • I personally use BlzGetAbilityCooldown in mine (and then abilities with dynamic cooldowns are processed individually). Out of interest is there a reason you did not want to use this?
     
  2. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Would be cool if this supports cooldown offset in addition to cooldown factor:
    cooldown = (<base cooldown> - <cooldown_reduction_offset>)*(1 - <cooldown_reduction_factor>)

    EDIT:
    Btw, when doing for example:
    Code (vJASS):
    call UnitAddCooldownReduction(u, 0.5)
    call UnitAddCooldownReduction(u, 0.5)

    It should stack the added reductions multiplicatively, not additively. This would be the intuitive interpretation of the API from a user perspective. Looking above, you could argue that the user intends for the unit to have 100% cooldown reduction there, but imagine for example that the two calls above^ are called by two different systems using the same API, each system expects that it would reduce 50% from what is currently the cooldown of spells of that unit.
     
    Last edited: Apr 25, 2020
  3. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    I'm working on a version where an ability object will be passed as a parameter instead of its id, so doing what you mentioned will be a breeze. For your example, you will only need to call RegisterDefaultCooldown(abiltiy) right after you change the ability cooldown based on mana to make the system work as intended.

    It's not a lot of code, the JASS API takes almost as much lines as the core system itself. The advantages, well JASS over GUI for starter, hence performance. Dynamic indexing for timed bonus and using only one timer. The ability to use it integrated with Spell description modification. there's more, but not going to write a book here.

    When using SetAbilityRealLevelField to change the cooldown you will be changing it permanently, so if at some point you need to revert it and you are not using any kind of data structure to save its default values than you wont be able to go back, that's why I created it. Also, to still allow the user to play around with the editor fields. In the new upcoming version, dynamic cooldowns will still be able to take advantage of cdr if you re-register the ability after the cooldown change.

    I'm implementing all the features you required, but by stacking multiplicatively, handling negative bonuses might not be possible.
     
  4. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    You multiply the reciprocal instead if the input is negative Lol no :p

    Another thing to consider.. It is important to make systems compatible with existing map/system codes, requiring minimal changes, to make it essentially plug-and-play if possible. And I can definitely see that it is possible to do it here. Which means, it should work correctly with existing jass natives like
    BlzGetUnitAbilityCooldown()


    This will definitely help those users who are displaying cooldown info in the abilities in their map and also other systems without having to change each native call with a custom one.

    The way to deal with this is to not set the new ability cooldown only on every cast, but it should update the cooldowns of the abilities a unit has on every
    call UnitAddCooldownReduction()
    and other similar call. You can maybe more easily achieve this by integrating this useful snippet List Unit Abilities into yours.
     
    Last edited: Apr 26, 2020
  5. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    That will be problematic for values like -1.0, but that`s the price to pay.
     
  6. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Sorry I got confused there, the formula should have been
    CD = (CD_Base - CDR_Offset) * [(1 - CDR_Factor1)*(1 - CDR_Factor2)...*(1 - CDR_FactorN)]
    It works regardless of sign
     
  7. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    Ok. Also i tested the List unit abilities snippet, and found a few problems.
    • Huge lag that the system causes when loading abilities.
    • It fails to detect abilities with raw codes that begin with letters greater than S
    • It lists a lot of useless abilities for this system, like Inventory, Move, Attack, which will add a lot of comparisons and degrade performance.
    In all honesty, doing a simple ctrl + shift + f and deleting 3 letters do not seem to be such a burden, considering the possibility that the system might work for some abilities and not for others.
    Let me know what you think.
     
  8. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,532
    Resources:
    23
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    5
    JASS:
    3
    Resources:
    23
  9. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    Basically the use for it would be to allow users to keep using BlzGetUnitAbilityCooldown instead of replacing it with a GetUnitAbilityCooldown that this system provides. The native function will still work, but with a one call delay after cooldown reduction is modified for an unit. Again, really seems to me like a minor modification.
     
  10. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Hmm, if it has such drawbacks, I think you can just stick to your original idea. I was just pushing it because the concept of this being a plug-and-play system is really cool. But yea, I think it better to stick with your previous method unless a more reasonable solution is found =).
     
  11. fatherofchill

    fatherofchill

    Joined:
    Apr 7, 2020
    Messages:
    33
    Resources:
    0
    Resources:
    0
    I'm quite a noob at JASS but I have a few questions in regards to this system. First off thanks a lot for this I've been looking for a way to do cooldown reductions across the game.

    First question: Is there a way to avoid this called function of CDR from running on Caster dummies. Second: Does it matter? Would I save performance even if I did?
     
  12. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    1, 2 & 3 - It's possible but I would say it's not relevant, since in normal conditions, abilities have few levels. I would say that it will only be a problem if you have an ability with 1000 levels and you are casting it very, very frequently, but other than that, I would not worry about performance. The system works but registering abilities on cast, so when a unit cast an ability for the first time, the system will take that casted ability from that unit, loop through all it's levels and save in a table the cooldowns for each level. Subsequent casts for that unit of that ability are loaded, which is pretty fast. For dummy units, who usually have their abilities added to them when they are created, the system will do this every time they cast an ability, but again, basically no impact on performance on normal conditions.
     
  13. fatherofchill

    fatherofchill

    Joined:
    Apr 7, 2020
    Messages:
    33
    Resources:
    0
    Resources:
    0
    That's what I thought, I just had to make sure! Thanks again. I was just in the process of making a cooldown reduction system myself literally this morning and I spotted this. Saved me a lot of time! :cool:2
     
  14. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,533
    Resources:
    9
    Models:
    1
    Icons:
    2
    Maps:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    9
    Doing a routine code inspection on the Cooldown Reduction System:
    • The following code appears to have a number of redundancy in lines:

      Code (vJASS):

      //Removes are paired with additions. If trying to remove a value that was not
      //added first, than it should fail.
      //Since its a multiplication, the order of operations do not alter the result,
      //so to save performance, when removing a bonus, the last bonus registered is
      // moved to the position of the bonus that was just removed and the loop breks.
      static method Remove takes unit u, real amount returns nothing
          // Auxilliary Code
          if amount != 0 then
              loop
                  if aux == amount then
                      // To be Refactored
                      if i == BonusCount[idx] - 1 then
                          call RemoveSavedReal(Ability_Table, id, i)
                      else
                          set aux = LoadReal(Ability_Table, id, BonusCount[idx] - 1)
                          call SaveReal(Ability_Table, id, i, aux)
                          call RemoveSavedReal(Ability_Table, id, BonusCount[idx] - 1)
                      endif
                  // Auxilliary Code
       


      The code can be refactored to reduce the number of lines and still do the same thing:
      Code (vJASS):

      if i != BonusCount[idx] - 1 then
          set aux = LoadReal(Ability_Table, id, BonusCount[idx] - 1)
          call SaveReal(Ability_Table, id, i, aux)
      endif
      call RemoveSavedReal(Ability_Table, id, i)
       


    • Moreover, in the function, the first condition can be rewritten to act as a break-point
      From,
      Code (vJASS):

      static method Remove takes unit u, real amount returns nothing
          // Auxilliary Code
          // To be Refactored
          if amount != 0 then
       


      To,
      Code (vJASS):

      static method Remove takes unit u, real amount returns nothing
          // Auxilliary Code
          // To be Refactored
          if not (amount != 0) then
              return
          endif
          loop
              // Auxilliary Code
       


    • In static method GetNewCooldown, the local abil variable is not nullified before
      the return statement.

    • Exposing the hashtable Ability_Table to users may run the risk of unwanted
      behavior due to the potential of overwriting vital ability data in ways that
      the system might not anticipate.
    During testing, the system ran with little to no issues; cooldown reduction not resetting back to normal when an item is removed. A demonstration of the bug is shown below.

     
  15. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    I'll look into it. Thanks for the reply.
     
  16. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    Actually the correct refactor is:
    Code (vJASS):

    call RemoveSavedReal(Ability_Table, id, i)
    if i != BonusCount[idx] - 1 then
        set aux = LoadReal(Ability_Table, id, BonusCount[idx] - 1)
        call SaveReal(Ability_Table, id, i, aux)
        call RemoveSavedReal(Ability_Table, id, BonusCount[idx] - 1)
    endif
     


    the way you put it, values will be shifted to the right and fail to remove middles, but thx anyway.

    Fixed.

    I've put a warning ahha and moved it to the Utils Lib. I mainly added it so i could display the bonuses in the test map, but someone might find a use to it.

    Fixed.

    I split the Lib in 2, cuz i know you will ask for it :p.
     
  17. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,533
    Resources:
    9
    Models:
    1
    Icons:
    2
    Maps:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    9
    [​IMG]

    There is also a logical mistake on my part in the suggested refactorization of the code. It should have been:
    call RemoveSavedReal(Ability_Table, id, BonusCount[idx] - 1)


    As far as test maps go, the reported bug was fixed (and no longer applies to newer versions of the Cooldown Reduction System). The cooldown reduction algorithm checks out, so I don't see a major reason for this not to be Approved.
     
  18. KitsuneTailsPrower

    KitsuneTailsPrower

    Joined:
    Oct 9, 2019
    Messages:
    16
    Resources:
    0
    Resources:
    0
    I'm curious why you don't base your system on BlzStartUnitAbilityCooldown. If you call BlzStartUnitAbilityCooldown and the ability is already cooling down on the chosen unit the value you specify overwrites the remaining cooldown. e.g. If you just had a bloodmage cast phoenix and call BlzStartUnitAbilityCooldown(udg_test_unit,'AHpx',10.0) the remaining cooldown will be set to 10 seconds.

    edit: I take that back, what I proposed doesn't work with "starts the effect of an ability"

    edit2: It works if you use some sort of a delay with a starts the effect of an ability event.

    edit3: I hadn't fully tested it before I brought this up hence the edits. I think your way is better.
     
    Last edited: Jul 3, 2020
  19. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    447
    Resources:
    6
    Spells:
    6
    Resources:
    6
    ahahahhahaha.

    @MyPad i'm sorry man, but i'm cooking a version of this system that will be fully automatic and more efficient, following the idea that @AGD gave a few post up. It will not calculate "new" cooldowns on every ability cast for every unit, but will do so when the cooldown reductions are manipulated for that unit in specific, so much better. I'll keep a section with the legacy code but I'm making it so it's compatible backwards as well, apart from a function to get the "new" cooldown, since the way I'm doing it the users will be able to use the BlzGetUnitAbilityCooldown() native.