Although World Editor's Jass compiler was finally replaced by PJass using WEHelper , there were a couple of other annoyances that still needed fixing, that's the reason this project begand.
Later I felt like going further and restarted the idea of extending Jass to OOP thus JassHelper is a compiler for the vJass language extension which includes structs, libraries, textmacros and more.
Although this is not really OOP the syntax is powerful enough I hope, there is no inheritance and that's the reason I am not calling the objects classes but structs. There are however, interfaces which allow polymorphism and since they can declare attributes you can have some kind of pseudo inheritance. Pseudo inheritance can be accomplished in many ways.
The design of vJass ought to stay static eventually so I stop adding syntax construct cause that' the right thing to do. After version 1.0.0 there should not be any addition, so if you have requests hurry up cause it would not be healthy to change the syntax after 1.0.0.
If you want actual instructions on how to install and use JassHelper: skip to the usage area, remember to come back here once you have it installed so you can actually take advantage of this solution.
Warcraft III world editor always made everything harder for us, inluding declaring globals, you needed to use that dialog and it was really difficult to recreate global variables from a map or another and you were forced to use GUI for that. It was also impossible to declare globals of some types without modding world editor and then make your map completelly unopenable by normal world editor.
Global declaration freedom simply allows you to write globals blocks wherever you want, for example in the custom script section or in a 'trigger', and because of this you can even use the constant prefix which can even make finalizers able to inline constants and stuff.
The JassHelper preprocessor will simply merge all the global blocks found around the map script and move them to the top of the map, all merged in a single globals block.
function something takes nothing returns nothing set somearray[SOMETHING_INDEX]=4 endfunction globals constant integer SOMETHING_INDEX = 45 integer array somearray endglobals
Will now work without any error, there is one limitation though, you can't use functions or non-constant values in the default value, for example you can make a global start on null, 1 , 19923 , 0xFFF, true, false, "Hi" , etc. But you can't make them initialize with any function call or with a native (although it is possible to use a native, most natives tend to crash the thread when used in globals declaration). You can't also assign to another global variable, because there isn't really a way to control to what position of the map would a global declaration go.
Jass includes a debug keyword which compiles correctly but makes the rest of the code be ignored. It seemed this keyword was used for debugging purposes like a switch in a debug build of warcraft III that enabled those calls.
We can now take advantage of this hidden Jass feature. Saving a map in debug mode using JassHelper will simply remove the debug keyword so the calls are enabled, if debug mode is not enabled, JassHelper will remove the lines that begin with debug.
function Something takes integer a returns nothing debug call BJDebugMsg("a is "+I2S(a)) call KillNUnits(a) endfunction
If we use this function in a map saved with debug mode, we will see "a is value" each time this function is called, otherwise it will only call KillNUnits silently.
Yet another issue with World editor and Jass was that it is impossible to control the order of triggers in the map's script when saved. The custom script partially solved the problem but it is really problematic to ask users to paste things there, it is also anti-modular programming to keep it full of unrelated stuff.
The library preprocessor allows you to keep your top functions in the top and being able to control where each one goes. It also has an smart requirement support so it will sort the function packs for you.
The syntax is simple, you use library LIBRARYNAME or library LIBRARYNAME requires ONEREQUIREMENT or library LIBRARYNAME requires REQ1, REQ2 ...
Remember to mark the end of a library using the endlibrary keyword .
library B function Bfun takes nothing returns nothing endfunction endlibrary library A function Afun takes nothing returns nothing endfunction endlibrary
If JassHelper finds this command, it will make sure to move the Afun and Bfun functions to the top of the map's script, so the rest of the map's script can freely call Afun() or Bfun().
Notice that it would be uncertain to know what would happen if Afun() was called from a function inside the B library. The command is to move libraries to the top, but we wouldn't know if B went before or after A.
If a function inside library B needed to call a function inside library A we should let JassHelper know that A must be added before B. That's the reason the 'requires' keyword exists:
library B requires A function Bfun takes nothing returns nothing call Afun() endfunction endlibrary library A function Afun takes nothing returns nothing endfunction endlibrary
Note: For senseless reasons: requires, needs and uses all work correctly and have the same function in the library syntax.
It will move Afun to the top of the map and it will place Bfun after it, Bfun can now freely call Afun()
A library can have multiple requirements, just separate them by commas:
library C needs A, B, D function Cfun takes nothing returns nothing call Afun() call Bfun() call Dfun() endfunction endlibrary library D function Dfun takes nothing returns nothing endfunction endlibrary library B uses A function Bfun takes nothing returns nothing call Afun() endfunction endlibrary library A function Afun takes nothing returns nothing endfunction endlibrary
The result in the top of the map would be:
function Afun takes nothing returns nothing endfunction function Dfun takes nothing returns nothing endfunction function Bfun takes nothing returns nothing call Afun() endfunction function Cfun takes nothing returns nothing call Afun() call Bfun() call Dfun() endfunction
or maybe:
function Dfun takes nothing returns nothing endfunction function Afun takes nothing returns nothing endfunction function Bfun takes nothing returns nothing call Afun() endfunction function Cfun takes nothing returns nothing call Afun() call Bfun() call Dfun() endfunction
or :
function Afun takes nothing returns nothing endfunction function Bfun takes nothing returns nothing call Afun() endfunction function Dfun takes nothing returns nothing endfunction function Cfun takes nothing returns nothing call Afun() call Bfun() call Dfun() endfunction
It would depend on the order WE saves the scripts in the input file, but notice that since the requirements were set accordingly to the usage of functions by each library none of the 3 possible ways would cause any compile error.
Make sure to remember that:
It is also difficult to control what code is executed first, that's the reason libraries also have an initializer keyword, you can add initializer FUNCTION_NAME after the name of a library and it will make it be executed with priority using ExecuteFunc , ExecuteFunc is forced so it uses another thread, most libraries require heavy operations on init so we better prevent the init thread from crashing. After the initializer keyword the 'needs' statement might be used as well.
The initializers are added to the script in the same order libraries are added. So if library A needs B and both have initializers then A's inititalizer will be called before B's.
Notice the initializer is a function that takes nothing .
library A initializer InitA requires B function InitA takes nothing returns nothing call StoreInteger(B_gamecache , "a_rect" , Rect(-100.0 , 100.0 , -100.0 , 100 ) ) endfunction endlibrary library B initializer InitB globals gamecache B_gamecache endglobals function InitB takes nothing returns nothing set B_gamecache=InitGameCache("B") endfunction endlibrary
B's initializer will be called on init before A's initializer.
Hints:
With the adition of libraries it was a good idea to add some scope control, private members are a great way of protecting users from themselves and to avoid collisions.
library privatetest globals private integer N=0 endglobals private function x takes nothing returns nothing set N=N+1 endfunction function privatetest takes nothing returns nothing call x() call x() endfunction endlibrary library otherprivatetest globals private integer N=5 endglobals private function x takes nothing returns nothing set N=N+1 endfunction function otherprivatetest takes nothing returns nothing call x() call x() endfunction endlibrary
Notice how both libraries have private globals and functions with the same names, this wouldn't cause any syntax errors since the private preprocessor will make sure that private members are only available for that scope and don't conflict with things named the same present in other scopes. In this case private members are only to be used by the libraries in which they are declared.
Sometimes, you don't want the code to go to the top of your script (it is not really a function library) yet you' still want to use the private keyword for a group of globals and functions. This is the reason we defined the scope keyword
The scope keyword has this syntax: scope NAME [...script block...] endscope
So, functions and other declarations inside an scope can freely use the private members of the scope, but code outside won't be able to. (Notice that a library is to be considered to have an internal scope with its name)
There are many applications for this feature:
scope GetUnitDebugStr private function H2I takes handle h returns integer return h return 0 endfunction function GetUnitDebugStr takes unit u returns string return GetUnitName(u)+"_"+I2S(H2I(u)) endfunction endscope
In this case, the function uses H2I, but H2I is a very common function name, so there could be conflicts with other scripts that might declare it as well, you could add a whole preffix to the H2I function yourself, or make the function a library that requires another library H2I, but that can be sometimes too complicated, by using an scope and private you can freely use H2I in that function without worrying. It doesn' matter if another H2I is declared elsewhere and it is not a private function, private also makes the scope keep a priority for its members.
It is more important for globals because if you make a function pack you might want to disallow direct access to globals but just allow access to some functions, to keep a sense of encapsullation, for example.
The way private work is actually by automatically prefixing scopename(random digit)__ to the identifier names of the private members. The random digit is a way to let it be truly private so people can not even use them by adding the preffix themselves. A double _ is used because we decided that it is the way to recognize preprocessor-generated variables/functions, so you should avoid to use double __ in your human-declarated identifier names. Being able to recognize preprocessor-generated identifiers is useful when reading the output file (for example when PJass returns syntax errors).
In order to use private members ExecuteFunc or real value change events you have to use SCOPE_PRIVATE (see bellow)
Hint:In a similar way to libraries, scopes used to have a syntax that required //! , that old syntax is still supported but it is being deprecated.
Public members are closely related to private members in that they do mostly the same, the difference is that public members don't get their names randomized, and can be used outside the scope. For a variable/function declared as public in an scope called SCP you can just use the declared function/variable name inside the scope, but to use it outside of the scope you call it with an SCP_ preffix.
An example should be easier to understand:
library cookiesystem
public function ko takes nothing returns nothing
call BJDebugMsg("a")
endfunction
function thisisnotpublicnorprivate takes nothing returns nothing
call ko()
call cookiesystem_ko() //cookiesystem_ preffix is optional
endfunction
endlibrary
function outside takes nothing returns nothing
call cookiesystem_ko() //cookiesystem_ preffix is required
endfunction
Public function members can be used by ExecuteFunc or real variable value events, but they always need the scope prefix when used as string:
library cookiesystem public function ko takes nothing returns nothing call BJDebugMsg("a") endfunction function thisisnotpublicnorprivate takes nothing returns nothing call ExecuteFunc("cookiesystem_ko") //Needs the prefix no matter it is inside the scope call ExecuteFunc("ko") //This will most likely crash the game. call cookiesystem_ko() //Does not need the prefix but can use it. call ko() //since it doesn't need the prefix, the line works correctly. endfunction endlibrary
Alternatively, you may use SCOPE_PREFIX (see bellow)
Note: If you use public on a function called InitTrig, it is handled in an special way, instead of becoming ScopeName_InitTrig it will become InitTrig_ScopeName, so you could have an scope/library in a trigger with the same scope name and use this public function instead of manually making InitTrig_Correctname.
Scopes can be nested, don't confuse this statement with `libraries can be nested´, in fact, you cannot even place a library inside an scope definition. You can however, have scope inside either library or scope definitions.
An scope inside another scope is considered a child scope. A child scope is considered to be a public member of the parent scope (?).
A child scope cannot declare members that were previously declared as private or global by a parent scope.
A child scope behaves in relation to its parent in the same way as a normal scope behaves in relation to the whole map script.
Since child scopes are always public members, you can access a child scope' public members froum outside the parent scope, but it needs a prefix for the parent and a prefix for the child.
An example :
library nestedtest
scope A
globals
private integer N=4
endglobals
public function display takes nothing returns nothing
call BJDebugMsg(I2S(N))
endfunction
endscope
scope B
globals
public integer N=5
endglobals
public function display takes nothing returns nothing
call BJDebugMsg(I2S(N))
endfunction
endscope
function nestedDoTest takes nothing returns nothing
call B_display()
call A_display()
endfunction
endlibrary
public function outside takes nothing returns nothing
set nestedtest_B_N= -4
call nestedDoTest()
call nestedtest_A_display()
endfunction
The next example will cause a syntax error:
library nestedtest
globals
private integer N=3
endglobals
scope A
globals
private integer N=4 //Error: 'N' redeclared
endglobals
endscope
endlibrary
It is actually caused by a limitation in the parser, there is a conflict caused by using N for the parent and then declaring it for the child. However this version does not cause syntax errors:
library nestedtest
scope A
globals
private integer N=4
endglobals
endscope
globals
private integer N=3
endglobals
endlibrary
It does kind of the same thing, but since the child's N was declared before the parent's the parser no longer gets in a confusion.
Another thing to keep in mind is that unlike normal global variables, private/public global variables cannot be used before declared, otherwise JassHelper will think they are just normal variables.
Scopes cannot be redeclared, there cannot be 2 scopes with the same name. But 2 child scopes might have the same name if they are children of different scopes, the reason is that they actually don't have the same name, they have different names given by their public child scope situation.
library nestedtest scope A function kkk takes nothing returns nothing set N=N+5 // By the time JassHelper gets to this line, it did not see the private integer N // declaration yet, so it assumes N is an attempt to use a global variable and does not // do any replacement endfunction endscope endlibrary scope X scope A //Declaring scope A again does not cause any problem, it is because the scope is actually X_A , the // previous declaration of A was actually nestedtest_A function DoSomething takes nothing returns nothing endfunction endscope endscope
There is no nesting limit, but notice that resulting variable and function names of private/public members get big and bigger depending of the depth of the scope nesting. Bigger variable names may affect performance. Not a lot but they do, and this efficiency issue is to be prevented by an obfuscator/finalizer that renames every identifier in the map script a.k.a the map optimizer's shortest names possible method.
Whenever you are inside an scope/library declaration, SCOPE_PREFIX and SCOPE_PRIVATE are enabled string constants that you could use.
SCOPE_PREFIX will return the name (as a Jass string) of the current scope concatenated with an underscode. (The prefix added for public memebers)
SCOPE_PRIVATE will return the name (as a Jass string) of the current prefix for private members.
scope test private function kol takes nothing returns nothing call BJDebugMsg("...") endfunction function lala takes nothing returns nothing call ExecuteFunc(SCOPE_PRIVATE+"kol") endfunction endscope
In the example, we are allowing lala() to call the private function kol via ExecuteFunc.
The keyword statement allows you to declare a replacement directive for an scope without declaring an actual function/variable/etc. It is useful for many reasons, the most important of the reasons being that you cannot use a private/public member in an scope before it is declared, in most cases this limitation is no more than an annoyance requiring you to change the position of declarations, in other situations though this is a limitator of other features.
For example two mutually recursive functions may use .evaluate (keep reading this readme) to call each other, but if you also want the functions to be private it is impossible to do it without using keyword:
scope myScope private keyword B //we were able to declare B as private without having to actually //include the statement of the function which would cause conflicts. private function A takes integer i returns nothing if(i!=0) then return B.evaluate(i-1)*2 //we can safely use B since it was already //declared as a private member of the scope endif return 0 endfunction private function B takes integer i returns nothing if(i!=0) then return A(i-1)*3 endif return 0 endfunction endscope
Let's accept it, sometimes we want very complex things added to Jass but other times, the only thing we actually need is an automatic text copy+paste+replace, textmacros were added because they can be really useful in a lot of very different cases
The syntax is simply, //! textmacro NAME [takes argument1, argument2, ..., argument n] then //! endtextmacro to finish. And just a runtextmacro to run. It is easier to understand after an example:
//! textmacro Increase takes TYPEWORD
function IncreaseStored$TYPEWORD$ takes gamecache g, string m, string l returns nothing
call Store$TYPEWORD$(g,m,l,GetStored$TYPEWORD$(g,m,l)+1)
endfunction
//! endtextmacro
//! runtextmacro Increase("Integer")
//! runtextmacro Increase("Real")
The result of the example is:
function IncreaseStoredInteger takes gamecache g, string m, string l returns nothing call StoreInteger(g,m,l,GetStoredInteger(g,m,l)+1) endfunction function IncreaseStoredReal takes gamecache g, string m, string l returns nothing call StoreReal(g,m,l,GetStoredReal(g,m,l)+1) endfunction
The $$ delimiters are required because the replace tokens could require to be together to other symbols.
Notice that strings and comments are not protected from the text replacement. So if there is a match for any of the arguments delimited by $$ it will always get replaced.
textmacros don't need arguments, in that case you simply remove the takes keyword.
//! textmacro bye
call BJDebugMsg("1")
call BJDebugMsg("2")
call BJDebugMsg("3")
//! endtextmacro
function test takes nothing returns nothing
//! runtextmacro bye()
//! runtextmacro bye()
endfunction
Textmacros add just a lot of fake dynamism to the language, the next is the typical attach/handle vars call for handles:
//! textmacro GetSetHandle takes TYPE, TYPENAME
function GetHandle$TYPENAME$ takes handle h, string k returns $TYPE$
return GetStoredInteger(udg_handlevars, I2S(H2I(h)), k)
return null
endfunction
function SetHandle$TYPENAME$ takes handle h, string k, $TYPE$ v returns nothing
call StoredInteger(udg_handlevars,I2S(H2I(h)),k, H2I(v))
endfunction
//! endtextmacro
//! runtextmacro GetSetHandle("unit","Unit")
//! runtextmacro GetSetHandle("location","Loc")
//! runtextmacro GetSetHandle("item","Item")
The development time has beed reduced significantly
Textmacros and scopes/libraries can become best friends by allowing some kind of static object orientedness:
//! textmacro STACK takes NAME, TYPE, TYPE2STRING
scope $NAME$
globals
private $TYPE$ array V
private integer N=0
endglobals
public function push takes $TYPE$ val returns nothing
set V[N]=val
set N=N+1
endfunction
public function pop takes nothing returns $TYPE$
set N=N-1
return V[N]
endfunction
public function print takes nothing returns nothing
local integer a=N-1
call BJDebugMsg("Contents of $TYPE$ stack $NAME$:")
loop
exitwhen a<0
call BJDebugMsg(" "+$TYPE2STRING$(V[a]))
set a=a-1
endloop
endfunction
endscope
//! endtextmacro
//! runtextmacro STACK("StackA","integer","I2S")
//! runtextmacro STACK("StackB","integer","I2S")
//! runtextmacro STACK("StackC","string","")
function Test takes nothing returns nothing
call StackA_push(4)
call StackA_push(5)
call StackB_push(StackA_pop())
call StackA_push(7)
call StackA_print()
call StackB_print()
call StackC_push("A")
call StackC_push("B")
call StackC_push("C")
call StackC_print()
endfunction
Hint: You can use textmacro_once in a similar way to library_once.
Structs introduce Jass to the object oriented programming paradigm.
I am unable to explain them without making an example first:
struct pair integer x integer y endstruct function testpairs takes nothing returns nothing local pair A=pair.create() set A.x=5 set A.x=8 call BJDebugMsg(I2S(A.x)+" : "+I2S(A.y)) call pair.destroy(A) endfunction
As you can see, you can store multiple values in a single struct, then you can just use the struct as if it was another Jass type, notice the . syntax which is used for members on most common languages.
Before using an struct you need to declare it, duh. The syntax is simply using the struct <name> and endstruct keyword, notice how similar they are to global blocks
To declare a member you simply use <type> <name> [= initial value]
In the above example we are declaring an struct type named pair, which has 2 members: x and y, they do not have an initial value set.
It is usually a good idea to assign initial values to the members, so that you don't have to manually initialize them after creating an object of the struct type, the usual default values are the null ones, but depending on the problem you want to solve you could need any other value.
Structs are pseudo-dynamic, you would often need to create and destroy structs, and you should create an struct an assign it to a variable before using it.
The syntax to create an struct (It is actually to get a unique id) is : structtypename.create()
In the case of the above struct you would have to use pair.create() to get a new struct.
JassHelper is just a preprocessor not a hack so whatever adition to Jass we add is still limited by Jass' own limitations, in this case, structs use arrays which have a 8191 values limit, and we cannot use index 0 which is null for structs, so there is an 8190 instances limit. This limit is for instances of each type, so you may have 8190 objects of a pair struct type and still are able to have many other instances of other types.
It means that if you keep creating many structs of a type without destroying them you would eventually reach the limit. So keep in mind this: IN the case the instances limit of a type is reached structtype.create() WILL RETURN 0.
The limit is not usually a worrying issue, 8190 is in practice a huge number, unless you want to make linked lists or things like that, but those should be solved by a lower level approach.
For example, if you are only using structs for spell instance data, it is even impossible to get more than 9 instances. And many other practical applications would never need more than 2000 instances.
UNLESS, of course some structs that are not used anymore are not getting destroyed. In that case the limit is reached by a bug in the usage of structs that should be fixed (In the case of structs, unlike handles, not destroying them does not increase the memory usage, but the risk of reaching the limit)
If you make calculations, if you create an struct per second and always forget to remove it, the map would need 2 hour, 16 minutes in order to reach the limit.
Either way if you are making this for usage by other people and are unsure about the possibility of reaching the limit you can always use a comparission with 0 after calling create() and block the process somehow in case the error is found.
In case the limit is reached and you don't have a way to catch it, struct 0 would be used and assigned and probably cause some conflicts later, the strenght of the conflicts could be null or huge depending on the way you are using the structs.
If debug mode is on when compiling the script, create() will show a warning message whenever the limit is reached.
To destroy an struct you simply use the destroy method which can work as an instance method or as a class method, in the above example call pair.destroy(a) is used to destroy the instance, but you could also use call a.destroy().
In the case you attempt to destroy the zero struct destroy will do nothing or would show a warning message if debug mode is on.
Just declare struct values the way you declare variables/functions/arguments of normal types.
Once an struct is declared and you create it and you want to access it, you have to access its members, the way is often (struct value).(member name) , in the case of pairs you use pair.x to access the x member and pair.y to access the y member.
Once a member is accessed the usage is quite similar to the usage of a variable. You can use it in set statements or as a value inside expressions.
struct pair integer x=1 integer y=2 endstruct function pair_sum takes pair A, pair B returns pair local pair C=pair.create() set C.x=A.x+B.x set C.y=A.y+B.y return C endfunction function testpairs takes nothing returns nothing local pair A=pair.create() local pair B=pair_sum(A, A) local pair C=pair_sum(A,B) call BJDebugMsg(I2S(C.x)+" : "+I2S(C.y)) //Dont forget, if you are not using an struct instance anymore, you destroy it call B.destroy() call C.destroy() call pair.destroy(A) endfunction
It would display "3 : 6"
So, you can declare struct members of any type, even struct types. You cannot however declare array members, this limitation should be removed in later versions.
struct pairpair pair x=0 //you cannot use pair.create() and should actually only use constants for default initial values. pair y=0 endstruct function testpairs takes nothing returns nothing local pairpair A=pairpair.create() local pair x set A.x=pair.create() set A.y=pair.create() set x=A.y //notice we are saving A.y in a backup variable so we can destroy it. set A.y= pair_sum(A.x,A.y) //this replaces A.y that's the reason we saved it call BJDebugMsg(I2S( A.y.x )+" : "+I2S( A.y.y )) //notice the nesting of the . operator call A.x.destroy() call A.y.destroy() call A.destroy() call x.destroy() endfunction
You can have globals of struct types. Because of Jass' limitations you cannot initialize them directly.
globals
pair globalpair=0 //legal
pair globalpair2= pair.create() //not legal
endglobals
You would have to assign them in an init function instead.
An static member would just behave like a global variable inside struct syntax, just add the static keyword before the member syntax. There can be static arrays as well.
They might be useful in conjunction to methods.
You can declare scope's public or private structs and struct variables with all freedom
scope cool public struct a integer x endstruct globals a x public a b endglobals public function test takes nothing returns nothing set b = a.create() set b.x = 3 call b.destroy() endfunction endscope function test takes nothing returns nothing local cool_a x=cool_a.create() set a.x=6 call a.destroy() endfunction
Methods are just like functions, the difference is that they are associated with the class, also [normal] methods are associated with an instance (in this case 'this')
Once again an example is needed
struct point
real x=0.0
real y=0.0
method move takes real tx, real ty returns nothing
set this.x=tx
set this.y=ty
endmethod
endstruct
function testpoint takes nothing returns nothing
local point p=point.create()
call p.move(56,89)
call BJDebugMsg(R2S(p.x))
endfunction
this : A keyword that denotes a pointer to current instance, [normal] methods are instance methods which means that they are called from an already assigned variable/value of that type, and this will point to it. In the above example we use this inside the method to assign x and y, when calling p.move() it ends up modiffying x and y attributes for the struct pointed by p.
method syntax : You might notice that method syntax is really similar to function syntax.
this is optional : You can use a single . instead of this. when you are inside an instance method. (For example set .member = value)
Methods are different to normal functions in that they can be called from any place (except global declarations) and that you are not necessarily able to use waits, sync natives or GetTriggeringTrigger() inside them (but you may use any other event response), you might be able to use them but it depends on various factors, it is not recommended to use them at all. In next versions the compiler might even raise a syntax error when it finds them.
Encapsullation is an object oriented programming concept in which you only give access to things that need to have access, in other words it is private and public for structs
struct encap
real a=0.0
private real b=0.0
public real c=4.5
method randomize takes nothing returns nothing
// All legal:
set this.a= GetRandomReal(0,45.0)
set this.b= GetRandomReal(0,45.0)
set this.c= GetRandomReal(0,45.0)
endmethod
endstruct
function test takes nothing returns nothing
local encap e=encap.create()
call BJDebugMsg(R2S(e.a)) //legal
call BJDebugMsg(R2S(e.c)) //legal
call BJDebugMsg(R2S(e.b)) //syntax error
endfunction
private members can only be used inside the struct declaration. Public and private are options for both variables and methods.
All struct members are public by default, so the public keyword is not necessary, on the other hand you must specify private. Something to point out is the existance of the readonly keyword, it allows code outside the struct to read the variable but not to assign it. It is a nonstandard at the moment, so you shouldn't use it on public releases.
Static methods or class methods do not use an instance, they are actually like functions but since they are declared inside the struct they can use private members.
struct encap
real a=0.0
private real b=0.0
public real c=4.5
private method dosomething takes nothing returns nothing
if (this.a==5) then
set this.a=56
endif
endmethod
static method altcreate takes real a, real b, real c returns encap
local encap r=encap.create()
set r.a=a
set r.b=b
set r.c=c
call r.dosomething() //even though it is private you can use
//it since we are inside the struct declaration
return r
endmethod
method randomize takes nothing returns nothing
// All legal:
set this.a= GetRandomReal(0,45.0)
set this.b= GetRandomReal(0,45.0)
set this.c= GetRandomReal(0,45.0)
endmethod
endstruct
function test takes nothing returns nothing
local encap e=encap.altcreate(5,12.4,78.0)
call BJDebugMsg(R2S(e.a)+" , "+R2S(e.c))
endfunction
You might notice that the usual create() syntax works like an static method, and destroy() can work as static or instance method
You can override the static method create by declaring your own one, once you do it, you might require another method just to allocate a unique id for the struct, this is the allocate() static method which is added by default to all structs, it is a private method. When a struct does not have an specific create method declared, jasshelper will use allocate directly when .create is called.
struct vec real x real y real z // static method create must return a value of the struct's type // create may have arguments. static method create takes real ax, real ay, real az returns vec local vec r= vec.allocate() //allocate() is private and //it gets a unique id for the struct set r.x=ax set r.y=ay set r.z=az return r endmethod endstruct function test takes nothing returns nothing local vec v= vec.create(1.0 , 0.0 , -1.0 ) call BJDebugMsg( R2S(v.z) ) call v.destroy() endfunction
Static methods that take nothing can also be used as code values
struct something
static method bb takes nothing returns nothing
call BJDebugMsg("!!")
endmethod
endstruct
function atest takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerAddAction(t, function something.bb)
call TriggerExecute(t)
endfunction
There is no actual syntax for destructors, but there is a rule and it is that if the struct has a method called onDestroy, it is always automatically called when .destroy() is issued on an instance.
It is useful to have onDestroy when an instance of the type may hold things that have to be correctly cleaned, it saves time and even makes things safer.
struct sta
real a
real b
endstruct
struct stb
sta H=0
sta K=0
method onDestroy takes nothing returns nothing
if (H!=0) then
call sta.destroy(H)
endif
if (K!=0) then
call sta.destroy(K)
endif
endmethod
endstruct
In the above example, it is only needed to destroy the object of type stb and it would automatically destroy the attached objects of type sta if present.
Polymorphism is an OOP concept in which different object classes may have the same action, although the action is different, the action gets the same name. For example both an ant and a person run, but they are pretty different objects and the run action is implemented in different ways.
An interface is like a set of rules struct types follow and allow you to call actions of an struct without really knowing the exact type of the struct.
interface printable
method toString takes nothing returns string
endinterface
struct singleint extends printable
integer v
method toString takes nothing returns string
return I2S(this.v)
endmethod
endstruct
struct intpair extends printable
integer a
integer b
method toString takes nothing returns string
return "("+I2S(this.a)+","+I2S(this.b)+")"
endmethod
endstruct
function printmany takes printable a, printable b, printable c returns nothing
call BJDebugMsg( a.toString()+" - "+b.toString()+" - "+c.toString())
endfunction
function test takes nothing returns nothing
local singleint x=singleint.create()
local singleint y=singleint.create()
local intpair z=intpair.create()
set x.v=56
set y.v=12
set z.a=45
set z.b=12
call printmany(x,y,z)
endfunction
The printmany function takes three arguments of type printable, it does not know exactly which types the objects are, only that they are of struct types that extend printable
The rule for an struct to follow the printable interface is that it has a toString() method, the printmany function can use that method even though it does not know the exact types of the arguments
The toString() method is different for singleint and intpair, when printmany is called it calls the correct version of toString() for each argument, the result for the above sample is : "56 - 12 - (45,12)".
Interfaces can have any number of methods and structs that extend them should implement all those methods, but can ther methods implemented as well.
It is illegal to declare onDestroy for an interface declaration, you can consider it to be automatically declared, you can use .destroy() on a variable of interface type and it will call the appropiate onDestroy method when necessary.
Interfaces can also implement variables, in this case interfaces allow some pseudo inheritance
interface withpos
real x
real y
endinterface
struct rectangle extends withpos
real a
real b
static method from takes real x, real y, real a, real b returns rectangle
local rectangle r=rectangle.create()
set r.x=x
set r.y=y
set r.a=a
set r.b=b
return r
endmethod
endstruct
struct circle extends withpos
real radius=67.0
static method from takes real x, real y, real rad returns circle
local circle r=circle.create()
set r.x=x
set r.y=y
set r.radius=rad
return r
endmethod
endstruct
function distance takes withpos A, withpos B returns real
local real dy=A.y-B.y
local real dx=A.x-B.x
return SquareRoot( dy*dy+dx*dx)
endfunction
function test takes nothing returns nothing
local circle c= circle.from(12.0, 45.0 , 13.0)
local rectangle r = rectangle.from ( 12.3 , 67.8, 12.0 , 10.0)
call BJDebugMsg(R2S(distance(c,r)))
endfunction
It is possible to acquire the type id of an instance of an struct that extends an interface, this type id is an integer number that is unique per struct type that extends that interface.
interface A integer x endinterface struct B extends A integer y endstruct struct C extends A integer y integer z endstruct function test takes A inst returns nothing if (inst.getType()==C.typeid) then // We know for sure inst is actually an instance of type C set C(inst).z=5 //notice the typecast operator endif if (inst.getType()==B.typeid) then call BJDebugMsg("It was of type B with value "+I2S( B(inst).y ) ) endif endfunction
In short, .getType() is a method that you use on instances of an object whose type extends an interface. And .typeid is an static constant set for struct types that extend an interface.
So, in the example we get to recognize that the given inst argument is of type C, then we can do the typecast and assignment.
There is another feature that uses typeids got another feature, and it is that interfaces got a constructor method that would create a new object given a correct typeid.
For example:
interface myinterface method msg takes nothing returns string endinterface struct mystructA extends myinterface method msg takes nothing returns string return "oranges" endmethod endstruct struct mystructB extends myinterface string x static method create takes nothing returns mystructB local mystructB m=mystructB.allocate() set m.x="apples" return m endmethod method msg takes nothing returns string return this.x endmethod endstruct struct mystructC extends myinterface string x //myinterface.create(...) can only use the default allocator or custom create //methods that take nothing. // //this declaration is not going to be taken into account if mystructC //is used in myinterface.create(...) // static method create takes string astring returns mystructC local mystructB m=mystructB.allocate() set m.x=astring return m endmethod method msg takes nothing returns string return this.x endmethod endstruct function test takes nothing returns nothing local integer T = mystructB.typeid local myinterface A set A=myinterface.create(mystructA.typeid) //this is not that useful since mystructA.create() does the same call BJDebugMsg(A.msg()) set A=myinterface.create(T) //this is more useful, we can create objects of variable types... call BJDebugMsg(B.msg()) set A=myinterface.create(122345) //using invalid values or 0 will make .create return 0 (no object) set A=myinterface.create(mystructC.typeid) //note that this will not use mystructC.create, just //mystructC.allocate, possibly causing errors, if this //happens you can an error message in-game if you compile under debug mode call BJDebugMsg(C.msg()) endfunction
If you plan using this feature, always be careful to handle 0 return values and also specify that it is better to use constructors without arguments on the interface's children.
It is also possible to declare the interface in a way that all the childs use a custom create method that returns nothing.
interface myinterface static method create takes nothing method qr takes unit u returns nothing endinterface struct st1 extends myinterface static method create takes nothing returns st1 //legal return st1.allocate() endmethod method qr takes unit u returns nothing call KillUnit(u) endmethod endstruct struct st2 extends myinterface integer k static method create takes integer f, integer k returns st2 //not legal local st2 s=st2.allocate() set st2.k=f+k*f return st2 endmethod method qr takes unit u returns nothing call ExplodeUnitBJ(u) endmethod endstruct
Interface methods allow you to use the defaults keyword. The defaults keyword allows methods to be optional when implementing the child struct. So if the method is not present in the child struct it will not show syntax errors requesting the method to be implemented. Instead we will implement a default empty method.
defaults is followed by "nothing" or by a value depending on the return type of the method (if the method returns nothing then it should default nothing, else it should default a value). defaults only supports constant values.
interface whattodo method onStrike takes real x, real y returns boolean defaults false method onBegin takes real x, real y returns nothing defaults nothing method onFinish takes nothing returns nothing endinterface struct A method onFinish takes nothing returns nothing //must be implemented //.. code endmethod // We are allowed to add onBegin, but not forced to method onBegin takes real x, real y returns nothing //.. code endmethod // when somebody calls .onStrike on a whattodo of type A, it will return false endstruct struct B method onFinish takes nothing returns nothing //must be implemented //.. code endmethod // when somebody calls .onBegin on a whattodo of type A, it will do nothing endstruct
Overloading operators is useful sometimes. vJass currently allows overloading operators for <, > , array set and array get.
An overloaded operator is a method, but it gets operator as name, after the operator keyword you specify the operator being overloaded.
An example is worth 1000 words:
struct operatortest
string str=""
method operator [] takes integer i returns string
return SubString(.str,i,i+1)
endmethod
method operator[]= takes integer i, string ch returns nothing
set .str=SubString(.str,0,i)+ch+SubString(.str,i+1,StringLength(.str)-i)
endmethod
endstruct
function test takes nothing returns nothing
local operatortest x=operatortest.create()
set x.str="Test"
call BJDebugMsg( x[1])
call BJDebugMsg( x[0]+x[3])
set x[1] = "."
call BJDebugMsg( x.str)
endfunction
By this example we are overloading the [] operator and giving it a new function for the objects of type operatortest. The operator [] specifies the replacement for array get and []= is the replacement for array get.
After inspecting the code you may notice that we are making the string function as an array of strings (or actually characters)
The [] operator requires 1 argument (index), the []= operator requires 2 arguments (index and value to assign). [] must return a value.
There is a lot of criticism towards operator overloading since it allows programmers to make code that does not make sense, please use this feature with responsibility.
You can also overload < and > , notice that there is only syntax to declare < by declaring it you are forcefully determining an order relation for structs of that type. So it automatically makes > based on your < declaration.
struct operatortest
string str=""
method operator [] takes integer i returns string
return SubString(.str,i,i+1)
endmethod
method operator[]= takes integer i, string ch returns nothing
set .str=SubString(.str,0,i)+ch+SubString(.str,i+1,StringLength(.str)-i)
endmethod
method operator< takes operatortest b returns boolean
return StringLength(this.str) < StringLength(b.str)
endmethod
endstruct
function test takes nothing returns nothing
local operatortest x=operatortest.create()
local operatortest y=operatortest.create()
set x.str="Test..."
set y.str=".Test"
if (x<y) then
call BJDebugMsg("Less than")
endif
if (x>y) then
call BJDebugMsg("Greater than")
endif
endfunction
In the example, an object of type operatortest is considered greater than another object of that type if the length of its str member is greater than the length of the other object's str member.
operator< must return a boolean value and take an argument of the same type as the struct.
Operators are interface friendly meaning that an interface may declare operators, there is a catch and it is that the operator< must be declared without signature in an interface. Also when using > or < to compare interface objects both instances must have the same type, otherwise the function would halt before performing the comparisson, if debug mode was enabled when compiling, it will also show a warning message.
interface ordered
method operator <
endinterface
interface indexed
method operator [] takes integer index returns ordered
method operator []= takes integer index, ordered v returns nothing
endinterface
function sort takes indexed a, integer from, integer to returns nothing
local integer i
local integer j
local ordered aux
set i=from
loop
exitwhen (i>=to)
set j=i+1
loop
exitwhen (j>to)
if (a[j]<a[i]) then
set aux = a[i]
set a[i] = a[j]
set a[j] = aux
endif
set j=j+1
endloop
set i=i+1
endloop
endfunction
This is an interface for a sorting algorithm. We may now declare custom types that work to sort stuff:
struct integerpair extends ordered
integer x
integer y
method operator< takes integerpair b returns boolean
if (b.x==this.x) then
return (this.y<b.y)
endif
return (this.x<b.x)
endmethod
endstruct
type ipairarray extends integerpair array [400]
struct integerpairarray extends indexed
ipairarray data
method operator[] takes integer index returns ordered
return ordered( this.data[index] )
endmethod
method operator[]= takes integer index, ordered value returns nothing
set this.data[index] = integerpair( value)
endmethod
endstruct
Of course, it is just an example, the logical way would be using quicksort, operators are also good since they would also allow textmacros to use them, the same sorting textmacro might then be compatible with integer, real and any struct with overloaded < operator.
It is possible to base an struct from a previously declared struct, by doing this your new type is going to acquire methods and variable members of the base struct, it is also able to use instances of this type with functions and variables of the base type.
Consider doing this as a way to add code and properties to a previous type.
struct A integer x integer y method setxy takes integer cx, integer cy returns nothing set this.x=cx set this.y=cy endmethod endstruct struct B extends A integer z method setxyz takes integer cx, integer cy, integer cz returns nothing call this.setxy(cx,cy) //we can use A's members set this.z=cz endmethod endstruct
Internally, B.allocate() is actually calling A's constructor and B.destroy will also call B's deconstructor. If a base struct got a custom create method, the structs extending it will have to use it for allocate(). If the custom create method requires arguments, the allocate method for child structs will require the same arguments:
struct A integer x static method create takes integer k returns A local A s=A.allocate() set A.x=k return s endmethod endstruct struct B extends A static method create takes nothing returns B return s= B.allocate(445) //notice that B.allocate requires the same arguments as A.create() endmethod endstruct struct C extends B //yep, it is possible to extend an struct that is extending another one static method create takes nothing returns B local C s=C.allocate() //C is a child of B that got a custom create method that takes nothing, so allocate takes nothing as well. set s.x=s.x*s.x //once again reusing the parents' members. return s endmethod endstruct
If an struct has declared create to be private, it is impossible to extend it. Structs cannot use private members from parent structs.
The behaviour of the onDestroy method is special in this case, if in the last example, A,B and C had an onDestroy method each, destroying an instance of a C would call C.onDestroy(), B.onDestroy() and A.onDestroy() (in that order)
It is possible to extend an struct that extends an interface, in this case, the child that extends the interface directly is forced to implement the interface's methods, but its childs are not. But it is possible for them to replace them again.
interface myinterface method processunit takes unit u returns nothing method onAnEvent takes nothing returns boolean endinterface struct A extends myinterface method processunit takes unit u returns nothing call KillUnit(u) endmethod method onAnEvent takes nothing returns boolean return false endmethod endstruct struct B extends A method processunit takes unit u returns nothing //we have just replaced A's processunit method, //if an interface variable of type myinterface holds an instance of //type B it will explode the unit. call ExplodeUnitBJ(u) endmethod // we are implementing processunit but we do not have to implement onAnEvent endstruct
If you plan using interface.create() you will have to be careful once again about constructors with arguments, if an interface is declared with the condition that create takes nothing every child (,grandchild, etc) of the interface will be affected by this condition.
Dynamic arrays are arrays you can instanciate dynamically, EACH custom array type has got a limit of 8190 TOTAL indexes, that means that an array type of size 100 has got an 81 instances limit.
They are kind of easy to declare and use, and somehow share syntax with structs.
You simply make a line outside any function/struct declaration: type <nameoftype> extends <nameofbasetype> array [ <size>] Where size is an integer value or constant global.
Then you can simply create/usem them in a similar way to structs and use the [] operator to access its indexes, dynamic arrays have also got an static size constant
type arsample extends integer array[8] function test takes nothing returns arsample local arsample r=arsample.create() local integer i=0 loop exitwhen i==arsample.size //holds size of the array type set r[i]=i set i=i+1 endloop return r endfunction function test2 takes nothing returns arsample local arsample r=test() local integer i=0 loop exitwhen i==arsample.size call BJDebugMsg(I2S(r[i])) set i=i+1 endloop return r endfunction
You can extend arrays of any type, even of custom types (structs, interfaces, other dynamic arrays) thus making a dynamic array of dynamic arrays, a matrix like syntax is possible:
type iar extends integer array[3] type iar_ar extends iar array[3] function test takes nothing returns arsample local iar_ar r=iar_ar.create() local integer i=0 local integer j loop exitwhen i==iar_ar.size //holds size of the array type set r[i]=iar.create() set j=0 loop exitwhen j==iar.size set r[i][j]=j*i set j=j+1 endloop set i=i+1 endloop return r endfunction
And structs may have these arrays as members thus allowing array members for instances (non-static)
type stackarray extends integer array [20] struct stack private stackarray V private integer N=0 method Create takes nothing returns stack local stack s=stack.create() set s.V=stackarray.create() if (s.V==0) then debug call BJDebugMsg("Warning: not enough space for stack array") return 0 endif endmethod method push takes integer i returns nothing if (this.N==stackarray.size) then debug call BJDebugMsg("Warning: stack is full") else set this.V[this.N]=i set .N = .N +1 //remember this syntax is valid as well endif endmethod method pop takes nothing returns nothing if (this.N>0) then set this.N=this.N-1 else debug call BJDebugMsg("Warning: attempt to pop an empty stack"); endif endmethod method top takes nothing returns integer return .V[.N-1] endmethod method empty takes nothing returns boolean return (.N==0) endmethod method onDestroy takes nothing returns nothing call this.V.destroy() endmethod endstruct
As you may notice, if there is no space for a new instance, the create method of arrays returns 0. It will also warn you automatically if compiled in debug mode
Dynamic arrays got the .size member that allows you to easily access the size you used to declare the array type.
Structs may have array members but you also require to declare the size of them.
struct stack private integer array V[100] private integer N=0 method push takes integer i returns nothing set .V[.N]=i set .N=.N+1 endmethod method pop takes nothing returns nothing set .N=.N-1 endmethod method top takes nothing returns integer return .V[.N-1] endmethod method empty takes nothing returns boolean return (.N==0) endmethod method full takes nothing returns boolean return (.N==.V.size) endmethod endstruct
In some way, this is syntax candy for declaring a new array type and making it a member of the struct, but this way is a little more optimizer and handles the array allocation/deallocation for you, with the exchange of some limitations.
Notice that this drastically reduces the instances limit of an struct type, for example, we can only have 80 instances (8190 div 100) of the above declared stack object.
An struct may have as many array members as you can type, notice that the array with the maximum size is the one considered when setting the instances limit, so if an struct has 2 array members, one of size 4 and one of size 100, the struct will have a limit of 80 instances.
The disadvantage of this method over declaring the dynamic array type separatedly is that you have less freedom in what concerns assigning to the member another array you create in other occation and things like that... The advantage is that it is faster and takes less code.
As dynamic arrays, array members may also use the .size field.
For vJass functions may behave as objects with 2 methods: evaluate and execute, both methods got the same arguments list as the function, and evaluate got its return value as well.
Using functions as objects has a couple of advantages, evaluate() allows you to call the function even from code that is above its function declaration, execute allows the same but it is also able to run the function in another thread.
The disadvantages are: Functions that are used with evaluate(), should not use GetTriggeringTrigger() (but you may use any other event response) or any sync native, evaluate() does not support waits, and evaluate() is slower than a normal function call.
.execute() is actually faster than good old ExecuteFunc, and in later versions it might actually get even faster. evaluate halves the duration of ExecuteFunc and it may get much better later.
For functions that have function arguments you would have to use global variables to pass arguments when using ExecuteFunc, .execute and .evaluate will pass the arguments directly.
function A takes real x returns real if(GetRandomInt(0,1)==0) then return B(x*0.02) endif return x endfunction function B takes real x returns real if(GetRandomInt(0,1)==1) then return A(x*1000.) endif return x endfunction
These are mutually recursive functions, and with normal Jass this would give a syntax error, in order to prevent this issue you may use evaluate:
function A takes real x returns real if(GetRandomInt(0,1)==0) then return B.evaluate(x*0.02) endif return x endfunction function B takes real x returns real if(GetRandomInt(0,1)==1) then return A(x*1000.) endif return x endfunction
Let us say you need to destroy an special effect after waiting x seconds using a wait. You could use a timer but for example sake we are going to use a normal wait, we do not want to stop execution of the function calling this effect destroying function so we need a new thread:
function DestroyEffectAfter takes effect fx, real t returns nothing call TriggerSleepAction(t) call DestroyEffect(fx) endfunction function test takes nothing returns nothing local unit u=GetTriggerUnit() local effect f=AddSpecialEffectTarget("Abilities\\Spells\\Undead\\Cripple\\CrippleTarget.mdl",u,"chest") call DestroyEffectAfter.execute(f,3.0) set u=null set f=null endfunction
Note: This feature is currently limited to functions declared in the map script, you cannot use it with common.j natives or blizzard.j functions yet.
If functions are objects then we may as well have interfaces for them.
The syntax for function interfaces is: function interface name takes (arguments) returns (return value)
It is actually similar to a function declaration.Variables/values of a function interface type may be called using execute() and evaluate() as defined above:
To assign to variables of a function interface type you first need to get a function's pointer. The syntax to get them is understandable if you assume that every declared function interface will get as static members the functions found in the map script that follow its argument/return value rules.
function interface Arealfunction takes real x returns real function double takes real x returns real return x*2.0 endfunction function triple takes real x returns real return x*2.0 endfunction function Test1 takes real x, Arealfunction F returns real return F.evaluate(F.evaluate(x)*F.evaluate(x)) endfunction function Test2 takes nothing returns nothing local Arealfunction fun = Arealfunction.double //syntax to get pointer to function call BJDebugMsg( R2S( Test1(1.2, fun) )) call BJDebugMsg( R2S( Test1(1.2, Arealfunction.triple ) )) //also possible... endfunction
In this example we are actually having functions as arguments and as a variable. You may also typecast(see bellow) a function pointer to integer and then back to the interface function it originated.
For the moment, assigning a value of an struct type to an integer variable / or variable of any other struct type will already change the type of that reference for the compiler
Notice that sometimes using a variable might get tedious or even create unneeded overhead, that is the reason the type cast operator was added.
Its syntax is mostly like a function but the name of the function is the name of a custom type. It will soon allow native types as well.
interface wack //... some declarations endinterface struct wek extends wack integer x // some more declarations endstruct function test takes wack W returns nothing //You are certain that W is of type wek, a way to cast the value is: local wek jo = W set jo.x= 5 //done // but sometimes creating a variable is too much work for the virtual machine and you // are only accessing it once set wek(W).x=5 //also works. endfunction type anarrayofdata extends integer array [6] function getdata5 takes unit u returns integer //a reference to an object of type anarrayofdata is saved as the unit's custom value return anarrayofdata(GetUnitUserData(u))[5] endfunction function setdata5 takes unit u, anarrayofdata x returns nothing // Here we are doing the opposite, notice that integer may be used as a type cast // operator for struct and dynamic array types. call SetUnitUserData(u, integer(x)) endfunction
Methods may work as objects to use the .execute() feature, they may also work as objects to allow access to the name field. This .name field will return the function name given to a method after the compiling.
This is specially useful in case you want to use an struct's static method on an ExecuteFunc based system.
struct mystruct static method mymethod takes nothing returns nothing call BJDebugMsg("this works") endmethod endstruct function myfunction takes nothing returns nothing call ExecuteFunc(mystruct.mymethod.name) //ExecuteFunc compatibility call OnAbilityCast('A000',mystruct.mymethod.name) //for example, caster system's OnAbilityCast, requires a function name endfunction
Certain advanced users might use the world editor yet prefer to have more control over the map script, namely making their own main or config functions, the inject preprocessors allows to replace such functions.
The syntax is: //! inject main/config (...) //! endinject
For example:
//! inject main
//some function calls may go here
// this places vjass initializations there, notice structs are first initialized then library initializers
// are called
//! dovjassinit
//other calls may go here
call InitCustomTriggers() //maybe you want to exploit that world editor function...
//! endinject
The dovjassinit preprocessor may prove very helpful, it is only necessary if there is no call to InitBlizzard in the custom main or if you need to control the position of such initializing of structs and libraries.
//! inject config works the same way only that there is no //! dovjassinit for that case.
JassHelper also includes a //! import preprocessor like WEHelper's, it allows you to import external files.
The usage is //! import "scriptfile" , you can use fixed paths or relative paths.
For example: //! import "c:\x.j" will include x.j file located on c:\ in your map script. It is effectively like pasting the file there before compiling it, so the file can include globals, libraries, textmacros, etc. It can even use //! import itself
If you use //! import twice or more on the same file name , the command is ignored.
Relative paths are allowed, for example: //! import "x.j" or //! import "subfolder\f.j" will use relative paths. When the path is relative JassHelper will look for the script file in the map's folder or in the lookup folders set in JassHelper's configuration.
Grimoire's version uses mapcompiler.conf to determine the lookup folders, WEHelper's version comes with a dialog that allows you to set them. Also the version for WEHelper will automatically add wehelper's import path to the lookup folders list.
WEHelper's internal import preprocessor is most likely to override JassHelper's import since the default is to call it before JassHelper, in case you want to use JassHelper's import you would have to disable WEHelper's, the only advantage the jasshelper one has over the wehelper one is the ability to configure lookup folders.
Grimoire's mapcompiler.conf already determines grimoire' Jass folder as a position of scripts. You can add whatever folders you find necessary. Notice that there is a priority system, if a file is found on the map's folder it will be imported no matter the same file exists in another folder used in mapcompiler.conf
There is a problem with using the map's folder and it is that testmap saves the map in another path. So you would preffer to use custom paths for this option.
Test map is often problematic with this kind of features. The best way to deal with this is to ALWAYS SAVE BEFORE USING TESTMAP, this will force the map to go to its path and then will use it for testmap, in case you need to test temporary changes, you can save it with another name in the same folder and then do testmap.
It is possible to load (convert) an slk into code to be added to the map's script, specifically struct assigments. This can be really useful when a system uses structs to store data, since SLK is a table format it can save some work and make things easier to edit without the manual struct assigning.
The preprocessor to load an slk file is //! loaddata "path.slk" . The file path argument follows exactly the same rules as the ones I already specified in import (including lookup folders)
Both the slk and the struct type to be loaded need to follow very specific rules.
The SLK file requires to have an struct name at (row 1, column 1) Then the first row contains field names, the next rows contain a [key] and values for the names specified up there.
| stname | this | is | just | an | example |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 | 10 | 11 | 12 |
In the example, the name of the struct type to be loaded is stname, the keys of the instances that will be loaded are 1 and 7, and the rest is information about fields and values.
The struct type to be used (in the example, stname) requires a getFromKey static method that returns a value of the struct type.
getFromKey() would simply convert a key value and return an equivalent struct, this is because you often want data structs to be related to something else. (Most of the times the [something else] will be an object id.)
For this example, getFromKey would have to take an integer value
The struct type also requires to have the fields declared in the SLK file, the SLK is not forced to list all the fields of the struct type.
So if we have this struct definition:
struct stname
integer this
integer is
integer just
integer an
integer example
static stname array values
static method getFromKey takes integer i returns nothing
if (stname.values[i]!=0) then
set values[i]=stname.create()
endif
return stname.values[i]
endmethod
endstruct
And we also load the SLK from the previous example, the loaded init code would be:
set s=stname.getFromKey(1)
set s.this=2
set s.is=3
set s.just=4
set s.an=5
set s.example=6
set s=stname.getFromKey(7)
set s.this=8
set s.is=9
set s.just=10
set s.an=11
set s.example=12
Notice that this code is then run after the struct and libraries initialization
For a more practical explanation check out the SLK demo included in the JassHelper distribution.
JassHelper allows the //! external preprocessor that let you call other tools on the map after the map is compiled with JassHelper, this way these tools may work the same with grimoire and WEHelper
The preprocessor is //! external EXTERNAL_TOOL_NAME [external arguments]
EXTERNAL_TOOL_NAME must match the name of the tool in the configuration (Either the dialog in the wehelper plugin or the .conf file for the grimoire version)
The text after the external name is optional and is given to the tool as command line arguments.
The next is kind of a section for programmers:
For a tool to work correctly with the external preprocessor it must have a very specific behaviour.
First of all JassHelper will pass these arguments to the tool
Then the tool must use the 0 code if it was succesful, otherwise return any number different to 0 and should write an error message to either stdin or stderr.
As a side effect of the parsing necessary for this project it also replaces line breaks inside Jass string literals into properly escaped \n this also fixes an issue with PJass giving the wrong line number if there are strings with linebreaks.
Jass New Gen Pack already comes with JassHelper, although it might not come with the most recent version, in order to update you can simply copy grimoire\jasshelper.exe to the NewGen Pack"s jasshelper subfolder. You may also update the JassHelper documentation if you wish
It is suggested that you install new gen pack first and then update jasshelper, it may be the case that newgen pack's jasshelper holds the same version of jasshelper then it would not be necessary to update.
In order to get Jass New Gen Pack visit: http://www.wc3campaigns.net/showthread.php?t=90999
JassHelper was mostly designed to be used with WEHelper, WEHelper is a World Editor hack with plugin compatibility and many other features, the main one being that it uses pJass to check for syntax errors instead of World editor's syntax checker which has a lot of issues.
There is no official link to get WEHelper yet, but currently the way is to go to WEHelper's thread This version of JassHelper plugin will only work with WEHelper 1.5.2 or greater, WEHelper versions greater than or equal to 1.7 are recommended. If a new WEHelper version doesn't work correctly with this plugin I will probably take less than a day to release a new JassHelper version.
Once WEHelper is installed you can run the WEHelper\JassHelperInstaller.exe program that will help you install/update the plugin to WEHelper.
It should be enabled automatically, if not , simply enable it and close and reopen WEHelper so it works now.
Since version 1.5.1 WEHelper includes its own preprocessor and also a preprocessor configuration section, for correct compatibility use WEHelper's preprocessors tab in configuration dialog and make sure JassHelper's preprocessor goes after the import preprocessor, the remaining internal preprocessors should execute after JassHelper. Most of the times, this will be the default positioning.
You need grimoire 1.2 or greater which includes wehack.dll. Then you have to copy grimoire\jasshelper.exe to a jasshelper subfolder in grimoire's folder. You also need pJass, locate pJass.exe on grimoire's folder as well. As of grimoire 1.2 you may have to update wehack.lua yourself or wait for a newer grimoire version that will be compatible with the new way jasshelper works.
You will have to copy SFMPQ.dll to the jasshelper folder
grimoire\jasshelper.exe may also work as an standalone compiler, you require SFMPQ.dll and pjass in the same folder, after that simply use the usual command line [jasshelper.exe common.j blizzard.j mappath.w3x] (replace arguments with the specifics) you may also add a --debug argument before common.j to enable the debug mode.
If you provide 4 file arguments to the program, it will consider the third one to be an external war3map.j and the 4th one is the map, this will make jasshelper use the external war3map.j as the script and ignore the one used by the map, this is very useful for people that do not use the trigger editor.
If you are gonna call jasshelper.exe from another tool, notice that jasshelper will create and use logs and backups subfolders inside its work folder. jasshelper.conf has priority in the work folder and if it is not found the one in jasshelper's folder is gonna be created and used.
Once enabled, JassHelper will parse and modiffy war3map.j everytime a map is saved, it is often a fast and unnoticeable process unless there are errors in global/library declarations or a cycle or missing requirement for libraries, in that case it will show an error message.
To toggle debug mode simply go to WEHelper's configuration dialog then to plugins tab, click JassHelper's and then click the configure button.
Finally, you can use the [lookup folders] and [external tools] buttons to configure those features.
Simply use we.bat to run grimoire, then you'd have to disable the syntax checker and enable the map compiler using the grimoire menus. To use debug mode simply use compiler\compiler debug mode.
Currently, compiler is not called when using testmap, so you must save the map before using the testmap button.
Once WEHelper is installed you can run the JASSHelperInstaller.exe program that will help you install/update the plugin to WEHelper.
For the other versions simply replace the files.