• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Buff

Level 3
Joined
Jun 25, 2011
Messages
36
Hi,
I would like to share this buff system and improve it if you have suggestions.
I have tested it and I didn't find bug but it's possible that there are.
(Sorry for the bad english in the code ^^)
JASS:
library Buff /*
*************************************************************************************
*
*   Allows rapid and efficient buff development.
*
*************************************************************************************
*
*   */ uses /*
*
*       -   */ UnitIndexer /*
*       -   */ TimerUtils /*
*       -   */ DDS /*
*       -   */ optional DamageEvent /*
*
*************************************************************************************
*
*   First you have to define a buff type : create a struct that extends array and
*   implement BuffStruct module.
*
*   struct BuffTypeA extends array
*
*       implement BuffStruct
*
*   endstruct
*
*   Then you can configure your buff type with those optional parameters :
*       -   static boolean stackable = false
*               -   When a non-stackable buff is applied on a unit that already
*                   have this buff type, the old buff is refreshed.
*               -   See onRefresh event response for more informations.
*       -   static boolean permanent = false
*               -   Permanent buffs can't be dispelled.
*               -   See Dispel part for more informations.
*       -   static integer dispelPriority = -1
*               -   See Dispel part for more informations.
*       -   static integer auraId = 0
*               -   Displays the buff in the status bar of the unit.
*               -   To do this, create a spell based on slow aura, and the set
*                   targets on self.
*               -   Then add the corresponding buff.
*       -   static integer buffId = 0
*               -   This is the buff added by the slow aura.
*       -   static integer alignment = BUFF_ALIGNMENT_NEUTRAL
*               -   Buff alignment. There are three types :
*               -   BUFF_ALIGNMENT_POSITIVE
*               -   BUFF_ALIGNMENT_NEGATIVE
*               -   BUFF_ALIGNMENT_NEUTRAL
*       -   static boolean positiveBuffImmune = false
*               -   Immune unit against the positive buffs.
*       -   static boolean negativeBuffImmune = false
*               -   Immune unit against the negative buffs.
*       -   static boolean neutralBuffImmune = false
*               -   Immune unit against the neutral buffs.
*       -   static boolean positiveDispellImmune = false
*               -   Immune unit against the positive buffs dissipations.
*       -   static boolean negativeDispellImmune = false
*               -   Immune unit against the negative buffs dissipations.
*       -   static boolean neutralDispellImmune = false
*               -   Immune unit against the neutral buffs dissipations.
*       -   static boolean keepAtDeath = false
*               -   If true, the unit keep the buff at death.
*       -   static real period
*               -   Period for the periodic callback.
*       -   static integer damagePriority = 0
*               -   Priority for the onDamage method call.
*       -   static integer category = 0
*               -   Use it as you wish.
*
*   Once these settings done, it is possible to add event responses.
*   There are eight responses, all optional:
*       -   static method onApply takes Buff this returns nothing
*               -   Called when the buff is applied.
*       -   static method onPeriodic takes Buff this returns nothing
*               -   Method called periodically.
*               -   It is necessary to add a period parameter.
*
*       -   static method onRefresh takes Buff this returns nothing
*               -   Called when the buff is refreshed.
*               -   To access the settings from the old buff, use:
*                       -   previousDuration
*                       -   previousCaster
*                       -   previousLevel
*
*       -   static method onDispel takes Buff this, unit dispeler, boolean b returns nothing
*               -   Called when an attempt to buff dissipation occur.
*               -   The unit argument is the unit trying to dispel the buff.
*               -   The boolean argument is true if the dissipation was successful
*               -   (so the buff will be destroyed), false otherwise.
*               -   See Dispel part for more informations.
*
*       -   static method onExpire takes Buff this returns nothing
*               -   Called when the buff expires (the duration is null).
*               -   It is possible to redefine the duration so the buff is not destroyed.
*
*       -   static method onDeath takes Buff this returns nothing
*               -   Called when the buff target dies.
*
*       -   static method onRemove takes Buff this returns nothing
*               -   Called when the buff is destroyed.
*               -   Redefine the duration or refresh don't prevent its destruction.
*
*       -   static method onDamage takes Buff this returns nothing
*               -   Called when buff target takes damage.
*               -   Can only be used with the library DamageEvent.
*
*   The module implements a method apply to apply the buff and a method type which returns
*   the buff type.
*
*************************************************************************************
*
*   struct Buff extends array
*
*       -   readonly unit caster (use refresh method if you want to modify it)
*       -   readonly unit target
*       -   readonly integer targetId
*       -   readonly real level (use refresh method if you want to modify it)
*       -   readonly integer buffType
*       -   public real duration (method operator)
*       -   readonly real initialDuration (method operator)
*       -   static method apply takes BuffType buffType, unit caster, unit target, real level,
*           real duration returns thistype
*               -   If the duration is null, the buff will never expire.
*       -   method destroy takes nothing returns nothing
*               -   Prefer using buff dissipation instead of destroy method.
*       -   method refresh takes unit caster, real level, real duration returns thistype
*               -   Refreshes the buff with the new settings.
*       -   static method has takes unit target, BuffType buffType returns boolean
*       -   static method count takes unit target returns integer
*       -   static method countType takes unit target, BuffType buffType returns integer
*       -   static method countAlignment takes unit target, integer alignment returns integer
*       -   static method immune takes unit target, integer alignment returns boolean
*               -   Returns true if the unit is immune to buffs of the specified alignment.
*       -   static method dispelImmune takes unit target, integer alignment returns boolean
*               -   Returns true if the unit is immune to dispels of the specified alignment.
*
*
*************************************************************************************
*
*   Dispel
*   ------
*
*   -   method dispel takes unit dispeler, integer priority returns boolean
*           -   The buff will be dissipated if the priority of dispel is strictly
*               greater than the priority of the buff dissipation.
*
*   -   static method dispelAlignment takes unit dispeler, unit target, integer priority, integer alignment returns integer
*           -   Applies a dissipation on all buffs of the specified alignment to the target.
*
*   -   static method dispelAll takes unit dispeler, unit target, integer priority returns integer
*           -   Applies a dissipation on all bufff of the target.
*
*   The permanent buffs can not be dispelled, but can be destroyed through with the destroy () method.
*
*   Enumerating buffs on a unit
*   ---------------------------
*
*   Buffs unit are classified to enable efficient enumerations. Each buff has next and prev
*   members, allowing access to the next and previous buffs:
*       A1 A2 A3 C1 C2    B1 B2 B3 D1 D2    E1 E2 E3 E4 E5
*       -->-->-->-->-- -> -->-->-->-->-- -> -->-->-->-->--
*       Positives buffs   Negatives buffs   Neutral buffs
*
*   There are three ways to access the buffs a unit:
*       -   local Buff this = Buff[unit]
*               -   Returns the first buff of the unit (A1 in the example) :
*               -   loop
*                       exitwhen this == 0
*                       set this = this.next
*                   endloop
*       -   local Buff this = Buff[unit][type]
*               -   Returns the first unit buff of the specified type (A1, C1, B1, D1 or E1 in the example)
*               -   loop
*                       exitwhen this.type != myType
*                       set this = this.next
*                   endloop
*       -   local Buff this = Buff[unit][alignment]
*               -   Returns the first unit buff of the specified alignment (A1, B1 or E1 in the example)
*               -   loop
*                       exitwhen this.type.alignment != monAlignement
*                       set this = this.next
*                   endloop
*
*************************************************************************************/
globals

    key BUFF_ALIGNMENT_POSITIVE
    key BUFF_ALIGNMENT_NEGATIVE
    key BUFF_ALIGNMENT_NEUTRAL

