Writing Triggers

Back to lessons

Writing Triggers

In this lesson, we will learn to write our first VJass trigger, and make a WC3 spell. The spell will be simple - when cast, it deals damage based on hero intelligence and creates an ice effect on the target unit.



As we learned in the previous lesson, a trigger is actually an object type variable, which we will need to declare in our scope. First, let's create a scope with an initializer, like in the first lesson:

scope VJassSpell initializer onInit
    function onInit takes nothing returns nothing
    endfunction
endscope


All trigger declaration should go inside the initialization function. We want to create the trigger right when the map starts (except in some very rare cases). Now - what is a trigger? Let's look at the parts from GUI.

1. Event - specifies specific events that will cause this trigger to fire
2. Conditions - checks to perform before executing the trigger body
3. Actions - the body of the trigger that performs what we want the trigger to do

Just like in GUI, we have these three sections in VJass. Unlike in GUI, we must manually declare and initialize our trigger, like this:

local trigger t = CreateTrigger()

This will do three things:

- Allocate memory to store a reference to a trigger
- Allocate memory to store a trigger object
- Set the variable t to reference the memory location of the created trigger

After doing this, we can use the variable t to set up the trigger. As stated before, this setup will have three parts. All three of these - event, condition, action - will use function calls to link the appropriate sections with the trigger.

Event:

Functions to add events to a trigger are very long and difficult to remember at first. If you forget these or don't know what they are, you can always create a GUI trigger and then convert to custom text to see what the function call is. This is a good strategy for learning any JASS function.

For this, the event we want is A Unit Starts The Effect Of An Ability. In VJass, we can add this event to the trigger referenced by t with the code:

call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)

The function TriggerRegisterAnyUnitEventBJ is the function we use for all Generic Unit Events. It requires two arguments: first, the trigger to add the event to, and second the specific type of Generic Unit Event to add. In this case, the type we want is EVENT_PLAYER_UNIT_SPELL_EFFECT. This name will be memorized when you use it enough, until then you can always look it up in the trigger editor.

Condition:

In VJass, the condition is a function. This can be any function we write, but it must take nothing and return a boolean. If the condition returns true, then the trigger will execute. If not, it will not execute. So before writing the line to add a condition to a trigger, let's write the condition function. In this case, we want to check whether the ability being cast is the ability we are writing this trigger for.

From the Object Editor, press Control + D to view rawcodes of abilities, and obtain the rawcode of the created ability.



Rawcode is A000

This is the data we'll be comparing in the trigger. Now write the function - I'm going to name it onCheck.

function onCheck takes nothing returns boolean
    return(GetSpellAbilityId()=='A000')
endfunction


There is a new function used here - GetSpellAbilityId(). It takes no arguments, so the parenthesis are empty. This is the equivalent of the Event Response for ability, Ability Being Cast. We compare it to the rawcode of the ability, 'A000'. Rawcodes must be in single quotes when in code. The == operator returns true if the values on both sides are exactly the same, false if not. Do not confuse it with the single =, which sets the variable on the left to the value on the right.

Now we need to link this condition to the trigger, like this:

call TriggerAddCondition(t,Condition(function onCheck))

Some new things here:

- TriggerAddCondition links the condition with the trigger
- Condition(...) converts a function to a useable condition, as long as it takes nothing and returns a boolean.
- function ____ gets the function of the name specified. A common mistake is to write the name without the word "function" before it, or to put parenthesis after the function name like it is a call. These will make your code not compile; it must be written exactly like it appears above.

Action:

Just like the condition above, the actions for a trigger is a function that we link with the trigger. So let's write a function first. In order to be a valid action, this must take nothing and return nothing.

function onCast takes nothing returns nothing
endfunction


Now we can link it with the trigger in a similar way with the condition

call TriggerAddAction(t,function onCast)

Note that there is no equivalent to the Condition(...) statement seen above, but the function ___ part is the same. Again, do not forget the word function, and do not put parenthesis after it like it is a call.

Body of the Actions:

So, from the description above, we have several goals to do in the action

- Create an icy effect on the target
- Damage the unit using the above formula
- Subtask: calculate damage

First thing's first: let's learn about VJass event responses. In GUI, we would use Get Triggering Unit and Get Target of Ability Being Cast. These two actions have equivalents, but typing them out over and over again gets tiring, so let's store them in some local variables. The equivalent functions for those event responses are GetTriggerUnit() and GetSpellTargetUnit().

function onCast takes nothing returns nothing
    local unit source = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
endfunction


Next, let's create the special effect. In GUI, we would create a special effect at the target's location, destroy the last created special effect, and then destroy the used location to prevent leaks. A big advantage of VJass is that we do not have to use locations anymore. We can refer to a unit directly by its x/y coordinates. Of course, we do still have to delete the effect leak, but we can do it in a single line now.

The function to destroy an effect is DestroyEffect(effect e). The function to create an effect is AddSpecialEffect(string path,real x,real y). We can get the x/y coordinate of a unit by using GetUnitX/Y(unit u) respectively. Let's combine these functions to make one line that does all we need.

call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl",GetUnitX(target),GetUnitY(target)))

One thing you might notice is that the first argument of AddSpecialEffect is a very long path to the special effect coded in WC3. There is no way you will memorize these paths. Instead, when you want to make a special effect, create a GUI trigger with an action creating the effect you want, convert it to custom text, and copy the string out of the function call.

Now, how about we create a function to calculate the damage we need to deal. Damage is usually dealt with a real, since damage can be in a decimal, so this function will return a real variable.

function getAbilityDamage takes integer heroInt returns real
    return 100. + heroInt * 10.
endfunction


This function is fairly simple. The only thing that looks strange is that 100 and 10 are written as "100." and "10." The reason for this is adding a decimal point with nothing after it tells the editor that we want these numbers as reals, not integers. heroInt will be automatically converted to a real when the arithmetic takes place.

Now in the actions, we want to add a local variable to store the damage to be dealt, and then set it to call getAbilityDamage. The function we can use to get a hero's intelligence is GetHeroInt(unit hero, boolean bonus). The second argument is whether to count bonus (green) stats.

function onCast takes nothing returns nothing
    local unit source = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
    local real damage
    set damage = getAbilityDamage(GetHeroInt(source,true))
    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl",GetUnitX(target),GetUnitY(target)))
endfunction


All that's left is to deal damage. Unfortunately the function to damage a unit in VJass is very messy. This is the function:

function UnitDamageTarget takes unit whichUnit, widget target, real amount, boolean attack, boolean ranged, attacktype attackType, damagetype damageType, weapontype weaponType returns boolean

As you can see, there are lots of arguments here. Most of them, we won't worry about now. The ones that matter are whichUnit, target, and amount. The rest we will fill in with generic values. This is the line to deal damage - you can ignore all values but the first three (other than to copy them)

call UnitDamageTarget(source,target,damage,true,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_ENHANCED,WEAPON_TYPE_WHOKNOWS)

Finally the only thing left to do is set any object reference variables we used to point to null. If you don't do this, we will have a local leak - we will discuss this more later, but for now, just set both units to null at the end of the action, and the trigger t to null at the end of the initializer for a completely leak-free spell. You do not need to set ther real variable to null - and you cannot, since it is not an object.

This is the final trigger. Test yours out if you were writing this along with me:



Writing spells is one of the main uses of VJass and also can be one of the most challenging. It is highly recommended that you get some practice writing simple spells like this before moving to the next lesson, which can be quite challenging. Here are some recommended spells to complete; you should write these from scratch for the practice, don't copy paste this spell and change lines.

- A spell that deals a flat 100 damage and creates a Thunder Clap effect
- A spell that heals the target to full HP and creates a Holy Light effect (hint: look up SetUnitLifePercent or SetUnitState)
- A spell that creates a Death Coil hit effect and deals damage equal to 20 * int, and heals the caster by the same (hint: look up SetUnitState and GetUnitState. You can call SetUnitState(myUnit,UNIT_STATE_LIFE,GetUnitState(myUnit,UNIT_STATE_LIFE)+20) to heal a unit by 20 HP.)
- A spell that instantly kills the target with a War stomp effect (hint: KillUnit)

To review:

- Triggers must be declared: local trigger t = CreateTrigger()
- Triggers have three parts - Event, Condition, Action - which are linked by functions such as TriggerRegisterAnyUnitEventBJ, TriggerAddConditon, and TriggerAddAction
- Conditions and Actions are linked together as functions.
- A condition must take nothing return boolean. An action must take nothing return nothing.
- A condition must be linked as: Condition(function onCheck)
- An action must be linked as: function onCast
- If you forget the name or argument list of any function, you can always write it in GUI and then convert to custom script, or view the Functions List and search for it. Memorizing the function names will come automatically with practice.

Back to lessons