Spells - Stacking/Non-Stacking Cumulative Buffs

Tutorial By emjlr3

Stacking/Non-Stacking Cumulative Buffs


Introduction:

For those of you who are maybe unsure exactly what I mean by a stacking or non-stacking cumulative buff, then let me give you an example. Say you have an ability, much like the one I will show here, that every time you kill an enemy, you gain +damage for a period of time, which can either dissipate after that time is up, or stack up to a point. So say at level 1 the ability adds +1 damage per kill, up to 5. Say you kill 5 units, one after the other every 2 seconds. You will have +5 damage, for the duration alloted by the abiltiy, starting at the end of each kill, then after the duratio, you will lose 1 bonus damage every 2 seconds, if it were non-stacking. What I mean by non-stacking is that, for each kill the duration of the damage increase does not lengthen for the last damage bonus added, the last one will still be removed after the allotted time. Now, however, if they are stacking, the time is reset after each, and you will keep the buff as long as you kill another unit within the allotted time that the buff lasts.

With that said, the purpose of this tutorial is to teach people how to make Stacking/Non-Stacking Cumulative Buffs. My methods will not only enable it to be very efficient, but also MUI, where in GUI this could be neither of the aforementioned.

This tutorial is NOT an introduction or starting course to JASS. It is highly recommended (more accurately, required) that you know the basics of JASS before you start on this, and that you have read my first Spell Making School tutorial here. If you feel you are not yet familiar enough with JASS, look around at the many tutorials, and come back after you feel more confident.

This is the third part of a series of tutorials covering different areas in spell-making that I plan to make - A so-called spell making school I have, per say.

All you need to follow this tutorial, is the WE, some basic JASS knowledge, JASS Craft and the CScache system found and easily implemented from Vexorian’s caster system, found here . I use this, and recommend that you do the same. We could go through and make our own here, but why bother? When there is one available for us to work from.

Even though code can be restored, I don't recommend coding spells in the WE, because a small error is enough to make the program crash on saving, and nobody enjoys that.
{Parts taken and edited from Blade.dk’s tutorial here .}

Creating Our Spell:

I will not go through each individual step as detailed as I did in the previous tutorial, because by now you should have that basic knowledge down pat.

Let us begin, by first creating an ability that stacks cumulative bonus damage:

First things first, let’s create our trigger, name it, well whatever you want, here I will name mine buff.

Ok, convert it to custom script, and delete everything it in so it is bare. The next thing to do is to create our init trigger, and add our event, condition and action:

function InitTrig_Buff takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(trig, Condition(function Buff_Detect)) call TriggerAddAction(trig, function Buff_Actions) set trig = null endfunction

If you are still a little rusty, I’m just creating a local trigger for us to use (remembering to set it to null at the end, as all handles should, except for players). We are registering the event that a unit dies, since we want this ability to fire when we kill a unit. I then add my conditions function, that is ran first, to see if the conditions are met, and if so, our added actions function will run. These I name Buff_Detect and Buff_Actions respectively.

As always, you can name your functions/local variables whatever you want, unless it is your init. function for the trigger, which must always be named:

InitTrig_TriggerName

In keeping in a “proper” order, lets make our conditions function, which should return a Boolean. Again a reminder, a Boolean is either true or false. If this function returns true, our actions are ran, if it returns false, they are not. So we must set it up in a way that it returns true when we want it to:

function Buff_Detect takes nothing returns boolean return GetUnitAbilityLevel(GetKillingUnit(),'A000')>0 endfunction

So first we think, what do be code so that this will return true when our unit of interest does the killing? Well, simply check if the killing units ability level is greater then 0. I will use the rawcode ‘A000’ for simplicity. Remember any function that returns something other then nothing, such as our InitTrig_Buff function, must have “return” then something. Here, we are returning the Boolean of whether or not the killing units ability level of ‘A000’ is greater then 0. If it is (our unit has the ability learned), it will return true, and our actions are ran.

On to our actions function:

function Buff_Actions takes nothing returns nothing local unit u = GetKillingUnit() local string s = I2S(CS_H2I(u))+"Buff" local integer lvl = GetUnitAbilityLevel(u,'A000') local integer bonus = GetUnitAbilityLevel(u,'A001') local timer t = GetTableTimer(s,"timer") local string st = I2S(CS_H2I(t))

Alright, remember I named my actions function Buff_Actions, well it’s got to be the same name, so here it is. The start of any function is always used to establish any and all locals needed for that function. We need our killing unit, set as u, the level of the ability on him, set as lvl, the level of the damage bonus ability we add to him, bonus, and a few other things. Before I explain them, let me explain the concept behind this. We are stacking damage gains on our hero, a good ability to make this from is an item ability that adds damage. They do not show an icon, and do not need to be added in spell books. What we will do is add the appropriate damage. And start a timer that will expire in the length of the damage buff. If this timer expires, it will remove our damage. However, if this function runs again before the timer expires, we will destroy that one, and create a new one, and do the same thing with it.

So, we want to attach the timer to the unit, but attaching things to units is dumb, and can leak, so we set our string as the string of the unit+”Buff”. We then get this attached timer, and it’s string, so we can attach our unit to the timer. Now if this is the first run, its ok, were just destroying the timer anyway, so if there is none, that is not a big deal.

Now before I go on I need to set up some sample damage bonus/level for this ability. Let us say:
Level 1 – 1 damage/kill up to 5
Level 2 – 2 damage/kill up to 10
Level 3 – 3 damage/kill up to 15

And it lasts for 15 seconds at all levels.

A good way to do this is to use one item damage bonus ability, and make it have 15 levels, each going up by 1, so it allows us to add the damage bonus for all levels of this ability. For here I will being using the rawcode ‘A001’ for this ability.

And now the rest of it:

function Buff_Actions takes nothing returns nothing local unit u = GetKillingUnit() local string s = I2S(CS_H2I(u))+"Buff" local integer lvl = GetUnitAbilityLevel(u,'A000') local integer bonus = GetUnitAbilityLevel(u,'A001') local timer t = GetTableTimer(s,"timer") local string st = I2S(CS_H2I(t)) if t!=null then call PauseTimer(t) call ClearTable(st) call DestroyTimer(t) endif set t = CreateTimer() set st = I2S(CS_H2I(t)) call SetTableObject(st,"hero",u) call TimerStart(t,15.,false,function Buff_Remove) call SetTableObject(s,"timer",t) if bonus==0 then call UnitAddAbility(u,'A001') endif if bonus<=(5*lvl)-lvl then call SetUnitAbilityLevel(u,'A001',bonus+lvl) else call SetUnitAbilityLevel(u,'A001',5*lvl) endif set u = null endfunction

Ok so like I said before, we have our timer, and destroy it and remove any leaks if there is a timer at all. Now we must restart it, for the duration of your buff, 15, and store our unit onto it so that if the timer expires, we can remove our damage bonus. Remembering to get our string for the new timer created, since it is now a different timer. The last part here is crucial, we must store this new timer to our unit string, so we can get it again later to destroy it. I called my timers function Buff_Remove, since that is what it is doing.

Next is manipulating the damage bonus. First check whether the unit has it or not, and if so add it. Next is a check for whether the level of it is at the max, or far enough within it that 1 more kill should set it to the max. For example, at level 3, we add +3 damage, up to 15. So we check whether bonus lvl<=15-3 (the level of the ability-the added bonus per level). If so we will add the bonus to it, if not, which must mean it is either greater then 12 or at 15, we set it to the max. Remember at the end to null all handles, except for those pesky timers.

The last part is to remove our buff after the allotted time, again my ability lasts for 15 seconds:

function Buff_Remove takes nothing returns nothing local timer t = GetExpiredTimer() local string s = I2S(CS_H2I(t)) local unit u = GetTableUnit(s,"hero") local string su = I2S(CS_H2I(u))+"Buff" call PauseTimer(t) call UnitRemoveAbility(u,'A001') call ClearTable(su) call ClearTable(s) call DestroyTimer(t) set u = null endfunction

This is pretty self explanatory. If our timer expires, well then the poor hero looses his damage bonus. So we get the stored unit from the timers string, remove our item damage bonus &#8216;A001&#8217;, remove leaks, etc., and that is that. Remember that we stored are timer into a special string, so we can remove that leak. This is shown above as well.

Let us take a look at this final trigger:

function Buff_Detect takes nothing returns boolean return GetUnitAbilityLevel(GetKillingUnit(),'A000')>0 endfunction function Buff_Remove takes nothing returns nothing local timer t = GetExpiredTimer() local string s = I2S(CS_H2I(t)) local unit u = GetTableUnit(s,"hero") local string su = I2S(CS_H2I(u))+"Buff" call PauseTimer(t) call UnitRemoveAbility(u,'A001') call ClearTable(su) call ClearTable(s) call DestroyTimer(t) set u = null endfunction function Buff_Actions takes nothing returns nothing local unit u = GetKillingUnit() local string s = I2S(CS_H2I(u))+"Buff" local integer lvl = GetUnitAbilityLevel(u,'A000') local integer bonus = GetUnitAbilityLevel(u,'A001') local timer t = GetTableTimer(s,"timer") local string st = I2S(CS_H2I(t)) if t!=null then call PauseTimer(t) call ClearTable(st) call DestroyTimer(t) endif set t = CreateTimer() set st = I2S(CS_H2I(t)) call SetTableObject(st,"hero",u) call TimerStart(t,15.,false,function Buff_Remove) call SetTableObject(s,"timer",t) if bonus==0 then call UnitAddAbility(u,'A001') endif if bonus<=(5*lvl)-lvl then call SetUnitAbilityLevel(u,'A001',bonus+lvl) else call SetUnitAbilityLevel(u,'A001',5*lvl) endif set u = null endfunction function InitTrig_Buff takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(trig, Condition(function Buff_Detect)) call TriggerAddAction(trig, function Buff_Actions) set trig = null endfunction