endglobals

globals

    private constant integer applyEvent = 0
    private constant integer refreshEvent = 1
    private constant integer dispelEvent = 2
    private constant integer removeEvent = 3
    private constant integer expireEvent = 4
    private constant integer deathEvent = 5

    private boolean eventBool
    private integer triggerBuff
    private boolexpr array buffEvent
    private integer eventType
    private trigger eventTrig = CreateTrigger()

endglobals

globals

    private real array buffTypePeriod
    private boolean array buffTypeStackable
    private integer array buffTypeAuraId
    private integer array buffTypeBuffId
    private integer array buffTypeAlignment
    private integer array buffTypeDispelPriority
    private boolean array buffTypePermanent
    private boolean array buffTypePositiveBuffImmune
    private boolean array buffTypeNegativeBuffImmune
    private boolean array buffTypeNeutralBuffImmune
    private boolean array buffTypePositiveDispelImmune
    private boolean array buffTypeNegativeDispelImmune
    private boolean array buffTypeNeutralDispelImmune
    private boolean array buffTypeKeepAtDeath
    
    private boolean array buffTypeHasApplyEvent
    private boolean array buffTypeHasDeathEvent
    private boolean array buffTypeHasRemoveEvent
    private boolean array buffTypeHasDispelEvent
    private boolean array buffTypeHasExpireEvent
    private boolean array buffTypeHasRefreshEvent
    
    private integer array buffTypeCategory
    
endglobals

struct BuffType extends array

    method operator stackable takes nothing returns boolean
        return buffTypeStackable[this]
    endmethod

    method operator period takes nothing returns real
        return buffTypePeriod[this]
    endmethod

    method operator auraId takes nothing returns integer
        return buffTypeAuraId[this]
    endmethod
    
    method operator buffId takes nothing returns integer
        return buffTypeBuffId[this]
    endmethod

    method operator alignment takes nothing returns integer
        return buffTypeAlignment[this]
    endmethod

    method operator dispelPriority takes nothing returns integer
        return buffTypeDispelPriority[this]
    endmethod

    method operator permanent takes nothing returns boolean
        return buffTypePermanent[this]
    endmethod

    method operator positiveBuffImmune takes nothing returns boolean
        return buffTypePositiveBuffImmune[this]
    endmethod

    method operator negativeBuffImmune takes nothing returns boolean
        return buffTypeNegativeBuffImmune[this]
    endmethod

    method operator neutralBuffImmune takes nothing returns boolean
        return buffTypeNeutralBuffImmune[this]
    endmethod

    method operator positiveDispelImmune takes nothing returns boolean
        return buffTypePositiveDispelImmune[this]
    endmethod

    method operator negativeDispelImmune takes nothing returns boolean
        return buffTypeNegativeDispelImmune[this]
    endmethod

    method operator neutralDispelImmune takes nothing returns boolean
        return buffTypeNeutralDispelImmune[this]
    endmethod

    method operator keepAtDeath takes nothing returns boolean
        return buffTypeKeepAtDeath[this]
    endmethod

    method operator hasApplyEvent takes nothing returns boolean
        return buffTypeHasApplyEvent[this]
    endmethod
    
    method operator hasDeathEvent takes nothing returns boolean
        return buffTypeHasDeathEvent[this]
    endmethod

    method operator hasRemoveEvent takes nothing returns boolean
        return buffTypeHasRemoveEvent[this]
    endmethod
    
    method operator hasDispelEvent takes nothing returns boolean
        return buffTypeHasDispelEvent[this]
    endmethod
    
    method operator hasExpireEvent takes nothing returns boolean
        return buffTypeHasExpireEvent[this]
    endmethod

    method operator hasRefreshEvent takes nothing returns boolean
        return buffTypeHasRefreshEvent[this]
    endmethod

    method operator category takes nothing returns integer
        return buffTypeCategory[this]
    endmethod

endstruct

globals
    // Periodic timer for onPeriodic method.
    private timer array periodicTim

endglobals

private module Init

    private static method onInit takes nothing returns nothing
        local trigger death = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(death, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddCondition(death, Filter(function thistype.onDeath))
        call UnitIndexer.DEINDEX.register(Filter(function thistype.onDeindex))
    endmethod

endmodule

struct Buff extends array

    readonly static real previousLevel = 0
    readonly static unit previousCaster = null
    readonly static real previousDuration = 0

    private static integer array buffCount
    private static thistype array firstBuff

    private static hashtable table = InitHashtable()
    private static integer instanceCount = 0
    
    private thistype recycle
    readonly thistype prev
    readonly thistype next

    readonly real level
    readonly unit caster

    readonly integer targetId
    readonly BuffType buffType
    
    private timer tim

    method operator target takes nothing returns unit
        return GetUnitById(targetId)
    endmethod

    method operator initialDuration takes nothing returns real
        return TimerGetTimeout(tim)
    endmethod

    method operator duration takes nothing returns real
        return TimerGetRemaining(tim)
    endmethod

    method operator duration= takes real dur returns nothing
        if (dur > 0) then
            if (tim == null) then
                set tim = NewTimerEx(this)
            endif
            call TimerStart(tim, dur, false, function thistype.expire)
        else
            if (tim != null) then
                call ReleaseTimer(tim)
                set tim = null
            endif
        endif
    endmethod

    static method has takes unit targ, integer id returns boolean
        return HaveSavedInteger(table, GetUnitUserData(targ), id)
    endmethod

    static method count takes unit target returns integer
        return buffCount[GetUnitUserData(target)]
    endmethod

    static method countType takes unit target, integer id returns integer
        return LoadInteger(table, GetUnitUserData(target), -id)
    endmethod

    static method countAlignment takes unit target, integer align returns integer
        return LoadInteger(table, GetUnitUserData(target), -align)
    endmethod

    static method operator [] takes unit targ returns thistype
        return firstBuff[GetUnitUserData(targ)]
    endmethod

    method operator [] takes integer id returns thistype
        return LoadInteger(table, GetUnitUserData(target), id)
    endmethod

    static method immune takes unit targ, integer align returns boolean
        return LoadInteger(table, -GetUnitUserData(targ), align) > 0
    endmethod
    
    static method immuneById takes integer targetId, integer align returns boolean
        return LoadInteger(table, -targetId, align) > 0
    endmethod

    static method dispelImmune takes unit targ, integer align returns boolean
        return LoadInteger(table, -GetUnitUserData(targ), -align) > 0
    endmethod

    static method dispelImmuneById takes integer targetId, integer align returns boolean
        return LoadInteger(table, -targetId, -align) > 0
    endmethod

    // Methods for more readability
    //******************************************************************************************
    
    private static method increaseImmuneCount takes integer targetId, integer alignment returns nothing
        call SaveInteger(table, -targetId, alignment, LoadInteger(table, -targetId, alignment) + 1)
    endmethod
    
    private static method decreaseImmuneCount takes integer targetId, integer alignment returns nothing
        call SaveInteger(table, -targetId, alignment, LoadInteger(table, -targetId, alignment) - 1)
    endmethod

    private static method increaseDispelImmuneCount takes integer targetId, integer alignment returns nothing
        call SaveInteger(table, -targetId, -alignment, LoadInteger(table, -targetId, -alignment) + 1)
    endmethod
    
    private static method decreaseDispelImmuneCount takes integer targetId, integer alignment returns nothing
        call SaveInteger(table, -targetId, -alignment, LoadInteger(table, -targetId, -alignment) - 1)
    endmethod
    
    private static method increaseCount takes integer targetId, integer id returns nothing
        call SaveInteger(table, targetId, -id, LoadInteger(table, targetId, -id) + 1)
    endmethod
    
    private static method decreaseCount takes integer targetId, integer id returns nothing
        call SaveInteger(table, targetId, -id, LoadInteger(table, targetId, -id) - 1)
    endmethod

    private static method first takes integer targetId, integer id returns thistype
        return LoadInteger(table, targetId, id)
    endmethod

    private static method setFirst takes integer targetId, integer id, integer this returns nothing
        call SaveInteger(table, targetId, id, this)
    endmethod

    private static method removeCount takes integer targetId, integer id returns nothing
        call RemoveSavedInteger(table, targetId, -id)
    endmethod

    private static method removeFirst takes integer targetId, integer id returns nothing
        call RemoveSavedInteger(table, targetId, id)
    endmethod

    //********************************************************************************************

    private method fireEvent takes integer whichEvent returns nothing
        set eventType = whichEvent
        set triggerBuff = this
        call TriggerClearConditions(eventTrig)
        call TriggerAddCondition(eventTrig, buffEvent[buffType])
        call TriggerEvaluate(eventTrig)
    endmethod

    method destroy takes nothing returns nothing
        local thistype previous = first(targetId, buffType)
        if (buffType.hasRemoveEvent) then
            call fireEvent(removeEvent)
        endif
        set buffCount[targetId] = buffCount[targetId] - 1
        call decreaseCount(targetId, buffType)
        call decreaseCount(targetId, buffType.alignment)
        if (buffType.positiveBuffImmune) then
            call decreaseImmuneCount(targetId, BUFF_ALIGNMENT_POSITIVE)
        endif
        if (buffType.negativeBuffImmune) then
            call decreaseImmuneCount(targetId, BUFF_ALIGNMENT_NEGATIVE)
        endif
        if (buffType.neutralBuffImmune) then
            call decreaseImmuneCount(targetId, BUFF_ALIGNMENT_NEUTRAL)
        endif
        if (buffType.positiveDispelImmune) then
            call decreaseDispelImmuneCount(targetId, BUFF_ALIGNMENT_POSITIVE)
        endif
        if (buffType.negativeDispelImmune) then
            call decreaseDispelImmuneCount(targetId, BUFF_ALIGNMENT_NEGATIVE)
        endif
        if (buffType.neutralDispelImmune) then
            call decreaseDispelImmuneCount(targetId, BUFF_ALIGNMENT_NEUTRAL)
        endif

        if (this == previous) then
            if (buffType != next.buffType) then
                call removeFirst(targetId, buffType)
                call removeCount(targetId, buffType)
                call UnitRemoveAbility(target, buffType.auraId)
                call UnitRemoveAbility(target, buffType.buffId)
            else
                call setFirst(targetId, buffType, next)
            endif
            set previous = first(targetId, buffType.alignment)
            if (this == previous) then
                if (buffType.alignment != next.buffType.alignment) then
                    call removeFirst(targetId, buffType.alignment)
                    call removeCount(targetId, buffType.alignment)
                else
                    call setFirst(targetId, buffType.alignment, next)
                endif
            endif
        endif
        if (prev == 0) then
            set firstBuff[targetId] = next
        else
            set prev.next = next
        endif
        set next.prev = prev      

        set caster = null
        call ReleaseTimer(tim)
        set tim = null
        call ReleaseTimer(periodicTim[this])
        set periodicTim[this] = null

        set recycle = thistype(0).recycle
        set thistype(0).recycle = this
    endmethod

    method dispel takes unit cast, integer priority returns boolean
        set eventBool = priority > buffType.dispelPriority and not buffType.permanent and not dispelImmuneById(targetId, buffType.alignment)
        if (buffType.hasDispelEvent) then
            set thistype(0).caster = cast
            call fireEvent(dispelEvent)
        endif
        if (eventBool) then
            call destroy()
        endif
        return eventBool
    endmethod

    static method dispelAlignment takes unit cast, unit targ, integer priority, integer align returns integer
        local thistype this = thistype[targ][align]
        local integer c = 0
        loop
            exitwhen (buffType.alignment != align)
            set eventBool = priority > buffType.dispelPriority and not buffType.permanent and not dispelImmuneById(targetId, align)
            if (buffType.hasDispelEvent) then
                set thistype(0).caster = cast
                call fireEvent(dispelEvent)
            endif
            if (eventBool) then
                set c = c + 1
                call destroy()
            endif
            set this = next
        endloop
        return c
    endmethod

    static method dispelAll takes unit cast, unit targ, integer priority returns integer
        return dispelAlignment(cast, targ, priority, BUFF_ALIGNMENT_POSITIVE) + /*
        */     dispelAlignment(cast, targ, priority, BUFF_ALIGNMENT_NEGATIVE) + /*
        */     dispelAlignment(cast, targ, priority, BUFF_ALIGNMENT_NEUTRAL)
    endmethod

    private static method expire takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        if (buffType.hasExpireEvent) then
            call fireEvent(expireEvent)
        endif
        if (tim != null and duration == 0) then
            call destroy()
        endif
    endmethod

    method refresh takes unit cast, real lev, real dur returns thistype
        local real plev = previousLevel
        local real pdur = previousDuration
        local unit pcas = previousCaster
        set previousLevel = level
        set previousDuration = duration
        set previousCaster = caster
        set caster = cast
        set level = lev
        set duration = dur
        if (buffType.hasRefreshEvent) then
            call fireEvent(refreshEvent)
        endif
        set previousLevel = plev
        set previousDuration = pdur
        set previousCaster = pcas
        return this
    endmethod

    static method apply takes BuffType buffType, unit cast, unit targ, real lev, real duration returns thistype
        local thistype this
        local thistype previous
        local thistype align
        local integer targId = GetUnitUserData(targ)
        if (targId == 0 or immuneById(targId, buffType.alignment)) then
            return 0
        endif
        set previous = LoadInteger(table, targId, buffType)
        if (not buffType.stackable and previous != 0) then
            return previous.refresh(cast, lev, duration)
        else
            if (thistype(0).recycle != 0) then
                set this = thistype(0).recycle
                set thistype(0).recycle = recycle
            else
                set instanceCount = instanceCount + 1
                set this = instanceCount
            endif        
            
            set align = first(targId, buffType.alignment)
            if (previous == 0) then
                if (align == 0) then
                    set previous = firstBuff[targId]
                else
                    set previous = align
                endif
                call UnitAddAbility(targ, buffType.auraId)
                call SetUnitAbilityLevel(targ, buffType.auraId, R2I(lev))
                call UnitMakeAbilityPermanent(targ, true, buffType.auraId)
            endif

            if (previous == firstBuff[targId]) then
                set firstBuff[targId] = this
                call setFirst(targId, buffType.alignment, this)
            elseif (previous == align) then
                call setFirst(targId, buffType.alignment, this)
            endif

            call setFirst(targId, buffType, this)
            
            set prev = previous.prev
            set next = previous
            set prev.next = this
            set next.prev = this            

            call increaseCount(targId, buffType)
            call increaseCount(targId, buffType.alignment)
            set buffCount[targId] = buffCount[targId] + 1

            if (buffType.positiveBuffImmune) then
                call increaseImmuneCount(targId, BUFF_ALIGNMENT_POSITIVE)
            endif
            if (buffType.negativeBuffImmune) then
                call increaseImmuneCount(targId, BUFF_ALIGNMENT_NEGATIVE)
            endif
            if (buffType.neutralBuffImmune) then
                call increaseImmuneCount(targId, BUFF_ALIGNMENT_NEUTRAL)
            endif
            if (buffType.positiveDispelImmune) then
                call increaseDispelImmuneCount(targId, BUFF_ALIGNMENT_POSITIVE)
            endif
            if (buffType.negativeDispelImmune) then
                call increaseDispelImmuneCount(targId, BUFF_ALIGNMENT_NEGATIVE)
            endif
            if (buffType.neutralDispelImmune) then
                call increaseDispelImmuneCount(targId, BUFF_ALIGNMENT_NEUTRAL)
            endif
    
            set caster = cast
            set targetId = targId
            set level = lev
            set this.buffType = buffType
            if (duration > 0) then
                set tim = NewTimerEx(this)
                call TimerStart(tim, duration, false, function thistype.expire)
            endif

            if (buffType.hasApplyEvent) then
                call fireEvent(applyEvent)
            endif

        endif
        return this
    endmethod

    private static method onDeath takes nothing returns boolean
        local thistype this = firstBuff[GetUnitUserData(GetDyingUnit())]
        loop
            exitwhen (this == 0)
            if (buffType.hasDeathEvent) then
                call fireEvent(deathEvent)
            endif
            if (not buffType.keepAtDeath) then
                call destroy()
            endif
            set this = next
        endloop
        return false
    endmethod

    private static method onDeindex takes nothing returns boolean
        local integer unitId = GetIndexedUnitId()
        local thistype this = firstBuff[unitId]
        loop
            exitwhen (this == 0)
            if (buffType.hasRemoveEvent) then
                call fireEvent(removeEvent)
            endif
            if (next == 0) then
                set recycle = thistype(0).recycle
            else
                set recycle = next
            endif
            set caster = null
            call ReleaseTimer(tim)
            set tim = null
            call ReleaseTimer(periodicTim[this])
            set periodicTim[this] = null
            set this = next
        endloop
        set this = firstBuff[unitId]
        set firstBuff[unitId] = 0
        set buffCount[unitId] = 0
        call FlushChildHashtable(table, unitId)
        call FlushChildHashtable(table, -unitId)
        set thistype(0).recycle = this
        return false
    endmethod

    implement Init

endstruct

private struct DefaultValues extends array

    static boolean stackable = false
    static boolean permanent = false
    static integer dispelPriority = -1
    static integer auraId = 0
    static integer buffId = 0
    static integer alignment = BUFF_ALIGNMENT_NEUTRAL
    static boolean positiveBuffImmune = false
    static boolean negativeBuffImmune = false
    static boolean neutralBuffImmune = false
    static boolean positiveDispelImmune = false
    static boolean negativeDispelImmune = false
    static boolean neutralDispelImmune = false
    static boolean keepAtDeath = false
    
    static integer category = 0
    
    static if (LIBRARY_PriorityEvent) then
        static integer damagePriority = 0
    endif

endstruct

module BuffStruct

    private static delegate DefaultValues default

    static if (LIBRARY_DamageEvent and thistype.onDamage.exists) then
        private static method damageCall takes nothing returns boolean
            static if (thistype.damageFilter.exists) then
                local Buff this
                if (damageFilter()) then
                    set this = Buff[DDS.target][typeid]
                    loop
                        exitwhen (this == 0)
                        call onDamage(this)
                        set this = this.next
                    endloop
                endif
            else
                local Buff this = Buff[DDS.target][typeid]
                loop
                    exitwhen (this == 0)
                    call onDamage(this)
                    set this = this.next
                endloop
            endif
            return false
        endmethod
    endif

    static if (thistype.onPeriodic.exists) then
        private static method periodicCall takes nothing returns nothing
            call onPeriodic(GetTimerData(GetExpiredTimer()))
        endmethod
    endif

    private static method onEvent takes nothing returns boolean
        if (eventType == applyEvent) then
            static if thistype.onPeriodic.exists then
                set periodicTim[triggerBuff] = NewTimerEx(triggerBuff)
                call TimerStart(periodicTim[triggerBuff], buffType.period, true, function thistype.periodicCall)
            endif
            static if thistype.onApply.exists then
                call onApply(triggerBuff)
            endif
        elseif (eventType == dispelEvent) then
            static if (thistype.onDispel.exists) then
                call onDispel(triggerBuff, Buff(0).caster, eventBool)
            endif
        elseif (eventType == refreshEvent) then
            static if (thistype.onRefresh.exists) then
                call onRefresh(triggerBuff)
            endif
        elseif (eventType == removeEvent) then
            static if (thistype.onRemove.exists) then
                call onRemove(triggerBuff)
            endif
        elseif (eventType == expireEvent) then
            static if thistype.onExpire.exists then
                call onExpire(triggerBuff)
            endif
        elseif (eventType == deathEvent) then
            static if (thistype.onDeath.exists) then
                call onDeath(triggerBuff)
            endif
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        set buffEvent[typeid] = Filter(function thistype.onEvent)
        set buffTypeStackable[typeid] = stackable
        static if thistype.onPeriodic.exists then
            set buffTypePeriod[typeid] = period
        endif
        set buffTypeAuraId[typeid] = auraId
        set buffTypeBuffId[typeid] = buffId
        set buffTypeAlignment[typeid] = alignment
        set buffTypeDispelPriority[typeid] = dispelPriority
        set buffTypePermanent[typeid] = permanent
        set buffTypePositiveBuffImmune[typeid] = positiveBuffImmune
        set buffTypeNegativeBuffImmune[typeid] = negativeBuffImmune
        set buffTypeNeutralBuffImmune[typeid] = neutralBuffImmune
        set buffTypePositiveDispelImmune[typeid] = positiveDispelImmune
        set buffTypeNegativeDispelImmune[typeid] = negativeDispelImmune
        set buffTypeNeutralDispelImmune[typeid] = neutralDispelImmune
        set buffTypeKeepAtDeath[typeid] = keepAtDeath
        set buffTypeCategory[typeid] = category
        static if (LIBRARY_DamageEvent and thistype.onDamage.exists) then
            static if LIBRARY_PriorityEvent then
                call DDS.ANY.register(Filter(function thistype.damageCall), damagePriority)
            else
                call DDS.ANY.register(Filter(function thistype.damageCall))
            endif
        endif
        static if (thistype.onApply.exists) then
            set buffTypeHasApplyEvent[typeid] = true
        elseif (thistype.onPeriodic.exists) then
            set buffTypeHasApplyEvent[typeid] = true
        endif
        static if (thistype.onRefresh.exists) then
            set buffTypeHasRefreshEvent[typeid] = true
        endif
        static if (thistype.onRemove.exists) then
            set buffTypeHasRemoveEvent[typeid] = true
        endif
        static if (thistype.onExpire.exists) then
            set buffTypeHasExpireEvent[typeid] = true
        endif
        static if (thistype.onDeath.exists) then
            set buffTypeHasDeathEvent[typeid] = true
        endif
        static if (thistype.onDispel.exists) then
            set buffTypeHasDispelEvent[typeid] = true
        endif
    endmethod

    static method apply takes unit caster, unit target, real level, real duration returns Buff
        return Buff.apply(typeid, caster, target, level, duration)
    endmethod

    static method operator buffType takes nothing returns BuffType
        return typeid
    endmethod

    static method operator previousCaster takes nothing returns unit
        return Buff.previousCaster
    endmethod
    
    static method operator previousDuration takes nothing returns real
        return Buff.previousDuration
    endmethod
    
    static method operator previousLevel takes nothing returns real
        return Buff.previousLevel
    endmethod

endmodule

endlibrary

Example :
JASS:
scope MagicShield initializer onInit
//Very original name :P

struct MagicShield extends array

    private static constant integer auraId = 'A000' //Spell based on slow aura
    private static constant integer buffId = 'B000' //The buff added by the slow aura
    private static constant boolean negativeBuffImmune = true

/*
    private static method onApply takes Buff this returns nothing
        call UnitAddArmor(this.target, R2I(this.level))
    endmethod
    
    private static method onRefresh takes Buff this returns nothing
        call UnitRemoveArmor(this.target, R2I(previousLevel))
        call UnitAddArmor(this.target, R2S(this.level))
    endmethod
    
    private static method onRemove takes Buff this returns nothing
        call UnitRemoveArmor(this.target, R2I(this.level))
    endmethod
*/
    private static method onDamage takes Buff this returns nothing
        call SetUnitState(this.target, UNIT_STATE_LIFE, GetUnitState(this.target, UNIT_STATE_LIFE) + DamageEvent.amount / 2)
    endmethod

    implement BuffStruct

endstruct

private function onInit takes nothing returns nothing
    local unit caster = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
    local unit target = CreateUnit(Player(0), 'hfoo', 200, 0, 0)
    local unit attacker = CreateUnit(Player(1), 'hfoo', 300, 0, 180)
    call MagicShield.apply(caster, target, 3, 15)
    set caster = null
    set target = null
    set attacker = null
endfunction

endscope
 
Last edited:
Level 7
Joined
Jan 28, 2012
Messages
266
JASS:
*               •   BUFF_ALIGNMENT_POSITIVE
*               •   BUFF_ALIGNMENT_NEUTRAL
*               •   BUFF_ALIGNMENT_NEUTRAL
->
JASS:
*               •   BUFF_ALIGNMENT_POSITIVE
*               •   BUFF_ALIGNMENT_NEUTRAL
*               •   BUFF_ALIGNMENT_HOSTILE
we all make little mistakes:)
looks pretty nice, could you please attach an example?


I think you can change this, so it doesn't generate all of those elseifs if the buff doesn't have that method
JASS:
if (EventType == 0) then // Apply & Periodic initialization
            static if thistype.onPeriodic.exists then
                set periodicTim[TriggerBuff] = NewTimerEx(TriggerBuff)
                call TimerStart(periodicTim[TriggerBuff], BuffTypePeriod[typeid], true, function thistype.periodicCall)
            endif
            static if thistype.onApply.exists then
                call onApply(TriggerBuff)
            endif
        elseif (EventType == 1) then // dispel
            static if (thistype.onDispel.exists) then
                call onDispel(TriggerBuff, UnitEvent, BoolEvent)
            endif
        elseif (EventType == 2) then // Refresh
            static if (thistype.onRefresh.exists) then
                call onRefresh(TriggerBuff)
            endif
        elseif (EventType == 3) then // Remove
            static if (thistype.onRemove.exists) then
                call onRemove(TriggerBuff)
            endif
        elseif (EventType == 4) then // Expire
            static if thistype.onExpire.exists then
                call onExpire(TriggerBuff)
            endif
        elseif (EventType == 5) then // Death
            static if (thistype.onDeath.exists) then
                call onDeath(TriggerBuff)
            endif
        endif

Edit: adding a couple of constant integers, EventOnDeath, EventOnApply, EventOnRefresh, would help people understand what is what in your code.
 
Level 3
Joined
Jun 25, 2011
Messages
36
I think you can change this, so it doesn't generate all of those elseifs if the buff doesn't have that method
I thought that elseifs were faster than simple ifs.
The method onEvent is currently always called even if the event method doesn't exist. How can I improve it?

I am improving it for more readability, but I have one question. Should I use RemoveSavedInteger when the buff count reachs 0 (this makes the code more complex but safer I think), or should I just decrease the buff count (and flush the hashtable when the unit is removed)?

I'll add an example today.
 
Could you use dashes (-) or asterisks (*) instead of those unicode characters? :/
We have some pretty bad history with unicode on this site. I don't want to see people's resources going crazy some very unfortunate day :p

readonly BuffType type
My compiler doesn't like this very much :p
I think it would be a safer bet to call it buffType. type_p works too.

One other thing, the accepted JASS convention currently states that global variables are to be writtenLikeThis (camel-cased just like your struct members)
 
Level 7
Joined
Jan 28, 2012
Messages
266
when ever you call an event you could do a boolean check, something like bufftype.has(EVENT), or maybe it would be better just to do bufftype.hasOnDispel, bufftype.hasOnDeath etc..

then you could add to the setup module for bufftypes,
JASS:
 static if onApply.exists
                set typeid.hasOnApply=true
          endif
          //etc... this would reduce the number of TriggerEvaluates

as for getting rid of the unnecessary elifs you can do
JASS:
 if EventType == 0 then
                 //regular actions her then add

                static if thistype.onDispel.exists then
                    elseif  EventType == 1 then
                        call onDispel()
                 endif// end of static if

                 static if thistype.onWhatEvs.exists then
                     elseif EventType == whoKnows then
                         call doSomeMethod
                  endif//end of static if

                 //etc..
            endif

. Should I use RemoveSavedInteger when the buff count reachs 0 (this makes the code more complex but safer I think), or should I just decrease the buff count (and flush the hashtable when the unit is removed)?
why aren't you using table?
 
Level 3
Joined
Jun 25, 2011
Messages
36
when ever you call an event you could do a boolean check, something like bufftype.has(EVENT), or maybe it would be better just to do bufftype.hasOnDispel, bufftype.hasOnDeath etc..
To avoid 8 boolean array, I could also match a prime number to each event, and use an integer x for each buff type whose value is the product of each event. Then to verify if a buff type has an event type, it suffices to verify that x / eventType == R2I (x / eventType).
But I think it's a horrible technique : /.

then you could add to the setup module for bufftypes,
JASS:
 static if onApply.exists
                set typeid.hasOnApply=true
          endif
          //etc... this would reduce the number of TriggerEvaluates

as for getting rid of the unnecessary elifs you can do
JASS:
 if EventType == 0 then
                 //regular actions her then add

                static if thistype.onDispel.exists then
                    elseif  EventType == 1 then
                        call onDispel()
                 endif// end of static if

                 static if thistype.onWhatEvs.exists then
                     elseif EventType == whoKnows then
                         call doSomeMethod
                  endif//end of static if

                 //etc..
            endif
It doesn't work with my compiler : /.


why aren't you using table?
I can't since I use negative parent keys. And it's not necessary.
 
Level 7
Joined
Jan 28, 2012
Messages
266
To avoid 8 boolean array, I could also match a prime number to each event, and use an integer x for each buff type whose value is the product of each event. Then to verify if a buff type has an event type, it suffices to verify that x / eventType == R2I (x / eventType).
But I think it's a horrible technique : /.
you should do something like that, it would be more efficient then doing a trigger Eval

It doesn't work with my compiler : /.
meh, just realized you would have to do this
JASS:
private static method onEvent takes nothing returns boolean
        local integer eT = eventType

        if (eT == applyEvent) then
            static if thistype.onPeriodic.exists then
                set periodicTim[triggerBuff] = NewTimerEx(triggerBuff)
                call TimerStart(periodicTim[triggerBuff], buffType.period, true, function thistype.periodicCall)
            endif
            static if thistype.onApply.exists then
                call onApply(triggerBuff)
            endif
        static if (thistype.onDispel.exists) then
            //! novjass
            elseif (eT == dispelEvent) then
            //! endnovjass
                call onDispel(triggerBuff, eventUnit, eventBool)
        endif
        static if (thistype.onRefresh.exists) then
            //! novjass
            elseif (eT == refreshEvent) then
            //! endnovjass
                    call onRefresh(triggerBuff)
        endif
        static if (thistype.onRemove.exists) then
            //! novjass
            elseif (eT == removeEvent) then
            //! endnovjass
                call onRemove(triggerBuff)
        endif
        
        static if thistype.onExpire.exists then
            //! novjass
            elseif (eT == expireEvent) then
            //! endnovjass
                call onExpire(triggerBuff)
        endif
        static if (thistype.onDeath.exists) then
            //! novjass
            elseif (eT == deathEvent) then
            //! endnovjass
                call onDeath(triggerBuff)
        endif
        endif
        return false
    endmethod
