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

[vJASS] Buff System

Level 22
Joined
Feb 6, 2014
Messages
2,466
JASS:
//! novjass

                           
                            Buff System v1.30
                                 by Flux
           
            Handles all interactions of self-defined Buffs.
           
           
            Features:
                - Can dispel positive/negative/both/all Buffs.
                - Supports 3 types of buff stacking.
                - Buffs with duration.
                - Pick all Buffs of a unit easily.
                                 
    //==================================================================//
                                   API
    //==================================================================//

        static method add takes unit source, unit target returns thistype
            - Adds and creates a new Buff to the target depending on the 
              stacking type.
             
              EXAMPLE:
                local MyBuff b = MyBuff.add(GetTriggerUnit(), GetSpellTargetUnit())

              If the stackType is BUFF_STACK_NONE, and the target already has
              the same Buff applied, it will not create a new Buff instance, 
              instead it will return an existing Buff of the same type from 
              the target.
             
              If the stackType is BUFF_STACK_PARTIAL, it will only create a new
              Buff instance if there is no existing Buff on the target with the
              same source, else it will return a Buff coming from that source.
              That means same type of Buff from different sources will stack.
             
              If the stackType is BUFF_STACK_FULL, then "static method add" will
              return a newly created Buff instance everytime it is called.
       
        method operator duration= takes real time returns nothing
            - Adds a countdown timer to a Buff or change the countdown time of a
              Buff if a count down timer already exist.
             
              EXAMPLE:
                set b.duration = 10
       
        static method get takes unit source, unit target, integer typeid returns thistype
            - Returns a Buff from <target> caused by <source> and has a type of typeid.
            - When you want to retrieve a Buff from target caused by any source, input
              null in the source argument.
            - If Buff is non-existing, it returns 0.
            - If there is more than a Buff because that Buff fully stacks, it will return
              the oldest applied Buff.
           
        static method has takes unit source, unit target, integer typeid returns boolean
            - A simple wrapper function for Buff.get(source, target, typeid)
               
        method operator name takes nothing returns string
            - Returns the name of the Buff as defined in Object Editor
             
              EXAMPLE:
                call BJDebugMsg("Buff name is " + b.name)
       
        static method dispel takes unit u, integer dispelType returns nothing
            - Removes all <dispelType> Buffs from a unit.
             
              EXAMPLE:
                call Buff.dispel(GetTriggerUnit(), BUFF_POSITIVE)
       
        static method dispelBoth takes unit u returns nothing
            - Removes positive and negative buffs of a unit.
              Buffs with dispelType of BUFF_NONE will not be removed.
               
              EXAMPLE:
                call Buff.dispelBoth(u)
       
        static method dispelAll takes unit u returns nothing
            - Removes all Buffs from a unit.
             
              EXAMPLE:
                call Buff.dispelAll(u)
       
        static method pickBuffs takes unit u returns nothing
            - Used when you want to pick all buffs of a unit. More detailed
              example below the API list
             
              EXAMPLE:
                call Buff.pickBuffs(GetTriggerUnit())
       
        method remove takes nothing returns nothing
            - Removes a Buff instance. Using inside 'method onRemove'
              will cause an infinite loop.
               
              EXAMPLE:
                call b.remove()
       
        //------------------------------------------------//
                   HOW TO PICK ALL BUFFS ON A UNIT
        //------------------------------------------------//
       
        1. Use Buff.pickBuffs(yourUnit)
        2. Put your scripts/actions between 'implement BuffListStart' and
           'implement BuffListEnd'
       
        EXAMPLE: (A Spell that will remove the first 3 negative buffs applied)
           
            private static method onCast takes nothing returns nothing
                local integer counter = 3
                call Buff.pickBuffs(GetTriggerUnit())
                implement BuffListStart
                    if Buff.picked.dispelType == BUFF_NEGATIVE then
                        call Buff.picked.remove()
                        set counter = counter - 1
                    endif
                    exitwhen counter == 0
                implement BuffListEnd
            endmethod
           
        //NOTE: Since it's using modules, it can only be done inside a struct.
   
    //==================================================================//
                            HOW TO USE BUFF SYSTEM:
    //==================================================================//
   
    1. Create your own struct that 'extends Buff'.
       Do not forget to put 'implement BuffApply' before the end of the struct
   
        Example:
       
        private struct <BuffName> extends Buff
            /*
             * Your code here
            */
            implement BuffApply
        endstruct
       
       
    2. Define basic Buff configurations inside your struct
       
        Example:
        //Rawcode of a spell based on Slow Aura (Tornado)
        //Will be tackled more on Step 4
        private static constant integer RAWCODE = 'AXYZ'
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE =  BUFF_STACK_PARTIAL

       
       
    3. Configure onApply and onRemove methods inside your struct.
       
        Example:
       
        //This will execute when:
        //  - A BUFF_STACK_NONE Buff is removed from a unit.
        //  - A BUFF_STACK_PARTIAL Buff from a certain source is removed from a unit.
        //  - A BUFF_STACK_FULL Buff instance is removed.
        method onRemove takes nothing returns nothing
            //Configure what happens when the Buff is removed.
        endmethod
       
        //This will execute when:
        //  - A BUFF_STACK_NONE/BUFF_STACK_PARTIAL Buff is applied to a unit
        //    not having the same Buff before it is applied.
        //  - A BUFF_STACK_PARTIAL Buff is applied to a unit already having
        //    the same buff but from a different source.
        //  - A BUFF_STACK_FULL Buff is applied to a unit.
        method onApply takes nothing returns nothing
            //Configure what happens when the Buff is applied.
        endmethod
       
   
    4. Create the Buff Objects.
       a. In Object Editor, find "Slow Aura (Tornado)" [Aasl] and use it as the basis 
          to create a new Ability because "Slow Aura (Tornado)" does not appear in the 
          unit command card.
       b. Make sure "Data - Attack Speed Factor" and "Data - Movement Speed Factor"
          are both zero so that it does not do anything. The Buff mechanics/effects will
          be entirely defined by code.
       c. Create a new Buff based on "Tornado (Slow Aura)" [Basl]. It is very important
          that the rawcode of this new Buff is exactly the same as the newly created
          Ability done in Step 4.a except the first letter which depends on the value
          of BUFF_OFFSET.
          Example: 
            BUFF_OFFSET = 0x01000000
                Ability - 'AXYZ'
                Buff    - 'BXYZ'
            BUFF_OFFSET = 0x20000000
                Ability - 'AXYZ'
                Buff    - 'aXYZ'
       d. Edit the Ability: "Stats - Buff" of the new Ability from Step 4.a so 
          that its new Buff is the the Buff created at Step 4.c.
       e. Change the Ability: "Stats - Targets Allowed" to "self"
       f. Change the Buff Icon, Attachment Effects and Tooltips.
   
    5. Configuration is done. You can now easily add the Buff to a unit using:
            <BuffName>.add(source, target)
       
        The system will automatically handles the stacking type of the Buff.


//! endnovjass


JASS:
library Buff /*
                           Buff v1.30
                            by Flux
             
            Handles all interactions of self-defined buffs.
         
            Features:
                - Can dispel positive/negative/both/all buffs.
                - Supports 3 types of buff stacking.
                - Buffs with duration.
                - Pick all Buffs of a unit easily.
             
         
        */ requires /*
           (nothing)
     
        */ optional TimerUtils /*
        */ optional RegisterPlayerUnitEvent /*


    ******************
         CREDITS
    ******************
     
        muzzel          - For BuffHandler which this resource is heavily based upon.
        Vexorian        - For the optional TimerUtils.
        Magtheridon96   - For the optional RegisterPlayerUnitEvent

    */

    globals      
        //-----------------------------//
        //--------- BUFF TYPES --------//
        //-----------------------------//
        constant integer BUFF_NONE = 0  
        constant integer BUFF_POSITIVE = 1    
        constant integer BUFF_NEGATIVE = 2
     
        //-----------------------------//
        //------ BUFF STACK TYPES -----//
        //-----------------------------//
        //Applying the same buff only refreshes the duration
        //If the buff is reapplied but from a different source, the Buff unit source gets replaced.
        constant integer BUFF_STACK_NONE = 0
     
        //Each buff from different source stacks.
        //Re-applying the same buff from the same source only refreshes the duration
        constant integer BUFF_STACK_PARTIAL = 1
     
        //Each buff applied fully stacks.
        constant integer BUFF_STACK_FULL = 2
       
        //Determines the automatic Buff rawcode based on the Ability rawcode
        //If BUFF_OFFSET = 0x01000000, then Ability rawcode of 'AXXX' will have Buff rawcode of 'BXXX'
        //If BUFF_OFFSET = 0x20000000, then Ability rawcode of 'AXXX' will have Buff rawcode of 'aXXX'
        private constant integer BUFF_OFFSET = 0x01000000
       
     
        //Automatically Preloads all Buff abilities defined in "method rawcode"
        //but will generate a lot of scripts in the process
        private constant boolean PRELOAD_BUFFS = true
       
        //Automatically initialize a Buff type.
        //If false, initialize it using <MyBuff>.initialize()
        private constant boolean AUTO_INITIALIZE = true
    endglobals
   

    struct Buff
        //Buff properties
        readonly boolean exist
        readonly unit target
        readonly unit source
        readonly integer rawcode
        readonly integer buffId
        readonly integer stackType
        readonly integer dispelType
       
        //For duration
        private timer t
       
        //Buff Enumeration
        private thistype bnext
        private thistype bprev
        //Events
        private static thistype callback
        private static trigger array onApply
        private static trigger array onRemove
     
        method operator name takes nothing returns string
            return GetObjectName(this.rawcode)
        endmethod
       
        private static hashtable hash = InitHashtable()
       
        //===============================================================
        //======================== BUFF CORE ============================
        //===============================================================    
        static method get takes unit source, unit target, integer typeid returns thistype
            local integer id = GetHandleId(target)
            local thistype this
            local thistype head
            if HaveSavedInteger(thistype.hash, id, 0) then
                set head = LoadInteger(thistype.hash, id, 0)
                set this = head
                loop
                    if this.getType() == typeid and (source == null or source == this.source) then
                        return this
                    endif
                    exitwhen this == head.bprev
                    set this = this.bnext
                endloop
            endif
            return 0
        endmethod
       
        static method has takes unit source, unit target, integer typeid returns boolean
            return thistype.get(source, target, typeid) > 0
        endmethod
       
        method remove takes nothing returns nothing
            local boolean remove = false
            local integer id
            local thistype head
            local integer count
         
            if this.exist then
                set id = GetHandleId(this.target)
               
                set thistype.callback = this
                call TriggerEvaluate(thistype.onRemove[this.getType()])
             
                if this.t != null then
                    static if LIBRARY_TimerUtils then
                        call ReleaseTimer(this.t)
                    else
                        call RemoveSavedInteger(thistype.hash, GetHandleId(this.t), 0)
                        call DestroyTimer(this.t)
                    endif
                    set this.t = null
                endif
             
                if this.stackType == BUFF_STACK_FULL or this.stackType == BUFF_STACK_PARTIAL then
                    //Update Buff count
                    set count = LoadInteger(thistype.hash, this.getType(), id) - 1
                    call SaveInteger(thistype.hash, this.getType(), id, count)
                   
                    if count == 0 then
                        set remove = true
                    endif
                 
                elseif this.stackType == BUFF_STACK_NONE then
                    set remove = true
                endif
             
                if remove then
                    call UnitRemoveAbility(this.target, this.rawcode)
                    call UnitRemoveAbility(this.target, this.buffId)
                endif
             
                //Remove from the BuffList
                set head = LoadInteger(thistype.hash, id, 0)
                if this == head and this.bnext == head then //If this is the only Buff of the unit
                    call RemoveSavedInteger(thistype.hash, id, 0)
                else
                    //If this is the head of the BuffList
                    if this == head then
                        //Change this unit's BuffList head
                        call SaveInteger(thistype.hash, id, 0, this.bnext)
                    endif
                    set this.bnext.bprev = this.bprev
                    set this.bprev.bnext = this.bnext
                endif
             
                set this.exist = false
                set this.target = null
                set this.source = null
                call this.destroy()
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "[Buff]: Attempted to remove non-existing Buff instance.")
            endif
        endmethod
     
        private static method expires takes nothing returns nothing
            static if LIBRARY_TimerUtils then
                local thistype this = GetTimerData(GetExpiredTimer())
                call ReleaseTimer(GetExpiredTimer())
            else
                local integer id = GetHandleId(GetExpiredTimer())
                local thistype this = LoadInteger(thistype.hash, id, 0)
                call RemoveSavedInteger(thistype.hash, id, 0)
                call DestroyTimer(GetExpiredTimer())
            endif
            if this.t != null then
                set this.t = null
                call this.remove()
            endif
        endmethod
     
        method operator duration takes nothing returns real
            if this.t != null then
                return TimerGetRemaining(this.t)
            endif
            return 0.0
        endmethod
     
        method operator duration= takes real time returns nothing
            if this.t == null then
                static if LIBRARY_TimerUtils then
                    set this.t = NewTimerEx(this)
                else
                    set this.t = CreateTimer()
                    call SaveInteger(thistype.hash, GetHandleId(this.t), 0, this)
                endif
            endif
            call TimerStart(this.t, time, false, function thistype.expires)
        endmethod
   
        method check takes unit source, unit target returns thistype
            local boolean apply = false
            local integer id = GetHandleId(target)
            local thistype head
            local thistype temp
            static if not LIBRARY_TimerUtils then
                local timer t
            endif
         
            if this.stackType == BUFF_STACK_FULL then
                //Count how many buffs are stored in a certain unit
                call SaveInteger(thistype.hash, this.getType(), id, LoadInteger(thistype.hash, this.getType(), id) + 1)              
                set apply = true
             
            elseif this.stackType == BUFF_STACK_PARTIAL then
                 //Check if a similar buff type with the same target and source exist
                set temp = thistype.get(source, target, this.getType())
                if temp == 0 then   //None is found
                    set apply = true
                    //Count how many buffs of this type are stored in this certain unit
                    call SaveInteger(thistype.hash, this.getType(), id, LoadInteger(thistype.hash, this.getType(), id) + 1)
                else               //Buff is found, use the previous Buff as the newly applied Buff
                    call this.destroy()
                    set this = temp
                endif

            elseif this.stackType == BUFF_STACK_NONE then
                //Check if a similar buff type with the same target exist
                set temp = thistype.get(null, target, this.getType())
                if temp == 0 then   //None is found
                    set apply = true
                else                //Buff is found, use the previous Buff as the newly applied Buff
                    call this.destroy()
                    set this = temp
                endif
            endif
         
            set this.source = source
            set this.target = target
            set this.exist = true
            set this.buffId = this.rawcode + BUFF_OFFSET
         
            if apply then
             
                if GetUnitAbilityLevel(target, this.rawcode) == 0 then
                    call UnitAddAbility(target, this.rawcode)
                    call UnitMakeAbilityPermanent(target, true, this.rawcode)
                endif
             
                //Add the Buff to a BuffList of this unit
                    //If BuffList already exist
                if HaveSavedInteger(thistype.hash, id, 0) then
                    set head = LoadInteger(thistype.hash, id, 0)
                    set this.bnext = head
                    set this.bprev = head.bprev
                    set this.bnext.bprev = this
                    set this.bprev.bnext = this
                else
                    //Set this as the unit's BuffList head
                    call SaveInteger(thistype.hash, id, 0, this)
                    set this.bnext = this
                    set this.bprev = this
                endif
               
                set thistype.callback = this
                call TriggerEvaluate(thistype.onApply[this.getType()])
            endif
         
            static if LIBRARY_BuffEvent then
                static if LIBRARY_TimerUtils then
                    call TimerStart(NewTimerEx(this), 0.0, false, function BuffEvent.pickAll)
                else
                    set t = CreateTimer()
                    call SaveInteger(thistype.hash, GetHandleId(t), 0, this)
                    call TimerStart(t, 0.0, false, function BuffEvent.pickAll)
                endif
            endif
            return this
        endmethod
     
        //===============================================================
        //======================== BUFF ENUM ============================
        //===============================================================
        readonly static thistype buffHead
        readonly static thistype picked
     
        static method pickBuffs takes unit u returns nothing
            local integer id = GetHandleId(u)
            if HaveSavedInteger(thistype.hash, id, 0) then
                set thistype.buffHead = LoadInteger(thistype.hash, id, 0)
            else
                set thistype.buffHead = 0
            endif
        endmethod
     
     
        //===============================================================
        //======================= BUFF DISPEL ===========================
        //===============================================================
        static method dispel takes unit u, integer dispelType returns nothing
            local integer id = GetHandleId(u)
            local thistype head
            local thistype this
            if HaveSavedInteger(thistype.hash, id, 0) then
                set head = LoadInteger(thistype.hash, id, 0)
                set this = head.bnext
                loop
                    if this.dispelType == dispelType then
                        call this.remove()
                    endif
                    exitwhen this == head
                    set this = this.bnext
                endloop
            endif
        endmethod

     
        static method dispelBoth takes unit u returns nothing
            local integer id = GetHandleId(u)
            local thistype head
            local thistype this
            if HaveSavedInteger(thistype.hash, id, 0) then
                set head = LoadInteger(thistype.hash, id, 0)
                set this = head.bnext
                loop
                    if this.dispelType == BUFF_POSITIVE or this.dispelType == BUFF_NEGATIVE then
                        call this.remove()
                    endif
                    exitwhen this == head
                    set this = this.bnext
                endloop
            endif
        endmethod
     
        static method dispelAll takes unit u returns nothing
            local integer id = GetHandleId(u)
            local thistype head
            local thistype this
            if HaveSavedInteger(thistype.hash, id, 0) then
                set head = LoadInteger(thistype.hash, id, 0)
                set this = head.bnext
                loop
                    call this.remove()
                    exitwhen this == head
                    set this = this.bnext
                endloop
            endif
        endmethod
     
        private static method onDeath takes nothing returns nothing
            call thistype.dispelAll(GetTriggerUnit())
        endmethod
     
        implement optional BuffInit
     
        private static method onInit takes nothing returns nothing
            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
            else
                local trigger t = CreateTrigger()
                local code c = function thistype.onDeath
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
                call TriggerAddCondition(t, Condition(c))
            endif
        endmethod
     
    endstruct

    static if PRELOAD_BUFFS then
        module BuffInit
            readonly static unit preloader
         
            private static method onInit takes nothing returns nothing
                set thistype.preloader = CreateUnit(Player(14), 'ushd', GetRectMaxX(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea), 0)
                call UnitApplyTimedLife(thistype.preloader, 'BTLF', 1.0)
            endmethod
        endmodule
    endif

    module BuffApply
       
        static method add takes unit source, unit target returns thistype
            local thistype this = thistype.create()  
            //Write into readonly attributes
            set s__Buff_rawcode[this] = thistype.RAWCODE
            set s__Buff_stackType[this] = thistype.STACK_TYPE
            set s__Buff_dispelType[this] = thistype.DISPEL_TYPE
            set this = this.check(source, target)
            return this
        endmethod
       
        private static method onApplyInit takes nothing returns boolean
            call thistype(s__Buff_callback).onApply()
            return false
        endmethod
       
        private static method onRemoveInit takes nothing returns boolean
            call thistype(s__Buff_callback).onRemove()
            return false
        endmethod
       
        static method initialize takes nothing returns nothing
            static if thistype.onApply.exists then
                set s__Buff_onApply[thistype.typeid] = CreateTrigger()
                call TriggerAddCondition(s__Buff_onApply[thistype.typeid], function thistype.onApplyInit)
            endif
            static if thistype.onRemove.exists then
                set s__Buff_onRemove[thistype.typeid] = CreateTrigger()
                call TriggerAddCondition(s__Buff_onRemove[thistype.typeid], function thistype.onRemoveInit)
            endif
        endmethod
     
        static if PRELOAD_BUFFS then
            private static method onInit takes nothing returns nothing
                local thistype this = thistype.create()  
                call UnitAddAbility(Buff.preloader, thistype.RAWCODE)
                call UnitRemoveAbility(Buff.preloader, thistype.RAWCODE)
                call this.destroy()
                static if AUTO_INITIALIZE then
                    call thistype.initialize()
                endif
            endmethod
        elseif AUTO_INITIALIZE then
            private static method onInit takes nothing returns nothing
                call thistype.initialize()
            endmethod
        endif
    endmodule

    module BuffListStart
        if Buff.buffHead > 0 then
            set s__Buff_picked = s__Buff_buffHead
            loop
    endmodule

    module BuffListEnd
                exitwhen Buff.picked == s__Buff_bprev[s__Buff_buffHead]
                set s__Buff_picked = s__Buff_bnext[s__Buff_picked]
            endloop
        endif
    endmodule

endlibrary


JASS:
library BuffEvent /*
                        BuffEvent v1.00
                            by Flux
             
            Allows you to catch an event when any Buff applied.
     
        API:
            - BuffEvent.create(code)
                > The code will run when any Buff is applied to any unit.
                > Will not create a new object if there is a BuffEvent already
                  existing for the input code argument.
         
            - BuffEvent.remove(code)
                > Remove the BuffEvent that has the code argument.
             
            - <BuffEventObject>.destroy()
                > Remove a BuffEvent instance.
           
            - BuffEvent.buff
                > The Buff that causes the event to run.
         
        */ requires Buff /*
     
        */ optional TimerUtils /*
*/
    struct BuffEvent
     
        readonly triggercondition tc
        readonly conditionfunc cf

        readonly static Buff buff
        private static trigger trg = CreateTrigger()
     
        method destroy takes nothing returns nothing
            call TriggerRemoveCondition(thistype.trg, this.tc)
            call RemoveSavedInteger(s__Buff_hash, GetHandleId(this.cf), 0)
            set this.tc = null
            set this.cf = null
            call this.deallocate()
        endmethod
     
        static method remove takes code c returns nothing
            local integer id = GetHandleId(Condition(c))
            if HaveSavedInteger(s__Buff_hash, id, 0) then
                call thistype(LoadInteger(s__Buff_hash, id, 0)).destroy()
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "[BuffEvent]: Attempted to unregister code with non-existing BuffEvent.")
            endif
        endmethod

        static method pickAll takes nothing returns nothing
            static if LIBRARY_TimerUtils then
                set thistype.buff = GetTimerData(GetExpiredTimer())
                call ReleaseTimer(GetExpiredTimer())
            else
                local integer id = GetHandleId(GetExpiredTimer())
                set thistype.buff = LoadInteger(s__Buff_hash, id, 0)
                call RemoveSavedInteger(s__Buff_hash, id, 0)
                call DestroyTimer(GetExpiredTimer())
            endif
            call TriggerEvaluate(thistype.trg)
        endmethod
     
        static method create takes code c returns thistype
            local conditionfunc cf = Condition(c)
            local integer id = GetHandleId(cf)
            local thistype this
            if HaveSavedInteger(s__Buff_hash, id, 0) then
                set this = thistype(LoadInteger(s__Buff_hash, id, 0))
            else
                set this = thistype.allocate()
                set this.tc = TriggerAddCondition(thistype.trg, cf)
                set this.cf = cf
                call SaveInteger(s__Buff_hash, id, 0, this)
            endif
            return this
        endmethod
    endstruct

endlibrary

Check the attached map for more examples and the demonstration of how Buff stacking works.

JASS:
scope DamageOverTime
   
    globals
        private constant integer SPELL_ID = 'dovt'
    endglobals
   
    native UnitAlive takes unit u returns boolean
   
    struct DOTBuff extends Buff
       
        private timer t
       
        private static constant integer RAWCODE = 'ADOT'
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE =  BUFF_STACK_FULL
       
        private static method onPeriod takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call UnitDamageTarget(this.source, this.target, 10.0, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        endmethod
       
        method onRemove takes nothing returns nothing
            call ReleaseTimer(this.t)
            set this.t = null
            call BJDebugMsg("A DOT Buff instance is removed, source = " + GetUnitName(this.source))
        endmethod
       
        method onApply takes nothing returns nothing
            set this.t = NewTimerEx(this)
            call TimerStart(this.t, 1.00, true, function thistype.onPeriod)
            call BJDebugMsg("A DOT Buff instance is added, source = " + GetUnitName(this.source))
        endmethod
       
        implement BuffApply
    endstruct

    private struct DOTSpell extends array
       
        private static method onCast takes nothing returns nothing
            local DOTBuff b
            call BJDebugMsg(GetUnitName(GetTriggerUnit()) + " casted DamageOverTime on " + GetUnitName(GetSpellTargetUnit()))
            set b = DOTBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
            set b.duration = 10.0
        endmethod
       
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
        endmethod
       
    endstruct
   
endscope


CREDITS:
- muzzel - For BuffHandler which this resource is heavily based upon.
- Vexorian - For the optional TimerUtils.
- Magtheridon96 - For the optional RegisterPlayerUnitEvent


v1.00 - [26 September 2016]
- Initial Release

v1.01 - [27 September 2016]
- Added automatic Buff removal of dying units.
- Fixed bug caused by hashtable collision.
- Fixed bug where retrieved Buffs gets added to the linked list.
- Removed periodic feature.
- Removed unnecessary function calls.
- Shortened module BuffApply for less generated script.

v1.02 - [2 October 2016]
- Implemented a BuffList on newly applied units to avoid enumerating all Buff Instances upon using dispel.

v1.10 - [29 December 2016]
- Added double free protection.
- Changed "method raw" to "method rawcode".
- Added automatic preload option.
- Added an easy way to pick all Buffs of a unit.
- Added an easy way to get Buff name.

v1.11 - [30 December 2016]
- Renamed internal struct attributes "next" and "prev" to avoid conflict with user custom made attributes.

v1.20 - [21 January 2017]
- Added BuffEvent.
- Added double free protection debug message.
- Added method operator duration allowing users to retrieve the current duration of a Buff instance.

v1.21 - [23 January 2017]
- No longer prone to hashtable collision if there are too many Buff types.
- No longer supports dynamic rawcode of a Buff type. Each Buff type should only have 1 rawcode.
- Added Buff.get(source, target, typeid) & Buff.has(source, target, typeid).
- Buff rawcode offset relative to the Ability rawcode can now be configured.

v1.30 - [23 February 2017]
- Replaced stub methods by an alternative that does not create a huge amount of triggers per Buff configuration.
- Changed Buff basic configuration syntax for rawcode, dispel type and stack type.
- Buff initialization (includes callback triggers creation) can now be done manually or automatically.
- Callback triggers will now only be created if the callback function is existing.
 

Attachments

  • Buff System. v1.30.w3x
    65.2 KB · Views: 488
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
Aren't buffs just supposed to be visual cues to the players that a unit is under the [+|-]effect of some ability?
It seems to me that currently the library encourages users to put functionality that belongs to the ability in the buff itself:

example (from the map):
JASS:
scope Slow

    globals
        private constant integer SPELL_ID = 'slow'
    endglobals

    private struct SlowBuff extends Buff

        private Movespeed ms

        method raw takes nothing returns integer
            return 'ASBP' //SBP = Slow Buff Placer
        endmethod

        method dispelType takes nothing returns integer
            return BUFF_NEGATIVE
        endmethod

        method stackType takes nothing returns integer
            return BUFF_STACK_PARTIAL
        endmethod

        method onRemove takes nothing returns nothing
            call this.ms.destroy()
            call BJDebugMsg("A Slow Buff instance is removed, source = " + GetUnitName(this.source) + ", new movespeed = " + R2S(GetUnitMoveSpeed(this.target)))
        endmethod

        method onApply takes nothing returns nothing
            //Create a Movespeed modifier instance
            set this.ms = Movespeed.create(this.target, -0.3, 0)
            call BJDebugMsg("Slow Buff applied, target's new movespeed = " + R2S(GetUnitMoveSpeed(this.target)))
        endmethod

        implement BuffApply
    endstruct

    private struct SlowSpell extends array

        private static method onCast takes nothing returns nothing
            local SlowBuff b
            call BJDebugMsg(GetUnitName(GetTriggerUnit()) + " casted Slow on " + GetUnitName(GetSpellTargetUnit()))
            set b = SlowBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
            set b.duration = 6.0
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
        endmethod

    endstruct

endscope


Also stub methods (I had to read the manual to find out what those were) calls turn out to be TriggerEvaluate calls with code duplication from jasshelper.

I also think that a simpler API (without extending structs) should suffice:
JASS:
Buff.add(<source>, <target>, <ability-id-based-on-tornado-aura>, <stack-type>, <dispel-type>) // the user would have to manually remove the buff
Buff.add_timed(<source>, <target>, <ability-id-based-on-tornado-aura>, <stack-type>, <dispel-type>, <duration>) // the buff is automatically removed after <duration> number of seconds
buff.remove()
Buff.dispel(<unit>, <dispel-type>)
Buff.has_buff(<unit>, <ability-id-based-on-tornado-aura>)


- Fix bug involving BUFF_STACK_PARTIAL. Can be solved by finding a way to use 3 keys in a hashtable [this.getType()][ids][idt].

[this.getType()][ids * some_big_number(1000000) + idt]?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
It seems to me that currently the library encourages users to put functionality that belongs to the ability in the buff itself:
Exactly, it is not just for visual cues. It also functions as a system that eases up spell making. Furthermore, it also handles the Buff stacking internally.

Also stub methods (I had to read the manual to find out what those were) calls turn out to be
TriggerEvaluate
calls with code duplication from jasshelper.
Yes I know, that's why I plan to remove stub method periodic in the next update and let the user do it himself using a timer with hashtable or TimerUtils (will show that in the next update through the DamageOverTime demo). But for other stub methods, it is fine since they are seldomly called.


I also think that a simpler API (without extending structs) should suffice:
The current API looks fine in my opinion.

[this.getType()][ids * some_big_number(1000000) + idt]?
Yeah, but instead I'll multiply this.getType() to a large number (the assumed max handle id that will be reached) instead and use ids as the other hash key.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Aren't buffs just supposed to be visual cues to the players that a unit is under the [+|-]effect of some ability?
Buffs are supposed to control the lifecycle of an effect that has it's behavior applied with a lifetime.
So, it can start some effect when it is created and remove that effect when it is removed.
It can also just do things in a periodic timeout like a damage over timer or whatever.
It is much better to let this be done by the buff system rather than the spell that would be checking if the unit still has the buff.

@Flux
(From the one I use.)
JASS:
    private static method onInterval takes nothing returns nothing
        local thistype this = Timer.getTimerIndex(GetExpiredTimer())
        call .event.runOnInterval(this, 1)
    endmethod
  
    public method setInterval takes real duration returns nothing
        if not .hasInterval then //practically unnecessary
            set .hasInterval = true
            set .intervalTimer = Timer.getIndexedTimer(this)
        endif
        set .intervalDuration = duration
        call TimerStart(.intervalTimer, duration, true, function thistype.onInterval)
    endmethod
If you want to go mayem on timers, then you could replace the lifetime timer of the buff with the periodic timer but that is a bit unnecessary.
(This function should be called when the buff starts, which is why the .hasInterval check is kind of unnecessary as it doesnt get called twice... if you do it right.)
The event also has to be called on the destroy function but then with a factor of "remainingInervalDuration / .intervalDuration" except if "remainingInervalDuratio" is 0, then it should be ran with a factor of 1.

"call UnitMakeAbilityPermanent(this.target, false, this.spellId)"
Is that really needed?

Also... why the hell do you need a hashtable?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
(From the one I use.)
JASS:
    private static method onInterval takes nothing returns nothing
        local thistype this = Timer.getTimerIndex(GetExpiredTimer())
        call .event.runOnInterval(this, 1)
    endmethod
 
    public method setInterval takes real duration returns nothing
        if not .hasInterval then //practically unnecessary
            set .hasInterval = true
            set .intervalTimer = Timer.getIndexedTimer(this)
        endif
        set .intervalDuration = duration
        call TimerStart(.intervalTimer, duration, true, function thistype.onInterval)
    endmethod
So is that your version of TimerUtils or something?

"call UnitMakeAbilityPermanent(this.target, false, this.spellId)"
Is that really needed?
I don't know, I saw it on muzzel's and the moderators didn't say anything about it.

Also... why the hell do you need a hashtable?
For the buff stacking feature. How else am I gonna retrieve a buff of certain type from a unit without enumerating all buffs? Furthermore, in stacking buffs, the system needs to count how many of those buffs of certain type are so that the system will know when to remove the ability (that is when all stacked buffs are removed).
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Timer is slightly safer than TimerUtils and has a few more features.
But it takes a bit of performance by function redirecting... (think about most of the BJ functions)
On top of that, I also have 3 functions for timers that are used in systems.

But that is not what I was aiming for.
Have a separate timer for the intervals.
It is amazing.

Afaik, removing an ability always works... unless the unit didnt have it.
It is easy to test though. Im just lazy.

About the hashtable.
I see...
What I do is I have a linked list for each unit instead of one linked list for all buffs in total.
I could have another linked list on top of that for all units that have a buff, but that is not really necessary as you dont really have to enumerate through all buffs on the map... at least, you dont have to enumerate through all buffs on the map while not enumerating though all the units on the map.
Allocating and deallocating these linked lists are done automatically by adding the first index or removing the last index.
To get a buff of a certain type of a certain source (unit) I just enumerate through all buffs of that unit.
In a reasonable map, you wont get 200 buffs on one unit would you?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Setting it to true is required for morphing, but setting it to false is not required at all... except if you want the ability to be removed when morphed.

Coming from my Unit struct:
JASS:
    public method addAbility takes integer abilityId returns boolean
        return UnitAddAbility(.ref, abilityId) and UnitMakeAbilityPermanent(.ref, true, abilityId)
    endmethod
   
    public method removeAbility takes integer abilityId returns boolean
        return UnitRemoveAbility(.ref, abilityId)
    endmethod
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
v1.10 - [29 December 2016]
- Added double free protection
- Changed "method raw" to "method rawcode"
- Added automatic preload option
- Added an easy way to pick all Buffs of a unit
- Added an easy way to get Buff name

v1.11 - [30 December 2016]
- Renamed internal struct attributes "next" and "prev" to avoid conflict with user custom made attributes.
 
Last edited:
I very like your code structering and style, it's very readable.
From functionality I personaly always have seen this as very good example [System] Buff. (though, it's actually good you structure it a bit more modular)
I like his buff type "neutral" more over "none", he also offers something like partial immunity, or permanent buffs, removal onDeath, and more events.

It's not that your's isn't good, but in past I liked the one by Goblin very much, but as it's in graveyard now, it would be kind of shame if we also not use it as reference.
Can you do me a favor and check his system yourself the mentioned things and maybe others that you like/not like, and evaluate to add it to your system?
Or had you done so maybe in past already?-- I'm just a bit lazy having to read core code of multiple resources just for comparisons. ^^
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I didn't saw that before (or forgot). Ok, I'll check some functionalities I can copy (and credit him for the idea) if there are any.
I've made a quick look and it seems to offer a lot of useful events.
What events do you think are useful?

Also wondering why that didn't got approved. Lol, I've even made a post there.

I like his buff type "neutral" more over "none", he also offers something like partial immunity, or permanent buffs, removal onDeath, and more events.
What I had in mind for the name BUFF_NONE is, the Buff is neither positive nor negative, so... none. Anyways, in my system, the user is free to create as many Buff type as he wishes.
This system can have permanent buffs too if the user didn't put a duration but it will be removed on death. I was thinking, if the user wants a permanent buff like something that will show in the unit status forever even after being revived, he can easily just add a Tornado (Slow) ability instead.
By default, all buffs will be removed on death, but if the user wants to make a particular type of buff not removed on death, he can create a new Buff type and edit static method onDeath to remove all except a particular buff type.
 
What events
When I look at your resource I'm not sure I miss the "onDamage" or "onDeath" event as it maybe should be managed by some handler library, but not very sure about it.
Most others seem to be actually useful on first look, hm..
And hm, yes, actually your "none" sounds good, too! ;D

This system can have permanent buffs
I see.
wondering why that didn't got approved. Lol, I've even made a post there.
Yeh, I don't know. But goblin then didn't care enough, too, sadly.

Hm what comes in mind is that his lists aiwht pos/neg/neutral buffs looks pretty cool, but sorry if it's implemented what you also have currently. Will find some other time to read better.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Updated!
- Replaced stub methods by an alternative that does not create a huge amount of triggers per Buff configuration.

Before, each configuration (rawcode, dispel type and stack type) generates its own trigger. Also, for it to be retrieved, it needs a trigger evaluation (or is it trigger execution?). Now by replacing it, not only does it compiles to less code and less triggers, it is also much faster because it is a variable lookup instead of trigger evaluation (or is it trigger execution?). My map has too many Buffs that pjass reached its limit, that's also the reason for this update.

- Changed Buff basic configuration syntax for rawcode, dispel type and stack type.

Side effect of first changelog. Also looks much cleaner and less code generated.

- Buff initialization (includes callback triggers creation) can now be done manually or automatically.

Some maps have a Spell Selection system meaning a player will "buy" or "select" the spells for his heroes. That means that some Spells (and therefore some Buff scripts) will not be necessary if not selected therefore it makes sense to initialize a Buff only if it is selected or needed for the gameplay.

- Callback triggers will now only be created if the callback function is existing.

Sometimes users may not used onApply or onRemove. If that's the case, no triggers are created for them unlike stub methods which automatically generates and spams lots of triggers.


@IcemanBo, I took a quick look at this but to be honest, I think some events are really not necessary to be tied to a Buff system. Tell me what you think is useful. I usually just update this whenever the map I'm making needs that feature. As of now, this system is able to satisfy all my map needs regarding Buffs.
Hm what comes in mind is that his lists aiwht pos/neg/neutral buffs looks pretty cool
You can 'almost' do that to this system too. In this system, you can only enumerate all buffs of a unit, but you can do something like
JASS:
           private static method onCast takes nothing returns nothing
                call Buff.pickBuffs(GetTriggerUnit())
                implement BuffListStart
                    //Buff.picked is the currently picked Buff
                    if Buff.picked.dispelType == BUFF_NEGATIVE then
                        //Do you stuffs
                    endif
                implement BuffListEnd
            endmethod
if you only want to pick negative buffs.
 
The only thing compared to others I might seeing missed is (actually both from Goblin):
  • onPeriod, where the user might maybe even define an interval
  • onRefresh, where the user gets notified if a unit is about getting applied the same buff type, and he has access also to the old buff data, still, so he can decide what to do.
What do you think?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
onPeriod, where the user might maybe even define an interval
There was support for onPeriod before but only at 0.03125 timeout. I removed it because the user can easily use TimerUtils instead as shown in the Demo (Example Hidden Button).

onRefresh, where the user gets notified if a unit is about getting applied the same buff type, and he has access also to the old buff data, still, so he can decide what to do.
You mean before onApply there will be onRefresh that only gets called when there is an already existing buff of the same type? As mentioned before, I'm personally using this system and my workaround for this is I just add and call a new function, say method reapply after adding a buff. Example
JASS:
scope DamageOverTime
  
    globals
        private constant integer SPELL_ID = 'dovt'
    endglobals

    private function DamageDealt takes integer level returns real
        return 10.0*level
    endfunction
  
    native UnitAlive takes unit u returns boolean
  
    struct DOTBuff extends Buff
      
        private timer t
        private real dmg
      
        private static constant integer RAWCODE = 'ADOT'
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE =  BUFF_STACK_FULL
      
        private static method onPeriod takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call UnitDamageTarget(this.source, this.target, this.dmg, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        endmethod
      
        method onRemove takes nothing returns nothing
            call ReleaseTimer(this.t)
            set this.t = null
            call BJDebugMsg("A DOT Buff instance is removed, source = " + GetUnitName(this.source))
        endmethod

        //Gets called after adding buff
        //Meaning the damage gets updated whatever the spell level of the latest
        method reapply takes integer level returns nothing
            set this.dmg = DamageDealt(level)
        endmethod
      
        method onApply takes nothing returns nothing
            set this.t = NewTimerEx(this)
            call TimerStart(this.t, 1.00, true, function thistype.onPeriod)
            call BJDebugMsg("A DOT Buff instance is added, source = " + GetUnitName(this.source))
        endmethod
      
        implement BuffApply
    endstruct

    private struct DOTSpell extends array
      
        private static method onCast takes nothing returns nothing
            local DOTBuff b
            //local DOTBuff prevB

            call BJDebugMsg(GetUnitName(GetTriggerUnit()) + " casted DamageOverTime on " + GetUnitName(GetSpellTargetUnit()))
            set b = DOTBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
            call b.reapply(GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID))
            set b.duration = 10.0

            //To make additive duration, I can do something like
            //set prevB = Buff.get(null, GetSpellTargetUnit(), DOTBuff.typeid)
            //if prevB > 0 then  //There is an existing when prevB is not zero
            //    set b.duration = prevB.duration + 10.0
            //else
            //    set b.duration = 10.0
            //endif
        endmethod
      
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
        endmethod
      
    endstruct
  
endscope
What do you think? Is it really necessary or will implementing those suggestions only provide convenience for the user?
 
(Example Hidden Button)
Example "Damge over time" you mean?
But yes, I think I also agree with your opinion to use TimerUtils.


JASS:
        //Gets called after adding buff
        //Meaning the damage gets updated whatever the spell level of the latest
        method reapply takes integer level returns nothing
            set this.dmg = DamageDealt(level)
        endmethod
What if some user wants to check for highest level, and only apply if new level is higher?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Example "Damge over time" you mean?
Yes

What if some user wants to check for highest level, and only apply if new level is higher?
add another struct attribute (say, highestLvl) and add if condition
JASS:
if level > this.highestLvl then
    set this.highestLvl = level
    set this.dmg = DamageDealt(this.highestLvl)
endif
Then onApply, initialize this.highestLvl to zero.
 
It's maybe not as perfect as an update method, but it's definilty fair enough. (you can defend your resources pretty well! xD)

I also had a close look at the demo, it's good for me, too. I like the overall thing, and the documentation. It's "another" buff system, so I will just wait 2-3 days before approving, so someone can chip-in, but I have personaly no issues anymore!
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
How does this deal with Spell Steal?
Nope because the intent is to provide users with a sense of pride and accomplishment a more powerful way to deal with Buffs that allows users to trigger/code the behavior of each Buff to a fairly large extent. It is also easy to code a custom Spell Steal using this system. This system aims to replace the default buff system in WC3 providing better controls/readability but there is a downside that Buffs close to expiring will not blink because it uses Slow Aura (Tornado).
 

Kyrbi0

Arena Moderator
Level 44
Joined
Jul 29, 2008
Messages
9,487
Nope because the intent is to provide users with a sense of pride and accomplishment a more powerful way to deal with Buffs that allows users to trigger/code the behavior of each Buff to a fairly large extent. It is also easy to code a custom Spell Steal using this system. This system aims to replace the default buff system in WC3 providing better controls/readability but there is a downside that Buffs close to expiring will not blink because it uses Slow Aura (Tornado).
Ah, ok, so I can't use this then.

Creative, though, using the Slow Aura trick.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Nope because the intent is to provide users with a sense of pride and accomplishment a more powerful way to deal with Buffs that allows users to trigger/code the behavior of each Buff to a fairly large extent. It is also easy to code a custom Spell Steal using this system. This system aims to replace the default buff system in WC3 providing better controls/readability but there is a downside that Buffs close to expiring will not blink because it uses Slow Aura (Tornado).
Hey, I want to use your system for my project, and I have a question. Is it possible to make a type of buff that can't be dispelled? For example, I want to have an item that can remove all negative effects. However, some negative effects should not be removed. For example, If you played league of legends, QSS removes all control buffs, but can't cancel Zed's ultimate.
 
Level 7
Joined
Oct 11, 2008
Messages
304
Hey, I want to use your system for my project, and I have a question. Is it possible to make a type of buff that can't be dispelled? For example, I want to have an item that can remove all negative effects. However, some negative effects should not be removed. For example, If you played league of legends, QSS removes all control buffs, but can't cancel Zed's ultimate.

JASS:
    static method dispelBoth takes unit u returns nothing
        - Removes positive and negative buffs of a unit.
          Buffs with dispelType of BUFF_NONE will not be removed.
Your ability dispelType should be BUFF_NONE and you're probably fine. :)

In your case, Zed's ultimate buff dispelType is BUFF_NONE.
 
Level 7
Joined
Feb 9, 2021
Messages
301
JASS:
    static method dispelBoth takes unit u returns nothing
        - Removes positive and negative buffs of a unit.
          Buffs with dispelType of BUFF_NONE will not be removed.
Your ability dispelType should be BUFF_NONE and you're probably fine. :)

In your case, Zed's ultimate buff dispelType is BUFF_NONE.
thanks
 
Level 7
Joined
Feb 9, 2021
Messages
301
JASS:
    static method dispelBoth takes unit u returns nothing
        - Removes positive and negative buffs of a unit.
          Buffs with dispelType of BUFF_NONE will not be removed.
Your ability dispelType should be BUFF_NONE and you're probably fine. :)

In your case, Zed's ultimate buff dispelType is BUFF_NONE.
Do you know whether this system handles every buff as an object on its own, making running multiple buffs with different timers by each other? This is opposed to refreshing the duration of every instance of the buff. Similar to GUI Buff System (GBS) V. 1.43.

So for example, I use the same buff for stunning knock backed units. If there are two knockbacks at the same time, will the newer knockback apply or will they have separate timers?
 
Level 7
Joined
Oct 11, 2008
Messages
304
Do you know whether this system handles every buff as an object on its own, making running multiple buffs with different timers by each other? This is opposed to refreshing the duration of every instance of the buff. Similar to GUI Buff System (GBS) V. 1.43.

So for example, I use the same buff for stunning knock backed units. If there are two knockbacks at the same time, will the newer knockback apply or will they have separate timers?
Each buff use its own timer.

JASS:
        method operator duration= takes real time returns nothing
            if this.t == null then
                static if LIBRARY_TimerUtils then
                    set this.t = NewTimerEx(this)
                else
                    set this.t = CreateTimer()
                    call SaveInteger(thistype.hash, GetHandleId(this.t), 0, this)
                endif
            endif
            call TimerStart(this.t, time, false, function thistype.expires)
        endmethod

About your example, unsure if I understand right, but it's depends of what type of stack buff you're using. According to the documentation:
JASS:
              If the stackType is BUFF_STACK_NONE, and the target already has
              the same Buff applied, it will not create a new Buff instance, 
              instead it will return an existing Buff of the same type from 
              the target.
             
              If the stackType is BUFF_STACK_PARTIAL, it will only create a new
              Buff instance if there is no existing Buff on the target with the
              same source, else it will return a Buff coming from that source.
              That means same type of Buff from different sources will stack.
             
              If the stackType is BUFF_STACK_FULL, then "static method add" will
              return a newly created Buff instance everytime it is called.

If you use BUFF_STACK_NONE the system will just ignore the new buff. If you use BUFF_STACK_PARTIAL it reapply the buff if the unit source of the buff is different from the first buff. If BUFF_STACK_FULL it will run entirely effect, applying, duration, remove, all separated.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Each buff use its own timer.

JASS:
        method operator duration= takes real time returns nothing
            if this.t == null then
                static if LIBRARY_TimerUtils then
                    set this.t = NewTimerEx(this)
                else
                    set this.t = CreateTimer()
                    call SaveInteger(thistype.hash, GetHandleId(this.t), 0, this)
                endif
            endif
            call TimerStart(this.t, time, false, function thistype.expires)
        endmethod

About your example, unsure if I understand right, but it's depends of what type of stack buff you're using. According to the documentation:
JASS:
              If the stackType is BUFF_STACK_NONE, and the target already has
              the same Buff applied, it will not create a new Buff instance,
              instead it will return an existing Buff of the same type from
              the target.
            
              If the stackType is BUFF_STACK_PARTIAL, it will only create a new
              Buff instance if there is no existing Buff on the target with the
              same source, else it will return a Buff coming from that source.
              That means same type of Buff from different sources will stack.
            
              If the stackType is BUFF_STACK_FULL, then "static method add" will
              return a newly created Buff instance everytime it is called.

If you use BUFF_STACK_NONE the system will just ignore the new buff. If you use BUFF_STACK_PARTIAL it reapply the buff if the unit source of the buff is different from the first buff. If BUFF_STACK_FULL it will run entirely effect, applying, duration, remove, all separated.
Thank you. Sorry, I should have read the documentation first. This is exactly what I needed.
 
Level 7
Joined
Feb 9, 2021
Messages
301
I have this error. Should it be RegisterAnyPlayerUnitEvent? ( I am using this system [Snippet] RegisterEvent pack)
1617370667405.png
 
Level 7
Joined
Feb 9, 2021
Messages
301
How can I use this system for disabling effects? (stuns, attack speed slows, sleep, disarm, root and others). I thought about combining it with [GUI-friendly] Disable System (Stun,Silence,Disarm,Snare) , but I am not sure how to do it. As far as my understanding goes, the Disable System can apply permanent stuns, and I can control their duration with buffs from the Buff System. However, the Disable System also applies a buff on a unit, which means the unit will have two buffs at the same time (something that I obviously don't want). Can anyone advise me on how to better use them together or propose any alternatives?

I want to have items that reduce negative buffs durations (stuns, charms, slows and etc)[similar to MR shoes in LoL], and I want to have items that can remove certain negative effects from the unit (either casted by an ally to a friendly unit or casted by stunned/charmed/slowed unit itself (similar to QSS in LoL).
 
Last edited:
Level 7
Joined
Oct 11, 2008
Messages
304
Well, that was something I was thinking about today morning, and I don't think you can hide a debuff icon, at least not stun/sleep/root one. Obviously, there's another example that you can simply trigger everything you want and prevent the buff icon at all (since you don't use the base ability for that), the problem here is: there's no way to trigger the 'condition' stun/sleep/root/etc. We have no access to the exact function Thunderbolt/Sleep/Silence/Doom/etc use to trigger this 'condition'.


So I think you can do 2 things in this case:

1) Accept the second buff icon and it's ok, an engine limitation (and to be honest that's the best answer for me).

2) Use the Disable System to manage the duration, and etc, and accept the fact that this Buff System will not dispel it (but you can manually do it, just removing the ability from the unit using the buff id instead of the ability id as parameter, unsure if works for all buffs, stun (thunderbolt), silence (drunken haze) and slow (cripple) removes just fine)

  • Untitled Trigger 001
    • Events
    • Conditions
    • Actions
      • Unit - Remove 'Stun/Silence/Doom/Etc' buff from (Triggering unit)
JASS:
call UnitRemoveAbility(unit, 'B000') //Where B000 = buff rawcode on object data
 
Level 7
Joined
Feb 9, 2021
Messages
301
Well, that was something I was thinking about today morning, and I don't think you can hide a debuff icon, at least not stun/sleep/root one. Obviously, there's another example that you can simply trigger everything you want and prevent the buff icon at all (since you don't use the base ability for that), the problem here is: there's no way to trigger the 'condition' stun/sleep/root/etc. We have no access to the exact function Thunderbolt/Sleep/Silence/Doom/etc use to trigger this 'condition'.


So I think you can do 2 things in this case:

1) Accept the second buff icon and it's ok, an engine limitation (and to be honest that's the best answer for me).

2) Use the Disable System to manage the duration, and etc, and accept the fact that this Buff System will not dispel it (but you can manually do it, just removing the ability from the unit using the buff id instead of the ability id as parameter, unsure if works for all buffs, stun (thunderbolt), silence (drunken haze) and slow (cripple) removes just fine)

  • Untitled Trigger 001
    • Events
    • Conditions
    • Actions
      • Unit - Remove 'Stun/Silence/Doom/Etc' buff from (Triggering unit)
JASS:
call UnitRemoveAbility(unit, 'B000') //Where B000 = buff rawcode on object data
There should be a better way. Also, you should check my comments and replies of the author under the Disable System.
 
Level 7
Joined
Oct 11, 2008
Messages
304
There should be a better way. Also, you should check my comments and replies of the author under the Disable System.

I read the thread, and the author is correct, he suggests a buff system for it.

The point is: using this buff system will not work as you want. Not without edit the Disable System.

This system uses a trick with Slow Aura (Tornado), which is an ability from the 'unit' Tornado (I use 'unit' because it's an effect, but using a unit to be selected by the player). The trick is simple, the Tornado has a slow aura, applying movement slow to all enemies, it's a passive ability from Tornado itself but if you select the Tornado, you don't see the icon, because it's hidden. This is used for some systems to add a specific buff to the unit giving the ability Slow Aura (Tornado) to the unit affected by the Aura/Buff System and control everything else internally, like duration (you can easily remove the ability Slow Aura (Tornado) and the buff is gone), effect, stats, etc.

4J18Cp6.png


As I said before, you can't do it with both systems without edit Disable System because Buff System APPLY the buff BEFORE Disable System detect the buff and issue the cast order.

So, if you try to do it without edit Disable System, it's like ignoring all the checks for the unit has buff (because it HAS the buff, because Buff System Apply BEFORE Disable System).

Here's what could be done: Use the buff id from stun (will just explain stun, same should work with other status buffs) into this system. Just remember what documentation says.

JASS:
c. Create a new Buff based on "Tornado (Slow Aura)" [Basl]. It is very important
          that the rawcode of this new Buff is exactly the same as the newly created
          Ability done in Step 4.a except the first letter which depends on the value
          of BUFF_OFFSET.
          Example:
            BUFF_OFFSET = 0x01000000
                Ability - 'AXYZ'
                Buff    - 'BXYZ'
            BUFF_OFFSET = 0x20000000
                Ability - 'AXYZ'
                Buff    - 'aXYZ'

So Stun buff id is B00D you need an ability with id A00D to work. I created a new buff for the ability of the Buff System (because of the documentation) and changed the buff the Disable System apply to the new one (also renamed the old one to don't make confusion).

and do your buff:

JASS:
scope Stun
    struct StunBuff extends Buff
        private static constant integer RAWCODE = 'A00D' //Stun based on Slow Aura (Tornado) with buff id 'B00D'
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE =  BUFF_STACK_PARTIAL
      
        method onRemove takes nothing returns nothing
            call BJDebugMsg("BUFF SYSTEM: Stun removed from the unit " + GetUnitName(this.target))
            //the Buff is removed by the Buff System itself
            // and to
        endmethod
      
        method onApply takes nothing returns nothing
            set udg_GDS_Type = udg_GDS_cSTUN
            set duration = udg_GDS_Duration
            call TriggerExecute(gg_trg_GDS_Main_Modifier_Example_2) //change this for your 'GDS Main Modifier'
          
            call BJDebugMsg("BUFF SYSTEM: Stun applied to unit " + GetUnitName(this.target) + " with duration: " + R2S(duration))
        endmethod
      
        implement BuffApply
    endstruct
endscope

As I said before: 1) Accept the second buff icon and it's ok, an engine limitation (and to be honest that's the best answer for me).

It's Warcraft III, there's not always a better way, but vJass could be a lot simple.
 

Attachments

  • Guhun Stun System.w3x
    171.5 KB · Views: 29
Level 7
Joined
Feb 9, 2021
Messages
301
I read the thread, and the author is correct, he suggests a buff system for it.

The point is: using this buff system will not work as you want. Not without edit the Disable System.

This system uses a trick with Slow Aura (Tornado), which is an ability from the 'unit' Tornado (I use 'unit' because it's an effect, but using a unit to be selected by the player). The trick is simple, the Tornado has a slow aura, applying movement slow to all enemies, it's a passive ability from Tornado itself but if you select the Tornado, you don't see the icon, because it's hidden. This is used for some systems to add a specific buff to the unit giving the ability Slow Aura (Tornado) to the unit affected by the Aura/Buff System and control everything else internally, like duration (you can easily remove the ability Slow Aura (Tornado) and the buff is gone), effect, stats, etc.

4J18Cp6.png


As I said before, you can't do it with both systems without edit Disable System because Buff System APPLY the buff BEFORE Disable System detect the buff and issue the cast order.

So, if you try to do it without edit Disable System, it's like ignoring all the checks for the unit has buff (because it HAS the buff, because Buff System Apply BEFORE Disable System).

Here's what could be done: Use the buff id from stun (will just explain stun, same should work with other status buffs) into this system. Just remember what documentation says.

JASS:
c. Create a new Buff based on "Tornado (Slow Aura)" [Basl]. It is very important
          that the rawcode of this new Buff is exactly the same as the newly created
          Ability done in Step 4.a except the first letter which depends on the value
          of BUFF_OFFSET.
          Example:
            BUFF_OFFSET = 0x01000000
                Ability - 'AXYZ'
                Buff    - 'BXYZ'
            BUFF_OFFSET = 0x20000000
                Ability - 'AXYZ'
                Buff    - 'aXYZ'

So Stun buff id is B00D you need an ability with id A00D to work. I created a new buff for the ability of the Buff System (because of the documentation) and changed the buff the Disable System apply to the new one (also renamed the old one to don't make confusion).

and do your buff:

JASS:
scope Stun
    struct StunBuff extends Buff
        private static constant integer RAWCODE = 'A00D' //Stun based on Slow Aura (Tornado) with buff id 'B00D'
        private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
        private static constant integer STACK_TYPE =  BUFF_STACK_PARTIAL
    
        method onRemove takes nothing returns nothing
            call BJDebugMsg("BUFF SYSTEM: Stun removed from the unit " + GetUnitName(this.target))
            //the Buff is removed by the Buff System itself
            // and to
        endmethod
    
        method onApply takes nothing returns nothing
            set udg_GDS_Type = udg_GDS_cSTUN
            set duration = udg_GDS_Duration
            call TriggerExecute(gg_trg_GDS_Main_Modifier_Example_2) //change this for your 'GDS Main Modifier'
        
            call BJDebugMsg("BUFF SYSTEM: Stun applied to unit " + GetUnitName(this.target) + " with duration: " + R2S(duration))
        endmethod
    
        implement BuffApply
    endstruct
endscope

As I said before: 1) Accept the second buff icon and it's ok, an engine limitation (and to be honest that's the best answer for me).

It's Warcraft III, there's not always a better way, but vJass could be a lot simple.
Thanks, I see what you mean. I can't open your map because I am on war 1.26, but I will try to implement it.
1) Do you think there are other systems that can address this problem?
2) Do you think the memory hack might help?

I also thought to make two slow debuffs based on the disabled system, each with 100 levels (-1% ms, -2% ms and etc). I wanted to do it because I want to have an ability that slows enemies' attack speed and movement speed depending on how long they stay in the area. Therefore, I would have duration of their presence * level of the ability to get my slow debuff. However, this would mean 3 debuffs. Also, do you think it is a good way to do this?
 
Level 7
Joined
Oct 11, 2008
Messages
304
Thanks, I see what you mean. I can't open your map because I am on war 1.26, but I will try to implement it.
1) Do you think there are other systems that can address this problem?
2) Do you think the memory hack might help?

I also thought to make two slow debuffs based on the disabled system, each with 100 levels (-1% ms, -2% ms and etc). I wanted to do it because I want to have an ability that slows enemies' attack speed and movement speed depending on how long they stay in the area. Therefore, I would have duration of their presence * level of the ability to get my slow debuff. However, this would mean 3 debuffs. Also, do you think it is a good way to do this?
Sorry if I sounded rude, was not my intention, my English is not very well :(.

I'll post the GDS vJass tagged on the post to you compare the difference (I would suggest a text comparison because it's 2 lines I think xD) but I'm unsure if the changes would affect in a negative way other things, need to test through.

JASS:
function GDSDebuffApplied takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer tHandle = GetHandleId(t)
    local integer stunType = LoadInteger(udg_GDS_Hashtable, tHandle , 1)
    local unit u = LoadUnitHandle(udg_GDS_Hashtable, tHandle , 0)

    if GetUnitAbilityLevel( u , udg_GDS_BUFF[stunType]) != 0 then
       
        call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(t))
        call PauseTimer(t)
        call DestroyTimer(t)

        set udg_GDS_DebuffEvent = stunType
        set udg_GDS_Target = u
        set udg_GDS_DebuffEvent = 0.5
    else
        call TimerStart(t, udg_GDS_zMIN, false, function GDSDebuffApplied) 
    endif
    set u = null
    set t = null
endfunction

function GDSDebuffEnd takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer tHandle = GetHandleId(t)
    local integer stunType = LoadInteger(udg_GDS_Hashtable, tHandle , 2)
    local unit u = LoadUnitHandle(udg_GDS_Hashtable, tHandle , 0)
    local integer uHandle = LoadInteger(udg_GDS_Hashtable, tHandle , 1)

    call UnitRemoveAbility(u, udg_GDS_BUFF[stunType])

    call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(t))
    call PauseTimer(t)
    call DestroyTimer(t)
    call RemoveSavedHandle(udg_GDS_Hashtable, uHandle , stunType)

    set u = null
    set t = null
endfunction

function GDSMain takes nothing returns nothing
    local timer t
    local timer timerOld = LoadTimerHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type )
    local real duration = udg_GDS_Duration
    local boolean hadTimer = timerOld != null

   
    set udg_GDS_Remaining = TimerGetRemaining(timerOld)
//Check if user wants a permanent debuff
    if udg_GDS_Permanent then
        set udg_GDS_Permanent = false
        if hadTimer then

            call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(timerOld))
            call PauseTimer(timerOld)
            call DestroyTimer(timerOld)
            call RemoveSavedHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type)

            set timerOld = null
        else
            if IssueTargetOrder( udg_GDS_DUMMY[udg_GDS_Type], udg_GDS_ORDERS[udg_GDS_Type], udg_GDS_Target ) or GetUnitAbilityLevel(udg_GDS_Target , udg_GDS_BUFF[udg_GDS_Type]) != 0 then
                set udg_GDS_Remaining = -1 //-1 for infinite duration
            else
                set udg_GDS_Remaining = 0
            endif
        endif

        if udg_GDS_RegisterDisable then
            set t = CreateTimer()
            call SaveUnitHandle(udg_GDS_Hashtable, GetHandleId(t) , 0 , udg_GDS_Target)
            call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 1 , udg_GDS_Type)
            call TimerStart(t, udg_GDS_zMIN, false, function GDSDebuffApplied)
            set t = null              
        endif

        return
    endif
   
//Check if user wants to reduce duration of stun (check for negative duration)
    if duration < -udg_GDS_zMIN and udg_GDS_Remaining > udg_GDS_zMIN then  //Check for reduction
        if -duration > udg_GDS_Remaining then  //Check if reduction goes below 0
            call UnitRemoveAbility(udg_GDS_Target , udg_GDS_BUFF[udg_GDS_Type])
            set duration = 0
        else
            set duration = udg_GDS_Remaining - duration //Change duration so it passes the checks
        endif
    endif

//Check if the desired duration is higher than remaining time
    if  udg_GDS_Remaining < duration and duration > udg_GDS_zMIN and hadTimer then

        if hadTimer then
            set t = timerOld
        else
            set t = CreateTimer()
            set timerOld = null
        endif
    else
        //Check if unit has buff, clean hashtable, destroy timer and set remaining time to 0
        if GetUnitAbilityLevel(udg_GDS_Target, udg_GDS_BUFF[udg_GDS_Type]) == 0 or udg_GDS_Remaining <= 0. then //Laiev - edited this because the Buff System apply BEFORE this run
            call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(timerOld))
            call PauseTimer(timerOld)
            call DestroyTimer(timerOld)
            call RemoveSavedHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type)

            set timerOld = null

            if duration < udg_GDS_zMIN then
                set udg_GDS_Remaining = 0
                return
            endif

            set t = CreateTimer()

        else

            if hadTimer then
                set timerOld = null
            else           
                set udg_GDS_Remaining = -1
            endif
            return       
        endif     
    endif


//Check was successful, proceed to attempt to stun
    if not IssueTargetOrder( udg_GDS_DUMMY[udg_GDS_Type], udg_GDS_ORDERS[udg_GDS_Type], udg_GDS_Target ) then //Laiev - Also edited here because Buff System Apply before this
        set timerOld = null
        set udg_GDS_Remaining = 0
       
        return //unable to stun, do nothing
    endif
   
//Check again for duration reduction (negative duration)   
    if udg_GDS_Duration < 0 then
        set duration = duration + 2*udg_GDS_Duration //Change duration to reduced duration
    endif
//Save Hashtable values only if a new timer has been created   
    if not hadTimer then
        call SaveUnitHandle(udg_GDS_Hashtable, GetHandleId(t) , 0 , udg_GDS_Target)
        call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 1 , GetHandleId(udg_GDS_Target))
        call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 2 , udg_GDS_Type)
        call SaveTimerHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type , t)
    endif
   

    call TimerStart(t, duration , false, function GDSDebuffEnd)
  
   
    set udg_GDS_Remaining = duration
   

    if udg_GDS_RegisterDisable then
        set t = CreateTimer()
        call SaveUnitHandle(udg_GDS_Hashtable, GetHandleId(t) , 0 , udg_GDS_Target)
        call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 1 , udg_GDS_Type)
        call TimerStart(t, 2*udg_GDS_zMIN, false, function GDSDebuffApplied)              
    endif
   
    set timerOld = null
    set t = null
   
endfunction



//===========================================================================
function InitTrig_GDS_Main takes nothing returns nothing
    set gg_trg_GDS_Main = CreateTrigger(  )
    call TriggerAddAction( gg_trg_GDS_Main, function GDSMain )
endfunction

  • New Stun selection
    • Events
      • Player - Player 1 (Red) types a chat message containing -stun as A substring
    • Conditions
    • Actions
      • Set VariableSet GDS_Duration = (Real((Substring((Entered chat string), 7, 10))))
      • Game - Display to (All players) the text: (Want to stun unit for: + (String(GDS_Duration)))
      • Unit Group - Pick every unit in (Units currently selected by Player 1 (Red)) and do (Actions)
        • Loop - Actions
          • Set VariableSet GDS_Target = (Picked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • GDS_Duration Less than or equal to 0.00
            • Then - Actions
              • Trigger - Run GDS Main <gen> (ignoring conditions) //I disabled this, because there's no infinite duration on BuffSystem
            • Else - Actions
              • Custom script: call StunBuff.add(null, udg_GDS_Target) //this 'null' should be the source of the buff itself, but was just an example
              • Trigger - Run GDS Main Modifier Example 2 <gen> (ignoring conditions)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • GDS_Remaining Equal to -1.00
            • Then - Actions
              • Game - Display to (All players) the text: Unit is permanently...
            • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • GDS_Duration Equal to GDS_Remaining
                • Then - Actions
                  • Game - Display to (All players) the text: (Unit Stunned for: + (String(GDS_Remaining)))
                • Else - Actions
                  • Game - Display to (All players) the text: Unit was already st...
In my old times on 1.26, if I remember right, I used BuffStruct from Jesus4Lyf for this, but I think it uses the same method of Slow Aura (Tornado) (it also generate the ability and the buff itself by the system), there's also BuffGenerator from old friend LuizBills that is approved here on hive (but I never used on my personal map, and seen like a little bit over-complicated).

What you mean with a memory hack? o.o

I see you're starting on Warcraft 3 Editor, and to be honest, if you wanna stay with this Buff System, you can use the method I told you before, it would work pretty well and also you have Dynamic and Standard buff types in the same situation (and because of you I started to standard my buffs on my project lol stun, silence, fear, and was thinking about a lot more).

Now a way you can do this, well, you could do your own system (and it doesn't need to be vJass or Jass or too complicated), you could use the Disable System and with its functionality, if you want to implement a reduction on CC (like Mercury's from LoL) I think the demo map already have an example how, use the GDS Main Modifier trigger, it's for dynamic assign of duration on the CC. If you want a way to remove the CC (like QSS from LoL) you can remove the buff yourself from GDS to your standard buffs and use this BuffSystem to your dynamic buffs.

For example:

JASS:
private static method onCast takes nothing returns nothing
    local integer counter = 3
    local unit u = GetTriggerUnit()
   
    //- UnitRemoveAbility returns a boolean
    //It returns true if removed, and false if not
    //That means true = unit had the ability
    //Or false = unit didn't had the ability
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cStun]) then
        set counter = counter - 1
    endif
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cStun]) then
        set counter = counter - 1
    endif
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cSilence]) then
        set counter = counter - 1
    endif
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cSnare]) then
        set counter = counter - 1
    endif
   
    call Buff.pickBuffs(GetTriggerUnit())
    implement BuffListStart
        exitwhen counter <= 0
        if Buff.picked.dispelType == BUFF_NEGATIVE then
            call Buff.picked.remove()
            set counter = counter - 1
        endif
    implement BuffListEnd
endmethod

