Branches/Loops

Back to lessons

Branching

Now that we know some basic functionality of triggers, we can start doing some more interesting coding.

The basic functionality we know so far is useful, but can't satisfy every spell we want to write. Let's say that we're going to write our own Holy Light type spell. In the Object Editor we can customize it to be cast on both Allies and Enemies.



Now, we want the spell to either heal 100 + (Int * 15) if the target is an ally, or deal 50 + (Int * 5) if the target is an enemy.



This spell has two separate outcomes depending on a condition (whether the unit is ally/enemy), so it sounds like a great place to use an if statement. If statements do exactly what you think they do: If something is true, they do something. Otherwise, they do something else. We can come up with plenty of examples of if statements in just English syntax:

if (it is the weekend) then
    sleep in
else
    wake up early
endif

if (it is nice outside) then
    go outside
else
    stay inside
endif


Etc. And it works the exact same way in vjass except we're going to put a boolean expression inside the if instead of an English statement. A boolean expression is defined as something that will either be true or false. In the example above, (it is the weekend) is an English boolean expression. It either is, or it is not. We've already seen an example of one of these in the first lesson:

(GetSpellAbilityId()=='A000')

Remember writing this? This is a boolean expression too, we just used it to return a boolean inside a condition rather than use for an if statement. For the if statement above, we need the function IsUnitAlly(...) or IsUnitEnemy(..). These two functions take a unit and a player to compare whether they are allied or not. In order to get the owning player of a unit, we can use GetOwningPlayer(myUnit). Here is the if statement for this:

if(IsUnitEnemy(source,GetOwningPlayer(target)))then
    ...deal damage...
else
    ...heal unit...
endif


Some notes about the syntax of an if statement:

- Statement always must begin with if - Statement always must end with then - Boolean expression should be in parenthesis (starting after if, ending before then) - There always must be an endif where you want the if block to end - There may or may not be an else somewhere in the code, to execute if the boolean expression returns false

I recommend you try coding the spell on your own; at this point in the tutorial, you know enough to finish it. Here is my solution:

scope VJassDemo initializer onInit

    function getDamage takes integer heroInt returns real
        return(50. + (heroInt*5.))
    endfunction

    function getHealing takes integer heroInt returns real
        return(100. + (heroInt*15.))
    endfunction

    function onCast takes nothing returns nothing
        local unit source = GetTriggerUnit()
        local unit target = GetSpellTargetUnit()
        local integer heroInt = GetHeroInt(source,true)
        local real amount = 0.
        call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl",GetUnitX(target),GetUnitY(target)))
        if(IsUnitEnemy(source,GetOwningPlayer(target)))then
            set amount = getDamage(heroInt)
            call UnitDamageTarget(source,target,amount,true,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_ENHANCED,WEAPON_TYPE_WHOKNOWS)
        else
            set amount = getHealing(heroInt)
            call SetUnitState(target,UNIT_STATE_LIFE,GetUnitState(target,UNIT_STATE_LIFE)+amount)
        endif
        set source = null
        set target = null
    endfunction

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

    function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function onCheck))
        call TriggerAddAction(t,function onCast)
        set t = null
    endfunction
endscope


In this case the function we used in our boolean expression, IsUnitEnemy, returns a boolean. So, we don't need to compare this to anything, since it will already return either true or false. This isn't always the case: let's say we want to compare the target's life percent, and do something for when it is above/below 50%. In this case we would use GetUnitLifePercent(myUnit). But this returns a real, not a boolean, so we need to use a comparison on this real. The comparison operators are:

== (is equal)
!= (is not equal)
> (strictly greater than)
>= (greater or equal)
< (strictly less than)
<= (less than or equal)


Choosing the right operator for your condition is critical. Let's say we want the spell to have two different outcomes for above/below 50% HP:

if(GetUnitLifePercent(source)>=50)then
    ...
else
    ...
endif


Furthermore, we can make the if statements have even more branches by using elseif. Elseif is another set of actions we can do, if the previous condition was false AND another condition was met. Let's say we want to have four separate outcomes for this trigger, for each section of 25% HP. (i.e. do something from 75%-100%, 50%-75%, 25%-50%, and 0%-25%)

if(GetUnitLifePercent(source)>=75)then
    ...
elseif(GetUnitLifePercent(source)>=50)then
    ...
elseif(GetUnitLifePercent(source)>=25)then
    ...
else
    ...
endif


The syntax for the elseif is very much like the syntax for the first if.

We can also combine multiple boolean expressions in one if statement. We have two operators to do this: and, or

They do exactly what you think they do. If there are two boolean expressions combined with and, the entire thing will only execute if both conditions are met. If they are joined by or, it will execute if either are met. You can join as many conditions you want with and/or. For an example, make an if statement that only executes if the target is an ally below 50% hp:

if(GetUnitLifePercent(source) < 50 and IsUnitEnemy(source,GetOwningPlayer(target)))then
    ...
endif


Looping



There's another incredibly useful type of action we can do, and that is a loop. The purpose of a loop is to execute a piece of code over and over until it is told to stop executing. Sometimes we could do this just by copy/pasting the code over and over and changing values each time, but as stated before, copying/pasting lines of code like that is usually a code mistake. It is messy, hard to read, time consuming to write, and introduces the possibility of a copy/paste error (forgetting to change the values one time) which can lead to really difficult to find bugs.

The syntax for an if statement is as follows:

loop
    exitwhen booleanexpression
    ...
endloop


This is much more difficult to understand than the if statement, so let's go through bit by bit.

loop - this keyword specifies the beginning of the loop. Anything between this and the endloop will be repeatedly executed. exitwhen - a boolean expression will come after this keyword. If the boolean expression returns true, then the loop will exit. If the boolean expression returns false, then the loop keeps repeating. code - this code will do whatever you want, but it must also contain some piece of code that changes the state being looked at in the boolean expression. What I mean by this is: the boolean expression must eventually change from being false to true because of the code in this block. If the boolean expression never changes and is always false, then we will have an infinite loop which will freeze your WC3. This will become more clear in the next example. endloop - specifies the end of the loop block

Let's take a very simple loop. We want to get the factorial of a number. Note that factorial is defined as the multiple of the given number and all integers less than, up to zero. For example: 5 factorial = 5 * 4 * 3 * 2 * 1. First, I'll write out my function block:

function factorial takes integer i returns integer
endfunction


So this accepts an integer to take factorial of, and returns the integer result. Now, we probably need a local variable to hold the end result, so I'm going to write that as well as the loop block.

function factorial takes integer i returns integer
    local integer result = 1
    loop
    endloop
endfunction


Result has an initial value of 1 because we're going to be multiplying it. Now we need to figure out the boolean expression. In the definition for factorial defined above, I said that it stops when the next number would be zero. So we can use this expression:

function factorial takes integer i returns integer
    local integer result = 1
    loop
        exitwhen i <= 0
    endloop
endfunction


It would also work to say i == 0, but then it would fail if a negative integer i was passed in. Now comes the next part: the code to write. To do a factorial operation, we multiply the result times the number we have:

function factorial takes integer i returns integer
    local integer result = 1
    loop
        exitwhen i <= 0
        set result = result * i
    endloop
endfunction


And then finally, we need to modify the state of the boolean expression. In this case, the boolean expression compares i and will exit when i <= 0. Currently, i > 0, so in order to modify the state, reduce i by 1.

function factorial takes integer i returns integer
    local integer result = 1
    loop
        exitwhen i <= 0
        set result = result * i
        set i = i - 1
    endloop
endfunction


(Sidenote: modifying an argument is allowed. It won't modify it for the caller - only in this one function)

Finally, just return the end result after the loop is finally done:

function factorial takes integer i returns integer
    local integer result = 1
    loop
        exitwhen i <= 0
        set result = result * i
        set i = i - 1
    endloop
    return result
endfunction


Now let's test. I created a simple function to display some factorials. Note that I2S converts an integer to a string:

function onInit takes nothing returns nothing
    call BJDebugMsg("5 Factorial: " + I2S(factorial(5)))
    call BJDebugMsg("10 Factorial: " + I2S(factorial(10)))
    call BJDebugMsg("0 Factorial: " + I2S(factorial(0)))
    call BJDebugMsg("-7 Factorial: " + I2S(factorial(-7)))
endfunction




These numbers would be difficult to calculate without a loop.

As before, I very much recommend that you get practice using if and loop.

- Create a spell that deals damage only if the caster is near full life
- Create a spell that repeatedly deals 75 damage until the target is below 50% life
- Create a spell that uses a loop to make a lot of special effects

To review:

- A boolean expression is a piece of code that results in either true or false
- If statements make a decision, and either execute or don't execute a block of code if the boolean expression given is true or false.
- If statements can have elseif/else blocks to do other actions based on the result of the first boolean expression
- Boolean expressions (in both if and loops) can be combined using and/or operators.
- Compare some non-boolean types using ==, !=, >, >=, <, <=
- Loops begin with loop, end with endloop, and have an exitwhen which compares a boolean expression: if true, exits. If false, continues executing code.
- The code body of the loop must somehow modify the state of the boolean expression being compared, or it will result in an infinite loop

Back to lessons