JASS: emjlr3's Users Guide *WIP*

Tutorial By emjlr3

emjlr3's JASS Users Guide

*What is my user guide you ask? Well it is quite simply put, a beginners, and experts alike, guide to the everyday usages that JASS has to offer. Everyday usages...I'm not quite sure I follow. Well, there are certain ways to do things in GUI, and there are certain ways to do those same things in JASS. There are also certain things one wishes he could do with GUI, which also have certain ways to be done in JASS. This guide encompasses all of these things and more, in an eloquently written, wonderfully laid out master piece of a tutorial...and no, I don't boast often...

*What does one need to begin such an intellectual journey into the unknown? Hmm...well, really what every JASS user needs. A healthy background, and a simple tool.

*Where can you look for such things you ask? I suggest taking a look at this guide, which should give you a basic nudge in the right direction, then read through this guide, to really get a good feel for some JASS. Once you have done this, go grab JASS Craft and then you should be just about ready to begin.

Go grab a sandwich...take a leak, no don't itch there.....fine....ok lets start.

Let me begin by saying, thank you for reading this...give me rep....just kidding, now that I got that out of my system, prepare to be amazed...



Looping Through Unit Groups:

This process is rather simple. The idea is to just, grab a unit from your group, do what needs done, and remove the unit, only to move onto the next, stopping, of course, when no more units remain in the group. Let’s see how this can be accomplished:

local group g = CreateGroup() local unit dum call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,null) loop set dum = FirstOfGroup(g) exitwhen dum==null call GroupRemoveUnit(g,dum) //Do Stuff endloop call DestroyGroup(g) set g = null

Once our group is established how it is wanted, we must loop through the units in contains. I use FirstOfGroup since it is faster then GroupPickRandomUnit, which I only use when that is what I really need, a random unit, however, since we will go through every unit in the group, the faster function is utilized. We want to exit the loop once there are no more units in it, thus, when dum==null, the last unit picked was no unit, and means there are no more units left. The GroupRemoveUnit is critical, since we only want to affect each unit once, or we would create an infinite loop :(

Another thing to note is when using unit groups in JASS, if you want to stick to natives, you must first and foremost create your unit group with CreateGroup. Else, you have no group to work with.

Another option is the for group function, shown below:

function pooh takes nothing returns nothing call KillUnit(GetEnumUnit()) endfunction function blarg takes nothing returns nothing local group g = CreateGroup() call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,null) call ForGroup(g,pooh) call DestroyGroup(g) set g = null endfunction

Calling the ForGroup function takes your unit group, and does to each unit in it what is in the function your pass to it. This is good in some respects, since your units remain in the group once it is over, however, any variables that are in your first function that you need must be passed into the second (using a global variable or game cache), since our function "pooh" cannot take anything, or we would get syntax errors( I know it sucks, thank Blizzard while your at it). One thing to note, GetEnumUnit() refers to the unit in the group that is being looped through.


Passing Variables Between Functions:

This is needed, like in the above example; you need to use things in a ForGroup call. Another instance could be when using a repeating timer or a boolean function in a unit group. Here is an example:

function pooh takes nothing returns nothing call UnitDamageTarget(bj_ghoul[55],GetEnumUnit(),50.*bj_randDistID[55],false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null) endfunction function blarg takes nothing returns nothing local group g = CreateGroup() local unit u = GetTriggerUnit() local integer level = GetUnitAbilityLevel(u,'A000') call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,null) set bj_ghoul[55] = u set bj_randDistID[55] = level call ForGroup(g,function pooh) call DestroyGroup(g) set g = null set u = null endfunction

