GSC Basics - AkbarHashimi/BO2-Plutonium-Modding-Guide GitHub Wiki

Topics:

Variables

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;

go to the top

Entities

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.

Acquiring Entities

Click to Expand

There are many ways to get a reference to an entity.

  1. 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().

  1. 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.

Entities and member variables

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;
}

Entities and functions

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.

Entities and threads

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();

go to the top

Operators

Click to Expand

Operators are symbols used to manipulate (do an operation on) data.

Math Operators

+ addition
- subtraction
* multiplication
/ division
% modulus

Bitwise Operators (Advanced Stuff)

<< 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

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

Boolean Operators

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

go to the top

Arrays

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";

go to the top

Comments

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?
*/

go to the top

Structs

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

go to the top

Execution Flow

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:

wait statement

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.

If statements

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.

Switch

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.

for loop

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.

while loop

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.

go to the top

Functions

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;

}

Functions can be Threaded or Called


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.

go to the top

Threads

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.

self

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

go to the top

notifies, waittills, endOns

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:

  1. First init() is ran first, all scripts need an init() function to run. Inside this function, we thread thesayLetsGooo()function.

  2. sayLetsGooo() function waits till the "end_script" notify is sent to the level.

  3. in init() we call the function countToThree() 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 of var_int until we have counted to 3. When var_int == 3 then the notify "end_script" is sent on self which is the level here. Since the level has received the notify "end_script", now countToThree() will end, meaning the function stopped running and now we are done with it.

  4. Next, since the "end_script" notify is sent to the level, sayLetsGooo() function stops waiting and continues. The function then prints to the screen Lets 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.

go to the top

Output

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!

⚠️ **GitHub.com Fallback** ⚠️