Spells - Auto-Cast Attack Ability

emjlr3

Change can be a good thing
Reaction score
395
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:

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

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

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

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

Code:
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.

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

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

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

Daelin

Kelani Mage
Reaction score
172
Some little problems. First you did not mention who "u" is. I think you wanted to use generic event but you forgot to modify it from your map. :D

I'd also like to mention that this method can prove sometimes abusable (as in it will not work if target moves really fast... take nerubian attack). 2 seconds may not be enough. Also if unit's attack is too fast, it may even prove for the second fire the effect to take place twice. If you mentioned Vex's cache, use it. I suggest you have the trigger attached to the caster. I would also use a counter (or just use user value). If the effect takes place reduce the counter. If the counter reaches 0 destroy the trigger. When caster attacks, if the trigger exists increase counter by 1, if not, create the trigger and set it to 1. It would prove to be a little bit "less efficient" but would avoid bugs.

Buff stuff is really cool btw for checking if the ability was enabled or not. Really good. ;)

Edit: Oops... just noticed a stuff I thought I should tell you. I bet you tested the ability while attacking the target only with the caster of the ability. Well... :rolleyes: I don't want to sound bitchy but if the unit is attacked by another unit while the arrow is still flying towards its target, the effect will take place. You should condition the trigger linked to local as well.

~Daelin
 

emjlr3

Change can be a good thing
Reaction score
395
it will never fire twice, I disable it the first time it fires

and it will only fire when the unit takes the damage and has the buff, so if its attacked during the arrow in the air, it will not fire

thx for looking
 

uberfoop

~=Admiral Stukov=~
Reaction score
177
Finally, a nice tut on how to make autocast abilities. I already basically knew how, but I've never actualy made one before, and its ALWAYS nice to have a premade template to base yours off of, saves you some time, lol :p


This needs to be tut paged
 

emjlr3

Change can be a good thing
Reaction score
395
hmm test map....change the raw code for the frost arrows abil, and buff, put the abil on a hero, place hero in map, paste code, test....that should about get er done
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top