I needed the caster to damage the unit in the group for an amount based on his level of the ability. I used two blizzard global variables to move the locals I needed from my one function to the other. This is MUI since it is all instant, and there are no waits involved. It is important to use arrays when this is done with blizzard globals(I use them so I don't have to make my own), and always pick values greater then 15 or so. A few more examples of some globals you can use in the same way are:

globals boolean array bj_stockAllowedCharged force array bj_FORCE_PLAYER integer array bj_randDistID timer array bj_crippledTimer timerdialog array bj_crippledTimerWindows trigger array bj_queuedExecTriggers unit array bj_ghoul endglobals

This can of course be done the same way with user created global variable arrays. A last way to transport things between functions is with the Game Cache(I will use tables for my example):

function yak_child takes nothing returns nothing local string s = "yak" local unit u = GetTableUnit(s,"u") local real r = GetTableReal(s,"r") call FlushStoredMission(CSCache(),s) //Do Stuff set u = null endfunction function yak takes nothing returns nothing local unit u = GetTriggerUnit() local real r = GetEventDamage() local string s = "yak" call SetTableObject(s,"u",u) call SetTableReal(s,"r",r) call ExecuteFunc("yak_child") set u = null endfunction

This is really the same deal as before, except using Game Cache. Remember to remove the leak as I did above using FlushStoredMission on the string you had stored things to. This, again is MUI, since it is instant, and as long as there are no waits around.


Creating Local Triggers:

In GUI, when you create a trigger in the trigger editor, a new trigger tab is created for that trigger, where you can enter events, conditions and actions for that trigger alone. However, when using JASS, you can dynamically create, destroy and use any number of triggers you want, all locally so it is MUI. For example:

//=========================================================================== function InitTrig_Transparent_Mirror_Images takes nothing returns nothing local trigger trig = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( trig, Condition( function Trig_Transparent_Mirror_Images_Conditions ) ) call TriggerAddAction( trig, function Trig_Transparent_Mirror_Images_Actions ) set trig = null endfunction

When this is first converted to JASS, the now local trigger trig was a global trigger created by Blizzard for each new trigger tab you create in the trigger editor. Simply used a local trigger in the same way, and used its name in place of the globals triggers. The same concept can be done dynamically in any function you have. Keep in mind however, and trigger action, trigger condition and boolean expressions you add to a trigger will remain there until removed and destroyed. Thus creating many local triggers in this fashion can leak a good bit, thus it is important to store the trigger action, etc. to variables so they can be dealt with later.


ExecuteFunc Native:

This natives is a very powerful tool for JASSers, let’s see what it looks like:

native ExecuteFunc takes string funcName returns nothing

What this allows you to do is run any function in your entire map instantly, and move onto your next function without having to wait for that function to finish what it is doing. This allows you to go over the op limit, which limits the amount of things you can loop through without a wait as well. Let’s have a peak out a simple application for it’s usage:

function blarg_2 takes nothing returns nothing local unit u = bj_ghoul[22] call SetHeroStr(u,50.,true) call PolledWait(GetRandomReal(5.,20.)) call SetHeroStr(u,0.,true) set u = null endfunction function blarg takes nothing returns nothing local group g = CreateGroup() local unit u call GroupEnumUnitsOfPlayer(g,GetTriggerPlayer(),null) loop set u = FirstOfGroup(g) exitwhen u==null call GroupRemoveUnit(g,u) set bj_ghoul[22] = u call ExecuteFunc("blarg_2") endloop call DestroyGroup(g) set g = null endfunction

This allowed me to add something to a unit in the group, and wait a random amount of time, differently for each unit affected, then remove it. Keep in mind that this is not a function call, but a sort of run function, so your function cannot take any arguments, and you must pass things to it like I did.


Cleaning Local Trigger Leaks:

As said earlier, when you create local triggers, you often have to add conditions and actions functions to them, and if you plan on doing this dynamically often, you can build up a lot of memory usage that needs to be cleared. The easiest method is to set each thing, the triggeraction, the condition and the boolexpr to locals, and remove them accordingly, let’s see how:

function blarg takes nothing returns nothing local trigger trig = CreateTrigger() local triggeraction ta = TriggerAddAction(trig,function blarg_ta) local boolexpr b = Condition(function blarg_boolexpr) local triggercondition tb = TriggerAddCondition(trig,b) //Do Stuff call DisableTrigger(trig) call TriggerRemoveAction(trig,ta) call TriggerRemoveCondition(trig,tb) call DestroyBoolExpr(b) call DestroyTrigger(trig) set trig = null set ta = null set b = null set tb = null endfunction

As you can see, it’s a pretty big hassle, but a necessary one. One of the biggest parts is the trigger conditions, so if you can, try to stay away from those if at all possible. If you need to store all these things and remove the leaks in a separate function, you can do so with the Table functions which can store each thing for you. You can then later retrieve them as you would anything else, and follow these steps for removing your leak.


What are Tables and why should I use them?:

Tables is a just another system made by Vexorian which makes use of the return bug and Game Cache to dynamically store and retrieve information. What makes them better then just using “Handle Vars” is the usage of a function which stores the string of a handle once, so you don’t have to keep repeating the return bug and I2S functions over and over again needlessly. Why is this good? Well both the I2S and H2I are extra function calls, each time, so not only are you saving two extra function calls for each storage/retrieval, you are also saving a lot of time because H2I is a very slow function to run.

I will briefly show the difference, however for more information please check out Vexorians official location at WC3C .

Two typical functions compared:

function GetTableUnit takes string table, string field returns unit return GetStoredInteger(cs_cache,table,field) return null endfunction function GetAttachedUnit takes handle h, string label returns unit return GetStoredInteger(cs_cache, I2S(CS_H2I(h)), label) return null endfunction

The major difference, and only difference, is the lack of the I2S(CS_H2I()) in the tables function as compared to the handle vars function. Where handle vars takes the actual handle you are storing things onto, like a timer or trigger, tables takes that handle (or integer, or whatever, you can store things to any string or on any object using this method) already converted to a string. You do this once in your function, then you have it for the rest of your storing/retrieval. Here is a quick sample:

function blarg_2 takes nothing returns nothing local timer t = GetExpiredTimer() local string s = I2S(CS_H2I(t)) local unit u = GetTableUnit(s,"hero") local real hp = GetTableReal(s,"hp") //Do Stuff call FlushStoredMission(cs_cache,s) call DestroyTimer(t) set t = null set u = null endfunction function blarg takes nothing returns nothing local timer t = CreateTimer() local string s = I2S(CS_H2I(t)) call SetTableObject(s,"hero",GetTriggerUnit()) call SetTableReal(s,"hp",GetWidgetLife(GetTriggerUnit())) call TimerStart(t,5.,false,function blarg_2) set t = null endfunction

Be forewarned, however, that there are now much better alternatives to Tables and Handle Vars, such as Structs, Locations and Dynamic Arrays. I will however not cover those here. For more information visit here and here


What is a bj?:

A bj refers to a function from the blizzard.j file which contains functions that are used by GUI when you add actions, events of conditions. There is another file called common.j which houses all the native files made by blizzard. These are functions which are called by bj functions to do things, and call no other functions themselves. Why is this important? Well, for 2 reasons, speed and leaks. Why would you want to call a function, which calls another, which calls another, which finally does what you want it to do? When you could just call that final native function and do it in 1 step? Also, some bj functions have leaks in them, which can be avoided through the use of natives. Let’s see one of my favorite examples:

function IsUnitAliveBJ takes unit whichUnit returns boolean return not IsUnitDeadBJ(whichUnit) endfunction function IsUnitDeadBJ takes unit whichUnit returns boolean return GetUnitState(whichUnit, UNIT_STATE_LIFE) <= 0 endfunction constant native GetUnitState takes unit whichUnit, unitstate whichUnitState returns real

In GUI if you were to check if a unit was alive, it would have to call 2 extra functions to check, where as in JASS, you can directly call the native GetUnitState, and be done with it.


How to create and use dummy casters:

This is a fairly easy concept, and should be done quite similarly to how you would with GUI. The idea is to create your unit, make sure he goes away, and get him to do what you want, lets see how this can be done:

function CreateCaster takes player owner, real x, real y, integer abilid, integer level returns unit set bj_ghoul[93] = CreateUnit(owner,'n003',x,y,0) call UnitAddAbility( bj_ghoul[93], abilid) call SetUnitAbilityLevel(bj_ghoul[93],abilid,level) call UnitApplyTimedLife(bj_ghoul[93],'BTLF',2) return bj_ghoul[93] endfunction

This is a sample function I use for my dummy caster creation. With a few simple inputs, for what player, the x and y of where, the integer of the ability you want to cast, and the level of that ability you want, you can use this to return a dummy caster for your use.

You must first create the unit, add your dummy ability, set it to the level you want it to be, and add a timed life to the unit. 2 is the length of time I use, since it is usually for more then enough time then is needed.

As far as getting it to cast an ability, there are three functions you can use:

native IssueImmediateOrder takes unit whichUnit, string order returns boolean native IssuePointOrder takes unit whichUnit, string order, real x, real y returns boolean native IssueTargetOrder takes unit whichUnit, string order, widget targetWidget returns boolean

Immediate order is used for instant abilities, like roar, unit order is for spells that require a unit to cast upon, and point order is for everything else. The string refers to the order string of the ability, for example, for roar, the string would be "roar".


Leaks, short and sweet:

This will not cover trigger leaks, as I did so further upwards. Anyhow, what leaks? Well lots and lots of things leak. This will be a compact list of what I know of that leaks that you can clean up:

*Handles not nulled, commonly used handles include:
units, triggers, timers, boolexprs, destructables, effects, forces, groups, triggeractions, triggerconditions, lightning, locations, items, rects, regions, sounds, texttags, timerdialogs, weathereffects

you must null these handles before the function ends to remove the leak

*Certain things need to be removed or destroyed or they leak, common things like this include:
boolexprs - DestroyBoolExpr()
effect - DestroyEffect()
force - DestroyForce()
group - DestroyGroup()
lightning - DestroyLightning()
location - RemoveLocation()
rect - RemoveRect()
region - RemoveRegion()
sound - DestroySound()
texttag - DestroyTextTag()
timer - DestroyTimer()

these function must be used to remove the leaks before the function ends

*Gamecache, many people use gamecache to store things, however these things leak. To remove this leak, simply use FlushStoredMission() with the game cache variable and string you stored things to to remove the leak





v 1.03

More to come hopefully. This originally seemed like a really good idea, as people are often asking how to do things like this in JASS, however I am at a loss for what else people ponder over. As is such, ideas and suggestions from you fine people are in order :)

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.