[Jass 101] - How to move from GUI to JASS

Jass Tutorials

JASS 101 - How to move from GUI to JASS


The purpose of this chapter is to introduce in the process to start coding in jass using Jass New Generation Pack (JNGP)

Let's start!!


Here we assume that you have installed and configured properly JNGP. If you haven't done this, please refer to this tutorial.

  • Open Jass New Gen Pack
  • If you have no map to test it, use the one that started the Editor.
  • Open the Trigger Editor in the menu Module > Trigger Editor or simply press F4.
  • You will see the following interface:


    As you can see any new map will start with a trigger which will start some configurations for a melee style game. The important parts of this trigger are the Events, the Conditions and the Actions
  • Now to the interesting part. As you may know, GUI is in fact an "cover" for the code inside the editor, so we need to tell the World Editor that we don't want to see this cover. To do that please go to the menu Edit > Convert to Custom Text as shown in the following picture:

  • After setting this you will see the following code:

    [lcode=jass]function Trig_Melee_Initialization_Actions takes nothing returns nothing
        call MeleeStartingVisibility(  )
        call MeleeStartingHeroLimit(  )
        call MeleeGrantHeroItems(  )
        call MeleeStartingResources(  )
        call MeleeClearExcessUnits(  )
        call MeleeStartingUnits(  )
        call MeleeStartingAI(  )
        call MeleeInitVictoryDefeat(  )
    endfunction

    //===========================================================================
    function InitTrig_Melee_Initialization takes nothing returns nothing
        set gg_trg_Melee_Initialization = CreateTrigger(  )
        call TriggerAddAction( gg_trg_Melee_Initialization, function Trig_Melee_Initialization_Actions )
    endfunction[/lcode]

    In order to understand it I'd suggest to read it from the bottom to the top. The first thing you will see is that we manage something called functions. All in Jass is a matter of developing functions to be honest. The functions has 3 parts: the name, the input arguments ([lcode=jass]takes[/lcode]) and the output argument ([lcode=jass]returns[/lcode]) sets the type of data or handle the functions will obtain.

Important tip: From now on you will work in the TESH editor environment . It's important to clarify that the undo and redo in the toolbar are not functional in the text edition, instead you have to right click over the code text and there will be a menu which will offer this feature in the code.


Trying some GUI code.


Well, we've made some test with an easy code, pretty straight. Now let's try with something more elaborated but simple to understand. As a sample, we'll do a trigger that makes Abominations Deals damage upon death. We clarify that we won't make it leakless or efficient in order to make it readable to see how this will look in Jass.


And here's the GUI for that:
[trigger]Test
    Events
        Unit - A unit Dies
    Conditions
        (Unit-type of (Triggering unit)) Equal to Abomination
    Actions
        Unit Group - Pick every unit in (Units within 512.00 of (Position of (Triggering unit)) matching (((Matching unit) belongs to an enemy of (Owner of (Triggering unit))) Equal to True)) and do (Actions)
            Loop - Actions
                Unit - Cause (Triggering unit) to damage (Picked unit), dealing 250.00 damage of attack type Chaos and damage type Acid
[/trigger]And then, let's convert it into Jass. The result is the following:
Code (jass) Select
function Trig_Test_Conditions takes nothing returns boolean
    if ( not ( GetUnitTypeId(GetTriggerUnit()) == 'uabo' ) ) then
        return false
    endif
    return true
endfunction

function Trig_Test_Func001001003 takes nothing returns boolean
    return ( IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit())) == true )
endfunction

function Trig_Test_Func001A takes nothing returns nothing
    call UnitDamageTargetBJ( GetTriggerUnit(), GetEnumUnit(), 250.00, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_ACID )
endfunction

function Trig_Test_Actions takes nothing returns nothing
    call ForGroupBJ( GetUnitsInRangeOfLocMatching(512, GetUnitLoc(GetTriggerUnit()), Condition(function Trig_Test_Func001001003)), function Trig_Test_Func001A )
endfunction

//===========================================================================
function InitTrig_Test takes nothing returns nothing
    set gg_trg_Test = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Test, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( gg_trg_Test, Condition( function Trig_Test_Conditions ) )
    call TriggerAddAction( gg_trg_Test, function Trig_Test_Actions )
endfunction


As you can see, it generates 5 functions. From bottom to top: [lcode]InitTrig_Test[/lcode], [lcode]Trig_Test_Actions[/lcode], [lcode]Trig_Test_Func001A[/lcode], [lcode]Trig_Test_Func001001003[/lcode] and [lcode]Trig_Test_Conditions[/lcode]. The first one will run at map initialization loading the trigger condition and action. the second will execute the actions block. The third will be executed for each unit near to the abomination and it will deal damage to it. The fourth filters all the units which are the enemies of the dying unit. And the fifth will check if the unit is of the type, if true, it will trigger the actions.

So in the end, all this GUI blocks form a long and non easy to read code. And the worst thing is that it does in an inefficient way. You see the long names?? they're done in this way some any function gets unique in the whole code.

How the code is arranged?? at the end, the code is armed in one file which will have a *.j extension. It will be the core of the map and will manage all the internal things in the map.

Now let's talk about the functions itself. Have you noticed that some of them are in cursive and others are in bold? the ones in cursive are functions from a library called [lcode]blizzard.j[/lcode], and the other are from the MAIN library called [lcode]common.j[/lcode] and they are called natives.

The functions that we want to use more are the natives because they are the fastest in execution and in Jass are the most preferred. Blizzard.j what it does?? well, this library is the connector from GUI to JASS. Most of the code done in blizzard.j is repetitive and does unnecessary steps. that's the reason GUI is slower than coding directly in JASS.


The first optimization


As you see, this code tends to be scary, so let's do a first try to make it look prettier. Let's start with the whole set of functions. You can see that these functions have strange names, that's because they need to be totally different from any other function defined in the triggers. We can  do a simplification in that part using a feature offered in vJASS called Scope. Scopes allow to differentiate the code. All that is inside a scope will be only valid inside the scope unless you define a set of prefixes (this part will be treated later). Meanwhile we'll use this to make it look nice.

Code (jass) Select
scope Test initializer Init

private function Conditions takes nothing returns boolean
    if ( not ( GetUnitTypeId(GetTriggerUnit()) == 'uabo' ) ) then
        return false
    endif
    return true
endfunction

private function GetEnemies takes nothing returns boolean
    return ( IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit())) == true )
endfunction

private function DoDamage takes nothing returns nothing
    call UnitDamageTargetBJ( GetTriggerUnit(), GetEnumUnit(), 250.00, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_ACID )
endfunction

private function Actions takes nothing returns nothing
    call ForGroupBJ( GetUnitsInRangeOfLocMatching(512, GetUnitLoc(GetTriggerUnit()), Condition(function GetEnemies)), function DoDamage )
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    set gg_trg_Test = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Test, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( gg_trg_Test, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Test, function Actions )
endfunction

endscope


Now is more readable: functions have names that let you get clearly its purpose. Scopes allow to define a function that runs at map initialization, very convenient to set variables of that specific part of the trigger. In addition, it provide context to the functions to only the trigger, no matter the name. We achieve this with a new keyword: [lcode=jass]private[/lcode] this makes those functions only valid inside the scope, allowing you to write faster your functions and make them more readable for you.

Well, the general optimization is done, now to the functions themselves. Let's start with [lcode]Conditions[/lcode].

Conditions function
Code (jass) Select
private function Conditions takes nothing returns boolean
    if ( not ( GetUnitTypeId(GetTriggerUnit()) == 'uabo' ) ) then
        return false
    endif
    return true
endfunction
I personally don't get why it evaluate too many stuff. If you use [lcode=jass]==[/lcode] the result will be a boolean. Comparing a boolean to obtain the same boolean is a waste of code we just simple do this:
Code (jass) Select
private function Conditions takes nothing returns boolean
    return GetUnitTypeId(GetTriggerUnit()) == 'uabo'
endfunction

As you see, we passed from 6 line of code to 3 lines. As you see too, it's straight to the point, hence for efficient.

Actions Function

Code (jass) Select
private function Actions takes nothing returns nothing
    call ForGroupBJ( GetUnitsInRangeOfLocMatching(512, GetUnitLoc(GetTriggerUnit()), Condition(function GetEnemies)), function DoDamage )
endfunction

Well, this function is pretty straight, so what can be optimized?? well, first of all it leaks variables, that means an object is created but their reference is lost so it keeps alive for the rest of the game without being removed and nullified. For that reason we'll set variables to call them later and remove them when they are not needed anymore. Additionally we have a function [lcode=jass]ForgroupBJ[/lcode] this one is a [lcode]Blizzard.j[/lcode] function so it has one or more process inside it. In JNGP, in the trigger editor, click on the text area and press CTRL, if you hover the mouse over those BJ functions you'll see they show as an hiperlink. if you click on it you'll see this code:
Code (jass,7) Select
function ForGroupBJ takes group whichGroup, code callback returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    call ForGroup(whichGroup, callback)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
endfunction

What we need actually is the function in line 7. As you see these BJ functions do a lot of unnecessary stuff, and normally they swap arguments to the natives, making most of them slow. Now let's see how it looks the code now using the native [lcode=jass]ForGroup()[/lcode]:
Code (jass) Select
private function Actions takes nothing returns nothing
    local group g = CreateGroup()
    local unit u = GetTriggerUnit()
    call GroupEnumUnitsInRange(g, GetUnitX(u), GetUnitY(u), 512., Condition(function GetEnemies))
    call ForGroup(g, function DoDamage )
    call DestroyGroup(g)
    set g = null
    set u = null
endfunction


Now we have just natives in our code and when we call an object, we can remove and clean the variables properly. And in this using local variables, we've made the code MUI (Multi Unit Instanciable) which means that this will run safely for each units without any collision in their information (we'll talk later about the importance of this).

Init Function

In the init function we don't want to create a global variable to store a trigger which won't be used or modified later, in fact in spells we need a code that triggers during all the game. So storing the trigger in a global is a waste of variables, in fact we can make it leak, because we don't need to refer to the trigger later. Here's the original code:
Code (jass,2) Select
private function Init takes nothing returns nothing
    set gg_trg_Test = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Test, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( gg_trg_Test, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Test, function Actions )
endfunction


And we will change this code into this:
Code (jass,2) Select
private function Init takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( t, Condition( function Conditions ) )
    call TriggerAddAction( t, function Actions )
    set t = null // we free the variable but not the handle itself... in this case it doesn't matter :)
endfunction


Now we manage the trigger as a local variable, this usage of locals makes easy to port code from one map to another.

To be continued...

It's time to move and we're ready to code. Please check for the following tutorial to see about the structure of the code and its features.

Added more content.
This is cool! ^.^

Moving from GUI to JASS directly via code conversion could work for a lot of people. In fact, that's what I did to learn JASS :P
Quote from: Magtheridon96 on February 13, 2013, 09:04:33 AM
This is cool! ^.^

Moving from GUI to JASS directly via code conversion could work for a lot of people. In fact, that's what I did to learn JASS :P
Yay!!! in fact I believe in the Rosseta Stone learning procedure because it's quite effective in languages.

I'm planning to add a basic formatting section to this tutorial so they can compare the raw code generated in GUI with the one optimized with vJASS and empathize the good aspects of writing directly in Jass code.
*Puts seat belt*

Mind if I join the ride?
Good job there boss! Hope this turns out to be a series :D