Functions#
Declaring Functions#
Functions are blocks of code, which can be ‘called’ from multiple locations. A
function, when called, can take any number of parameters
, which are values
set as part of the call which become local variables inside the function.
Additionally, a function can return
a single value back to the calling code.
All runnable code is inside functions; each script has
a special void run()
function, whose max number of parameters
varys depending
on the script’s type. The script begins execution by calling this function,
using values set in the editor for the parameter
values.
int double(int val)
{
return val * 2;
}
global script Active
{
void run()
{
Trace(double(5)); // outputs '10.0000'
Trace(double(25)); // outputs '50.0000'
Trace(double(0.4)); // outputs '0.8000'
}
}
In the above example, the double
function is defined, taking a single int
parameter, and returning an int
value. The global script Active
then calls
the internal function Trace 3 times, each time
giving it the return value of a call to the double
function as its parameter
(each time with a different number given to the double
function).
Warning
Unless a function has a return type of void
, it must return a value.
This was not required in previous versions, which lead to whatever random value
was leftover in the d2
register being returned instead, causing some
script bugs.
int triple(int val)
{
int newval = val * 3;
<error>/* Error S102: Function 'triple' is not void, and must return a value! */</error>
}
Remote Declaration#
You can declare functions in remote scopes by using an “identifier list” instead of a single “identifier”- both of the following examples are identical.
namespace foo
{
void bar()
{
Trace(5);
}
}
void foo::bar()
{
Trace(5);
}
Optional Parameters#
You can give function parameters a compile-time constant initializer, to make them ‘optional’. If a function is called without some of its optional parameters, they will automatically use the default value provided instead. No non-optional parameters may appear after an optional parameter.
void foo(int x = 25)
{
Trace(x * 2);
}
foo(); // outputs '50'
foo(10); // outputs '20'
#include "std.zh"
/*
Finds the closest enemy to the player.
Enemies farther away than 'max_range' are ignored.
If no enemies are close enough, returns 'NULL'.
*/
npc find_closest_npc(int max_range = MAX_INT)
{
npc ret;
int min = max_range;
for(n : Screen->NPCs)
{
int dist = Distance(Hero->X, Hero->Y, n->X, n->Y);
if(dist <= min)
{
ret = n;
min = dist;
}
}
return ret;
}
Can’t put a mandatory parameter after an optional parameter!
void example(<error>int x = 5, int y</error>)
{
// syntax error, unexpected RPAREN, expecting ASSIGN
}
Template Parameters#
If you want a function to be able to take multiple types, you can make use
of template parameters, to allow a ‘variable type’ parameter. To do so, you
first declare the names of the template types you wish to use, as a
comma-delimited list inside <>
before the function parameter list.
Then, you can use these types both in the parameters and in the return type-
though, if you use one in the return type, you need to use the same one in at
least 1 parameter (so the compiler can understand what it is).
T double<T>(T x)
{
return x * 2;
}
auto v1 = double(2);
auto v2 = double(2L);
// v1 is an 'int' valued '4'
// v2 is a 'long' valued '4L'
This is used by several internal functions, such as Max, Min, ArrayPopBack, and more, to allow them to either return a type based on the input, appropriately take any type of value, or both.
Variadic Parameters#
Instead of having optional parameters, which allow you to pass less than the total number of paramters, you can instead have variadic parameters, which allow you to pass MORE than the total number of parameters.
To allow variadic parameters, simply declare a final parameter to your function,
using an array type, preceded by ...
. All the extra parameters that are passed
will be placed into this array, in order.
int sum(...int[] args)
{
int s = 0;
for(v : args)
s += v;
return s;
}
int prod(...int[] args)
{
int p = 1;
for(v : args)
p *= v;
return p;
}
Trace(sum(1,2,3,4)); // outputs '10.0000'
Trace(sum(5,2,5,4)); // outputs '16.0000'
Trace(sum(1.2,2.2,3.2,4.2)); // outputs '10.8000'
Trace(prod(2,3,3,4)); // outputs '72.0000'
int SumDropLowest(...int[] args)
{
if(SizeOfArray(args) < 2) // if < 2 values, will always get 0
return 0;
int sum = 0;
int lowest = MAX_INT;
for(val : args) // Just treat it like any normal array
{
sum += val;
if(val < lowest)
lowest = val;
}
return sum - lowest; // drop the lowest from the sum
}
void RollDNDCharacterStats()
{
int stats[0];
loop(0=..6) // repeat for all 6 stats
{
// Roll 4d6, and drop the lowest
int v = SumDropLowest(
RandGen->Rand(1,6),
RandGen->Rand(1,6),
RandGen->Rand(1,6),
RandGen->Rand(1,6)
);
ArrayPushBack(stats, v);
}
printf("Rolled stats: %ad\n", stats);
// Example possible outputs:
// Rolled stats: { 13, 13, 14, 14, 14, 16 }
// Rolled stats: { 13, 8, 9, 13, 13, 10 }
// Rolled stats: { 15, 5, 12, 11, 9, 9 }
}
Prototype Functions#
You may declare a ‘function prototype’, as a function with no body. This function then may be declared elsewhere, including in any other file, and will overwrite the prototype rather than causing a compile error.
Calling the function will return a default value if it was never fully defined.
This is usually 0
/NULL
, but can be set manually as well.
// will return false always unless declared elsewhere
bool is_stealthy();
// will return 1 always unless declared elsewhere
int damage_multiplier() : default 1;
This could be useful if these functions would be defined in another script that you don’t know if will be included or not- you can effectively use the default return to signify that script not existing, and then any script can define that function to work with your script and integrate.
In the example above, I could allow a custom enemy not to see the player
if is_stealthy
returns true
- but, unless a script exists
that implements some sort of stealth mechanic, and implements
the bool is_stealthy()
function to return true under some condition.
You could also use these the other way- have some function prototype declared that is part of your script, meant for sharing with others for use in their various quests- and then each of them can define a body for the function if they so desire, specifically tailored to their individual quest. (Tango.zh did something similar to this with its ‘screen freeze’ functions, although it required you edit the actual tango file to edit the function, as that was written before prototype functions were implemented)