- Joined
- Feb 6, 2014
- Messages
- 2,466
JASS:
//! novjass
Buff System v1.30
by Flux
Handles all interactions of self-defined Buffs.
Features:
- Can dispel positive/negative/both/all Buffs.
- Supports 3 types of buff stacking.
- Buffs with duration.
- Pick all Buffs of a unit easily.
//==================================================================//
API
//==================================================================//
static method add takes unit source, unit target returns thistype
- Adds and creates a new Buff to the target depending on the
stacking type.
EXAMPLE:
local MyBuff b = MyBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
If the stackType is BUFF_STACK_NONE, and the target already has
the same Buff applied, it will not create a new Buff instance,
instead it will return an existing Buff of the same type from
the target.
If the stackType is BUFF_STACK_PARTIAL, it will only create a new
Buff instance if there is no existing Buff on the target with the
same source, else it will return a Buff coming from that source.
That means same type of Buff from different sources will stack.
If the stackType is BUFF_STACK_FULL, then "static method add" will
return a newly created Buff instance everytime it is called.
method operator duration= takes real time returns nothing
- Adds a countdown timer to a Buff or change the countdown time of a
Buff if a count down timer already exist.
EXAMPLE:
set b.duration = 10
static method get takes unit source, unit target, integer typeid returns thistype
- Returns a Buff from <target> caused by <source> and has a type of typeid.
- When you want to retrieve a Buff from target caused by any source, input
null in the source argument.
- If Buff is non-existing, it returns 0.
- If there is more than a Buff because that Buff fully stacks, it will return
the oldest applied Buff.
static method has takes unit source, unit target, integer typeid returns boolean
- A simple wrapper function for Buff.get(source, target, typeid)
method operator name takes nothing returns string
- Returns the name of the Buff as defined in Object Editor
EXAMPLE:
call BJDebugMsg("Buff name is " + b.name)
static method dispel takes unit u, integer dispelType returns nothing
- Removes all <dispelType> Buffs from a unit.
EXAMPLE:
call Buff.dispel(GetTriggerUnit(), BUFF_POSITIVE)
static method dispelBoth takes unit u returns nothing
- Removes positive and negative buffs of a unit.
Buffs with dispelType of BUFF_NONE will not be removed.
EXAMPLE:
call Buff.dispelBoth(u)
static method dispelAll takes unit u returns nothing
- Removes all Buffs from a unit.
EXAMPLE:
call Buff.dispelAll(u)
static method pickBuffs takes unit u returns nothing
- Used when you want to pick all buffs of a unit. More detailed
example below the API list
EXAMPLE:
call Buff.pickBuffs(GetTriggerUnit())
method remove takes nothing returns nothing
- Removes a Buff instance. Using inside 'method onRemove'
will cause an infinite loop.
EXAMPLE:
call b.remove()
//------------------------------------------------//
HOW TO PICK ALL BUFFS ON A UNIT
//------------------------------------------------//
1. Use Buff.pickBuffs(yourUnit)
2. Put your scripts/actions between 'implement BuffListStart' and
'implement BuffListEnd'
EXAMPLE: (A Spell that will remove the first 3 negative buffs applied)
private static method onCast takes nothing returns nothing
local integer counter = 3
call Buff.pickBuffs(GetTriggerUnit())
implement BuffListStart
if Buff.picked.dispelType == BUFF_NEGATIVE then
call Buff.picked.remove()
set counter = counter - 1
endif
exitwhen counter == 0
implement BuffListEnd
endmethod
//NOTE: Since it's using modules, it can only be done inside a struct.
//==================================================================//
HOW TO USE BUFF SYSTEM:
//==================================================================//
1. Create your own struct that 'extends Buff'.
Do not forget to put 'implement BuffApply' before the end of the struct
Example:
private struct <BuffName> extends Buff
/*
* Your code here
*/
implement BuffApply
endstruct
2. Define basic Buff configurations inside your struct
Example:
//Rawcode of a spell based on Slow Aura (Tornado)
//Will be tackled more on Step 4
private static constant integer RAWCODE = 'AXYZ'
private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
private static constant integer STACK_TYPE = BUFF_STACK_PARTIAL
3. Configure onApply and onRemove methods inside your struct.
Example:
//This will execute when:
// - A BUFF_STACK_NONE Buff is removed from a unit.
// - A BUFF_STACK_PARTIAL Buff from a certain source is removed from a unit.
// - A BUFF_STACK_FULL Buff instance is removed.
method onRemove takes nothing returns nothing
//Configure what happens when the Buff is removed.
endmethod
//This will execute when:
// - A BUFF_STACK_NONE/BUFF_STACK_PARTIAL Buff is applied to a unit
// not having the same Buff before it is applied.
// - A BUFF_STACK_PARTIAL Buff is applied to a unit already having
// the same buff but from a different source.
// - A BUFF_STACK_FULL Buff is applied to a unit.
method onApply takes nothing returns nothing
//Configure what happens when the Buff is applied.
endmethod
4. Create the Buff Objects.
a. In Object Editor, find "Slow Aura (Tornado)" [Aasl] and use it as the basis
to create a new Ability because "Slow Aura (Tornado)" does not appear in the
unit command card.
b. Make sure "Data - Attack Speed Factor" and "Data - Movement Speed Factor"
are both zero so that it does not do anything. The Buff mechanics/effects will
be entirely defined by code.
c. Create a new Buff based on "Tornado (Slow Aura)" [Basl]. It is very important
that the rawcode of this new Buff is exactly the same as the newly created
Ability done in Step 4.a except the first letter which depends on the value
of BUFF_OFFSET.
Example:
BUFF_OFFSET = 0x01000000
Ability - 'AXYZ'
Buff - 'BXYZ'
BUFF_OFFSET = 0x20000000
Ability - 'AXYZ'
Buff - 'aXYZ'
d. Edit the Ability: "Stats - Buff" of the new Ability from Step 4.a so
that its new Buff is the the Buff created at Step 4.c.
e. Change the Ability: "Stats - Targets Allowed" to "self"
f. Change the Buff Icon, Attachment Effects and Tooltips.
5. Configuration is done. You can now easily add the Buff to a unit using:
<BuffName>.add(source, target)
The system will automatically handles the stacking type of the Buff.
//! endnovjass
JASS:
library Buff /*
Buff v1.30
by Flux
Handles all interactions of self-defined buffs.
Features:
- Can dispel positive/negative/both/all buffs.
- Supports 3 types of buff stacking.
- Buffs with duration.
- Pick all Buffs of a unit easily.
*/ requires /*
(nothing)
*/ optional TimerUtils /*
*/ optional RegisterPlayerUnitEvent /*
******************
CREDITS
******************
muzzel - For BuffHandler which this resource is heavily based upon.
Vexorian - For the optional TimerUtils.
Magtheridon96 - For the optional RegisterPlayerUnitEvent
*/
globals
//-----------------------------//
//--------- BUFF TYPES --------//
//-----------------------------//
constant integer BUFF_NONE = 0
constant integer BUFF_POSITIVE = 1
constant integer BUFF_NEGATIVE = 2
//-----------------------------//
//------ BUFF STACK TYPES -----//
//-----------------------------//
//Applying the same buff only refreshes the duration
//If the buff is reapplied but from a different source, the Buff unit source gets replaced.
constant integer BUFF_STACK_NONE = 0
//Each buff from different source stacks.
//Re-applying the same buff from the same source only refreshes the duration
constant integer BUFF_STACK_PARTIAL = 1
//Each buff applied fully stacks.
constant integer BUFF_STACK_FULL = 2
//Determines the automatic Buff rawcode based on the Ability rawcode
//If BUFF_OFFSET = 0x01000000, then Ability rawcode of 'AXXX' will have Buff rawcode of 'BXXX'
//If BUFF_OFFSET = 0x20000000, then Ability rawcode of 'AXXX' will have Buff rawcode of 'aXXX'
private constant integer BUFF_OFFSET = 0x01000000
//Automatically Preloads all Buff abilities defined in "method rawcode"
//but will generate a lot of scripts in the process
private constant boolean PRELOAD_BUFFS = true
//Automatically initialize a Buff type.
//If false, initialize it using <MyBuff>.initialize()
private constant boolean AUTO_INITIALIZE = true
endglobals
struct Buff
//Buff properties
readonly boolean exist
readonly unit target
readonly unit source
readonly integer rawcode
readonly integer buffId
readonly integer stackType
readonly integer dispelType
//For duration
private timer t
//Buff Enumeration
private thistype bnext
private thistype bprev
//Events
private static thistype callback
private static trigger array onApply
private static trigger array onRemove
method operator name takes nothing returns string
return GetObjectName(this.rawcode)
endmethod
private static hashtable hash = InitHashtable()
//===============================================================
//======================== BUFF CORE ============================
//===============================================================
static method get takes unit source, unit target, integer typeid returns thistype
local integer id = GetHandleId(target)
local thistype this
local thistype head
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head
loop
if this.getType() == typeid and (source == null or source == this.source) then
return this
endif
exitwhen this == head.bprev
set this = this.bnext
endloop
endif
return 0
endmethod
static method has takes unit source, unit target, integer typeid returns boolean
return thistype.get(source, target, typeid) > 0
endmethod
method remove takes nothing returns nothing
local boolean remove = false
local integer id
local thistype head
local integer count
if this.exist then
set id = GetHandleId(this.target)
set thistype.callback = this
call TriggerEvaluate(thistype.onRemove[this.getType()])
if this.t != null then
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.t)
else
call RemoveSavedInteger(thistype.hash, GetHandleId(this.t), 0)
call DestroyTimer(this.t)
endif
set this.t = null
endif
if this.stackType == BUFF_STACK_FULL or this.stackType == BUFF_STACK_PARTIAL then
//Update Buff count
set count = LoadInteger(thistype.hash, this.getType(), id) - 1
call SaveInteger(thistype.hash, this.getType(), id, count)
if count == 0 then
set remove = true
endif
elseif this.stackType == BUFF_STACK_NONE then
set remove = true
endif
if remove then
call UnitRemoveAbility(this.target, this.rawcode)
call UnitRemoveAbility(this.target, this.buffId)
endif
//Remove from the BuffList
set head = LoadInteger(thistype.hash, id, 0)
if this == head and this.bnext == head then //If this is the only Buff of the unit
call RemoveSavedInteger(thistype.hash, id, 0)
else
//If this is the head of the BuffList
if this == head then
//Change this unit's BuffList head
call SaveInteger(thistype.hash, id, 0, this.bnext)
endif
set this.bnext.bprev = this.bprev
set this.bprev.bnext = this.bnext
endif
set this.exist = false
set this.target = null
set this.source = null
call this.destroy()
debug else
debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "[Buff]: Attempted to remove non-existing Buff instance.")
endif
endmethod
private static method expires takes nothing returns nothing
static if LIBRARY_TimerUtils then
local thistype this = GetTimerData(GetExpiredTimer())
call ReleaseTimer(GetExpiredTimer())
else
local integer id = GetHandleId(GetExpiredTimer())
local thistype this = LoadInteger(thistype.hash, id, 0)
call RemoveSavedInteger(thistype.hash, id, 0)
call DestroyTimer(GetExpiredTimer())
endif
if this.t != null then
set this.t = null
call this.remove()
endif
endmethod
method operator duration takes nothing returns real
if this.t != null then
return TimerGetRemaining(this.t)
endif
return 0.0
endmethod
method operator duration= takes real time returns nothing
if this.t == null then
static if LIBRARY_TimerUtils then
set this.t = NewTimerEx(this)
else
set this.t = CreateTimer()
call SaveInteger(thistype.hash, GetHandleId(this.t), 0, this)
endif
endif
call TimerStart(this.t, time, false, function thistype.expires)
endmethod
method check takes unit source, unit target returns thistype
local boolean apply = false
local integer id = GetHandleId(target)
local thistype head
local thistype temp
static if not LIBRARY_TimerUtils then
local timer t
endif
if this.stackType == BUFF_STACK_FULL then
//Count how many buffs are stored in a certain unit
call SaveInteger(thistype.hash, this.getType(), id, LoadInteger(thistype.hash, this.getType(), id) + 1)
set apply = true
elseif this.stackType == BUFF_STACK_PARTIAL then
//Check if a similar buff type with the same target and source exist
set temp = thistype.get(source, target, this.getType())
if temp == 0 then //None is found
set apply = true
//Count how many buffs of this type are stored in this certain unit
call SaveInteger(thistype.hash, this.getType(), id, LoadInteger(thistype.hash, this.getType(), id) + 1)
else //Buff is found, use the previous Buff as the newly applied Buff
call this.destroy()
set this = temp
endif
elseif this.stackType == BUFF_STACK_NONE then
//Check if a similar buff type with the same target exist
set temp = thistype.get(null, target, this.getType())
if temp == 0 then //None is found
set apply = true
else //Buff is found, use the previous Buff as the newly applied Buff
call this.destroy()
set this = temp
endif
endif
set this.source = source
set this.target = target
set this.exist = true
set this.buffId = this.rawcode + BUFF_OFFSET
if apply then
if GetUnitAbilityLevel(target, this.rawcode) == 0 then
call UnitAddAbility(target, this.rawcode)
call UnitMakeAbilityPermanent(target, true, this.rawcode)
endif
//Add the Buff to a BuffList of this unit
//If BuffList already exist
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this.bnext = head
set this.bprev = head.bprev
set this.bnext.bprev = this
set this.bprev.bnext = this
else
//Set this as the unit's BuffList head
call SaveInteger(thistype.hash, id, 0, this)
set this.bnext = this
set this.bprev = this
endif
set thistype.callback = this
call TriggerEvaluate(thistype.onApply[this.getType()])
endif
static if LIBRARY_BuffEvent then
static if LIBRARY_TimerUtils then
call TimerStart(NewTimerEx(this), 0.0, false, function BuffEvent.pickAll)
else
set t = CreateTimer()
call SaveInteger(thistype.hash, GetHandleId(t), 0, this)
call TimerStart(t, 0.0, false, function BuffEvent.pickAll)
endif
endif
return this
endmethod
//===============================================================
//======================== BUFF ENUM ============================
//===============================================================
readonly static thistype buffHead
readonly static thistype picked
static method pickBuffs takes unit u returns nothing
local integer id = GetHandleId(u)
if HaveSavedInteger(thistype.hash, id, 0) then
set thistype.buffHead = LoadInteger(thistype.hash, id, 0)
else
set thistype.buffHead = 0
endif
endmethod
//===============================================================
//======================= BUFF DISPEL ===========================
//===============================================================
static method dispel takes unit u, integer dispelType returns nothing
local integer id = GetHandleId(u)
local thistype head
local thistype this
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head.bnext
loop
if this.dispelType == dispelType then
call this.remove()
endif
exitwhen this == head
set this = this.bnext
endloop
endif
endmethod
static method dispelBoth takes unit u returns nothing
local integer id = GetHandleId(u)
local thistype head
local thistype this
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head.bnext
loop
if this.dispelType == BUFF_POSITIVE or this.dispelType == BUFF_NEGATIVE then
call this.remove()
endif
exitwhen this == head
set this = this.bnext
endloop
endif
endmethod
static method dispelAll takes unit u returns nothing
local integer id = GetHandleId(u)
local thistype head
local thistype this
if HaveSavedInteger(thistype.hash, id, 0) then
set head = LoadInteger(thistype.hash, id, 0)
set this = head.bnext
loop
call this.remove()
exitwhen this == head
set this = this.bnext
endloop
endif
endmethod
private static method onDeath takes nothing returns nothing
call thistype.dispelAll(GetTriggerUnit())
endmethod
implement optional BuffInit
private static method onInit takes nothing returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
else
local trigger t = CreateTrigger()
local code c = function thistype.onDeath
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, Condition(c))
endif
endmethod
endstruct
static if PRELOAD_BUFFS then
module BuffInit
readonly static unit preloader
private static method onInit takes nothing returns nothing
set thistype.preloader = CreateUnit(Player(14), 'ushd', GetRectMaxX(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea), 0)
call UnitApplyTimedLife(thistype.preloader, 'BTLF', 1.0)
endmethod
endmodule
endif
module BuffApply
static method add takes unit source, unit target returns thistype
local thistype this = thistype.create()
//Write into readonly attributes
set s__Buff_rawcode[this] = thistype.RAWCODE
set s__Buff_stackType[this] = thistype.STACK_TYPE
set s__Buff_dispelType[this] = thistype.DISPEL_TYPE
set this = this.check(source, target)
return this
endmethod
private static method onApplyInit takes nothing returns boolean
call thistype(s__Buff_callback).onApply()
return false
endmethod
private static method onRemoveInit takes nothing returns boolean
call thistype(s__Buff_callback).onRemove()
return false
endmethod
static method initialize takes nothing returns nothing
static if thistype.onApply.exists then
set s__Buff_onApply[thistype.typeid] = CreateTrigger()
call TriggerAddCondition(s__Buff_onApply[thistype.typeid], function thistype.onApplyInit)
endif
static if thistype.onRemove.exists then
set s__Buff_onRemove[thistype.typeid] = CreateTrigger()
call TriggerAddCondition(s__Buff_onRemove[thistype.typeid], function thistype.onRemoveInit)
endif
endmethod
static if PRELOAD_BUFFS then
private static method onInit takes nothing returns nothing
local thistype this = thistype.create()
call UnitAddAbility(Buff.preloader, thistype.RAWCODE)
call UnitRemoveAbility(Buff.preloader, thistype.RAWCODE)
call this.destroy()
static if AUTO_INITIALIZE then
call thistype.initialize()
endif
endmethod
elseif AUTO_INITIALIZE then
private static method onInit takes nothing returns nothing
call thistype.initialize()
endmethod
endif
endmodule
module BuffListStart
if Buff.buffHead > 0 then
set s__Buff_picked = s__Buff_buffHead
loop
endmodule
module BuffListEnd
exitwhen Buff.picked == s__Buff_bprev[s__Buff_buffHead]
set s__Buff_picked = s__Buff_bnext[s__Buff_picked]
endloop
endif
endmodule
endlibrary
JASS:
library BuffEvent /*
BuffEvent v1.00
by Flux
Allows you to catch an event when any Buff applied.
API:
- BuffEvent.create(code)
> The code will run when any Buff is applied to any unit.
> Will not create a new object if there is a BuffEvent already
existing for the input code argument.
- BuffEvent.remove(code)
> Remove the BuffEvent that has the code argument.
- <BuffEventObject>.destroy()
> Remove a BuffEvent instance.
- BuffEvent.buff
> The Buff that causes the event to run.
*/ requires Buff /*
*/ optional TimerUtils /*
*/
struct BuffEvent
readonly triggercondition tc
readonly conditionfunc cf
readonly static Buff buff
private static trigger trg = CreateTrigger()
method destroy takes nothing returns nothing
call TriggerRemoveCondition(thistype.trg, this.tc)
call RemoveSavedInteger(s__Buff_hash, GetHandleId(this.cf), 0)
set this.tc = null
set this.cf = null
call this.deallocate()
endmethod
static method remove takes code c returns nothing
local integer id = GetHandleId(Condition(c))
if HaveSavedInteger(s__Buff_hash, id, 0) then
call thistype(LoadInteger(s__Buff_hash, id, 0)).destroy()
debug else
debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "[BuffEvent]: Attempted to unregister code with non-existing BuffEvent.")
endif
endmethod
static method pickAll takes nothing returns nothing
static if LIBRARY_TimerUtils then
set thistype.buff = GetTimerData(GetExpiredTimer())
call ReleaseTimer(GetExpiredTimer())
else
local integer id = GetHandleId(GetExpiredTimer())
set thistype.buff = LoadInteger(s__Buff_hash, id, 0)
call RemoveSavedInteger(s__Buff_hash, id, 0)
call DestroyTimer(GetExpiredTimer())
endif
call TriggerEvaluate(thistype.trg)
endmethod
static method create takes code c returns thistype
local conditionfunc cf = Condition(c)
local integer id = GetHandleId(cf)
local thistype this
if HaveSavedInteger(s__Buff_hash, id, 0) then
set this = thistype(LoadInteger(s__Buff_hash, id, 0))
else
set this = thistype.allocate()
set this.tc = TriggerAddCondition(thistype.trg, cf)
set this.cf = cf
call SaveInteger(s__Buff_hash, id, 0, this)
endif
return this
endmethod
endstruct
endlibrary
Check the attached map for more examples and the demonstration of how Buff stacking works.
JASS:
scope DamageOverTime
globals
private constant integer SPELL_ID = 'dovt'
endglobals
native UnitAlive takes unit u returns boolean
struct DOTBuff extends Buff
private timer t
private static constant integer RAWCODE = 'ADOT'
private static constant integer DISPEL_TYPE = BUFF_NEGATIVE
private static constant integer STACK_TYPE = BUFF_STACK_FULL
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call UnitDamageTarget(this.source, this.target, 10.0, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
endmethod
method onRemove takes nothing returns nothing
call ReleaseTimer(this.t)
set this.t = null
call BJDebugMsg("A DOT Buff instance is removed, source = " + GetUnitName(this.source))
endmethod
method onApply takes nothing returns nothing
set this.t = NewTimerEx(this)
call TimerStart(this.t, 1.00, true, function thistype.onPeriod)
call BJDebugMsg("A DOT Buff instance is added, source = " + GetUnitName(this.source))
endmethod
implement BuffApply
endstruct
private struct DOTSpell extends array
private static method onCast takes nothing returns nothing
local DOTBuff b
call BJDebugMsg(GetUnitName(GetTriggerUnit()) + " casted DamageOverTime on " + GetUnitName(GetSpellTargetUnit()))
set b = DOTBuff.add(GetTriggerUnit(), GetSpellTargetUnit())
set b.duration = 10.0
endmethod
static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
endmethod
endstruct
endscope
CREDITS:
- muzzel - For BuffHandler which this resource is heavily based upon.
- Vexorian - For the optional TimerUtils.
- Magtheridon96 - For the optional RegisterPlayerUnitEvent
v1.00 - [26 September 2016]
- Initial Release
v1.01 - [27 September 2016]
- Added automatic Buff removal of dying units.
- Fixed bug caused by hashtable collision.
- Fixed bug where retrieved Buffs gets added to the linked list.
- Removed periodic feature.
- Removed unnecessary function calls.
- Shortened module BuffApply for less generated script.
v1.02 - [2 October 2016]
- Implemented a BuffList on newly applied units to avoid enumerating all Buff Instances upon using dispel.
v1.10 - [29 December 2016]
- Added double free protection.
- Changed "method raw" to "method rawcode".
- Added automatic preload option.
- Added an easy way to pick all Buffs of a unit.
- Added an easy way to get Buff name.
v1.11 - [30 December 2016]
- Renamed internal struct attributes "next" and "prev" to avoid conflict with user custom made attributes.
v1.20 - [21 January 2017]
- Added BuffEvent.
- Added double free protection debug message.
- Added method operator duration allowing users to retrieve the current duration of a Buff instance.
v1.21 - [23 January 2017]
- No longer prone to hashtable collision if there are too many Buff types.
- No longer supports dynamic rawcode of a Buff type. Each Buff type should only have 1 rawcode.
- Added Buff.get(source, target, typeid) & Buff.has(source, target, typeid).
- Buff rawcode offset relative to the Ability rawcode can now be configured.
v1.30 - [23 February 2017]
- Replaced stub methods by an alternative that does not create a huge amount of triggers per Buff configuration.
- Changed Buff basic configuration syntax for rawcode, dispel type and stack type.
- Buff initialization (includes callback triggers creation) can now be done manually or automatically.
- Callback triggers will now only be created if the callback function is existing.
Attachments
Last edited: