JASS Tools
News
Download
Help
JASS Manual
-------------------------------------------------------------

Library Functions

Many native functions and constants are declared in common.j, common.ai, and Blizzard.j. This section will descibe several functions that are of note. Obviously, this section is incomplete; if you have written or have found a more thorough description of the native API, I would be happy to link it here. See the API Browser section for a listing of the API.

Important Note: Others have observed that the following functions in common.j do not function correctly when used in AI scripts:

  • native functions that return string.
  • native functions that take callbacks (code, trigger, boolexpr, etc.) as arguments such as triggers and enumerators (ForGroup, etc.).
  • ExecuteFunc.
These functions work normally in map scripts. Also be aware that native functions declared in common.ai are obviously not available to map scripts.

There are 3 common.ai functions that work for map scripts, once you manually add them to common.j , those functions are GetUnitGoldCost , GetUnitWoodCost and GetUnitBuildTime

The current list of topics are:

Threads

Usually only a single thread of execution is started for the AI. New threads can be started in an AI script with the function declared in common.ai:

Map scripts, can simply use ExecuteFunc to create another thread.

native StartThread takes code func returns nothing

Calling this function via the statement call StartThread(function my_func) causes a new thread to begin execution starting at the beginning of the my_func function.

There are 6 thread slots per player. This includes the "main" thread, so there is a slot for the main script and 5 extra slots. If you have filled all 6 thread slots then further calls to StartThread() are ignored. Thread slots are not recycled. Once you have created 5 threads of your own, all slots are used and you can't start more threads for the same player.

When a new thread is created, it receives time immediately. When the new thread yields (gives up the interpreter), the thread that created it resumes.

Threads can yield in the following ways:

  • There is an opcode execution limit; when a thread runs more opcodes than the limit it is put to sleep automatically for 1 second.
  • When a thread calls the Sleep(N) function, it is put to sleep and will resume after N seconds.

Threads can terminate in the following ways:

  • If there was no main function (e.g., it was null), the thread is terminated.
  • When the thread's main function returns, it exits.
  • If a thread uses a variable that was not set before it was used, then it is terminated. (All variables must be set before they are used.)
  • If a thread executes a divide by 0, it is terminated.

You should never let the main thread (the one that is started by the game automatically) exit. For example, you can call SleepForever() at the end of the main function to avoid exiting.

All threads for a single AI player share global state, so if you modify a global variable in one thread, the change will be visible in other threads in the same script. You can not start new threads in map scripts.

Triggers

A trigger, used in a map script, is a callback; that is, it an event handler that is installed to execute when a particular event(s) occur. Triggers do not function properly in AI scripts. A trigger is of type trigger which is a subtype of handle (hence it is just a pointer to some in game data-structure).

New triggers are created with the function (in common.j):

native CreateTrigger takes nothing returns trigger

The actual trigger contains conditions and actions. The conditions are a set of boolexprs. These are handle types which encapsulate functions that return booleans. A condition boolexpr can be created with the following function. The argument function should have the signature takes nothing returns boolean. (conditionfunc is a subtype of boolexpr)

native Condition takes code func returns conditionfunc

A condition is added to a trigger with the function:

native TriggerAddCondition takes trigger whichTrigger, 
                                 boolexpr condition returns triggercondition

It returns a handle to the condition which can be used later in other trigger related functions (e.g., if you want to remove the condition from the trigger).

Actions are just functions which execute when the event occurs and the conditions are satisfied. They should have the signature takes nothing returns nothing.

native TriggerAddAction takes trigger whichTrigger, 
                              code actionFunc returns triggeraction

Again, the function returns a handle to the action, in case it must be refered to later.

Once a trigger is created, it is must be registered with a game event(s) which indicate when it will be executed. This is accomplished using the functions that look like this in common.j:

native TriggerRegister*Event takes trigger whichTrigger, ... returns event

There are many other trigger manipulation functions in common.j that can help trigger management.

Inter-Script Communication

During a game, there will be several independent JASS scripts executing; one for the map trigger script, and one AI script for each computer player. The global variables in each of these scripts are not shared, so setting a global value in one AI script will not affect the same global variable in another computer's AI script.

The map script and AI scripts can communicate with each other using commands. A command is a pair of integer values, a command value and a data value. To send a command to a particular AI script, use the function (declared in common.j):

native CommandAI takes player num, integer command, integer data returns nothing

Each computer player has a command stack onto which new commands sent to them are placed. To determine how many commands are waiting on the stack, use the following function (declared in common.ai):

native CommandsWaiting takes nothing returns integer

To look at the command at the top of the stack, use the following functions:

native GetLastCommand takes nothing returns integer
native GetLastData takes nothing returns integer

Which return the latest command and data values sent, respectively. These functions merely look at the command at the top of the stack; they do not remove it. To remove the command at the top of the stack, use:

native PopLastCommand takes nothing returns nothing

Remember that adding a new element to a stack places it on top, while popping an element off the stack also removes the first from the top.

Enumerations

Although defining custom data structures in JASS is impossible due to the lack of first class pointers, the native API does provide a crude implementation of sets for certain handle types. A set of units is called a group; a set of players is called a force; you can also work with a set of destructables (doodads that can be destroyed), but there is no type that names the set.

The API for managing custom groups and forces is similar. For example, to create, add an element, and remove an element to a group and force respectively:

// group management
native CreateGroup takes nothing returns group
native GroupAddUnit takes group whichGroup, unit whichUnit returns nothing
native GroupRemoveUnit takes group whichGroup, unit whichUnit returns nothing

// force management
native CreateForce takes nothing returns force
native ForceAddPlayer takes force whichForce, player whichPlayer returns nothing
native ForceRemovePlayer takes force whichForce, player whichPlayer returns nothing

A common design pattern used with a set is an iterator or enumeration. An enumeration is an interface that allows you to retrieve each element from the set and operate it. Usually, you want to express the notion "for each element in the set, do something to it" in the same way you would iterate through an array. For example, the standard way to process each element in a set in Java (and similarly in C++) is:

// While the iterator of 'set' has more elements in it
for (Iterator iter = set.iterator(); iter.hasNext(); ) {
      // Retreive the next element from the iterator
      Object element = iter.next();
      // Do something with it...
      processElement(element);
}

In JASS, there is no way* to "look inside" a group or force directly, so the native API relies on callbacks to achieve the same functionality. To process each element in a group and force respectively, use the following functions. The callback function has the signature takes nothing returns nothing.

// For each unit in 'whichGroup' call the 'callback' function
native ForGroup takes group whichGroup, code callback returns nothing

// For each player in 'whichForce' call the 'callback' function
native ForForce takes force whichForce, code callback returns nothing

A similar function exists to process each element in a destructable set that consists of all destructables in a certain rect (a rectangular region of the map). See the subsection on Filters for more information on the boolexpr. * One way to get around this apparent limitation is by using the FirstOfGroup() and GroupRemoveUnitSimple() functions to iterate through a temporary group (see this for an example). This is useful in AI scripts where callbacks do not work.

// For each destructable in 'r' that satisfies 'filter', call the 'actionFunc' function
native EnumDestructablesInRect takes rect r, boolexpr filter, code actionFunc returns nothing

Within the callback function, you retreive the next element to process with the functions (for groups, forces, destructable sets, respectively):

constant native GetEnumUnit         takes nothing returns unit
constant native GetEnumPlayer       takes nothing returns player
constant native GetEnumDestructable takes nothing returns destructable

(These enumerators to not work correctly in AI scripts so only use them in map scripts) For example, suppose you wanted to kill all the units in a group. You could use the following callback function:

function KillGroupCallback takes nothing returns nothing
     // Get the next unit to process
     local unit nextUnit = GetEnumUnit()
     // Kill it
     call KillUnit(nextUnit)
endfunction

Then you can kill all the units in a group with the following statement:

     call ForGroup(groupToKill, function KillGroupCallback)

Another common operation is to find the element(s) in a set that satisfies certain conditions in relation to the other elements in the set. For example, you might want to find the unit with the most life in a group. Unfortunately, because JASS relies on callbacks to process each element, the only way to maintain state across all the elements in a set is to use a global variable. For example, to find the unit with the most life in a group, you could use the code:

globals
     real mostLifeSoFar
     unit unitWithMostLifeSoFar
endglobals

function MostLifeCallback takes nothing returns nothing
     local unit nextUnit = GetEnumUnit()
     local real life = GetUnitState(nextUnit, UNIT_STATE_LIFE)
 
     if life > mostLifeSoFar then
          set mostLifeSoFar = life
          set unitWithMostLifeSoFar = nextUnit
     endif
endfunction

...
     // Initialize variables to find unit with max life in 'myGroup'
     set mostLifeSoFar = 0
     set unitWithMostLifeSoFar = null
     // Process each element in 'myGroup'
     call ForGroup(myGroup, function MostLifeCallback)
     // Now 'unitWithMostLifeSoFar' refers to the desired unit, 
     // or null if the group was empty
...

Finally, you should note that you can achieve most of the same functionality with arrays instead of groups and forces, since an array can be viewed as a set. The drawbacks are that an array always uses up a certain amount of storage (see Types), you will end up with "holes" in your set if you remove elements in the middle of the array, and you can not use the filter functions described in the Filters subsection directly to create array sets (though you can use them to first create a group, then use enumeration callbacks to fill your array). In addition, you can not have an array of arrays, whereas you can have an array of groups or forces.

Filters

The Enumerations subsection described one way to create sets (groups and forces), but sometimes it is desirable to dynamically create a set containing all elements with particular properties. For example, you might want to create a group containing all the units with less than 20 mana (then you can use ForGroup(...) to do something to each of them).

There are several native functions that facilitate this. For example, the following functions can be used to fill groups. They take a group as an argument and add all units that satisfy a certain property to the group.

