System BuffHandler
requires:
Example 1:
Example 2:
Example 3:
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
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: