GSC Basics - AkbarHashimi/BO2-Plutonium-Modding-Guide GitHub Wiki
- Variables
- Entities
- Operators
- Arrays
- Comments
- Structs
- Execution Flow
- Functions
- Threads
- self
- notifies, waittills, endOns
- Output
Click To Expand
Variables are basically a way to store data. Data usually comes in different types: - int (integer or whole number) ex: 45 - float/double (decimal number) ex: 2.7 - bool (boolean or true or false) ex: true - string (text) ex: "Hello" - undefined (special to GSC)
GSC is loosely-typed, meaning when you create a new variable (also know as declaring) you do not need to state its type. All you do give the variable a name and a value Here is an example:
words_variable = "hello";
Note: You can always change the data type of a variable:
strText = "Hello";
iPrintLnBold(strText);
strText = 1;
iPrintLnBold(strText);
Output:
Hello
1
Special Case: Undefined Type:
In GSC, if a variable (or entity) does not exist or is not initialized (contains data in it), the data type of that variable is undefined. This is usually used to prevent errors and crashes by checking if a variable exists or does not contain data anymore before attempting to use that variable.
Some types are able to be used together:
You can mix integers and strings, it will treat the integer as if it was a string: (this example was ripped from zeroy wiki)
// set the variable to var_int concatenated as a string onto the end of var_string
var_compound_string = var_string + var_int;
// the == operator signifies the value of var_compound_string must be the same as "test_string7"
if ( var_compound_string == "test_string7" )
return true;
Note: Variables can be global (which can be used in all threads without needing to be called) by using the level
:
level.var_int = 100;
this created a variable var_int
that is globally accessible as its owned by the level.
Note: Variables can be assigned to entities:
entity.variable = data
Example:
player.health = 100;
Click to Expand
These are objects that are used in maps, and can be referenced in the script. Entities include players, dropped guns, objectives, script_models, etc. They can be referenced using their their targetname or classname that has been set within the map.
Click to Expand
There are many ways to get a reference to an entity.
- get entity by number
entity = GetEntByNum(entnum)
Here is the function GetEntByNum()
. This function returns an entity and accepts an entity number as a parameter. The code i showed above is where an entity is stored in the variable entity
by using the function GetEntByNum()
.
- get entity using key value pair
Every entity has a key-value pair used to reference that entity.
To get an entity you use the getent()
function.
Pattern:
getent("value as a string", "key as a string")
Example:
getent("elevator","targetname")
where elevator
is value, targetname
is key.
If there are multiple objects of the same name:
getentarray("value", "key")
The key's are "targetname", "classname", "target", or "script_noteworthy" depending on the entity.
Click to Expand
Entities are structs. Some of the member variables include the key-value pairs discussed above. Remember that you can always test for whether a given key is present on an entity with the isDefined function.
code example:
ent.origin = (100, 0, 0);
if ( !isDefined( enemy.script_noteworthy ) )
{
enemy.ignoreme = false;
}
else
{
enemy.ignoreme = enemy.script_noteworthy;
}
Click to Expand
A large quantity of the built-in script functions require an entity to act upon, resulting in the following syntax:
level.player playsoundtoplayer( "grenade_nearby_warning" );
Here the soundalias "grenade_nearby_warning" will be played emanating from the player. Of course we can still pass entities to functions as input parameters if we need to:
level.player playsoundtoplayer( "grenade_nearby_warning", teammate );
This time the "grenade_nearby_warning" is heard only by the entity named teammate, but still emanating from the player because that is the entity that the function was called on.
A large quantity of the built-in script functions require an entity to act upon, resulting in the following syntax:
level.player playsoundtoplayer( "grenade_nearby_warning" );
Here the soundalias "grenade_nearby_warning" will be played emanating from the player. Of course we can still pass entities to functions as input parameters if we need to:
level.player playsoundtoplayer( "grenade_nearby_warning", teammate );
This time the "grenade_nearby_warning" is heard only by the entity named teammate, but still emanating from the player because that is the entity that the function was called on.
Click to Expand
Finally, one of the most common methods of using an entity is to create a thread which runs on it. Much like calling a function on an entity, you simply precede the thread declaration with the entity variable:
elevator thread rise();
Finally, one of the most common methods of using an entity is to create a thread which runs on it. Much like calling a function on an entity, you simply precede the thread declaration with the entity variable:
elevator thread rise();
Click to Expand
Operators are symbols used to manipulate (do an operation on) data.
+
addition
-
subtraction
*
multiplication
/
division
%
modulus
<<
shift left
>>
shift right
<<=
Shift left with assignment
>>=
shift right with assignment
|
bitwise or
&
bitwise and
^
bitwise xor
~
bitwise not
|=
bitwise or with assignment
^=
bitwise xor with assignment
&=
bitwise and with assignment
Assignment operators assign a value, for example: var_int = 5;
, here we assigned the value of 5
to the variable called var_int
.
=
assignment operator
++
post increment
--
post decrement
+=
addition with assignment
-=
subtraction with assignment
*=
multiplication with assignment
/=
division with assignment
%=
modulus with assignment
<<=
Shift left with assignment
>>=
shift right with assignment
|=
bitwise or with assignment
^=
bitwise xor with assignment
&=
bitwise and with assignment
Used for true/false statements. Example if var_int == 5 iPrintLnBold("var_int is equal to 5")
this tests if var_int
is equal to 5
and if that is true, then we display on the screen var_int is equal to 5
.
||
Boolean or
&&
Boolean and
==
evaluate equality
===
super equality. True only if both sides are same type and same value.
!=
evaluate inequality.
!==
super inequality. True only if both sides are different types or same type and different values.
<
less than
>
greater than
<=
less than or equal to
>=
greater than or equal to
!
Boolean not
Click to Expand
Concept:
Arrays are a way to store data like variables, but can store more than 1 value at a time. Arrays are like an ordered list with positions (index) and values (Elements). In GSC, array can expand its capacity (the amount of data stored).
Array Example:
Lets call this array NumbersArray:
Index(Position) | Element(Data Value) |
---|---|
0 | 69 |
1 | 42 |
2 | 34 |
Arrays start with 0 as their starting position, not 1. So the first index is always 0. This is common for most scripting/programming languages. Another thing is that this array contains 3 elements: 69, 42, and 34.
Code:
NumbersArray = [];
NumbersArray[0] = 69;
NumbersArray[1] = 42;
NumbersArray[2] = 34;
The first line is creating an empty array. The second line creates a new element (69) at the index 0. The third and fourth line do a similar thing, which is add a new element at a new position(index).
There is another way to do this same thing:
Code:
NumbersArray = [];
NumbersArray[NumbersArray.size] = 69;
NumbersArray[NumbersArray.size] = 42;
NumbersArray[NumbersArray.size] = 34;
The first line of code is the same. The second line is more confusing.
the code NumbersArray.size
means the size of the array called "NumbersArray". During the second line, NumbersArray.size
is equal to zero, because the array was empty before this line of code was executed. This replaces the need to type NumbersArray[0] = 69;
. On the third line, NumbersArray.size
has a value of 1. The last line NumbersArray.size
has a value of 2.
String indexed arrays
In GSC, arrays can also be indexed with strings. Here is an example:
Let us call this array AlphabetArray.
index | element |
---|---|
"a" | 4.25 |
"b" | 16.25 |
"c" | 21.25 |
Code:
AlphabetArray = [];
AlphabetArray["a"] = 4.25;
AlphabetArray["b"] = 16.25;
AlphabetArray["c"] = 21.25;
here the index are strings: "a", "b", "c". If you find that an array is indexed by strings but you still want to use a numbered index, use the function getArrayKeys(). getArrayKeys() will take an array that is indexed by strings as input, then will return or output an array, this array will have all the strings that were used as an index. Here is a code example:
Code:
AlphabetArray= [];
AlphabetArray["a"] = 4.25;
AlphabetArray["b"] = 16.25;
AlphabetArray["c"] = 21.25;
stringIndexArray = getArrayKeys(AlphabetArray); // get an array of the strings used to index
for (index = 0; index < AlphabetArray.size; index++)
{
iPrintLnBold( AlphabetArray[ stringIndexArray[index] ] );
}
Output:
16.25
4.25
21.25
16.25
4.25
21.25
// this goes on forever
In our code we first defined AlphabetArray
and filled it with values. Next we used the function getArrayKeys()
on AlphabetArray
. This gave us an array that looks like this:
index | element |
---|---|
0 | "c" |
1 | "b" |
2 | "a" |
Note: The strings used to index like "a"
and "b"
are ordered in reverse-alphabetical order in this array.
This tells you that when using getArrayKeys(), the array it returns will be sorted in reverse-alphabetical order.
stringIndexArray = getArrayKeys(AlphabetArray);
On this line we took the array above and stored it in the array called stringIndexArray
.
Lastly we did a for-loop to allow us to go through both arrays ( stringIndexArray
and AlphabetArray
).
If this part seems confusing to you, read the for-loop section in Execution Flow.
Here is a chart for getArrayKeys() and how it determines what order the array of keys ("strings") will be:
priority (smaller/top means appears first) | string data |
---|---|
1 | Symbols: Space to "/" (Reverse-Order) (Ascii decimal values: 32 to 47) |
2 | Symbols: "{" to "~" (Reverse-Order) (Ascii decimal values: 123 to 126) |
3 | Symbols: "[" to "`" (Reverse-Order) (Ascii decimal values: 91 to 96) |
4 | Symbols: ":" to "@" (Reverse-Order) (Ascii decimal values: 58 to 64) |
5 | Capital Letters (Reverse-Alphabetical Order) |
6 | Numbers 0-9 (Reverse Order / Highest number first) |
7 | Lower Case Letters (Reverse-Alphabetical Order) |
It is always helpful to refer to an ascii-table. The ASCII table has an incorrect order but is helpful for finding characters in a certain group (such as alphabetical characters or numbers 0-9) and what priority they will be. For example: "a" has an ascii decimal value of 97, "z" has an ascii decimal value of 122. Since getArrayKeys() operates in reverse order, "z" will be first and "a" second.
Note: You can mix types of data stored in an array:
myarray = [];
myarray[myarray.size] = 1;
myarray[myarray.size] = false;
myarray[myarray.size] = 3;
myarray[myarray.size] = "scribblenauts";
Click to Expand
Comments are a way to type something without having it checked for errors or be compiled. It is meant to leave notes for yourself and others. It can also be used to temporarily disable lines of code without the need to delete those lines of code and rewrite them.
// This is a single line comment
/*
This is a multi-line comment
pretty sweet, right?
*/
Click to Expand
A struct is a way to store multiple pieces of data into one variable. Here is a code example:
Code:
student = spawnStruct(); //initializes struct
student.name = "Joe Clown";
student.id = 1;
iPrintLnBold(student.name);
iPrintLnBold(student.id);
Output:
Joe Clown
1
In the first line of code, we made student
become a struct with the function spawnStruct()
. Next we created a data member (basically another variable contained within student
called name
. We use the "." to tell the compiler that name
is a data member of the struct student
. Next line creates a data member called id
. The next two lines print to the screen student.name
and student.id
.
Note: Structs can have arrays as data members
Code:
student = spawnStruct();
student.nameArray = [];
student.idArray = [];
student.nameArray[0] = "bob";
student.nameArray[1] = "jill";
student.nameArray[2] = "roxanne";
student.idArray[0] = 21;
student.idArray[1] = 39;
student.idArray[2] = 69;
for (index = 0; index < student.nameArray.size; index++)
{
iPrintLnBold(student.nameArray[index]);
iPrintLnBold(student.idArray[index]);
}
Output:
bob
21
jill
39
roxanne
69
Note: Structs can have other structs as data members
Code:
student = spawnStruct();
teacher = spawnStruct();
student.name = "bob";
teacher.name = "barker";
teacher.pupil = student;
iPrintLnBold(teacher.name);
iPrintLnBold("the student of " + teacher.name + " is " + teacher.pupil.name);
Output:
the student of barker is bob
Click to Expand
Execution Flow (Control Flow) means to control the flow of code execution. Here are some tools to control the flow of code execution:
Click to Expand
GSC runs once every server frame, and there are 20 server frames per second. Script can not run indefinitely each server frame and still maintain a solid 60FPS, so the wait command is offered to force execution of a given thread to cease for 1 or more frames. The wait command takes a float value as a parameter representing the number of seconds to wait:
wait 0.05; // waits 1/20th of a second, or 1 server frame
wait 0.5; // waits half a second, or 10 server frames
wait 1; // waits 1 second, or 20 server frames
Of special note is the script error "script runtime warning: potential infinite loop in script", which occurs when the game determines that a thread has run for too long during a single thread. This occurs either when script tries to do too many operations all at once, which can be fixed by inserting wait statements to break up the tasks across multiple frames, or when an infinite for or while loop (discussed later) run without hitting a wait statement, and again the solution is to add a wait statement.
Click to Expand
If statements are used to execute statements based on one or more conditions:
if ( a < b ) // if the value of a is less than the value of b
{
c = b - a;
}
else if ( a > b ) // otherwise, if the value of a is greater than the value of b
{
c = a - b;
}
else // otherwise, the value of a must be equal to the value of b
{
c = 0;
}
Here we see set the variable c
to the absolute value of the difference of variables a
and b
. The first expression within the parentheses that evaluates to true will cause the statements in the braces below to be executed, and no further checking of other "else if" statements in the chain will occur. Any number of "else if" statements are optional, as well as the optional trailing else statement. Furthermore the expression in the parentheses can be compounded to test multiple things:
if ( !isDefined( a ) || !isDefined( b ) ) // if either a or b has yet to be declared
{
c = 0;
}
The "||" operator represents the concept of "or". So in the above example if either a
is not defined or b
is not defined, then the statement becomes true. An interesting note is that if a
is not defined, then whether or not b
is defined will not be checked, as the expression is already known to be true. The expression could be equivalently written using the and operator "&&":
// again, if either a or b has yet to be declared, this time using a slightly different logic
if ( !( isDefined( a ) && isDefined( b ) ) )
{
c = 0;
}
All expressions in an "and" operation must be true for the entire expression to be true, thus similarly to the or operator, if a
is determined to not be defined, we will not be checking whether or not b
is defined as the expression is already known to be false.
Click to Expand
The switch statement allows for a more compact script when you want to have multiple execution paths based on numerous possible values for a single variable:
switch ( level.gametype ) // check the value of level.gametype
{
case "sab": // if the value of level.gametype is "sab"
setDvar( "ui_gametype_text", "@MP_SABOTAGE" );
break;
case "sd": // if the value of level.gametype is "sd"
setDvar( "ui_gametype_text", "@MP_SEARCH_AND_DESTROY" );
break;
case "dom": // if the value of level.gametype is "dom"
setDvar( "ui_gametype_text", "@MP_DOMINATION" );
break;
case "war": // if the value of level.gametype is "war"
default: // if the value of level.gametype is none of the values listed above
setDvar( "ui_gametype_text", "@MP_WAR" );
break;
}
Rather than use a long series of if-else-if statements, the switch statement will skip to the case statement whose value matches the value of the variable in parentheses (numerals may also be used as the parameter to the case statements), and if no matching case statement is found, execution skips to "default:". Multiple case statements can be stacked together, as seen above where default and case "war" are together, allowing multiple values to result in the same code execution. It's important to add the break statement after your list of desired statements, otherwise execution would continue on past the next case statement and execute the code there as well.
Click to Expand
The for loop is typically used to perform the same set of statements on a series of items. It takes three expressions as input separated by semicolons (all 3 of which are optional, though the semicolons are not), the initialization expression (to initialize variables as needed), the continuation check expression (an expression which if true will cause the script within the for loop's braces to execute again, otherwise we exit the for loop, and execution will continue on past it), and the post execution step (the opportunity to perform variable incrementing or other such loop maintenance just after the loop has been executed, but just before the continuation check occurs):
// starting off with idx equal to 0, and iterating as long as it is less than the number of elements
// in the weaponslist array, increasing idx by 1 on each iteration
for( idx = 0; idx < weaponsList.size; idx++ )
{
weapon = weaponsList[idx];
if ( weapon == "none" )
continue; // skip the remainder of this for loop, but continue iterating
if ( weapon == "claymore" )
continue; // skip the remainder of this for loop, but continue iterating
if ( weapon == "claymore_detonator" )
continue; // skip the remainder of this for loop, but continue iterating
self switchToWeapon( weapon );
break; // leave the for loop
}
This for loop will start by setting idx to 0 (initialization expression is run), check if that value is less than the size of the weaponsList and if so run the code in the braces (continuation check), then increase the value of idx by 1 and check against the size again (post execution step), repeating this entire process (with the exception of the initializing of idx to 0, which only occurs once) until the continuation check is false.
We also see two additional keywords that may be used in a for loop. The break statement immediately leaves the for loop and execution goes on past it. The continue statement skips the remainder of execution within the loop, moving directly to the post execution step.
Click to Expand
A while loop is functionally very similar to a for loop, in fact a for loop can be made functionally equivalent to a while loop by omitting the initialization and post execution step. A while loop simply tests a single expression and as long as that expression is true, the body of the while loop will execute, and this will repeat until that expression is false:
index = 3;
while ( index ) // as long as index is true, which is as long as it is not zero
{
iprintlnbold( "index is currently " + index ); // prints the given string to the screen
index--; // decrease the value of index by 1
}
iprintlnbold( "done" ); // prints the given string to the screen
Output:
index is currently 3
index is currently 2
index is currently 1
done
Also note that the continue and break statements are valid for the while loop and function the same way as in a for loop.
Click to Expand
A function is a group of code statements lumped together under one name like AddTwoNumbers()
. These are usually created with the idea of doing one task. Functions are first defined, which means writing code that tells the function what to do. Next the function is called.
When functions are defined, you can give it input variables also known as parameters/arguments.
Here is an Example of a Function:
init() //entry point for program
{
wait 5; //wait 5 seconds
firstInt = 2;
secondInt = 3;
someNumber = AddTwoNumbers(firstInt, secondInt); //AddTwoNumbers() is called and returns the sum of the two numbers
iPrintLnBold(someNumber);
}
AddTwoNumbers( number1, number2) //function definition
{
return number1 + number2;
}
Output:
5
Let us look at AddTwoNumbers()
definition. This function returns a value, the sum of number1
and number2
variables.
Going to the init()
function, we first create two variables: firstInt
(which has the value 2) and secondInt
(value of 3). Next we have the variable someNumber
which is given a value: AddTwoNumbers(firstInt, secondInt)
. What is happening here is that firstInt
and secondInt
are being passed into AddTwoNumbers()
and the result (in this case was 5) is stored in the variable someNumber
.
Note: You can make functions with optional inputs (parameters)
init()
{
wait 5;
firstInt = 2;
secondInt = 3;
someNumber = AddTwoOrThreeNumbers(firstInt, secondInt); //here i called the function omitting the last parameter
iPrintLnBold(someNumber);
}
AddTwoOrThreeNumbers( number1, number2, number3)
{
opt_number3 = 0; //default value used if number 3 does not exist
if (isDefined(number3))
{
opt_number3 = number3;
}
return number1 + number2 + opt_number3;
}
Output:
5
First, let us look at the definition of AddTwoOrThreeNumbers()
. We created a variable called opt_number3
. This variable is used to store the default value in the case that the optional paramter number3
is not defined. The next line we test if number3
is defined. If number3
is defined (passed to the function) then its value will be copied into opt_number3
. If number3
is not defined (not passed to the function), then opt_number3
will still have the default value of 0. Lastly we return the sum of number1
,number2
, and opt_number3
.
Inside init() we define two variables: firstInt
and secondInt
. Next we pass firstInt
and secondInt
into AddTwoOrThreeNumbers()
, omitting (not including) a third parameter as it is optional. AddTwoOrThreeNumbers()
returns the sum of these numbers and that value is stored into the variable someNumber
. We then print to the screen the value of someNumber
.
Note: You cannot omit( not include) a parameter that is sandwiched between two included parameters
init()
{
wait 5;
firstInt = 2;
secondInt = 3;
someNumber = AddTwoOrThreeNumbers(firstInt, ,secondInt); //This causes a compile error, you cannot omit the second parameter here
iPrintLnBold(someNumber);
}
AddTwoOrThreeNumbers( number1, number2, number3) //the second parameter is required here
{
opt_number3 = 0;
if (isDefined(number3))
{
opt_number3 = number3;
}
return number1 + number2 + opt_number3;
}
Calling a Function
When a function is called, the code within that function will run, and the rest of the program will have to wait until the function is finished to continue.
Threading a Function
When a function is threaded, this means that the function will continue to run in the background and the program may still continue. You will learn more about threads later in this guide.
Example
function(); //the script will stop at this line and carry out function() before going down to...
thread function2(); //this will start function2() and carry on to execute 'ent moveZ'
function3();
Basically function()
when its called will run all its code, then function2
will be threaded and then function3`` can be run now without waiting for
function2``` to complete.
Click to Expand
Threads allow multiple paths of execution to run in the same server frame, and they can wait until a certain event occurs, do some processing, and then either end themselves, or wait again for the next event they care about. They are very simple to create, you simply define a function as you normally would, and then launch the thread using the name of that function:
// declare the function foo that is intended for use as a thread
foo()
{
for ( ; ; ) // this for loop continues forever
{
do_stuff();
wait 5;
}
}
thread foo(); // start up a thread, using the function foo as the basis of the thread
In this example the thread foo will call the do_stuff() function every 5 seconds for the remainder of the level.
Click to Expand
When a thread is run on a particular variable, say the player, a trigger, or perhaps a struct, that variable can be referenced from within the thread using the self variable:
// declare the function foo_self that is intended for use as a thread that runs on a specific object
foo_self()
{
for ( ; ; ) // this for loop continues forever
{
self moveto(self.origin + (0, 0, 5), .05); // self is the object the thread is run on
wait 0.05;
}
}
// start up a thread, using the function foo_self as the basis of the thread, and run it on elevator
elevator thread foo_self();
Here, we have run the foo_self() thread on an entity named elevator, and the thread causes it to smoothly rise 5 units every server frame by referencing it through the self variable
Click to Expand
Notifies
Notifies are basically signals sent onto a variable. They are used to trigger code to run. Notifies take a string as their parameter.
Waittills
Waittills basically force the code run in a function to stop and wait till a certain notify is sent to the variable we are using.
EndOns
Endon is a way to end a thread or a level upon receiving a certain notify.
Example of Notifies, Waittills, and endOns:
init()
{
wait 5;
level thread sayLetsGooo();
level countToThree();
}
countToThree()
{
self endon("end_script");
var_int = 0;
for (;;)
{
if ( var_int == 3)
{
iPrintLnBold("finishing script");
self notify("end_script");
}
var_int = var_int + 1;
iPrintLnBold(var_int);
wait 1;
}
}
sayLetsGooo()
{
self waittill("end_script");
iPrintLnBold("Lets GOOO");
}
Output:
1
2
3
finishing script
Lets GOOO
Explanation:
-
First
init()
is ran first, all scripts need aninit()
function to run. Inside this function, we thread thesayLetsGooo()
function. -
sayLetsGooo()
function waits till the"end_script"
notify is sent to the level. -
in
init()
we call the functioncountToThree()
on the level. Inside the function, we say that the function ** ends** when the"end_script"
notify is sent on the level. We then loop and print to the screen the value ofvar_int
until we have counted to 3. Whenvar_int == 3
then the notify"end_script"
is sent onself
which is the level here. Since the level has received the notify"end_script"
, nowcountToThree()
will end, meaning the function stopped running and now we are done with it. -
Next, since the
"end_script"
notify is sent to the level,sayLetsGooo()
function stops waiting and continues. The function then prints to the screenLets GOOO
.
Summary: Notify is sent onto a variable (like an entity, player, level, etc). Waittill is used to pause a function and wait until the notify is sent to that variable. endOn is used to end a threaded function or level when the variable receives the notify.
Click to Expand
To show a message on the screen for a player or everyone, there are two main ways to do this:
Using iPrintLn()
and iPrintLnBold()
functions.
iPrintLn()
gives a message on the lower left part of the screen and it is quite small.
iPrintLnBold()
gives a big message at the top center of the screen.
You can call either of these functions on a single player:
player iPrintLnBold(stringMessage);
where player
is a specific player, stringMessage
is a variable that contains some message.
Or you can not do that:
iPrintLnBold(stringMessage);
When you don't call the function on a certain player, it will show the message to everyone.
Another useful thing is that you can print variables values on screen:
var_int = 1;
iPrintLnBold(var_int);
Output:
1
You can also make it so your variable is part of your message:
var_int = 1;
iPrintLnBold("the variable var_int has a value of " + var_int + " and that's awesome!);
Output:
the variable var_int has a value of 1 and that's awesome!