- For a directory of commonly used functions, see Function Index. For a quick reference guide of how the scripting language works, see HSL for programmers.
Functions are discrete sections of HSL code, and, along with class methods, the basic building blocks of HeroScript. Scripts are composed of either functions or methods. Within the functions and methods are HSL commands, or calls to other functions and methods. For a complete list of possible commands, see the Commands Syntax Reference.
It is important to keep in mind that not all functions are available to all scripts. The functions that can be called, will be dependent on where the script is running. Scripts can run on one of several different servers, or they can run on the client on an individual user's computer.
Creating a function
The typical format of a function is:
- An optional modifier keyword
- A function name
- A signature, involving a list of parameter arguments and an optional return value
- A series of commands within the function
- A closing dot
For example, this private function will take a noderef and an integer, and return a string.
function DoStuff(wand as noderef, spell as integer) as string waveWand(wand) return "The wand was waved! Spell " + spell + " was cast." .
Then when it is called, the syntax would be:
output_string = DoStuff(wandNode,234) println(output_string)
or the function could even be embedded in another function call:
- See also: Signature
The basic format of the declaration structure is:
[modifier(s)] function <name>([<parameters>]) as <returnType> // section of code goes here .
The first line of the structure declares the function's:
- Modifier (private/public/shared/remote/untrusted)
- Signature: what parameters it requires to be passed in (if any), and what it will return to a calling script (if anything).
- Main page: Function/method modifiers
Functions may have prepended keyword modifiers, which indicate where the functions exist, and what can call them.
When a script is compiled, the compiler checks all of the called functions in the compiled script, to ensure that they exist, no matter which other files that they may be in. The compiler also checks that the argument list (the signature) is correct. For public functions, this is simple because the location of the called function is coded directly into the function call. However, sometimes the location is not specified at compile time, since a
scriptref variable is used instead. In those cases, the name of the script does not have to be specified, but the function that is being called must be a shared function. The compiler maintains a list of signatures of all known shared functions.
- (none): If no other modifier is used, this is the default, and it is known as a private function. A private function is only available to the other functions that are within the same script.
- Public: Accessible by any other function on the same server
- Shared: Similar to public, but has an expected signature which matches the other functions of the same name
Functions may have the following additional modifiers:
- Remote: Specifies a function which may be called via Remote Calls . It could go from one server to another, or from a server to the client. A Remote function may not be called in any other manner.
- Untrusted: Specifies a function which may be remote called from a client script. Because clients may be hacked, the Untrusted keyword reminds us that input must be carefully verified and may not be trusted.
public function SwordAttack(sword_node as noderef, target_node as noderef)
shared function DealDamage(player_node as noderef, damage as integer)
Every function must be declared with a
<name>. By convention, names should be alpha-numeric with no special characters, including underscores. Also by convention, function names should follow CamelCase formatting, which has each word uppercased.
A script can contain only one function of a given name, regardless of its type modifier.
A function's signature is the portion of the declaration line that follows the function's name. So in the case of the shared IsHotSpotItemChecked() function:
shared function IsHotSpotItemChecked(item as NodeRef of Class GUIControl) as Boolean // do stuff .
The signature part is:
(item as NodeRef of Class GUIControl) as Boolean
Specifically, the signature incorporates two pieces: the function's parameters, and its return type.
Other (non-signature) elements on the line include the function's name, and its Modifier (in this case, the keyword "Shared").
Calling a function
The syntax for calling a function, depends on its location relative to the calling script. There are four different possible locations for the function that is being called:
- It could be in the same script as the command that is calling it.
- It could be a built-in (hard-coded) function.
- For the above two options, a function can be invoked simply by using its name:
- It could be in a different script, as either a public or a shared function.
- In this case, the script name must be prepended to the function name. For example, suppose that in script
a.hsl, exists a
GetString()function. In script
ReadText()function. If a line in script B is calling
GetString(), the syntax would be:
- It could be in a script which is on a completely different server
- If the function is on a remote server, it must be accessed via a "Remote Function Call", which involves initializing a data structure with detailed information explaining where it is located, and what to do if it can't be found. For more information, please see the section on Remote Call.
Checking if a function exists
When calling a function in another script, it is essential to know the name of the function and script ahead of time -- they have to be hardcoded in.
There is one exception to this, which is a "shared" function. In these cases, a special built-in function called hasFunction can be used to determine whether or not a particular function exists in a given script. For example, if something in the game is about to cause damage to a character, it might be desired to first check to see if the character already has a damage handler function in the script on the character, even if the calling script does not initially know the name of that script. This could be done with the following code:
if hasFunction(playerScript, "DamageHandler") // the function exists .
Function Call examples
Assume that there is a script called FunctionLibrary which contains functions aFunc, bFunc, and cFunc:
// FunctionLibrary.hsl function aFunc() stuff . public function bFunc() stuff . shared function cFunc() stuff .
And then another script
WorkScript wants to call functions in
// WorkScript.hsl function main() scriptVariable as scriptref FunctionLibrary:aFunc() // Will not work, since it's private to the library FunctionLibrary:bFunc() // will work scriptVariable=FunctionLibrary scriptVariable:bFunc() // will not work (see below for why) scriptVariable:cFunc() // will work .
In order for the above script to be able to use
bFunc function would have to be declared as a shared function, instead of a public one. This would also be necessary for the function to be findable via
There is no shortcut way to declare all functions within a given script as public or shared. If it is desired to do this, simply prepend the appropriate word to every function in the script.
In the following case, the "takeDamage" function in the character's script would be a shared function.
function PlayerSteppedInLava(character as noderef) if hasFunction(character.script,"takeDamage") // Might the character have some defensive ability // we need to be aware of? character.script:takeDamage(character,50) // Here, let the character's script deal with the damage else character.life = character.life - 50 // Ow! . .
In the above example, if takeDamage were a public function instead of a shared function, it could not be called in the above way, or even found.
hasFunction can only be used on shared functions, and not on public functions.
Many functions return a value such as a string, integer, or noderef. This is declared in the function signature line by adding the optional word
as and the datatype which will be returned. Within the function, a RETURN statement will specify the data to be returned.
Typical syntax might be such things as:
function CreateNodeFromClass(classname as string) as noderef CreatedNode as noderef (stuff) return CreatedNode . function FindString(hay as string, needle as string) as string foundString as string (stuff) return foundString . function DoThings() n as noderef n = CreateNodeFromClass("container") pin as string pin = FindString(haystack,needle) .
If a function declares that it returns a value but fails to do so, a Script Error is produced at runtime.
Functions can also be used as part of an expression. For example:
OutputMsg("The number is "+itos(5)) response_s = "Hi "+FindName(friendList,"spouse")+"!"
(Note: This is easier with HSL functions than built-in functions -- some built-in functions cannot yet be embedded)
Functions have a signature which is made up of parameters and their types.
function <name>(<arg> <argdef> <type>, <arg> <argdef> <type>, ...) [as <type>] .
A function's parameters are a comma-delimited list of variables that the function requires from a call. In a signature, each parameter is defined exactly like any other variable is defined, and can be any of the allowed variable types. Take this example:
function DoStuff(num as Integer, text copies String, state references Boolean) // .
Whenever a function is called, each parameter in its signature defines a variable with the specified name and type. So DoStuff() will define the integer variable "num", the string variable "text", and the boolean variable "state". Additionally, these variables will be set automatically, using the values that are passed in by the call.
Notice that each of the parameters in our example has a different keyword between the variable name and the type. Each causes unique behavior with respect to those variables.
- One of the arguments to the function. Also called a parameter.
- This is a linking word in the parameter list which must be one of the following:
- the parameter is just passed in, but its value cannot be changed within the function
- a copy is made of the variable that the function can change, but it doesn't change the value that was originally passed in.
- passes the variable as a pointer, so the value can be changed. This means that, within the function, you will be operating the data that is passed in directly, not a copy of that data. This is particularly useful when you have a function that needs to operate on (or return) large lists or classes.
- One of the possible Data Types, such as
string, noderef, list of noderef, integer, enum <enum_list>, etc.
- (optional) The return value of the function. If not specified, then the function does not return a value.
Only one function of a particular name can exist in a single script. However, there can be functions of the same name that exist in different scripts. Functions of the same name do not have to have the same signature unless they are Shared functions.
For more information on the syntax to call a particular script, please see "Calling a Function" above.
Bear in mind that variables of type NodeRef are special. While the above keywords still apply to them as it relates to their reference, the node itself is still subject to permanent change as it relates to its member fields. In other words, while a parameter like "pc as NodeRef of Class _playerCharacter" cannot be changed to reference another node, the fields on the node itself can still be changed, and those changes are permanent.
An additional hidden parameter also exists in many cases, which defines and sets the variable called "me". The "me" variable's type will always be a NodeRef of unspecified Class. For details on which this variable is set, see Me node.
function attack( target as noderef, maneuv as enum maneuver )
In this example, the parameter names are
maneuv. Inside the function they are accessed by those names.
When calling the attack() function, values for the parameters (also called arguments) must be given. For example, if the
attack() function resides in the same script as the function that is calling it, the syntax might be:
attack( creature, SpinKick )
varScript:attack( creature, SpinKick )
Normally parameters are read-only, which means that they cannot be set like other variables. It is possible, however, to use the parameter like a variable and then in some cases use it to modify something else.
If it is desired to have the ability to modify a parameter within a function, it is necessary to tell the compiler whether to make the argument simply a local copy, or whether to change the argument passed when the function was called. This is done by replacing
as with either
As an example, with the
as argdef in the above example, the
target parameter is a node reference which can be used to set fields on the
creature node. However, if it was desired to allow the
attack function to change which node that
target pointed to, then the
references definition should have been used instead.
function another( target references noderef, maneuv copies enum maneuver) target = someOtherNode maneuv = LowThrust . function foo(str as string, nn as noderef of class item) . . . . function scribble(str references string) as list of string . . . .
Functions may return a single variable to the calling script via the RETURN command, although this value may be a complex type, such as a class. This is done by declaring the return type in the function's signature, as shown here:
function ReturnValue() as Integer i as Integer = 5 return i .
However, functions are not required to return anything, in which case no return type is included in the signature, like so:
function ReturnNoValue() return .
For the purposes of most HSL scripters, all functions can be treated pretty much the same, however there are some subtle category differences which are defined here:
- HSL functions - The normal building blocks of a script, these are functions which can be written by any scripter, called by name from any other script within their arena (client or server). They can be modified by anyone with HSL access, via the script editor. There are client HSL functions and server HSL functions.
- Server functions
- These are functions which run on a server process, instead of the client. Server functions implement the game rules.
Client and server functions are mutually exclusive, and cannot directly call each other. The only way to send messages back and forth is by building special packets that are sent via command channels. For more information, see the Command Handler and Remote call.
When creating a new script, it is necessary to know whether it's going to be a client script or a server script in order to ensure that it is working properly with the other scripts in that database; however, there is no difference in syntax between writing client scripts or server scripts.
It is possible to have an identical function name on both client and server, and even possible to have a function which has a different signature between client and server, but in general, any external function should operate identically to its client or server counterpart, if it is on both.
There are some pre-created functions for common tasks.:
- Built-in Functions - Sometimes also referred to as "exposed", "system', or "external" functions, these are functions that are written in the lower-level C++ code, with a function's signature which makes them accessible (exposed) to the regular HSL functions. They can be called by any script in their arena (client or server), but can only be changed by an in-house coder, and not via the script editor. For a list, please see the section on Built-in_functions. A subset of exposed/external functions are referred to as "Built-in" functions, which means that they are common to both client and server. The rest of the functions may be specific to client or server -- please see the specific function to learn where it is accessible from.
- External functions (also called Exposed functions) - These are similar to built-in functions, however instead of being accessible from both client and server, they are specific to only one or the other. It is even possible for there to be two functions of the same name -- one on the client, and one on the server, with completely different signatures. In general though, if a function has the same name on both client and server, it should probably work identically.
For example, there exists a special script called
ExternalFunctions which uses the
external keyword to classify these external functions:
external function println(text as string)
The definitions in the script are a reference to the compiler, to communicate the signature of each of the external (aka exposed) functions.
external keyword is for use in this script only, and cannot be used in other scripts.
- Main page: HeroScript Extension Plugin
HeroEngine supports a plug-in architecture on both client and server, that allows you to add new functions to HSL that exercise the capabilities of the given plugin.
Go through all the items in a backpack and see how many are flasks.
function flaskcounter(backpack as noderef) as integer flaskcount as integer x as list of association X = QueryAssociation(backpack, "in" , 0) // X is all the things with backpack "in" association foreach item in x loop // Check each node in X, setting ITEM in turn tgt as noderef tgt = item.target // Set TGT to a target node of the X association where tgt is exactly container // Check its class if (item.target.name = "flask") flaskcount = (flaskcount + 1) . . . OutputMsg("Flasks so far: " + flaskcount) .
Difference between functions and methods
On first glance, functions and methods are both blocks of HSL code. The main differences between them are:
Which form you use, depends entirely on your own design. You can use either functions, or methods, or a combination of both, depending on what works best with your game.
Functions are always prefixed with the keyword "function"
In order to use a node in a function, you would have to pass a NodeRef in the argument list when the function was called.
The location of a function (which script it was in), might be determined by the script property of a node, or via some other creative means.
function Add(l as Integer, r as Integer) as Integer return l + r .
Function scripts can include only functions. There is no strict convention on how function scripts are named.
If an attempt is made to add a method to a function script (meaning one that is not named as a ClassMethods script), the script will not compile.
- Main page: Class methods
Methods are always prefixed with the keyword "method"
When a method is called, the Me object will be set to the node on which the method was called:
method exampleMethod() me.examplefield = 5 // me is a valid nodeRef and points to the node on which this method was called .
Class method scripts
Methods are always stored in a script, which is associated with a class. Each class may have a method script, which is named based on the class. For example, if a node has three classes, AA, BB, CC. For the class "AA", the expected script name would be AAClassMethods. There might or not be class method scripts for BB and CC.
A class method script could include functions. However, a function script can not include methods (it wouldn't compile).
There is no concern about duplication of methods between class method scripts on a node, as it is not possible to have duplicated methods in the first place. As soon as an attempt is made to glom a class onto a node, where the class's classMethods script has duplicated methods to what already exist on the node, an error will be generated.
For an example of the differences between functions and methods: if a GUI Control is being used, and a check is being made to see if a GUI Event has been triggered. This might result in a call to a method, or to a function, depending on what is available.
If the GUI Control node has any classes with associated class methods scripts, then HeroEngine will check each script to see if it needs to call a relevant method. If it cannot find a method, or the Control does not have any methods scripts, HeroEngine will then call the relevant function, looking for it in the script referenced by the Script Property of the GUI Control node.