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

[System] BuffHandler

Discussion in 'JASS Resources' started by muzzel, Oct 1, 2013.

  1. muzzel

    muzzel

    Joined:
    Jun 27, 2008
    Messages:
    1,303
    Resources:
    2
    JASS:
    1
    Wurst:
    1
    Resources:
    2
    System BuffHandler
    requires:

    Code (vJASS):
    library BuffHandler requires AIDS, TimerUtils
    //*******************************************
    //*        .___.
    //*       /     \
    //*      | O _ O |
    //*      /  \_/  \
    //*    .' /     \ `.
    //*   / _|       |_ \
    //*  (_/ |       | \_)
    //*      \       /
    //*     __\_>-<_/__
    //*     ~;/     \;~
    //*
    //*  muzzels Spellbox presents:
    //*
    //*  BuffHandler
    //*  Version 1.1
    //*
    //*******************************************
    //*  Documentation
    //*
    //*
    //*  1. Basics
    //*  =========
    //*  1.1 Defining bufftypes
    //*  Each bufftype basically exists of one struct that holds all the variable
    //*  members and methods your buff needs. The struct has to extend "Buff" which
    //*  allows you to access most of the functionality of this system and registers
    //*  your buff for the automated bufflists of your units.
    //*  For a cleaner code it is recommended that you use a new library for every
    //*  bufftype you define.
    //*  
    //*  struct MyBuff extends Buff
    //*    // ...
    //*  endstruct
    //*  
    //*  1.2 Applying buffs to units
    //*  To bring a buff on a unit you first have to create a new instance by calling:
    //*  
    //*  local MyBuff b = MyBuff.create()
    //*  
    //*  Then you can apply the newly created buff to a unit:
    //*  call b.apply(someUnit)
    //*  
    //*  To destroy a buff simply call the destructor method:
    //*  
    //*  call b.destroy()
    //*  
    //*  1.3 Buff events
    //*  A very important thing to remember is that you may not define the normal
    //*  constructor and destructor methods for your bufftype, namely the "create"
    //*  and "onDestroy" methods. They are used by the system and writing your own
    //*  will throw an error.
    //*  Instead you may optionally implement the following methods:
    //*  
    //*  method onCreate takes nothing returns nothing
    //*  endmethod
    //*  // Will be run immediately after the buff is created.
    //*  // Use this as a replacement for the "create" method.
    //*  
    //*  method onApply takes nothing returns nothing
    //*  endmethod
    //*  // Will be run immediately after the buff is applied to a unit.
    //*  
    //*  method onRemove takes nothing returns nothing
    //*  endmethod
    //*  // Will be run right before the buff is removed from the unit.
    //*  // (e.g. applied to another unit or destroyed)
    //*  
    //*  method preDestroy takes nothing returns nothing
    //*  endmethod
    //*  // Will be run right before the buff is destroyed.
    //*  // Use this as a replacement for the "onDestroy" method.
    //*  
    //*  To access the unit the buff is currently applied to you may use the
    //*  method ".getUnit()".
    //*  If the buff is created but not yet applied it will return "null".
    //*  
    //*  1.4 Eyecandy
    //*  Defining a bufficon involves some object data creation.
    //*  First you need to create a new ability based off "Slow-Aura (Tornad)"
    //*  ('Aasl') with a buff based on "Tornado (Slow-Aura)" ('Basl'). Change the
    //*  name, tooltip and icon of the buff to whatever you want.
    //*  The name of the buff is by default displayed in red. To change it to green
    //*  add the following colortags: "|cff00ff00BuffName|r".
    //*  
    //*  To apply that bufficon to your buff just add the following method to your
    //*  struct which returns the raw id of your newly created ability:
    //*  
    //*  method getRaw takes nothing returns integer
    //*     return 'A000'
    //*  endmethod
    //*  
    //*  In addition to "getRaw" to remove the delay between the removal of the
    //*  buff and its icon you can optionally define the function:
    //*  
    //*  method getBuffRaw takes nothing returns integer
    //*     return 'B000'
    //*  endmethod
    //*  
    //*  You may also define an optional name for your buff. Note that this name
    //*  is not required and also not displayed anywhere, but some of the debugging
    //*  functions make use of it and its a good convention to define names for all
    //*  your buffs. To define a name for your buff just add the following method
    //*  to your struct which returns the desired name:
    //*  
    //*  method getName takes nothing returns string
    //*     return "MyBuff"
    //*  endmethod
    //*  
    //*  2. Advanced Features
    //*  ====================
    //*  2.1 Unapplying and Reapplying
    //*  Once applied buffs are not bound to a specific unit but can be Reapplied
    //*  (moved) to another unit which may even be "null" (the buff is then in a
    //*  not applied state, but does not get deleted). Do do this simply call the
    //*  apply method again:
    //*  
    //*  call b.apply(someOtherUnit) // reapplying the buff to someOtherUnit
    //*  call b.apply(null) // unapplying the buff
    //*  
    //*  When reapplying a buff to another unit the methods "onRemove" and
    //*  "onApply" will, if implemented, be called.
    //*  Note that in "onRemove" the method "getUnit()" will refer to the old unit
    //*  while in "onApply" it will already return the new unit.
    //*  
    //*  2.2 Buffs with Duration
    //*  To easily create a buff with limited duration you can make use of the
    //*  following method that starts a timer and destroys the buff after the
    //*  specified timeout:
    //*  
    //*  method setDuration takes real duration returns nothing
    //*  
    //*  You can either call this function inside of "onCreate" or inside of
    //*  "onApply". This will have two different effects:
    //*  When called in "onCreate" the timer will continue when the buff is being
    //*  reapplied to another unit. When called in "onApply" the timer will
    //*  restart when reapplied.
    //*  
    //*  2.3 Periodic Buffs
    //*  To easily create buffs that perform a certain action periodically you
    //*  can define the following method:
    //*  
    //*  method periodic takes nothing returns nothing
    //*    // Do something
    //*  endmethod
    //*  
    //*  You can start and stop the periodical loop with the following methods:
    //*  method startPeriodic takes real timeout returns nothing
    //*  method stopPeriodic takes nothing returns nothing
    //*  
    //*  3. BuffLists
    //*  ============
    //*  3.1 Accessing BuffLists
    //*  When applied to a unit buffs get stored in the units bufflist. Any units
    //*  bufflist can be accessed using the following function:
    //*  
    //*  function getUnitBuffList takes unit u returns BuffList
    //*  
    //*  To access the bufflist a buff is currently applied you can use the
    //*  following method of the buffs struct:
    //*  
    //*  method getParentBufflist takes nothing returns BuffList
    //*  
    //*  Note that this is faster than using "getUnitBuffList(this.getUnit())"
    //*  since it does not require the unit indexing system.
    //*  
    //*  3.2 BuffType-Ids
    //*  Something that might be useful when working with BuffLists are the
    //*  BuffType-Ids. BuffType-Ids are automatically assigned to each bufftype
    //*  and provide a simple way to compare the type of different buffs.
    //*  
    //*  To get a buffs BuffType-Id call the function:
    //*  
    //*  method getBuffTypeId takes nothing returns integer
    //*  
    //*  For technical reasons there is currently no static method that returns
    //*  the BuffType-Id, but this might be added in future versions.
    //*  
    //*  3.3 BuffList API
    //*  To get the unit a bufflist is applied to you can use the following method:
    //*  
    //*  method getUnit takes nothing returns unit
    //*  
    //*  To iterate through a bufflist object you can use its iterator:
    //*  
    //*  method iterator takes nothing returns BuffListIterator
    //*  
    //*  The BuffListIterator then offers the following methods:
    //*  
    //*  method next takes nothing returns Buff
    //*  // Returns the next Buff in the BuffList.
    //*  // You should only use this function if ".hasNext()" returned "true".
    //*  
    //*  method hasNext takes nothing returns boolean
    //*  // Returns "true" if the next ".next()" call will return a valid Buff.
    //*  // Returns "false" if there are no more buffs in the BuffList.
    //*  
    //*  method hasNoNext takes nothing returns boolean
    //*  // Negated version of "hasNext" for easier use with "endloop".
    //*  
    //*  Note that the iterators are exclusively designed for use immediately after
    //*  creation. Iterators will not work correctly after the BuffList is altered.
    //*  After using the iterator dont forget to destroy it by calling its
    //*  ".destroy()" method.
    //*  
    //*  Lets make a short example of a buff that, when applied to a unit, destroys
    //*  all other buffs from that unit except itself and other buffs of this type:
    //*  
    //*  struct MyNewBuff
    //*     method onApply takes nothing returns nothing
    //*         local BuffListIterator iter = this.getParentBufflist().iterator()
    //*         local Buff b
    //*         loop
    //*             exitwhen iter.hasNoNext()
    //*             set b = iter.next()
    //*             if (b.getBuffTypeId() != this.getBuffTypeId()) then
    //*                 call b.destroy()
    //*             endif
    //*         endloop
    //*         call iter.destroy()
    //*     endmethod
    //*  endstruct
    //*  
    //*  To count the amount of buffs of a specified type you can use the following
    //*  To count the amount of buffs of a certain type you can use the following
    //*   of a BuffList object:
    //*  
    //*  method countBuffsOfType takes Buff buff returns integer
    //*  
    //*  method countBuffsOfTypeId takes integer buffTypeId returns integer
    //*  // Use this if you only have the BuffType-Id to specify the buff type.
    //*  
    //*  4. Stacking Policies
    //*  ====================
    //*  There are different possibilities to define how multiple buffs of the same
    //*   behave when applied to the same unit.
    //*  4.1 Premade policies
    //*  For most situations selecting the right one of the premade stacking
    //*  policies will suffice. Currently there are:
    //*  
    //*  BUFF_POLICY_STACKING (default)
    //*  // An unlimited amout of buffs of this type can coexist on all units.
    //*  
    //*  BUFF_POLICY_LAST
    //*  // Buffs of this type are limited to one instance per target unit.
    //*  // The last applied buf will destroy existing ones.
    //*  
    //*  To define the policy for a bufftype you can just add the following method
    //*  to your buff struct which returns the the policy type:
    //*  
    //*  method getPolicy takes nothing returns integer
    //*     return BUFF_POLICY_LAST
    //*  endmethod
    //*  
    //*  Default is BUFF_POLICY_STACKING.
    //*  
    //*  There might be more premade policies in future versions.
    //*  
    //*  4.2 Customize Stacking Behaviour
    //*  If you require a stacking behaviour that the premade policies cannot provide
    //*  you can customize it by defining the following function:
    //*  
    //*  method checkApply takes nothing returns boolean
    //*     return true // if you simply return true this is equal to BUFF_POLICY_STACKING
    //*  endmethod
    //*  
    //*  If defined this function runs before the buff is applied to unit and before
    //*  "onApply" fires.
    //*  In the method body you can check the bufflist for other instances of this
    //*  type, remove them, modify them etc.
    //*  If this function returns true the buff will get applied.
    //*  If this function returns false the buff will not get applied but als not
    //*  deleted. If you want it deleted you may call ".delete()" right before
    //*  the return statement.
    //*  
    //*  5. Custom Members
    //*  =================
    //*  A quick way to add custom members to all buff types is the CustomMembers
    //*  module. Its defined in the top of this system and is automatically implemented
    //*  by all buff structs you define. Its method "initCustomMembers" can be used to
    //*  initialize default values to the custom members.
    //*
    //*  Changelog
    //*  =========
    //*  Version 1.1:
    //*  - Making use of "UnitMakeAbilityPermanent"
    //*  - Added the optional possibility to remove the buff directly, avoiding the small
    //*    delay between the removal of the buff and the removal of the buff icon
    //*  Version 1.0:
    //*  - Initial Release
    //*
    //*  Credits
    //*  =======
    //*  - Menag
    //*
    //*******************************************
    //*
    //* Add custom members here:
        module CustomMembers
           
            // Initialize default values to the custom members here:
            method initCustomMembers takes nothing returns nothing
            endmethod
        endmodule
    //*
    //*
    //*******************************************

        // For using with AIDS
        struct UnitBuffs extends array
            //! runtextmacro AIDS()
            BuffList bl
            method AIDS_onCreate takes nothing returns nothing
                set this.bl = BuffList.create(this.unit)
            endmethod
            method AIDS_onDestroy takes nothing returns nothing
                call this.bl.destroy()
            endmethod
        endstruct
       
        // Make sure this returns the specified units bufflist.
        function getUnitBuffList takes unit u returns BuffList
            return UnitBuffs[u].bl
        endfunction
       
    //*******************************************

        globals
            constant key BUFF_POLICY_STACKING
            constant key BUFF_POLICY_LAST
        endglobals
       
        struct Buff
            // For LinkedList:
            thistype llPrev
            thistype llNext
           
            method setLl takes thistype prev, thistype next returns nothing
                set this.llPrev = prev
                set this.llNext = next
            endmethod
           
            method getNext takes nothing returns thistype
                return this.llNext
            endmethod
           
            private BuffList parent
            private timer durationTimer
            private timer periodicTimer
           
            implement CustomMembers
           
            // Returns an id that identifies the buff type.
            method getBuffTypeId takes nothing returns integer
                return this.getType()
            endmethod
           
            // Returns the bufflist this buff is currently applied to.
            method getParentBufflist takes nothing returns BuffList
                return this.parent
            endmethod
           
            // Optional: You can set a name for the buff.
            stub method getName takes nothing returns string
                return ""
            endmethod
           
            // Optional: You can set a bufficon for the buff. Return the auras raw id.
            stub method getRaw takes nothing returns integer
                return 0
            endmethod
           
            // Optional: You can set the bufficons buff raw. This removes the delay on
            // the removal of the bufficon. Only ised if "getRaw" is defined.
            stub method getBuffRaw takes nothing returns integer
                return 0
            endmethod
           
            // Optional: You can set a policy for the buff type.
            // Default is BUFF_POLICY_STACKING
            stub method getPolicy takes nothing returns integer
                return BUFF_POLICY_STACKING
            endmethod
           
            // Optional: If and only if this returns true the buff gets applied.
            // You can use this to define your own buff policy and, if
            // needed, remove other instances from the target unit.
            stub method checkApply takes nothing returns boolean
                local BuffListIterator iter = 0
                local Buff b = 0
                if this.getPolicy() == BUFF_POLICY_STACKING then
                    return true
                elseif this.getPolicy() == BUFF_POLICY_LAST then
                    set iter = this.parent.iterator()
                    loop
                        exitwhen iter.hasNoNext()
                        set b = iter.next()
                        if ((b.getBuffTypeId() == this.getBuffTypeId()) and (b != this)) then
                            call b.destroy()
                        endif
                    endloop
                    call iter.destroy()
                    return true
                endif
                return true
            endmethod
           
            // Optional: Runs periodically.
            // Start by calling startPeriodic(real timeout).
            // Stop by calling stopPeriodic().
            stub method periodic takes nothing returns nothing
            endmethod
           
            // Optional: Fires after the buff is created.
            stub method onCreate takes nothing returns nothing
            endmethod
           
            // Optional: Fires after the buff is applied to a unit.
            // The new unit is accessable via "this.u".
            stub method onApply takes nothing returns nothing
            endmethod
           
            // Optional: Fires before the buff is removed from the unit.
            // The unit is still accessable via "this.u".
            stub method onRemove takes nothing returns nothing
            endmethod
           
            // Optional: Fires before the buff is destroyed.
            stub method preDestroy takes nothing returns nothing
            endmethod
           
            // Gets the unit the buff is applied to. Returns null if not applied to any unit.
            method getUnit takes nothing returns unit
                if this.parent != 0 then
                    return this.parent.getUnit()
                else
                    return null
                endif
            endmethod
           
            method startPeriodic takes real timeout returns nothing
                if this.periodicTimer == null then
                    set this.periodicTimer = NewTimer()
                else
                    call PauseTimer(this.periodicTimer)
                endif
                call SetTimerData(this.periodicTimer, this)
                call TimerStart(this.periodicTimer, timeout, true, function thistype.invokePeriodic)
            endmethod
           
            method stopPeriodic takes nothing returns nothing
                call ReleaseTimer(this.periodicTimer)
                set this.periodicTimer = null
            endmethod
           
            // Starts a timer that destroys the buff after the specified duration.
            method setDuration takes real duration returns nothing
                if this.durationTimer == null then
                    set this.durationTimer = NewTimer()
                else
                    call PauseTimer(this.durationTimer)
                endif
                call SetTimerData(this.durationTimer, this)
                call TimerStart(this.durationTimer, duration, false, function thistype.durationEnd)
            endmethod
           
            // Returns true if a buff of this type is currently applied on the specified unit.
            static method isOn takes unit u returns boolean
                // TODO: doesnt work without a static version of .getBufTypeId()
                return false
            endmethod
           
            private static method durationEnd takes nothing returns nothing
                local thistype b = GetTimerData(GetExpiredTimer())
                call ReleaseTimer(b.durationTimer)
                call b.destroy()
            endmethod
           
            private static method invokePeriodic takes nothing returns nothing
                local thistype b = GetTimerData(GetExpiredTimer())
                call b.periodic()
            endmethod
           
            static method create takes nothing returns thistype
                local thistype this = thistype.allocate()
                set this.parent = 0
                call initCustomMembers()
                call this.onCreate()
                return this
            endmethod
           
            private method addBuffIcon takes nothing returns nothing
                local integer raw = this.getRaw()
                if raw != null then
                    if parent.countBuffsOfType(this) <= 1 then
                        call UnitAddAbility(parent.getUnit(), raw)
                        call UnitMakeAbilityPermanent(parent.getUnit(), true, raw)
                    endif
                endif
            endmethod
           
            private method removeBuffIcon takes nothing returns nothing
                local integer raw = this.getRaw()
                local integer buffRaw
                if raw != null then
                    if parent.countBuffsOfType(this) <= 1 then
                        call UnitMakeAbilityPermanent(parent.getUnit(), false, raw)
                        call UnitRemoveAbility(parent.getUnit(), raw)
                        set buffRaw = this.getBuffRaw()
                        if buffRaw != 0 then
                            call UnitRemoveAbility(parent.getUnit(), buffRaw)
                        endif
                    endif
                endif
            endmethod
           
            // Applies the buff to the specified unit. The specified unit can be null.
            method apply takes unit target returns nothing
                if this.parent != 0 then
                    call this.onRemove()
                    call this.removeBuffIcon()
                    call this.parent.removeBuff(this)
                    set this.parent = 0
                endif
                if target != null then
                    set this.parent = getUnitBuffList(target)
                    if this.checkApply() then
                        call this.parent.addBuff(this)
                        call this.addBuffIcon()
                        call this.onApply()
                    endif
                endif
            endmethod
           
            method onDestroy takes nothing returns nothing
                call this.apply(null)
                call this.preDestroy()
                // Clean duration timer
                if this.durationTimer != null then
                    call ReleaseTimer(this.durationTimer)
                    set this.durationTimer = null
                endif
                // Clean periodic timer
                if this.periodicTimer != null then
                    call ReleaseTimer(this.periodicTimer)
                    set this.periodicTimer = null
                endif
            endmethod
        endstruct
       
        struct BuffList
            // For LinkedList:
            private Buff llHead
            private integer llSize
           
            private unit u
           
            static method create takes unit u returns thistype
                local thistype this = thistype.allocate()
                set this.llHead = 0
                set this.u = u
                return this
            endmethod
           
            // Returns the unit the BuffList belongs to.
            method getUnit takes nothing returns unit
                return this.u
            endmethod
           
            private method llAdd takes Buff b returns nothing
                set b.llNext = this.llHead
                if this.llHead != 0 then
                    set this.llHead.llPrev = b
                endif
                set this.llHead = b
                set b.llPrev = 0
            endmethod
           
            private method llRemove takes Buff b returns nothing
                if b.llPrev != 0 then
                    set b.llPrev.llNext = b.llNext
                else
                    set this.llHead = b.llNext
                endif
                if b.llNext != 0 then
                    set b.llNext.llPrev = b.llPrev
                endif
            endmethod
           
            method getFirstBuff takes nothing returns Buff
                return this.llHead
            endmethod
           
            method iterator takes nothing returns BuffListIterator
                return BuffListIterator.create(this)
            endmethod
           
            method addBuff takes Buff b returns nothing
                call this.llAdd(b)
            endmethod
           
            method removeBuff takes Buff b returns nothing
                call this.llRemove(b)
            endmethod
           
            method onDestroy takes nothing returns nothing
                local BuffListIterator iter = this.iterator()
                loop
                    exitwhen iter.hasNoNext()
                    call this.llRemove(iter.next())
                endloop
                call iter.destroy()
            endmethod
           
            method countBuffsOfTypeId takes integer typeId returns integer
                local BuffListIterator iter = this.iterator()
                local integer n = 0
                loop
                    exitwhen iter.hasNoNext()
                    if (iter.next().getBuffTypeId() == typeId) then
                        set n = n + 1
                    endif
                endloop
                call iter.destroy()
                return n
            endmethod
           
            method countBuffsOfType takes Buff b returns integer
                local integer typeId = b.getBuffTypeId()
                return countBuffsOfTypeId(typeId)
            endmethod
           
            method debugPrint takes nothing returns nothing
                local BuffListIterator iter = this.iterator()
                local Buff tmp
                loop
                    exitwhen iter.hasNoNext()
                    set tmp = iter.next()
                    call BJDebugMsg("Buff: " + tmp.getName() + " (id: " + I2S(tmp) + " )")
                endloop
                call iter.destroy()
            endmethod
        endstruct
       
        struct BuffListIterator
            private BuffList buffList
            private Buff nextBuff
           
            static method create takes BuffList buffList returns thistype
                local thistype this = thistype.allocate()
                set this.buffList = buffList
                set this.nextBuff = buffList.getFirstBuff()
                return this
            endmethod
           
            method hasNext takes nothing returns boolean
                return this.nextBuff != 0
            endmethod
           
            method hasNoNext takes nothing returns boolean
                return this.hasNext() == false
            endmethod
           
            method next takes nothing returns Buff
                local Buff tmp = this.nextBuff
                set this.nextBuff = this.nextBuff.getNext()
                return tmp
            endmethod
        endstruct

    endlibrary


    Example 1:
    Code (vJASS):

    library BuffRage requires BuffHandler
        struct BuffRage extends Buff
            effect sfx
           
            method getName takes nothing returns string
                return "Rage"
            endmethod
           
            method getRaw takes nothing returns integer
                return 'A000'
            endmethod
           
            method onApply takes nothing returns nothing
                local integer str = GetHeroStr(this.getUnit(), false)
                call SetHeroStr(this.getUnit(), str + 200, false)
                set this.sfx = AddSpecialEffectTarget("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", this.getUnit(), "overhead")
            endmethod
           
            method onRemove takes nothing returns nothing
                local integer str = GetHeroStr(this.getUnit(), false)
                call SetHeroStr(this.getUnit(), str - 200, false)
                call DestroyEffect(this.sfx)
            endmethod
        endstruct
    endlibrary
     


    Example 2:
    Code (vJASS):

    library BuffSpellsteal initializer init requires BuffHandler
       
        globals
            hashtable ht
        endglobals
       
        struct BuffSpellsteal extends Buff
            trigger trig
            method getName takes nothing returns string
                return "SpellSteal"
            endmethod
           
            method getRaw takes nothing returns integer
                return 'A001'
            endmethod
           
            method getBuffRaw takes nothing returns integer
                return 'B001'
            endmethod
           
            method getPolicy takes nothing returns integer
                return BUFF_POLICY_LAST
            endmethod
           
            method stealSpell takes unit target returns nothing
                local BuffListIterator iter = getUnitBuffList(target).iterator()
                local Buff b = 0
                loop
                    exitwhen iter.hasNoNext()
                    set b = iter.next()
                    if (b.stealable) then
                        call b.apply(GetAttacker())
                    endif
                endloop
                call iter.destroy()
                call this.destroy()
            endmethod
           
            static method unitAttacked takes nothing returns nothing
                local thistype b = LoadInteger(ht, GetHandleId(GetTriggeringTrigger()), 1)
                if b.getUnit() == GetAttacker() then
                    call b.stealSpell(GetTriggerUnit())
                endif
            endmethod
           
            method onCreate takes nothing returns nothing
                set this.stealable = false
            endmethod
           
            method onApply takes nothing returns nothing
                set this.trig = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(this.trig, EVENT_PLAYER_UNIT_ATTACKED)
                call TriggerAddAction(this.trig, function thistype.unitAttacked)
                call SaveInteger(ht, GetHandleId(trig), 1, this)
                call this.setDuration(5)
            endmethod
           
            method onRemove takes nothing returns nothing
                call FlushChildHashtable(ht, GetHandleId(this.trig))
                call DestroyTrigger(this.trig)
            endmethod
        endstruct
       
        private function addBuff takes nothing returns nothing
            local unit caster = GetTriggerUnit()
            local BuffSpellsteal b
            if GetSpellAbilityId() == 'A002' then
                set b = BuffSpellsteal.create()
                call b.apply(caster)
            endif
        endfunction
       
        private function init takes nothing returns nothing
            local trigger t = CreateTrigger()
            set ht = InitHashtable()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
            call TriggerAddAction(t, function addBuff)
        endfunction
    endlibrary
     


    Example 3:
    Code (vJASS):

    library BuffDiarrhea initializer init requires BuffHandler

        struct BuffDiarrhea extends Buff
            method onApply takes nothing returns nothing
                call this.startPeriodic(1.5)
                call this.setDuration(8)
            endmethod
           
            method getRaw takes nothing returns integer
                return 'A004'
            endmethod
           
            method periodic takes nothing returns nothing
                call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl", GetUnitX(this.getUnit()), GetUnitY(this.getUnit())))
            endmethod
        endstruct
       
        private function addBuff takes nothing returns nothing
            local unit caster = GetTriggerUnit()
            local BuffDiarrhea b
            if GetSpellAbilityId() == 'A003' then
                set b = BuffDiarrhea.create()
                call b.apply(caster)
            endif
        endfunction
       
        private function init takes nothing returns nothing
            local trigger t = CreateTrigger()
            set ht = InitHashtable()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
            call TriggerAddAction(t, function addBuff)
        endfunction
    endlibrary
     


    Comments and a demomap which contains example 1-3 can be found in the thread in the Lab:
    http://www.hiveworkshop.com/forums/lab-715/buffhandler-240347/

    Wurst version:
    https://github.com/muzzel/BuffHandler
     
    Last edited: Aug 25, 2015
  2. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    If only you would have made that way earlier. Now I'm too lazy to convert all of my spells. :/
     
  3. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,673
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    I think many of your methods would be better off as operator overloads.

    For example
    Code (vJASS):

            // Returns an id that identifies the buff type.
            method operator typeId takes nothing returns integer
                return this.getType()
            endmethod
           
            // Returns the bufflist this buff is currently applied to.
            method operator parentBufflist takes nothing returns BuffList
                return this.parent
            endmethod
     


    buff.typeId
    just looks much nicer than
    buff.getBuffTypeId()
     
  4. muzzel

    muzzel

    Joined:
    Jun 27, 2008
    Messages:
    1,303
    Resources:
    2
    JASS:
    1
    Wurst:
    1
    Resources:
    2
    Might look "nicer" but i find this kind of operator abuse very ugly. The operators look like variables and especially for methods which are more than simple setters/getters this confuses the user.
    One example is xemissile. It uses operators .x=, .y= to set the units position. Which is really confusing because it looks like you are setting a variable but it is really calling a function. (And when you set a variable you dont expect something to move, you expect nothing to happen).
    Same with .targetUnit= which changes the target unit.

    Also the WurstScript version of this system is much cooler anyway, since it generates the required objectdata automatically while still being written in 100% wurst. No ugly as fuck mix of vJass, Lua, Textmacros and ObjectMerger shit like you have to use to do the same thing in vJass/JNGP. Oh and the wurst version is faster.
     
  5. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,673
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    The methods I showed you are simple 'getters'.
     
  6. muzzel

    muzzel

    Joined:
    Jun 27, 2008
    Messages:
    1,303
    Resources:
    2
    JASS:
    1
    Wurst:
    1
    Resources:
    2
    I still find it ugly, i just used more extreme examples to explain my point.

    I guess this is a style question and we just have different opinions on it.
     
  7. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    Have to test it. Very useful and nice system, if it's working...

    If it performs correct in a test map, I will approve it.

    Personally I never used stub methods, performance wise they are not the first choice.
    Therefore I would like to hear, if someone here has some serious arguments against using them.
     
  8. Xonok

    Xonok

    Joined:
    Mar 27, 2012
    Messages:
    3,040
    Resources:
    8
    Spells:
    3
    Tutorials:
    5
    Resources:
    8
    I have a system that works quite similarly, but with one key difference - The core system only handles the framework and not stuff such as stacking.

    Basically my system works so that each buff has a list of effects. Buff type is input during buff creation and buff type as an object doesn't exist.
    Instead, each buff is given a list of effects that define the entirety of its functionality, including duration, stacking, whether it deals damage, etc.
    Such a design is more extendable.
    Each effect type(which does exist as an object somewhat) can have some methods:
    Apply - When the parent buff of this effect is applied to a unit, this function is called.
    Clear - Like previous, but removal.
    Construct - Runs when the effect is added to the buff.
    Destruct - Opposite to construct.

    With those 4 methods(all of them optional), I have been able to create every buff that I've wanted so far in an easily extendable manner.
    This is superior to defining effects based on buff type in the sense that it's more modular.
     
  9. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    I tested this out now, finally.

    And I got to say that this is pure awesome. With some layers of abstraction applied, you can basicly use it to code any spells in readable and efficient code, not just buffs.

    Is there a reason why this is not approved yet?

    Also, it can be used to handle blizzard default buffs aswell, by just making a dummyunit cast for example frost nova or sleep spell with a duration of 0 "onApply" and remove the buffs "onRemove" (you have to interate through the parent bufflist here, so you don't remove the buff when there's others of the same type on the unit yet).
     
  10. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    Sounds great. This forum section often suffered a lack of moderation.
    For that reason awesome snippets rot without getting the attention they deserve.
    I'm on it asap :)
     
  11. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Some minor things, though:

    Code (vJASS):

         function UnitGetBuffList takes unit u returns BuffList
             return UnitBuffs[u].bl
         endfunction
     

    Just renamed to maintain the naming convention established by natives.

    Code (vJASS):
         
        //Gets the first buff of a specific name.
        function UnitGetBuff takes unit u, string which returns Buff
            local BuffListIterator iter = UnitGetBuffList(u).iterator()
            local Buff b
            loop
                exitwhen iter.hasNoNext()
                set b = iter.next()
                if b.getName() == which then
                    call iter.destroy()
                    return b
                endif
            endloop
            call iter.destroy()
            return 0
         endfunction
         
         //checks if a buff with the specified name exists on the unit.
         function UnitHasBuff takes unit u, string which returns boolean
            return UnitGetBuff(u, which) != 0
         endfunction
     

    I added these on top of the library and think they are quite useful. Though they require to use the optional naming feature of BuffHandler.
     
  12. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    I read the code and it looks good from what I can judge without testing ingame.

    For me the most important and missing function is UnitHasBuff(). I contacted
    muzzel and hope he will add it.

    The code could be sorted different to avoid code copies from the JassHelper.
    In my opinion it's a good practise to place everything in a way like it
    would be in plain JASS. Of course only if possible :p
     
  13. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    It's not important or missing. The system expects you to cycle through the existing buffs on a unit via the bufflist iterator. UnitHasBuff() is just a shortcut or wrapper for doing what you would do anyway.
    In practice, I found UnitHasBuff() to be not as useful as I thought it would be. But I agree that for the sake of completeness, this should be added (and, if possible, at O(1), since bufflist iterator is O(n) complexity).

    Readability vs performance? You ain't gonna win this battle, bro. ;)

    From how I know him, Muzzel has always preferred readability over performance. And I agree with him on that part.
     
  14. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    Wrapper API like
    UnitHasBuff()
    or
    UnitGetBuff()
    can be seen as particular wishes for
    future snippet updates. The system itself works as intended by Muzzel.

    Independent from complexity ( O(n) or O(1) ), I think
    UnitHasBuff(unit, string)// Buff.has(unit, string)
    could prove beneficial,
    as code looks much cleaner compared to iterating over a buff list.
    For example in a stun system I'm also expecting an IsUnitStunned() wrapper function.

    I hope to see it in on future updates.

    Approved!
     
  15. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    In the meantime, people can just use the script I posted above and insert it into the system.
     
  16. muzzel

    muzzel

    Joined:
    Jun 27, 2008
    Messages:
    1,303
    Resources:
    2
    JASS:
    1
    Wurst:
    1
    Resources:
    2
    Hello,

    I am surprised (and happy) there are still people showing interest in this, I assumed all my code would go to the graveyard :grin:

    First of all - thank you for approving this, and thanks for all the input. In case you didn't notice, I have been inactive for quite a while. And as I am quite busy with other stuff I dont feel like installing Wc3 and JNGP to test this system again.

    Zwiebelchen: If you want to use or work on this system, feel free to add code or change it in any way you like. Post it in a new thread and write your name on it or ask a moderator to change my posts in this thread, you have my permission.


    About the suggestions:

    This system aims to be flexible and have as few limitations as possible while at the same time providing a very simple interface for the most commonly used features. This has been done by writing a very generic system with some wrappers for the most important functions. So you could say, our approaches are somewhat similar, I just added a bunch of higher-level functions which don't actually add functionality, simply for convenience.


    Feel free to rename the whole API to match the blizzard conventions. I always prefered the java-style conventions, but thats just personal preference.


    I tried to implement a version of "UnitHasBuff", it's still in the code:
    Code (vJASS):

            // Returns true if a buff of this type is currently applied on the specified unit.
            static method isOn takes unit u returns boolean
                // TODO: doesnt work without a static version of .getBufTypeId()
                return false
            endmethod
     

    I tried to make use of the "Buff Ids", which are unique for Bufftypes (unlike the names, which are optional and don't get checked for collisions). The idea was to make something like:
    Code (vJASS):

            MyBuff.isOn(someUnit)
     

    which is equal to your
    Code (vJASS):

            UnitHasBuff(u, "MyBuff")
     

    My problem was that there is no static version .getBuffTypeId(), which made it impossible to retrieve a BuffTypes Id without a valid instance. I still have no idea if there is a clean solution.

    I think using the names is a simple solution, however you should make Buff names mandatory (change the description, maybe add a check that throws a warning).

    As the buffs are stored in a linked list it is not possible to make "UnitHasBuff" O(1). But I dont think this is a problem, as the runtime is linear in the amount of buffs on that unit. And usually units dont have 10000 buffs, right?



    Thats not the idea of vJass. And I doubt there is a single wc3 map in the world where code copies from JH made a map lag.

    [​IMG]

    Cheers,
    muzzel
     
  17. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Thanks for giving permission to edit and re-upload this; I'll probably add some extra functionality to this (like adding getSource(), which is an extremely useful feature) when I get the time (... unfortunately, I can't see this happen very soon).

    I don't know what your problem with buffTypeIDs is, though.
    Isn't it basicly as simple as assigning a static unique integer for each new BuffType at struct.onInit, then simply getting that integer? I think you are overthinking this hard.

    Also, once BuffTypeIds are implemented, UnitHasBuff can be O(1) by having a simple lookup table per unit:

    Integer [GetHandleId(unit)] [BuffTypeID] ---> returns the number of applied buffs of the BuffTypeID on the unit (which can be used in CountBuffsOfType to make it O(1))
    Buff [GetHandleId(unit)] [BuffTypeID] ---> returns the last applied Buff of the BuffTypeID on the unit (just a convenience feature... sometimes, you just want to get the last applied buff of a specific type).

    Makes UnitHasBuff and UnitGetBuff O(1) [at O(n) memory cost, though, but who really cares about memory?].
     
    Last edited: Jul 8, 2015
  18. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    Every struct has a unique static integer
    structName.typeid
    ( if I got you right here ).

    I can edit the thread, once you posted what should be added. Of course
    you can start a new thread aswell. Whatever fits you best.

    @Muzzel: Thanks for the reply.
     
  19. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    I'm not familiar with how structs work internally, but does
    structName.typeid
    return the same value as
    structInstance.getType()
    ?

    The whole problem with the unique Type IDs in Buffhandler was that .getType() only exists for allocated struct instances, not for unallocated struct types.
     
  20. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    I made use of
    thistype.typeid
    in Missile, as it is a unique constant integer for structs
    in your map. I used them as index for a boolexpr array.
    call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)

    -->
    Code (vJASS):
        private function MissileCreateExpression takes integer structId, code func returns nothing
            set expression[structId] = Condition(func)
        endfunction



    For exact details look into the jass helper manual.

    Two quotes from the jass helper manual:
    and