• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] An introduction to vJASS, structs and good JASS technique

Status
Not open for further replies.
Level 6
Joined
Jun 30, 2006
Messages
230
Introduction to vJASS, Structs and Good JASS Technique

Note: I will convert this to a tutorial for everyone, but I am teaching Flame and he needs this now.
An Introduction to vJASS, Structs, and Good Programming Practice
Introduction to Structs
Methods
Scopes

Structs
Structs are part of vJASS, an extension to JASS that can be obtained through JassHelper which is part of JassNewGen. If are somewhat familiar with Handle Vars, think of this as the place where data is stored, kind of like the gamecache in Handle Vars. It is sort of a mini-database. Structs are used as datatypes. Inside of them they can hold whatever data you tell it to. Behind the scenes, structs are just cleverly designed arrays, so they are much faster than a gamecache.
JASS:
struct MyStruct //declares a struct and gives it a name
    unit u    //this is how to declare datatypes inside of a struct
    timer t
endstruct //ends the struct

function Actions takes nothing returns nothing
    // how to use a struct:
    // local <structname> <variablename> = <structname>.create()
    local MyStruct data = MyStruct.create() 
    // to set a value inside of a struct, you use:
    // set <variable name for your struct>.<variable you want to set>
    set data.u = GetTriggerUnit() //set u in our struct to the triggering unit
    set data.t = CreateTimer() //and the timer to create a new timer
endfunction
The above trigger can be written like this, as well:
JASS:
struct MyStruct 
    unit u = GetTriggerUnit()
    timer t = CreateTimer()
endstruct 

function Actions takes nothing returns nothing
    local MyStruct data = MyStruct.create() 
endfunction

In this example, we store a unit and a timer. These two datatypes are used very often in spells, so that is why they were chosen for this example. You can store any dataype you would like inside of a struct. When structs are declared in the latter way, the struct assigns the values to unit u and timer t when a .create() is used. I prefer the use of the second method over the first, but sometimes it is not possible. Your variables can change, so you may need to set them like we did in the first example.

Structs help us greatly because instead of using gamecache to store each of the datatypes we need, we can store them in our struct. This lets us do an attachment once, just on our custom datatype, rather than each piece one at a time.

Methods
Structs can also hold functions in them. When a function is used inside of a struct, it is called a method. They are declared just like functions are. However, you call a method slightly different from a function. Remember, structs are datatypes, so we have a special syntax for calling methods. You have already seen it. When you define a struct, you used the .create() method. The .create() method does some things behind the scenes. It is part of every struct. You can give it more things to do, though, like in the following example:
JASS:
struct MyStruct
    unit u
    timer t
    
    //the create method must return a MyStruct and be static
    static method create takes nothing returns MyStruct
        local MyStruct data = MyStruct.allocate()//must define this line
        set data.u = GetTriggerUnit() //sets the variables when create is called
        set data.t = CreateTimer()
        return data 
    endmethod
endstruct

function Actions takes nothing returns nothing
    local MyStruct data = MyStruct.create()
endfunction
There are essentially three types of methods: private, public, and static. Private means that only methods within the struct can access and use the method. Public means that the method can be accessed from outside the struct. Private and public methods create an 'instance', thus you cannot use it in code callback (examples: ForGroup(),Condition(),TimerStart()[icode=jass]). If a method takes nothing and is declared as static, it does not create an 'instance' so you can use that method as code callback. The create method is required to be static. In the above code, while we can declare/use [icode=jass]unit u and timer t outside of our struct, integer i cannot.

You might have noticed there are no local variables (instances of the new datatype are the exception). You do not need local variables inside of methods, you declare them where unit u and timer t are declared. As you can see in the above code, you use the variable name with a period before it.

Another keyword in vJASS is "constant". When you define something as constant, you can no longer change its value:
JASS:
struct MyStruct
    unit u
    timer t
    constant integer AID_SPELL_NAME='A000'
    private integer i
    
    private method Constant takes nothing returns nothing
        set AID_SPELL_NAME='A001' //doesn't work, AID_SPELL_NAME is constant
    endmethod
    
    static method create takes nothing returns MyStruct
        local MyStruct data = MyStruct.allocate()
        set data.u = GetTriggerUnit()
        set data.t = CreateTimer()
        set data.i = 0
        call data.Constant() //works because we are in the same struct
        return data
    endmethod
endstruct

function Actions takes nothing returns nothing
    local MyStruct data = MyStruct.create()
    call data.Constant() //does not work, .Constant is private
endfunction

It is important to note that you can declare a method or function as constant if the method/function returns a value that does not change. This actually does nothing in and of itself. However, if you use a map optimizer, it will "inline" the method/function, meaning it literally replaces the method/function call with the value it returns, this improving speed.

Also, when you are done with a struct, you should destroy it. This is done by calling the .destroy() method. You cannot modify the .destroy() method. However, when the .destroy() method is called, a method named onDestroy will be called as well if you define one. This allows you to do cleanup of our own when we call the .destroy method. This is very usefull for pausing and destroying timers, as well as deleting groups and many other things. The following code is a very simpilistic knockback struct:
JASS:
scope Knockback

struct knockback
    unit u
    real power
    real angle
    real x
    real y
    timer t
    
    static method create takes unit u, real power, real angle returns knockback
        local knockback data = knockback.allocate()
        set data.t = CreateTimer()
        set data.u = u
        set data.x = GetUnitX(u)
        set data.y = GetUnitY(u)
        set data.power = power
        set data.angle = angle
        return data
    endmethod
    private method onDestroy takes nothing returns nothing
        //ClearTimerStructA is part of the ABC system
        //don't worry about it for now
        call ClearTimerStructA(.t) 
        call PauseTimer(.t)
        call DestroyTimer(.t)
    endmethod
    static method Knockback takes nothing returns nothing
        //GetTimerStructA is part of the ABC system
        //don't worry about it for now
        local knockback data = GetTimerStructA(GetExpiredTimer())
        set data.power = data.power * 0.96
        set data.x = data.x + data.power * Cos(data.angle * bj_DEGTORAD)
        set data.y = data.y + data.power * Sin(data.angle * bj_DEGTORAD)
        call SetUnitPosition(data.u, data.x, data.y)
        if data.power < 4 then
            call data.destroy()
        endif
    endmethod
endstruct

Basically, when the static method Knockback is called with a timer, it will automate the rest of the knockback and do cleanup by calling .destroy() when you are done. Notice that onDestroy is private, because we do not want people calling it outside of the struct as it will prematurely destroy our timer. That would make the unit stop being "knockbacked", and we do not want this to happen.

Scopes / Good Programming Practice
Each struct should generally have two entry points into the struct. The first one is obviously the create method. The second is the method that automates the rest of the sequence. For example, the afore mentioned knockback struct has only 1 entry point besides create. If you design your structs to automate the entire purpose of the trigger, then you can keep what the trigger does separate from Conditions and the InitTrig. It also makes it easier for you to transfer your spell from map-to-map. Doing this will also help eliminate errors (does for me, at least).

There are some more practices I think every programmer who uses NewGen should know and use. Part of vJASS has things called scopes. Scopes are very useful when you are programming because they easily let you do a few good programming techniques automatically, as you will see. I think the best way to teach scopes is to show you how it used first. The following is a an ability that is basically Blur from DotA:
JASS:
globals
    private constant integer AID_BLUR='A000'
    private integer array alpha
endglobals

struct StructUnit
    unit u=GetTriggerUnit()
    timer t=CreateTimer()
endstruct

function Blur_Refresh takes nothing returns nothing
    local StructUnit s=GetTimerStructA(GetExpiredTimer())
    call SetUnitVertexColor(s.u,255,255,255,alpha[GetUnitAbilityLevel(s.u,AID_BLUR)])
endfunction

function Blur_Conditions takes nothing returns boolean
    return GetLearnedSkill()==AID_BLUR and IsUnitIllusion(GetTriggerUnit())==false
endfunction

function Blur_Actions takes nothing returns nothing
    local StructUnit s=StructUnit.create()
    call SetUnitVertexColor(s.u,255,255,255,alpha[GetUnitAbilityLevel(s.u,AID_BLUR)])
    call SetTimerStructA(s.t,s)
    call TimerStart(s.t,2,true,function Blur_Refresh)
endfunction

function InitTrig_Blur takes nothing returns nothing
    set gg_trg_Blur=CreateTrigger()
    set alpha[1] = 90
    set alpha[2] = 65
    set alpha[3] = 40
    set alpha[4] = 20
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Blur,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(gg_trg_Blur,Condition(function Blur_Conditions))
    call TriggerAddAction(gg_trg_Blur,function Blur_Actions)
endfunction

endscope

And then using scopes:
JASS:
scope Blur

globals
    private constant integer AID_BLUR='A000'
    private integer array alpha
endglobals

struct StructUnit
    unit u
    timer t=CreateTimer()
endstruct

private function Refresh takes nothing returns nothing
    local StructUnit s=GetTimerStructA(GetExpiredTimer())
    call SetUnitVertexColor(s.u,255,255,255,alpha[GetUnitAbilityLevel(s.u,AID_BLUR)])
endfunction

private function Conditions takes nothing returns boolean
    return GetLearnedSkill()==AID_BLUR and IsUnitIllusion(GetTriggerUnit())==false
endfunction

private function Actions takes nothing returns nothing
    local StructUnit s=StructUnit.create()
    set s.u=GetTriggerUnit()
    call SetUnitVertexColor(s.u,255,255,255,alpha[GetUnitAbilityLevel(s.u,AID_BLUR)])
    call SetTimerStructA(s.t,s)
    call TimerStart(s.t,2,true,function Refresh)
endfunction

public function InitTrig takes nothing returns nothing
    local trigger trig=CreateTrigger()
    set alpha[1] = 90
    set alpha[2] = 65
    set alpha[3] = 40
    set alpha[4] = 20
    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(trig,Condition(function Conditions))
    call TriggerAddAction(trig,function Actions)
endfunction

endscope

Functions inside of a scope can be declared as private or public, just like structs. However, unlike methods, there is no need for a static function, and thus do not exist. Unlike structs, triggers should have only one entry point: your InitTrig function. This is so that you do not accidentally use a function you are not suppose to, which will help eliminate errors. So you will notice inside of the scope that all my functions are private EXCEPT InitTrig, which is declared public. InitTrig is the entry point, of course.

Also, notice that I renamed my functions. I dropped the Blur_ prefixes. In scopes, you do not need them, as the function gets the Blur_ prefix automatically. The exception to this rule is InitTrig, which it sticks _Blur after it, but only if you declare it as public which you should do anyways. The automatic prefixes help when you are transferring spells from map to map, as well as when you have conflicting trigger names. With scopes, just change the scope name. It is far easier than having to rename each function to have the <triggername>_ prefix.
 
Last edited:
Hii, thx for all your help. This way, you help not only me, but a whole community =) Thx.

Now back to business:
I have a question, according to you first example:
// local <structname> <variablename> = <structname>.create() local MyStruct data = GasGrenade.create()

it should be:
// local <structname> <variablename> = <structname>.create() local MyStruct data = MyStruct.create()

rit ?

Also, what is the advantage of using private variables ??
I know some people say that it is a professional courtesy, to prevent others from changing important data that may damage greatly the code, but in the case, since it will be public (and everyone will be able to change it), is there any real advantage to use private variables ?


Finally, to end, which is the most efficient and fast way to use a struc ?? Is creating your own method, like you did with Create(), or to do things step by step, like in the previous examples ?

Btw,
I found some spelling mistake, and wolf did too:
"we can store then in our" we believe you mean "them"
"The .create() method does some some" - 1 extra some =P

Great tut, I just can't wait for you next lesson =)
 
Last edited:
Level 6
Joined
Jun 30, 2006
Messages
230
I wrote the tutorial quickly, thanks for the error reports. I'm honestly surprised at how few there are (or been found, at least).

Should be fixed. Expect an update shortly.

Updated
Fixed some very important syntax errors with the create method.
Added the bit on scopes.
 
Last edited:
Level 6
Joined
Jun 30, 2006
Messages
230
It has had some minor updating/rewording. I am planning on adding a little bit on libraries soon. I'm trying to get a spell submitted for the Zephyr contest, but my web development is really moving right now... At this point it doesn't look like the spell will be finished in time. I'll revisit this when I get the time.
 
Btw,

vJass sux and Vexorian is a pig headed genius who creates buggy programs.


vJass can't handle my map... both two version of it. it just crashes when I save it, every time... why ? I make no idea ... my map only uses GUI and JASS.

And still NewGenPack, can't handle it ....

If you guys know any reason .. I would like to know why plz ..
 
??
My map has lots of imports, from Icons to models
How do I know if a model is corrupted ???
They all work in game if that is what you want to know ... but I don import skins nor musics =S

EDIT

So BJ, when will you have time to update this tut ?

Btw Wulf, i tested, and JNGP doesn't work with a map with no imports ... so imports are not the problem ... what is it ?????
 
Level 12
Joined
Aug 20, 2007
Messages
866
Great Tutorial

I'm just getting my bearings on regular JASS, this is a mouthful

Once I program more in JASS i'll have to come back to this and study it over, as it appears it is the most efficient way to program for Warcraft
 
Level 6
Joined
Jun 30, 2006
Messages
230
So BJ, when will you have time to update this tut ?

Btw Wulf, i tested, and JNGP doesn't work with a map with no imports ... so imports are not the problem ... what is it ?????

JassNewGen Works fine for me. I suggest re-downloading it, using the 1.21b version of the worldedit.exe, and using the patch that is mentioned in the thread. Also, Vexorian did not make the entire JassNewGen composition. JassHelper is what Vexorian makes, and is part of JassNewGen. Vexorian is very good at working through bugs, usually quickly.

I'm not sure when I will be able to update this, I hope it is soon. Work has been busy lately, and my mother had surgery, so this is the first chanced I've had to be on a computer when I wasn't working.

@Ghostwolf... I don't have to scroll.... does anyone else? What makes you think I don't know what static means? I most certainly do. I just don't know how to to explain it without going into the details of the converted code.

@Herman, feel free to ask questions!
 
Level 15
Joined
Feb 15, 2006
Messages
851
JASS:
struct StructUnit
    unit u=GetTriggerUnit() // leak a unit...
timer t=CreateTimer() // Leak a timer...
endstruct
This is a bad practice with handles... unless you add a destroy method which deals with the variables. This set is done every time you call the allocate() command.

My suggestion is not use this preset, instead, always do a custom create and destroy method, so you can ensure the proper creation and destruction (or recycle) process with the components of the struct.
 
Status
Not open for further replies.
Top