Spells - Auto-Cast Attack Ability

Tutorial By emjlr3

Spell Making School: Auto-Cast Attack Ability


Introduction:

For those of you who are maybe unsure exactly, what I mean by an auto-cast attack ability is something similar to frost arrows. Where you can right click to auto cast it(and fire cold arrows on each attack), or simple click the ability on the target to use it once. Both of these are a little tricky to detect, and this will show you how it can be done easily.

The purpose of this tutorial is to teach people how to make an Auto-Cast Attack ability in JASS. This not only enables it to be very efficient, but also MUI, where in GUI this could be neither of the above.

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 second 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:

First things first, let’s create our trigger, name it, Auto Attack.

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:

function InitTrig_Auto_Attack takes nothing returns nothing set gg_trg_Auto_Attack = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(gg_trg_Auto_Attack, EVENT_PLAYER_UNIT_ATTACKED) call TriggerRegisterUnitEvent(gg_trg_Auto_Attack,u, EVENT_UNIT_SPELL_EFFECT)

Our first event will fire for us whenever any unit is attacked, period. This is fine,and we can use this. The second will fire when a spell effect is started. This, we will also need. These two allow us to cover both bases, whether just clicking on the unit with the ability, or auto-casting it.

We now add your actions and conditions function to the trigger, and close our init function:

function InitTrig_Auto_Attack takes nothing returns nothing set gg_trg_Auto_Attack = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(gg_trg_Auto_Attack, EVENT_PLAYER_UNIT_ATTACKED) call TriggerRegisterUnitEvent(gg_trg_Auto_Attack,u, EVENT_UNIT_SPELL_EFFECT) call TriggerAddCondition(gg_trg_Auto_Attack, Condition(function Auto_Attack_Detect)) call TriggerAddAction(gg_trg_Auto_Attack, function Auto_Attack_Detect_Actions) endfunction

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

InitTrig_TriggerName

The conditions function we will need to detect whether our attacker has the ability or whether the spell cast is the ability we want. And the actions function…well….you no how it goes.

Let’s create our conditions function, which remember, must always be above our init function, as every function called in a function must be above it:

function Auto_Attack_Detect takes nothing returns boolean if GetTriggerEventId()==EVENT_PLAYER_UNIT_ATTACKED and GetUnitAbilityLevel(GetAttacker(),'A000')>0 then return true elseif GetTriggerEventId()==EVENT_UNIT_SPELL_EFFECT and GetSpellAbilityId()=='A000' then return true endif return false endfunction

Alright, so we have to events that can fire, so we must check for both of these. This is pretty obvious, if the event is that a unit is attacked, we must check whether or not the attacker has the ability (‘A000’ is what I am using). If so, we return true to run our actions, if those conditions are not met, we check whether the ability effect was the ability we want (we must check if the event is a spell effect, since this elseif will run if a unit is attacked and the attacker does not have the ability) and that the ability cast is the one we want. If it is we run our actions, else we return false, and do nothing.

Next is our actions function:

function Auto_Attack_Detect_Actions takes nothing returns nothing local trigger trig = CreateTrigger() local triggeraction ta local unit u if GetTriggerEventId()==EVENT_UNIT_SPELL_EFFECT then set u = GetSpellTargetUnit() else set u = GetTriggerUnit() endif call TriggerRegisterUnitEvent(trig,u, EVENT_UNIT_DAMAGED) set ta = TriggerAddAction(trig, function Auto_Attack_Effects) call PolledWait(2) call DisableTrigger(trig) call TriggerRemoveAction(trig,ta) call DestroyTrigger(trig) set ta = null set u = null set trig = null endfunction

Not too difficult here either. We need a trigger for when the target is damaged, so we create it, we also need a triggeraction so we can remove it later, and last we need a unit, which is the target.

First thing is to check what the event was. If it is a spell, then the target is going to be GetSpellTargetUnit(), however if it is not, then it must be the unit being attacked, which is the GetTriggerUnit(). Now to detect when the target actually takes the damage, we set our event to just that, the target taking damage. Add our action function of the target taking damage. Then we wait.

Why do we wait. Well we have to destroy the trigger eventually. So, why not do it in this run of our function. I like to use a sleep time of 2., since this is usually plenty of time for the target to take damage, however it is also good incase someone is trying to abuse the ability, so that it will not fire off twice.

After our wait, we remove the triggeraction, and destroy the trigger, remembering to set all our handle locals to null at the end to avoid leaks.

Last thing it to write our action function for when and if that target does take the damage from the attack:

function Auto_Attack_Effects takes nothing returns nothing local real dam = GetEventDamage() local unit targ = GetTriggerUnit() local unit cast = GetEventDamageSource() local texttag t if GetUnitAbilityLevel(targ,'B000')>0 and GetUnitAbilityLevel(cast,'A000')>0 then call DisableTrigger(GetTriggeringTrigger()) set dam = (GetUnitAbilityLevel(cast,'A000')*.25)*dam set t = CreateTextTag() call SetTextTagText(t,"+"+I2S(R2I(dam)),.023) call SetTextTagPosUnit(t,targ,15) call SetTextTagColor(t,0,0,255,50) call SetTextTagVelocity(t,.0355 * Cos(90 * bj_DEGTORAD),.0355 * Sin(90 * bj_DEGTORAD)) call SetTextTagLifespan(t,1.) call UnitDamageTarget(cast,targ,dam,false,false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_COLD,null) call PolledWait(1.) call DestroyTextTag(t) set t = null endif set targ = null set cast = null endfunction

