Functions

A function is a list of commands. When the function is called, the commands will be executed one after another. Such a command can assign a value to a variable, or call another user-defined or pre-defined function.

A function is defined this way:

type name (parameter1, parameter2, ...) { command1; command2; ... }

type is the type of the variable (such as var, int, float...) that the function returns to the caller. If the function returns a var, or returns nothing, you can write just function instead of the type.

name is the name of the function. It must follow the same rules as variable names.

(parameter1, parameter2, ...) is a list of variables that the function expects from the caller. For each variable that is passed to the function, the parameter list must contain one entry. This entry specifies the variable type and the name with which the variable is referred to inside the function. If the variable type is var, it can be omitted. The parameter list is enclosed in parentheses. If the function expects no variables, leave the parentheses empty.

{ command1; command2; ... } is the function body, enclosed in braces. It's here that the real work is done. When a function is called, execution begins at the start of the function body and terminates (returns to the calling program) when a return or wait statement is encountered or when execution reaches the closing brace.

A simple function definition without parameters looks like this:

function beep3() 
{ 
  beep();
  beep();
  beep();
} 
...
beep3(); // beeps 3 times

This function is executed whenever the engine encounters a line containing beep3() . The next example is a function that takes a var as parameter:

function beeps(var times)
{
  while (times > 0) { 
    beep(); 
    times -= 1; 
    wait(1); 
  }
}
...
beeps(7); // beeps seven times

If a global variable or another object of the same name as the parameter exists, all references within the function refer to the 'local' parameter and not to the 'global' variable. The function may change any parameter, as 'times' in the example, but the original parameter in the calling function remains unchanged. The parameters can be omitted, in that case the space between the parentheses remains empty.

 LC  In lite-C, a function can have any number of parameters of any type. Structs can not be used for parameters, but struct pointers can. If the type is not a var, it must be given before the parameter name, as in "function beeps (int times,float loudness)". In C-Script however a function can only have up to 4 parameters, and they must be either a var or a var pointer . Because a var can also store handles or object pointers, you can also pass a a handle or a pointer to any object to a C-Script function. You just must then assign it to a 'real' pointer before you can access its parameters directly.

Example (C-Script):

// this function takes a var set to an entity pointer as argument.
function ent_init(ent)
{
   my = ent; // necessary, because my.bright = on works, but ent.bright = on won't work!
   my.bright = on;
   my.abient = 100;
   ent_animate(ent,"walk",50,0); // it does not matter whether you pass my or ent here
}
...
ent_init(you);
...

Example (lite-C):

// this function takes an entity pointer as argument.
function ent_init(ENTITY* ent)
{
   set(ent,BRIGHT);
   ent.ambient(100);
   ent_animate(ent,"walk",50,0);
}
...
ent_init(you);
...

Functions also accept pointers to arrays or to structs as parameters. In C-Script we prefix the parameter name by a "&" in order to indicate that a parameter is a var pointer instead of a single number. In lite-C however we indicate this like any other non-var parameter by preceding the parameter name by its var* type. If an array pointer is passed to a function, its elements can be accessed as usual through [..] indices:

Example (C-Script):

function vector_add2(&sum,&v1,&v2)
{ // calculate the sum of two vectors
   sum[0] = v1[0] + v2[0];
   sum[1] = v1[1] + v2[1];
   sum[2] = v1[2] + v2[2];
} 	

var vector1[3] = 1,2,3;
var vector2[3] = 4,5,6;
var vector3[3];
...
vector_add2(vector3,vector1,vector2); // vector3 now contains 5,7,9

Example (lite-C):

function vector_add2(var* sum,var* v1,var* v2)
{ // calculate the sum of two vectors
   sum[0] = v1[0] + v2[0];
   sum[1] = v1[1] + v2[1];
   sum[2] = v1[2] + v2[2];
} 	

var vector1[3] = { 1,2,3 };
var vector2[3] = { 4,5,6 };
var vector3[3];
...
vector_add2(vector3,vector1,vector2); // vector3 now contains 5,7,9
You can also pass struct parameters to functions that expect var pointers. This is often useful to pass entity or view positions, angles or skills to a function:

Example (C-Script):

function vector_direction(&result,&from,&to)
{ // calculate the direction angles between two positions
    result[0] = to[0] - from[0];
    result[1] = to[1] - from[1];
    result[2] = to[2] - from[2];
    vec_to_angle(result,result);
} 		

var direction[3];
...
vector_direction(direction,my.x,your.x);
// calculates the angle pointing from my to you
vector_direction(direction,camera.x,nullvector);
// calculates the angle from the camera to the level origin

Example (lite-C):

function vector_direction(var* result, var* from, var* to)
{ // calculate the direction angles between two positions
    result[0] = to[0] - from[0];
    result[1] = to[1] - from[1];
    result[2] = to[2] - from[2];
    vec_to_angle(result,result);
} 		

var direction[3];
...
vector_direction(direction,my.x,your.x);
// calculates the angle pointing from my to you
vector_direction(direction,camera.x,nullvector);
// calculates the angle from the camera to the level origin

You can pass the same variable to a function as a variable parameter, or as a variable pointer. However there is a subtle difference. If the function modifies the variable, in the first case it only operates on a copy of the variable content, i.e. the modification happens only internally within the scope of the function. The passed variable itself is not changed. But when a variable pointer is passed, the function directly operates on the content and the variable is thus modified 'globally'.

Saving, loading, multitasking

A game engine is normally a multitasking engine. This means that many functions can run simultaneously, or several instances of the same function can run at the same time. This requires special precautions in some cases, like saving or loading a game, or idling during a wait cycle.

The current state of a function, its local variables and the predefined my and you pointers are preserved during a wait() call, and are saved with the game_save function. This ensures that when you load a previously saved game, all functions continue to run at the point where they were saved. Well, almost. Pointers other than my or you however are not saved, and thus will become invalid within functions after loading a game. This can only happen during wait() time, so you should be aware that your pointers could become invalid after wait() when you load a game. If you need pointers to remain valid in your function during wait() time, convert them to a handle before wait() and back to a pointer afterwards.

Waiting functions are stored on a Scheduler List and called in the order of their last wait() execution. This order can be modified by the proc_mode variable (lite-C) resp. the proc_late function (C-Script).

Function prototypes and pointers

Like all other objects, functions must be defined before they are assigned to another object or called by another function. Sometimes this is inconvenient, for instance if two functions call each other. In that case, a function can be predefined through a prototype (known to C++ programmers). The prototype consists of the function title with the parameters, followed by a semicolon, but without the instruction list in {...}. Example:
function beep_sum(beep1,beep2); // prototype of function beep_sum        

function use_beep_sum()
{
   beep_sum(3,4); // beep_sum is used here, and needs the prototype defined before
}

function beep_sum(beep1,beep2) // this is the real function
{
   beep1 += beep2;
   while (beep1 > 0)
   {
      beep();
      beep1 -= 1;
      wait(1);
   }
}

 LC  In lite-C a function prototype can also be used as a function pointer. Example:

long MessageBox(HWND,char *,char *,long); // prototype of function MessageBox
...
MessageBox = DefineApi("user32!MessageBoxA"); // set the MessageBox pointer to a function in the user32.dll

Return values

A function can return a value to its caller, through the return instruction. Example:
function average(a,b) 
{ 
  return((a+b)/2); 
} 


// this is called this way:
x = average(123,456); // assign the average of 123 and 456 to x
 LC  While C-Script can only return a var, lite-C, C, or C++ can return any variable or pointer type. Instead of "function", the function is then defined with the returned type, like long, float, ENTITY*, or whatever. Examples (lite-C):
PANEL* emptypanel(layer) 
{ 
  return(pan_create("",layer)); 
}

float fAverage(float a, float b) 
{ 
  return((a+b)/2.0); 
} 
...
float x = fAverage(123.0,456.0); // assign the average of 123 and 456 to x

For indicating that a function returns nothing, it can be defined with the type void, such as:

void beep_twice() 
{
  beep(); 
  beep(); 
} 
That's why you'll sometimes find the main function defined as void main(), rather than function main(). However, as the type function can also be used for returning nothing, both are equivalent.

Recursive functions

A function can call itself - this is named a recursive function. Recursive functions are useful for solving certain mathematical or AI problems. This is an Example:
function factorial(x)
{
  if (x <= 1) { return(1); }
  if (x >= 10) { return(0); } // number becomes too big...
  return (x*factorial(x-1));
}

Recursive functions must be used with care, and are for advanced users only. If you make a mistake, like a function that infinitely calls itself, you can easily produce a 'stack overflow' which crashes the computer. The stack size allows a recursion depth of around 10,000 nested function calls.

Overloaded functions

 LC  In lite-C or C++, functions can be overloaded, which means you can define several functions with the same name, but different arguments. The compiler knows which function to use depending on the types and numbers of arguments passed. Example:
double square(double x) 
{ 
  return(x*x); 
}
int square(int x) 
{ 
  return(x*x); 
}
...
double x = square(3.0); // calls the first square function
int i = square(3);      // calls the second square function 
 !!  When calling overloaded functions, make sure that argument types exactly match one of the defined functions. The automatic type conversion and pointer detection of the lite-C compiler does not happen here. That means you must really use the C++ & operator for pointers. When the function wants a STRING*, passing a char* won't do. Keep this in mind when passing constants to overloaded functions, which may be of a different type than you expect. Text constants like "this is a text" are of type char* (not STRING*), integer numbers like 12345 are of type long (not var), constants with decimals like 123.456 are of type double (not float), and single letter constants such as 'A' or 'B' are also of type long (not char). Make sure that overloaded functions exist for all types of arguments that can occur - or use typecasting to convert the arguments to the right type. Example:
int square(int x) 
{ 
  return(x*x); 
}
var square(var x) 
{ 
  return(x*x); 
}
...
int i = square(3); 		// calls the first square function
var a = square((var)3);   // calls the second square function 
A7.75 When no exact numeric parameter match is found however, the compiler converts numeric types for finding the next-best match among the overloaded functions. If no match is found at all, the compiler gives an error message.

Calling modifiers

 LC  In lite-C, C,or C++ the __stdcall calling modifier is used to call Win32 API functions. The called function cleans the stack. The __cdecl calling modifier is the default calling convention in C-Script, lite-C, C and C++ programs. Because the stack is cleaned up by the caller, it can do variable argument (vararg) functions, like printf(...).

Special functions

A function named main() is automatically executed at the start of the program, before the engine and video device is initialized. This allows to set variables that affect initialization (such as lvideo_mode) in the main function before the first wait() or level_load() call.

Functions ending with ..._startup are executed right after the main() function, and before the engine and video device is initialized. This way, every script can have its own startup function which initializes its variables or objects. Example:

function debug_startup() // automatically started if this script is included
{
  set(debug_panel,SHOW);
  while (1) 
  { // run forever
    debug_panel.pos_y = screen_size.y - 15;
    fps = 0.9*fps + 0.1/time_step;
    wait(1);
  }
}

 !!  Note that _startup function run after main(). Any initialization by a _startup function is only ready after the first wait()!

 LC  Functions ending with ..._event are automatically assigned to the corresponding event. An event function is started by a certain trigger - like an entity collision, a button or a mouse click, or the game start or level loading. A list of events can be found in the Predefined Variables chapter. Example:

// the following function is automatically assigned to the on_x event
function on_x_event()
{
   printf("[X] key pressed!");
}

 !!  An event function can start during execution of any engine function that allows events, like a collision instruction or a wait() instruction. It can start when you don't expect it. Therefore care must be taken that event functions don't alter global variables that other functions rely upon.

Actions are another special kind of functions. They don't have parameters and don't return a value, but will appear in the action pop-up list in WED's entity property panel. So actions can be assigned to entities through WED, and are automatically executed after level loading. Action names are limited to 20 characters. Besides that, there is no difference between functions and actions. This is our usual example for an entity action, which consists of just three instructions, and lets the entity rotate. Example:

action ent_rotate()
{
   while (1) 
   {
     my.pan += 3*time_step;
     wait(1);
   }
}

In a multiplayer environment, actions normally run only on the server; this way they influence the whole game world. All other functions, normally assigned to the user interface - that means functions attached to keystrokes or panel buttons - are run there where they were triggered, normally on a client. In a single-player environment the server and the client are the same PC, so you don't have to bother about where your functions run.

See also:

Variables, Pointers, Structs ► latest version online