For your slow, I suggest using an old system for Attributes/Status, because it's safer and easier. In my old times, I used to use Status by Jesus4Lyf but there's a ton of systems like that those days, unsure if compatible with 1.26, since now we have access directly to the object editor using trigger with the new function on 1.31/1.32.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Sorry if I sounded rude, was not my intention, my English is not very well :(.

I'll post the GDS vJass tagged on the post to you compare the difference (I would suggest a text comparison because it's 2 lines I think xD) but I'm unsure if the changes would affect in a negative way other things, need to test through.

JASS:
function GDSDebuffApplied takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer tHandle = GetHandleId(t)
    local integer stunType = LoadInteger(udg_GDS_Hashtable, tHandle , 1)
    local unit u = LoadUnitHandle(udg_GDS_Hashtable, tHandle , 0)

    if GetUnitAbilityLevel( u , udg_GDS_BUFF[stunType]) != 0 then
  
        call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(t))
        call PauseTimer(t)
        call DestroyTimer(t)

        set udg_GDS_DebuffEvent = stunType
        set udg_GDS_Target = u
        set udg_GDS_DebuffEvent = 0.5
    else
        call TimerStart(t, udg_GDS_zMIN, false, function GDSDebuffApplied)
    endif
    set u = null
    set t = null
endfunction

function GDSDebuffEnd takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer tHandle = GetHandleId(t)
    local integer stunType = LoadInteger(udg_GDS_Hashtable, tHandle , 2)
    local unit u = LoadUnitHandle(udg_GDS_Hashtable, tHandle , 0)
    local integer uHandle = LoadInteger(udg_GDS_Hashtable, tHandle , 1)

    call UnitRemoveAbility(u, udg_GDS_BUFF[stunType])

    call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(t))
    call PauseTimer(t)
    call DestroyTimer(t)
    call RemoveSavedHandle(udg_GDS_Hashtable, uHandle , stunType)

    set u = null
    set t = null
endfunction

function GDSMain takes nothing returns nothing
    local timer t
    local timer timerOld = LoadTimerHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type )
    local real duration = udg_GDS_Duration
    local boolean hadTimer = timerOld != null


    set udg_GDS_Remaining = TimerGetRemaining(timerOld)
//Check if user wants a permanent debuff
    if udg_GDS_Permanent then
        set udg_GDS_Permanent = false
        if hadTimer then

            call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(timerOld))
            call PauseTimer(timerOld)
            call DestroyTimer(timerOld)
            call RemoveSavedHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type)

            set timerOld = null
        else
            if IssueTargetOrder( udg_GDS_DUMMY[udg_GDS_Type], udg_GDS_ORDERS[udg_GDS_Type], udg_GDS_Target ) or GetUnitAbilityLevel(udg_GDS_Target , udg_GDS_BUFF[udg_GDS_Type]) != 0 then
                set udg_GDS_Remaining = -1 //-1 for infinite duration
            else
                set udg_GDS_Remaining = 0
            endif
        endif

        if udg_GDS_RegisterDisable then
            set t = CreateTimer()
            call SaveUnitHandle(udg_GDS_Hashtable, GetHandleId(t) , 0 , udg_GDS_Target)
            call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 1 , udg_GDS_Type)
            call TimerStart(t, udg_GDS_zMIN, false, function GDSDebuffApplied)
            set t = null         
        endif

        return
    endif

//Check if user wants to reduce duration of stun (check for negative duration)
    if duration < -udg_GDS_zMIN and udg_GDS_Remaining > udg_GDS_zMIN then  //Check for reduction
        if -duration > udg_GDS_Remaining then  //Check if reduction goes below 0
            call UnitRemoveAbility(udg_GDS_Target , udg_GDS_BUFF[udg_GDS_Type])
            set duration = 0
        else
            set duration = udg_GDS_Remaining - duration //Change duration so it passes the checks
        endif
    endif

//Check if the desired duration is higher than remaining time
    if  udg_GDS_Remaining < duration and duration > udg_GDS_zMIN and hadTimer then

        if hadTimer then
            set t = timerOld
        else
            set t = CreateTimer()
            set timerOld = null
        endif
    else
        //Check if unit has buff, clean hashtable, destroy timer and set remaining time to 0
        if GetUnitAbilityLevel(udg_GDS_Target, udg_GDS_BUFF[udg_GDS_Type]) == 0 or udg_GDS_Remaining <= 0. then //Laiev - edited this because the Buff System apply BEFORE this run
            call FlushChildHashtable(udg_GDS_Hashtable, GetHandleId(timerOld))
            call PauseTimer(timerOld)
            call DestroyTimer(timerOld)
            call RemoveSavedHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type)

            set timerOld = null

            if duration < udg_GDS_zMIN then
                set udg_GDS_Remaining = 0
                return
            endif

            set t = CreateTimer()

        else

            if hadTimer then
                set timerOld = null
            else      
                set udg_GDS_Remaining = -1
            endif
            return  
        endif
    endif


//Check was successful, proceed to attempt to stun
    if not IssueTargetOrder( udg_GDS_DUMMY[udg_GDS_Type], udg_GDS_ORDERS[udg_GDS_Type], udg_GDS_Target ) then //Laiev - Also edited here because Buff System Apply before this
        set timerOld = null
        set udg_GDS_Remaining = 0
  
        return //unable to stun, do nothing
    endif

//Check again for duration reduction (negative duration)
    if udg_GDS_Duration < 0 then
        set duration = duration + 2*udg_GDS_Duration //Change duration to reduced duration
    endif
//Save Hashtable values only if a new timer has been created
    if not hadTimer then
        call SaveUnitHandle(udg_GDS_Hashtable, GetHandleId(t) , 0 , udg_GDS_Target)
        call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 1 , GetHandleId(udg_GDS_Target))
        call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 2 , udg_GDS_Type)
        call SaveTimerHandle(udg_GDS_Hashtable, GetHandleId(udg_GDS_Target) , udg_GDS_Type , t)
    endif


    call TimerStart(t, duration , false, function GDSDebuffEnd)


    set udg_GDS_Remaining = duration


    if udg_GDS_RegisterDisable then
        set t = CreateTimer()
        call SaveUnitHandle(udg_GDS_Hashtable, GetHandleId(t) , 0 , udg_GDS_Target)
        call SaveInteger(udg_GDS_Hashtable , GetHandleId(t) , 1 , udg_GDS_Type)
        call TimerStart(t, 2*udg_GDS_zMIN, false, function GDSDebuffApplied)         
    endif

    set timerOld = null
    set t = null

endfunction



//===========================================================================
function InitTrig_GDS_Main takes nothing returns nothing
    set gg_trg_GDS_Main = CreateTrigger(  )
    call TriggerAddAction( gg_trg_GDS_Main, function GDSMain )
endfunction

  • New Stun selection
    • Events
      • Player - Player 1 (Red) types a chat message containing -stun as A substring
    • Conditions
    • Actions
      • Set VariableSet GDS_Duration = (Real((Substring((Entered chat string), 7, 10))))
      • Game - Display to (All players) the text: (Want to stun unit for: + (String(GDS_Duration)))
      • Unit Group - Pick every unit in (Units currently selected by Player 1 (Red)) and do (Actions)
        • Loop - Actions
          • Set VariableSet GDS_Target = (Picked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • GDS_Duration Less than or equal to 0.00
            • Then - Actions
              • Trigger - Run GDS Main <gen> (ignoring conditions) //I disabled this, because there's no infinite duration on BuffSystem
            • Else - Actions
              • Custom script: call StunBuff.add(null, udg_GDS_Target) //this 'null' should be the source of the buff itself, but was just an example
              • Trigger - Run GDS Main Modifier Example 2 <gen> (ignoring conditions)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • GDS_Remaining Equal to -1.00
            • Then - Actions
              • Game - Display to (All players) the text: Unit is permanently...
            • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • GDS_Duration Equal to GDS_Remaining
                • Then - Actions
                  • Game - Display to (All players) the text: (Unit Stunned for: + (String(GDS_Remaining)))
                • Else - Actions
                  • Game - Display to (All players) the text: Unit was already st...
In my old times on 1.26, if I remember right, I used BuffStruct from Jesus4Lyf for this, but I think it uses the same method of Slow Aura (Tornado) (it also generate the ability and the buff itself by the system), there's also BuffGenerator from old friend LuizBills that is approved here on hive (but I never used on my personal map, and seen like a little bit over-complicated).

What you mean with a memory hack? o.o

I see you're starting on Warcraft 3 Editor, and to be honest, if you wanna stay with this Buff System, you can use the method I told you before, it would work pretty well and also you have Dynamic and Standard buff types in the same situation (and because of you I started to standard my buffs on my project lol stun, silence, fear, and was thinking about a lot more).

Now a way you can do this, well, you could do your own system (and it doesn't need to be vJass or Jass or too complicated), you could use the Disable System and with its functionality, if you want to implement a reduction on CC (like Mercury's from LoL) I think the demo map already have an example how, use the GDS Main Modifier trigger, it's for dynamic assign of duration on the CC. If you want a way to remove the CC (like QSS from LoL) you can remove the buff yourself from GDS to your standard buffs and use this BuffSystem to your dynamic buffs.

For example:

JASS:
private static method onCast takes nothing returns nothing
    local integer counter = 3
    local unit u = GetTriggerUnit()

    //- UnitRemoveAbility returns a boolean
    //It returns true if removed, and false if not
    //That means true = unit had the ability
    //Or false = unit didn't had the ability
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cStun]) then
        set counter = counter - 1
    endif
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cStun]) then
        set counter = counter - 1
    endif
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cSilence]) then
        set counter = counter - 1
    endif
    if UnitRemoveAbility(u, udg_GDS_BUFF[udg_GDS_cSnare]) then
        set counter = counter - 1
    endif

    call Buff.pickBuffs(GetTriggerUnit())
    implement BuffListStart
        exitwhen counter <= 0
        if Buff.picked.dispelType == BUFF_NEGATIVE then
            call Buff.picked.remove()
            set counter = counter - 1
        endif
    implement BuffListEnd
endmethod

For your slow, I suggest using an old system for Attributes/Status, because it's safer and easier. In my old times, I used to use Status by Jesus4Lyf but there's a ton of systems like that those days, unsure if compatible with 1.26, since now we have access directly to the object editor using trigger with the new function on 1.31/1.32.
You didn't sound rude at all. In fact, I am very grateful for your help.

I code on Jass/vJass, and, yes, I started a month ago. I want to make this part right, so I don't have to rebuild my map later.

One problem that I see with the Disable System is that control buffs are not independent. This means you can have situations in which the second stun is shorter than the first, which would result in overall lower stun.

Edit: Memory hack

Edit2: Buff Generator looks very similar to this system, but has less configurations in terms of whether the buff positive/negative or stacking/non stacking.
 
Last edited:
Level 7
Joined
Oct 11, 2008
Messages
304
You didn't sound rude at all. In fact, I am very grateful for your help.

I code on Jass/vJass, and, yes, I started a month ago. I want to make this part right, so I don't have to rebuild my map later.

One problem that I see with the Disable System is that control buffs are not independent. This means you can have situations in which the second stun is shorter than the first, which would result in overall lower stun.

Edit: Memory hack

Edit2: Buff Generator looks very similar to this system, but has less configurations in terms of whether the buff positive/negative or stacking/non stacking.
Oh! You code on Jass/vJass, that is a lot easier them :D
You can do your own system using BuffSystem

JASS:
struct StunBuff extends Buff
    private static constant integer RAWCODE = 'ASTU'
    private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
    private static constant integer STACK_TYPE =  BUFF_STACK_FULL
      
    method onRemove takes nothing returns nothing
        //nothing, because this system will remove the buff icon used by thunderbolt
    endmethod
      
    method onApply takes nothing returns nothing
        local unit u = CreateUnit(dummy...)
        call UnitAddAbility(u, 'ACST) //this ability a thunderbolt with duration 1000.
        call IssueTargetOrder(u, "thunderbolt", this.target)
           
        //Recycle your dummy with timed life or a recycle system
        set u = null
    endmethod
      
    implement BuffApply
endstruct


local StunBuff sb = StunBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
set sb.duration = 10.

You can do crazy things with BuffSystem, for example, a ChillingBuff, after 3 application, the next one will freeze (stun) the target, there's an example like this for a DoT on the map at the first post

And thx for the thread on Memory Hack :D

EDIT: There's also a buff like that on LoL, that stack and work with multiple sources.

Teemo dart can build poison for Cassiopeia's E :D
 
Level 7
Joined
Feb 9, 2021
Messages
301
Oh! You code on Jass/vJass, that is a lot easier them :D
You can do your own system using BuffSystem

JASS:
struct StunBuff extends Buff
    private static constant integer RAWCODE = 'ASTU'
    private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
    private static constant integer STACK_TYPE =  BUFF_STACK_FULL
     
    method onRemove takes nothing returns nothing
        //nothing, because this system will remove the buff icon used by thunderbolt
    endmethod
     
    method onApply takes nothing returns nothing
        local unit u = CreateUnit(dummy...)
        call UnitAddAbility(u, 'ACST) //this ability a thunderbolt with duration 1000.
        call IssueTargetOrder(u, "thunderbolt", this.target)
          
        //Recycle your dummy with timed life or a recycle system
        set u = null
    endmethod
     
    implement BuffApply
endstruct


local StunBuff sb = StunBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
set sb.duration = 10.

You can do crazy things with BuffSystem, for example, a ChillingBuff, after 3 application, the next one will freeze (stun) the target, there's an example like this for a DoT on the map at the first post

And thx for the thread on Memory Hack :D

EDIT: There's also a buff like that on LoL, that stack and work with multiple sources.

Teemo dart can build poison for Cassiopeia's E :D
Thanks, let me try this! If this works well, then I can do something similar to the Disable System for Stun Reduction.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Oh! You code on Jass/vJass, that is a lot easier them :D
You can do your own system using BuffSystem

JASS:
struct StunBuff extends Buff
    private static constant integer RAWCODE = 'ASTU'
    private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
    private static constant integer STACK_TYPE =  BUFF_STACK_FULL
     
    method onRemove takes nothing returns nothing
        //nothing, because this system will remove the buff icon used by thunderbolt
    endmethod
     
    method onApply takes nothing returns nothing
        local unit u = CreateUnit(dummy...)
        call UnitAddAbility(u, 'ACST) //this ability a thunderbolt with duration 1000.
        call IssueTargetOrder(u, "thunderbolt", this.target)
          
        //Recycle your dummy with timed life or a recycle system
        set u = null
    endmethod
     
    implement BuffApply
endstruct


local StunBuff sb = StunBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
set sb.duration = 10.

You can do crazy things with BuffSystem, for example, a ChillingBuff, after 3 application, the next one will freeze (stun) the target, there's an example like this for a DoT on the map at the first post

And thx for the thread on Memory Hack :D

EDIT: There's also a buff like that on LoL, that stack and work with multiple sources.

Teemo dart can build poison for Cassiopeia's E :D
I don't get it. In your code, you add a certain buff with a buff system for 10 seconds and add an infinite stun. There is nothing to remove the stun.
 
Level 7
Joined
Oct 11, 2008
Messages
304
I don't get it. In your code, you add a certain buff with a buff system for 10 seconds and add an infinite stun. There is nothing to remove the stun.
In my example, you add the stun using the call StunBuff.add(source, target), inside when the buff is applied (tornado aura: ASTU with buff BSTU) is created a unit and cast thunderbolt to the target (with the same buff BSTU in the object editor), the Buff System will remove the BSTU buff, which is used by the same thunderbolt after the duration, so it REMOVES the thunderbolt stun 'indirectly', because it's the same buff id used by the BuffSystem. Using this method, you only have 1 buff icon (because of how warcraft 3 is, there's no way to add multiple buff with the same id) and a stun working how you want :D
 
Level 7
Joined
Feb 9, 2021
Messages
301
In my example, you add the stun using the call StunBuff.add(source, target), inside when the buff is applied (tornado aura: ASTU with buff BSTU) is created a unit and cast thunderbolt to the target (with the same buff BSTU in the object editor), the Buff System will remove the BSTU buff, which is used by the same thunderbolt after the duration, so it REMOVES the thunderbolt stun 'indirectly', because it's the same buff id used by the BuffSystem. Using this method, you only have 1 buff icon (because of how warcraft 3 is, there's no way to add multiple buff with the same id) and a stun working how you want :D
I think your example has the same problem = buffs don't have independent timers. Based on the documentation, it seems that I need BUFF_STACK_NONE. Imagine a scenario: I use a 5-sec stun on the enemy and then my teammate uses 1-sec stun on the enemy = the enemy will have 1-sec stun. What I am looking for is to have independent timers for each instance of the stun. Therefore, in my scenario, the unit should get 5 sec of stun.

Alternatively, if I stun the enemy for 5 seconds, and , 3 seconds later, my teammate stuns the enemy 3 seconds = overall stun is 6 seconds.

Obviously, with BUFF_STACK_NONE, your solution does not work. Do you think I can find something to fit the bill?

Edit: Does System - Status stack or has an independent timer? It says " supports multi-instancing stuns on one unit at once "

Edit2:
I am actually not sure whether disable system stacks or has independent timers.

Edit3: For attack speed slow, it seems like Status by Jesus4Lyf is very limited. It has only 9 levels for attack speed, and I need to change it freely from -1 to - 100.
 
Last edited:
Level 7
Joined
Oct 11, 2008
Messages
304
Well, I think is not an easy way with only one icon (buff icon I mean).

Because the way to do that is to separate timer/instance for each stun, ok, that's the easier part, now here's the problem, the stun can only be removed when all instances are clear (or 0).

This is from Status:
JASS:
method addStun takes [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL] returns [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL]
        set this.stunLevel=this.stunLevel+1
        if this.stunLevel>0 then
            static if not PERMENANTLY_REVEAL then
                call [URL='http://wiki.thehelper.net/wc3/jass/common.j/UnitShareVision']UnitShareVision[/URL](this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL],DUMMY_CASTER_OWNER,true)
            endif
            call [URL='http://wiki.thehelper.net/wc3/jass/common.j/IssueTargetOrderById']IssueTargetOrderById[/URL]([URL='http://wiki.thehelper.net/wc3/jass/common.j/thistype']thistype[/URL].dummyCaster,OID_STUN,this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL])
            static if not PERMENANTLY_REVEAL then
                call [URL='http://wiki.thehelper.net/wc3/jass/common.j/UnitShareVision']UnitShareVision[/URL](this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL],DUMMY_CASTER_OWNER,false)
            endif
        endif
    endmethod
    method removeStun takes [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL] returns [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL]
        set this.stunLevel=this.stunLevel-1
        if this.stunLevel==0 then
            call [URL='http://wiki.thehelper.net/wc3/jass/common.j/UnitRemoveAbility']UnitRemoveAbility[/URL](this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL],BUFF_STUN)
        endif
    endmethod
    method isStunned takes [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL] returns [URL='http://wiki.thehelper.net/wc3/jass/common.j/boolean']boolean[/URL]
        return this.stunLevel>0
    endmethod

The problem persists because of how Warcraft works.
JASS:
// First Case
onApply -> addStun (add an instance of Status) -> Unit is stunned
onRemove -> removeStun (remove an instance of Status) -> Unit is not stunned

// Second Case
onApply1 -> addStun with duration 10 -> Unit is stunned
onApply2 -> addStun with duration 3 -> Unit was already stunned
onApply3 -> addStun with duration 2 -> Unit was already stunned
onRemove2 -> removeStun (reduce counter, now counter is 2) -> Unit is stunned (because still have 2 instances)
onRemove3 -> removeStun (reduce counter, now counter is 1) -> Unit is stunned (because still have 1 instance)

And we back to the problem with 2 buff icon :/ because BuffSystem removed 2 buffs, but it can't remove stun itself, because 1 instance and 1 buff still remaining, I don't know if you gonna fix this in any way :( exactly because there's no native to stun the unit without applying a buff from an ability.
 
Level 7
Joined
Feb 9, 2021
Messages
301
Well, I think is not an easy way with only one icon (buff icon I mean).

Because the way to do that is to separate timer/instance for each stun, ok, that's the easier part, now here's the problem, the stun can only be removed when all instances are clear (or 0).

This is from Status:
JASS:
method addStun takes [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL] returns [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL]
        set this.stunLevel=this.stunLevel+1
        if this.stunLevel>0 then
            static if not PERMENANTLY_REVEAL then
                call [URL='http://wiki.thehelper.net/wc3/jass/common.j/UnitShareVision']UnitShareVision[/URL](this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL],DUMMY_CASTER_OWNER,true)
            endif
            call [URL='http://wiki.thehelper.net/wc3/jass/common.j/IssueTargetOrderById']IssueTargetOrderById[/URL]([URL='http://wiki.thehelper.net/wc3/jass/common.j/thistype']thistype[/URL].dummyCaster,OID_STUN,this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL])
            static if not PERMENANTLY_REVEAL then
                call [URL='http://wiki.thehelper.net/wc3/jass/common.j/UnitShareVision']UnitShareVision[/URL](this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL],DUMMY_CASTER_OWNER,false)
            endif
        endif
    endmethod
    method removeStun takes [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL] returns [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL]
        set this.stunLevel=this.stunLevel-1
        if this.stunLevel==0 then
            call [URL='http://wiki.thehelper.net/wc3/jass/common.j/UnitRemoveAbility']UnitRemoveAbility[/URL](this.[URL='http://wiki.thehelper.net/wc3/jass/common.j/unit']unit[/URL],BUFF_STUN)
        endif
    endmethod
    method isStunned takes [URL='http://wiki.thehelper.net/wc3/jass/common.j/nothing']nothing[/URL] returns [URL='http://wiki.thehelper.net/wc3/jass/common.j/boolean']boolean[/URL]
        return this.stunLevel>0
    endmethod

The problem persists because of how Warcraft works.
JASS:
// First Case
onApply -> addStun (add an instance of Status) -> Unit is stunned
onRemove -> removeStun (remove an instance of Status) -> Unit is not stunned

// Second Case
onApply1 -> addStun with duration 10 -> Unit is stunned
onApply2 -> addStun with duration 3 -> Unit was already stunned
onApply3 -> addStun with duration 2 -> Unit was already stunned
onRemove2 -> removeStun (reduce counter, now counter is 2) -> Unit is stunned (because still have 2 instances)
onRemove3 -> removeStun (reduce counter, now counter is 1) -> Unit is stunned (because still have 1 instance)

And we back to the problem with 2 buff icon :/ because BuffSystem removed 2 buffs, but it can't remove stun itself, because 1 instance and 1 buff still remaining, I don't know if you gonna fix this in any way :( exactly because there's no native to stun the unit without applying a buff from an ability.
I think the problem can be solved if the system just keeps the stun with the longest remaining duration? I think it is the same as independent stacking from a practical perspective.

Edit: How does disable system handle multiple instances of the stun?

Edit2: I don't get what is the problem with instances of the stun? In the example you provided, you can just have 3 removals?
 
Last edited:
Top