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

[System] BuffHandler

Level 14
Joined
Jun 27, 2008
Messages
1,325
System BuffHandler
requires:

JASS:
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:
JASS:
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:
JASS:
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:
JASS:
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:
I think many of your methods would be better off as operator overloads.

For example
JASS:
        // 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.typeIdjust looks much nicer thanbuff.getBuffTypeId()
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
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.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
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.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
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.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
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).
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
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 :)
Some minor things, though:

JASS:
     function UnitGetBuffList takes unit u returns BuffList
         return UnitBuffs[u].bl
     endfunction
Just renamed to maintain the naming convention established by natives.

JASS:
    //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.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
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
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
For me the most important and missing function is UnitHasBuff(). I contacted muzzel and hope he will add it.
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).

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
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.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
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!
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
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:

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.
[...]
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.

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.


JASS:
     function UnitGetBuffList takes unit u returns BuffList
         return UnitBuffs[u].bl
     endfunction
Just renamed to maintain the naming convention established by natives.

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


JASS:
    //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.

I tried to implement a version of "UnitHasBuff", it's still in the code:
JASS:
        // 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:
JASS:
        MyBuff.isOn(someUnit)
which is equal to your
JASS:
        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?



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

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.

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.

right.jpg


Cheers,
muzzel
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
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:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Isn't it basicly as simple as assigning a static unique integer for each new BuffType at struct.onInit
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.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
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.
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.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
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)
-->
JASS:
    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:
In short, .getType() is a method that you use on instances of an object whose type extends an interface. And .typeid is an static constant set for struct types that extend an interface.
and
struct.typeid now returns an accurate integer not dependant on parent ids and is replaced by a constant added to the map script instead of a single number.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I see, so basicly, thistype.typeid is the static version of this.getType().

Nice! That basicly solves Muzzels problem with isOn() elegantly.


EDIT:

JASS:
        // 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


--> becomes


JASS:
globals
        private constant hashtable Hash = InitHashtable()
endglobals

        // 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 RemoveSavedInteger(Hash, GetHandleId(target), this.getType())
                 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 SaveInteger(Hash, GetHandleId(target), this.getType(), this)
                     call this.parent.addBuff(this)
                     call this.addBuffIcon()
                     call this.onApply()
                 endif
             endif
         endmethod



        static method isOn takes unit u returns boolean
             return HaveSavedInteger(Hash, GetHandleId(u), thistype.typeid)
         endmethod

        static method getLast takes unit u returns thistype
             return LoadInteger(Hash, GetHandleId(u), thistype.typeid)
         endmethod
 
Last edited:
Level 14
Joined
Jun 27, 2008
Messages
1,325
I see, so basicly, thistype.typeid is the static version of this.getType().

Correct. The issue was that the static and non-static version return different values for the same type (at least when I tested it). Which is alright if you only use it to compare instances with instances and types with types, but you cannot use it to check whether a given object is an instance of a certain type.

int a = MyStruct.typeid
MyStruct obj = MyStruct.create()
int b = obj.getType()

-> a != b


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?].

Yea if you use additional data structures its possible. And it makes those functions faster, however at the cost of more overhead in the apply() method. I guess it doesn't make much of a difference.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Correct. The issue was that the static and non-static version return different values for the same type (at least when I tested it). Which is alright if you only use it to compare instances with instances and types with types, but you cannot use it to check whether a given object is an instance of a certain type.

int a = MyStruct.typeid
MyStruct obj = MyStruct.create()
int b = obj.getType()

-> a != b
My guess is that the return value depends on if you use getType() on the parent or the extending struct.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
getType() should return the struct id it was allocated from.
thistype.typeid is unique for every struct hold that structs id.

Copied from the JassHelper manual
It is possible to acquire the type id of an instance of an struct that extends an interface, this type id is an integer number that is unique per struct type that extends that interface.

JASS:
interface A
    integer x
endinterface

struct B extends A
    integer y
endstruct

struct C extends A
    integer y
    integer z
endstruct

function test takes A inst returns nothing
   if (inst.getType()==C.typeid) then
     // We know for sure inst is actually an instance of type C
       set C(inst).z=5 //notice the typecast operator
   endif
   if (inst.getType()==B.typeid) then
       call BJDebugMsg("It was of type B with value "+I2S( B(inst).y  ) )
   endif
endfunction

In short, .getType() is a method that you use on instances of an object whose type extends an interface. And .typeid is an static constant set for struct types that extend an interface.

So, in the example we get to recognize that the given inst argument is of type C, then we can do the typecast and assignment.

There is another feature that uses typeids got another feature, and it is that interfaces got a constructor method that would create a new object given a correct typeid.
 
Last edited:
Level 14
Joined
Jun 27, 2008
Messages
1,325
My guess is that the return value depends on if you use getType() on the parent or the extending struct.

Nah, the returned value of getType() should depend on the actual type, otherwise this whole function wouldnt make sense.

And if the documentation BPower posted is correct, both .getType() and .typeid do exactly what we need. However, last time I tested it it did not work, maybe I made a mistake? Feel free to test it again, or maybe I find some time to do it myself in about 2 weeks.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
And if the documentation BPower posted is correct, both .getType() and .typeid do exactly what we need. However, last time I tested it it did not work, maybe I made a mistake? Feel free to test it again, or maybe I find some time to do it myself in about 2 weeks.
That's what I expect. Because what else would typeid be for, if not for comparing it with the extension type of another struct instance?

But yeah, would be cool if you would do this, because I don't like messing around with other people's code.


The solution I posted above should definitly work; but you can probably improve on this so that doesn't require a hashtable. Bonus points if you can make the countOfType method O(1) aswell by using another lookup table. It's pretty much the last remaining getter that is not O(1) if we implement the table approach from above.

The extra overhead on buff creation/removal is negligible. It's just incrementing a count integer, after all.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
So my current plan is to rewrite a part of the linkedlist structure which holds a units buff. This will bring some benefits:
  • O(1) IsOn(unit, buffType)
  • O(1) Retrieving a (random) buff instance of a certain buff type from a unit without using the iterator (-> faster). This is especially useful for buffs which don't stack, since there is only one instance.
  • Iterators which only iterate through buffs of a certain type: .iterator(bufftype) instead of .iterator() (its not just a filter for the iterator, each bufftypes instances can be accessed separetely, so this is a speedup!)
  • All this with only one hashtable access per operation.

Counting buffs of one type is still O(n), but its not required to iterate through all buffs of a unit, but only through the instances of the specified type. So its O(#instances of that bufftype), which is totally fine in my opinion.

The only downside:
The buffs are not stored in the order in which they were applied, so the iterator will not return the buffs in that order anymore. However, when only looking at buffs of a certain type, they will still get returned in the correct order.

Any comments on this?
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
So my current plan is to rewrite a part of the linkedlist structure which holds a units buff. This will bring some benefits:
  • O(1) IsOn(unit, buffType)
  • O(1) Retrieving a (random) buff instance of a certain buff type from a unit without using the iterator (-> faster). This is especially useful for buffs which don't stack, since there is only one instance.
  • Iterators which only iterate through buffs of a certain type: .iterator(bufftype) instead of .iterator() (its not just a filter for the iterator, each bufftypes instances can be accessed separetely, so this is a speedup!)
  • All this with only one hashtable access per operation.

Counting buffs of one type is still O(n), but its not required to iterate through all buffs of a unit, but only through the instances of the specified type. So its O(#instances of that bufftype), which is totally fine in my opinion.

The only downside:
The buffs are not stored in the order in which they were applied, so the iterator will not return the buffs in that order anymore. However, when only looking at buffs of a certain type, they will still get returned in the correct order.

Any comments on this?
Sounds great;
I don't think the order in which the iterator retrieves buffs is important, so yeah, I wouldn't bother with that.

Now the only bad thing for me is that I have to replace all UnitHasBuff instances with .isOn(). :D
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
number of instances can be O(1).

Whenever new instance of given buff type is created, increase integer inside table under certain ID(lets say -buffId) and whenever the instance is deallocated, you can decrease this integer
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
number of instances can be O(1).

Whenever new instance of given buff type is created, increase integer inside table under certain ID(lets say -buffId) and whenever the instance is deallocated, you can decrease this integer

Yes I know, also Zwiebel suggested it here: http://www.hiveworkshop.com/forums/...em-buffhandler-242121/index2.html#post2703417

But I dont think it is necessary, actually I am pretty sure its counterproductive in regard to the systems overall performance. First of all, the Landau Notation "O(...)" only refers to the asymptotic time complexity, not to the runtime itself. So an execution that runs in O(1) is not necessarily faster than one in O(n).
In this case here, the n in O(n) refers to the amount of buffs of a certain type on a certain unit. So this number wont be large, usually lower than ~5. So 5 iterations through a linkedlist should be a bit slower than a hashtable lookup, but not considerably. The point I dont like about this is that I need one additional hashtable read and write operation whenever I apply a buff. This does not seem like much, but I would expect that applying buffs happens much more often than counting buffs, so it makes sense to minimize the overhead in the apply method.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Yes I know, also Zwiebel suggested it here: http://www.hiveworkshop.com/forums/...em-buffhandler-242121/index2.html#post2703417

But I dont think it is necessary, actually I am pretty sure its counterproductive in regard to the systems overall performance. First of all, the Landau Notation "O(...)" only refers to the asymptotic time complexity, not to the runtime itself. So an execution that runs in O(1) is not necessarily faster than one in O(n).
In this case here, the n in O(n) refers to the amount of buffs of a certain type on a certain unit. So this number wont be large, usually lower than ~5. So 5 iterations through a linkedlist should be a bit slower than a hashtable lookup, but not considerably. The point I dont like about this is that I need one additional hashtable read and write operation whenever I apply a buff. This does not seem like much, but I would expect that applying buffs happens much more often than counting buffs, so it makes sense to minimize the overhead in the apply method.
There's one thing you need to consider with buffs, though:

Creating/destroying buffs can be ineffective. If a mapper spams buffs in large numbers, this is bad game design anyway, as the UI is pretty limited in the amount of buffs it can display at the same time. Yes, in most applications, you won't have more than 5 different buffs on a unit, I agree with that.

On the other hand, we have the lookup operations like checking if a buff exists. These indeed are time-critical, for the simple reason that you will probably run these checks as part of heavily loaded events like onDamage.

Imho UnitHasBuff() should be first priority in runtime efficiency because of that. The frequency in which you check UnitHasBuff() is much higher than the frequency in which you create/destroy, so a little extra overhead for the sake of optimizing UnitHasBuff() is negligible.

CountBuffsOfType() basicly follows a similar principle, as you will most likely use it for conditionals in highly reactive events (like dealing 1% more damage on an attack event per stack of a buff). It's just that you probably not use it as frequently as UnitHasBuff(). Still, it's likely to be used on time-critical events.


But yes, we are talking minor minutiae here. You won't be able to measure a speed difference in the scale of miliseconds in any real-world-application here; so I'd say decide on what you think looks cleaner.

But here's the kicker: a second hashtable lookup actually cleans up the code, as it makes the count feature a one-liner, instead of having to do all that iterator stuff. And getters that are one-liners always look sexy.
As a bonus, it also provides a new default buff policy:
BUFF_POLICY_LIMITED_STACKS
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Btw, I noticed a small bug with this: when a unit is removed from the game, buffs on this unit are not properly cleaned up (the bufflist gets cleaned, but not the actual buffs).

I guess this is intentional, as buffs in buffhandler can exist virtually even without a unit assigned to them (for example a persitant area effect), however, I think this is impractical, as most of the time you actually want buffs to be purged from removed units.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Does stub method onCreate work correctly? I would say the correct struct typeid is
not yet set within the parent struct create method.

Consider struct B extends A

JASS:
function s__A__allocate takes nothing returns integer
    local integer this=si__A_F
    if (this!=0) then
        set si__A_F=si__A_V[this]
    else
        set si__A_I=si__A_I+1
        set this=si__A_I
    endif
    if (this>8190) then
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,1000.,"Unable to allocate id for an object of type: A")
        return 0
    endif

    set si__A_type[this]=1
    set si__A_V[this]=-1
    return this
endfunction

TriggerAddCondition(st__A_create,Condition( function sa__A_create))

// Will return the new allocated instance to B.
function sc__A_create takes nothing returns integer
    call TriggerEvaluate(st__A_create)// Finally will access A.create()
    return f__result_integer
endfunction

function sa__A_create takes nothing returns boolean
    local integer this= s__A__allocate()
    call sc__A_onCreate(this)// set typeid is still the parent struct typeid.
    set f__result_integer= this
    return true
endfunction

// Create a new instance for struct B. Internally calls A.create()
function s__B__allocate takes nothing returns integer
    local integer this=sc__A_create()// Evaluates parent creator method A.create(), Here also this.onCreate is evaluated.
    local integer kthis
    if(this==0) then
        return 0
    endif
    set si__A_type[this]=2// typeid is set here.
    set kthis=this

    return this
endfunction
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I don't see a problem? The typeid is set before the end of B.allocate(), which is all that matters. onCreate is redeclared as a stub method and manually called within static method A.create. Which means that both allocators have been run at this point.

What you are looking at is the default implementation of onCreate, not the one used in BuffHandler.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
library A

 struct A
    
    // Eval via call TriggerEvaluate(st__A_onCreate[si__A_type[this]])
    public stub method onCreate takes nothing returns nothing
        // Will always run
    endmethod
    
    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()// set si__A_type[this]=1

        call this.onCreate()// Evaluates st__A_onCreate[si__A_type[this]]

        return this// Returned to a function, which set si__A_type[this]=2
    endmethod
        
 endstruct

  struct B extends A
    
    private method onCreate takes nothing returns nothing
        // Will never run
    endmethod
    
    static method doit takes nothing returns nothing
        local B this = B.create()// here si__A_type[this]=2
        call Print(I2S(this.getType()))// Prints 2
    endmethod
        
 endstruct
 
endlibrary
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
JASS:
library A

 struct A
    
    // Eval via call TriggerEvaluate(st__A_onCreate[si__A_type[this]])
    public stub method onCreate takes nothing returns nothing
        // Will always run
    endmethod
    
    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()// set si__A_type[this]=1

        call this.onCreate()// Evaluates st__A_onCreate[si__A_type[this]]

        return this// Returned to a function, which set si__A_type[this]=2
    endmethod
        
 endstruct

  struct B extends A
    
    private method onCreate takes nothing returns nothing
        // Will never run
    endmethod
    
    static method doit takes nothing returns nothing
        local B this = B.create()// here si__A_type[this]=2
        call Print(I2S(this.getType()))// Prints 2
    endmethod
        
 endstruct
 
endlibrary
Yeah that seems like a problem...
Have you tested this ingame with a debug msg in B.onCreate(), just to go sure?
If so, then you have just discovered a bug with JassHelper...

... or rather, a limitation of JassHelper in which it doesn't allow calling stub methods from within A.create().

Btw, how does B.create look like in compiled Jass? Maybe Vex implemented something there to alleviate this problem?
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm currently trying to make an full screen system and made a small package of
resources ( Borders, buttons, and one global parent struct for every full screen system ),
which should ease full screen window creation in general.

Every custom UI extends from one parent struct, which I called UIWindow.
I also declared an onCreate stub method and then realised it's not working.

I quickly run the test code I posted here.

JASS:
library A initializer Init

 struct A
    
    public stub method onCreate takes nothing returns nothing
        call BJDebugMsg("parent onCreate")
    endmethod
    
    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()
        call this.onCreate()
        return this
    endmethod
        
 endstruct

  struct B extends A
    
    public method onCreate takes nothing returns nothing
        call BJDebugMsg("child [B] onCreate") 
    endmethod
    
    static method testIt takes nothing returns nothing
        local B this = B.create()
    endmethod
        
 endstruct
 
 private function Init takes nothing returns nothing
    call B.testIt()
 endfunction 
endlibrary
 

Attachments

  • test.png
    test.png
    639.2 KB · Views: 104

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Instead of use stub method, we can use implement likes DDS, Unit Indexer or Missile.
Implement doesn't do the same as stub. Also, it doesn't fix the actual problem.

And let's be totally honest here: onCreate is not a particulary useful method to have anyway. In fact, I can not think of a single reason why you would actually need onCreate in case of a buff system. I've used BuffHandler for dozens of buffs in my map already and never used onCreate even once. I didn't even know that it's there tbh. :p

However, BPower, I think you should add this issue to your extends tutorial!
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I have right now a case, where an onCreate would be useful.
It's an inventory full screen system. Too much code to post it here.

This is how I discovered an onCreate stub method is not working
JASS:
library UIWindow
    
    // Parent struct for each UI we want to create
    struct UIWindow
        
        public stub method onCreate takes nothing returns nothing
        endmethod
        
        static method create takes integer anyType returns thistype
            local thistype this = thistype.allocate()
            // Do Stuff.
            call onCreate()
            return this
        endmethod
        
    endstruct

endlibrary

// Our system. 
library Inventory uses UIWindow

    module INVENTORY_STRUCT
        
        // This method is registered as trigger condition to Inventory.ON_CREATE trigger. 
        static method onInventoryCreate takes nothing returns boolean
            local Inventory i = Inventory.triggerIndex
            call thistype.create(i)
        endmethod
    endmodule

    static thistype triggerIndex
    private static trigger ON_CREATE = CreateTrigger()

    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()
        //...
        set triggerIndex = this
        call TriggerEvaluate(Inventory.ON_CREATE)
        return this
    endmethod
        
endlibrary

library Backpack uses Inventory

    // extends UIWindow
    struct Backpack extends UIWindow
    
        private method onCreate takes integer anyType returns nothing
            // Init setup window visuals and data
        endmethod
    
        // calls create --> fires onCreate()
        implement INVENTORY_STRUCT
    
    endstruct

endlibrary

However you are right. I could simply call onCreate from the module and make
onCreate a normal static method in each struct instead of an stub method.
 
Top