1. Fill your cup and take your pick among the maps best suited for this year's Hive Cup. The 6th Melee Mapping Contest Poll is up!
    Dismiss Notice
  2. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  3. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Buff System

Discussion in 'JASS Resources' started by Flux, Sep 26, 2016.

Tags:
  1. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Documentation
    Code (vJASS):

    //! 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
     


    Buff

    Code (vJASS):

    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
     



    BuffEvent
    Code (vJASS):


    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.
    Example

    Code (vJASS):

    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

    Changelog

    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.
     

    Attached Files:

    Last edited: Feb 22, 2017
  2. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    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):
    Code (vJASS):

    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:
    Code (vJASS):

    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>)
     



    [this.getType()][ids * some_big_number(1000000) + idt]
    ?
     
  3. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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.

    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.


    The current API looks fine in my opinion.

    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.
     
  4. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    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.)
    Code (vJASS):

        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?
     
  5. Yes.
     
  6. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Indeed jondrean, the answer is yes, buffs arent just supposed to be visual cues to the players that a unit is under the [+/-] effect of some ability.
     
  7. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    So is that your version of TimerUtils or something?

    I don't know, I saw it on muzzel's and the moderators didn't say anything about it.

    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: Sep 27, 2016
  8. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    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?
     
  9. chobibo

    chobibo

    Joined:
    Sep 24, 2005
    Messages:
    2,699
    Resources:
    0
    Resources:
    0
    Probably for metamorphing abilities, I dunno if I remember that right though.
     
  10. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    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:
    Code (vJASS):

        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
     
     
  11. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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: Dec 29, 2016
  12. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Updated again
    - Added BuffEvent.
    - Added double free protection debug message.
    - Added method operator duration allowing users to retrieve the current duration of a Buff instance.
     
  13. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,525
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    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. ^^
     
  14. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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.

    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.
     
  15. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,525
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    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

    I see.
    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.
     
  16. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Updated!
    - Replaced stub methods by an alternative that does not create a huge amount of triggers per Buff configuration.
    Reason

    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.
    Reason

    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.
    Reason

    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.
    Reason

    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.
    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
    Code (vJASS):

               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.
     
  17. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    562
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Flux, I guess it would be really helpful it you'd include an objectmerger/lua script for generating a new ability and buff and then wrap them up with a textmacro so that the users can easily define a new one by just inputing the last 3 digits of the rawcode.
     
  18. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    I thought about that too, but the fact that you have to restart WE to view the gwnerated object makes me think that it is easier to copy paste a Slow Aura (Tornado) ability (and it's corresponding buff) instead.
     
  19. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,525
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    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?