Variables are named identifiers that contain a value. If you’ve ever done algebra, you’ve used a variable. Variables are a basic building block of script.
Type
Variable type describes the kind of data the variable contains, and how it will behave when interacting with logic expressions. One of the fundamental differences between C and OpenBOR Script is that variables in OpenBOR Script are weakly typed as opposed to strong typed. By weak typed, we mean the engine handles typing for you, and will assign or reassign variable types automatically.
The obvious advantage to this model is not having to worry about type, but it does mean you need to be wary of what’s going on. Take a look at this example.
int i;
i = 0;
i = i + " is a string";
In C, this would never work. You’d get a nasty typing error and the application would not compile. In OpenBOR Script, the engine reassigns i to a string type, and i is now “0 is a string”. It’s simple to work with at first, but be careful you don’t let it ruin your logic expressions.
In any case, there may be times when you want to know what type OpenBOR has assigned to a variable and act accordingly. To do so, use the typeof() function as follows.
int type = typeof(mixed <variable>);
The typeof()function will return a constant indicating the variable type.
| Type | Constant | Declaration | Notes |
|---|---|---|---|
| Float | openborconstant("VT_DECIMAL") | float x = 0.1; | Floating point decimal. In OpenBOR script you must fully qualify a float. In other words, 0.1 is a valid floating decimal, .1 is not. Floating values have a precision of seven decimals and a value range of -3.4E+38 to 3.4E+38. |
| Integer | openborconstant("VT_INTEGER") | int x = 1; | A whole numeric value. Probably the most commonly used type. Integer values can range from −2,147,483,648 to 2,147,483,647 (signed) or 0 to 4,294,967,295 (unsigned). Native integers are signed unless noted otherwise. All user defined integers are signed. Some native integer values may be limited to a specific list of constants. See the individual properties for details. |
| Null | openborconstant("VT_EMPTY") | mixed x = NULL(); | A variable that is undefined or has no value at all. |
| Pointer | openborconstant("VT_PTR") | void x = <object>; | Pointer to a memory address. Pointers are typically used to reference structured objects (ex. an entity) or collections of items. |
| String | openborconstant("VT_STR") | char x = "string"; | Any arbitrary collection of characters. This sentence is a string. |
Note that once a variable is typed to a string, it is never auto typed into anything else. You should always avoid using strings in logical expressions as they are extremely sub-optimal compared to other types (this is not an OpenBOR caveat, take this advice for any other engine or coding). Moreover, if it isn’t obvious, you can’t do any sort of math on a string at all.
There are cases where you might end up with numeric values typed as a string. To force them back to integer or float type, use the following functions:
float x = string_to_float(<string>)int x = string_to_int(<string>)
Example:
/* This number is typed as a string, so we can't do much with it. */
char string_value = "5";
/* Now we have an integer we can add, subtract, multiply, etc. */
int integer_value = string_to_int(string_value);
Scope
Scope refers to the visibility of a variable throughout a function, its parent scripts, and so on. It’s best practice to use the most narrow scope possible for a given task (this again is true for all coding, not just OpenBOR Script).
Function
Function variables are variables you define inside a given function without specifying the scope. They are unique to that function and only that function. Function vars are destroyed as soon as the function is finished. In other languages, these are sometimes called local variables, and that’s the correct terminology here too. We just call them function variables to avoid confusion with Local Vars.
function somefunction() {
int name = value;
}
Global
mixed var = setglobalvar(mixed <identifier>, mixed <value>);
getglobalvar(mixed <identifier>);
As their name implies, global vars occupy the global scope. Once defined, they are available to all functions in all scripts at all times. Global vars are even persistent through game saves, so turning off the engine is not always enough to get rid of them. This later caveat can be very powerful if used correctly, but if misunderstood can also introduce bugs into your scripts.
Tip: Avoid global vars whenever possible They are very powerful, but also easily misused and extremely difficult to debug. It is best practice to limit yourself to the scope you absolutely need for a given task, and save global vars for the jobs only they can do.
Entity
setentityvar(void <entity>, mixed <identifier>, mixed <value>)
mixed var = getentityvar(void <object>, mixed <identifier>)
Entity variables are similar to global variables. They occupy the global scope, and so are available to any function, anywhere, anytime. The difference is that entity vars include a second key to identify them as belonging to a specific entity. Entity vars remain in memory until the associated entity is killed.
function somefunction() {
void entity = getlocalvar("self");
setentityvar(entity, "birthdate", "2004-01-01");
}
Local
setlocalvar(mixed <identifier>, mixed <value>);
mixed var = getlocalvar(mixed <identifier>);
Local vars can be a little confusing to understand, but they are immensely powerful once mastered. A local variable is accessible to all functions within a given individual script instance, and remains persistent until the script is destroyed. For those familiar with object oriented programming, local vars could be considered analogous to class variables.
For example, if you declare a local var in an entity’s animation script, that variable is now available to all functions in that specific entity’s animation script. Animation scripts stay active as long as the entity does, so the local var remains persistent until the entity is killed. When the entity is killed and its animation script destroyed, the local var is destroyed along with it. This means unlike global vars, you don’t need to worry about clean up since the engine will do it for you.
Local vars are ideal for transferring information between functions in a script without using tons of parameters or global level variables. They also are perfect for temporary storage of information across multiple runs of a function(s).
main() {
/* Set local var. */
setlocalvar("test_localvar", "Hello world!");
/* Print local var to log with another function. */
print_log();
}
function print_log() {
char test;
/* Get the local var */
test = getlocalvar("test_localvar");
/* Print text to log. */
log(test);
}
Strings
As noted in types, strings are any arbitrary collection of characters.
- Maximum string length is 127 characters. Any characters exceeding the limit are truncated.
- You may use the
+operator to concatenate a string with any other variable (the result is always a string).
In addition, the following functions are included to assist in string work.
strinfirst()
char haystack = "Hello World";
char needle = "llo";
char result = strinfirst(haystack, needle);
// result = "llo World";
Accepts a string (haystack) and a search string (needle). Returns string starting from the first occurrence of substring. If substring is not found, returns -1.
strinlast()
char haystack = "Hello World";
char needle = "o";
char result = strinlast(haystack, needle);
// result = "orld";
Accepts a string (haystack) and a search string (needle). Returns string starting from the last occurrence of substring. If substring is not found, returns -1.
strleft()
char haystack = "Hello World";
char length = 3;
char result = strleft(haystack, length);
// result = "Hel";
Accepts a string and a length. Returns length characters of string starting from the left.
strright()
char haystack = "Hello World";
char length = 3;
char result = strright(haystack, length);
// result = "rld";
Accepts a string and a length. Returns length characters of string starting from the right.
strlength()
char haystack = "Hello World";
int result = strlength(haystack);
// result = 11;
Accepts a string and returns the number of characters.
strwidth()
char haystack = "Hello World";
int font = <font index>;
int result = strwidth(haystack, font);
Accepts a string and a font index. Returns the total width in pixels. For example, if the font’s character width is 10 pixels and the string has 11 characters, the width returned is 110 pixels.
Arrays
OpenBOR supports indexed and string-keyed arrays. In actuality, an OpenBOR array is a defined object occupying the global space, so you will need to treat it as such for purposes of initializing, referencing, and cleanup (see free()).
While they may behave similarly in script, under the hood the two types of arrays behave very differently, and you should tailor your data structures and scripts accordingly.
Indexed
Indexed arrays are essentially C arrays, meaning they are zero indexed, concurrent groups of elements with a contiguous space allocated in the hardware platform’s memory. This makes indexed arrays blazing fast and memory efficient for accessing and modifying existing elements. However, arrays cannot be resized without reallocating a whole new array and copying existing elements over, so it is important to figure out how many elements you will need and avoid inserts or deletes during gameplay.
String-Keyed
Sometimes called literal arrays. String-keyed arrays are not actually arrays all, but a double linked list with hash map acceleration. They are thus not less memory efficient and not as fast as indexed arrays, but are highly flexible and can shrink or grow without penalty.
Switching Types
You do not actually switch array types. When you allocate an array, OpenBOR creates the indexed array first. If you then add() or set() with a string key, OpenBOR builds the linked list along-side the original indexed array. This is why when planning to use string keys, you should allocate 0.
Warning: It is technically possible to treat an array as both index and string-keyed – but you should never do this. Treat an array as either string-keyed or indexed, not both. Mixing them can produce confusing size results, broken assumptions, erroneous output, and hard-to-track bugs.
Tips
- Don’t forget indexed keys are 0 indexed.
- You can nest array pointers to create multidimensional data structures. Nested arrays can safely have different key types than their parents.
Functions
add()
void key = 0; // Int or string.
void value = 0; // Any value type.
add(void array_pointer, index, value);
- Indexed – Inserts value at
key. Key must be from0throughsize(array_pointer). Resizes the array by one and shifts all indexes fromkeyupward. - String-Keyed – Creates a named entry for
keywithvalue, or updatesvalueifkeyalready exists. Same behavior asset().
array()
void array_pointer = array(int size);
Allocates an array with int size initial indexed elements and returns the array pointer.
- Indexed – Allocate the number of elements you expect to use.
- String-Keyed – Use
array(0), because string-keyed entries are stored separately from indexed block.
delete()
void index = 0; // Int or string.
delete(void array_pointer, index);
- Indexed – Deletes the target element, resizes the array, and shifts all higher indexes down.
- String-Keyed – Removes the named entry for
key.
Avoid using delete() during active gameplay. For indexed arrays, delete() causes resizing and shifting. For string-keyed arrays, it changes the named list structure. Both create avoidable memory churn.
Tip: You can set NULL() to clear a value without churn.
get()
void key = 0; // Int or string.
void value = get(void array_pointer, key); // Can return any type.
Returns value at key, or NULL() if the entry does not exist.
- String-Keyed – Retrieves by string key.
- Indexed – Retrieves by integer index.
isArray()
int value = isarray(void object_pointer);
Version 4.0+ only. Returns 1 if pointer is an array. 0 otherwise.
isFirst()
int value = isfirst(void array_pointer);
String-keyed only. Returns 1 if the internal cursor is at first node. 0 otherwise.
islast()
int value = islast(void array_pointer);
String-Keyed only.Returns 1 if the internal cursor is at last node. 0 otherwise.
key()
void value = key(void array_pointer); // String.
String-Keyed only. Returns the string key at current cursor position.
next()
int success = next(void array_pointer);
String-Keyed only. Attempts to move cursor to next node in order. Returns 1 on success, 0 if already at last node.
previous()
int success = previous(void array_pointer);
String-keyed only. Attempts to move cursor to previous node in order. Returns 1 on success, 0 if already at first node.
reset()
int success = reset(void array_pointer);
String-Keyed only. Attempts to move cursor to first node in order. Returns 1 on success, 0 if array has no nodes.
set()
void key = 0; // Int or string.
void value = 0; // Any type.
set(void array_pointer, index, value);
- Indexed – Set target
keyto value. Ifkeydoes not exist, resizes array to accommodatekeybefore setting value. - String-Keyed – Creates a named entry for
keywithvalue, or updatesvalueifkeyalready exists. Same behavior asadd().
For indexed arrays, prefer set() over add() when you already know the slot you want to write.
size()
int array_size = size(void array_pointer);
- Indexed – Returns current number of elements in the array.
- String-Keyed – Returns number of named entries.
Caution: If you mistakenly allocate >0 entries to an array you intend for string keys, size() will return that allocated number of elements until you add or set a string-keyed node. Always allocate 0 for string-keyed arrays, and never mix key types.
value()
void value = value(void array_pointer); // Any type.
String-Keyed only.Returns value at current cursor position.
Cleanup
Deleting Variables
To delete any variable, assign it a NULL() value. OpenBOR will destroy the variable and free the memory it used.
setglobalvar("some_name", NULL())
You can also remove an string-keyed array element by setting its value to NULL(). The node will remain allocated until the array is freed, but OpenBOR will treat it as removed for purposes of size() and iteration.
Indexed array elements can cleared, but not removed with NULL().
Freeing Objects
If the variable was pointing to an object of some type (i.e. a pointer), you may first want to delete the object it is pointing to. Otherwise, the object continues to exist in memory with no variable to reference it. To do this, use the free() function. In the example below, we load a sprite into memory, then delete the sprite, and afterward delete the variable that pointed to it.
void load_sprite() {
/* Load sprite into memory and get its pointer so we can work with it. */
void sprite = loadsprite("data/some_folder/some_sprite.png");
/* Store the pointer into a localvar so other functions can use it. */
setlocalvar("sprite_name", sprite);
}
void free_sprite() {
/* Get the sprite pointer so we can work with it. */
void sprite = getlocalvar("sprite_name");
/* Remove the sprite from memory. */
free(sprite);
/* Delete the variable that held sprite pointer. */
setlocalvar("sprite_name", NULL());
}
Limitations
Variables In Use
There is no hard code limit for the number of variables you may have in use. It’s still a good idea to only allocate what you need and clean up what you don’t, since every active variable consumes memory.
Variable Length
Prior to release 4539, variables in OpenBOR Script were limited to 63 characters in length. Note that’s length, not value – so it really only matters for strings. Should the length exceed 63, the remaining characters are truncated and lost. As of 4539+ the limit is now 127.