// Add all units that are of type 'unitname' and satisfy the 'filter'
native GroupEnumUnitsOfType takes group whichGroup, string unitname, 
  boolexpr filter returns nothing

// Add all units that belong to 'whichPlayer' and satisfy the 'filter'
native GroupEnumUnitsOfPlayer takes group whichGroup, player whichPlayer, 
  boolexpr filter returns nothing

// Add up to 'countLimit' units of type 'unitname' that satisty the 'filter'
native GroupEnumUnitsOfTypeCounted takes group whichGroup, string unitname, 
  boolexpr filter, integer countLimit returns nothing

// Add all units that are in the rectable 'r' and satisfy 'filter'
native GroupEnumUnitsInRect takes group whichGroup, rect r, boolexpr filter 
  returns nothing

// Add up to 'countLimit' of units that are in 'r' and satisfy 'filter'
native GroupEnumUnitsInRectCounted takes group whichGroup, rect r, 
  boolexpr filter, integer countLimit returns nothing

// Add all units that are within 'radius' of the point ('x','y') and 
// satisfy 'filter'
native GroupEnumUnitsInRange takes group whichGroup, real x, real y, 
  real radius, boolexpr filter returns nothing

// Add all units that are within 'radius' of the point 'location' and 
// satisfy 'filter'
native GroupEnumUnitsInRangeOfLoc takes group whichGroup, 
  location whichLocation, real radius, boolexpr filter returns nothing

// Add up to 'countLimit' units that are within 'radius' of the 
// point ('x','y') and satisfy 'filter'
native GroupEnumUnitsInRangeCounted takes group whichGroup, real x, real y, 
  real radius, boolexpr filter, integer countLimit returns nothing

// Add up to 'countLimit' units that are within 'radius' of the point 
// 'location' and satisfy 'filter'
native GroupEnumUnitsInRangeOfLocCounted takes group whichGroup, 
  location whichLocation, real radius, boolexpr filter, 
  integer countLimit returns nothing

// Add all units that are selected by 'whichPlayer' and satisfy 'filter'
native GroupEnumUnitsSelected takes group whichGroup, player whichPlayer,
  boolexpr filter returns nothing

A similar set of functions exist for adding players to forces:

// Add all players that satisfy 'filter'
native ForceEnumPlayers takes force whichForce, boolexpr filter returns nothing

// Add up to 'countLimit' players that satisfy 'filter'
native ForceEnumPlayersCounted takes force whichForce, boolexpr filter,
  integer countLimit returns nothing

// Add all units that are allies of 'whichPlayer' that satisfy 'filter'
native ForceEnumAllies takes force whichForce, player whichPlayer,
  boolexpr filter returns nothing

// Add all units that are enemies of 'whichPlayer' that satisfy 'filter'
native ForceEnumEnemies takes force whichForce, player whichPlayer,
  boolexpr filter returns nothing

Each of these functions takes a boolexpr which is also used for conditions in triggers (see the Triggers subsection). In particular, you will usually pass in a filterfunc (which is nearly identical to a conditionfunc). A filterfunc encapsulates a boolean function just like a conditionfunc. To create a filterfunc use:

native Filter takes code func returns filterfunc

The argument's function signature should be takes nothing returns boolean. The filter serves as an additional condition that each element must satisfy before being added to the group or force. Filters work like enumerations; when one of the above functions are used to add elements to sets, each element that satisfies the initial property (all units in a region, all players allied with some player, etc.) is "passed" to the filter function. If the filter function returns true, then the element is added, otherwise it is not. In the filter callback, you can retrieve the next unit, player, and destructable doodad to examine with the following functions, respectively:

constant native GetFilterUnit         takes nothing returns unit
constant native GetFilterPlayer       takes nothing returns player
constant native GetFilterDestructable takes nothing returns destructable

For example, if I wanted to create a filter that is only satisfied by units with less than 20 mana, I can use the following callback funtion:

function LessThan20ManaCallback takes nothing returns boolean
     // Get the next unit to examine
     local unit nextUnit = GetFilterUnit()
     // Check to see if it satisfies our condition
     return GetUnitState(nextUnit, UNIT_STATE_MANA) < 20
endfunction

Then, I can use the following statements to add all units that are in a particular region and have less than 20 mana into my group:

     // Create the filter
     local filterfunc myFilter = Filter(function LessThan20ManaCallback)
     // Add all satisfying units in 'someRect' into 'myGroup'
     call GroupEnumUnitsInRect(myGroup, someRect, myFilter)
     // Destroy the filter if we are not going to use it again
     call DestroyFilter(myFilter)

Note that we destroy the filter after we are done with it so there is not a memory leak.

If no additional condition is required, then you can pass in the value null instead of an actual filterfunc.

------------------------------------------------------------
Copyright (c) 2003 Jeff Pang
Not affiliated or endorsed by Blizzard Entertainment
SourceForge.net Logo