Not bad and fairly simple. What if, however, you do not want the damage bonuses to stack? This is even easier then the previous. Let us take a look:

function InitTrig_Buff takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(trig, Condition(function Buff_Detect)) call TriggerAddAction(trig, function Buff_Actions) set trig = null endfunction

This functions stays the same, since again we want it to run when the hero kills a unit.

function Buff_Detect takes nothing returns boolean return GetUnitAbilityLevel(GetKillingUnit(),'A000')>0 endfunction

That too is untouched, for obvious reasons as well. And now to the actions:

function Buff_Actions takes nothing returns nothing local unit u = GetKillingUnit() local integer lvl = GetUnitAbilityLevel(u,'A000') local integer bonus = GetUnitAbilityLevel(u,'A001') local timer t local string s if bonus<lvl*5 then if bonus==0 then call UnitAddAbility(u,'A001') endif if bonus<=(lvl*5)-lvl then call SetUnitAbilityLevel(u,'A001',bonus+lvl) else call SetUnitAbilityLevel(u,'A001',lvl*5) endif set t = CreateTimer() set s = I2S(CS_H2I(t)) call SetTableObject(s,"hero",u) call SetTableInt(s,"lvl",lvl) call TimerStart(t,15.,false,function Buff_Remove) endif set u = null endfunction

The general idea is the same. I will use the same ability stats and rawcodes as I used for the last one.

Basically, if the level of the bonus ability is less then the max-the bonus increment, then we up it for the increment, else we set it to the max. Again checking to see first whether the unit has the ability yet or not. Our timer is then created, we store our unit and the level to it, and set it to expire in 15 seconds, our buff duration. nothing is done however is the units is already at the max damage bonus, we just have to wait for this to fire again when the damage bonus is less then the max.

The Buff_Remove function is about the same as well:

function Buff_Remove takes nothing returns nothing local timer t = GetExpiredTimer() local string s = I2S(CS_H2I(t)) local unit u = GetTableUnit(s,"hero") local integer lvl = GetTableInt(s,"lvl") local integer bonus = GetUnitAbilityLevel(u,'A001') call PauseTimer(t) if bonus==lvl then call UnitRemoveAbility(u,'A001') else call SetUnitAbilityLevel(u,'A001',bonus-lvl) endif call ClearTable(s) call DestroyTimer(t) set u = null endfunction

Here, we will either remove the damage bonus ability, if the level of it is equal to the level of our ability, since the increments are equal to the ability level. Or we subtract from it for our increment bonus amount. Remember to remove all leaks as we have done in the past.

The idea here is pretty fundamental. Every time the hero kills a unit, if he can get more bonus, add it and start a timer that will then remove that added bonus after the 15 seconds. As opposed to last time where the bonus damage stacked up to a point, indefinitely, unless the timer expired. Let us take a look at this final trigger:

function Buff_Detect takes nothing returns boolean return GetUnitAbilityLevel(GetKillingUnit(),'A000')>0 endfunction function Buff_Remove takes nothing returns nothing local timer t = GetExpiredTimer() local string s = I2S(CS_H2I(t)) local unit u = GetTableUnit(s,"hero") local integer lvl = GetTableInt(s,"lvl") local integer bonus = GetUnitAbilityLevel(u,'A001') call PauseTimer(t) if bonus==lvl then call UnitRemoveAbility(u,'A001') else call SetUnitAbilityLevel(u,'A001',bonus-lvl) endif call ClearTable(s) call DestroyTimer(t) set u = null endfunction function Buff_Actions takes nothing returns nothing local unit u = GetKillingUnit() local integer lvl = GetUnitAbilityLevel(u,'A000') local integer bonus = GetUnitAbilityLevel(u,'A001') local timer t local string s if bonus<lvl*5 then if bonus==0 then call UnitAddAbility(u,'A001') endif if bonus<=(lvl*5)-lvl then call SetUnitAbilityLevel(u,'A001',bonus+lvl) else call SetUnitAbilityLevel(u,'A001',lvl*5) endif set t = CreateTimer() set s = I2S(CS_H2I(t)) call SetTableObject(s,"hero",u) call SetTableInt(s,"lvl",lvl) call TimerStart(t,15.,false,function Buff_Remove) endif set u = null endfunction function InitTrig_Buff takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(trig, Condition(function Buff_Detect)) call TriggerAddAction(trig, function Buff_Actions) set trig = null endfunction

Ending Notes:

Now you should not be limited just to a damage bonus. This opens up many doors for you, such as adding armor bonus for a kill, or when you take damage. Or something like adding a gold bonus for each kill you get. Even stacking a slow on an attacked unit, for each attack made. These can all be done in the same fashion, with a change in the event, and a little different coding.

And that is it, Stacking/Non-Stacking Cumulative Buffs for use in your custom map. I hope you enjoyed this and learned a thing or two, and happy mapping!

v1.01

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.