and you would also need to replace deathEvent etc... in the code with , with either their generated name (you would need to switch them to public), or a local var. on the plus side it would reduce generated code size dramatically

I can't since I use negative parent keys. And it's not necessary.
you would be able to change it over, it would be a lot of hassle, but it might be better as it would reduce a maps hashtable usage. but its really not that important.

anyways cool system keep it up.
 
Awesome stuff!
Too bad this hasn't been made more early. There was a good buffstruct available on TheHelper, but it had a bug with multiple instances of the same buff that was never fixed (and I was too lazy to do it on my own), so I hardcoded almost all of my buff spells.
... and now it's way too many of them to make it worth a switch. :/
 
Btw, do you use levelable abilities to apply the buff for stacking buffs? I'm asking because I don't know if Tornado Aura (which is the most common practice for adding dummybuffs) allows multiple levels to be displayed.
If not, I really suggest doing it or adding a feature for it, because the WC3 UI will display the level of an aura ability with levels in the buff tooltip, which is useful for the player (ingame, I know you can get the number of stacks by your system) to find out how many stacks are on the target.
 
JASS:
    method operator duration takes nothing returns real
        return TimerGetRemaining(tim)
    endmethod

    method operator duration= takes real duration returns nothing
        if (duration > 0) then
            if (tim == null) then
                set tim = NewTimerEx(this)
            endif
            call TimerStart(tim, duration, false, function thistype.expire)
        else
            if (tim != null) then
                call ReleaseTimer(tim)
                set tim = null
            endif
        endif
    endmethod

I think think this brings up compiler dependence.
It would be better to have the parameter named dur so that JassHelper will /never/ use the duration method operator inside the duration= method operator. Who knows, an update might give rise to some bugs we most likely won't be aware of :p
 
Level 3
Joined
Jun 25, 2011
Messages
36
Ok i didn't understand that all these modules are implemented in DDS librairy :ogre_icwydt:.

But i have a problem : the creation of the event "ANY" is made after that the onInit method is called in BuffStruct.
 
Level 3
Joined
Jun 25, 2011
Messages
36
Ok I think all is fixed.
But as I said, if this library is added in the map after a BuffType declaration, the onInit method of the BuffType will be called before that the event 'ANY' is created.
 
Level 3
Joined
Jun 25, 2011
Messages
36
I'm already using modules initializers :s.
Or maybe I should put the onInit method from the BuffStruct module in another module ^^?
But it doesn't make sense : the method is already in a module.
 
Level 3
Joined
Jun 25, 2011
Messages
36
You mean that I should call that method :
JASS:
static method damageEventInit takes nothing returns nothing
                static if LIBRARY_PriorityEvent then
                    set ANY = PriorityEvent.create()
                else
                    set ANY = Event.create()
                endif
            endmethod
?

But this method would be called a second time then :s.
 
Oh woops

You shouldn't be having this issue of ANY not existing, cuz it's initialized inside of a module. What you are saying makes no sense.

Try using vex's jasshelper if ur running into this issue.

edit
And don't call this
JASS:
            static method damageEventInit takes nothing returns nothing
                static if LIBRARY_PriorityEvent then
                    set ANY = PriorityEvent.create()
                else
                    set ANY = Event.create()
                endif
            endmethod

Unless you wanna break everything -.-, it wasn't documented for a reason
 
Level 3
Joined
Jun 25, 2011
Messages
36
Did you read it LuizBills? It seems like you don't know what you're talking about :S
DDS is only an optional required library.
Or I missunderstood something... But I don't understand your two previous messages.

The main problem now is that Nestharus deleted all his libraries (and even if his coding style isn't the best, some of these libraries were useful, and all other libraries on hive used them, that's why I think we should reupload them even if he doesn't want to) and that the moderators are naps. If you choose to be a moderator, do your job, wtf.
 
Last edited:
oh sorry, I do not want to cause trouble.
IMO the onDamage method of your Buff system should be an Add-on/Extension (a separate script).
I think it's perfectly fine if onDamage is optional and bound to the existance of the DDS lib requirement. Seperate script is a no-go imho for a system like this. I hate plugin overloads, really. It's nice and clean as it is.
 
What's with the "needs power/time buff" tag on this resource? Also, I see no reason why this shouldn't be approved. It looks bery well-made.

One thing I have noticed, and it's not a big deal, but you can use 2 TableArrays in this resource instead of the hashtable. 1 TableArray for unit indices, and another for what you are currently using as negative unit indices (which would just be positive indices when converted to TableArray.

Either way, please get back to me on the damage/time buff. Is that what was keeping it from being approved by previous modders or was that just the resource creator planning out some future stuff?
 
Top