Alright, we first set our locals. Dam is used for the damage done, target is the unit taking the damage from the attack, and cast is the damager. Another local is used for a text tag, which is often used in abilities like this, as I will show, and another for a real, which will also be used later.

The next step is crucial. Since our target may be taking damage from any number of other things, we must check that the target has our frost arrows buff (‘B000’ is what I have used) (since the ability should be based off frost arrows, this I find is the best). The buff can be set for as low as .01 sec, and you will never see it, and that is fine for what we are doing. Also, buffs have only 1 level, so just checking if it is greater then 0 will let you know if the unit has the buff or not.

Since when the target is finally hit with the attack, they will have that buff for .01 sec, we can check whether that target takes the damage while having that buff, so only if something else happens to deal damage during that .01 sec time spawn will we have issues.

I also like to check whether the damage source has the ability, just to be even safer as to if this is the right damage to fire the trigger from.

If these things are true, we can do our effects.

The first thing I like to do is disable the trigger. This makes it so it does not fire anymore, even if the unit takes more damage. There is no need to destroy the trigger since our other function is doing that for us.

set dam = (GetUnitAbilityLevel( cast,'A000')*.25)*dam

This is simply used for setting the damage a damage amount I want the attacker to deal extra on the attack. This will yield 25% bonus damage on top of the damage taken for each level of the ability.

The next few lines are just some basic text tag functions. Instead of using the bj, I just went though and picked out all the natives to optimize it a bit more. It’s not really a big deal, but I like to do it. I then order my attacker to damage the target for the real I set earlier, the bonus damage for the ability.

The last part is to destroy our text tag and setting the variable to null to remove leaks. The reason I set it to null in the if/endif is since I only create it in there, so if that does not run, there was never any text tag to set to null. The final part at the end, as always, is used to clean anymore leaks left, which in this case is setting to null the two unit locals we used.

Alright let’s take a look at our final trigger shall we:

function Auto_Attack_Detect takes nothing returns boolean if GetTriggerEventId()==EVENT_PLAYER_UNIT_ATTACKED and GetUnitAbilityLevel(GetAttacker(),'A008')>0 then return true elseif GetTriggerEventId()==EVENT_UNIT_SPELL_EFFECT and GetSpellAbilityId()=='A008' then return true endif return false endfunction function Auto_Attack_Effects takes nothing returns nothing local real dam = GetEventDamage() local unit targ = GetTriggerUnit() local unit cast = GetEventDamageSource() local texttag t if GetUnitAbilityLevel(targ,'B000')>0 and GetUnitAbilityLevel(cast,'A000')>0 then call DisableTrigger(GetTriggeringTrigger()) set dam = (GetUnitAbilityLevel(cast,'A000')*.25)*dam set t = CreateTextTag() call SetTextTagText(t,"+"+I2S(R2I(dam)),.023) call SetTextTagPosUnit(t,targ,15) call SetTextTagColor(t,0,0,255,50) call SetTextTagVelocity(t,.0355 * Cos(90 * bj_DEGTORAD),.0355 * Sin(90 * bj_DEGTORAD)) call SetTextTagLifespan(t,1.) call UnitDamageTarget(cast,targ,dam,false,false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_COLD,null) call PolledWait(1.) call DestroyTextTag(t) set t = null endif set targ = null set cast = null endfunction function Auto_Attack_Detect_Actions takes nothing returns nothing local trigger trig = CreateTrigger() local triggeraction ta local unit u if GetTriggerEventId()==EVENT_UNIT_SPELL_EFFECT then set u = GetSpellTargetUnit() else set u = GetTriggerUnit() endif call TriggerRegisterUnitEvent(trig,u, EVENT_UNIT_DAMAGED) set ta = TriggerAddAction(trig, function Auto_Attack_Effects) call PolledWait(2) call DisableTrigger(trig) call TriggerRemoveAction(trig,ta) call DestroyTrigger(trig) set ta = null set u = null set trig = null endfunction function InitTrig_Auto_Attack takes nothing returns nothing set gg_trg_Auto_Attack = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(gg_trg_Auto_Attack, EVENT_PLAYER_UNIT_ATTACKED) call TriggerRegisterUnitEvent(gg_trg_Auto_Attack,u, EVENT_UNIT_SPELL_EFFECT) call TriggerAddCondition(gg_trg_Auto_Attack, Condition(function Auto_Attack_Detect)) call TriggerAddAction(gg_trg_Auto_Attack, function Auto_Attack_Detect_Actions) endfunction

One final thing I would like to share is that of destroying triggers. It has recently been found that destroying triggers right away can lead to nasty results in your map. This, here is a simple function you can use in your custom script section to destroy triggers, and avoid the bug:

function DesTrig_Child takes nothing returns nothing local trigger trig = bj_rescueUnitBehavior if trig!=null then call PolledWait(15.) call DestroyTrigger(trig) endif set trig = null endfunction function DesTrig takes trigger trig returns nothing if trig!=null then call DisableTrigger(trig) set bj_rescueUnitBehavior = trig call ExecuteFunc("DesTrig_Child") endif endfunction

Basically this just takes your trigger and waits 15 seconds before destroying it. Does not make much sense to me, but it avoids the bug, and that is all that matters. This can be used in place of call DestroyTrigger() in function Auto_Attack_Detect_Actions.

And that is it, an auto-cast attack ability for use in your custom map. I hope you enjoyed this and learned a thing or two, and happy mapping!

v1.00

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.