Deleted member 219079
D
Deleted member 219079
Last time updated : 21.10.2014
JASS is a scripting language for Warcraft III. It is rumored to stand for Just Another Scripting Syntax, although there's no validation for it. GUI stands for Graphical User Interface, which represents basically JASS but in more graphical way. People who have learned/mastered JASS might say: "don't use gui", and call it bad, but it's totally up to you whether you use GUI or not. Do you recognize these GUI lines?
Yes, custom scripts. Now you might be familiar with
1.1 : Our first JASS trigger
Here's a simple JASS code for you to create:
Let's see what I did there. First I converted empty trigger called myTrigger to JASS code via Edit > Convert to Custom text.
Then I removed everything before commented line, and cleared code in InitTrig_myTrigger.
Now let's take a look at
Then comes the next line
As a side note, JASSers often put all their "Actions" into the condition function, instead of creating one function for a condition and one function for actions. It saves handles and is faster.
You might wonder, why do we create trigger locally? The trigger isn't actually local, we can't decide (in vanilla JASS), whether trigger is local or not, it's just the variable which is local, trigger stays trigger. If you're confused, let's make it more simple:
Let's imagine there's a variable type "door". We create new variable of door called handle. If we use
Hive user chobibo explained this further, expand this if you want to study the role of variables a bit more:
So after we've done assigning condition to the trigger, we can nullify the variable, without touching the trigger itself. We do this by typing
1.2 : Functioning Trigger
Now let's make the trigger actually do something:
Let me clear the functions I added for you:
You may test the trigger in your map now. If it doesn't print out lol after 0 seconds of game-time, make sure the trigger name is myTrigger and repeat the steps if necessary.
vJASS stands for very-JASS. It was made by Vexorian, who is a god of vJASS who you'll never outperform. vJASS syntax adds additional features to JASS, this is why you'll need your own syntax checker for it. Make sure you have your JNGP (jasnewgenpack) working and ready before going further in this section. Remember because it uses its own syntax checker, you need to save your map before testing it.
2.1 : How to get vJASS
Download link for user made WE: http://blizzardmodding.info/4263/the-jass-newgen-pack-jngp-2-0/
2.2 : Scopes and private functions
Hopefully you haven't removed myTrigger which we made in the last chapter, we're going to transform it to vJASS world
We added
Initializer is function that's ran in the loading screen, just like Map Initialization Event in GUI.
Scope is something I recommend you to use when you're making a code which is not used elsewhere, a code which you want to work as an individual. Scopes can have private members, let's explore that feature now.
If you save and test (Ctrl + S and Ctrl + F9) the map, it does exactly the same thing, outprints lol. Why? Because vJASS is just like JASS, the syntax is just a little bit more advanced. Here's a situation where in normal JASS would happen a collision: We create a new trigger myProof:
If we try to save it the syntax checker will nag at us about redeclaring and stuff like that. To prevent it, we make functions inside these two scopes local to them. Just make
2.3 : Libraries and public functions
Make the triggers look like this (the function lines):
Try to save it. It should not nag about functions, even when we made them public. Calling the functions outside them is not that simple, you need to do it in syntax of scopeName_functionName. Here's an example trigger:
Libraries are scopes which can require other libraries. Libraries are directly connected to the map header code, and requiring means simply this: If lib A requires lib B, computer puts lib B above lib A in the map header code. Let's make our first library:
Save and test the map, you should see hello! , lol , lol , lol and lol printed on the screen.
As libraries are inserted to the map header code and scopes' role is to be "individual", scopes can use any non-private function of any library in your map.
For library to use other library's functions you need to use
2.4 : Globals and private variables
Local variable is private to that function by default, but what if we want to use our variable in another function? You can use udg, which stands for user defined global. But this is slow and requires you to use GUI's interface. This is why I suggest you to use globals, variables which are defined like GUI's variables, at initialization. They are linked to your map via your trigger, so you don't need to remove them manually after you've quit using a system, unlike GUI's variables. Let's practice:
Its name is written in caps lock for clarity. Your possible questions reaching your mind might be: 1. Where do I place them? 2. Do I have to assign them to something? 3. How to make array?
1; You place globals on top of your trigger 2; You don't have to assign them to anything (you can just define MYSTRING like
We talked about collisions between triggers in out previous segment. There can be collisions between globals too. To prevent this, type
You can use non-private globals outside your trigger. This is because they're generated on top of map code:
Above you can see map code going through syntax checker. I have global variable MYSTRING in my scope, and GUI's variable MYSTRING in GUI. They're created at the same segment in reality, but in our editing environment they're placed elsewhere.
2.5 (optional) : Structs
Structs are inspired by object oriented programming (OOP). They can make your life a lot easier.
Here's some concept explanations:
Let's make trigger which does absolutely nothing in game:
myStruct is the name of our struct type. In function init we create a pointer, and instantly issue it to point to newly created struct of type myStruct.
<structtypename>.create() gives our struct a unique id. Structs use arrays, with maximum of 8191 values. This is actually 8190, index of 0 is used for null.
You can use structs as globals, but you can't initialize them in globals section. You need to do this in init function.
Let's create a member str for our struct:
When I added
Static methods can't be called with <instance>.<method>(), they must be called with <structtype>.<method>().
We can use keywords
I made a new method in our struct which is no static, so we can call it in out init function with <instancename>.<methodname>.
Now, members can be static (non-indexed) too:
It doesn't nag about declaring static member in our create method, but it makes no sense to do so. So we simply declare it in the members section.
If you want to go more in-depth about them, follow the link at the last chapter.
2.6 (optional) : Textmacros
Textmacros are begun with
Attaching textmacros to your code is made first on progress of converting your vJASS code to something WC III can handle. Now, you can even create scopes with textmacros:
This loops "lol" 10 times for you. There's so much you can create with textmacros, explore the features yourself and you can become the next JASS god
We've learned functions, scopes, libraries, locals, globals, privates and publics. That's a lot of coverage, but there's a lot more to be covered. We'll now learn a couple of new events and how to use them.
3.1 : Periodic Loop
Let's make a looping trigger which prints out "hi" every 2 seconds:
You can recognize a function
3.2 : Timer Loop
Do you know how you can create timers and timer windows in GUI? We can use those timers in JASS to trigger functions. Let's modify our trigger by adding globals, which we learned from previous chapter:
Timer can be stopped, so it's more advanced. You can stop the timer by calling
3.3 : Ability Event
Just like in GUI, you can get event for ability cast. In GUI it'd be like this:
In JASS the equivalent of this would be:
There's a couple of new functions for me to cover:
I'll talk about
This loops the event (unit owned by <player> casts a spell) for every player.
Place a Mountain King on the map, give him thunder clap (set the level from 0 to 1 at the terrain editor) and save+run the map. It should outprint "lol" everytime you cast thunder clap. If it doesn't CnP my trigger to your map and make sure you have no other triggers on your map (after all we haven't made it private or anything.)
3.4 : Scope transformation
Let's transform the JASS trigger we made into vJASS:
Now the trigger can't collide with other triggers, as it has private functions. But if you have two scopes called mySpell, you're in for a nagging by syntax checker. Save and test run, it should do exactly same thing as before.
In the next chapter you'll learn advanced methods to use inside functions. Combine them with stuff learned in this section, and you'll able to create full JASS triggers
You may now remove the triggers you've made before, they're not necessary anymore. Now we'll learn how to use JASS. I will show you multiple functions, which you can combine in different ways for awesome triggers
4.1 : Unit Groups
Create couple of footmen and couple of peasants on the map.
In this code I trigger every unit pre-placed on the map editor to be colored blue. I've added comments for clarifying stuff.
We get the picked unit (the unit which is currently going through groupAction) with
Now do notice how I've placed the groupAction above init function. Because we need the groupAction function in init function, we have to place it above it.
4.2 : Loops
Loop is something that repeats given code until it can be concluded.
We use
Don't use this function in your map, it's worthless Let's use the loop a bit differently.
4.3 : Group Loop
Hope you didn't remove the footmen and peasants, we'll now recolor them via different kind of method
Save and test the map, you should see your units turn blue. If they did, good job, you're doing great!
But be careful when using this kind of loop, while it has no problems on-demand filled groups (groups that are filled by enumerations within the local block where you perform the FoG iteration), it has issues with groups holding units for a period of time. See GroupUtils for more details.
4.4 : If / Then / Else
Again save and test the map, you should see that some of the peasants turned teal instead of blue.
4.5 : Unit IDs
Go to object editor, find footman. Press hotkey Ctrl + D, you should see the name of footman giving you a code "hfoo". This is actually integer. The format in JASS is
Save and test, if footmen turned blue and peasants teal, I'm damn proud of you
4.6 : Group filters
Let's fix the null in
Naturally you need to change the line where you define your group to this -->
Save and test, it shouldn't affect the map in any way.
I only scratched the surface of JASS features. Why? Because when you discover stuff by yourself, you get the most out of it. When editing JASS code in JNGP, you should see "Function List" button above your code. Click and explore it well, and you can become the next JASS god And I really recommend messing with FoG loop, it's very effective way of browsing through unit groups in a single function.
We all know our code can cause major performance impact on the map we work on. Sometimes the performance drop can be felt only after an hour of gameplay, due to thousands of leaks in your map.
I don't want my dear student to make badly written maps, so here's some tips to avoid faulty scripts.
5.1 : Always null abstract locals
Certain non-nullied locals can cause performance impact. Abstract is something conceptual, for example an unit; we define unit by certain properties and unit is always a subject to change in-game.
Something not abstract is integer, we are certain 3 is 3.
To null local use
For further learning about which variable types should be nullied, open up this page. Null everything which extends widget or agent; these have abstract properties so you need to null them.
5.2 : Avoid extra native calls
Native functions are super useful and without them you basically couldn't do anything visual or physical in-game. But as these are often visual functions, they have their performance impact.
By extra I mean like this:
You could just store the result gained from
For example, hashtables have more flexible structure than variable arrays or plain variables, but take more memory AND are slower on delivering data.
Everytime you're planning to use only one dimension of hashtable, you should consider; could it be possible with only arrays?
E.g.
Other example is hidden native calls. Most often BJs are functions like:
Avoid hidden native calls to ensure efficient code.
5.3 : Pre-calculate for loops
Example right away:
-->
It's good to reduce function calls for your periodic loops, it makes your spell run smoother and stuff like unit angle, which won't change, can be pre-calculated.
5.4 : Use Proper Indexing
Proper indexing is for example dynamic indexing. It's a bit hard to visualize, so I recommend reading Visualize: Dynamic Indexing by PurgeandFire.
For alternative solution and ease of use, download TimerUtils made by Vexorian.
A quick guide:
Instead of looping through all of the instances via one loop - as you probably got used to in GUI world - use timers which function on their own times. This increases performance too.
Remember to release the timer after you don't need it!
Downside of TimerUtils is that it uses more memory. Downside of dynamic indexing is that it forces the computer to go through all of the instances at once.
5.5 : Use variables efficiently
Example:
This is a standard FoG loop, what's bad about it? The function name, it's a looping function, and looping functions gotta be as fast as possible.
Avoid 2 native calls by using global group:
By adding constant in front of the group, you ensure you won't null it in accident.
As a side note, you don't have to null FoG, loop concludes only when the FoG is null so the loop takes care of it.
5.6 : Avoid interfaces
When you call a method of a struct which extends a certain interface, vJass compiler will convert it to
Avoid interface usage especially on looping functions which are supposed to be fast. I bet you can come up with a workaround, my dear JASS student!
5.7 : Use Unit Indexer
I recommend using Perfect Unit Indexing by Cohadar. It indexes only units you need, and is pretty lightweight. There's some other good indexers too.
A quick guide:
There's a native call behind GetUnitIndex, so I recommend indexing e.g. caster's index number instead of calling it in a looping function
5.8 : Don't do stuff which is already done
Here's libraries (beside PUI and TimerUtils) I often use and recommend you to also use:
GeometryLibBase by moyack - Proper way of getting an angle from point to point
Table by Bribe - One hashtable for your whole map, requires a bit of brainwork to get started
Table by Vexorian - Alternative to thebetter Table
IsDestructableTree by BPower - depends on your map whether you need it, but is certainly useful
StructuredDD by Cokemonkey11 - Good for certain systems and spells, when you need "is attacked" event
Hope I taught you something, just PM me if there's something you're having troubles with Just remember to complete your map before pumping it to maximum with cool systems, you never know when you're going to lose interest in your project :/ But that's not up to me to teach, go learn about map-making somewhere else ^^
Sources
For advanced tutorials, I recommend:
Thanks to:
|
| |
Required :
|
JASS is a scripting language for Warcraft III. It is rumored to stand for Just Another Scripting Syntax, although there's no validation for it. GUI stands for Graphical User Interface, which represents basically JASS but in more graphical way. People who have learned/mastered JASS might say: "don't use gui", and call it bad, but it's totally up to you whether you use GUI or not. Do you recognize these GUI lines?
-
Trigger
-
Events
- Time - Elapsed game time is 0.00 seconds
- Conditions
-
Actions
- Custom script: //line
- Custom script: //another line
- Custom script: call RemoveUnit(CreateUnit('u000', Player(0), 0, 0, 270))
-
Events
call RemoveLocation(udg_p)
, call DestroyGroup(udg_ug)
and call DestroyForce(udg_pg)
, especially if you're a spell maker. Now what these do is they release memory held by objects, for example call RemoveLocation() removes Location(x,y) from it, so you can't use it anymore as it doesn't know what you're referring to. This is essential for keeping your RAM free. If you don't do this, it's called leaking. Removing leaks is what mods require from you before you can get your spell approved.1.1 : Our first JASS trigger
Here's a simple JASS code for you to create:
JASS:
function myCondition takes nothing returns boolean
return false
endfunction
//===========================================================================
function InitTrig_myTrigger takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerAddCondition( t, Condition(function myCondition) )
set t = null
endfunction
Then I removed everything before commented line, and cleared code in InitTrig_myTrigger.
Now let's take a look at
local trigger t = CreateTrigger()
, we create local variable (not accessible outside the function) trigger t, and assign function CreateTrigger() to it. Why can we assign function to it? Because native CreateTrigger takes nothing returns trigger
returns trigger. We don't need any arguments to this function because it says it takes nothing (only fill parentheses when the function asks for it). Simple, isn't it?Then comes the next line
call TriggerAddCondition( t, function myCondition )
, it's another function which does what it says: assigns a condition to a specific trigger. We tell it to assign function myCondition, which we have above, to trigger which we just created locally. Condition of course has to return a boolean, so we make myCondition return false. Why? Because if a condition (any of them) returns false, it'll not trigger the actions. And since we have no actions, we'll simply return false.As a side note, JASSers often put all their "Actions" into the condition function, instead of creating one function for a condition and one function for actions. It saves handles and is faster.
You might wonder, why do we create trigger locally? The trigger isn't actually local, we can't decide (in vanilla JASS), whether trigger is local or not, it's just the variable which is local, trigger stays trigger. If you're confused, let's make it more simple:
Let's imagine there's a variable type "door". We create new variable of door called handle. If we use
set handle = null
, we don't actually remove the door. The door still exists, but we just removed our way of using the door, by removing a handle for it. This is the reason why you need to use call RemoveLocation(udg_myLocation)
, even in gui, because if you just assign myLocation to new location, you have a leak in your code.Hive user chobibo explained this further, expand this if you want to study the role of variables a bit more:
Variables are really just identifiers, so when you create a new variable, it really points to nothing [useful] (null on global handle types but undefined on local handle types), now when you create an "object" (an object is a data that we use, like regions, units, etc.) using a "function" (CreateUnit, etc..) you use data, that data can be "linked to" by a variable. e.g.:
Now, that "point" uses memory, if were done with it (no longer need it) we must destroy it to free the memory space it uses. The first step is to tell warcraft that we no longer need it using a function Blizzard supplied.
Now, the variable links to (or points to) something useless, to avoid errors, Blizzard made it so that when a variable still links to something in memory (the now useless data, subjective of course), that said memory will not be freed. The next, albeit important and last, step is to make our variable point to "null".
Failing to do this crucial step will result in the memory being used by the object our variable links to (points to) unreachable, hence it will not be recycled by warcraft, *cue scary music ♫dum dum dumdam ♫* that is called a "memory leak". Too much memory leaks will cause your map to lag and eventually crash the game...
Now that we've done that, warcraft will recycle that chunk of memory for us!
Now, we've just freed a memory block thanks to that!
JASS:
local location point // This is a declaration, it tells the compiler
// (warcraft) to create a variable. The variable
// is empty, it does not link to anything useful.
local location point = Location(0,0) // This time, the declaration
// and use is done simultaeneously
// The variable now links to
// something useful.
JASS:
call RemoveLocation(point) // This function tells warcraft that the
// object linked to by "point" is no longer
// useful. This is the first step to remove
// this unuseful data.
JASS:
set point = null // This tells warcraft to make the variable
// point link to the "null" value. This in
// effect allows warcraft to recycle the
// memory that the previously pointed object
// by the variable "point" is linking to
// (pointing to).
Failing to do this crucial step will result in the memory being used by the object our variable links to (points to) unreachable, hence it will not be recycled by warcraft, *cue scary music ♫dum dum dumdam ♫* that is called a "memory leak". Too much memory leaks will cause your map to lag and eventually crash the game...
Now that we've done that, warcraft will recycle that chunk of memory for us!
Now, we've just freed a memory block thanks to that!
So after we've done assigning condition to the trigger, we can nullify the variable, without touching the trigger itself. We do this by typing
set t = null
.1.2 : Functioning Trigger
Now let's make the trigger actually do something:
JASS:
function myCondition takes nothing returns boolean
call BJDebugMsg( "lol" )
return false
endfunction
//===========================================================================
function InitTrig_myTrigger takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterTimerEvent( t, 0, false )
call TriggerAddCondition( t, Condition(function myCondition) )
set t = null
endfunction
call BJDebugMsg( "lol" )
this will outprint message lol.call TriggerRegisterTimerEvent( t, 0, false )
let me get the declaration for you: native TriggerRegisterTimerEvent takes trigger whichTrigger, real timeout, boolean periodic returns event
we just connect things together by using values we want as arguments. We want it to affect trigger t, timeout at 0 seconds, and not loop itself. This is pretty self-evident.You may test the trigger in your map now. If it doesn't print out lol after 0 seconds of game-time, make sure the trigger name is myTrigger and repeat the steps if necessary.
vJASS stands for very-JASS. It was made by Vexorian, who is a god of vJASS who you'll never outperform. vJASS syntax adds additional features to JASS, this is why you'll need your own syntax checker for it. Make sure you have your JNGP (jasnewgenpack) working and ready before going further in this section. Remember because it uses its own syntax checker, you need to save your map before testing it.
2.1 : How to get vJASS
Download link for user made WE: http://blizzardmodding.info/4263/the-jass-newgen-pack-jngp-2-0/
- Extract jassnewgenpack2XX.7z to somewhere on you PC
- Start NewGen WE.exe, if it doesn't boot up, try to contact the program creator
- (optional) Attach the New WE to your Start Menu for faster access
2.2 : Scopes and private functions
Hopefully you haven't removed myTrigger which we made in the last chapter, we're going to transform it to vJASS world
JASS:
scope myScope initializer init
function myCondition takes nothing returns boolean
call BJDebugMsg( "lol" )
return false
endfunction
//===========================================================================
function init takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterTimerEvent( t, 0, false )
call TriggerAddCondition( t, function myCondition )
set t = null
endfunction
endscope
scope <name> initializer <function name>
to the top row and endscope
to the bottom row.Initializer is function that's ran in the loading screen, just like Map Initialization Event in GUI.
Scope is something I recommend you to use when you're making a code which is not used elsewhere, a code which you want to work as an individual. Scopes can have private members, let's explore that feature now.
If you save and test (Ctrl + S and Ctrl + F9) the map, it does exactly the same thing, outprints lol. Why? Because vJASS is just like JASS, the syntax is just a little bit more advanced. Here's a situation where in normal JASS would happen a collision: We create a new trigger myProof:
JASS:
scope myProof initializer init
function myCondition takes nothing returns boolean
call BJDebugMsg( "lol" )
return false
endfunction
//===========================================================================
function init takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterTimerEvent( t, 0, false )
call TriggerAddCondition( t, function myCondition )
set t = null
endfunction
endscope
function myFunction
--> private function myFunction
, add private in front of each function. Now you can't use the function outside it. But what if we want to, but not to cause collisions?2.3 : Libraries and public functions
Make the triggers look like this (the function lines):
JASS:
scope myScope initializer init
public function myCondition takes nothing returns boolean
call BJDebugMsg( "lol" )
return false
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterTimerEvent( t, 0, false )
call TriggerAddCondition( t, function myCondition )
set t = null
endfunction
endscope
Try to save it. It should not nag about functions, even when we made them public. Calling the functions outside them is not that simple, you need to do it in syntax of scopeName_functionName. Here's an example trigger:
-
asd
-
Events
- Time - Elapsed game time is 0.00 seconds
- Conditions
-
Actions
- Custom script: call myScope_myCondition()
- Custom script: call myProof_myCondition()
-
Events
Libraries are scopes which can require other libraries. Libraries are directly connected to the map header code, and requiring means simply this: If lib A requires lib B, computer puts lib B above lib A in the map header code. Let's make our first library:
JASS:
library myLib initializer init
private function init takes nothing returns nothing
call BJDebugMsg("hello!")
endfunction
endlibrary
As libraries are inserted to the map header code and scopes' role is to be "individual", scopes can use any non-private function of any library in your map.
For library to use other library's functions you need to use
library <name> requires <name>
, as explained before.2.4 : Globals and private variables
Local variable is private to that function by default, but what if we want to use our variable in another function? You can use udg, which stands for user defined global. But this is slow and requires you to use GUI's interface. This is why I suggest you to use globals, variables which are defined like GUI's variables, at initialization. They are linked to your map via your trigger, so you don't need to remove them manually after you've quit using a system, unlike GUI's variables. Let's practice:
JASS:
scope myGlobals initializer init
globals
string MYSTRING = "asd"
endglobals
private function init takes nothing returns nothing
call BJDebugMsg(MYSTRING)
endfunction
endscope
1; You place globals on top of your trigger 2; You don't have to assign them to anything (you can just define MYSTRING like
string MYSTRING
) 3; You make array like this: string array MYSTRING
, but then you can't assign them at globals section.We talked about collisions between triggers in out previous segment. There can be collisions between globals too. To prevent this, type
private
in front of your global. Now your trigger should look something like this:
JASS:
scope myGlobals initializer init
globals
private string MYSTRING = "asd"
endglobals
private function init takes nothing returns nothing
call BJDebugMsg(MYSTRING)
endfunction
endscope
JASS:
globals
// User-defined
string udg_MYSTRING
// Generated
string MYSTRING= "asd"
endglobals
2.5 (optional) : Structs
Structs are inspired by object oriented programming (OOP). They can make your life a lot easier.
Here's some concept explanations:
struct
is an object defined by coderinstance
is an individual object of struct made with allocate()
member
is a variable which is used by structmethod
is a function which belongs to structstatic
means same amongst all instances of structallocate
means giving instance its own indexdeallocate
means destroying an instanceextends
is something I'm not gonna coverextends array
is awesomeLet's make trigger which does absolutely nothing in game:
JASS:
scope myScope initializer init
struct myStruct
endstruct
private function init takes nothing returns nothing
local myStruct A=myStruct.create()
call A.destroy()
endfunction
endscope
<structtypename>.create() gives our struct a unique id. Structs use arrays, with maximum of 8191 values. This is actually 8190, index of 0 is used for null.
You can use structs as globals, but you can't initialize them in globals section. You need to do this in init function.
Let's create a member str for our struct:
JASS:
scope myScope initializer init
struct myStruct
string str
endstruct
private function init takes nothing returns nothing
local myStruct A=myStruct.create()
set A.str="lol"
call BJDebugMsg(A.str)
call A.destroy()
endfunction
endscope
string str
in the struct, I added a member for it. Then we declared it in our init function. After that we called the member via BJDebugMsg using <pointername>.<membername>. Let's give our struct a function which runs on creation:
JASS:
scope myScope initializer init
struct myStruct
string str
static method create takes nothing returns thistype
local thistype returned = thistype.allocate()
set returned.str = "lol"
return returned
endmethod
endstruct
private function init takes nothing returns nothing
local myStruct A=myStruct.create()
call BJDebugMsg(A.str)
call A.destroy()
endfunction
endscope
thistype
= is the name of the struct. But you can use your struct's name (myStruct in this example).Static methods can't be called with <instance>.<method>(), they must be called with <structtype>.<method>().
We can use keywords
private
and public
in structs too. Try to add keyword public in front of static method create, it doesn't nag about it. But when we add private, we can't use the method outside of struct anymore. Let's modify the struct so that its str can be private:
JASS:
scope myScope initializer init
struct myStruct
private string str
public static method create takes nothing returns thistype
local thistype returned = thistype.allocate()
set returned.str = "lol"
return returned
endmethod
public method debugMsg takes nothing returns nothing
call BJDebugMsg(str)
endmethod
endstruct
private function init takes nothing returns nothing
local myStruct A=myStruct.create()
call A.debugMsg()
call A.destroy()
endfunction
endscope
Now, members can be static (non-indexed) too:
JASS:
scope myScope initializer init
struct myStruct
private static string str = "lol"
public static method create takes nothing returns thistype
local thistype returned = thistype.allocate()
return returned
endmethod
public method debugMsg takes nothing returns nothing
call BJDebugMsg(str)
endmethod
endstruct
private function init takes nothing returns nothing
local myStruct A=myStruct.create()
call A.debugMsg()
call A.destroy()
endfunction
endscope
If you want to go more in-depth about them, follow the link at the last chapter.
2.6 (optional) : Textmacros
//! textmacro
is your personal CnP tool made possible by vJASS. Let's imagine you want to loop different texts many times:
JASS:
scope myScope initializer init
//! textmacro LoopText takes STR
call BJDebugMsg("$STR$")
call BJDebugMsg("$STR$")
call BJDebugMsg("$STR$")
//! endtextmacro
private function init takes nothing returns nothing
//! runtextmacro LoopText("asd")
//! runtextmacro LoopText("lol")
endfunction
endscope
//! textmacro <name> takes <parameter>, <parameter>
, and ended with //! endtextmacro
, the parameters are attached to the macro via $-marks. Textmacros are called with //! runtextmacro <name>(<parameters>)
Attaching textmacros to your code is made first on progress of converting your vJASS code to something WC III can handle. Now, you can even create scopes with textmacros:
JASS:
//! textmacro NewScope takes NAME, STR
scope $NAME$
globals
private string STR = "$STR$"
endglobals
public function loop10 takes nothing returns nothing
local integer i = 0
loop
call BJDebugMsg(STR)
exitwhen i == 10
set i = i + 1
endloop
endfunction
endscope
//! endtextmacro
//! runtextmacro NewScope("asd","lol")
scope myScope initializer init
private function init takes nothing returns nothing
call asd_loop10()
endfunction
endscope
We've learned functions, scopes, libraries, locals, globals, privates and publics. That's a lot of coverage, but there's a lot more to be covered. We'll now learn a couple of new events and how to use them.
3.1 : Periodic Loop
Let's make a looping trigger which prints out "hi" every 2 seconds:
JASS:
function myFunc takes nothing returns boolean
call BJDebugMsg("hi")
return false
endfunction
//===========================================================================
function InitTrig_myTrig takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterTimerEvent( t, 2, true )
call TriggerAddCondition( t, Condition( function myFunc ) )
set t = null
endfunction
call TriggerRegisterTimerEvent( t, 2, true )
which we used in part 1 also. Part 1's trigger didn't loop itself, however. Let's take a look at the declaration: native TriggerRegisterTimerEvent takes trigger whichTrigger, real timeout, boolean periodic returns event
we changed timeout from 0 to 2 and boolean from false to true. This way we got ourselves a 2 second loop.3.2 : Timer Loop
Do you know how you can create timers and timer windows in GUI? We can use those timers in JASS to trigger functions. Let's modify our trigger by adding globals, which we learned from previous chapter:
JASS:
globals
timer TIMER = CreateTimer()
endglobals
function myFunc takes nothing returns nothing
call BJDebugMsg("hi")
endfunction
//===========================================================================
function InitTrig_myTrig takes nothing returns nothing
call TimerStart( TIMER, 2, true, function myFunc )
endfunction
call PauseTimer( TIMER )
3.3 : Ability Event
Just like in GUI, you can get event for ability cast. In GUI it'd be like this:
-
GUI
-
Events
- Unit - A unit Starts the effect of an ability
-
Conditions
- (Ability being cast) Equal to Thunder Clap
-
Actions
- Custom script: call BJDebugMsg("lol")
-
Events
JASS:
function myFunc takes nothing returns boolean
if GetSpellAbilityId() == 'AHtc' then
call BJDebugMsg("lol")
endif
return false
endfunction
//===========================================================================
function InitTrig_JASS takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( t, Condition( function myFunc ) )
set t = null
endfunction
'AHtc'
is a rawcode, I'll cover it up later in "4.5 : Unit IDs".There's a couple of new functions for me to cover:
GetSpellAbilityId() == 'AHtc'
this is a boolean, the real function here is;GetSpellAbilityId()
, this is a native which gets the ID of an ability, which triggered the function.I'll talk about
if then endif
tags and 'AHtc' ID in the next chapter more.call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
when there's a BJ at the end of a function, it means there's a script connected to it. For example the function here is actually the script below:
JASS:
function TriggerRegisterAnyUnitEventBJ takes trigger trig, playerunitevent whichEvent returns nothing
local integer index
set index = 0
loop
call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
endfunction
Place a Mountain King on the map, give him thunder clap (set the level from 0 to 1 at the terrain editor) and save+run the map. It should outprint "lol" everytime you cast thunder clap. If it doesn't CnP my trigger to your map and make sure you have no other triggers on your map (after all we haven't made it private or anything.)
3.4 : Scope transformation
Let's transform the JASS trigger we made into vJASS:
JASS:
scope mySpell initializer init
private function myFunc takes nothing returns boolean
if GetSpellAbilityId() == 'AHtc' then
call BJDebugMsg("lol")
endif
return false
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger t = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( t, Condition( function myFunc ) )
set t = null
endfunction
endscope
In the next chapter you'll learn advanced methods to use inside functions. Combine them with stuff learned in this section, and you'll able to create full JASS triggers
You may now remove the triggers you've made before, they're not necessary anymore. Now we'll learn how to use JASS. I will show you multiple functions, which you can combine in different ways for awesome triggers
4.1 : Unit Groups
Create couple of footmen and couple of peasants on the map.
In this code I trigger every unit pre-placed on the map editor to be colored blue. I've added comments for clarifying stuff.
JASS:
scope myScope initializer init
private function groupAction takes nothing returns nothing
call SetUnitColor(GetEnumUnit(), PLAYER_COLOR_BLUE) //we change the color for picked unit
endfunction
private function init takes nothing returns nothing
local group g = CreateGroup() //create variable of type group, assign it to newly created group
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null) //assign units in playable map area to group g
call ForGroup(g, function groupAction) //we assign every unit in the group through a function
call DestroyGroup(g) //no use for the unit group anymore, destroy it
set g = null //nullifying, I've covered this step in chapter 1
endfunction
endscope
GetEnumUnit()
.Now do notice how I've placed the groupAction above init function. Because we need the groupAction function in init function, we have to place it above it.
4.2 : Loops
Loop is something that repeats given code until it can be concluded.
We use
loop exitwhen true endloop
tags for this structure;
JASS:
function GetStrength takes unit u returns integer
local integer i
loop //loop, just like in gui
set i = i + 1
exitwhen i == GetUnitLevel(u) //exitwhen (true) quits the loop
endloop //end the loop
return i
endfunction
exitwhen <boolexpr>
is necessary for the loop to conclude successfully. If you don't exit your loop, you'll reach OP (operation) limit and crash the thread (simply put a process). exitwhen true
works fine if you want to exit the loop without expections.4.3 : Group Loop
Hope you didn't remove the footmen and peasants, we'll now recolor them via different kind of method
JASS:
scope myScope initializer init
private function init takes nothing returns nothing
local group g = CreateGroup()
local unit FoG //create handler for unit, don't assign it to anything
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
loop //loop, just like in gui
set FoG = FirstOfGroup(g) //assign FoG to first unit in g
exitwhen FoG == null //exitwhen (true) quits the loop
call SetUnitColor(FoG, PLAYER_COLOR_BLUE) //custom function, not necessary for FoG loop
call GroupRemoveUnit(g, FoG) //remove FoG from g to not to cause infinite loop
endloop //end the loop
call DestroyGroup(g) //destroy g
set g = null //nullify g
endfunction
endscope
But be careful when using this kind of loop, while it has no problems on-demand filled groups (groups that are filled by enumerations within the local block where you perform the FoG iteration), it has issues with groups holding units for a period of time. See GroupUtils for more details.
4.4 : If / Then / Else
if then else elseif endif
, these are the elements used for creating conditions inside functions. We'll have native GetRandomInt takes integer lowBound, integer highBound returns integer
to do us the condition. Modify the loop part on your trigger to look something like this:
JASS:
scope myScope initializer init
private function init takes nothing returns nothing
local group g = CreateGroup()
local unit FoG
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
loop
set FoG = FirstOfGroup(g)
exitwhen FoG == null
if GetRandomInt(0,1)==0 then //if (true) then
call SetUnitColor(FoG, PLAYER_COLOR_BLUE) //do this
else //if not (true) then
call SetUnitColor(FoG, PLAYER_COLOR_CYAN) //do this instead
endif //remember to always end your if:s!
call GroupRemoveUnit(g, FoG)
endloop
call DestroyGroup(g)
set g = null
endfunction
endscope
4.4.1 : Elseif
This will come handy at some point of your code.
elseif
is only checked at if the first if
didn't trigger. Example:
JASS:
if myInt==3 then
call BJDebugMsg("it's 3!")
elseif myInt==1 then
call BJDebugMsg("it's 1!")
else
call BJDebugMsg("it's alien!")
endif
4.5 : Unit IDs
Go to object editor, find footman. Press hotkey Ctrl + D, you should see the name of footman giving you a code "hfoo". This is actually integer. The format in JASS is
'hfoo'
. Now did I call it integer, it looks more like a string to us right? call BJDebugMsg(I2S('hfoo'))
will print out 1751543663, so it is integer after all, it's just in disguise. Let's turn footmen blue, and peasants teal.
JASS:
scope myScope initializer init
private function init takes nothing returns nothing
local group g = CreateGroup()
local unit FoG
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
loop
set FoG = FirstOfGroup(g)
exitwhen FoG == null
if GetUnitTypeId(FoG)=='hfoo' then //if the picked unit has this integer as id, it'll return true
call SetUnitColor(FoG, PLAYER_COLOR_BLUE)
else
call SetUnitColor(FoG, PLAYER_COLOR_CYAN)
endif
call GroupRemoveUnit(g, FoG)
endloop
call DestroyGroup(g)
set g = null
endfunction
endscope
4.6 : Group filters
Let's fix the null in
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
. The declaration for the native is native GroupEnumUnitsInRect takes group whichGroup, rect r, boolexpr filter returns nothing
so you can see the null is boolexpr. It filters whether an unit can be added to the group. Let's make a simple filter which checks if unit is dead:
JASS:
private function groupFilter takes nothing returns boolean
return not IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD)
endfunction
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, Filter(function groupFilter))
Save and test, it shouldn't affect the map in any way.
I only scratched the surface of JASS features. Why? Because when you discover stuff by yourself, you get the most out of it. When editing JASS code in JNGP, you should see "Function List" button above your code. Click and explore it well, and you can become the next JASS god And I really recommend messing with FoG loop, it's very effective way of browsing through unit groups in a single function.
We all know our code can cause major performance impact on the map we work on. Sometimes the performance drop can be felt only after an hour of gameplay, due to thousands of leaks in your map.
I don't want my dear student to make badly written maps, so here's some tips to avoid faulty scripts.
5.1 : Always null abstract locals
Certain non-nullied locals can cause performance impact. Abstract is something conceptual, for example an unit; we define unit by certain properties and unit is always a subject to change in-game.
Something not abstract is integer, we are certain 3 is 3.
To null local use
set myLocal = null
For further learning about which variable types should be nullied, open up this page. Null everything which extends widget or agent; these have abstract properties so you need to null them.
5.2 : Avoid extra native calls
Native functions are super useful and without them you basically couldn't do anything visual or physical in-game. But as these are often visual functions, they have their performance impact.
By extra I mean like this:
JASS:
call BJDebugMsg(GetUnitName(GetTriggerUnit())+" just killed someone!")
call BJDebugMsg("he's level "+I2S(GetUnitLevel(GetTriggerUnit())))
GetTriggerUnit()
- native into a variable.For example, hashtables have more flexible structure than variable arrays or plain variables, but take more memory AND are slower on delivering data.
Everytime you're planning to use only one dimension of hashtable, you should consider; could it be possible with only arrays?
E.g.
set myString = LoadStr(HASHTABLE, 0, i)
--> set myString = STRING[i]
Other example is hidden native calls. Most often BJs are functions like:
JASS:
function AttachSoundToUnitBJ takes sound soundHandle, unit whichUnit returns nothing
call AttachSoundToUnit(soundHandle, whichUnit)
endfunction
5.3 : Pre-calculate for loops
Example right away:
JASS:
function loop
...
set x = GetUnitX(missile)
set y = GetUnitY(missile)
set angle = GetUnitFacing(missile)
set x = x + 5 * Cos(angle*bj_DEGTORAD)
set y = y + 5 * Sin(angle*bj_DEGTORAD)
call SetUnitX(missile, x)
call SetUnitY(missile, y)
...
endfunction
function cast
...
set missile = CreateUnit(...)
...
endfunction
JASS:
function loop
...
set x = x + 5 * Cos(angle)
set y = y + 5 * Sin(angle)
call SetUnitX(missile, x)
call SetUnitY(missile, y)
...
endfunction
function cast
...
set x = GetUnitX(caster)
set y = GetUnitY(caster)
set angle = GetUnitFacing(caster)*bj_DEGTORAD
set missile = CreateUnit(...)
...
endfunction
5.4 : Use Proper Indexing
Proper indexing is for example dynamic indexing. It's a bit hard to visualize, so I recommend reading Visualize: Dynamic Indexing by PurgeandFire.
For alternative solution and ease of use, download TimerUtils made by Vexorian.
A quick guide:
local timer t = NewTimer()
- creates a new timercall SetTimerData(t, myInteger)
- defines a number you want to attach to a timercall GetTimerData(t)
- obtains the number you attached beforecall ReleaseTimer(t)
- destroys the timerInstead of looping through all of the instances via one loop - as you probably got used to in GUI world - use timers which function on their own times. This increases performance too.
Remember to release the timer after you don't need it!
Downside of TimerUtils is that it uses more memory. Downside of dynamic indexing is that it forces the computer to go through all of the instances at once.
5.5 : Use variables efficiently
Example:
JASS:
function myLoop takes nothing returns nothing
...
local group g = CreateGroup()
call GroupEnumUnits(g)
loop
set FoG = FirstOfGroup(g)
exitwhen FoG == null
call GroupRemoveUnit(g, FoG)
endloop
call DestroyGroup(g)
set g = null
...
endfunction
Avoid 2 native calls by using global group:
JASS:
globals
private constant group loopGroup = CreateGroup()
endglobals
function myLoop takes nothing returns nothing
...
call GroupEnumUnits(loopGroup)
loop
set FoG = FirstOfGroup(loopGroup)
exitwhen FoG == null
call GroupRemoveUnit(loopGroup, FoG)
endloop
...
endfunction
As a side note, you don't have to null FoG, loop concludes only when the FoG is null so the loop takes care of it.
5.6 : Avoid interfaces
When you call a method of a struct which extends a certain interface, vJass compiler will convert it to
TriggerExecute
which is another native function, which isn't one of the faster ones.Avoid interface usage especially on looping functions which are supposed to be fast. I bet you can come up with a workaround, my dear JASS student!
5.6.1 Avoid
You can avoid a major mess by not using extends at all. I recommend you to do so.
struct a extends b
You can avoid a major mess by not using extends at all. I recommend you to do so.
5.7 : Use Unit Indexer
I recommend using Perfect Unit Indexing by Cohadar. It indexes only units you need, and is pretty lightweight. There's some other good indexers too.
A quick guide:
local integer index = GetUnitIndex(u)
- gets unit's personal number, creates it if not created yetlocal unit u = GetIndexUnit(index)
- gets a unit bound to a certain number, if there is anyThere's a native call behind GetUnitIndex, so I recommend indexing e.g. caster's index number instead of calling it in a looping function
5.8 : Don't do stuff which is already done
Here's libraries (beside PUI and TimerUtils) I often use and recommend you to also use:
GeometryLibBase by moyack - Proper way of getting an angle from point to point
Table by Bribe - One hashtable for your whole map, requires a bit of brainwork to get started
Table by Vexorian - Alternative to the
IsDestructableTree by BPower - depends on your map whether you need it, but is certainly useful
StructuredDD by Cokemonkey11 - Good for certain systems and spells, when you need "is attacked" event
Hope I taught you something, just PM me if there's something you're having troubles with Just remember to complete your map before pumping it to maximum with cool systems, you never know when you're going to lose interest in your project :/ But that's not up to me to teach, go learn about map-making somewhere else ^^
Sources
For advanced tutorials, I recommend:
- Hive user iAyanami has written a very good tutorial about structs
- Hive user Earth-Fury has made a marvelous explanation of how computer checks your booleans
- Hive user Silvenon has written very in-depth tutorial about knockbacks, check it out if you're planning on creating a kb system (requires knowledge of structs)
Thanks to:
- edo494, for fix suggestions
- Malhorne, for helping to finish the tutorial and teaching me JASS
- chobibo, for helping with tutorial and fix suggestions
- Magtheridon96, who reviewed my first GUI spell and gave me motivation to continue learning GUI
- PurgeandFire, for reviewing this tutorial
Attachments
Last edited by a moderator: