- Joined
- Feb 26, 2008
- Messages
- 892
Welcome back to what has turned into a series to tutorials concerning JASS hot off my keyboard.
If you have not journeyed with me before into the realm of JASS, I suggest that you take a look at that past venture here:
JASS: A Concise Introduction
before continuing.
This tutorial will attempt to expand your understanding of what JASS is and enhance your abilities with it. I will endeavor to keep it as simple as I did last time as we move into more advanced concepts.
And I suppose that is what we are talking about in this tutorial.
Concepts.
A scary proposition. I'm not going to be giving you blocks of code with the words, "This chunk of code will do this for you." Not going to happen. The purpose of this tutorial is to make you understand JASS. Not just be able to wield it without knowing what it is. That would be like handing you some big, complicated weapon and saying, "Here! Press the big red button when the enemy gets close!" without even giving you an explanation of what, exactly, it is.
So, let us begin this newest trek.
JASS is a programming language. Or, more simply, a language. You use this language to communicate with the computer what you want done. But computers are stupid. They do exactly what you tell them, when you tell them, in the manner that you tell them. And you must tell them what to do in a very specified way.
What does this mean? This means that whenever a computer does something you didn't want it to, if the programming language itself is not the issue (such as some flaw that you run into) it's not the computer's fault. It was, most unfortunately, yours. The programmer's. But don't let you discourage you. This is why you're reading a tutorial, right?
JASS has rules. Very strict rules, too. And you must follow them when telling the computer what to do.
So, let us move onward to telling the computer what to do in a proper manner, shall we?
Functions allow you to do actions. They are where you put your code.
They can take and return values. Take a look:
What's going on here? An integer was given, and an integer was returned. The integer that was taken got 5 added to it, and that value was returned. Simple enough? Certainly.
So, what can you do with your brand new function?
Use it.
How, you ask?
Well, let's say you wanted to use some number in a function, and you wanted to add 5 to it.
How about a loop condition? Remember loops?
What have we here? Our function we looked at above! It takes 5. But, how can it but used in an integer comparison? Take a look. It returns an integer. So, you can use it just like one! What will this function return? 10. 5 goes in, it adds 5, then returns it. 5 + 5 = 10. Easy. What if you wanted to have an add function where you can give it the two numbers to add? Take a look at this:
This function will take two numbers, and them together, and give you the result. Obviously, this is not a very useful function for practical purposes, but it gives a good illustration of the basic operation of functions.
Functions also have other purposes.
You know when you create some condition in your GUI trigger? That's a function.
Try this! Create a new trigger. Add some condition, and convert it to JASS. What do you get? Here's mine:
Start at the bottom. There's the function that creates the trigger. Above it is the actions function. And above that is some new function. How does that work? Well, take a look down at the bottom again. See the call TriggerAddCondition line? That adds a condition to the function. The name of the trigger is there, and some thing that says Condition( function Trig_Untitled...). Well, that's the condition. Now look at that function up top.
Just read the first line. See how it returns boolean? That's how it gives you a yes or no for the trigger. It gives the trigger true or false to that condition that you entered. The syntax of that actual condition is there, but see how it returns true or returns false depending on the comparison. That's the key. It gives true or false back to the AddCondition call there at the bottom.
The Condition( function ...) part is a bit tricky. This is what is called a boolexpr (boolean expression). It's an entire condition that is in itself a function. You can store these in boolexpr variables if you wish. Boolexpr functions will always return a boolean. Unfortunately, they cannot take any parameters when used in this manner. JASS will yell at you for trying.
Ah, yes. Everyone's favorite. Pick Every Unit and Do Actions. Hopefully you know how to eliminate the leaks that come with that. We'll be looking at it in JASS, of course.
So, create some such function and convert it. Here's what I have:
Again, start at the bottom. There's our trigger starter function. Now look up. There an action in the action function, but it doesn't look like something we want. It says call ForGroupBJ( udg_SomeGroup, function Trig_Untitled...).
What's this? Well, whatever actions you want to do under the Pick Every function go in their own function that is called through the ForGroupBJ function.
This means that actions you want to have happen to each unit goes in the function that is named in the ForGroupBJ call. The way you get the Picked Unit in that function is GetEnumUnit().
The same sort of thing happens with functions that set some group of units or use the Matching keyword are used. Take a look:
See the Action function where it's setting the variable? It uses the GetUnitsInRange function (this is a BJ function) and includes a condition just like we saw earlier. Yes, it's a boolexpr. And this function is using it to get the correct units to put into the unit group variable. It tests units, and puts them in the group if they meet the conditions. That's the equivalent of the Matching part of the function in GUI. Pretty simple. Here's the GUI that I used:
So, what are they? Well, global variables are the ones in the Variable Editor. You create them there and can use them in any trigger anywhere. They're Global to the entire world that is your map. So, how are local variables different? Well, let's explore that....
Local variables are created in a function and can only be used in that function. A quick example:
That creates a local unit variable called SomeUnit, and, for good measure, we set it to the triggering unit, assuming there is one.
You can use this variable in that function, and there's no need for a udg_ prefix! You can use it as-is.
The best thing about local variables is that they can't be overwritten. They only exist the one time the function is called. They act sort of like Triggering Unit in GUI. The same function can be called multiple times, but it won't lose what was stored the first time. That will be key when making spells MUI.
With local variables come more leak problems. Any local handle - WHAT!? Handle! Huh!?!
Handles. A handle is basically some object in Warcraft. If you take a look at the basic variable types in JASSCraft, you may see that some extend handle. These things will most likely leak if you use local versions of them. This is because the thing you stored can't ever be accessed again, but it's still in that variable. This also can occur with objects that extend the widget type. You don't really have to know what all of that means, just know these things can leak.
Local units can leak.
So can timers.
Unit Groups leak a bit differently when they are locals.
Locations are the same as Unit Groups.
But these are fairly simple to resolve.
If the object has a destroy function, such as timers, use it first.
call DestroyTimer(SomeTimer)
call RemoveLocation(SomeLocation)
Then, set the variable to null. Like so:
set SomeUnit = null
set SomeGroup = null
One other thing. If the object in question will never be destroyed, there is no need to clean the leak as there will not be one. For example, you don't need to clean player leaks. Even though they can technically leak, players never get destroyed, so there is no need to do anything.
Pretty easy. That should do it for your leaks.
Integers, reals, booleans, strings, and codes do not need to be destroyed or nulled, so no need to worry about them! Hold on. Only one section left to go!
Remember that ForGroupBJ that we looked at earlier? That is a BJ function, hence the two letters there at the end. This stands for Blizzard JASS. Unfortunately for us, GUI uses a lot of these, and they can be inefficient.
Take a look at this:
This is a BJ function. It doesn't say BJ, but you can find out by looking in JASSCraft to see if that function calls another function. If it says native, though, it's not a BJ.
All this does is change the order of the parameters of the native function.
Here's the native:
See that? All the one above does is call this one with the things in the right place. Isn't that inefficient? It calls the first one, then the second one. When you're writing JASS, try to avoid these kinds of silly functions that make unnecessary calls.
So! How do you feel? Beat? Perhaps. But if you go over this tutorial a few times, I'm sure you can learn a few things. Remember this is the second tutorial in this series. I do not think it will be the last. I hope you enjoyed it, at least a little, and I'll see you back here for the next one!
The next tutorial will cover optimization as one of its topics. Just to give you something to look forward to!
If you have not journeyed with me before into the realm of JASS, I suggest that you take a look at that past venture here:
JASS: A Concise Introduction
before continuing.
This tutorial will attempt to expand your understanding of what JASS is and enhance your abilities with it. I will endeavor to keep it as simple as I did last time as we move into more advanced concepts.
And I suppose that is what we are talking about in this tutorial.
Concepts.
A scary proposition. I'm not going to be giving you blocks of code with the words, "This chunk of code will do this for you." Not going to happen. The purpose of this tutorial is to make you understand JASS. Not just be able to wield it without knowing what it is. That would be like handing you some big, complicated weapon and saying, "Here! Press the big red button when the enemy gets close!" without even giving you an explanation of what, exactly, it is.
So, let us begin this newest trek.
Introduction (Take Two)
JASS is a programming language. Or, more simply, a language. You use this language to communicate with the computer what you want done. But computers are stupid. They do exactly what you tell them, when you tell them, in the manner that you tell them. And you must tell them what to do in a very specified way.
What does this mean? This means that whenever a computer does something you didn't want it to, if the programming language itself is not the issue (such as some flaw that you run into) it's not the computer's fault. It was, most unfortunately, yours. The programmer's. But don't let you discourage you. This is why you're reading a tutorial, right?
JASS has rules. Very strict rules, too. And you must follow them when telling the computer what to do.
So, let us move onward to telling the computer what to do in a proper manner, shall we?
Functions Revisited
And Boolexprs
We've looked at functions. But we need to look at them even more to fully comprehend what they can do.And Boolexprs
Functions allow you to do actions. They are where you put your code.
They can take and return values. Take a look:
JASS:
function SomeFunc takes integer A returns integer
return A + 5
endfunction
What's going on here? An integer was given, and an integer was returned. The integer that was taken got 5 added to it, and that value was returned. Simple enough? Certainly.
So, what can you do with your brand new function?
Use it.
How, you ask?
Well, let's say you wanted to use some number in a function, and you wanted to add 5 to it.
How about a loop condition? Remember loops?
JASS:
loop
exitwhen SomeInteger > SomeFunc(5)
// Do Stuff!
set SomeInteger = SomeInteger + 1
endloop
What have we here? Our function we looked at above! It takes 5. But, how can it but used in an integer comparison? Take a look. It returns an integer. So, you can use it just like one! What will this function return? 10. 5 goes in, it adds 5, then returns it. 5 + 5 = 10. Easy. What if you wanted to have an add function where you can give it the two numbers to add? Take a look at this:
JASS:
function add takes integer A, integer B returns integer
return A+B
endfunction
This function will take two numbers, and them together, and give you the result. Obviously, this is not a very useful function for practical purposes, but it gives a good illustration of the basic operation of functions.
Functions also have other purposes.
You know when you create some condition in your GUI trigger? That's a function.
Try this! Create a new trigger. Add some condition, and convert it to JASS. What do you get? Here's mine:
JASS:
function Trig_Untitled_Trigger_002_Conditions takes nothing returns boolean
if ( not ( IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) == true ) ) then
return false
endif
return true
endfunction
function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
set gg_trg_Untitled_Trigger_002 = CreateTrigger( )
call TriggerAddCondition( gg_trg_Untitled_Trigger_002, Condition( function Trig_Untitled_Trigger_002_Conditions ) )
call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
endfunction
Start at the bottom. There's the function that creates the trigger. Above it is the actions function. And above that is some new function. How does that work? Well, take a look down at the bottom again. See the call TriggerAddCondition line? That adds a condition to the function. The name of the trigger is there, and some thing that says Condition( function Trig_Untitled...). Well, that's the condition. Now look at that function up top.
Just read the first line. See how it returns boolean? That's how it gives you a yes or no for the trigger. It gives the trigger true or false to that condition that you entered. The syntax of that actual condition is there, but see how it returns true or returns false depending on the comparison. That's the key. It gives true or false back to the AddCondition call there at the bottom.
The Condition( function ...) part is a bit tricky. This is what is called a boolexpr (boolean expression). It's an entire condition that is in itself a function. You can store these in boolexpr variables if you wish. Boolexpr functions will always return a boolean. Unfortunately, they cannot take any parameters when used in this manner. JASS will yell at you for trying.
Pick Every Unit Functions and Matching Functions
Ah, yes. Everyone's favorite. Pick Every Unit and Do Actions. Hopefully you know how to eliminate the leaks that come with that. We'll be looking at it in JASS, of course.
So, create some such function and convert it. Here's what I have:
JASS:
function Trig_Untitled_Trigger_002_Func001A takes nothing returns nothing
endfunction
function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
call ForGroupBJ( udg_SomeGroup, function Trig_Untitled_Trigger_002_Func001A )
endfunction
//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
set gg_trg_Untitled_Trigger_002 = CreateTrigger( )
call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
endfunction
Again, start at the bottom. There's our trigger starter function. Now look up. There an action in the action function, but it doesn't look like something we want. It says call ForGroupBJ( udg_SomeGroup, function Trig_Untitled...).
What's this? Well, whatever actions you want to do under the Pick Every function go in their own function that is called through the ForGroupBJ function.
This means that actions you want to have happen to each unit goes in the function that is named in the ForGroupBJ call. The way you get the Picked Unit in that function is GetEnumUnit().
The same sort of thing happens with functions that set some group of units or use the Matching keyword are used. Take a look:
JASS:
function Trig_Untitled_Trigger_002_Func001002003 takes nothing returns boolean
return ( IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) == true )
endfunction
function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
set udg_SomeGroup = GetUnitsInRangeOfLocMatching(500.00, GetRectCenter(GetPlayableMapRect()), Condition(function Trig_Untitled_Trigger_002_Func001002003))
endfunction
//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
set gg_trg_Untitled_Trigger_002 = CreateTrigger( )
call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
endfunction
See the Action function where it's setting the variable? It uses the GetUnitsInRange function (this is a BJ function) and includes a condition just like we saw earlier. Yes, it's a boolexpr. And this function is using it to get the correct units to put into the unit group variable. It tests units, and puts them in the group if they meet the conditions. That's the equivalent of the Matching part of the function in GUI. Pretty simple. Here's the GUI that I used:
Code:
Set SomeGroup = (Units within 500.00 of (Center of (Playable map area)) matching (((Triggering unit) is A structure) Equal to True))
Local and Global Variables - The Shakedown
So, what are they? Well, global variables are the ones in the Variable Editor. You create them there and can use them in any trigger anywhere. They're Global to the entire world that is your map. So, how are local variables different? Well, let's explore that....
Local variables are created in a function and can only be used in that function. A quick example:
JASS:
function SomeFunc takes nothing returns nothing
local unit SomeUnit = GetTriggerUnit()
endfunction
That creates a local unit variable called SomeUnit, and, for good measure, we set it to the triggering unit, assuming there is one.
You can use this variable in that function, and there's no need for a udg_ prefix! You can use it as-is.
The best thing about local variables is that they can't be overwritten. They only exist the one time the function is called. They act sort of like Triggering Unit in GUI. The same function can be called multiple times, but it won't lose what was stored the first time. That will be key when making spells MUI.
Leaks
With local variables come more leak problems. Any local handle - WHAT!? Handle! Huh!?!
Handles. A handle is basically some object in Warcraft. If you take a look at the basic variable types in JASSCraft, you may see that some extend handle. These things will most likely leak if you use local versions of them. This is because the thing you stored can't ever be accessed again, but it's still in that variable. This also can occur with objects that extend the widget type. You don't really have to know what all of that means, just know these things can leak.
Local units can leak.
So can timers.
Unit Groups leak a bit differently when they are locals.
Locations are the same as Unit Groups.
But these are fairly simple to resolve.
If the object has a destroy function, such as timers, use it first.
call DestroyTimer(SomeTimer)
call RemoveLocation(SomeLocation)
Then, set the variable to null. Like so:
set SomeUnit = null
set SomeGroup = null
One other thing. If the object in question will never be destroyed, there is no need to clean the leak as there will not be one. For example, you don't need to clean player leaks. Even though they can technically leak, players never get destroyed, so there is no need to do anything.
Pretty easy. That should do it for your leaks.
Integers, reals, booleans, strings, and codes do not need to be destroyed or nulled, so no need to worry about them! Hold on. Only one section left to go!
BJs
Remember that ForGroupBJ that we looked at earlier? That is a BJ function, hence the two letters there at the end. This stands for Blizzard JASS. Unfortunately for us, GUI uses a lot of these, and they can be inefficient.
Take a look at this:
JASS:
SetUnitAbilityLevelSwapped(integer abilcode, unit whichUnit, integer level)
This is a BJ function. It doesn't say BJ, but you can find out by looking in JASSCraft to see if that function calls another function. If it says native, though, it's not a BJ.
All this does is change the order of the parameters of the native function.
Here's the native:
JASS:
SetUnitAbilityLevel(unit whichUnit, integer abilcode, integer level)
See that? All the one above does is call this one with the things in the right place. Isn't that inefficient? It calls the first one, then the second one. When you're writing JASS, try to avoid these kinds of silly functions that make unnecessary calls.
Conclusion
So! How do you feel? Beat? Perhaps. But if you go over this tutorial a few times, I'm sure you can learn a few things. Remember this is the second tutorial in this series. I do not think it will be the last. I hope you enjoyed it, at least a little, and I'll see you back here for the next one!
The next tutorial will cover optimization as one of its topics. Just to give you something to look forward to!
Last edited: