- Joined
- Jul 29, 2007
- Messages
- 5,174
ALL GLORY TO THE HYPNOTOAD.
Functions, arguments, and return values
I will begin by talking about math. More specifically, the mathematical function called Sine, usually referred to as "sin".
You can use sin in any decent calculator, it looks like this: sin(x).
The "x" denotes a number which you give sin, it does something to that number, and it returns the result to you.
In other words, if we type this line in our calculator: "sin(pi/2)", then we can say that we gave sin the number pi/2, and in response it would return to us the number 1.
Equivalently: sin(pi/2) = 1.
How is this relevant to Jass you ask? "I never came here to learn math!" you exclaim. So here we begin with Jass.
Exactly like the example above, Jass (and GUI), have a Sine function. In Jass, it is called (unimaginatively)
A function is a black box, very much like Sin, that takes values (note: these are usually referred to as "arguments" or "parameters"), in this case x, and returns something (the "return value").
We don’t necessarily know what it does, but we know what to give it, and what to expect it to return.
Jass code is in fact written in functions, just like Sin. This is the declaration of Sin in Jass:
This line tells us that there is a function called Sin. It takes a real, which is a number, called x, and it returns the result as another number.
Sin was a function made by Blizzard, but we can write our own functions!
Let’s give it a try.
We just declared a
This might seem to make a function useless. If it doesn’t take values and returns a result, what’s the use of it? That’s for later.
Every time we define a function, we need to tell Jass where it ends. You do this with the keyword
Our HelloWorld function with the closing keyword is this then:
This function doesn’t actually do anything yet, so let’s make it more interesting.
Every time that we use HelloWorld, we want it to display the message "Hello World" in Warcraft.
To display messages in Warcraft, yet another function made by Blizzard is given to use. It is called
We give BJDebugMsg a string, which is another word for text, named s, and it creates an in-game message for us with that string.
This is an example of a function, that while it doesn’t return a result to us, it is still useful, because it did something we wanted also without returning a result.
Since we wanted to display "Hello World" in our HelloWorld function, this is how it now looks:
There are two things to note here.
The "
Our text was wrapped with quotation marks. They are used to define strings (text).
Now, since our HelloWorld function actually does something, let’s
This would display our message in-game.
So we made a function that takes nothing, returns no result, but displays a message for us.
Let’s now create a function that does take values from us, and does return a result.
We will define a function named Add, that, unimaginatively, takes two numbers, adds them together, and returns the result.
As you can see, if you want a function to take more than one value, you need to put a comma (,) between each value.
So we pass our function two numbers, named "a" and "b", and we return a number as a result.
The
We can now call this function with any arbitrary numbers, and it will return to us their addition.
We will now create a function that calls our Add function, and creates an in-game message with the result.
Before we do that, let me introduce you to our third function created by Blizzard, called
Its definition looks like this:
It takes a number named "r", and returns to us a string representation of that number.
What this means, is that if we gave it the number 10, it will return the string "10" (note the quotation marks).
Since we want to make a function that creates an in-game message, we must use BJDebugMsg. Since BJDebugMsg can only create a message from a string, we must pass it a string, and since we want the message to be a result from Add, which returns a number, we must convert that number to a string, and so we must use R2S.
Let’s now define our new function. I’ll name it "MessageNumber". It will take two numbers, return no result, and create an in-game message containing the addition of those numbers.
Whoah! That line of code looks a little daunting, so let’s go over it.
We first call our Add function to add a and b.
We then give the result of Add to R2S, which returns its string representation.
Finally, we give the result of R2S to BJDebugMsg, so it would create the message for us.
I bet something is bothering you about the above function...because it lacks
What I didn’t mention until now, is that there are two ways to use a function.
Calling it, like we always did with the call keyword. Do this if the function doesn’t return any result, or if you don’t care what the result is.
Omitting the call keyword. Do this if you want to use the result of a function.
In MessageNumber, since we want to use the result of Add as the input for R2S, we omit the call keyword. In the same way, since we want to use the result of R2S in BJDebugMsg, we again omit it.
Variables
We are now going to set functions aside, and forget about them for a moment (but make sure to remember about them after the moment ends!).
Let’s talk about variables.
A variable is a data store, or, a variable is something in which you can store data.
You can use variable to store any data you want, for example: the results of functions calls.
There are two variable types in Jass. Local scope variables, and global scope variables.
Let’s begin with local scope variables, but dump the word "scope" because it makes the text lengthy.
You start defining a local variable with, you probably guessed it, the keyword
After local, you need to write the type of the variable. So far we went over the
Following is the name of the variable. You can use any name you want! It’s a free language!
Let’s start then, by creating a local real and a local string:
Now that we have "r" and "s", and we can store any number or string we want into each respectively, by using the keyword
And we stored the number 5 in "r", and the string "Hi" in "s".
We can, in fact, store any kind of code that results in the type of our variables.
This means that, remembering our Add function (yes, it’s time to partially remember functions again) and R2S, and how they return a number and a string, we can use them directly to store the results in "r" and "s":
"r" will now have the number 15 stored in it, and "s" will have the string "15" stored in it.
Also note that we didn’t need the call keyword, because we are using the results of Add and R2S.
Here as an example of using a variable in our Add function:
We create a real variable named "c", store a + b in it, and then return it as a result.
You can only define local variables inside functions, and then only at the very beginning of them.
This means that if you need a local variable, but you don’t know its value at the beginning, you still must define it at the beginning, and then set it later.
As an example, let’s edit our MessageNumber function. We will add a call to HelloWorld like so:
This function now creates an in-game message saying "Hello World", and then another message with our number result from Add(a, b).
Let’s try to use a variable, "c", to store the result from Add, like we’ve already seen:
Note the introduction of comments. Anything that is written after "//" is ignored by Jass.
This code is not valid, because we must define locals at the very beginning of the function. This means you can’t call functions before declaring a local variable.
Here is how the above should be written:
Or, another variation that works:
So, we keep saying local this, local that, but what exactly does the "local" mean?
Local variables are variables that only exist inside the function they are declared in, and also then, they are copied for each call.
What this means, is that every time you call MessageNumber, you get a new copy of a variable named "c".
If you could call MessageNumber two times at the same time, you would have two copies of "c", they don’t interfere with each other.
Global variables, on the other hand, are the exact opposite.
A global variable exists everywhere, and it is never duplicated.
Global variables are declared in a
Much like how function and endfunction define a section (or block) of code, so do the
In a globals block, you declare variables, like we have already seen, but without the local keyword.
We now have a number variable called "R" with 5 stored in it, and we can access and change it in any function we define.
Conditions
Sometimes, you need to do something only if some conditions are met.
A simple example would be the mathematical absolute value function.
The absolute value of a number is defined such that if the number is 0 or anything above it, than the absolute of it is the number itself, and if the number is negative, than the absolute value of the number is its negative.
For example, the absolute value of 5, is 5, and the absolute value of -3 is -(-3) = 3.
So, if we were to create an absolute value function (called "abs" from now on), we need to somehow run different code, depending on the value of the input.
The word "if" is the hint here.
To make a condition in, you start with
In the case of abs, we want to know if the input number (let’s call it "x") is smaller than 0.
This is how you would write it: x < 0.
"<" is generally used as a smaller-than condition.
After the condition, you follow with the keyword
Following that, comes your code, and finally the keyword
We surmised that if "x" is smaller than 0, we need to negate it. If it’s greater or equal to 0, we don’t do anything, so this is how the function would look:
And a new concept has been introduced. You can have more than one
Now let’s create a new function called clamp. The definition will look like this:
What clamp does, is take our number x, and make sure that it is in the range [minVal, maxVal].
In other words, if x is smaller than minVal, we must return minVal. If x is bigger than maxVal, we must return maxVal. If x is between (or equal to) minVal and maxVal, we simply return x.
As you read the sentence above, you can probably notice you need two conditions for this function.
One for x < minVal, and one for x > maxVal.
If you have more than one condition, you have two ways to write the code.
You can either have multiple
That is, inside a condition block, you can use the keyword
And yet another keyword,
With these keywords, you can chain multiple conditions in one block. So now let’s use these keywords to write clamp:
If you read "<" and "> as "smaller than" and "greater than" respectively, then this function is saying: "if x is smaller than minVal, return minVal. Otherwise, if x is greater than maxVal, return maxVal. Otherwise, return x".
Loops
There are times, when you want to run the same code more than once, maybe with different inputs.
For example, let’s assume we want to use BJDebugMsg to write in the game all the numbers from 1 to 10, each in its own message.
The naive approach would be to simply copy and paste the call, with a different number:
While this works, it doesn’t look very nice. Imagine, if instead of a simple call to BJDebugMsg, we would want to repeat multiple lines of code? You would get a really big and messy function.
This is where loops come in. A loop is a code block that keeps executing itself, until we tell it to stop.
A loop begins with the keyword
All the code between loop and endloop keeps running, again and again, until we somehow tell it to stop.
You tell it to stop by making an exit condition.
The idea of a loop, is that it works to make the exit condition true. That is, every time the loop runs, you get a step closer to making the exit condition true.
If this isn’t true, your loop will run forever, which is a bad thing.
You define a loop exit condition with the keyword
Before showing a loop, let me introduce you to a new variable type. So far, we only talked about real numbers, and strings.
There is another type of numbers called "
While real numbers can be of any value, integer numbers cannot have a decimal point.
-0.145, 45.3, and ¾ are all real numbers. -1 and 45 are integer numbers.
Now back to our loop. We want to BJDebugMsg all the numbers from 1 to 10 with a loop.
What can be done, is having an int variable set to 1 before the loop.
In the loop, we BJDebugMsg our int variable, and after that we add 1 to it.
The exit condition, in this case, would be when our int variable is bigger than 10.
An integer variable is used here, because integer variables should always be used for a code that counts something (unless that something can come in fractions):
A thing of note is the use of the
Every time the loop above runs, it checks if "i" is bigger than 10. If it is, it exists. Otherwise, it BJDebugMsg’s it, and adds 1 to it.
Arrays
An array is a mechanism that allows you to store many variables under one name.
You define an array by using the
The variable "r" is now not a real number, but in fact a container of many real numbers (8192 to be exact).
Each of these real numbers in "r" is referred to as an "array element".
To access an element in an array, you must first write the name of the array, and then enclose the element index in square brackets:
"x" in this context is any number between (and including) 0 and 8191, because Jass arrays are 0-indexed, which means that the first element is at index 0, and the last at index 8191.
Arrays cannot have values when you declare them, you can only use the
Suppose we have 5 different global strings, and we want to BJDebugMsg them one after another.
The naive approach would be to do exactly what the previous sentence said: create 5 different variables, and BJDebugMsg them one by one:
This might look decent enough with only these 5 strings, but what if we want to run many lines of code per string? what if we have 200 strings?
A better approach would be to use a combination of arrays and loops then.
Note that this example runs the init function when the map loads. The next section explains how to do this.
Always remember that array elements start at index 0, not 1!
InitTrig, Triggers, Events, Conditions and Actions
Up until now, while we could write as many functions as we want, we couldn't actually get Warcraft to run them. This section explains how to do exactly that.
Every GUI trigger - those white pages in the World Editor - has a function that the game calls when you load the map.
This function is always named InitTrig_<White page name>.
If your white page is named "Blarg", then behind the scenes you have a function InitTrig_Blarg that is called as the map loads.
The declaration of this function is like so:
The purpose of this function is to be a point for you to put any code you want.
Triggers are objects that actually end up running your code. They allow you to register events that they respond to, conditions, and actions.
If this sounds exactly like GUI, it's because every GUI trigger is, in fact, a Jass trigger variable.
Creating triggers is a little different than what we did so far.
To create one, you need to call the function
A trigger alone doesn't do anything, so we need to tell it what events to respond to first.
There are many different events in Jass (the same ones you can select in GUI). As an example, let's register an event that will run in 5 seconds:
The
After we successfully registered an event (or multiple ones) to our trigger, the next step is to attach a condition function to it.
A condition function is simply a function that takes nothing, and returns a
While you have already seen booleans, they weren't properly introduced yet.
A boolean is another variable type, that can be one of two values: true or false.
When we talked about conditions, and we wrote
That is, if x is indeed smaller than minVal, that would return true. If x isn't smaller than minVal, it would return false.
Every condition is in fact a boolean expression, or in other words, code that results, at the end, in a boolean: true, or false.
You attach a condition function to a trigger using the
Note the
When an event that a trigger is registered to happens, Jass runs the condition function. If it returns true, it runs the actions we attached to it, otherwise it doesn't do anything.
Actions are functions that take nothing and return nothing, and you attach them using the function
Note the use of the
Going back to the last array example, where the init function runs once when the map loads, this is how it is done:
We don't actually need a
Here is an example that uses a trigger, and BJDebugMsg's our old "Hello World" (remember that guy?):
Note that the conditions function above always returns
It is in the example just for completeness' sake.
Native functions, BJs, and what's good for you (if you guessed BJs, you guessed wrong)
There are two function types given by Blizzard - Native functions, and BJ function.
Many of the BJ functions are simple wrappers above native functions:
In this case, there is no reason why not to directly call the native function.
Since many of the BJ functions are wrappers that do nothing but call a native (and most of the times change the order of arguments for some reason), they are considered useless, and impact the game performance, and are thus avoided.
Note that not all of the wrappers are useless, but many of them are.
In all the examples so far, you can recognize BJ functions by their red color, and natives by their purple color.
The only BJ that has been used so far was BJDebugMsg, which is actually a useful wrapper: it sends a message to all the players.
We can make a function that does this as well, but it would do the same exact thing, which shows that BJDebugMsg is useful.
In general, when you want to use a BJ function, you should check what that function does. If it doesn't do anything useful beside calling a native, then just call the native directly.
Here is an example of a useless BJ function:
It calls a native with the arguments swapped, nothing more.
Primitive variables, handles, and how to properly clean after yourself
There are two types of variables: primitives, and handles.
Primitive types include real numbers, integer numbers, strings, booleans, and more types that weren't discussed here.
Handles include every Warcraft object, such as triggers, units, unit groups, special effects, and the list goes on (there are many).
All handles are created through functions. We have previously seen the
When a handle is created, it takes memory. If the handle isn't needed anymore, you need to remove it. When primitive types are not needed, they remove themselves.
When handles are created, and never removed even though you are not using them anymore, it is referred to as a "memory leak".
The most used example is a
If we have this function:
Every time it is called, we create a new location, but never remove it. If we call it 100 times, we have 100 unused locations, and they use up memory.
To remove a location, you would use the
Now we can call it any number of times without using up memory for locations we are not using.
However, there is a second variation of leaking memory!
If you set a handle variable to any value, you must set it to the special value
The previous example should, then, be written like this:
An important note is that triggers don't need to be removed!
Since you want the triggers to actually do things, you never need to remove them.
Setting them to null, however, is good practice.
For example:
Caveats
The size of arrays in Warcraft may be 8192, but index number 8191 is discarded and we like to assume that the last index is 8190. This is because in a loaded game, the 8191th index of an array does not store anything properly.
JNGP
JNGP stands for Jass New Gen Pack. It's an improved World Editor with many additions, many of them being geared towards writing Jass.
An example would be the
The main advantage of it for a Jass'er is the fact that it has a full reference of all the Jass language (types, functions, BJ functions, pre-defined variables).
If you are not using it, I highly suggest you to get it from here.
Closing
I hope this has been instructive enough as to teach how to use Jass.
For a reference of all the Jass language (if you don't have JNGP), you can look here.
Once you feel comfortable with Jass, you might want to check out vJass, which adds an extended keywords set to Jass that some people like better.
Good luck!
Jass, or "What the func?"
Functions, arguments, and return values
I will begin by talking about math. More specifically, the mathematical function called Sine, usually referred to as "sin".
You can use sin in any decent calculator, it looks like this: sin(x).
The "x" denotes a number which you give sin, it does something to that number, and it returns the result to you.
In other words, if we type this line in our calculator: "sin(pi/2)", then we can say that we gave sin the number pi/2, and in response it would return to us the number 1.
Equivalently: sin(pi/2) = 1.
How is this relevant to Jass you ask? "I never came here to learn math!" you exclaim. So here we begin with Jass.
Exactly like the example above, Jass (and GUI), have a Sine function. In Jass, it is called (unimaginatively)
Sin
, and very much like our calculator, you use it with by writing Sin(x), and it returns to you the result.A function is a black box, very much like Sin, that takes values (note: these are usually referred to as "arguments" or "parameters"), in this case x, and returns something (the "return value").
We don’t necessarily know what it does, but we know what to give it, and what to expect it to return.
Jass code is in fact written in functions, just like Sin. This is the declaration of Sin in Jass:
JASS:
function Sin takes real x returns real
Sin was a function made by Blizzard, but we can write our own functions!
Let’s give it a try.
JASS:
function HelloWorld takes nothing returns nothing
function
called HelloWorld, but with a new concept. Unlike mathematical functions, in code, we sometimes have functions that don’t take any values, don’t return results, or a combination of them both.This might seem to make a function useless. If it doesn’t take values and returns a result, what’s the use of it? That’s for later.
Every time we define a function, we need to tell Jass where it ends. You do this with the keyword
endfunction
.Our HelloWorld function with the closing keyword is this then:
JASS:
function HelloWorld takes nothing returns nothing
endfunction
Every time that we use HelloWorld, we want it to display the message "Hello World" in Warcraft.
To display messages in Warcraft, yet another function made by Blizzard is given to use. It is called
BJDebugMsg
, and it is defined like this:
JASS:
function BJDebugMsg takes string s returns nothing
This is an example of a function, that while it doesn’t return a result to us, it is still useful, because it did something we wanted also without returning a result.
Since we wanted to display "Hello World" in our HelloWorld function, this is how it now looks:
JASS:
function HelloWorld takes nothing returns nothing
call BJDebugMsg("Hello World")
endfunction
The "
call
" keyword was used. This is the same as telling Jass "use this function".Our text was wrapped with quotation marks. They are used to define strings (text).
Now, since our HelloWorld function actually does something, let’s
call
(use) it!
JASS:
call HelloWorld()
So we made a function that takes nothing, returns no result, but displays a message for us.
Let’s now create a function that does take values from us, and does return a result.
We will define a function named Add, that, unimaginatively, takes two numbers, adds them together, and returns the result.
JASS:
function Add takes real a, real b returns real
return a + b
endfunction
So we pass our function two numbers, named "a" and "b", and we return a number as a result.
The
return
keyword is used to return a result, and what follows it is what it is going to return, in this case, a + b
.We can now call this function with any arbitrary numbers, and it will return to us their addition.
call Add(5, 10)
will return the result 15.We will now create a function that calls our Add function, and creates an in-game message with the result.
Before we do that, let me introduce you to our third function created by Blizzard, called
R2S
.Its definition looks like this:
JASS:
function R2S takes real r returns string
What this means, is that if we gave it the number 10, it will return the string "10" (note the quotation marks).
Since we want to make a function that creates an in-game message, we must use BJDebugMsg. Since BJDebugMsg can only create a message from a string, we must pass it a string, and since we want the message to be a result from Add, which returns a number, we must convert that number to a string, and so we must use R2S.
Let’s now define our new function. I’ll name it "MessageNumber". It will take two numbers, return no result, and create an in-game message containing the addition of those numbers.
JASS:
function MessageNumber takes real a, real b returns nothing
call BJDebugMsg(R2S(Add(a, b)))
endfunction
We first call our Add function to add a and b.
We then give the result of Add to R2S, which returns its string representation.
Finally, we give the result of R2S to BJDebugMsg, so it would create the message for us.
I bet something is bothering you about the above function...because it lacks
call
keywords!What I didn’t mention until now, is that there are two ways to use a function.
Calling it, like we always did with the call keyword. Do this if the function doesn’t return any result, or if you don’t care what the result is.
Omitting the call keyword. Do this if you want to use the result of a function.
In MessageNumber, since we want to use the result of Add as the input for R2S, we omit the call keyword. In the same way, since we want to use the result of R2S in BJDebugMsg, we again omit it.
Variables
We are now going to set functions aside, and forget about them for a moment (but make sure to remember about them after the moment ends!).
Let’s talk about variables.
A variable is a data store, or, a variable is something in which you can store data.
You can use variable to store any data you want, for example: the results of functions calls.
There are two variable types in Jass. Local scope variables, and global scope variables.
Let’s begin with local scope variables, but dump the word "scope" because it makes the text lengthy.
You start defining a local variable with, you probably guessed it, the keyword
local
.After local, you need to write the type of the variable. So far we went over the
real
and string
types.Following is the name of the variable. You can use any name you want! It’s a free language!
Let’s start then, by creating a local real and a local string:
JASS:
local real r
local string s
set
, following by the variable name, following by an equation mark (=), and finally following with what we want to store in it.
JASS:
set r = 5
set s = "Hi"
We can, in fact, store any kind of code that results in the type of our variables.
This means that, remembering our Add function (yes, it’s time to partially remember functions again) and R2S, and how they return a number and a string, we can use them directly to store the results in "r" and "s":
JASS:
set r = Add(5, 10)
set s = R2S(r)
Also note that we didn’t need the call keyword, because we are using the results of Add and R2S.
Here as an example of using a variable in our Add function:
JASS:
function Add takes real a, real b returns real
local real c = a + b
return c
endfunction
You can only define local variables inside functions, and then only at the very beginning of them.
This means that if you need a local variable, but you don’t know its value at the beginning, you still must define it at the beginning, and then set it later.
As an example, let’s edit our MessageNumber function. We will add a call to HelloWorld like so:
JASS:
function MessageNumber takes real a, real b returns nothing
call HelloWorld()
call BJDebugMsg(R2S(Add(a, b)))
endfunction
Let’s try to use a variable, "c", to store the result from Add, like we’ve already seen:
JASS:
function MessageNumber takes real a, real b returns nothing
call HelloWorld()
local real c = Add(a, b) // Error!
call BJDebugMsg(R2S(c))
endfunction
This code is not valid, because we must define locals at the very beginning of the function. This means you can’t call functions before declaring a local variable.
Here is how the above should be written:
JASS:
function MessageNumber takes real a, real b returns nothing
local real c = Add(a, b)
call HelloWorld()
call BJDebugMsg(R2S(c))
endfunction
JASS:
function MessageNumber takes real a, real b returns nothing
local real c
call HelloWorld()
set c = Add(a, b)
call BJDebugMsg(R2S(c))
endfunction
So, we keep saying local this, local that, but what exactly does the "local" mean?
Local variables are variables that only exist inside the function they are declared in, and also then, they are copied for each call.
What this means, is that every time you call MessageNumber, you get a new copy of a variable named "c".
If you could call MessageNumber two times at the same time, you would have two copies of "c", they don’t interfere with each other.
Global variables, on the other hand, are the exact opposite.
A global variable exists everywhere, and it is never duplicated.
Global variables are declared in a
globals
block.Much like how function and endfunction define a section (or block) of code, so do the
globals
and endglobals
keywords:
JASS:
globals
endglobals
In a globals block, you declare variables, like we have already seen, but without the local keyword.
JASS:
globals
real R = 5
endglobals
Conditions
Sometimes, you need to do something only if some conditions are met.
A simple example would be the mathematical absolute value function.
The absolute value of a number is defined such that if the number is 0 or anything above it, than the absolute of it is the number itself, and if the number is negative, than the absolute value of the number is its negative.
For example, the absolute value of 5, is 5, and the absolute value of -3 is -(-3) = 3.
So, if we were to create an absolute value function (called "abs" from now on), we need to somehow run different code, depending on the value of the input.
if
the input is greater or equal to zero, we don’t actually run any code, but if it is below zero, we must negate the input.The word "if" is the hint here.
To make a condition in, you start with
if
. Following that, you need the actual condition. The "what" part in "if what".In the case of abs, we want to know if the input number (let’s call it "x") is smaller than 0.
This is how you would write it: x < 0.
"<" is generally used as a smaller-than condition.
After the condition, you follow with the keyword
then
.Following that, comes your code, and finally the keyword
endif
.We surmised that if "x" is smaller than 0, we need to negate it. If it’s greater or equal to 0, we don’t do anything, so this is how the function would look:
JASS:
function abs takes real x returns real
if x < 0 then
return -x
endif
return x
endfunction
return
keyword in a function. But always remember this: when the code executes a return, it will exit. There is no point in having more than one return keyword if it doesn’t involve conditions, because the first one will always be the one to actually happen.Now let’s create a new function called clamp. The definition will look like this:
JASS:
function clamp takes real x, real minVal, real maxVal returns real
What clamp does, is take our number x, and make sure that it is in the range [minVal, maxVal].
In other words, if x is smaller than minVal, we must return minVal. If x is bigger than maxVal, we must return maxVal. If x is between (or equal to) minVal and maxVal, we simply return x.
As you read the sentence above, you can probably notice you need two conditions for this function.
One for x < minVal, and one for x > maxVal.
If you have more than one condition, you have two ways to write the code.
You can either have multiple
if … endif
blocks of code, or you can elseif
in there.That is, inside a condition block, you can use the keyword
elseif
to declare "what happens if the condition isn’t true".And yet another keyword,
else
, is used to declare "what happens if no condition has been met".With these keywords, you can chain multiple conditions in one block. So now let’s use these keywords to write clamp:
JASS:
function clamp takes real x, real minVal, real maxVal returns real
if x < minVal then
return minVal
elseif x > maxVal then
return maxVal
else
return x
endif
endfunction
Loops
There are times, when you want to run the same code more than once, maybe with different inputs.
For example, let’s assume we want to use BJDebugMsg to write in the game all the numbers from 1 to 10, each in its own message.
The naive approach would be to simply copy and paste the call, with a different number:
JASS:
function WriteNumbers takes nothing returns nothing
call BJDebugMsg(R2S(1))
call BJDebugMsg(R2S(2))
call BJDebugMsg(R2S(3))
call BJDebugMsg(R2S(4))
call BJDebugMsg(R2S(5))
call BJDebugMsg(R2S(6))
call BJDebugMsg(R2S(7))
call BJDebugMsg(R2S(8))
call BJDebugMsg(R2S(9))
call BJDebugMsg(R2S(10))
endfunction
This is where loops come in. A loop is a code block that keeps executing itself, until we tell it to stop.
A loop begins with the keyword
loop
and ends with the keyword endloop
.
JASS:
loop
endloop
All the code between loop and endloop keeps running, again and again, until we somehow tell it to stop.
You tell it to stop by making an exit condition.
The idea of a loop, is that it works to make the exit condition true. That is, every time the loop runs, you get a step closer to making the exit condition true.
If this isn’t true, your loop will run forever, which is a bad thing.
You define a loop exit condition with the keyword
exitwhen
, followed by a condition, much like the if
keyword.Before showing a loop, let me introduce you to a new variable type. So far, we only talked about real numbers, and strings.
There is another type of numbers called "
integer
".While real numbers can be of any value, integer numbers cannot have a decimal point.
-0.145, 45.3, and ¾ are all real numbers. -1 and 45 are integer numbers.
Now back to our loop. We want to BJDebugMsg all the numbers from 1 to 10 with a loop.
What can be done, is having an int variable set to 1 before the loop.
In the loop, we BJDebugMsg our int variable, and after that we add 1 to it.
The exit condition, in this case, would be when our int variable is bigger than 10.
An integer variable is used here, because integer variables should always be used for a code that counts something (unless that something can come in fractions):
JASS:
function WriteNumbers takes nothing returns nothing
local integer i = 1
loop
exitwhen i > 10
call BJDebugMsg(I2S(i))
set i = i + 1
endloop
endfunction
I2S
function. Similarly to R2S
, it converts an integer number to its string representation.Every time the loop above runs, it checks if "i" is bigger than 10. If it is, it exists. Otherwise, it BJDebugMsg’s it, and adds 1 to it.
Arrays
An array is a mechanism that allows you to store many variables under one name.
You define an array by using the
array
keyword after the variable type, when declaring a new variable:
JASS:
local real array r
Each of these real numbers in "r" is referred to as an "array element".
To access an element in an array, you must first write the name of the array, and then enclose the element index in square brackets:
[x]
."x" in this context is any number between (and including) 0 and 8191, because Jass arrays are 0-indexed, which means that the first element is at index 0, and the last at index 8191.
Arrays cannot have values when you declare them, you can only use the
set
keyword after their creation to set elements.Suppose we have 5 different global strings, and we want to BJDebugMsg them one after another.
The naive approach would be to do exactly what the previous sentence said: create 5 different variables, and BJDebugMsg them one by one:
JASS:
globals
string s1 = "I am string 1"
string s2 = "I am string 2"
string s3 = "I am string 3"
string s4 = "I am string 4"
string s5 = "I am string 5"
endglobals
function WriteStrings takes nothing returns nothing
call BJDebugMsg(s1)
call BJDebugMsg(s2)
call BJDebugMsg(s3)
call BJDebugMsg(s4)
call BJDebugMsg(s5)
endfunction
This might look decent enough with only these 5 strings, but what if we want to run many lines of code per string? what if we have 200 strings?
A better approach would be to use a combination of arrays and loops then.
Note that this example runs the init function when the map loads. The next section explains how to do this.
JASS:
globals
string array strings
endglobals
// This will only run once when the map loads
function init takes nothing returns nothing
set strings[0] = "I am string 1"
set strings[1] = "I am string 2"
set strings[2] = "I am string 3"
set strings[3] = "I am string 4"
set strings[4] = "I am string 5"
endfunction
function WriteStrings takes nothing returns nothing
local integer i = 0
loop
exitwhen i > 4
call BJDebugMsg(strings[i])
set i = i + 1
endloop
endfunction
Always remember that array elements start at index 0, not 1!
InitTrig, Triggers, Events, Conditions and Actions
Up until now, while we could write as many functions as we want, we couldn't actually get Warcraft to run them. This section explains how to do exactly that.
Every GUI trigger - those white pages in the World Editor - has a function that the game calls when you load the map.
This function is always named InitTrig_<White page name>.
If your white page is named "Blarg", then behind the scenes you have a function InitTrig_Blarg that is called as the map loads.
The declaration of this function is like so:
JASS:
function InitTrig_<Name> takes nothing returns nothing
Triggers are objects that actually end up running your code. They allow you to register events that they respond to, conditions, and actions.
If this sounds exactly like GUI, it's because every GUI trigger is, in fact, a Jass trigger variable.
Creating triggers is a little different than what we did so far.
To create one, you need to call the function
CreateTrigger
:
JASS:
local trigger t = CreateTrigger()
A trigger alone doesn't do anything, so we need to tell it what events to respond to first.
There are many different events in Jass (the same ones you can select in GUI). As an example, let's register an event that will run in 5 seconds:
JASS:
call TriggerRegisterTimerEvent(t, 5, false)
false
keyword will be explained shortly.After we successfully registered an event (or multiple ones) to our trigger, the next step is to attach a condition function to it.
A condition function is simply a function that takes nothing, and returns a
boolean
:
JASS:
function <Name> takes nothing returns boolean
A boolean is another variable type, that can be one of two values: true or false.
When we talked about conditions, and we wrote
if x < minVal then
, we actually checked to see if x < minVal
is true or false.That is, if x is indeed smaller than minVal, that would return true. If x isn't smaller than minVal, it would return false.
Every condition is in fact a boolean expression, or in other words, code that results, at the end, in a boolean: true, or false.
You attach a condition function to a trigger using the
TriggerAddCondition
function:
JASS:
call TriggerAddCondition(t, Condition(function <Your Function>))
Condition
function inside, that tells Jass this is a condition function, and the usage of the function
keyword before the function name.When an event that a trigger is registered to happens, Jass runs the condition function. If it returns true, it runs the actions we attached to it, otherwise it doesn't do anything.
Actions are functions that take nothing and return nothing, and you attach them using the function
TriggerAddAction
:
JASS:
call TriggerAddAction(t, function <Your Function>)
function
keyword again.Going back to the last array example, where the init function runs once when the map loads, this is how it is done:
JASS:
globals
string array strings
endglobals
// This will only run once when the map loads
function init takes nothing returns nothing
set strings[0] = "I am string 1"
set strings[1] = "I am string 2"
set strings[2] = "I am string 3"
set strings[3] = "I am string 4"
set strings[4] = "I am string 5"
endfunction
function WriteStrings takes nothing returns nothing
local integer i = 0
loop
exitwhen i > 4
call BJDebugMsg(strings[i])
set i = i + 1
endloop
endfunction
// This assumes the white page is named "Strings"
function InitTrig_Strings takes nothing returns nothing
call init()
call WriteStrings()
endfunction
trigger
variable for this example, because it runs instantly when the map loads.Here is an example that uses a trigger, and BJDebugMsg's our old "Hello World" (remember that guy?):
JASS:
function actions takes nothing returns nothing
call BJDebugMsg("Hello World")
endfunction
function conditions takes nothing returns boolean
return true
endfunction
// This assumes the white page is named "HelloWorld"
function InitTrig_HelloWorld takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, 5, false)
call TriggerAddCondition(t, Condition(function conditions))
call TriggerAddAction(t, function actions)
endfunction
true
. In this case, it is not needed, you don't have to have conditions.It is in the example just for completeness' sake.
Native functions, BJs, and what's good for you (if you guessed BJs, you guessed wrong)
There are two function types given by Blizzard - Native functions, and BJ function.
- Native functions are functions that exist in the Warcraft engine itself. You can't recreate them.
- BJ functions are functions written in Jass that use the native functions.
Many of the BJ functions are simple wrappers above native functions:
JASS:
function Wrapper takes ... returns ...
return Native(...)
endfunction
Since many of the BJ functions are wrappers that do nothing but call a native (and most of the times change the order of arguments for some reason), they are considered useless, and impact the game performance, and are thus avoided.
Note that not all of the wrappers are useless, but many of them are.
In all the examples so far, you can recognize BJ functions by their red color, and natives by their purple color.
The only BJ that has been used so far was BJDebugMsg, which is actually a useful wrapper: it sends a message to all the players.
We can make a function that does this as well, but it would do the same exact thing, which shows that BJDebugMsg is useful.
In general, when you want to use a BJ function, you should check what that function does. If it doesn't do anything useful beside calling a native, then just call the native directly.
Here is an example of a useless BJ function:
JASS:
function UnitAddItemSwapped takes item whichItem, unit whichHero returns boolean
return UnitAddItem(whichHero, whichItem)
endfunction
Primitive variables, handles, and how to properly clean after yourself
There are two types of variables: primitives, and handles.
Primitive types include real numbers, integer numbers, strings, booleans, and more types that weren't discussed here.
Handles include every Warcraft object, such as triggers, units, unit groups, special effects, and the list goes on (there are many).
All handles are created through functions. We have previously seen the
CreateTrigger
function that creates a trigger.When a handle is created, it takes memory. If the handle isn't needed anymore, you need to remove it. When primitive types are not needed, they remove themselves.
When handles are created, and never removed even though you are not using them anymore, it is referred to as a "memory leak".
The most used example is a
location
(Position in GUI), which is created with the Location
function:
JASS:
function Location takes real x, real y returns location
If we have this function:
JASS:
function Leak takes nothing returns nothing
local location l = Location(0, 0)
endfunction
To remove a location, you would use the
RemoveLocation
function:
JASS:
function NoLeak takes nothing returns nothing
local location l = Location(0, 0)
call RemoveLocation(l)
endfunction
However, there is a second variation of leaking memory!
If you set a handle variable to any value, you must set it to the special value
null
when you don't need to use it anymore (generally at the end of the function).The previous example should, then, be written like this:
JASS:
function ReallyNoLeakThisTime takes nothing returns nothing
local location l = Location(0, 0)
call RemoveLocation(l)
set l = null
endfunction
An important note is that triggers don't need to be removed!
Since you want the triggers to actually do things, you never need to remove them.
Setting them to null, however, is good practice.
For example:
JASS:
function InitTrig_<Name> takes nothing returns nothing
local trigger t = CreateTrigger()
...
set t = null
endfunction
Caveats
The size of arrays in Warcraft may be 8192, but index number 8191 is discarded and we like to assume that the last index is 8190. This is because in a loaded game, the 8191th index of an array does not store anything properly.
JNGP
JNGP stands for Jass New Gen Pack. It's an improved World Editor with many additions, many of them being geared towards writing Jass.
An example would be the
globals
block, which you don't have direct access to unless you use JNGP.The main advantage of it for a Jass'er is the fact that it has a full reference of all the Jass language (types, functions, BJ functions, pre-defined variables).
If you are not using it, I highly suggest you to get it from here.
Closing
I hope this has been instructive enough as to teach how to use Jass.
For a reference of all the Jass language (if you don't have JNGP), you can look here.
Once you feel comfortable with Jass, you might want to check out vJass, which adds an extended keywords set to Jass that some people like better.
Good luck!
Last edited: